diff --git a/.github/actions/setup-ios-runtime/action.yml b/.github/actions/setup-ios-runtime/action.yml index 2aa2f0454..89164b238 100644 --- a/.github/actions/setup-ios-runtime/action.yml +++ b/.github/actions/setup-ios-runtime/action.yml @@ -3,18 +3,13 @@ description: 'Download and Install requested iOS Runtime' runs: using: "composite" steps: - - name: Cache iOS Simulator Runtime - uses: actions/cache@v4 - id: runtime-cache - with: - path: ./*.dmg - key: ipsw-runtime-ios-${{ inputs.version }} - restore-keys: ipsw-runtime-ios-${{ inputs.version }} - name: Setup iOS Simulator Runtime shell: bash run: | + sudo rm -rfv ~/Library/Developer/CoreSimulator/* || true brew install blacktop/tap/ipsw bundle exec fastlane install_runtime ios:${{ inputs.version }} + sudo rm -rfv *.dmg || true xcrun simctl list runtimes - name: Create Custom iOS Simulator shell: bash diff --git a/.github/workflows/cron-checks.yml b/.github/workflows/cron-checks.yml index e84661c09..3fd9ceb10 100644 --- a/.github/workflows/cron-checks.yml +++ b/.github/workflows/cron-checks.yml @@ -2,8 +2,9 @@ name: Cron Checks on: schedule: - # Runs "At 01:00 every night" - - cron: '0 1 * * *' + # Runs "At 01:00 every night except weekends" + - cron: '0 1 * * 1-5' + workflow_dispatch: concurrency: @@ -12,6 +13,7 @@ concurrency: env: HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: build-test-app-and-frameworks: @@ -58,7 +60,6 @@ jobs: runs-on: ${{ matrix.os }} env: GITHUB_EVENT: ${{ toJson(github.event) }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }} XCODE_VERSION: ${{ matrix.xcode }} IOS_SIMULATOR_DEVICE: "${{ matrix.device }} (${{ matrix.ios }})" # For the Allure report @@ -147,7 +148,6 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} XCODE_VERSION: ${{ matrix.xcode }} STREAM_VIDEO_SECRET: ${{ secrets.STREAM_VIDEO_SECRET }} steps: @@ -213,6 +213,10 @@ jobs: env: XCODE_VERSION: "15.0.1" steps: + - name: Connect Bot + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} - uses: actions/checkout@v3.1.0 - uses: ./.github/actions/ruby-cache - name: List Xcode versions @@ -225,10 +229,6 @@ jobs: - name: Build UIKit run: bundle exec fastlane test_uikit device:"iPhone 15" build_for_testing:true timeout-minutes: 25 - - name: Install Bot SSH Key - uses: webfactory/ssh-agent@v0.7.0 - with: - ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} - name: Build XCFrameworks run: bundle exec fastlane build_xcframeworks timeout-minutes: 40 diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml deleted file mode 100644 index 879681950..000000000 --- a/.github/workflows/publish-release.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: "Publish new release" - -on: - pull_request: - branches: - - main - types: - - closed - -jobs: - release: - name: Publish new release - runs-on: macos-13 - if: github.event.pull_request.merged == true # only merged pull requests must trigger this job - steps: - - name: Install Bot SSH Key - uses: webfactory/ssh-agent@v0.7.0 - with: - ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} - - uses: actions/checkout@v4.1.1 - - name: Extract version from branch name (for release branches) - if: startsWith(github.event.pull_request.head.ref, 'release/') - run: | - BRANCH_NAME="${{ github.event.pull_request.head.ref }}" - VERSION=${BRANCH_NAME#release/} - echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - - uses: ./.github/actions/ruby-cache - - name: "Fastlane - Publish Release" - if: startsWith(github.event.pull_request.head.ref, 'release/') - env: - GITHUB_TOKEN: ${{ secrets.CI_BOT_GITHUB_TOKEN }} - COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} - MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} - run: bundle exec fastlane publish_release version:${{ env.RELEASE_VERSION }} --verbose diff --git a/.github/workflows/release-merge.yml b/.github/workflows/release-merge.yml new file mode 100644 index 000000000..c8c29253c --- /dev/null +++ b/.github/workflows/release-merge.yml @@ -0,0 +1,31 @@ +name: "Merge release" + +on: + issue_comment: + types: [created] + + workflow_dispatch: + +jobs: + merge-comment: + name: Merge release to main + runs-on: macos-14 + if: github.event_name == 'workflow_dispatch' || (github.event.issue.pull_request && github.event.issue.state == 'open' && github.event.comment.body == '/merge release') + steps: + - name: Connect Bot + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} + + - uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 + + - uses: ./.github/actions/ruby-cache + + - name: Merge + run: bundle exec fastlane merge_release author:"$USER_LOGIN" --verbose + env: + GITHUB_TOKEN: ${{ secrets.ADMIN_API_TOKEN }} # A token with the "admin:org" scope to get the list of the team members on GitHub + GITHUB_PR_NUM: ${{ github.event.issue.number }} + USER_LOGIN: ${{ github.event.comment.user.login != null && github.event.comment.user.login || github.event.sender.login }} diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 000000000..b5986f7b7 --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,30 @@ +name: "Publish new release" + +on: + push: + branches: + - main + + workflow_dispatch: + +jobs: + release: + name: Publish new release + runs-on: macos-13 + steps: + - name: Connect Bot + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} + + - uses: actions/checkout@v4.1.1 + + - uses: ./.github/actions/ruby-cache + + - name: "Fastlane - Publish Release" + env: + GITHUB_TOKEN: ${{ secrets.CI_BOT_GITHUB_TOKEN }} + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} + run: bundle exec fastlane publish_release --verbose diff --git a/.github/workflows/start-new-release.yml b/.github/workflows/release-start.yml similarity index 96% rename from .github/workflows/start-new-release.yml rename to .github/workflows/release-start.yml index 1f434bee8..6900e055c 100644 --- a/.github/workflows/start-new-release.yml +++ b/.github/workflows/release-start.yml @@ -13,15 +13,19 @@ jobs: name: Start new release runs-on: macos-14 steps: - - name: Install Bot SSH Key + - name: Connect Bot uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} + - uses: actions/checkout@v4.1.1 with: fetch-depth: 0 # to fetch git tags + - uses: ./.github/actions/ruby-cache + - uses: ./.github/actions/xcode-cache + - name: Create Release PR run: bundle exec fastlane release version:"${{ github.event.inputs.version }}" --verbose env: diff --git a/.github/workflows/sdk-size-metrics.yml b/.github/workflows/sdk-size-metrics.yml new file mode 100644 index 000000000..b2245193b --- /dev/null +++ b/.github/workflows/sdk-size-metrics.yml @@ -0,0 +1,37 @@ +name: SDK Size + +on: + pull_request: + + workflow_dispatch: + + push: + branches: + - develop + +env: + HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI + +jobs: + sdk_size: + name: Metrics + runs-on: macos-14 + env: + GITHUB_TOKEN: '${{ secrets.CI_BOT_GITHUB_TOKEN }}' + steps: + - name: Connect Bot + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} + + - uses: actions/checkout@v3.1.0 + + - uses: ./.github/actions/bootstrap + + - name: Run SDK Size Metrics + run: bundle exec fastlane show_frameworks_sizes + timeout-minutes: 30 + env: + GITHUB_PR_NUM: ${{ github.event.pull_request.number }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} diff --git a/.github/workflows/smoke-checks.yml b/.github/workflows/smoke-checks.yml index 71cb8b5c8..af34b7bb1 100644 --- a/.github/workflows/smoke-checks.yml +++ b/.github/workflows/smoke-checks.yml @@ -29,6 +29,8 @@ concurrency: env: HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI IOS_SIMULATOR_DEVICE: "iPhone 15 Pro (17.4)" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_NUM: ${{ github.event.pull_request.number }} jobs: test-llc-debug: @@ -36,8 +38,6 @@ jobs: runs-on: macos-14 if: ${{ github.event.inputs.swiftui_snapshots != 'true' && github.event.inputs.uikit_snapshots != 'true' }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_NUM: ${{ github.event.number }} STREAM_VIDEO_SECRET: ${{ secrets.STREAM_VIDEO_SECRET }} steps: - uses: actions/checkout@v4.1.1 @@ -53,13 +53,9 @@ jobs: env: XCODE_VERSION: "15.2" # the most stable pair of Xcode IOS_SIMULATOR_DEVICE: "iPhone 15 Pro (17.2)" # and iOS - - name: Get branch name - id: get_branch_name - run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT - name: Run Sonar analysis run: bundle exec fastlane sonar_upload env: - BRANCH_NAME: ${{ steps.get_branch_name.outputs.branch }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - uses: actions/upload-artifact@v4 @@ -97,7 +93,6 @@ jobs: if: ${{ github.event_name != 'push' && github.event.inputs.swiftui_snapshots != 'false' }} env: GITHUB_TOKEN: ${{ secrets.CI_BOT_GITHUB_TOKEN }} # to open a PR - GITHUB_PR_NUM: ${{ github.event.number }} steps: - uses: actions/checkout@v4.1.1 - uses: ./.github/actions/bootstrap @@ -129,7 +124,6 @@ jobs: if: ${{ github.event_name != 'push' && github.event.inputs.uikit_snapshots != 'false' }} env: GITHUB_TOKEN: ${{ secrets.CI_BOT_GITHUB_TOKEN }} # to open a PR - GITHUB_PR_NUM: ${{ github.event.number }} steps: - uses: actions/checkout@v4.1.1 - uses: ./.github/actions/bootstrap @@ -161,8 +155,6 @@ jobs: runs-on: macos-13 env: XCODE_VERSION: "15.0.1" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_NUM: ${{ github.event.number }} if: ${{ github.event_name != 'push' && github.event.inputs.swiftui_snapshots != 'true' && github.event.inputs.uikit_snapshots != 'true' }} steps: - uses: actions/checkout@v4.1.1 @@ -186,9 +178,11 @@ jobs: if: ${{ github.event_name != 'push' && github.event.inputs.snapshots != 'true' }} env: XCODE_VERSION: "15.0.1" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_NUM: ${{ github.event.number }} steps: + - name: Connect Bot + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} - uses: actions/checkout@v3.1.0 - uses: ./.github/actions/ruby-cache - name: List Xcode versions @@ -201,10 +195,6 @@ jobs: - name: Build UIKit run: bundle exec fastlane test_uikit device:"iPhone 15" build_for_testing:true timeout-minutes: 25 - - name: Install Bot SSH Key - uses: webfactory/ssh-agent@v0.7.0 - with: - ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} - name: Build XCFrameworks run: bundle exec fastlane build_xcframeworks timeout-minutes: 40 @@ -238,9 +228,6 @@ jobs: runs-on: macos-14 needs: build-test-app-and-frameworks if: ${{ github.event_name != 'push' && github.event.inputs.swiftui_snapshots != 'true' && github.event.inputs.uikit_snapshots != 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_NUM: ${{ github.event.number }} steps: - uses: actions/checkout@v4.1.1 - uses: actions/download-artifact@v4 @@ -270,7 +257,6 @@ jobs: run: bundle exec fastlane allure_launch env: ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_EVENT: ${{ toJson(github.event) }} - id: get_launch_id run: echo "launch_id=${{env.LAUNCH_ID}}" >> $GITHUB_OUTPUT @@ -285,8 +271,6 @@ jobs: - build-test-app-and-frameworks env: LAUNCH_ID: ${{ needs.allure_testops_launch.outputs.launch_id }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_NUM: ${{ github.event.number }} ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }} strategy: matrix: diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 820f0b501..951a73a97 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -1,10 +1,9 @@ name: Test Flight Deploy DemoApp on: - # TODO: commented until `develop` branch is in place - # pull_request: - # branches: - # - 'main' + pull_request: + branches: + - 'main' release: types: [published] @@ -32,7 +31,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PR_NUM: ${{ github.event.number }} steps: - - name: Install Bot SSH Key + - name: Connect Bot uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore index 00577aa41..60500fe5e 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ fastlane/screenshots fastlane/test_output fastlane/allurectl fastlane/xcresults +**/metrics/ recordings *.coverage.txt vendor/bundle/ @@ -92,6 +93,8 @@ derived_data/ spm_cache/ .buildcache buildcache +App Thinning Size Report.txt +app-thinning.plist *.dmg # Stream Video Buddy diff --git a/CHANGELOG.md b/CHANGELOG.md index f87dfe5ed..6b02488e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### 🔄 Changed +# [1.10.0](https://github.com/GetStream/stream-video-swift/releases/tag/1.10.0) +_August 29, 2024_ + +### ✅ Added +- Participants (regular and anonymous) count, can be accessed - before or after joining a call - from the `Call.state.participantCount` & `Call.state.anonymousParticipantCount` respectively. [#496](https://github.com/GetStream/stream-video-swift/pull/496) +- You can now provide the `CallSettings` when you start a ringing call [#497](https://github.com/GetStream/stream-video-swift/pull/497) + # [1.0.9](https://github.com/GetStream/stream-video-swift/releases/tag/1.0.9) _July 19, 2024_ diff --git a/DemoApp/Sources/Models/CallKitState.swift b/DemoApp/Sources/Models/CallKitState.swift deleted file mode 100644 index 5e5f0a140..000000000 --- a/DemoApp/Sources/Models/CallKitState.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// Copyright © 2024 Stream.io Inc. All rights reserved. -// - -import Foundation - -enum CallKitState { - case idle - case joining - case inCall -} diff --git a/DemoApp/Sources/Views/Login/DebugMenu.swift b/DemoApp/Sources/Views/Login/DebugMenu.swift index 248ff1313..bf9232bde 100644 --- a/DemoApp/Sources/Views/Login/DebugMenu.swift +++ b/DemoApp/Sources/Views/Login/DebugMenu.swift @@ -129,7 +129,7 @@ struct DebugMenu: View { currentValue: callExpiration, additionalItems: { customCallExpirationView }, label: "Call Expiration" - ) { _ in self.callExpiration = .custom(10) } + ) { self.callExpiration = $0 } makeMenu( for: [.enabled, .disabled], diff --git a/DocumentationTests/DocumentationTests/DocumentationTests/06-advanced/03-callkit-integration.swift b/DocumentationTests/DocumentationTests/DocumentationTests/06-advanced/03-callkit-integration.swift index 8a8ac510e..319bf4c94 100644 --- a/DocumentationTests/DocumentationTests/DocumentationTests/06-advanced/03-callkit-integration.swift +++ b/DocumentationTests/DocumentationTests/DocumentationTests/06-advanced/03-callkit-integration.swift @@ -139,6 +139,12 @@ fileprivate func content() { } } + container { + @Injected(\.callKitAdapter) var callKitAdapter + + callKitAdapter.callSettings = CallSettings(audioOn: true, videoOn: false) + } + container { @Injected(\.callKitService) var callKitService diff --git a/Gemfile.lock b/Gemfile.lock index cfc99e2d3..9cdb7e098 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM base64 nkf rexml - activesupport (7.1.3.3) + activesupport (7.1.3.4) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -15,8 +15,8 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) @@ -24,20 +24,20 @@ GEM ast (2.4.2) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.937.0) - aws-sdk-core (3.196.1) + aws-partitions (1.962.0) + aws-sdk-core (3.201.3) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.82.0) - aws-sdk-core (~> 3, >= 3.193.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.151.0) - aws-sdk-core (~> 3, >= 3.194.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.157.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) badge (0.13.0) @@ -94,11 +94,11 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.3.1) + concurrent-ruby (1.3.3) connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) - danger (9.4.3) + danger (9.5.0) claide (~> 1.0) claide-plugins (>= 0.9.2) colored2 (~> 3.1) @@ -108,7 +108,6 @@ GEM git (~> 1.13) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.0) - no_proxy_fix octokit (>= 4.0) terminal-table (>= 1, < 4) danger-commit_lint (0.0.7) @@ -125,7 +124,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.110.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -149,7 +148,7 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) @@ -157,7 +156,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.220.0) + fastlane (2.222.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -203,10 +202,10 @@ GEM bundler fastlane pry - fastlane-plugin-stream_actions (0.3.38) + fastlane-plugin-stream_actions (0.3.63) xctest_list (= 1.2.1) fastlane-plugin-versioning (0.5.2) - ffi (1.16.3) + ffi (1.17.0) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -229,7 +228,7 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.7.0) + google-cloud-core (1.7.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) @@ -250,24 +249,24 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) i18n (1.14.5) concurrent-ruby (~> 1.0) - jazzy (0.15.0) + jazzy (0.15.1) cocoapods (~> 1.5) mustache (~> 1.1) open4 (~> 1.3) redcarpet (~> 3.4) - rexml (~> 3.2) + rexml (>= 3.2.7, < 4.0) rouge (>= 2.0.6, < 5.0) sassc (~> 2.1) sqlite3 (~> 1.3) xcinvoke (~> 0.3.0) jmespath (1.6.2) json (2.7.2) - jwt (2.8.1) + jwt (2.8.2) base64 kramdown (2.4.0) rexml @@ -275,15 +274,15 @@ GEM kramdown (~> 2.0) liferaft (0.0.6) method_source (1.1.0) - mini_magick (4.12.0) + mini_magick (4.13.2) mini_mime (1.1.5) - mini_portile2 (2.8.6) - minitest (5.23.1) + mini_portile2 (2.8.7) + minitest (5.24.1) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.4.1) mustache (1.1.1) - mustermann (3.0.0) + mustermann (3.0.1) ruby2_keywords (~> 0.0.1) mutex_m (0.2.0) nanaimo (0.3.0) @@ -292,19 +291,17 @@ GEM netrc (0.11.0) nio4r (2.7.3) nkf (0.2.0) - no_proxy_fix (0.1.2) - nokogiri (1.16.5) + nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) - octokit (8.1.0) - base64 + octokit (9.1.0) faraday (>= 1, < 3) sawyer (~> 0.9) open4 (1.3.4) optparse (0.5.0) os (1.1.4) - parallel (1.24.0) - parser (3.3.2.0) + parallel (1.25.1) + parser (3.3.4.0) ast (~> 2.4.1) racc plist (3.7.1) @@ -314,8 +311,8 @@ GEM public_suffix (4.0.7) puma (6.4.2) nio4r (~> 2.0) - racc (1.8.0) - rack (3.0.11) + racc (1.8.1) + rack (3.1.7) rack-protection (4.0.0) base64 (>= 0.1.0) rack (>= 3.0.0, < 4) @@ -334,8 +331,8 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.8) - strscan (>= 3.0.9) + rexml (3.2.9) + strscan rouge (2.0.7) rubocop (1.38.0) json (~> 2.3) @@ -347,7 +344,7 @@ GEM rubocop-ast (>= 1.23.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.31.3) + rubocop-ast (1.32.0) parser (>= 3.3.1.0) rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) @@ -378,7 +375,7 @@ GEM rack-protection (= 4.0.0) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - slather (2.8.0) + slather (2.8.3) CFPropertyList (>= 2.2, < 4) activesupport clamp (~> 1.3) @@ -390,7 +387,7 @@ GEM terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - tilt (2.3.0) + tilt (2.4.0) trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.2) @@ -433,7 +430,7 @@ DEPENDENCIES fastlane fastlane-plugin-create_xcframework fastlane-plugin-lizard - fastlane-plugin-stream_actions (= 0.3.38) + fastlane-plugin-stream_actions (= 0.3.63) fastlane-plugin-versioning jazzy json diff --git a/README.md b/README.md index cfeac14eb..76d4d7659 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,10 @@

- StreamVideo - StreamVideoSwiftUI + StreamVideo + StreamVideoSwiftUI + StreamVideoUIKit + StreamWebRTC

![Stream Video for iOS Header image](https://github.com/GetStream/stream-video-swift/assets/12433593/e4a44ae5-a8eb-4ac7-8910-28187aa011f6) diff --git a/Sources/StreamVideo/Call.swift b/Sources/StreamVideo/Call.swift index 54c1e12be..063cd31f8 100644 --- a/Sources/StreamVideo/Call.swift +++ b/Sources/StreamVideo/Call.swift @@ -43,7 +43,8 @@ public class Call: @unchecked Sendable, WSEventsSubscriber { callType: String, callId: String, coordinatorClient: DefaultAPI, - callController: CallController + callController: CallController, + callSettings: CallSettings? = nil ) { self.callId = callId self.callType = callType @@ -51,18 +52,26 @@ public class Call: @unchecked Sendable, WSEventsSubscriber { self.callController = callController microphone = MicrophoneManager( callController: callController, - initialStatus: .enabled + initialStatus: callSettings?.audioOn == false ? .disabled : .enabled ) camera = CameraManager( callController: callController, - initialStatus: .enabled, + initialStatus: callSettings?.videoOn == false ? .disabled : .enabled, initialDirection: .front ) speaker = SpeakerManager( callController: callController, - initialSpeakerStatus: .enabled, - initialAudioOutputStatus: .enabled + initialSpeakerStatus: callSettings?.speakerOn == false ? .disabled : .enabled, + initialAudioOutputStatus: callSettings?.audioOutputOn == false ? .disabled : .enabled ) + + /// If we received a non-nil initial callSettings, we updated them here. + if let callSettings { + Task { @MainActor [weak self] in + self?.state.update(callSettings: callSettings) + } + } + self.callController.call = self // It's important to instantiate the stateMachine as soon as possible // to ensure it's uniqueness. diff --git a/Sources/StreamVideo/CallKit/CallKitAdapter.swift b/Sources/StreamVideo/CallKit/CallKitAdapter.swift index ac6ee23df..6e9137f45 100644 --- a/Sources/StreamVideo/CallKit/CallKitAdapter.swift +++ b/Sources/StreamVideo/CallKit/CallKitAdapter.swift @@ -20,6 +20,12 @@ open class CallKitAdapter { set { callKitService.iconTemplateImageData = newValue } } + /// The callSettings to use when joining a call (after accepting it on CallKit) + /// default: nil + open var callSettings: CallSettings? { + didSet { callKitService.callSettings = callSettings } + } + /// The currently active StreamVideo client. /// - Important: We need to update it whenever a user logins. public var streamVideo: StreamVideo? { diff --git a/Sources/StreamVideo/CallKit/CallKitService.swift b/Sources/StreamVideo/CallKit/CallKitService.swift index 0c6d7ca49..a7978da6a 100644 --- a/Sources/StreamVideo/CallKit/CallKitService.swift +++ b/Sources/StreamVideo/CallKit/CallKitService.swift @@ -58,6 +58,8 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable { /// - Note: defaults to `false`. open var supportsVideo: Bool = false + var callSettings: CallSettings? + /// The call controller used for managing calls. open internal(set) lazy var callController = CXCallController() /// The call provider responsible for handling call-related actions. @@ -307,7 +309,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable { } do { - try await callToJoinEntry.call.join() + try await callToJoinEntry.call.join(callSettings: callSettings) action.fulfill() } catch { callToJoinEntry.call.leave() diff --git a/Sources/StreamVideo/CallState.swift b/Sources/StreamVideo/CallState.swift index 9ba5f4f36..3b6797aea 100644 --- a/Sources/StreamVideo/CallState.swift +++ b/Sources/StreamVideo/CallState.swift @@ -117,6 +117,7 @@ public class CallState: ObservableObject { } @Published public internal(set) var reconnectionStatus = ReconnectionStatus.connected + @Published public internal(set) var anonymousParticipantCount: UInt32 = 0 @Published public internal(set) var participantCount: UInt32 = 0 @Published public internal(set) var isInitialized: Bool = false @Published public internal(set) var callSettings = CallSettings() @@ -207,14 +208,12 @@ public class CallState: ObservableObject { case .typeHealthCheckEvent: // note: health checks are not relevant for call state sync'ing break - case .typeCallUserMuted: - break case .typeCallDeletedEvent: break case .typeCallHLSBroadcastingFailedEvent: break case .typeCallRecordingFailedEvent: - break + recordingState = .noRecording case .typeCallRecordingReadyEvent: break case .typeClosedCaptionEvent: @@ -227,6 +226,18 @@ public class CallState: ObservableObject { transcribing = true case .typeCallTranscriptionStoppedEvent: transcribing = false + case .typeCallMissedEvent: + break + case .typeCallRtmpBroadcastStartedEvent: + break + case .typeCallRtmpBroadcastStoppedEvent: + break + case .typeCallUserMutedEvent: + break + case .typeCallRtmpBroadcastFailedEvent: + break + case .typeCallSessionParticipantCountsUpdatedEvent: + break } } @@ -421,10 +432,17 @@ public class CallState: ObservableObject { } private func didUpdate(_ session: CallSessionResponse?) { - if startedAt != session?.liveStartedAt { - startedAt = session?.liveStartedAt + guard let session else { return } + if let startedAt = session.startedAt { + self.startedAt = startedAt + } else if let liveStartedAt = session.liveStartedAt { + startedAt = liveStartedAt + } else if startedAt == nil { + /// If we don't receive a value from the SFU we start the timer on the current date. + startedAt = Date() } - if session?.liveEndedAt != nil { + + if session.liveEndedAt != nil { resetTimer() } } diff --git a/Sources/StreamVideo/Controllers/CallController.swift b/Sources/StreamVideo/Controllers/CallController.swift index 57f509c08..5ece590fc 100644 --- a/Sources/StreamVideo/Controllers/CallController.swift +++ b/Sources/StreamVideo/Controllers/CallController.swift @@ -13,10 +13,20 @@ class CallController: @unchecked Sendable { didSet { handleParticipantsUpdated() handleParticipantCountUpdated() + handleAnonymousParticipantCountUpdated() + } + } + + weak var call: Call? { + didSet { + participantsCountUpdatesTask?.cancel() + participantsCountUpdatesTask = nil + if let call { + participantsCountUpdatesTask = subscribeToParticipantsCountUpdatesEvent(call) + } } } - weak var call: Call? private let user: User private let callId: String private let callType: String @@ -30,7 +40,8 @@ class CallController: @unchecked Sendable { private var currentSFU: String? private var statsInterval: TimeInterval = 5 private var statsCancellable: AnyCancellable? - + private var participantsCountUpdatesTask: Task? + init( defaultAPI: DefaultAPI, user: User, @@ -478,7 +489,15 @@ class CallController: @unchecked Sendable { } } } - + + private func handleAnonymousParticipantCountUpdated() { + webRTCClient?.onAnonymousParticipantCountUpdated = { [weak self] participantCount in + Task { @MainActor [weak self] in + self?.call?.state.anonymousParticipantCount = participantCount + } + } + } + private func handleSignalChannelConnectionStateChange(_ state: WebSocketConnectionState) { switch state { case let .disconnected(source): @@ -487,6 +506,10 @@ class CallController: @unchecked Sendable { self?.handleSignalChannelDisconnect(source: source) } case .connected(healthCheckInfo: _): + /// Once connected we should stop listening for CallSessionParticipantCountsUpdatedEvent + /// updates and only rely on the healthCheck event. + participantsCountUpdatesTask?.cancel() + participantsCountUpdatesTask = nil log.debug("Signal channel connected") if reconnectionDate != nil { reconnectionDate = nil @@ -691,6 +714,31 @@ class CallController: @unchecked Sendable { } } } + + private func subscribeToParticipantsCountUpdatesEvent(_ call: Call) -> Task { + Task { + let anonymousUserRoleKey = "anonymous" + for await event in call.subscribe(for: CallSessionParticipantCountsUpdatedEvent.self) { + Task { @MainActor in + call.state.participantCount = event + .participantsCountByRole + .filter { $0.key != anonymousUserRoleKey } // TODO: Workaround. To be removed + .values + .map(UInt32.init) + .reduce(0) { $0 + $1 } + + // TODO: Workaround. To be removed + if event.anonymousParticipantCount > 0 { + call.state.anonymousParticipantCount = UInt32(event.anonymousParticipantCount) + } else if let anonymousCount = event.participantsCountByRole[anonymousUserRoleKey] { + call.state.anonymousParticipantCount = UInt32(anonymousCount) + } else { + call.state.anonymousParticipantCount = 0 + } + } + } + } + } } extension CallController { diff --git a/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift b/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift index 97cd5ac23..3b1904962 100644 --- a/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift +++ b/Sources/StreamVideo/Generated/SystemEnvironment+Version.swift @@ -7,7 +7,7 @@ import Foundation extension SystemEnvironment { /// A Stream Video version. - public static let version: String = "1.0.9" + public static let version: String = "1.10.0" /// The WebRTC version. public static let webRTCVersion: String = "114.5735.8" } diff --git a/Sources/StreamVideo/Info.plist b/Sources/StreamVideo/Info.plist index b1d7e4550..2a68649f6 100644 --- a/Sources/StreamVideo/Info.plist +++ b/Sources/StreamVideo/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0.9 + 1.10.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift b/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift index 0c2208c27..b5331d9db 100644 --- a/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift +++ b/Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift @@ -157,12 +157,11 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { return r } - /** Accept Call - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: AcceptCallResponse */ @@ -186,19 +185,19 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Accept Call - POST /video/call/{type}/{id}/accept - - Sends events: - call.accepted Required permissions: - JoinCall - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Sends events: - call.accepted Required permissions: - JoinCall + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** Block user on a call - - parameter type: (path) - - parameter id: (path) - - parameter blockUserRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter blockUserRequest: (body) - returns: BlockUserResponse */ @@ -223,21 +222,21 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Block user on a call - POST /video/call/{type}/{id}/block - - Block a user, preventing them from joining the call until they are unblocked. Sends events: - call.blocked_user Required permissions: - BlockUser - - parameter type: (path) - - parameter id: (path) - - parameter blockUserRequest: (body) - - returns: RequestBuilder + - Block a user, preventing them from joining the call until they are unblocked. Sends events: - call.blocked_user Required permissions: - BlockUser + - parameter type: (path) + - parameter id: (path) + - parameter blockUserRequest: (body) + - returns: RequestBuilder */ /** Collect user feedback - - parameter type: (path) - - parameter id: (path) - - parameter session: (path) - - parameter collectUserFeedbackRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) + - parameter collectUserFeedbackRequest: (body) - returns: CollectUserFeedbackResponse */ @@ -262,11 +261,22 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { try self.jsonDecoder.decode(CollectUserFeedbackResponse.self, from: $0) } } - + /** + Collect user feedback + - POST /video/call/{type}/{id}/feedback/{session} + - Required permissions: - JoinCall + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) + - parameter collectUserFeedbackRequest: (body) + - returns: RequestBuilder + */ + + /** Create device - - parameter createDeviceRequest: (body) + - parameter createDeviceRequest: (body) - returns: ModelResponse */ @@ -285,16 +295,16 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Create device - POST /video/devices - - Adds a new device to a user, if the same device already exists the call will have no effect - - parameter createDeviceRequest: (body) - - returns: RequestBuilder + - Adds a new device to a user, if the same device already exists the call will have no effect + - parameter createDeviceRequest: (body) + - returns: RequestBuilder */ /** Create Guest - - parameter createGuestRequest: (body) + - parameter createGuestRequest: (body) - returns: CreateGuestResponse */ @@ -313,25 +323,61 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Create Guest - POST /video/guest - - - - parameter createGuestRequest: (body) - - returns: RequestBuilder + - + - parameter createGuestRequest: (body) + - returns: RequestBuilder + */ + + + /** + Delete Call + + - parameter type: (path) + - parameter id: (path) + - parameter deleteCallRequest: (body) + - returns: DeleteCallResponse + */ + + open func deleteCall(type: String, id: String, deleteCallRequest: DeleteCallRequest) async throws -> DeleteCallResponse { + var localVariablePath = "/video/call/{type}/{id}/delete" + let typePreEscape = "\(APIHelper.mapValueToPathItem(type))" + let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{type}", with: typePostEscape, options: .literal, range: nil) + let idPreEscape = "\(APIHelper.mapValueToPathItem(id))" + let idPostEscape = idPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{id}", with: idPostEscape, options: .literal, range: nil) + + let urlRequest = try makeRequest( + uriPath: localVariablePath, + httpMethod: "POST", + request: deleteCallRequest + ) + return try await send(request: urlRequest) { + try self.jsonDecoder.decode(DeleteCallResponse.self, from: $0) + } + } + /** + Delete Call + - POST /video/call/{type}/{id}/delete + - Sends events: - call.deleted Required permissions: - DeleteCall + - parameter type: (path) + - parameter id: (path) + - parameter deleteCallRequest: (body) + - returns: RequestBuilder */ /** Delete device - - parameter id: (query) (optional) - - parameter userId: (query) (optional) + - parameter id: (query) - returns: ModelResponse */ - open func deleteDevice(id: String? = nil, userId: String? = nil) async throws -> ModelResponse { + open func deleteDevice(id: String) async throws -> ModelResponse { let localVariablePath = "/video/devices" let queryParams = APIHelper.mapValuesToQueryItems([ - "id": (wrappedValue: id?.encodeToJSON(), isExplode: true), - "user_id": (wrappedValue: userId?.encodeToJSON(), isExplode: true), + "id": (wrappedValue: id.encodeToJSON(), isExplode: true), ]) let urlRequest = try makeRequest( uriPath: localVariablePath, @@ -345,18 +391,107 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Delete device - DELETE /video/devices - - Deletes one device - - parameter id: (query) (optional) - - parameter userId: (query) (optional) - - returns: RequestBuilder + - Deletes one device + - parameter id: (query) + - returns: RequestBuilder + */ + + + /** + Delete recording + + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) + - parameter filename: (path) + - returns: DeleteRecordingResponse + */ + + open func deleteRecording(type: String, id: String, session: String, filename: String) async throws -> DeleteRecordingResponse { + var localVariablePath = "/video/call/{type}/{id}/{session}/recordings/{filename}" + let typePreEscape = "\(APIHelper.mapValueToPathItem(type))" + let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{type}", with: typePostEscape, options: .literal, range: nil) + let idPreEscape = "\(APIHelper.mapValueToPathItem(id))" + let idPostEscape = idPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{id}", with: idPostEscape, options: .literal, range: nil) + let sessionPreEscape = "\(APIHelper.mapValueToPathItem(session))" + let sessionPostEscape = sessionPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{session}", with: sessionPostEscape, options: .literal, range: nil) + let filenamePreEscape = "\(APIHelper.mapValueToPathItem(filename))" + let filenamePostEscape = filenamePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{filename}", with: filenamePostEscape, options: .literal, range: nil) + + let urlRequest = try makeRequest( + uriPath: localVariablePath, + httpMethod: "DELETE" + ) + return try await send(request: urlRequest) { + try self.jsonDecoder.decode(DeleteRecordingResponse.self, from: $0) + } + } + /** + Delete recording + - DELETE /video/call/{type}/{id}/{session}/recordings/{filename} + - Deletes recording Required permissions: - DeleteRecording + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) + - parameter filename: (path) + - returns: RequestBuilder + */ + + + /** + Delete transcription + + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) + - parameter filename: (path) + - returns: DeleteTranscriptionResponse + */ + + open func deleteTranscription(type: String, id: String, session: String, filename: String) async throws -> DeleteTranscriptionResponse { + var localVariablePath = "/video/call/{type}/{id}/{session}/transcriptions/{filename}" + let typePreEscape = "\(APIHelper.mapValueToPathItem(type))" + let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{type}", with: typePostEscape, options: .literal, range: nil) + let idPreEscape = "\(APIHelper.mapValueToPathItem(id))" + let idPostEscape = idPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{id}", with: idPostEscape, options: .literal, range: nil) + let sessionPreEscape = "\(APIHelper.mapValueToPathItem(session))" + let sessionPostEscape = sessionPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{session}", with: sessionPostEscape, options: .literal, range: nil) + let filenamePreEscape = "\(APIHelper.mapValueToPathItem(filename))" + let filenamePostEscape = filenamePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{filename}", with: filenamePostEscape, options: .literal, range: nil) + + let urlRequest = try makeRequest( + uriPath: localVariablePath, + httpMethod: "DELETE" + ) + return try await send(request: urlRequest) { + try self.jsonDecoder.decode(DeleteTranscriptionResponse.self, from: $0) + } + } + /** + Delete transcription + - DELETE /video/call/{type}/{id}/{session}/transcriptions/{filename} + - Deletes transcription Required permissions: - DeleteTranscription + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) + - parameter filename: (path) + - returns: RequestBuilder */ /** End call - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: EndCallResponse */ @@ -380,26 +515,27 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** End call - POST /video/call/{type}/{id}/mark_ended - - Sends events: - call.ended Required permissions: - EndCall - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Sends events: - call.ended Required permissions: - EndCall + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** Get Call - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - parameter connectionId: (query) (optional) - parameter membersLimit: (query) (optional) - parameter ring: (query) (optional) - parameter notify: (query) (optional) + - parameter video: (query) (optional) - returns: GetCallResponse */ - open func getCall(type: String, id: String, connectionId: String? = nil, membersLimit: Int? = nil, ring: Bool? = nil, notify: Bool? = nil) async throws -> GetCallResponse { + open func getCall(type: String, id: String, connectionId: String? = nil, membersLimit: Int? = nil, ring: Bool? = nil, notify: Bool? = nil, video: Bool? = nil) async throws -> GetCallResponse { var localVariablePath = "/video/call/{type}/{id}" let typePreEscape = "\(APIHelper.mapValueToPathItem(type))" let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" @@ -412,6 +548,7 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { "members_limit": (wrappedValue: membersLimit?.encodeToJSON(), isExplode: true), "ring": (wrappedValue: ring?.encodeToJSON(), isExplode: true), "notify": (wrappedValue: notify?.encodeToJSON(), isExplode: true), + "video": (wrappedValue: video?.encodeToJSON(), isExplode: true), ]) let urlRequest = try makeRequest( uriPath: localVariablePath, @@ -425,23 +562,24 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Get Call - GET /video/call/{type}/{id} - - Required permissions: - ReadCall - - parameter type: (path) - - parameter id: (path) + - Required permissions: - ReadCall + - parameter type: (path) + - parameter id: (path) - parameter connectionId: (query) (optional) - parameter membersLimit: (query) (optional) - parameter ring: (query) (optional) - parameter notify: (query) (optional) - - returns: RequestBuilder + - parameter video: (query) (optional) + - returns: RequestBuilder */ /** Get Call Stats - - parameter type: (path) - - parameter id: (path) - - parameter session: (path) + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) - returns: GetCallStatsResponse */ @@ -468,11 +606,11 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Get Call Stats - GET /video/call/{type}/{id}/stats/{session} - - Required permissions: - ReadCallStats - - parameter type: (path) - - parameter id: (path) - - parameter session: (path) - - returns: RequestBuilder + - Required permissions: - ReadCallStats + - parameter type: (path) + - parameter id: (path) + - parameter session: (path) + - returns: RequestBuilder */ @@ -496,17 +634,17 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Get Edges - GET /video/edges - - Returns the list of all edges available for video calls. - - returns: RequestBuilder + - Returns the list of all edges available for video calls. + - returns: RequestBuilder */ /** Get or create a call - - parameter type: (path) - - parameter id: (path) - - parameter getOrCreateCallRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter getOrCreateCallRequest: (body) - parameter connectionId: (query) (optional) - returns: GetOrCreateCallResponse */ @@ -535,21 +673,21 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Get or create a call - POST /video/call/{type}/{id} - - Gets or creates a new call Sends events: - call.created - call.notification - call.ring Required permissions: - CreateCall - ReadCall - UpdateCallSettings - - parameter type: (path) - - parameter id: (path) - - parameter getOrCreateCallRequest: (body) + - Gets or creates a new call Sends events: - call.created - call.notification - call.ring Required permissions: - CreateCall - ReadCall - UpdateCallSettings + - parameter type: (path) + - parameter id: (path) + - parameter getOrCreateCallRequest: (body) - parameter connectionId: (query) (optional) - - returns: RequestBuilder + - returns: RequestBuilder */ /** Set call as live - - parameter type: (path) - - parameter id: (path) - - parameter goLiveRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter goLiveRequest: (body) - returns: GoLiveResponse */ @@ -574,20 +712,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Set call as live - POST /video/call/{type}/{id}/go_live - - Sends events: - call.live_started Required permissions: - UpdateCall - - parameter type: (path) - - parameter id: (path) - - parameter goLiveRequest: (body) - - returns: RequestBuilder + - Sends events: - call.live_started Required permissions: - UpdateCall + - parameter type: (path) + - parameter id: (path) + - parameter goLiveRequest: (body) + - returns: RequestBuilder */ /** Join call - - parameter type: (path) - - parameter id: (path) - - parameter joinCallRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter joinCallRequest: (body) - parameter connectionId: (query) (optional) - returns: JoinCallResponse */ @@ -616,30 +754,26 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Join call - POST /video/call/{type}/{id}/join - - Request to join a call Required permissions: - CreateCall - JoinCall - - parameter type: (path) - - parameter id: (path) - - parameter joinCallRequest: (body) + - Request to join a call Required permissions: - CreateCall - JoinCall + - parameter type: (path) + - parameter id: (path) + - parameter joinCallRequest: (body) - parameter connectionId: (query) (optional) - - returns: RequestBuilder + - returns: RequestBuilder */ /** List devices - - parameter userId: (query) (optional) - returns: ListDevicesResponse */ - open func listDevices(userId: String? = nil) async throws -> ListDevicesResponse { + open func listDevices() async throws -> ListDevicesResponse { let localVariablePath = "/video/devices" - let queryParams = APIHelper.mapValuesToQueryItems([ - "user_id": (wrappedValue: userId?.encodeToJSON(), isExplode: true), - ]) + let urlRequest = try makeRequest( uriPath: localVariablePath, - queryParams: queryParams ?? [], httpMethod: "GET" ) return try await send(request: urlRequest) { @@ -649,17 +783,16 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** List devices - GET /video/devices - - Returns all available devices - - parameter userId: (query) (optional) - - returns: RequestBuilder + - Returns all available devices + - returns: RequestBuilder */ /** List recordings - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: ListRecordingsResponse */ @@ -683,18 +816,18 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** List recordings - GET /video/call/{type}/{id}/recordings - - Lists recordings Required permissions: - ListRecordings - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Lists recordings Required permissions: - ListRecordings + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** List transcriptions - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: ListTranscriptionsResponse */ @@ -718,19 +851,19 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** List transcriptions - GET /video/call/{type}/{id}/transcriptions - - Lists transcriptions Required permissions: - ListTranscriptions - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Lists transcriptions Required permissions: - ListTranscriptions + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** Mute users - - parameter type: (path) - - parameter id: (path) - - parameter muteUsersRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter muteUsersRequest: (body) - returns: MuteUsersResponse */ @@ -755,18 +888,46 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Mute users - POST /video/call/{type}/{id}/mute_users - - Mutes users in a call Required permissions: - MuteUsers - - parameter type: (path) - - parameter id: (path) - - parameter muteUsersRequest: (body) - - returns: RequestBuilder + - Mutes users in a call Required permissions: - MuteUsers + - parameter type: (path) + - parameter id: (path) + - parameter muteUsersRequest: (body) + - returns: RequestBuilder + */ + + + /** + Query call members + + - parameter queryMembersRequest: (body) + - returns: QueryMembersResponse + */ + + open func queryMembers(queryMembersRequest: QueryMembersRequest) async throws -> QueryMembersResponse { + let localVariablePath = "/video/call/members" + + let urlRequest = try makeRequest( + uriPath: localVariablePath, + httpMethod: "POST", + request: queryMembersRequest + ) + return try await send(request: urlRequest) { + try self.jsonDecoder.decode(QueryMembersResponse.self, from: $0) + } + } + /** + Query call members + - POST /video/call/members + - Query call members with filter query Required permissions: - ReadCall + - parameter queryCallMembersRequest: (body) + - returns: RequestBuilder */ /** Query Call Stats - - parameter queryCallStatsRequest: (body) + - parameter queryCallStatsRequest: (body) - returns: QueryCallStatsResponse */ @@ -785,16 +946,16 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Query Call Stats - POST /video/call/stats - - Required permissions: - ReadCallStats - - parameter queryCallStatsRequest: (body) - - returns: RequestBuilder + - Required permissions: - ReadCallStats + - parameter queryCallStatsRequest: (body) + - returns: RequestBuilder */ /** Query call - - parameter queryCallsRequest: (body) + - parameter queryCallsRequest: (body) - parameter connectionId: (query) (optional) - returns: QueryCallsResponse */ @@ -817,38 +978,10 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Query call - POST /video/calls - - Query calls with filter query Required permissions: - ReadCall - - parameter queryCallsRequest: (body) + - Query calls with filter query Required permissions: - ReadCall + - parameter queryCallsRequest: (body) - parameter connectionId: (query) (optional) - - returns: RequestBuilder - */ - - - /** - Query call members - - - parameter queryMembersRequest: (body) - - returns: QueryMembersResponse - */ - - open func queryMembers(queryMembersRequest: QueryMembersRequest) async throws -> QueryMembersResponse { - let localVariablePath = "/video/call/members" - - let urlRequest = try makeRequest( - uriPath: localVariablePath, - httpMethod: "POST", - request: queryMembersRequest - ) - return try await send(request: urlRequest) { - try self.jsonDecoder.decode(QueryMembersResponse.self, from: $0) - } - } - /** - Query call members - - POST /video/call/members - - Query call members with filter query Required permissions: - ReadCall - - parameter queryMembersRequest: (body) - - returns: RequestBuilder + - returns: RequestBuilder */ @@ -879,13 +1012,23 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { try self.jsonDecoder.decode(RejectCallResponse.self, from: $0) } } + /** + Reject Call + - POST /video/call/{type}/{id}/reject + - Sends events: - call.rejected Required permissions: - JoinCall + - parameter type: (path) + - parameter id: (path) + - parameter rejectCallRequest: (body) + - returns: RequestBuilder + */ + /** Request permission - - parameter type: (path) - - parameter id: (path) - - parameter requestPermissionRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter requestPermissionRequest: (body) - returns: RequestPermissionResponse */ @@ -910,11 +1053,11 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Request permission - POST /video/call/{type}/{id}/request_permission - - Request permission to perform an action Sends events: - call.permission_request - - parameter type: (path) - - parameter id: (path) - - parameter requestPermissionRequest: (body) - - returns: RequestBuilder + - Request permission to perform an action Sends events: - call.permission_request + - parameter type: (path) + - parameter id: (path) + - parameter requestPermissionRequest: (body) + - returns: RequestBuilder */ @@ -948,20 +1091,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Send custom event - POST /video/call/{type}/{id}/event - - Sends custom event to the call Sends events: - custom Required permissions: - SendEvent - - parameter type: (path) - - parameter id: (path) - - parameter sendEventRequest: (body) - - returns: RequestBuilder + - Sends custom event to the call Sends events: - custom Required permissions: - SendEvent + - parameter type: (path) + - parameter id: (path) + - parameter sendCallEventRequest: (body) + - returns: RequestBuilder */ /** Send reaction to the call - - parameter type: (path) - - parameter id: (path) - - parameter sendReactionRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter sendReactionRequest: (body) - returns: SendReactionResponse */ @@ -986,19 +1129,19 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Send reaction to the call - POST /video/call/{type}/{id}/reaction - - Sends reaction to the call Sends events: - call.reaction_new Required permissions: - CreateCallReaction - - parameter type: (path) - - parameter id: (path) - - parameter sendReactionRequest: (body) - - returns: RequestBuilder + - Sends reaction to the call Sends events: - call.reaction_new Required permissions: - CreateCallReaction + - parameter type: (path) + - parameter id: (path) + - parameter sendReactionRequest: (body) + - returns: RequestBuilder */ /** Start HLS broadcasting - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: StartHLSBroadcastingResponse */ @@ -1022,19 +1165,57 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Start HLS broadcasting - POST /video/call/{type}/{id}/start_broadcasting - - Starts HLS broadcasting Required permissions: - StartBroadcasting - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Starts HLS broadcasting Required permissions: - StartBroadcasting + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder + */ + + + /** + Start RTMP broadcasts + + - parameter type: (path) + - parameter id: (path) + - parameter startRTMPBroadcastsRequest: (body) + - returns: StartRTMPBroadcastsResponse + */ + + open func startRTMPBroadcast(type: String, id: String, startRTMPBroadcastsRequest: StartRTMPBroadcastsRequest) async throws -> StartRTMPBroadcastsResponse { + var localVariablePath = "/video/call/{type}/{id}/rtmp_broadcasts" + let typePreEscape = "\(APIHelper.mapValueToPathItem(type))" + let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{type}", with: typePostEscape, options: .literal, range: nil) + let idPreEscape = "\(APIHelper.mapValueToPathItem(id))" + let idPostEscape = idPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{id}", with: idPostEscape, options: .literal, range: nil) + + let urlRequest = try makeRequest( + uriPath: localVariablePath, + httpMethod: "POST", + request: startRTMPBroadcastsRequest + ) + return try await send(request: urlRequest) { + try self.jsonDecoder.decode(StartRTMPBroadcastsResponse.self, from: $0) + } + } + /** + Start RTMP broadcasts + - POST /video/call/{type}/{id}/rtmp_broadcasts + - Starts RTMP broadcasts for the provided RTMP destinations Required permissions: - StartBroadcasting + - parameter type: (path) + - parameter id: (path) + - parameter startRTMPBroadcastsRequest: (body) + - returns: RequestBuilder */ /** Start recording - - parameter type: (path) - - parameter id: (path) - - parameter startRecordingRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter startRecordingRequest: (body) - returns: StartRecordingResponse */ @@ -1059,20 +1240,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Start recording - POST /video/call/{type}/{id}/start_recording - - Starts recording Sends events: - call.recording_started Required permissions: - StartRecording - - parameter type: (path) - - parameter id: (path) - - parameter startRecordingRequest: (body) - - returns: RequestBuilder + - Starts recording Sends events: - call.recording_started Required permissions: - StartRecording + - parameter type: (path) + - parameter id: (path) + - parameter startRecordingRequest: (body) + - returns: RequestBuilder */ /** Start transcription - - parameter type: (path) - - parameter id: (path) - - parameter startTranscriptionRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter startTranscriptionRequest: (body) - returns: StartTranscriptionResponse */ @@ -1097,19 +1278,54 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Start transcription - POST /video/call/{type}/{id}/start_transcription - - Starts transcription Required permissions: - StartTranscription - - parameter type: (path) - - parameter id: (path) - - parameter startTranscriptionRequest: (body) - - returns: RequestBuilder + - Starts transcription Required permissions: - StartTranscription + - parameter type: (path) + - parameter id: (path) + - parameter startTranscriptionRequest: (body) + - returns: RequestBuilder + */ + + + /** + Stop all RTMP broadcasts for a call + + - parameter type: (path) + - parameter id: (path) + - returns: StopAllRTMPBroadcastsResponse + */ + + open func stopAllRTMPBroadcasts(type: String, id: String) async throws -> StopAllRTMPBroadcastsResponse { + var localVariablePath = "/video/call/{type}/{id}/rtmp_broadcasts/stop" + let typePreEscape = "\(APIHelper.mapValueToPathItem(type))" + let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{type}", with: typePostEscape, options: .literal, range: nil) + let idPreEscape = "\(APIHelper.mapValueToPathItem(id))" + let idPostEscape = idPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{id}", with: idPostEscape, options: .literal, range: nil) + + let urlRequest = try makeRequest( + uriPath: localVariablePath, + httpMethod: "POST" + ) + return try await send(request: urlRequest) { + try self.jsonDecoder.decode(StopAllRTMPBroadcastsResponse.self, from: $0) + } + } + /** + Stop all RTMP broadcasts for a call + - POST /video/call/{type}/{id}/rtmp_broadcasts/stop + - Stop all RTMP broadcasts for the provided call Required permissions: - StopBroadcasting + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** Stop HLS broadcasting - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: StopHLSBroadcastingResponse */ @@ -1133,18 +1349,18 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Stop HLS broadcasting - POST /video/call/{type}/{id}/stop_broadcasting - - Stops HLS broadcasting Required permissions: - StopBroadcasting - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Stops HLS broadcasting Required permissions: - StopBroadcasting + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** Set call as not live - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: StopLiveResponse */ @@ -1168,18 +1384,61 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Set call as not live - POST /video/call/{type}/{id}/stop_live - - Sends events: - call.updated Required permissions: - UpdateCall - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Sends events: - call.updated Required permissions: - UpdateCall + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder + */ + + + /** + Stop RTMP broadcasts + + - parameter type: (path) + - parameter id: (path) + - parameter name: (path) + - parameter body: (body) + - returns: StopRTMPBroadcastsResponse + */ + + open func stopRTMPBroadcast(type: String, id: String, name: String, body: [String: RawJSON]) async throws -> StopRTMPBroadcastsResponse { + var localVariablePath = "/video/call/{type}/{id}/rtmp_broadcasts/{name}/stop" + let typePreEscape = "\(APIHelper.mapValueToPathItem(type))" + let typePostEscape = typePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{type}", with: typePostEscape, options: .literal, range: nil) + let idPreEscape = "\(APIHelper.mapValueToPathItem(id))" + let idPostEscape = idPreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{id}", with: idPostEscape, options: .literal, range: nil) + let namePreEscape = "\(APIHelper.mapValueToPathItem(name))" + let namePostEscape = namePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{name}", with: namePostEscape, options: .literal, range: nil) + + let urlRequest = try makeRequest( + uriPath: localVariablePath, + httpMethod: "POST", + request: body + ) + return try await send(request: urlRequest) { + try self.jsonDecoder.decode(StopRTMPBroadcastsResponse.self, from: $0) + } + } + /** + Stop RTMP broadcasts + - POST /video/call/{type}/{id}/rtmp_broadcasts/{name}/stop + - Stop RTMP broadcasts for the provided RTMP destinations Required permissions: - StopBroadcasting + - parameter type: (path) + - parameter id: (path) + - parameter name: (path) + - parameter body: (body) + - returns: RequestBuilder */ /** Stop recording - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: StopRecordingResponse */ @@ -1203,18 +1462,18 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Stop recording - POST /video/call/{type}/{id}/stop_recording - - Stops recording Sends events: - call.recording_stopped Required permissions: - StopRecording - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Stops recording Sends events: - call.recording_stopped Required permissions: - StopRecording + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** Stop transcription - - parameter type: (path) - - parameter id: (path) + - parameter type: (path) + - parameter id: (path) - returns: StopTranscriptionResponse */ @@ -1238,19 +1497,19 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Stop transcription - POST /video/call/{type}/{id}/stop_transcription - - Stops transcription Sends events: - call.transcription_stopped Required permissions: - StopTranscription - - parameter type: (path) - - parameter id: (path) - - returns: RequestBuilder + - Stops transcription Sends events: - call.transcription_stopped Required permissions: - StopTranscription + - parameter type: (path) + - parameter id: (path) + - returns: RequestBuilder */ /** Unblocks user on a call - - parameter type: (path) - - parameter id: (path) - - parameter unblockUserRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter unblockUserRequest: (body) - returns: UnblockUserResponse */ @@ -1275,20 +1534,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Unblocks user on a call - POST /video/call/{type}/{id}/unblock - - Removes the block for a user on a call. The user will be able to join the call again. Sends events: - call.unblocked_user Required permissions: - BlockUser - - parameter type: (path) - - parameter id: (path) - - parameter unblockUserRequest: (body) - - returns: RequestBuilder + - Removes the block for a user on a call. The user will be able to join the call again. Sends events: - call.unblocked_user Required permissions: - BlockUser + - parameter type: (path) + - parameter id: (path) + - parameter unblockUserRequest: (body) + - returns: RequestBuilder */ /** Update Call - - parameter type: (path) - - parameter id: (path) - - parameter updateCallRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter updateCallRequest: (body) - returns: UpdateCallResponse */ @@ -1313,20 +1572,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Update Call - PATCH /video/call/{type}/{id} - - Sends events: - call.updated Required permissions: - UpdateCall - - parameter type: (path) - - parameter id: (path) - - parameter updateCallRequest: (body) - - returns: RequestBuilder + - Sends events: - call.updated Required permissions: - UpdateCall + - parameter type: (path) + - parameter id: (path) + - parameter updateCallRequest: (body) + - returns: RequestBuilder */ /** Update Call Member - - parameter type: (path) - - parameter id: (path) - - parameter updateCallMembersRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter updateCallMembersRequest: (body) - returns: UpdateCallMembersResponse */ @@ -1351,20 +1610,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Update Call Member - POST /video/call/{type}/{id}/members - - Sends events: - call.member_added - call.member_removed - call.member_updated Required permissions: - RemoveCallMember - UpdateCallMember - UpdateCallMemberRole - - parameter type: (path) - - parameter id: (path) - - parameter updateCallMembersRequest: (body) - - returns: RequestBuilder + - Sends events: - call.member_added - call.member_removed - call.member_updated Required permissions: - RemoveCallMember - UpdateCallMember - UpdateCallMemberRole + - parameter type: (path) + - parameter id: (path) + - parameter updateCallMembersRequest: (body) + - returns: RequestBuilder */ /** Update user permissions - - parameter type: (path) - - parameter id: (path) - - parameter updateUserPermissionsRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter updateUserPermissionsRequest: (body) - returns: UpdateUserPermissionsResponse */ @@ -1389,20 +1648,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Update user permissions - POST /video/call/{type}/{id}/user_permissions - - Updates user permissions Sends events: - call.permissions_updated Required permissions: - UpdateCallPermissions - - parameter type: (path) - - parameter id: (path) - - parameter updateUserPermissionsRequest: (body) - - returns: RequestBuilder + - Updates user permissions Sends events: - call.permissions_updated Required permissions: - UpdateCallPermissions + - parameter type: (path) + - parameter id: (path) + - parameter updateUserPermissionsRequest: (body) + - returns: RequestBuilder */ /** Pin - - parameter type: (path) - - parameter id: (path) - - parameter pinRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter pinRequest: (body) - returns: PinResponse */ @@ -1427,20 +1686,20 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { /** Pin - POST /video/call/{type}/{id}/pin - - Pins a track for all users in the call. Required permissions: - PinCallTrack - - parameter type: (path) - - parameter id: (path) - - parameter pinRequest: (body) - - returns: RequestBuilder + - Pins a track for all users in the call. Required permissions: - PinCallTrack + - parameter type: (path) + - parameter id: (path) + - parameter pinRequest: (body) + - returns: RequestBuilder */ /** Unpin - - parameter type: (path) - - parameter id: (path) - - parameter unpinRequest: (body) + - parameter type: (path) + - parameter id: (path) + - parameter unpinRequest: (body) - returns: UnpinResponse */ @@ -1462,16 +1721,6 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable { try self.jsonDecoder.decode(UnpinResponse.self, from: $0) } } - /** - Unpin - - POST /video/call/{type}/{id}/unpin - - Unpins a track for all users in the call. Required permissions: - PinCallTrack - - parameter type: (path) - - parameter id: (path) - - parameter unpinRequest: (body) - - returns: RequestBuilder - */ - } protocol DefaultAPIEndpoints { @@ -1492,13 +1741,22 @@ protocol DefaultAPIEndpoints { func createGuest(createGuestRequest: CreateGuestRequest) async throws -> CreateGuestResponse - func deleteDevice(id: String?, userId: String?) async throws -> ModelResponse + func deleteCall(type: String, id: String, deleteCallRequest: DeleteCallRequest) async throws -> DeleteCallResponse + + + func deleteDevice(id: String) async throws -> ModelResponse + + + func deleteRecording(type: String, id: String, session: String, filename: String) async throws -> DeleteRecordingResponse + + + func deleteTranscription(type: String, id: String, session: String, filename: String) async throws -> DeleteTranscriptionResponse func endCall(type: String, id: String) async throws -> EndCallResponse - func getCall(type: String, id: String, connectionId: String?, membersLimit: Int?, ring: Bool?, notify: Bool?) async throws -> GetCallResponse + func getCall(type: String, id: String, connectionId: String?, membersLimit: Int?, ring: Bool?, notify: Bool?, video: Bool?) async throws -> GetCallResponse func getCallStats(type: String, id: String, session: String) async throws -> GetCallStatsResponse @@ -1516,7 +1774,7 @@ protocol DefaultAPIEndpoints { func joinCall(type: String, id: String, joinCallRequest: JoinCallRequest, connectionId: String?) async throws -> JoinCallResponse - func listDevices(userId: String?) async throws -> ListDevicesResponse + func listDevices() async throws -> ListDevicesResponse func listRecordings(type: String, id: String) async throws -> ListRecordingsResponse @@ -1552,18 +1810,27 @@ protocol DefaultAPIEndpoints { func startHLSBroadcasting(type: String, id: String) async throws -> StartHLSBroadcastingResponse + func startRTMPBroadcast(type: String, id: String, startRTMPBroadcastsRequest: StartRTMPBroadcastsRequest) async throws -> StartRTMPBroadcastsResponse + + func startRecording(type: String, id: String, startRecordingRequest: StartRecordingRequest) async throws -> StartRecordingResponse func startTranscription(type: String, id: String, startTranscriptionRequest: StartTranscriptionRequest) async throws -> StartTranscriptionResponse + func stopAllRTMPBroadcasts(type: String, id: String) async throws -> StopAllRTMPBroadcastsResponse + + func stopHLSBroadcasting(type: String, id: String) async throws -> StopHLSBroadcastingResponse func stopLive(type: String, id: String) async throws -> StopLiveResponse + func stopRTMPBroadcast(type: String, id: String, name: String, body: [String: RawJSON]) async throws -> StopRTMPBroadcastsResponse + + func stopRecording(type: String, id: String) async throws -> StopRecordingResponse diff --git a/Sources/StreamVideo/OpenApi/generated/Extensions.swift b/Sources/StreamVideo/OpenApi/generated/Extensions.swift index 557638320..fd01f2647 100644 --- a/Sources/StreamVideo/OpenApi/generated/Extensions.swift +++ b/Sources/StreamVideo/OpenApi/generated/Extensions.swift @@ -120,102 +120,3 @@ extension String: CodingKey { } } - -extension KeyedEncodingContainerProtocol { - - public mutating func encodeArray(_ values: [T], forKey key: Self.Key) throws where T: Encodable { - var arrayContainer = nestedUnkeyedContainer(forKey: key) - try arrayContainer.encode(contentsOf: values) - } - - public mutating func encodeArrayIfPresent(_ values: [T]?, forKey key: Self.Key) throws where T: Encodable { - if let values = values { - try encodeArray(values, forKey: key) - } - } - - public mutating func encodeMap(_ pairs: [Self.Key: T]) throws where T: Encodable { - for (key, value) in pairs { - try encode(value, forKey: key) - } - } - - public mutating func encodeMapIfPresent(_ pairs: [Self.Key: T]?) throws where T: Encodable { - if let pairs = pairs { - try encodeMap(pairs) - } - } - - public mutating func encode(_ value: Decimal, forKey key: Self.Key) throws { - var mutableValue = value - let stringValue = NSDecimalString(&mutableValue, Locale(identifier: "en_US")) - try encode(stringValue, forKey: key) - } - - public mutating func encodeIfPresent(_ value: Decimal?, forKey key: Self.Key) throws { - if let value = value { - try encode(value, forKey: key) - } - } -} - -extension KeyedDecodingContainerProtocol { - - public func decodeArray(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T: Decodable { - var tmpArray = [T]() - - var nestedContainer = try nestedUnkeyedContainer(forKey: key) - while !nestedContainer.isAtEnd { - let arrayValue = try nestedContainer.decode(T.self) - tmpArray.append(arrayValue) - } - - return tmpArray - } - - public func decodeArrayIfPresent(_ type: T.Type, forKey key: Self.Key) throws -> [T]? where T: Decodable { - var tmpArray: [T]? - - if contains(key) { - tmpArray = try decodeArray(T.self, forKey: key) - } - - return tmpArray - } - - public func decodeMap(_ type: T.Type, excludedKeys: Set) throws -> [Self.Key: T] where T: Decodable { - var map: [Self.Key: T] = [:] - - for key in allKeys { - if !excludedKeys.contains(key) { - let value = try decode(T.self, forKey: key) - map[key] = value - } - } - - return map - } - - public func decode(_ type: Decimal.Type, forKey key: Self.Key) throws -> Decimal { - let stringValue = try decode(String.self, forKey: key) - guard let decimalValue = Decimal(string: stringValue) else { - let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value") - throw DecodingError.typeMismatch(type, context) - } - - return decimalValue - } - - public func decodeIfPresent(_ type: Decimal.Type, forKey key: Self.Key) throws -> Decimal? { - guard let stringValue = try decodeIfPresent(String.self, forKey: key) else { - return nil - } - guard let decimalValue = Decimal(string: stringValue) else { - let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value") - throw DecodingError.typeMismatch(type, context) - } - - return decimalValue - } - -} diff --git a/Sources/StreamVideo/OpenApi/generated/JSONEncodingHelper.swift b/Sources/StreamVideo/OpenApi/generated/JSONEncodingHelper.swift deleted file mode 100644 index 02f78ffb4..000000000 --- a/Sources/StreamVideo/OpenApi/generated/JSONEncodingHelper.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// JSONEncodingHelper.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -open class JSONEncodingHelper { - - open class func encodingParameters(forEncodableObject encodableObj: T?) -> [String: Any]? { - var params: [String: Any]? - - // Encode the Encodable object - if let encodableObj = encodableObj { - let encodeResult = CodableHelper.encode(encodableObj) - do { - let data = try encodeResult.get() - params = JSONDataEncoding.encodingParameters(jsonData: data) - } catch { - print(error.localizedDescription) - } - } - - return params - } - - open class func encodingParameters(forEncodableObject encodableObj: Any?) -> [String: Any]? { - var params: [String: Any]? - - if let encodableObj = encodableObj { - do { - let data = try JSONSerialization.data(withJSONObject: encodableObj, options: .prettyPrinted) - params = JSONDataEncoding.encodingParameters(jsonData: data) - } catch { - print(error.localizedDescription) - return nil - } - } - - return params - } - -} diff --git a/Sources/StreamVideo/OpenApi/generated/Models.swift b/Sources/StreamVideo/OpenApi/generated/Models.swift index b49cf8d12..9092bd661 100644 --- a/Sources/StreamVideo/OpenApi/generated/Models.swift +++ b/Sources/StreamVideo/OpenApi/generated/Models.swift @@ -62,26 +62,6 @@ extension NullEncodable: Codable where Wrapped: Codable { } } -public enum ErrorResponse: Error { - case error(Int, Data?, URLResponse?, Error) -} - -public enum DownloadException: Error { - case responseDataMissing - case responseFailed - case requestMissing - case requestMissingPath - case requestMissingURL -} - -public enum DecodableRequestBuilderError: Error { - case emptyDataResponse - case nilHTTPResponse - case unsuccessfulHTTPStatusCode - case jsonDecoding(DecodingError) - case generalError(Error) -} - open class Response { public let statusCode: Int public let header: [String: String] @@ -106,21 +86,3 @@ open class Response { self.init(statusCode: response.statusCode, header: header, body: body, bodyData: bodyData) } } - -public final class RequestTask: @unchecked Sendable { - private var lock = NSRecursiveLock() - private var task: URLSessionTask? - - internal func set(task: URLSessionTask) { - lock.lock() - defer { lock.unlock() } - self.task = task - } - - public func cancel() { - lock.lock() - defer { lock.unlock() } - task?.cancel() - task = nil - } -} diff --git a/Sources/StreamVideo/OpenApi/generated/Models/BackstageSettingsResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/BackstageSettingsResponse.swift deleted file mode 100644 index 2cd2bb307..000000000 --- a/Sources/StreamVideo/OpenApi/generated/Models/BackstageSettingsResponse.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// BackstageSettingsResponse.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - - -public struct BackstageSettingsResponse: Codable, JSONEncodable, Hashable { - public var enabled: Bool - public var joinAheadTimeSeconds: Int? - - public init(enabled: Bool, joinAheadTimeSeconds: Int? = nil) { - self.enabled = enabled - self.joinAheadTimeSeconds = joinAheadTimeSeconds - } - - public enum CodingKeys: String, CodingKey, CaseIterable { - case enabled - case joinAheadTimeSeconds = "join_ahead_time_seconds" - } - - // Encodable protocol methods - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(enabled, forKey: .enabled) - try container.encodeIfPresent(joinAheadTimeSeconds, forKey: .joinAheadTimeSeconds) - } -} - diff --git a/Sources/StreamVideo/OpenApi/generated/Models/CallMissedEvent.swift b/Sources/StreamVideo/OpenApi/generated/Models/CallMissedEvent.swift new file mode 100644 index 000000000..374688f87 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/CallMissedEvent.swift @@ -0,0 +1,60 @@ +// +// CallMissedEvent.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +/** This event is sent to call members who did not accept/reject/join the call to notify they missed the call */ + +public struct CallMissedEvent: @unchecked Sendable, Event, Codable, JSONEncodable, Hashable, WSCallEvent { + public var call: CallResponse + public var callCid: String + public var createdAt: Date + /** List of members who missed the call */ + public var members: [MemberResponse] + public var notifyUser: Bool + /** Call session ID */ + public var sessionId: String + /** The type of event: \"call.notification\" in this case */ + public var type: String = "call.missed" + public var user: UserResponse + + public init(call: CallResponse, callCid: String, createdAt: Date, members: [MemberResponse], notifyUser: Bool, sessionId: String, type: String = "call.missed", user: UserResponse) { + self.call = call + self.callCid = callCid + self.createdAt = createdAt + self.members = members + self.notifyUser = notifyUser + self.sessionId = sessionId + self.type = type + self.user = user + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case call + case callCid = "call_cid" + case createdAt = "created_at" + case members + case notifyUser = "notify_user" + case sessionId = "session_id" + case type + case user + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(call, forKey: .call) + try container.encode(callCid, forKey: .callCid) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(members, forKey: .members) + try container.encode(notifyUser, forKey: .notifyUser) + try container.encode(sessionId, forKey: .sessionId) + try container.encode(type, forKey: .type) + try container.encode(user, forKey: .user) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastFailedEvent.swift b/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastFailedEvent.swift new file mode 100644 index 000000000..24e503d2a --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastFailedEvent.swift @@ -0,0 +1,45 @@ +// +// CallRtmpBroadcastFailedEvent.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +/** This event is sent when a call RTMP broadcast has failed */ + +public struct CallRtmpBroadcastFailedEvent: @unchecked Sendable, Event, Codable, JSONEncodable, Hashable, WSCallEvent { + /** The unique identifier for a call (:) */ + public var callCid: String + /** Date/time of creation */ + public var createdAt: Date + /** Name of the given RTMP broadcast */ + public var name: String + /** The type of event: \"call.rtmp_broadcast_failed\" in this case */ + public var type: String = "call.rtmp_broadcast_failed" + + public init(callCid: String, createdAt: Date, name: String, type: String = "call.rtmp_broadcast_failed") { + self.callCid = callCid + self.createdAt = createdAt + self.name = name + self.type = type + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case callCid = "call_cid" + case createdAt = "created_at" + case name + case type + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(callCid, forKey: .callCid) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(name, forKey: .name) + try container.encode(type, forKey: .type) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastStartedEvent.swift b/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastStartedEvent.swift new file mode 100644 index 000000000..bfb6e30de --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastStartedEvent.swift @@ -0,0 +1,42 @@ +// +// CallRtmpBroadcastStartedEvent.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +/** This event is sent when RTMP broadcast has started */ + +public struct CallRtmpBroadcastStartedEvent: @unchecked Sendable, Event, Codable, JSONEncodable, Hashable, WSCallEvent { + public var callCid: String + public var createdAt: Date + public var name: String + /** The type of event: \"call.rtmp_broadcast_started\" in this case */ + public var type: String = "call.rtmp_broadcast_started" + + public init(callCid: String, createdAt: Date, name: String, type: String = "call.rtmp_broadcast_started") { + self.callCid = callCid + self.createdAt = createdAt + self.name = name + self.type = type + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case callCid = "call_cid" + case createdAt = "created_at" + case name + case type + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(callCid, forKey: .callCid) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(name, forKey: .name) + try container.encode(type, forKey: .type) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastStoppedEvent.swift b/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastStoppedEvent.swift new file mode 100644 index 000000000..23a390c36 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/CallRtmpBroadcastStoppedEvent.swift @@ -0,0 +1,42 @@ +// +// CallRtmpBroadcastStoppedEvent.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +/** This event is sent when RTMP broadcast has stopped */ + +public struct CallRtmpBroadcastStoppedEvent: @unchecked Sendable, Event, Codable, JSONEncodable, Hashable, WSCallEvent { + public var callCid: String + public var createdAt: Date + public var name: String + /** The type of event: \"call.rtmp_broadcast_stopped\" in this case */ + public var type: String = "call.rtmp_broadcast_stopped" + + public init(callCid: String, createdAt: Date, name: String, type: String = "call.rtmp_broadcast_stopped") { + self.callCid = callCid + self.createdAt = createdAt + self.name = name + self.type = type + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case callCid = "call_cid" + case createdAt = "created_at" + case name + case type + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(callCid, forKey: .callCid) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(name, forKey: .name) + try container.encode(type, forKey: .type) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/CallSessionParticipantCountsUpdatedEvent.swift b/Sources/StreamVideo/OpenApi/generated/Models/CallSessionParticipantCountsUpdatedEvent.swift new file mode 100644 index 000000000..90ad4ce67 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/CallSessionParticipantCountsUpdatedEvent.swift @@ -0,0 +1,51 @@ +// +// CallSessionParticipantCountsUpdatedEvent.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +/** This event is sent when the participant counts in a call session are updated */ + +public struct CallSessionParticipantCountsUpdatedEvent: @unchecked Sendable, Event, Codable, JSONEncodable, Hashable, WSCallEvent { + public var anonymousParticipantCount: Int + public var callCid: String + public var createdAt: Date + public var participantsCountByRole: [String: Int] + /** Call session ID */ + public var sessionId: String + /** The type of event: \"call.session_participant_count_updated\" in this case */ + public var type: String = "call.session_participant_count_updated" + + public init(anonymousParticipantCount: Int, callCid: String, createdAt: Date, participantsCountByRole: [String: Int], sessionId: String, type: String = "call.session_participant_count_updated") { + self.anonymousParticipantCount = anonymousParticipantCount + self.callCid = callCid + self.createdAt = createdAt + self.participantsCountByRole = participantsCountByRole + self.sessionId = sessionId + self.type = type + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case anonymousParticipantCount = "anonymous_participant_count" + case callCid = "call_cid" + case createdAt = "created_at" + case participantsCountByRole = "participants_count_by_role" + case sessionId = "session_id" + case type + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(anonymousParticipantCount, forKey: .anonymousParticipantCount) + try container.encode(callCid, forKey: .callCid) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(participantsCountByRole, forKey: .participantsCountByRole) + try container.encode(sessionId, forKey: .sessionId) + try container.encode(type, forKey: .type) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/CallSessionResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/CallSessionResponse.swift index ec8369917..504eda601 100644 --- a/Sources/StreamVideo/OpenApi/generated/Models/CallSessionResponse.swift +++ b/Sources/StreamVideo/OpenApi/generated/Models/CallSessionResponse.swift @@ -10,22 +10,26 @@ import Foundation public struct CallSessionResponse: Codable, JSONEncodable, Hashable { public var acceptedBy: [String: Date] + public var anonymousParticipantCount: Int? public var endedAt: Date? public var id: String public var liveEndedAt: Date? public var liveStartedAt: Date? + public var missedBy: [String: Date]? public var participants: [CallParticipantResponse] public var participantsCountByRole: [String: Int] public var rejectedBy: [String: Date] public var startedAt: Date? public var timerEndsAt: Date? - public init(acceptedBy: [String: Date], endedAt: Date? = nil, id: String, liveEndedAt: Date? = nil, liveStartedAt: Date? = nil, participants: [CallParticipantResponse], participantsCountByRole: [String: Int], rejectedBy: [String: Date], startedAt: Date? = nil, timerEndsAt: Date? = nil) { + public init(acceptedBy: [String: Date], anonymousParticipantCount: Int?, endedAt: Date? = nil, id: String, liveEndedAt: Date? = nil, liveStartedAt: Date? = nil, missedBy: [String: Date]?, participants: [CallParticipantResponse], participantsCountByRole: [String: Int], rejectedBy: [String: Date], startedAt: Date? = nil, timerEndsAt: Date? = nil) { self.acceptedBy = acceptedBy + self.anonymousParticipantCount = anonymousParticipantCount self.endedAt = endedAt self.id = id self.liveEndedAt = liveEndedAt self.liveStartedAt = liveStartedAt + self.missedBy = missedBy self.participants = participants self.participantsCountByRole = participantsCountByRole self.rejectedBy = rejectedBy @@ -35,10 +39,12 @@ public struct CallSessionResponse: Codable, JSONEncodable, Hashable { public enum CodingKeys: String, CodingKey, CaseIterable { case acceptedBy = "accepted_by" + case anonymousParticipantCount = "anonymous_participant_count" case endedAt = "ended_at" case id case liveEndedAt = "live_ended_at" case liveStartedAt = "live_started_at" + case missedBy = "missed_by" case participants case participantsCountByRole = "participants_count_by_role" case rejectedBy = "rejected_by" @@ -51,10 +57,12 @@ public struct CallSessionResponse: Codable, JSONEncodable, Hashable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(acceptedBy, forKey: .acceptedBy) + try container.encodeIfPresent(anonymousParticipantCount, forKey: .anonymousParticipantCount) try container.encodeIfPresent(endedAt, forKey: .endedAt) try container.encode(id, forKey: .id) try container.encodeIfPresent(liveEndedAt, forKey: .liveEndedAt) try container.encodeIfPresent(liveStartedAt, forKey: .liveStartedAt) + try container.encodeIfPresent(missedBy, forKey: .missedBy) try container.encode(participants, forKey: .participants) try container.encode(participantsCountByRole, forKey: .participantsCountByRole) try container.encode(rejectedBy, forKey: .rejectedBy) @@ -62,3 +70,4 @@ public struct CallSessionResponse: Codable, JSONEncodable, Hashable { try container.encodeIfPresent(timerEndsAt, forKey: .timerEndsAt) } } + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/CallUserMutedEvent.swift b/Sources/StreamVideo/OpenApi/generated/Models/CallUserMutedEvent.swift new file mode 100644 index 000000000..8dae55d65 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/CallUserMutedEvent.swift @@ -0,0 +1,46 @@ +// +// CallUserMutedEvent.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +/** This event is sent when a call member is muted */ + +public struct CallUserMutedEvent: @unchecked Sendable, Event, Codable, JSONEncodable, Hashable, WSCallEvent { + public var callCid: String + public var createdAt: Date + public var fromUserId: String + public var mutedUserIds: [String] + /** The type of event: \"call.user_muted\" in this case */ + public var type: String = "call.user_muted" + + public init(callCid: String, createdAt: Date, fromUserId: String, mutedUserIds: [String], type: String = "call.user_muted") { + self.callCid = callCid + self.createdAt = createdAt + self.fromUserId = fromUserId + self.mutedUserIds = mutedUserIds + self.type = type + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case callCid = "call_cid" + case createdAt = "created_at" + case fromUserId = "from_user_id" + case mutedUserIds = "muted_user_ids" + case type + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(callCid, forKey: .callCid) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(fromUserId, forKey: .fromUserId) + try container.encode(mutedUserIds, forKey: .mutedUserIds) + try container.encode(type, forKey: .type) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/DeleteCallRequest.swift b/Sources/StreamVideo/OpenApi/generated/Models/DeleteCallRequest.swift new file mode 100644 index 000000000..5ae8074b5 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/DeleteCallRequest.swift @@ -0,0 +1,30 @@ +// +// DeleteCallRequest.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct DeleteCallRequest: Codable, JSONEncodable, Hashable { + /** if true the call will be hard deleted along with all related data */ + public var hard: Bool? + + public init(hard: Bool? = nil) { + self.hard = hard + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case hard + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(hard, forKey: .hard) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/DeleteCallResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/DeleteCallResponse.swift new file mode 100644 index 000000000..6b7dae0a6 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/DeleteCallResponse.swift @@ -0,0 +1,38 @@ +// +// DeleteCallResponse.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct DeleteCallResponse: Codable, JSONEncodable, Hashable { + public var call: CallResponse + /** Duration of the request in milliseconds */ + public var duration: String + public var taskId: String? + + public init(call: CallResponse, duration: String, taskId: String? = nil) { + self.call = call + self.duration = duration + self.taskId = taskId + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case call + case duration + case taskId = "task_id" + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(call, forKey: .call) + try container.encode(duration, forKey: .duration) + try container.encodeIfPresent(taskId, forKey: .taskId) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/DeleteRecordingResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/DeleteRecordingResponse.swift new file mode 100644 index 000000000..8bb982434 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/DeleteRecordingResponse.swift @@ -0,0 +1,29 @@ +// +// DeleteRecordingResponse.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct DeleteRecordingResponse: Codable, JSONEncodable, Hashable { + public var duration: String + + public init(duration: String) { + self.duration = duration + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case duration + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(duration, forKey: .duration) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/DeleteTranscriptionResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/DeleteTranscriptionResponse.swift new file mode 100644 index 000000000..62f5b5ef8 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/DeleteTranscriptionResponse.swift @@ -0,0 +1,29 @@ +// +// DeleteTranscriptionResponse.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct DeleteTranscriptionResponse: Codable, JSONEncodable, Hashable { + public var duration: String + + public init(duration: String) { + self.duration = duration + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case duration + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(duration, forKey: .duration) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/GoLiveRequest.swift b/Sources/StreamVideo/OpenApi/generated/Models/GoLiveRequest.swift index b1836568e..dc06eb434 100644 --- a/Sources/StreamVideo/OpenApi/generated/Models/GoLiveRequest.swift +++ b/Sources/StreamVideo/OpenApi/generated/Models/GoLiveRequest.swift @@ -12,13 +12,15 @@ public struct GoLiveRequest: Codable, JSONEncodable, Hashable { public var recordingStorageName: String? public var startHls: Bool? public var startRecording: Bool? + public var startRtmpBroadcasts: Bool? public var startTranscription: Bool? public var transcriptionStorageName: String? - public init(recordingStorageName: String? = nil, startHls: Bool? = nil, startRecording: Bool? = nil, startTranscription: Bool? = nil, transcriptionStorageName: String? = nil) { + public init(recordingStorageName: String? = nil, startHls: Bool? = nil, startRecording: Bool? = nil, startRtmpBroadcasts: Bool? = nil, startTranscription: Bool? = nil, transcriptionStorageName: String? = nil) { self.recordingStorageName = recordingStorageName self.startHls = startHls self.startRecording = startRecording + self.startRtmpBroadcasts = startRtmpBroadcasts self.startTranscription = startTranscription self.transcriptionStorageName = transcriptionStorageName } @@ -27,6 +29,7 @@ public struct GoLiveRequest: Codable, JSONEncodable, Hashable { case recordingStorageName = "recording_storage_name" case startHls = "start_hls" case startRecording = "start_recording" + case startRtmpBroadcasts = "start_rtmp_broadcasts" case startTranscription = "start_transcription" case transcriptionStorageName = "transcription_storage_name" } @@ -38,6 +41,7 @@ public struct GoLiveRequest: Codable, JSONEncodable, Hashable { try container.encodeIfPresent(recordingStorageName, forKey: .recordingStorageName) try container.encodeIfPresent(startHls, forKey: .startHls) try container.encodeIfPresent(startRecording, forKey: .startRecording) + try container.encodeIfPresent(startRtmpBroadcasts, forKey: .startRtmpBroadcasts) try container.encodeIfPresent(startTranscription, forKey: .startTranscription) try container.encodeIfPresent(transcriptionStorageName, forKey: .transcriptionStorageName) } diff --git a/Sources/StreamVideo/OpenApi/generated/Models/LayoutSettings.swift b/Sources/StreamVideo/OpenApi/generated/Models/LayoutSettings.swift new file mode 100644 index 000000000..1ec07c2f1 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/LayoutSettings.swift @@ -0,0 +1,48 @@ +// +// LayoutSettings.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct LayoutSettings: Codable, JSONEncodable, Hashable { + public enum Name: String, Codable, CaseIterable { + case spotlight = "spotlight" + case grid = "grid" + case singleParticipant = "single-participant" + case mobile = "mobile" + case custom = "custom" + } + public var externalAppUrl: String? + public var externalCssUrl: String? + public var name: Name + public var options: [String: RawJSON]? + + public init(externalAppUrl: String? = nil, externalCssUrl: String? = nil, name: Name, options: [String: RawJSON]? = nil) { + self.externalAppUrl = externalAppUrl + self.externalCssUrl = externalCssUrl + self.name = name + self.options = options + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case externalAppUrl = "external_app_url" + case externalCssUrl = "external_css_url" + case name + case options + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(externalAppUrl, forKey: .externalAppUrl) + try container.encodeIfPresent(externalCssUrl, forKey: .externalCssUrl) + try container.encode(name, forKey: .name) + try container.encodeIfPresent(options, forKey: .options) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/StartRTMPBroadcastsRequest.swift b/Sources/StreamVideo/OpenApi/generated/Models/StartRTMPBroadcastsRequest.swift new file mode 100644 index 000000000..46861c6af --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/StartRTMPBroadcastsRequest.swift @@ -0,0 +1,53 @@ +// +// StartRTMPBroadcastsRequest.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct StartRTMPBroadcastsRequest: Codable, JSONEncodable, Hashable { + public var layout: LayoutSettings? + public var name: String + public var password: String? + public var quality: String? + public var streamKey: String? + public var streamUrl: String + public var username: String? + + public init(layout: LayoutSettings? = nil, name: String, password: String? = nil, quality: String? = nil, streamKey: String? = nil, streamUrl: String, username: String? = nil) { + self.layout = layout + self.name = name + self.password = password + self.quality = quality + self.streamKey = streamKey + self.streamUrl = streamUrl + self.username = username + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case layout + case name + case password + case quality + case streamKey = "stream_key" + case streamUrl = "stream_url" + case username + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(layout, forKey: .layout) + try container.encode(name, forKey: .name) + try container.encodeIfPresent(password, forKey: .password) + try container.encodeIfPresent(quality, forKey: .quality) + try container.encodeIfPresent(streamKey, forKey: .streamKey) + try container.encode(streamUrl, forKey: .streamUrl) + try container.encodeIfPresent(username, forKey: .username) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/StartRTMPBroadcastsResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/StartRTMPBroadcastsResponse.swift new file mode 100644 index 000000000..a016247cf --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/StartRTMPBroadcastsResponse.swift @@ -0,0 +1,30 @@ +// +// StartRTMPBroadcastsResponse.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct StartRTMPBroadcastsResponse: Codable, JSONEncodable, Hashable { + /** Duration of the request in milliseconds */ + public var duration: String + + public init(duration: String) { + self.duration = duration + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case duration + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(duration, forKey: .duration) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/StopAllRTMPBroadcastsResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/StopAllRTMPBroadcastsResponse.swift new file mode 100644 index 000000000..7c0a6beb7 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/StopAllRTMPBroadcastsResponse.swift @@ -0,0 +1,30 @@ +// +// StopAllRTMPBroadcastsResponse.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct StopAllRTMPBroadcastsResponse: Codable, JSONEncodable, Hashable { + /** Duration of the request in milliseconds */ + public var duration: String + + public init(duration: String) { + self.duration = duration + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case duration + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(duration, forKey: .duration) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/StopRTMPBroadcastsResponse.swift b/Sources/StreamVideo/OpenApi/generated/Models/StopRTMPBroadcastsResponse.swift new file mode 100644 index 000000000..b75325b35 --- /dev/null +++ b/Sources/StreamVideo/OpenApi/generated/Models/StopRTMPBroadcastsResponse.swift @@ -0,0 +1,30 @@ +// +// StopRTMPBroadcastsResponse.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + + +public struct StopRTMPBroadcastsResponse: Codable, JSONEncodable, Hashable { + /** Duration of the request in milliseconds */ + public var duration: String + + public init(duration: String) { + self.duration = duration + } + + public enum CodingKeys: String, CodingKey, CaseIterable { + case duration + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(duration, forKey: .duration) + } +} + diff --git a/Sources/StreamVideo/OpenApi/generated/Models/WSEvent.swift b/Sources/StreamVideo/OpenApi/generated/Models/WSEvent.swift index 507c4ec4e..f8903974c 100644 --- a/Sources/StreamVideo/OpenApi/generated/Models/WSEvent.swift +++ b/Sources/StreamVideo/OpenApi/generated/Models/WSEvent.swift @@ -28,6 +28,7 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { case typeCallMemberRemovedEvent(CallMemberRemovedEvent) case typeCallMemberUpdatedEvent(CallMemberUpdatedEvent) case typeCallMemberUpdatedPermissionEvent(CallMemberUpdatedPermissionEvent) + case typeCallMissedEvent(CallMissedEvent) case typeCallNotificationEvent(CallNotificationEvent) case typeCallReactionEvent(CallReactionEvent) case typeCallRecordingFailedEvent(CallRecordingFailedEvent) @@ -36,7 +37,11 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { case typeCallRecordingStoppedEvent(CallRecordingStoppedEvent) case typeCallRejectedEvent(CallRejectedEvent) case typeCallRingEvent(CallRingEvent) + case typeCallRtmpBroadcastFailedEvent(CallRtmpBroadcastFailedEvent) + case typeCallRtmpBroadcastStartedEvent(CallRtmpBroadcastStartedEvent) + case typeCallRtmpBroadcastStoppedEvent(CallRtmpBroadcastStoppedEvent) case typeCallSessionEndedEvent(CallSessionEndedEvent) + case typeCallSessionParticipantCountsUpdatedEvent(CallSessionParticipantCountsUpdatedEvent) case typeCallSessionParticipantJoinedEvent(CallSessionParticipantJoinedEvent) case typeCallSessionParticipantLeftEvent(CallSessionParticipantLeftEvent) case typeCallSessionStartedEvent(CallSessionStartedEvent) @@ -45,14 +50,13 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { case typeCallTranscriptionStartedEvent(CallTranscriptionStartedEvent) case typeCallTranscriptionStoppedEvent(CallTranscriptionStoppedEvent) case typeCallUpdatedEvent(CallUpdatedEvent) - case typeCallUserMuted(CallUserMuted) + case typeCallUserMutedEvent(CallUserMutedEvent) case typeClosedCaptionEvent(ClosedCaptionEvent) case typeConnectedEvent(ConnectedEvent) case typeConnectionErrorEvent(ConnectionErrorEvent) case typePermissionRequestEvent(PermissionRequestEvent) case typeUnblockedUserEvent(UnblockedUserEvent) case typeUpdatedCallPermissionsEvent(UpdatedCallPermissionsEvent) - public var type: String { switch self { case .typeBlockedUserEvent(let value): @@ -81,6 +85,8 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value.type case .typeCallMemberUpdatedPermissionEvent(let value): return value.type + case .typeCallMissedEvent(let value): + return value.type case .typeCallNotificationEvent(let value): return value.type case .typeCallReactionEvent(let value): @@ -97,8 +103,16 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value.type case .typeCallRingEvent(let value): return value.type + case .typeCallRtmpBroadcastFailedEvent(let value): + return value.type + case .typeCallRtmpBroadcastStartedEvent(let value): + return value.type + case .typeCallRtmpBroadcastStoppedEvent(let value): + return value.type case .typeCallSessionEndedEvent(let value): return value.type + case .typeCallSessionParticipantCountsUpdatedEvent(let value): + return value.type case .typeCallSessionParticipantJoinedEvent(let value): return value.type case .typeCallSessionParticipantLeftEvent(let value): @@ -115,7 +129,7 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value.type case .typeCallUpdatedEvent(let value): return value.type - case .typeCallUserMuted(let value): + case .typeCallUserMutedEvent(let value): return value.type case .typeClosedCaptionEvent(let value): return value.type @@ -123,16 +137,16 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value.type case .typeConnectionErrorEvent(let value): return value.type + case .typeCustomVideoEvent(let value): + return value.type + case .typeHealthCheckEvent(let value): + return value.type case .typePermissionRequestEvent(let value): return value.type case .typeUnblockedUserEvent(let value): return value.type case .typeUpdatedCallPermissionsEvent(let value): return value.type - case .typeHealthCheckEvent(let value): - return value.type - case .typeCustomVideoEvent(let value): - return value.type } } public var rawValue: Event { @@ -163,6 +177,8 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value case .typeCallMemberUpdatedPermissionEvent(let value): return value + case .typeCallMissedEvent(let value): + return value case .typeCallNotificationEvent(let value): return value case .typeCallReactionEvent(let value): @@ -179,8 +195,16 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value case .typeCallRingEvent(let value): return value + case .typeCallRtmpBroadcastFailedEvent(let value): + return value + case .typeCallRtmpBroadcastStartedEvent(let value): + return value + case .typeCallRtmpBroadcastStoppedEvent(let value): + return value case .typeCallSessionEndedEvent(let value): return value + case .typeCallSessionParticipantCountsUpdatedEvent(let value): + return value case .typeCallSessionParticipantJoinedEvent(let value): return value case .typeCallSessionParticipantLeftEvent(let value): @@ -197,7 +221,7 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value case .typeCallUpdatedEvent(let value): return value - case .typeCallUserMuted(let value): + case .typeCallUserMutedEvent(let value): return value case .typeClosedCaptionEvent(let value): return value @@ -205,16 +229,16 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { return value case .typeConnectionErrorEvent(let value): return value + case .typeCustomVideoEvent(let value): + return value + case .typeHealthCheckEvent(let value): + return value case .typePermissionRequestEvent(let value): return value case .typeUnblockedUserEvent(let value): return value case .typeUpdatedCallPermissionsEvent(let value): return value - case .typeHealthCheckEvent(let value): - return value - case .typeCustomVideoEvent(let value): - return value } } public func encode(to encoder: Encoder) throws { @@ -246,6 +270,8 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { try container.encode(value) case .typeCallMemberUpdatedPermissionEvent(let value): try container.encode(value) + case .typeCallMissedEvent(let value): + try container.encode(value) case .typeCallNotificationEvent(let value): try container.encode(value) case .typeCallReactionEvent(let value): @@ -262,8 +288,16 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { try container.encode(value) case .typeCallRingEvent(let value): try container.encode(value) + case .typeCallRtmpBroadcastFailedEvent(let value): + try container.encode(value) + case .typeCallRtmpBroadcastStartedEvent(let value): + try container.encode(value) + case .typeCallRtmpBroadcastStoppedEvent(let value): + try container.encode(value) case .typeCallSessionEndedEvent(let value): try container.encode(value) + case .typeCallSessionParticipantCountsUpdatedEvent(let value): + try container.encode(value) case .typeCallSessionParticipantJoinedEvent(let value): try container.encode(value) case .typeCallSessionParticipantLeftEvent(let value): @@ -280,7 +314,7 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { try container.encode(value) case .typeCallUpdatedEvent(let value): try container.encode(value) - case .typeCallUserMuted(let value): + case .typeCallUserMutedEvent(let value): try container.encode(value) case .typeClosedCaptionEvent(let value): try container.encode(value) @@ -288,16 +322,16 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { try container.encode(value) case .typeConnectionErrorEvent(let value): try container.encode(value) + case .typeCustomVideoEvent(let value): + try container.encode(value) + case .typeHealthCheckEvent(let value): + try container.encode(value) case .typePermissionRequestEvent(let value): try container.encode(value) case .typeUnblockedUserEvent(let value): try container.encode(value) case .typeUpdatedCallPermissionsEvent(let value): try container.encode(value) - case .typeHealthCheckEvent(let value): - try container.encode(value) - case .typeCustomVideoEvent(let value): - try container.encode(value) } } @@ -346,6 +380,9 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { } else if dto.type == "call.member_updated_permission" { let value = try container.decode(CallMemberUpdatedPermissionEvent.self) self = .typeCallMemberUpdatedPermissionEvent(value) + } else if dto.type == "call.missed" { + let value = try container.decode(CallMissedEvent.self) + self = .typeCallMissedEvent(value) } else if dto.type == "call.notification" { let value = try container.decode(CallNotificationEvent.self) self = .typeCallNotificationEvent(value) @@ -376,9 +413,21 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { } else if dto.type == "call.ring" { let value = try container.decode(CallRingEvent.self) self = .typeCallRingEvent(value) + } else if dto.type == "call.rtmp_broadcast_failed" { + let value = try container.decode(CallRtmpBroadcastFailedEvent.self) + self = .typeCallRtmpBroadcastFailedEvent(value) + } else if dto.type == "call.rtmp_broadcast_started" { + let value = try container.decode(CallRtmpBroadcastStartedEvent.self) + self = .typeCallRtmpBroadcastStartedEvent(value) + } else if dto.type == "call.rtmp_broadcast_stopped" { + let value = try container.decode(CallRtmpBroadcastStoppedEvent.self) + self = .typeCallRtmpBroadcastStoppedEvent(value) } else if dto.type == "call.session_ended" { let value = try container.decode(CallSessionEndedEvent.self) self = .typeCallSessionEndedEvent(value) + } else if dto.type == "call.session_participant_count_updated" { + let value = try container.decode(CallSessionParticipantCountsUpdatedEvent.self) + self = .typeCallSessionParticipantCountsUpdatedEvent(value) } else if dto.type == "call.session_participant_joined" { let value = try container.decode(CallSessionParticipantJoinedEvent.self) self = .typeCallSessionParticipantJoinedEvent(value) @@ -407,8 +456,8 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { let value = try container.decode(CallUpdatedEvent.self) self = .typeCallUpdatedEvent(value) } else if dto.type == "call.user_muted" { - let value = try container.decode(CallUserMuted.self) - self = .typeCallUserMuted(value) + let value = try container.decode(CallUserMutedEvent.self) + self = .typeCallUserMutedEvent(value) } else if dto.type == "connection.error" { let value = try container.decode(ConnectionErrorEvent.self) self = .typeConnectionErrorEvent(value) @@ -421,11 +470,9 @@ public enum VideoEvent: Codable, JSONEncodable, Hashable { } else if dto.type == "health.check" { let value = try container.decode(HealthCheckEvent.self) self = .typeHealthCheckEvent(value) - } - else { + } else { throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of WSEvent")) } } } - diff --git a/Sources/StreamVideo/StreamVideo.swift b/Sources/StreamVideo/StreamVideo.swift index b633d3ea4..94da6b3b6 100644 --- a/Sources/StreamVideo/StreamVideo.swift +++ b/Sources/StreamVideo/StreamVideo.swift @@ -215,10 +215,13 @@ public class StreamVideo: ObservableObject, @unchecked Sendable { /// - Parameters: /// - callType: the type of the call. /// - callId: the id of the all. + /// - callSettings: the initial CallSettings to use. If `nil` is provided, the default CallSettings + /// will be used. /// - Returns: `Call` object. public func call( callType: String, - callId: String + callId: String, + callSettings: CallSettings? = nil ) -> Call { callCache.call(for: callCid(from: callId, callType: callType)) { let callController = makeCallController(callType: callType, callId: callId) @@ -226,7 +229,8 @@ public class StreamVideo: ObservableObject, @unchecked Sendable { callType: callType, callId: callId, coordinatorClient: coordinatorClient, - callController: callController + callController: callController, + callSettings: callSettings ) eventsMiddleware.add(subscriber: call) return call @@ -272,7 +276,7 @@ public class StreamVideo: ObservableObject, @unchecked Sendable { /// - Parameter id: the id of the device that will be deleted. @discardableResult public func deleteDevice(id: String) async throws -> ModelResponse { - try await coordinatorClient.deleteDevice(id: id, userId: user.id) + try await coordinatorClient.deleteDevice(id: id) } /// Lists the devices registered for the user. diff --git a/Sources/StreamVideo/WebRTC/SfuMiddleware.swift b/Sources/StreamVideo/WebRTC/SfuMiddleware.swift index 388ea0ddb..2864d506b 100644 --- a/Sources/StreamVideo/WebRTC/SfuMiddleware.swift +++ b/Sources/StreamVideo/WebRTC/SfuMiddleware.swift @@ -16,6 +16,7 @@ class SfuMiddleware: EventMiddleware { private var publisher: PeerConnection? var onSocketConnected: ((Bool) -> Void)? var onParticipantCountUpdated: ((UInt32) -> Void)? + var onAnonymousParticipantCountUpdated: ((UInt32) -> Void)? var onSessionMigrationEvent: (() -> Void)? var onPinsChanged: (([Stream_Video_Sfu_Models_Pin]) -> Void)? @@ -76,6 +77,7 @@ class SfuMiddleware: EventMiddleware { await loadParticipants(from: event) case let .healthCheckResponse(event): onParticipantCountUpdated?(event.participantCount.total) + onAnonymousParticipantCountUpdated?(event.participantCount.anonymous) case let .trackPublished(event): await handleTrackPublishedEvent(event) case let .trackUnpublished(event): diff --git a/Sources/StreamVideo/WebRTC/WebRTCClient.swift b/Sources/StreamVideo/WebRTC/WebRTCClient.swift index df5b076f9..3232026a4 100644 --- a/Sources/StreamVideo/WebRTC/WebRTCClient.swift +++ b/Sources/StreamVideo/WebRTC/WebRTCClient.swift @@ -189,6 +189,7 @@ class WebRTCClient: NSObject, @unchecked Sendable { var onParticipantsUpdated: (([String: CallParticipant]) -> Void)? var onSignalConnectionStateChange: ((WebSocketConnectionState) -> Void)? var onParticipantCountUpdated: ((UInt32) -> Void)? + var onAnonymousParticipantCountUpdated: ((UInt32) -> Void)? var onSessionMigrationEvent: (() -> Void)? { didSet { sfuMiddleware.onSessionMigrationEvent = onSessionMigrationEvent @@ -313,6 +314,10 @@ class WebRTCClient: NSObject, @unchecked Sendable { sfuMiddleware.onParticipantCountUpdated = { [weak self] participantCount in self?.onParticipantCountUpdated?(participantCount) } + sfuMiddleware.onAnonymousParticipantCountUpdated = { [weak self] in + self?.onAnonymousParticipantCountUpdated?($0) + } + sfuMiddleware.onPinsChanged = { [weak self] pins in self?.handlePinsChanged(pins) } diff --git a/Sources/StreamVideo/WebSockets/Client/WebSocketClient.swift b/Sources/StreamVideo/WebSockets/Client/WebSocketClient.swift index 5a88e2c3e..06e022620 100644 --- a/Sources/StreamVideo/WebSockets/Client/WebSocketClient.swift +++ b/Sources/StreamVideo/WebSockets/Client/WebSocketClient.swift @@ -317,11 +317,5 @@ extension ClientError { public class WebSocket: ClientError {} } -/// WebSocket Error -struct WebSocketErrorContainer: Decodable { - /// A server error was received. - let error: ErrorPayload -} - struct WSDisconnected: Event {} struct WSConnected: Event {} diff --git a/Sources/StreamVideo/WebSockets/Client/WebSocketConstants.swift b/Sources/StreamVideo/WebSockets/Client/WebSocketConstants.swift deleted file mode 100644 index ed560941d..000000000 --- a/Sources/StreamVideo/WebSockets/Client/WebSocketConstants.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// Copyright © 2024 Stream.io Inc. All rights reserved. -// - -import Foundation - -enum WebSocketConstants { - static let callId = "callId" - static let callType = "callType" - static let sessionId = "sessionId" -} diff --git a/Sources/StreamVideo/protobuf/sfu/models/models.pb.swift b/Sources/StreamVideo/protobuf/sfu/models/models.pb.swift index ea62ab464..9f0e4f987 100644 --- a/Sources/StreamVideo/protobuf/sfu/models/models.pb.swift +++ b/Sources/StreamVideo/protobuf/sfu/models/models.pb.swift @@ -567,16 +567,12 @@ enum Stream_Video_Sfu_Models_WebsocketReconnectStrategy: SwiftProtobuf.Enum { /// and establish a new WebSocket connection. case fast // = 2 - /// SDK should drop existing pc instances and creates a fresh WebSocket connection, - /// ensuring a clean state for the reconnection. - case clean // = 3 - /// SDK should obtain new credentials from the coordinator, drops existing pc instances, set a new session_id and initializes /// a completely new WebSocket connection, ensuring a comprehensive reset. - case rejoin // = 4 + case rejoin // = 3 /// SDK should migrate to a new SFU instance - case migrate // = 5 + case migrate // = 4 case UNRECOGNIZED(Int) init() { @@ -588,9 +584,8 @@ enum Stream_Video_Sfu_Models_WebsocketReconnectStrategy: SwiftProtobuf.Enum { case 0: self = .unspecified case 1: self = .disconnect case 2: self = .fast - case 3: self = .clean - case 4: self = .rejoin - case 5: self = .migrate + case 3: self = .rejoin + case 4: self = .migrate default: self = .UNRECOGNIZED(rawValue) } } @@ -600,9 +595,8 @@ enum Stream_Video_Sfu_Models_WebsocketReconnectStrategy: SwiftProtobuf.Enum { case .unspecified: return 0 case .disconnect: return 1 case .fast: return 2 - case .clean: return 3 - case .rejoin: return 4 - case .migrate: return 5 + case .rejoin: return 3 + case .migrate: return 4 case .UNRECOGNIZED(let i): return i } } @@ -617,7 +611,6 @@ extension Stream_Video_Sfu_Models_WebsocketReconnectStrategy: CaseIterable { .unspecified, .disconnect, .fast, - .clean, .rejoin, .migrate, ] @@ -876,6 +869,8 @@ struct Stream_Video_Sfu_Models_TrackInfo { var red: Bool = false + var muted: Bool = false + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -1223,9 +1218,8 @@ extension Stream_Video_Sfu_Models_WebsocketReconnectStrategy: SwiftProtobuf._Pro 0: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED"), 1: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT"), 2: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_FAST"), - 3: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_CLEAN"), - 4: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_REJOIN"), - 5: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_MIGRATE"), + 3: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_REJOIN"), + 4: .same(proto: "WEBSOCKET_RECONNECT_STRATEGY_MIGRATE"), ] } @@ -1719,6 +1713,7 @@ extension Stream_Video_Sfu_Models_TrackInfo: SwiftProtobuf.Message, SwiftProtobu 7: .same(proto: "dtx"), 8: .same(proto: "stereo"), 9: .same(proto: "red"), + 10: .same(proto: "muted"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1734,6 +1729,7 @@ extension Stream_Video_Sfu_Models_TrackInfo: SwiftProtobuf.Message, SwiftProtobu case 7: try { try decoder.decodeSingularBoolField(value: &self.dtx) }() case 8: try { try decoder.decodeSingularBoolField(value: &self.stereo) }() case 9: try { try decoder.decodeSingularBoolField(value: &self.red) }() + case 10: try { try decoder.decodeSingularBoolField(value: &self.muted) }() default: break } } @@ -1761,6 +1757,9 @@ extension Stream_Video_Sfu_Models_TrackInfo: SwiftProtobuf.Message, SwiftProtobu if self.red != false { try visitor.visitSingularBoolField(value: self.red, fieldNumber: 9) } + if self.muted != false { + try visitor.visitSingularBoolField(value: self.muted, fieldNumber: 10) + } try unknownFields.traverse(visitor: &visitor) } @@ -1772,6 +1771,7 @@ extension Stream_Video_Sfu_Models_TrackInfo: SwiftProtobuf.Message, SwiftProtobu if lhs.dtx != rhs.dtx {return false} if lhs.stereo != rhs.stereo {return false} if lhs.red != rhs.red {return false} + if lhs.muted != rhs.muted {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/StreamVideoSwiftUI/CallViewModel.swift b/Sources/StreamVideoSwiftUI/CallViewModel.swift index 825296f6e..6034593e1 100644 --- a/Sources/StreamVideoSwiftUI/CallViewModel.swift +++ b/Sources/StreamVideoSwiftUI/CallViewModel.swift @@ -351,7 +351,11 @@ open class CallViewModel: ObservableObject { backstage: backstage ) } else { - let call = streamVideo.call(callType: callType, callId: callId) + let call = streamVideo.call( + callType: callType, + callId: callId, + callSettings: callSettings + ) self.call = call Task { do { @@ -572,7 +576,11 @@ open class CallViewModel: ObservableObject { enteringCallTask = Task { do { log.debug("Starting call") - let call = call ?? streamVideo.call(callType: callType, callId: callId) + let call = call ?? streamVideo.call( + callType: callType, + callId: callId, + callSettings: callSettings + ) var settingsRequest: CallSettingsRequest? var limits: LimitsSettingsRequest? if maxDuration != nil || maxParticipants != nil { diff --git a/Sources/StreamVideoSwiftUI/Info.plist b/Sources/StreamVideoSwiftUI/Info.plist index b1d7e4550..2a68649f6 100644 --- a/Sources/StreamVideoSwiftUI/Info.plist +++ b/Sources/StreamVideoSwiftUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0.9 + 1.10.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/Sources/StreamVideoUIKit/Info.plist b/Sources/StreamVideoUIKit/Info.plist index b1d7e4550..2a68649f6 100644 --- a/Sources/StreamVideoUIKit/Info.plist +++ b/Sources/StreamVideoUIKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0.9 + 1.10.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/StreamVideo-XCFramework.podspec b/StreamVideo-XCFramework.podspec index 6e053adc1..13db3de20 100644 --- a/StreamVideo-XCFramework.podspec +++ b/StreamVideo-XCFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamVideo-XCFramework' - spec.version = '1.0.9' + spec.version = '1.10.0' spec.summary = 'StreamVideo iOS Video Client' spec.description = 'StreamVideo is the official Swift client for Stream Video, a service for building video applications.' @@ -17,10 +17,10 @@ Pod::Spec.new do |spec| spec.module_name = 'StreamVideo' spec.source = { http: "https://github.com/GetStream/stream-video-swift/releases/download/#{spec.version}/#{spec.module_name}.zip" } - spec.preserve_paths = "#{spec.module_name}.xcframework/*" + spec.vendored_frameworks = "#{spec.module_name}.xcframework", 'Frameworks/StreamWebRTC.xcframework' + spec.preserve_paths = "#{spec.module_name}.xcframework/*", 'Frameworks/*' spec.dependency('SwiftProtobuf', '~> 1.18.0') - spec.vendored_frameworks = 'Frameworks/StreamWebRTC.xcframework' spec.prepare_command = <<-CMD mkdir -p Frameworks/ diff --git a/StreamVideo.podspec b/StreamVideo.podspec index 2abdefd61..612d77b45 100644 --- a/StreamVideo.podspec +++ b/StreamVideo.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamVideo' - spec.version = '1.0.9' + spec.version = '1.10.0' spec.summary = 'StreamVideo iOS Video Client' spec.description = 'StreamVideo is the official Swift client for Stream Video, a service for building video applications.' diff --git a/StreamVideo.xcodeproj/project.pbxproj b/StreamVideo.xcodeproj/project.pbxproj index 514aa82b7..135dc354b 100644 --- a/StreamVideo.xcodeproj/project.pbxproj +++ b/StreamVideo.xcodeproj/project.pbxproj @@ -140,6 +140,7 @@ 404C27CC2BF2552900DF2937 /* XCTestCase+PredicateFulfillment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409CA7982BEE21720045F7AA /* XCTestCase+PredicateFulfillment.swift */; }; 404CAEE72B8F48F6007087BC /* DemoBackgroundEffectSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A0E95F2B88ABC80089E8D3 /* DemoBackgroundEffectSelector.swift */; }; 4059C3422AAF0CE40006928E /* DemoChatViewModel+Injection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4059C3412AAF0CE40006928E /* DemoChatViewModel+Injection.swift */; }; + 405EFF112C7F120700AC5CE6 /* SFUMiddlewareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405EFF102C7F120700AC5CE6 /* SFUMiddlewareTests.swift */; }; 4063033F2AD847EC0091AE77 /* CallState_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4063033E2AD847EC0091AE77 /* CallState_Tests.swift */; }; 406303422AD848000091AE77 /* CallParticipant_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406303412AD848000091AE77 /* CallParticipant_Mock.swift */; }; 406303462AD9432D0091AE77 /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 406303442AD942ED0091AE77 /* GoogleSignInSwift */; }; @@ -156,7 +157,6 @@ 4069A0052AD985D3009A3A06 /* CallParticipant_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406303412AD848000091AE77 /* CallParticipant_Mock.swift */; }; 406A8E8D2AA1D78C001F598A /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4030E59F2A9DF5BD003E8CBA /* AppEnvironment.swift */; }; 406A8E8E2AA1D79D001F598A /* UserState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445AD2A9DFC34004BE3DA /* UserState.swift */; }; - 406A8E922AA1D7B3001F598A /* CallKitState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445AF2A9DFC58004BE3DA /* CallKitState.swift */; }; 406A8E932AA1D7BF001F598A /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8456E6C3287EB43A004E180E /* LoginViewModel.swift */; }; 406A8E942AA1D7C5001F598A /* UserCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445AB2A9DFC13004BE3DA /* UserCredentials.swift */; }; 406A8E952AA1D7CB001F598A /* AddUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84201792288AB699004964B3 /* AddUserView.swift */; }; @@ -358,7 +358,6 @@ 40F18B8E2BEBB65100ADF76E /* View+OptionalPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F18B8D2BEBB65100ADF76E /* View+OptionalPublisher.swift */; }; 40F445AC2A9DFC13004BE3DA /* UserCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445AB2A9DFC13004BE3DA /* UserCredentials.swift */; }; 40F445AE2A9DFC34004BE3DA /* UserState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445AD2A9DFC34004BE3DA /* UserState.swift */; }; - 40F445B02A9DFC58004BE3DA /* CallKitState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445AF2A9DFC58004BE3DA /* CallKitState.swift */; }; 40F445B22A9DFFBB004BE3DA /* User+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445B12A9DFFBB004BE3DA /* User+Demo.swift */; }; 40F445B42A9E01B2004BE3DA /* AuthenticationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445B32A9E01B2004BE3DA /* AuthenticationProvider.swift */; }; 40F445BE2A9E0823004BE3DA /* RobotVoiceFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F445BD2A9E0823004BE3DA /* RobotVoiceFilter.swift */; }; @@ -549,6 +548,7 @@ 8415D3E1290B2AF2006E53CB /* outgoing.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 8415D3E0290B2AF2006E53CB /* outgoing.m4a */; }; 8415D3E3290BC882006E53CB /* Sounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8415D3E2290BC882006E53CB /* Sounds.swift */; }; 841947982886D9CD0007B36E /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841947972886D9CD0007B36E /* BundleExtensions.swift */; }; + 841AE18A2C738CCC005B6560 /* LayoutSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841AE1892C738CCC005B6560 /* LayoutSettings.swift */; }; 841BAA312BD15CDE000C73E4 /* VideoQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BAA182BD15CDC000C73E4 /* VideoQuality.swift */; }; 841BAA322BD15CDE000C73E4 /* CallTranscriptionStoppedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BAA192BD15CDC000C73E4 /* CallTranscriptionStoppedEvent.swift */; }; 841BAA332BD15CDE000C73E4 /* SFULocationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BAA1A2BD15CDC000C73E4 /* SFULocationResponse.swift */; }; @@ -674,8 +674,15 @@ 8442993A29422BEA0037232A /* BackportStateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8442993929422BEA0037232A /* BackportStateObject.swift */; }; 8442993C294232360037232A /* IncomingCallView_iOS13.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8442993B294232360037232A /* IncomingCallView_iOS13.swift */; }; 844299412942394C0037232A /* VideoView_iOS13.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844299402942394C0037232A /* VideoView_iOS13.swift */; }; - 844542F02C296AAB001D5ADF /* BackstageSettingsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844542EF2C296AAA001D5ADF /* BackstageSettingsResponse.swift */; }; 8446AF912A4D84F4002AB07B /* Retries_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8446AF902A4D84F4002AB07B /* Retries_Tests.swift */; }; + 844982472C738A830029734D /* DeleteRecordingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8449823F2C738A830029734D /* DeleteRecordingResponse.swift */; }; + 844982482C738A830029734D /* DeleteCallResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844982402C738A830029734D /* DeleteCallResponse.swift */; }; + 844982492C738A830029734D /* StartRTMPBroadcastsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844982412C738A830029734D /* StartRTMPBroadcastsRequest.swift */; }; + 8449824A2C738A830029734D /* StopRTMPBroadcastsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844982422C738A830029734D /* StopRTMPBroadcastsResponse.swift */; }; + 8449824B2C738A830029734D /* DeleteCallRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844982432C738A830029734D /* DeleteCallRequest.swift */; }; + 8449824C2C738A830029734D /* DeleteTranscriptionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844982442C738A830029734D /* DeleteTranscriptionResponse.swift */; }; + 8449824D2C738A830029734D /* StartRTMPBroadcastsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844982452C738A830029734D /* StartRTMPBroadcastsResponse.swift */; }; + 8449824E2C738A830029734D /* StopAllRTMPBroadcastsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844982462C738A830029734D /* StopAllRTMPBroadcastsResponse.swift */; }; 844ADA652AD3F1AB00769F6A /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 844ADA642AD3F1AB00769F6A /* GoogleSignInSwift */; }; 844ADA672AD3F21000769F6A /* GoogleSignIn.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844ADA662AD3F21000769F6A /* GoogleSignIn.plist */; }; 844ADA692AD3F78F00769F6A /* GoogleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844ADA682AD3F78F00769F6A /* GoogleHelper.swift */; }; @@ -864,6 +871,10 @@ 84CBBE0B29228BA900D0DA61 /* StreamVideoTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8492B8722908024800006649 /* StreamVideoTestCase.swift */; }; 84CC05892A530C3F00EE9815 /* SpeakerManager_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC05882A530C3F00EE9815 /* SpeakerManager_Tests.swift */; }; 84CC058B2A531B0B00EE9815 /* CallSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC058A2A531B0B00EE9815 /* CallSettingsManager.swift */; }; + 84CD12162C73831000056640 /* CallRtmpBroadcastStartedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CD12082C73830F00056640 /* CallRtmpBroadcastStartedEvent.swift */; }; + 84CD12202C73831000056640 /* CallMissedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CD12122C73831000056640 /* CallMissedEvent.swift */; }; + 84CD12222C73831000056640 /* CallRtmpBroadcastStoppedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CD12142C73831000056640 /* CallRtmpBroadcastStoppedEvent.swift */; }; + 84CD12252C73840300056640 /* CallUserMutedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CD12242C73840300056640 /* CallUserMutedEvent.swift */; }; 84D114DA29F092E700BCCB0C /* CallController_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D114D929F092E700BCCB0C /* CallController_Tests.swift */; }; 84D2E37629DC856D001D2118 /* CallMemberRemovedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2E37029DC856C001D2118 /* CallMemberRemovedEvent.swift */; }; 84D2E37729DC856D001D2118 /* CallMemberUpdatedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2E37129DC856C001D2118 /* CallMemberUpdatedEvent.swift */; }; @@ -881,9 +892,10 @@ 84D6494429E9AD08002CA428 /* CallIngressResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6494229E9AD08002CA428 /* CallIngressResponse.swift */; }; 84D6494729E9F2D0002CA428 /* WebRTCClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6494629E9F2D0002CA428 /* WebRTCClient_Tests.swift */; }; 84D6E53A2B3AD10000D0056C /* RepeatingTimer_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6E5392B3AD10000D0056C /* RepeatingTimer_Tests.swift */; }; + 84D91E9C2C7CB0AA00B163A0 /* CallSessionParticipantCountsUpdatedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D91E9A2C7CB0AA00B163A0 /* CallSessionParticipantCountsUpdatedEvent.swift */; }; + 84D91E9D2C7CB0AA00B163A0 /* CallRtmpBroadcastFailedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D91E9B2C7CB0AA00B163A0 /* CallRtmpBroadcastFailedEvent.swift */; }; 84DC382D29A8B9EC00946713 /* CallParticipantMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC382C29A8B9EC00946713 /* CallParticipantMenuAction.swift */; }; 84DC382F29A8BB8D00946713 /* CallParticipantsInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC382E29A8BB8D00946713 /* CallParticipantsInfoViewModel.swift */; }; - 84DC388E29ADFCFD00946713 /* JSONEncodingHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC383C29ADFCFB00946713 /* JSONEncodingHelper.swift */; }; 84DC388F29ADFCFD00946713 /* CodableHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC383D29ADFCFB00946713 /* CodableHelper.swift */; }; 84DC389029ADFCFD00946713 /* SendEventResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC383F29ADFCFC00946713 /* SendEventResponse.swift */; }; 84DC389129ADFCFD00946713 /* VideoSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC384029ADFCFC00946713 /* VideoSettings.swift */; }; @@ -976,7 +988,6 @@ 84EA5D4328C1E944004D3531 /* AudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EA5D4228C1E944004D3531 /* AudioSession.swift */; }; 84EBA4A22A72B81100577297 /* BroadcastBufferConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EBA4A12A72B81100577297 /* BroadcastBufferConnection.swift */; }; 84ED240D286C9515002A3186 /* DemoCallContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ED240C286C9515002A3186 /* DemoCallContainerView.swift */; }; - 84F3B0DA289083E70088751D /* WebSocketConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F3B0D9289083E70088751D /* WebSocketConstants.swift */; }; 84F3B0DE28913E0F0088751D /* CallControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F3B0DD28913E0E0088751D /* CallControlsView.swift */; }; 84F3B0E0289150B10088751D /* CallParticipant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F3B0DF289150B10088751D /* CallParticipant.swift */; }; 84F3B0E228916FF20088751D /* CallParticipantsInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F3B0E128916FF20088751D /* CallParticipantsInfoView.swift */; }; @@ -1294,6 +1305,7 @@ 4049CE832BBBF8EF003D07D2 /* StreamAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamAsyncImage.swift; sourceTree = ""; }; 404A5CFA2AD5648100EF1C62 /* DemoChatModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoChatModifier.swift; sourceTree = ""; }; 4059C3412AAF0CE40006928E /* DemoChatViewModel+Injection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DemoChatViewModel+Injection.swift"; sourceTree = ""; }; + 405EFF102C7F120700AC5CE6 /* SFUMiddlewareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFUMiddlewareTests.swift; sourceTree = ""; }; 4063033E2AD847EC0091AE77 /* CallState_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallState_Tests.swift; sourceTree = ""; }; 406303412AD848000091AE77 /* CallParticipant_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallParticipant_Mock.swift; sourceTree = ""; }; 406583852B87694B00B4F979 /* BlurBackgroundVideoFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurBackgroundVideoFilter.swift; sourceTree = ""; }; @@ -1431,7 +1443,6 @@ 40F18B8D2BEBB65100ADF76E /* View+OptionalPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+OptionalPublisher.swift"; sourceTree = ""; }; 40F445AB2A9DFC13004BE3DA /* UserCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCredentials.swift; sourceTree = ""; }; 40F445AD2A9DFC34004BE3DA /* UserState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserState.swift; sourceTree = ""; }; - 40F445AF2A9DFC58004BE3DA /* CallKitState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallKitState.swift; sourceTree = ""; }; 40F445B12A9DFFBB004BE3DA /* User+Demo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+Demo.swift"; sourceTree = ""; }; 40F445B32A9E01B2004BE3DA /* AuthenticationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationProvider.swift; sourceTree = ""; }; 40F445BD2A9E0823004BE3DA /* RobotVoiceFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RobotVoiceFilter.swift; sourceTree = ""; }; @@ -1572,6 +1583,7 @@ 8415D3E0290B2AF2006E53CB /* outgoing.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = outgoing.m4a; sourceTree = ""; }; 8415D3E2290BC882006E53CB /* Sounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sounds.swift; sourceTree = ""; }; 841947972886D9CD0007B36E /* BundleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = ""; }; + 841AE1892C738CCC005B6560 /* LayoutSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutSettings.swift; sourceTree = ""; }; 841BAA182BD15CDC000C73E4 /* VideoQuality.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoQuality.swift; sourceTree = ""; }; 841BAA192BD15CDC000C73E4 /* CallTranscriptionStoppedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallTranscriptionStoppedEvent.swift; sourceTree = ""; }; 841BAA1A2BD15CDC000C73E4 /* SFULocationResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SFULocationResponse.swift; sourceTree = ""; }; @@ -1696,8 +1708,15 @@ 8442993929422BEA0037232A /* BackportStateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackportStateObject.swift; sourceTree = ""; }; 8442993B294232360037232A /* IncomingCallView_iOS13.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView_iOS13.swift; sourceTree = ""; }; 844299402942394C0037232A /* VideoView_iOS13.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoView_iOS13.swift; sourceTree = ""; }; - 844542EF2C296AAA001D5ADF /* BackstageSettingsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackstageSettingsResponse.swift; sourceTree = ""; }; 8446AF902A4D84F4002AB07B /* Retries_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Retries_Tests.swift; sourceTree = ""; }; + 8449823F2C738A830029734D /* DeleteRecordingResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteRecordingResponse.swift; sourceTree = ""; }; + 844982402C738A830029734D /* DeleteCallResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteCallResponse.swift; sourceTree = ""; }; + 844982412C738A830029734D /* StartRTMPBroadcastsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartRTMPBroadcastsRequest.swift; sourceTree = ""; }; + 844982422C738A830029734D /* StopRTMPBroadcastsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StopRTMPBroadcastsResponse.swift; sourceTree = ""; }; + 844982432C738A830029734D /* DeleteCallRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteCallRequest.swift; sourceTree = ""; }; + 844982442C738A830029734D /* DeleteTranscriptionResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteTranscriptionResponse.swift; sourceTree = ""; }; + 844982452C738A830029734D /* StartRTMPBroadcastsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartRTMPBroadcastsResponse.swift; sourceTree = ""; }; + 844982462C738A830029734D /* StopAllRTMPBroadcastsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StopAllRTMPBroadcastsResponse.swift; sourceTree = ""; }; 844ADA662AD3F21000769F6A /* GoogleSignIn.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = GoogleSignIn.plist; sourceTree = ""; }; 844ADA682AD3F78F00769F6A /* GoogleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleHelper.swift; sourceTree = ""; }; 844ADA6A2AD4439D00769F6A /* DemoCallsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoCallsView.swift; sourceTree = ""; }; @@ -1887,6 +1906,10 @@ 84C4003E29E3F446007B69C2 /* ConnectedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectedEvent.swift; sourceTree = ""; }; 84CC05882A530C3F00EE9815 /* SpeakerManager_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeakerManager_Tests.swift; sourceTree = ""; }; 84CC058A2A531B0B00EE9815 /* CallSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettingsManager.swift; sourceTree = ""; }; + 84CD12082C73830F00056640 /* CallRtmpBroadcastStartedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRtmpBroadcastStartedEvent.swift; sourceTree = ""; }; + 84CD12122C73831000056640 /* CallMissedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallMissedEvent.swift; sourceTree = ""; }; + 84CD12142C73831000056640 /* CallRtmpBroadcastStoppedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRtmpBroadcastStoppedEvent.swift; sourceTree = ""; }; + 84CD12242C73840300056640 /* CallUserMutedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallUserMutedEvent.swift; sourceTree = ""; }; 84D114D929F092E700BCCB0C /* CallController_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController_Tests.swift; sourceTree = ""; }; 84D2E37029DC856C001D2118 /* CallMemberRemovedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallMemberRemovedEvent.swift; sourceTree = ""; }; 84D2E37129DC856C001D2118 /* CallMemberUpdatedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallMemberUpdatedEvent.swift; sourceTree = ""; }; @@ -1904,9 +1927,10 @@ 84D6494229E9AD08002CA428 /* CallIngressResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallIngressResponse.swift; sourceTree = ""; }; 84D6494629E9F2D0002CA428 /* WebRTCClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCClient_Tests.swift; sourceTree = ""; }; 84D6E5392B3AD10000D0056C /* RepeatingTimer_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatingTimer_Tests.swift; sourceTree = ""; }; + 84D91E9A2C7CB0AA00B163A0 /* CallSessionParticipantCountsUpdatedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallSessionParticipantCountsUpdatedEvent.swift; sourceTree = ""; }; + 84D91E9B2C7CB0AA00B163A0 /* CallRtmpBroadcastFailedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRtmpBroadcastFailedEvent.swift; sourceTree = ""; }; 84DC382C29A8B9EC00946713 /* CallParticipantMenuAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallParticipantMenuAction.swift; sourceTree = ""; }; 84DC382E29A8BB8D00946713 /* CallParticipantsInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallParticipantsInfoViewModel.swift; sourceTree = ""; }; - 84DC383C29ADFCFB00946713 /* JSONEncodingHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONEncodingHelper.swift; sourceTree = ""; }; 84DC383D29ADFCFB00946713 /* CodableHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableHelper.swift; sourceTree = ""; }; 84DC383F29ADFCFC00946713 /* SendEventResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendEventResponse.swift; sourceTree = ""; }; 84DC384029ADFCFC00946713 /* VideoSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoSettings.swift; sourceTree = ""; }; @@ -1996,7 +2020,6 @@ 84EBA4A12A72B81100577297 /* BroadcastBufferConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BroadcastBufferConnection.swift; sourceTree = ""; }; 84EBAA92288C137E00BE3176 /* Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modifiers.swift; sourceTree = ""; }; 84ED240C286C9515002A3186 /* DemoCallContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoCallContainerView.swift; sourceTree = ""; }; - 84F3B0D9289083E70088751D /* WebSocketConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketConstants.swift; sourceTree = ""; }; 84F3B0DD28913E0E0088751D /* CallControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallControlsView.swift; sourceTree = ""; }; 84F3B0DF289150B10088751D /* CallParticipant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallParticipant.swift; sourceTree = ""; }; 84F3B0E128916FF20088751D /* CallParticipantsInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallParticipantsInfoView.swift; sourceTree = ""; }; @@ -2243,7 +2266,6 @@ 401A64B02A9DF83200534ED1 /* TokenResponse.swift */, 40F445AB2A9DFC13004BE3DA /* UserCredentials.swift */, 40F445AD2A9DFC34004BE3DA /* UserState.swift */, - 40F445AF2A9DFC58004BE3DA /* CallKitState.swift */, 40F445D32A9E2051004BE3DA /* Reaction.swift */, ); path = Models; @@ -3467,7 +3489,6 @@ 84A7E1952883661A00526C98 /* BackgroundTaskScheduler.swift */, 84A7E1852883632100526C98 /* ConnectionStatus.swift */, 84A7E1A92883E4AD00526C98 /* APIKey.swift */, - 84F3B0D9289083E70088751D /* WebSocketConstants.swift */, ); path = Client; sourceTree = ""; @@ -3944,7 +3965,6 @@ 84DC383D29ADFCFB00946713 /* CodableHelper.swift */, 84DC388929ADFCFC00946713 /* Extensions.swift */, 84DC388A29ADFCFC00946713 /* JSONDataEncoding.swift */, - 84DC383C29ADFCFB00946713 /* JSONEncodingHelper.swift */, 84DC388C29ADFCFC00946713 /* Models.swift */, 84DC388829ADFCFC00946713 /* OpenISO8601DateFormatter.swift */, ); @@ -4063,6 +4083,7 @@ 8414081229F28B5600FF2D7C /* RTCConfiguration_Tests.swift */, 8446AF902A4D84F4002AB07B /* Retries_Tests.swift */, 841FF5042A5D815700809BBB /* VideoCapturerUtils_Tests.swift */, + 405EFF102C7F120700AC5CE6 /* SFUMiddlewareTests.swift */, ); path = WebRTC; sourceTree = ""; @@ -4070,7 +4091,21 @@ 84DC383E29ADFCFC00946713 /* Models */ = { isa = PBXGroup; children = ( - 844542EF2C296AAA001D5ADF /* BackstageSettingsResponse.swift */, + 84D91E9B2C7CB0AA00B163A0 /* CallRtmpBroadcastFailedEvent.swift */, + 84D91E9A2C7CB0AA00B163A0 /* CallSessionParticipantCountsUpdatedEvent.swift */, + 841AE1892C738CCC005B6560 /* LayoutSettings.swift */, + 844982432C738A830029734D /* DeleteCallRequest.swift */, + 844982402C738A830029734D /* DeleteCallResponse.swift */, + 8449823F2C738A830029734D /* DeleteRecordingResponse.swift */, + 844982442C738A830029734D /* DeleteTranscriptionResponse.swift */, + 844982412C738A830029734D /* StartRTMPBroadcastsRequest.swift */, + 844982452C738A830029734D /* StartRTMPBroadcastsResponse.swift */, + 844982462C738A830029734D /* StopAllRTMPBroadcastsResponse.swift */, + 844982422C738A830029734D /* StopRTMPBroadcastsResponse.swift */, + 84CD12242C73840300056640 /* CallUserMutedEvent.swift */, + 84CD12122C73831000056640 /* CallMissedEvent.swift */, + 84CD12082C73830F00056640 /* CallRtmpBroadcastStartedEvent.swift */, + 84CD12142C73831000056640 /* CallRtmpBroadcastStoppedEvent.swift */, 845C09962C11AAA100F725B3 /* RejectCallRequest.swift */, 845C09822C0DEB5C00F725B3 /* LimitsSettingsRequest.swift */, 845C09832C0DEB5C00F725B3 /* LimitsSettingsResponse.swift */, @@ -5086,7 +5121,6 @@ 84093811288A90390089A35B /* DetailedCallingView.swift in Sources */, 84ED240D286C9515002A3186 /* DemoCallContainerView.swift in Sources */, 40F445B22A9DFFBB004BE3DA /* User+Demo.swift in Sources */, - 40F445B02A9DFC58004BE3DA /* CallKitState.swift in Sources */, 40D946452AA5F67E00C8861B /* DemoCallingTopView.swift in Sources */, 847B47B72A260CF1000714CE /* CustomCallView.swift in Sources */, 40F445EA2A9E297B004BE3DA /* CallStateResponseFields+Identifiable.swift in Sources */, @@ -5229,7 +5263,6 @@ 407F29FF2AA6011500C3EAF8 /* MemoryLogViewer.swift in Sources */, 40AB35572B738C7100E465CC /* CallsViewModel.swift in Sources */, 408D29AE2B6D681000885473 /* SnapshotTrigger.swift in Sources */, - 406A8E922AA1D7B3001F598A /* CallKitState.swift in Sources */, 406A8E8D2AA1D78C001F598A /* AppEnvironment.swift in Sources */, 40AB35442B738C4100E465CC /* DemoReactionButton.swift in Sources */, 849322612908385C0013C029 /* HomeViewController.swift in Sources */, @@ -5265,12 +5298,12 @@ 8454A3192AAB374B00A012C6 /* CallStatsReport.swift in Sources */, 84E5C51C2A013C440003A27A /* PushNotificationsConfig.swift in Sources */, 84A7E184288362DF00526C98 /* Atomic.swift in Sources */, + 8449824E2C738A830029734D /* StopAllRTMPBroadcastsResponse.swift in Sources */, 84D2E37729DC856D001D2118 /* CallMemberUpdatedEvent.swift in Sources */, 40149DD02B7E839500473176 /* AudioSessionProtocol.swift in Sources */, 8409465B29AF4EEC007AF5BF /* ListRecordingsResponse.swift in Sources */, 8490DD21298D4ADF007E53D2 /* StreamJsonDecoder.swift in Sources */, 84C4004229E3F446007B69C2 /* ConnectedEvent.swift in Sources */, - 844542F02C296AAB001D5ADF /* BackstageSettingsResponse.swift in Sources */, 84DC389C29ADFCFD00946713 /* GetOrCreateCallResponse.swift in Sources */, 4065839B2B877ADA00B4F979 /* CIImage+Sendable.swift in Sources */, 84DCA2242A3A0F0D000C3411 /* HTTPClient.swift in Sources */, @@ -5291,8 +5324,8 @@ 8409465629AF4EEC007AF5BF /* CallReactionEvent.swift in Sources */, 84DCA21F2A39DA15000C3411 /* APIHelper.swift in Sources */, 84DC38D329ADFCFD00946713 /* CallEndedEvent.swift in Sources */, + 84D91E9D2C7CB0AA00B163A0 /* CallRtmpBroadcastFailedEvent.swift in Sources */, 84A737D028F4716E001A6769 /* models.pb.swift in Sources */, - 84DC388E29ADFCFD00946713 /* JSONEncodingHelper.swift in Sources */, 846D16222A52B8D00036CE4C /* MicrophoneManager.swift in Sources */, 842E70DB2B91BE1700D2D68B /* ListTranscriptionsResponse.swift in Sources */, 4012B1902BFCA4D3006B0031 /* StreamCallStateMachine+AcceptedStage.swift in Sources */, @@ -5322,6 +5355,7 @@ 8490DD1F298D39D9007E53D2 /* JsonEventDecoder.swift in Sources */, 40FB15192BF77EE700D5E580 /* StreamCallStateMachine+IdleStage.swift in Sources */, 84BAD7842A6C01AF00733156 /* BroadcastBufferReader.swift in Sources */, + 84D91E9C2C7CB0AA00B163A0 /* CallSessionParticipantCountsUpdatedEvent.swift in Sources */, 846E4AF529CDEA66003733AB /* ConnectUserDetailsRequest.swift in Sources */, 846D16262A52CE8C0036CE4C /* SpeakerManager.swift in Sources */, 841BAA3A2BD15CDE000C73E4 /* Location.swift in Sources */, @@ -5333,11 +5367,13 @@ 841BAA332BD15CDE000C73E4 /* SFULocationResponse.swift in Sources */, 84DC38D129ADFCFD00946713 /* Credentials.swift in Sources */, 84A7E1B02883E73100526C98 /* EventBatcher.swift in Sources */, + 84CD12222C73831000056640 /* CallRtmpBroadcastStoppedEvent.swift in Sources */, 40FB15212BF78FA100D5E580 /* Publisher+NextValue.swift in Sources */, 84BAD77C2A6BFF4300733156 /* BroadcastBufferReaderConnection.swift in Sources */, 84DC38DB29ADFCFD00946713 /* JSONDataEncoding.swift in Sources */, 40FB15112BF77D5800D5E580 /* StreamStateMachineStage.swift in Sources */, 8496A9A629CC500F00F15FF1 /* StreamVideoCaptureHandler.swift in Sources */, + 84CD12162C73831000056640 /* CallRtmpBroadcastStartedEvent.swift in Sources */, 8411925E28C5E5D00074EF88 /* DefaultRTCConfiguration.swift in Sources */, 8409465929AF4EEC007AF5BF /* SendReactionResponse.swift in Sources */, 8412903729DDD1ED00C70A6D /* UpdateCallMembersResponse.swift in Sources */, @@ -5347,6 +5383,7 @@ 406583992B877AB400B4F979 /* CIImage+Resize.swift in Sources */, 84EA5D4328C1E944004D3531 /* AudioSession.swift in Sources */, 841BAA382BD15CDE000C73E4 /* CallTimeline.swift in Sources */, + 8449824D2C738A830029734D /* StartRTMPBroadcastsResponse.swift in Sources */, 84DC38C629ADFCFD00946713 /* CallRejectedEvent.swift in Sources */, 84DC38B729ADFCFD00946713 /* CallRecordingStartedEvent.swift in Sources */, 40F161AB2A4C6B5C00846E3E /* ScreenSharingSession.swift in Sources */, @@ -5366,6 +5403,7 @@ 406AF2012AF3D98F00ED4D0C /* SimulatorScreenCapturer.swift in Sources */, 84A7E18A2883638200526C98 /* URLSessionWebSocketEngine.swift in Sources */, 40C4DF492C1C2C210035DBC2 /* Publisher+WeakAssign.swift in Sources */, + 844982472C738A830029734D /* DeleteRecordingResponse.swift in Sources */, 408679F72BD12F1000D027E0 /* AudioFilter.swift in Sources */, 8456E6D2287EC343004E180E /* ConsoleLogDestination.swift in Sources */, 84DC38A529ADFCFD00946713 /* SFUResponse.swift in Sources */, @@ -5374,6 +5412,7 @@ 4089378B2C062B17000EEB69 /* StreamUUIDFactory.swift in Sources */, 84DC38A829ADFCFD00946713 /* QueryCallsResponse.swift in Sources */, 840F59912A77FDCB00EF3EB2 /* HLSSettingsRequest.swift in Sources */, + 8449824C2C738A830029734D /* DeleteTranscriptionResponse.swift in Sources */, 842B8E1B2A2DFED900863A87 /* CallSessionStartedEvent.swift in Sources */, 40FA12F22B76AC8300CE3EC9 /* RTCCVPixelBuffer+Convenience.swift in Sources */, 8490032629D308A000AD9BB4 /* TranscriptionSettings.swift in Sources */, @@ -5384,6 +5423,7 @@ 84B9A56D29112F39004DE31A /* EndpointConfig.swift in Sources */, 8469593829BB6B4E00134EA0 /* GetEdgesResponse.swift in Sources */, 84DC389A29ADFCFD00946713 /* APIError.swift in Sources */, + 8449824B2C738A830029734D /* DeleteCallRequest.swift in Sources */, 84AF64DB287C7A2C0012A503 /* ErrorPayload.swift in Sources */, 84A7E1812883629700526C98 /* WebSocketPingController.swift in Sources */, 84DCA2232A39E432000C3411 /* AuthMiddlewares.swift in Sources */, @@ -5408,18 +5448,19 @@ 842E70D62B91BE1700D2D68B /* StartRecordingRequest.swift in Sources */, 840042CB2A701C2000917B30 /* BroadcastUtils.swift in Sources */, 844ECF4F2A33458A0023263C /* Member.swift in Sources */, + 84CD12252C73840300056640 /* CallUserMutedEvent.swift in Sources */, 84DC38AC29ADFCFD00946713 /* CallAcceptedEvent.swift in Sources */, 84FC2C2828AD350100181490 /* WebRTCEvents.swift in Sources */, 84DC38A129ADFCFD00946713 /* BlockUserResponse.swift in Sources */, 4012B1942BFCAC1C006B0031 /* StreamCallStateMachine+RejectingStage.swift in Sources */, 84DCA2152A38A79E000C3411 /* Token.swift in Sources */, 40FB151B2BF77EEE00D5E580 /* StreamCallStateMachine+JoiningStage.swift in Sources */, - 84F3B0DA289083E70088751D /* WebSocketConstants.swift in Sources */, 840F59902A77FDCB00EF3EB2 /* UnpinRequest.swift in Sources */, 8440861E2901A1700027849C /* SfuMiddleware.swift in Sources */, 40FB15172BF77EA600D5E580 /* StreamCallStateMachine.swift in Sources */, 84DC38C829ADFCFD00946713 /* Device.swift in Sources */, 8412903629DDD1ED00C70A6D /* UpdateCallMembersRequest.swift in Sources */, + 8449824A2C738A830029734D /* StopRTMPBroadcastsResponse.swift in Sources */, 84CC058B2A531B0B00EE9815 /* CallSettingsManager.swift in Sources */, 84DC38B929ADFCFD00946713 /* MemberRequest.swift in Sources */, 84DC38BE29ADFCFD00946713 /* CallSettingsRequest.swift in Sources */, @@ -5542,6 +5583,7 @@ 847BE09C29DADE0100B55D21 /* Call.swift in Sources */, 848CCCEF2AB8ED8F002E83A2 /* ThumbnailsSettings.swift in Sources */, 40C4DF4D2C1C2CD80035DBC2 /* DefaultParticipantAutoLeavePolicy.swift in Sources */, + 844982492C738A830029734D /* StartRTMPBroadcastsRequest.swift in Sources */, 84FC2C2228ACF2E000181490 /* PeerConnection.swift in Sources */, 401A0F032AB1C1B600BE2DBD /* ThermalStateObserver.swift in Sources */, 84D2E37929DC856D001D2118 /* CallMemberAddedEvent.swift in Sources */, @@ -5558,6 +5600,7 @@ 84530C6C2A3C4E0700F2678E /* CallState.swift in Sources */, 84DC38C729ADFCFD00946713 /* UpdateCallResponse.swift in Sources */, 84A7E1AA2883E4AD00526C98 /* APIKey.swift in Sources */, + 841AE18A2C738CCC005B6560 /* LayoutSettings.swift in Sources */, 848CCCEE2AB8ED8F002E83A2 /* CallUserMuted.swift in Sources */, 435F01B32A501148009CD0BD /* OwnCapability+Identifiable.swift in Sources */, 40FB150F2BF77CEC00D5E580 /* StreamStateMachine.swift in Sources */, @@ -5565,6 +5608,7 @@ 84E4F7D1294CB5F300DD4CE3 /* ConnectionQuality.swift in Sources */, 848CCCE72AB8ED8F002E83A2 /* ThumbnailsSettingsRequest.swift in Sources */, 8206D8532A5FF3260099F5EC /* SystemEnvironment+Version.swift in Sources */, + 84CD12202C73831000056640 /* CallMissedEvent.swift in Sources */, 842B8E1C2A2DFED900863A87 /* AcceptCallResponse.swift in Sources */, 40F18B8E2BEBB65100ADF76E /* View+OptionalPublisher.swift in Sources */, 842B8E262A2DFED900863A87 /* CallParticipantResponse.swift in Sources */, @@ -5590,6 +5634,7 @@ 8490032429D308A000AD9BB4 /* RingSettings.swift in Sources */, 840F598E2A77FDCB00EF3EB2 /* BroadcastSettingsRequest.swift in Sources */, 842B8E272A2DFED900863A87 /* CallSessionEndedEvent.swift in Sources */, + 844982482C738A830029734D /* DeleteCallResponse.swift in Sources */, 84DC38CF29ADFCFD00946713 /* QueryCallsRequest.swift in Sources */, 84A7E1822883629700526C98 /* RetryStrategy.swift in Sources */, 841BAA402BD15CDE000C73E4 /* UserStats.swift in Sources */, @@ -5721,6 +5766,7 @@ 8251E62B2A17BEB400E7257A /* StreamVideoTestResources.swift in Sources */, 403FB14C2BFE14760047A696 /* Publisher_NextTests.swift in Sources */, 84A4DCBB2A41DC6E00B1D1BF /* AsyncAssert.swift in Sources */, + 405EFF112C7F120700AC5CE6 /* SFUMiddlewareTests.swift in Sources */, 84CBBE0B29228BA900D0DA61 /* StreamVideoTestCase.swift in Sources */, 40F017512BBEF00500E89FD1 /* ScreensharingSettings+Dummy.swift in Sources */, 403FB14E2BFE18D10047A696 /* StreamStateMachine_Tests.swift in Sources */, diff --git a/StreamVideoArtifacts.json b/StreamVideoArtifacts.json index 821056842..1f156071e 100644 --- a/StreamVideoArtifacts.json +++ b/StreamVideoArtifacts.json @@ -1 +1 @@ -{"0.4.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.4.2/StreamVideo-All.zip","0.5.0":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.0/StreamVideo-All.zip","0.5.1":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.1/StreamVideo-All.zip","0.5.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.2/StreamVideo-All.zip","0.5.3":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.3/StreamVideo-All.zip","1.0.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.0/StreamVideo-All.zip","1.0.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.1/StreamVideo-All.zip","1.0.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.2/StreamVideo-All.zip","1.0.3":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.3/StreamVideo-All.zip","1.0.4":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.4/StreamVideo-All.zip","1.0.5":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.5/StreamVideo-All.zip","1.0.6":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.6/StreamVideo-All.zip","1.0.7":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.7/StreamVideo-All.zip","1.0.8":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.8/StreamVideo-All.zip","1.0.9":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.9/StreamVideo-All.zip"} \ No newline at end of file +{"0.4.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.4.2/StreamVideo-All.zip","0.5.0":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.0/StreamVideo-All.zip","0.5.1":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.1/StreamVideo-All.zip","0.5.2":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.2/StreamVideo-All.zip","0.5.3":"https://github.com/GetStream/stream-video-swift/releases/download/0.5.3/StreamVideo-All.zip","1.0.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.0/StreamVideo-All.zip","1.0.1":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.1/StreamVideo-All.zip","1.0.2":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.2/StreamVideo-All.zip","1.0.3":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.3/StreamVideo-All.zip","1.0.4":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.4/StreamVideo-All.zip","1.0.5":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.5/StreamVideo-All.zip","1.0.6":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.6/StreamVideo-All.zip","1.0.7":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.7/StreamVideo-All.zip","1.0.8":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.8/StreamVideo-All.zip","1.0.9":"https://github.com/GetStream/stream-video-swift/releases/download/1.0.9/StreamVideo-All.zip","1.10.0":"https://github.com/GetStream/stream-video-swift/releases/download/1.10.0/StreamVideo-All.zip"} \ No newline at end of file diff --git a/StreamVideoSwiftUI-XCFramework.podspec b/StreamVideoSwiftUI-XCFramework.podspec index c669109b3..77e0228e5 100644 --- a/StreamVideoSwiftUI-XCFramework.podspec +++ b/StreamVideoSwiftUI-XCFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamVideoSwiftUI-XCFramework' - spec.version = '1.0.9' + spec.version = '1.10.0' spec.summary = 'StreamVideo SwiftUI Video Components' spec.description = 'StreamVideoSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamVideo SDK.' diff --git a/StreamVideoSwiftUI.podspec b/StreamVideoSwiftUI.podspec index 0fd735ec8..37d725b08 100644 --- a/StreamVideoSwiftUI.podspec +++ b/StreamVideoSwiftUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamVideoSwiftUI' - spec.version = '1.0.9' + spec.version = '1.10.0' spec.summary = 'StreamVideo SwiftUI Video Components' spec.description = 'StreamVideoSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamVideo SDK.' diff --git a/StreamVideoTests/CallKit/CallKitAdapterTests.swift b/StreamVideoTests/CallKit/CallKitAdapterTests.swift index 530648a29..5e9c6cd26 100644 --- a/StreamVideoTests/CallKit/CallKitAdapterTests.swift +++ b/StreamVideoTests/CallKit/CallKitAdapterTests.swift @@ -67,6 +67,16 @@ final class CallKitAdapterTests: XCTestCase { XCTAssertTrue(callKitPushNotificationAdapter.unregisterWasCalled) } + // MARK: - callSettings updated + + func test_callSettings_callKitServiceReceivedTheUpdatedValue() { + let callSettings = CallSettings(audioOn: false, videoOn: true) + + subject.callSettings = callSettings + + XCTAssertEqual(callKitService.callSettings, callSettings) + } + // MARK: - Private Helpers private func makeStreamVideo() async throws -> StreamVideo { diff --git a/StreamVideoTests/CallKit/CallKitServiceTests.swift b/StreamVideoTests/CallKit/CallKitServiceTests.swift index 432628f04..4f6912c8e 100644 --- a/StreamVideoTests/CallKit/CallKitServiceTests.swift +++ b/StreamVideoTests/CallKit/CallKitServiceTests.swift @@ -276,6 +276,44 @@ final class CallKitServiceTests: XCTestCase, @unchecked Sendable { } } + // MARK: - accept + + @MainActor + func test_accept_callWasJoinedAsExpected() async throws { + let customCallSettings = CallSettings(audioOn: false, videoOn: true) + subject.callSettings = customCallSettings + + let firstCallUUID = UUID() + uuidFactory.getResult = firstCallUUID + let call = stubCall(response: defaultGetCallResponse) + subject.streamVideo = mockedStreamVideo + + subject.reportIncomingCall( + cid, + localizedCallerName: localizedCallerName, + callerId: callerId + ) { _ in } + + await waitExpectation(timeout: 1) + + // Accept call + subject.provider( + callProvider, + perform: CXAnswerCallAction( + call: firstCallUUID + ) + ) + + await waitExpectation(timeout: 1) + + XCTAssertEqual(call.stubbedFunctionInput[.join]?.count, 1) + let input = try XCTUnwrap(call.stubbedFunctionInput[.join]?.first) + switch input { + case let .join(_, _, _, _, callSettings): + XCTAssertEqual(callSettings, customCallSettings) + } + } + // MARK: - callAccepted @MainActor diff --git a/StreamVideoTests/Controllers/CallsController_Tests.swift b/StreamVideoTests/Controllers/CallsController_Tests.swift index 22cda991e..8ce56a0c3 100644 --- a/StreamVideoTests/Controllers/CallsController_Tests.swift +++ b/StreamVideoTests/Controllers/CallsController_Tests.swift @@ -75,11 +75,7 @@ final class CallsController_Tests: ControllerTestCase { streamVideo?.state.connection = .disconnected() try await waitForCallEvent() streamVideo?.state.connection = .connected - try await waitForCallEvent() - - // Then - // Calls should be rewatched - XCTAssertEqual(httpClient.requestCounter, 2) + try await fulfillment { self.httpClient.requestCounter == 2 } } func test_callsController_noWatchingCalls() async throws { diff --git a/StreamVideoTests/Mock/MockCall.swift b/StreamVideoTests/Mock/MockCall.swift index 1b336e642..7ff484e01 100644 --- a/StreamVideoTests/Mock/MockCall.swift +++ b/StreamVideoTests/Mock/MockCall.swift @@ -9,14 +9,27 @@ final class MockCall: Call, Mockable { typealias FunctionKey = MockCallFunctionKey - enum MockCallFunctionKey: Hashable { + enum MockCallFunctionKey: Hashable, CaseIterable { case get case accept case join } + enum MockCallFunctionInputKey { + case join( + create: Bool, + options: CreateCallOptions?, + ring: Bool, + notify: Bool, + callSettings: CallSettings? + ) + } + var stubbedProperty: [String: Any] var stubbedFunction: [FunctionKey: Any] = [:] + @Atomic var stubbedFunctionInput: [FunctionKey: [MockCallFunctionInputKey]] = MockCallFunctionKey + .allCases + .reduce(into: [FunctionKey: [MockCallFunctionInputKey]]()) { $0[$1] = [] } override var state: CallState { get { self[dynamicMember: \.state] } @@ -66,6 +79,15 @@ final class MockCall: Call, Mockable { notify: Bool = false, callSettings: CallSettings? = nil ) async throws -> JoinCallResponse { + stubbedFunctionInput[.join]?.append( + .join( + create: create, + options: options, + ring: ring, + notify: notify, + callSettings: callSettings + ) + ) if let stub = stubbedFunction[.join] as? JoinCallResponse { return stub } else { diff --git a/StreamVideoTests/Mock/MockResponseBuilder.swift b/StreamVideoTests/Mock/MockResponseBuilder.swift index d4355c2de..61ee1bccd 100644 --- a/StreamVideoTests/Mock/MockResponseBuilder.swift +++ b/StreamVideoTests/Mock/MockResponseBuilder.swift @@ -175,10 +175,12 @@ class MockResponseBuilder { ) -> CallSessionResponse { CallSessionResponse( acceptedBy: acceptedBy, + anonymousParticipantCount: 0, endedAt: liveEndedAt, id: cid, liveEndedAt: liveEndedAt, liveStartedAt: liveStartedAt, + missedBy: [:], participants: [], participantsCountByRole: [:], rejectedBy: rejectedBy, diff --git a/StreamVideoTests/Mock/MockStreamVideo.swift b/StreamVideoTests/Mock/MockStreamVideo.swift index 51a42837f..463d22e6d 100644 --- a/StreamVideoTests/Mock/MockStreamVideo.swift +++ b/StreamVideoTests/Mock/MockStreamVideo.swift @@ -60,7 +60,11 @@ final class MockStreamVideo: StreamVideo, Mockable { stubbedFunction[function] = value } - override func call(callType: String, callId: String) -> Call { + override func call( + callType: String, + callId: String, + callSettings: CallSettings? = nil + ) -> Call { stubbedFunction[.call] as! Call } diff --git a/StreamVideoTests/StreamVideoTestCase.swift b/StreamVideoTests/StreamVideoTestCase.swift index 6edbfb631..537a1299b 100644 --- a/StreamVideoTests/StreamVideoTestCase.swift +++ b/StreamVideoTests/StreamVideoTestCase.swift @@ -7,14 +7,14 @@ import XCTest open class StreamVideoTestCase: XCTestCase { - public var streamVideo: StreamVideo! + public internal(set) var streamVideo: StreamVideo! var httpClient: HTTPClient_Mock! = HTTPClient_Mock() override open func setUp() { super.setUp() streamVideo = StreamVideo.mock(httpClient: httpClient) } - + override open func tearDown() async throws { try await super.tearDown() await streamVideo?.disconnect() @@ -29,7 +29,7 @@ open class StreamVideoTestCase: XCTestCase { } // TODO: replace this with something a bit better - func waitForCallEvent(nanoseconds: UInt64 = 500_000_000) async throws { + func waitForCallEvent(nanoseconds: UInt64 = 5_000_000_000) async throws { try await Task.sleep(nanoseconds: nanoseconds) } } diff --git a/StreamVideoTests/Utilities/Dummy/CallSessionResponse+Dummy.swift b/StreamVideoTests/Utilities/Dummy/CallSessionResponse+Dummy.swift index a4b53b249..91490d7f8 100644 --- a/StreamVideoTests/Utilities/Dummy/CallSessionResponse+Dummy.swift +++ b/StreamVideoTests/Utilities/Dummy/CallSessionResponse+Dummy.swift @@ -19,10 +19,12 @@ extension CallSessionResponse { ) -> CallSessionResponse { .init( acceptedBy: acceptedBy, + anonymousParticipantCount: 0, endedAt: endedAt, id: id, liveEndedAt: liveEndedAt, liveStartedAt: liveStartedAt, + missedBy: [:], participants: participants, participantsCountByRole: participantsCountByRole, rejectedBy: rejectedBy, diff --git a/StreamVideoTests/WebRTC/SFUMiddlewareTests.swift b/StreamVideoTests/WebRTC/SFUMiddlewareTests.swift new file mode 100644 index 000000000..e3d03f1a6 --- /dev/null +++ b/StreamVideoTests/WebRTC/SFUMiddlewareTests.swift @@ -0,0 +1,62 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +import Foundation +@testable import StreamVideo +import XCTest + +final class SFUMiddlewareTests: XCTestCase { + + private lazy var sessionID: String! = .unique + private lazy var user: User! = .init(id: .unique) + private lazy var state: WebRTCClient.State! = .init() + private lazy var signalService: Stream_Video_Sfu_Signal_SignalServer! = .init( + httpClient: HTTPClient_Mock(), + apiKey: .unique, + hostname: .unique, + token: .unique + ) + private lazy var participantThreshold: Int! = 10 + + private lazy var subject: SfuMiddleware! = .init( + sessionID: sessionID, + user: user, + state: state, + signalService: signalService, + participantThreshold: participantThreshold + ) + + override func tearDown() { + subject = nil + user = nil + state = nil + signalService = nil + participantThreshold = nil + super.tearDown() + } + + // MARK: - handle(event:) + + func test_handle_healthCheck_passesCorrectValuesForParticipantsAndAnonymous() async throws { + var participantCount = Stream_Video_Sfu_Models_ParticipantCount() + participantCount.total = 10 + participantCount.anonymous = 3 + var healthCheckInfo = Stream_Video_Sfu_Event_HealthCheckResponse() + healthCheckInfo.participantCount = participantCount + let participantsCountExpectation = expectation(description: "onParticipantCountUpdated was called") + let anonymousCountExpectation = expectation(description: "onAnonymousParticipantCountUpdated was called") + subject.onParticipantCountUpdated = { + XCTAssertEqual($0, participantCount.total) + participantsCountExpectation.fulfill() + } + subject.onAnonymousParticipantCountUpdated = { + XCTAssertEqual($0, participantCount.anonymous) + anonymousCountExpectation.fulfill() + } + + _ = subject.handle(event: .sfuEvent(.healthCheckResponse(healthCheckInfo))) + + await fulfillment(of: [participantsCountExpectation, anonymousCountExpectation]) + } +} diff --git a/StreamVideoUIKit-XCFramework.podspec b/StreamVideoUIKit-XCFramework.podspec index 3fe883c3e..f3a3d8e63 100644 --- a/StreamVideoUIKit-XCFramework.podspec +++ b/StreamVideoUIKit-XCFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamVideoUIKit-XCFramework' - spec.version = '1.0.9' + spec.version = '1.10.0' spec.summary = 'StreamVideo UIKit Video Components' spec.description = 'StreamVideoUIKit SDK offers flexible UIKit components able to display data provided by StreamVideo SDK.' diff --git a/StreamVideoUIKit.podspec b/StreamVideoUIKit.podspec index e85ad060e..b35928e12 100644 --- a/StreamVideoUIKit.podspec +++ b/StreamVideoUIKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamVideoUIKit' - spec.version = '1.0.9' + spec.version = '1.10.0' spec.summary = 'StreamVideo UIKit Video Components' spec.description = 'StreamVideoUIKit SDK offers flexible UIKit components able to display data provided by StreamVideo SDK.' diff --git a/docusaurus/docs/iOS/03-guides/02-joining-creating-calls.mdx b/docusaurus/docs/iOS/03-guides/02-joining-creating-calls.mdx index d87d7cbf1..3452e47a8 100644 --- a/docusaurus/docs/iOS/03-guides/02-joining-creating-calls.mdx +++ b/docusaurus/docs/iOS/03-guides/02-joining-creating-calls.mdx @@ -31,6 +31,33 @@ let call = streamVideo.call(callType: "default", callId: "123") let result = try await call.join() ``` +### Create and join a call + +For convenience, you can create and join a call in a single operation. One of the flags you can provide there is `create`. +Set this to `true` if you want to enable creating new call. Set it to `false` if you only want to join an existing call. + +```swift +try await call.join(create: true) +``` + +### Leave call + +To leave a call, you can use the `leave` method: + +```swift +call.leave() +``` + +### End call + +Ending a call requires a [special permission](../permissions-and-moderation). This action terminates the call for everyone. + +```typescript +try await call.end() +``` + +Only users with special permission can join an ended call. + ### Call CRUD Basic CRUD operations are available on the call object @@ -131,4 +158,41 @@ You can filter the member list on these fields, and sort on the selected fields. | `role` | The member's role. | No | | `custom` | The custom data on the member. | No | | `created_at` | When the member was created. | Yes | -| `updated_at` | When the member was last updated. | No | \ No newline at end of file +| `updated_at` | When the member was last updated. | No | + +## Restricting access + +You can restrict access to a call by tweaking the [Call Type](../configuring-call-types/) permissions and roles. +A typical use case is to restrict access to a call to a specific set of users -> call members. + +#### Step 1: Set up the roles and permissions + +On our [dashboard](https://dashboard.getstream.io/), navigate to the **Video & Audio -> Roles & Permissions** section and select the appropriate role and scope. +In this example, we will use `my-call-type` scope. + +By default, all users unless specified otherwise, have the `user` role. + +We start by removing the `JoinCall` permission from the `user` role for the `my-call-type` scope. +It will prevent regular users from joining a call of this type. + +![Revoke JoinCall for user role](../assets/user-revoke-joincall.png) + +Next, let's ensure that the `call_member` role has the `JoinCall` permission for the `my-call-type` scope. +It will allow users with the `call_member` role to join a call of this type. + +![Grant JoinCall for call_member role](../assets/call_member-grant-joincall.png) + +Once this is set, we can proceed with setting up a `call` instance. + +#### Step 2: Set up the call + +```swift +let call = streamVideo.call(callType: "my-call-type", callId: "my-call-id") +try await call.create(members: [.init(role: "call_member", userId: "alice")]) + +// and if necessary, to grant access to more users +try await call.addMembers(members: [.init(role: "call_member", userId: "charlie")]) + +// or, to remove access from some users +try await call.removeMembers(ids: ["charlie"]) +``` \ No newline at end of file diff --git a/docusaurus/docs/iOS/03-guides/03-call-and-participant-state.mdx b/docusaurus/docs/iOS/03-guides/03-call-and-participant-state.mdx index d68640e05..ced3ccce4 100644 --- a/docusaurus/docs/iOS/03-guides/03-call-and-participant-state.mdx +++ b/docusaurus/docs/iOS/03-guides/03-call-and-participant-state.mdx @@ -63,15 +63,32 @@ The following fields are available on the call: ### Participant State -The `CallParticipant` is the most essential component used to render a participant in a call. It contains all of the information to render a participant, such as audio & video renderers, availabilities of audio & video, the screen sharing session, reactions, and etc. Here's how you iterate over the participants: +The `CallParticipant` is the most essential component used to render a participant in a call. It contains all of the information to render a participant, such as audio & video tracks, availabilities of audio & video, the screen sharing session, reactions, and etc. Here's how you can subscribe to participants updates: ```swift // all participants let cancellable = call.state.$participants.sink { participants in // .. } +``` + +Filtering of the participants is also supported. You can get all the participants with the role "host", with the following code: + +```swift +var hosts: [CallParticipant] { + call.state.participants.filter { $0.roles.contains("host") } +} +``` -// you +When you join a call with many participants, maximum of 250 participants are returned in the join response. The list of participants is updated dynamically when there are join call events. + +The participants that are publishing video, audio or screensharing are prioritized over the other participants in the list. + +The total number of participants is updated realtime via health check events. This value is available from the call state's `participantCount` property. + +You can get the current user with the following code: + +```swift let localParticipant: CallParticipant? = call.state.localParticipant ``` diff --git a/docusaurus/docs/iOS/03-guides/07-dependency-injection.mdx b/docusaurus/docs/iOS/03-guides/07-dependency-injection.mdx deleted file mode 100644 index d08991528..000000000 --- a/docusaurus/docs/iOS/03-guides/07-dependency-injection.mdx +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Dependency Injection ---- - -For injecting dependencies in the SwiftUI SDK, we are using an approach based on [this article](https://www.avanderlee.com/swift/dependency-injection/). It works similarly to the @Environment in SwiftUI, but it also allows access to the dependencies in non-view related code. - -When you initialize the SDK (by creating the `StreamVideoUI` object), all the dependencies are created too, and you can use them anywhere in your code. In order to access a particular type, you need to use the `@Injected(\.keyPath)` property wrapper: - -```swift -@Injected(\.streamVideo) var streamVideo -@Injected(\.fonts) var fonts -@Injected(\.colors) var colors -@Injected(\.images) var images -@Injected(\.sounds) var sounds -@Injected(\.utils) var utils -``` - -### Extending the DI with Custom Types - -In some cases, you might also need to extend our DI mechanism with your own types. For example, you may want to be able to access your custom types like this: - -```swift -@Injected(\.customType) var customType -``` - -In order to achieve this, you first need to define your own `InjectionKey`, and define it's `currentValue`, which basically creates the new instance of your type. - -```swift -class CustomType { - // your custom logic here -} - -struct CustomInjectionKey: InjectionKey { - static var currentValue: CustomType = CustomType() -} -``` - -Next, you need to extend our `InjectedValues` with your own custom type, by defining its getter and setter. - -```swift -extension InjectedValues { - /// Provides access to the `CustomType` instance in the views and view models. - var customType: CustomType { - get { - Self[CustomInjectionKey.self] - } - set { - Self[CustomInjectionKey.self] = newValue - } - } -} -``` - -With these few simple steps, you can now access your custom functionality in both your app code and in your custom implementations of the views used throughout the SDK. diff --git a/docusaurus/docs/iOS/03-guides/10-view-slots.mdx b/docusaurus/docs/iOS/03-guides/10-view-slots.mdx deleted file mode 100644 index b41dea366..000000000 --- a/docusaurus/docs/iOS/03-guides/10-view-slots.mdx +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: View Slots ---- - -You can swap certain views in the SwiftUI SDK, by implementing your own version of the `ViewFactory` protocol. Here's a list of the supported slots that can be swapped with your custom views. - -In most of these slots, the whole `CallViewModel` is provided, allowing you to update the state from these views. - -### Outgoing Call View - -In order to swap the outgoing call view, we will need to implement the `makeOutgoingCallView(viewModel: CallViewModel) -> some View` in the `ViewFactory`: - -```swift - -class CustomViewFactory: ViewFactory { - - func makeOutgoingCallView(viewModel: CallViewModel) -> some View { - CustomOutgoingCallView(viewModel: viewModel) - } - -} -``` - -### Incoming Call View - -Similarly, the incoming call view can be replaced by implementing the `makeIncomingCallView(viewModel: CallViewModel, callInfo: IncomingCall) -> some View` in the `ViewFactory`: - -```swift -public func makeIncomingCallView(viewModel: CallViewModel, callInfo: IncomingCall) -> some View { - CustomIncomingCallView(callInfo: callInfo, viewModel: viewModel) -} -``` - -### Call View - -When the call state change to `.inCall`, the call view slot is shown. The default implementation provides several customizable parts, such as the video participants, the call controls (mute/unmute, hang up) and the top trailing view (which by default displays participants' info). - -In order to swap the default call view, you will need to implement the `makeCallView(viewModel: CallViewModel) -> some View`: - -```swift -public func makeCallView(viewModel: CallViewModel) -> some View { - CustomCallView(viewModel: viewModel) -} -``` - -Apart from the main call view, you can also swap its building blocks. - -#### Call Controls View - -The call controls view by default displays controls for hiding/showing the camera, muting/unmuting the microphone, changing the camera source (front/back) and hanging up. If you want to change these controls, you will need to implement the `makeCallControlsView(viewModel: CallViewModel) -> some View` method: - -```swift -func makeCallControlsView(viewModel: CallViewModel) -> some View { - CustomCallControlsView(viewModel: viewModel) -} -``` - -#### Video Participants View - -The video participants view slot presents the grid of users that are in the call. If you want to provide a different variation of the participants display, you will need to implement the `makeVideoParticipantsView` in the `ViewFactory`: - -```swift -public func makeVideoParticipantsView( - viewModel: CallViewModel, - availableFrame: CGRect, - onChangeTrackVisibility: @escaping @MainActor(CallParticipant, Bool) -> Void -) -> some View { - VideoParticipantsView( - viewFactory: self, - viewModel: viewModel, - availableFrame: availableFrame, - onChangeTrackVisibility: onChangeTrackVisibility - ) -} -``` - -In the method, the following parameters are provided: - -- `viewModel` - the viewModel that manages the call. -- `availableFrame` - the available frame for the participants view. -- `onChangeTrackVisibility` - callback when the track changes its visibility. - -#### Video Participant View - -If you want to customize one particular participant view, you can change it via the method `makeVideoParticipantView`: - -```swift -func makeVideoParticipantView( - participant: CallParticipant, - id: String, - availableFrame: CGRect, - contentMode: UIView.ContentMode, - customData: [String: RawJSON], - call: Call? -) -> some View { - VideoCallParticipantView( - participant: participant, - id: id, - availableFrame: availableFrame, - contentMode: contentMode, - customData: customData, - call: call - ) -} -``` - -Additionally, you can change the modifier applied to the view, by implementing the `makeVideoCallParticipantModifier`: - -```swift -public func makeVideoCallParticipantModifier( - participant: CallParticipant, - call: Call?, - availableFrame: CGRect, - ratio: CGFloat, - showAllInfo: Bool -) -> some ViewModifier { - VideoCallParticipantModifier( - participant: participant, - call: call, - availableFrame: availableFrame, - ratio: ratio, - showAllInfo: showAllInfo - ) -} -``` - -#### Top View - -This is the view presented in the top area of the call view. By default, it displays a back button (to go in minimized mode) and a button that shows the list of participants. You can swap this view with your own implementation, by implementing the `makeCallTopView` in the `ViewFactory`: - -```swift -public func makeCallTopView(viewModel: CallViewModel) -> some View { - CallTopView(viewModel: viewModel) -} -``` diff --git a/docusaurus/docs/iOS/03-guides/11-call-lifecycle.mdx b/docusaurus/docs/iOS/03-guides/11-call-lifecycle.mdx deleted file mode 100644 index a3123e210..000000000 --- a/docusaurus/docs/iOS/03-guides/11-call-lifecycle.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Call Lifecycle ---- - -## Call - -The `Call` object manages everything related to a particular call, such as creating, joining a call, performing actions for a user (mute/unmute, camera change, invite, etc) and listening to events. - -When a call starts, the iOS SDK communicates with our backend infrastructure, to find the best Selective Forwarding Unit (SFU) to host the call, based on the locations of the participants. It then establishes the connection with that SFU and provides updates on all events related to a call. - -You can create a new call via the `StreamVideo`'s method `func call(callType: String, callId: String)`. - -It's a lower-level component than the stateful `CallViewModel`, and it's suitable if you want to create your own presentation logic and state handling. - -The `Call` object should exist while the call is active. Afterwards, you should clean up all the state related to the call (provided you don't use our `CallViewModel`), by calling the `leave` method and de-allocating the instance. - -Every call has a call id and type. You can join a call with the same id as many times as you need. However, the call sends ringing events only the first time. If you want to receive ring events, you should always use a unique call id. - -## Web Socket Connection - -The web socket connection with our backend is established when the `StreamVideo` object is being created. If you go into the background, and come back, the SDK tries to re-establish this connection. The web socket connection is persisted in order to listen to events such as incoming calls, that can be presented in-app (if you're not using CallKit). diff --git a/docusaurus/docs/iOS/03-guides/13-ui-customization.mdx b/docusaurus/docs/iOS/03-guides/13-ui-customization.mdx deleted file mode 100644 index 06dfb1e90..000000000 --- a/docusaurus/docs/iOS/03-guides/13-ui-customization.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: UI customization ---- - -## UI components vs Custom - -StreamVideo provides both ready made components to use directly in your app, as well as extension points that you can use to inject your own custom UI. If you just need the calling functionality and completely custom UI, you can use only our low-level client. - -Let's explore the different possibilities and how they would impact your app and the integration efforts. - -## Using only the low-level client - -If your app needs a completely custom UI and calling flow, you can use only our low-level client that implements the WebRTC protocol and communicates with our backend services. If you go with this approach, you can either use our stateful `CallViewModel` that allows you to observe the call state (list of participants, camera & microphone state, etc), or use our lower level `Call` object and implement your own presentation objects. - -Additionally, if you go with this approach, you can still use some components from our UI SDKs (if they fit your use-case), to facilitate your development. We have several examples for this in [our cookbook](../../ui-cookbook/overview). - -This approach would require some familiarity with our low-level client, and the highest development efforts compared to the other two options. On the other hand, it gives you a maximum flexibility to customize the calling flow according to your needs. - -In any case, our view components are highly customizable and flexible for many video/audio calling cases, and they can save big development efforts. Therefore, we recommend that you consider the other two options below, before deciding on starting from scratch. - -## Mix & match - -The mix & match approach is ideal if you need one of the standard calling flows, but with a possibility to replace parts of the UI with your own implementation. Our UI SDK allows you to completely swap views with your own custom interface elements. - -For example, if you are building an app with incoming / outgoing calling screens, you can easily swap only those screens. For building your custom screens, you can still reuse our lower level components. - -This approach provides a nice balance between levels of customization and development efforts. Find examples and extension slots to get started in our docs [here](../view-slots). - -## Simple theming - -If you need a standard video calling experience that needs to match the rest of your app's look and feel, you can use our theming customizations. - -This is the fastest way to add calling support to your app, just setup our video client and attach our `CallModifier` to your hosting view. You can change the fonts, colors, icons, texts and sounds used in the SDK, by interacting with our `Appearance` class. - -You can find more details about how to customize the theming [here](../../ui-components/overview). diff --git a/docusaurus/docs/iOS/04-ui-components/01-overview.mdx b/docusaurus/docs/iOS/04-ui-components/01-overview.mdx index 2a0a543be..a461382f1 100644 --- a/docusaurus/docs/iOS/04-ui-components/01-overview.mdx +++ b/docusaurus/docs/iOS/04-ui-components/01-overview.mdx @@ -5,10 +5,40 @@ description: Overview of the UI components ## Introduction -The StreamVideo SDK provides UI components to facilitate the integration of video capabilities into your apps. You can either use our out-of-the-box solution (and customize theming and some views), or completely build your own UI, while reusing our lower-level components whenever you see them fit. +The StreamVideo SDK provides UI components to facilitate the integration of video capabilities into your apps. The UI components are provided in SwiftUI. If you use UIKit, we also provide UIKit wrappers, that can make it easier for you to integrate video in UIKit-based apps. +## UI components vs Custom + +StreamVideo provides both ready made components to use directly in your app, as well as extension points that you can use to inject your own custom UI. If you only need the calling functionality to support your (custom built) UI, you can simply rely on our low-level client. + +Let's explore the different possibilities and how they would impact your app and the integration efforts. + +## Using only the low-level client + +If your app needs a completely custom UI and calling flow, you can use only our low-level client that implements the WebRTC protocol and communicates with our backend services. If you go with this approach, you can either use our stateful `CallViewModel` that allows you to observe the call state (list of participants, camera & microphone state, etc), or use our lower level `Call` object and implement your own presentation objects. + +Additionally, if you go with this approach, you can still use some components from our UI SDKs (if they fit your use-case), to facilitate your development. We have several examples for this in [our cookbook](../../ui-cookbook/overview). + +This approach would require some familiarity with our low-level client, and the highest development efforts compared to the other two options. On the other hand, it gives you maximum flexibility to customize the calling flow according to your needs. + +In any case, our view components are highly customizable and flexible for many video/audio calling cases, and they can save big development efforts. Therefore, we recommend that you consider the other two options below, before deciding on starting from scratch. + +## Mix & match + +The mix & match approach is ideal if you need one of the standard calling flows, but with a possibility to replace parts of the UI with your own implementation. Our UI SDK allows you to completely swap views with your own interface elements. + +For example, if you are building an app with incoming / outgoing calling screens, you can easily swap only those screens. For building your custom screens, you can still reuse our lower level components. + +This approach provides a nice balance between levels of customization and development efforts. Find examples and extension slots to get started in our docs [here](../view-slots). + +## Simple theming + +If you need a standard video calling experience that needs to match the rest of your app's look and feel, you can use our theming customizations. + +This is the fastest way to add calling support to your app, just setup our video client and attach our `CallModifier` to your hosting view. You can change the fonts, colors, icons, texts and sounds used in the SDK, by interacting with our `Appearance` class. + ## StreamVideoUI object The UI SDK provides a context provider object that allows simple access to functionalities exposed by the SDK, such as branding, presentation logic, icons, and the low-level video client. @@ -55,3 +85,58 @@ Find more details on how to do this on [this page](../video-theme). ### Changing Views Apart from the basic theming customizations, you can also swap certain views, with your implementation. You can find more details on how to do that on this [page](../customizing-views). + +## Dependency Injection + +For injecting dependencies in the SwiftUI SDK, we are using an approach based on [this article](https://www.avanderlee.com/swift/dependency-injection/). It works similarly to the @Environment in SwiftUI, but it also allows access to the dependencies in non-view related code. + +When you initialize the SDK (by creating the `StreamVideoUI` object), all the dependencies are created too, and you can use them anywhere in your code. In order to access a particular type, you need to use the `@Injected(\.keyPath)` property wrapper: + +```swift +@Injected(\.streamVideo) var streamVideo +@Injected(\.fonts) var fonts +@Injected(\.colors) var colors +@Injected(\.images) var images +@Injected(\.sounds) var sounds +@Injected(\.utils) var utils +``` + +### Extending the DI with Custom Types + +In some cases, you might also need to extend our DI mechanism with your own types. For example, you may want to be able to access your custom types like this: + +```swift +@Injected(\.customType) var customType +``` + +In order to achieve this, you first need to define your own `InjectionKey`, and define it's `currentValue`, which basically creates the new instance of your type. + +```swift +class CustomType { + // your custom logic here +} + +struct CustomInjectionKey: InjectionKey { + static var currentValue: CustomType = CustomType() +} +``` + +Next, you need to extend our `InjectedValues` with your own custom type, by defining its getter and setter. + +```swift +extension InjectedValues { + /// Provides access to the `CustomType` instance in the views and view models. + var customType: CustomType { + get { + Self[CustomInjectionKey.self] + } + set { + Self[CustomInjectionKey.self] = newValue + } + } +} +``` + +With these few simple steps, you can now access your custom functionality in both your app code and in your custom implementations of the views used throughout the SDK. + +Additionally, DI entries can be accessed by using the `InjectedValues[\.]` syntax (for example `InjectedValues[\.customType]`). This approach can be useful in case you want to override our default injected values. \ No newline at end of file diff --git a/docusaurus/docs/iOS/03-guides/14-swiftui-vs-uikit.mdx b/docusaurus/docs/iOS/04-ui-components/02-swiftui-vs-uikit.mdx similarity index 100% rename from docusaurus/docs/iOS/03-guides/14-swiftui-vs-uikit.mdx rename to docusaurus/docs/iOS/04-ui-components/02-swiftui-vs-uikit.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/04-customizing-views.mdx b/docusaurus/docs/iOS/04-ui-components/04-customizing-views.mdx index 9bf22050c..ef24738ca 100644 --- a/docusaurus/docs/iOS/04-ui-components/04-customizing-views.mdx +++ b/docusaurus/docs/iOS/04-ui-components/04-customizing-views.mdx @@ -35,4 +35,132 @@ var body: some View { } ``` -For the full list of supported view slots that can be swapped, please refer to this [page](../../guides/view-slots). +Here are all the slots available for customization in the SwiftUI SDK. + +### Outgoing Call View + +In order to swap the outgoing call view, we will need to implement the `makeOutgoingCallView(viewModel: CallViewModel) -> some View` in the `ViewFactory`: + +```swift + +class CustomViewFactory: ViewFactory { + + func makeOutgoingCallView(viewModel: CallViewModel) -> some View { + CustomOutgoingCallView(viewModel: viewModel) + } + +} +``` + +### Incoming Call View + +Similarly, the incoming call view can be replaced by implementing the `makeIncomingCallView(viewModel: CallViewModel, callInfo: IncomingCall) -> some View` in the `ViewFactory`: + +```swift +public func makeIncomingCallView(viewModel: CallViewModel, callInfo: IncomingCall) -> some View { + CustomIncomingCallView(callInfo: callInfo, viewModel: viewModel) +} +``` + +### Call View + +When the call state change to `.inCall`, the call view slot is shown. The default implementation provides several customizable parts, such as the video participants, the call controls (mute/unmute, hang up) and the top trailing view (which by default displays participants' info). + +In order to swap the default call view, you will need to implement the `makeCallView(viewModel: CallViewModel) -> some View`: + +```swift +public func makeCallView(viewModel: CallViewModel) -> some View { + CustomCallView(viewModel: viewModel) +} +``` + +Apart from the main call view, you can also swap its building blocks. + +#### Call Controls View + +The call controls view by default displays controls for hiding/showing the camera, muting/unmuting the microphone, changing the camera source (front/back) and hanging up. If you want to change these controls, you will need to implement the `makeCallControlsView(viewModel: CallViewModel) -> some View` method: + +```swift +func makeCallControlsView(viewModel: CallViewModel) -> some View { + CustomCallControlsView(viewModel: viewModel) +} +``` + +#### Video Participants View + +The video participants view slot presents the grid of users that are in the call. If you want to provide a different variation of the participants display, you will need to implement the `makeVideoParticipantsView` in the `ViewFactory`: + +```swift +public func makeVideoParticipantsView( + viewModel: CallViewModel, + availableFrame: CGRect, + onChangeTrackVisibility: @escaping @MainActor(CallParticipant, Bool) -> Void +) -> some View { + VideoParticipantsView( + viewFactory: self, + viewModel: viewModel, + availableFrame: availableFrame, + onChangeTrackVisibility: onChangeTrackVisibility + ) +} +``` + +In the method, the following parameters are provided: + +- `viewModel` - the viewModel that manages the call. +- `availableFrame` - the available frame for the participants view. +- `onChangeTrackVisibility` - callback when the track changes its visibility. + +#### Video Participant View + +If you want to customize one particular participant view, you can change it via the method `makeVideoParticipantView`: + +```swift +func makeVideoParticipantView( + participant: CallParticipant, + id: String, + availableFrame: CGRect, + contentMode: UIView.ContentMode, + customData: [String: RawJSON], + call: Call? +) -> some View { + VideoCallParticipantView( + participant: participant, + id: id, + availableFrame: availableFrame, + contentMode: contentMode, + customData: customData, + call: call + ) +} +``` + +Additionally, you can change the modifier applied to the view, by implementing the `makeVideoCallParticipantModifier`: + +```swift +public func makeVideoCallParticipantModifier( + participant: CallParticipant, + call: Call?, + availableFrame: CGRect, + ratio: CGFloat, + showAllInfo: Bool +) -> some ViewModifier { + VideoCallParticipantModifier( + participant: participant, + call: call, + availableFrame: availableFrame, + ratio: ratio, + showAllInfo: showAllInfo + ) +} +``` + +#### Top View + +This is the view presented in the top area of the call view. By default, it displays a back button (to go in minimized mode) and a button that shows the list of participants. You can swap this view with your own implementation, by implementing the `makeCallTopView` in the `ViewFactory`: + +```swift +public func makeCallTopView(viewModel: CallViewModel) -> some View { + CallTopView(viewModel: viewModel) +} +``` \ No newline at end of file diff --git a/docusaurus/docs/iOS/04-ui-components/02-video-renderer.mdx b/docusaurus/docs/iOS/04-ui-components/07-video-renderer.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/02-video-renderer.mdx rename to docusaurus/docs/iOS/04-ui-components/07-video-renderer.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/01-call-container.mdx b/docusaurus/docs/iOS/04-ui-components/08-call/01-call-container.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/01-call-container.mdx rename to docusaurus/docs/iOS/04-ui-components/08-call/01-call-container.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/02-outgoing-call.mdx b/docusaurus/docs/iOS/04-ui-components/08-call/02-outgoing-call.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/02-outgoing-call.mdx rename to docusaurus/docs/iOS/04-ui-components/08-call/02-outgoing-call.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/03-incoming-call.mdx b/docusaurus/docs/iOS/04-ui-components/08-call/03-incoming-call.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/03-incoming-call.mdx rename to docusaurus/docs/iOS/04-ui-components/08-call/03-incoming-call.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/04-active-call.mdx b/docusaurus/docs/iOS/04-ui-components/08-call/04-active-call.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/04-active-call.mdx rename to docusaurus/docs/iOS/04-ui-components/08-call/04-active-call.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/05-call-controls.mdx b/docusaurus/docs/iOS/04-ui-components/08-call/05-call-controls.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/05-call-controls.mdx rename to docusaurus/docs/iOS/04-ui-components/08-call/05-call-controls.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/06-call-app-bar.mdx b/docusaurus/docs/iOS/04-ui-components/08-call/06-call-app-bar.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/06-call-app-bar.mdx rename to docusaurus/docs/iOS/04-ui-components/08-call/06-call-app-bar.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/07-screen-share-content.mdx b/docusaurus/docs/iOS/04-ui-components/08-call/07-screen-share-content.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/07-screen-share-content.mdx rename to docusaurus/docs/iOS/04-ui-components/08-call/07-screen-share-content.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/07-call/_category_.json b/docusaurus/docs/iOS/04-ui-components/08-call/_category_.json similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/07-call/_category_.json rename to docusaurus/docs/iOS/04-ui-components/08-call/_category_.json diff --git a/docusaurus/docs/iOS/04-ui-components/08-participants/01-call-participant.mdx b/docusaurus/docs/iOS/04-ui-components/09-participants/01-call-participant.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/08-participants/01-call-participant.mdx rename to docusaurus/docs/iOS/04-ui-components/09-participants/01-call-participant.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/08-participants/02-call-participants.mdx b/docusaurus/docs/iOS/04-ui-components/09-participants/02-call-participants.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/08-participants/02-call-participants.mdx rename to docusaurus/docs/iOS/04-ui-components/09-participants/02-call-participants.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/08-participants/03-call-participants-info-menu.mdx b/docusaurus/docs/iOS/04-ui-components/09-participants/03-call-participants-info-menu.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/08-participants/03-call-participants-info-menu.mdx rename to docusaurus/docs/iOS/04-ui-components/09-participants/03-call-participants-info-menu.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/08-participants/04-local-video.mdx b/docusaurus/docs/iOS/04-ui-components/09-participants/04-local-video.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/08-participants/04-local-video.mdx rename to docusaurus/docs/iOS/04-ui-components/09-participants/04-local-video.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/08-participants/_category_.json b/docusaurus/docs/iOS/04-ui-components/09-participants/_category_.json similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/08-participants/_category_.json rename to docusaurus/docs/iOS/04-ui-components/09-participants/_category_.json diff --git a/docusaurus/docs/iOS/04-ui-components/09-utility/02-sound-indicator.mdx b/docusaurus/docs/iOS/04-ui-components/10-utility/02-sound-indicator.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/09-utility/02-sound-indicator.mdx rename to docusaurus/docs/iOS/04-ui-components/10-utility/02-sound-indicator.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/09-utility/03-avatars.mdx b/docusaurus/docs/iOS/04-ui-components/10-utility/03-avatars.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/09-utility/03-avatars.mdx rename to docusaurus/docs/iOS/04-ui-components/10-utility/03-avatars.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/09-utility/04-connection-quality-indicator.mdx b/docusaurus/docs/iOS/04-ui-components/10-utility/04-connection-quality-indicator.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/09-utility/04-connection-quality-indicator.mdx rename to docusaurus/docs/iOS/04-ui-components/10-utility/04-connection-quality-indicator.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/09-utility/05-call-background.mdx b/docusaurus/docs/iOS/04-ui-components/10-utility/05-call-background.mdx similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/09-utility/05-call-background.mdx rename to docusaurus/docs/iOS/04-ui-components/10-utility/05-call-background.mdx diff --git a/docusaurus/docs/iOS/04-ui-components/09-utility/_category_.json b/docusaurus/docs/iOS/04-ui-components/10-utility/_category_.json similarity index 100% rename from docusaurus/docs/iOS/04-ui-components/09-utility/_category_.json rename to docusaurus/docs/iOS/04-ui-components/10-utility/_category_.json diff --git a/docusaurus/docs/iOS/05-ui-cookbook/14-livestream-player.mdx b/docusaurus/docs/iOS/05-ui-cookbook/14-livestream-player.mdx index 5e34b3e4b..d66d0403b 100644 --- a/docusaurus/docs/iOS/05-ui-cookbook/14-livestream-player.mdx +++ b/docusaurus/docs/iOS/05-ui-cookbook/14-livestream-player.mdx @@ -45,4 +45,24 @@ Apart from the required parameters, you can also specify some optional ones in t - `muted`: `Bool` - whether the livestream audio should be on when joining the stream (default is `false`). - `showParticipantCount`: `Bool` - whether the participant count should be shown (default is `true`). -- `onFullScreenStateChange`: `((Bool) -> ())?` - closure that is invoked when the full screen state changes. Useful if you use the livestream component as part of your custom views, since this is the chance to update the visibility of your custom UI elements. \ No newline at end of file +- `onFullScreenStateChange`: `((Bool) -> ())?` - closure that is invoked when the full screen state changes. Useful if you use the livestream component as part of your custom views, since this is the chance to update the visibility of your custom UI elements. + +## Accessing the livestream track + +You can also build your own version of a livestream player, depending on your requirements. In those cases, you need to have access to the livestream track (or tracks). + +If there is only one video track (you only have one person livestreaming), you can get it with the following code: + +```swift +let livestream = call.state.participants.first(where: { $0.track != nil }) +``` + +If you have multiple hosts that are livestreaming, and you want to show them all, you can fetch the hosts by role: + +```swift +var hosts: [CallParticipant] { + call.state.participants.filter { $0.roles.contains("host") } +} +``` + +Then, you can access the video track they are streaming, with the `track` property. \ No newline at end of file diff --git a/docusaurus/docs/iOS/06-advanced/02-push-notifications.mdx b/docusaurus/docs/iOS/06-advanced/02-push-notifications.mdx index 39b024094..2fe9fa4a9 100644 --- a/docusaurus/docs/iOS/06-advanced/02-push-notifications.mdx +++ b/docusaurus/docs/iOS/06-advanced/02-push-notifications.mdx @@ -5,6 +5,11 @@ description: Push Notifications setup The `StreamVideo` SDK supports two types of push notifications: regular and VoIP notifications. You can use one of them, or both, depending on your use-case. +Push notifications are sent in the following scenarios: +- you create a call with the `ring` value set to true. In this case, a VoIP notification that shows a ringing screen is sent. +- you create a call with the `notify` value set to true. In this case, a regular push notification is sent. +- you haven't answered a call. In this case, a missed call notification is sent (regular push notification). + ### StreamVideo setup The push notification config is provided optionally, when the SDK is initalized. By default, the config uses `apn` as a push provider, for both VoIP and regular push. The push provider name for regular push is "apn", while for VoIP, the name is "voip". diff --git a/docusaurus/docs/iOS/06-advanced/03-callkit-integration.mdx b/docusaurus/docs/iOS/06-advanced/03-callkit-integration.mdx index a038cd70a..3fa064790 100644 --- a/docusaurus/docs/iOS/06-advanced/03-callkit-integration.mdx +++ b/docusaurus/docs/iOS/06-advanced/03-callkit-integration.mdx @@ -161,6 +161,16 @@ If none of the fields above are being set, the property will be an empty string. - **created_by_display_name** The property is always set and contains the name of the user who created the call. +#### Call Settings when accepting a call + +Depending on your business logic, you may need users to join call with different `CallSettings`(e.g auioOn=true while videoOn=false). In order to achieve that when using the `CallKitAdapter` you can provide your custom `CallSettings` at any point before you receive a call: + +```swift +@Injected(\.callKitAdapter) var callKitAdapter + +callKitAdapter.callSettings = CallSettings(audioOn: true, videoOn: false) +``` + #### Call's type suffix Depending on the `Call` type `CallKit` adds a suffix in the push notification's subtitle (which contains the application name). That suffix can either be `Audio` or `Video`. `CallKitService` allows you to configure what the supported call types are, by setting the `CallKitService.supportsVideo` property like below: diff --git a/docusaurus/docs/iOS/06-advanced/15-sdk-size-impact.mdx b/docusaurus/docs/iOS/06-advanced/15-sdk-size-impact.mdx index d08e772d5..fb845a3ca 100644 --- a/docusaurus/docs/iOS/06-advanced/15-sdk-size-impact.mdx +++ b/docusaurus/docs/iOS/06-advanced/15-sdk-size-impact.mdx @@ -2,33 +2,6 @@ When developing a mobile app, one crucial performance metric is app size. An app’s size can be difficult to accurately measure with multiple variants and device spreads. Once measured, it’s even more difficult to understand and identify what’s contributing to size bloat. -This document provides an analysis of the impact of adding StreamVideo and StreamVideoSwiftUI iOS SDKs to an existing mobile application. The analysis includes the size of the SDKs and the impact on the app's binary size. +We track and update the SDK size on every commit to our `develop` branch. The sizes of all SDKs that are part of our video product are shown with badges, at the top of our GitHub [repo](https://github.com/GetStream/stream-video-swift). -## SDK Information - -| | StreamVideo | StreamVideoSwiftUI | -| ------------------- | :---------: | :----------------: | -| **Download Size:** | 4.20 MB | 2.85 MB | -| **Installed Size:** | 4.74 MB | 3.35 MB | - -### Analysis - -The following analysis was conducted using the `mdls -n kMDItemPhysicalSize` command in the Terminal after building the app with and without the SDK. - -1. **SwiftVideo** - - | | w/o framework | with framework | difference | - | ------------------ | :-----------: | :------------: | :--------: | - | App Size (Release) | 13_463_552 | 18_440_192 | 4_976_640 | - -2. **StreamVideoSwiftUI** - - | | w/o framework | with framework | difference | - | ------------------ | :-----------: | :------------: | :--------: | - | App Size (Release) | 13_463_552 | 16_986_112 | 3_522_560 | - -The tables above show the impact of integrating the SDKs on the app's binary size, in bytes. - -## Conclusion - -Based on the analysis conducted, both StreamVideo and StreamVideoSwiftUI SDKs are lightweight and optimized for optimal performance. We are confident that our Video SDKs will enhance the functionality of your app without compromising its performance or increasing its binary size significantly. +Therefore, please check our repo to get the up-to date sizes of our video SDKs. diff --git a/docusaurus/docs/iOS/03-guides/15-migration-from-dolby.mdx b/docusaurus/docs/iOS/06-advanced/16-migration-from-dolby.mdx similarity index 100% rename from docusaurus/docs/iOS/03-guides/15-migration-from-dolby.mdx rename to docusaurus/docs/iOS/06-advanced/16-migration-from-dolby.mdx diff --git a/docusaurus/docs/iOS/assets/call_member-grant-joincall.png b/docusaurus/docs/iOS/assets/call_member-grant-joincall.png new file mode 100644 index 000000000..d950f9b30 Binary files /dev/null and b/docusaurus/docs/iOS/assets/call_member-grant-joincall.png differ diff --git a/docusaurus/docs/iOS/assets/user-revoke-joincall.png b/docusaurus/docs/iOS/assets/user-revoke-joincall.png new file mode 100644 index 000000000..1aee4570d Binary files /dev/null and b/docusaurus/docs/iOS/assets/user-revoke-joincall.png differ diff --git a/fastlane/Allurefile b/fastlane/Allurefile index c40298403..6f7014b45 100755 --- a/fastlane/Allurefile +++ b/fastlane/Allurefile @@ -8,11 +8,10 @@ allure_results_path = 'allure-results' desc 'Upload test results to Allure TestOps' lane :allure_upload do |options| - branch = github_run_details['head_branch'] allure_args = "-e #{allure_url} --project-id #{allure_project_id} --launch-id #{options[:launch_id]}" sh("./xcresults export test_output/DemoApp.xcresult #{allure_results_path} || true") sh("./allurectl launch reopen #{options[:launch_id]} || true") # to prevent allure from uploading results to a closed launch - sh("env BRANCH_NAME='#{branch}' ./allurectl upload #{allure_args} #{allure_results_path} || true") + sh("env BRANCH_NAME='#{current_branch}' ./allurectl upload #{allure_args} #{allure_results_path} || true") UI.success("Check out test results in Allure TestOps: #{allure_url}/launch/#{options[:launch_id]} 🎉") end @@ -54,6 +53,6 @@ def github_run_details return nil unless is_ci github_path = "#{ENV.fetch('GITHUB_API_URL', nil)}/repos/#{ENV.fetch('GITHUB_REPOSITORY', nil)}/actions/runs/#{ENV.fetch('GITHUB_RUN_ID', nil)}" - output = sh(command: "curl -s -H 'authorization: Bearer #{ENV.fetch('GITHUB_TOKEN', nil)}' -X GET -G #{github_path}", log: false) + output = sh(command: "curl -s -H 'authorization: Bearer #{ENV.fetch('GITHUB_TOKEN', nil)}' -X GET -G #{github_path}") JSON.parse(output) end diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 2d59dc76f..bf2ae5eff 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -21,12 +21,14 @@ gci = ENV['GOOGLE_CLIENT_ID'] || '' reversed_gci = gci.split('.').reverse.join('.') is_localhost = !is_ci @force_check = false +swift_environment_path = File.absolute_path('../Sources/StreamVideo/Generated/SystemEnvironment+Version.swift') swiftformat_excluded_paths = ["**/Generated", "**/generated", "**/protobuf", "**/OpenApi"] swiftformat_source_paths = ["Sources", "DemoApp", "DemoAppUIKit", "StreamVideoTests", "StreamVideoSwiftUITests", "StreamVideoUIKitTests"] before_all do |lane| if is_ci setup_ci + setup_git_config xcversion(version: xcode_version) unless [:publish_release, :allure_launch, :allure_upload, :pod_lint, :stop_e2e_helpers].include?(lane) elsif lane == :test_e2e stop_e2e_helpers @@ -42,7 +44,6 @@ desc "Release a new version" lane :release do |options| previous_version_number = last_git_tag artifacts_path = File.absolute_path('../StreamVideoArtifacts.json') - swift_environment_path = File.absolute_path('../Sources/StreamVideo/Generated/SystemEnvironment+Version.swift') extra_changes = lambda do |release_version| # Set the framework version on the artifacts artifacts = JSON.parse(File.read(artifacts_path)) @@ -52,6 +53,9 @@ lane :release do |options| # Set the framework version in SystemEnvironment+Version.swift new_content = File.read(swift_environment_path).gsub!(previous_version_number, release_version) File.open(swift_environment_path, 'w') { |f| f.puts(new_content) } + + # Update sdk sizes + Dir.chdir('fastlane') { update_img_shields_sdk_sizes } end pod_lint @@ -66,10 +70,18 @@ lane :release do |options| ) end +lane :merge_release do |options| + merge_release_to_main(author: options[:author]) +end + desc "Publish a new release to GitHub and CocoaPods" lane :publish_release do |options| - xcversion(version: '15.0.1') + release_version = File.read(swift_environment_path).match(/String\s+=\s+"([\d.]+)"/)[1] + UI.user_error!("Release #{release_version} has already been published.") if git_tag_exists(tag: release_version, remote: true) + UI.user_error!('Release version cannot be empty') if release_version.to_s.empty? + ensure_git_branch(branch: 'main') + xcversion(version: '15.0.1') clean_products build_xcframeworks compress_frameworks @@ -77,14 +89,14 @@ lane :publish_release do |options| publish_ios_sdk( skip_git_status_check: false, - version: options[:version], + version: release_version, sdk_names: sdk_names, podspec_names: podspec_names, github_repo: github_repo, upload_assets: ['Products/StreamVideo.zip', 'Products/StreamVideoSwiftUI.zip', 'Products/StreamVideoUIKit.zip', 'Products/StreamVideo-All.zip'] ) - update_spm(version: options[:version]) + update_spm(version: release_version) merge_main_to_develop end @@ -281,7 +293,7 @@ private_lane :test_ui do |options| png_files.each { |png| sh("git add #{png}") || true } sh('git restore .') - create_pr( + pr_create( title: '[CI] Snapshots', base_branch: current_branch, head_branch: "#{current_branch}-snapshots" @@ -479,36 +491,6 @@ private_lane :build_example_app do |options| ) end -lane :merge_release_to_main do - ensure_git_status_clean - sh('git checkout main') - sh('git pull') - - # Grep all remote release branches and ensure there's only one - release_branches = sh(command: 'git branch -a', log: false).delete(' ').split("\n").grep(%r(origin/.*release/)) - UI.user_error!("Expected 1 release branch, found #{release_branches.size}") if release_branches.size != 1 - - # Merge release branch to main. For more info, read: https://notion.so/iOS-Branching-Strategy-37c10127dc26493e937769d44b1d6d9a - sh("git merge #{release_branches.first} --ff-only") - UI.user_error!('Not pushing changes') unless prompt(text: 'Will push changes. All looking good?', boolean: true) - sh('git push') - UI.important('Please, wait for the `Publish new release` workflow to pass on GitHub Actions: ' \ - "https://github.com/#{github_repo}/actions/workflows/publish-release.yml") -end - -lane :merge_main_to_develop do - if is_ci - sh('git reset --hard') - else - ensure_git_status_clean - end - - sh('git checkout main && git pull') - sh('git checkout develop && git pull') - sh('git merge main') - sh('git push') -end - desc 'Compresses the XCFrameworks into zip files' lane :compress_frameworks do Dir.chdir('..') do @@ -581,7 +563,6 @@ private_lane :update_spm do |options| File.open('./Package.swift', 'w') { |file| file << file_data } # Update the repo - sh('git config --global user.name "Stream Bot"') sh('git add -A') sh("git commit -m 'Bump #{version}'") sh('git push') @@ -606,25 +587,6 @@ lane :code_generation do sync_xcodeproj_references end -private_lane :git_status do |options| - UI.user_error!('Extension should be provided') unless options[:ext] - - untracked_files = sh('git status -s', log: false).split("\n").map(&:strip) - UI.important("Git Status: #{untracked_files}") - - deleted_files = select_files_from(files: untracked_files, with_extension: options[:ext], that_start_with: 'D') - added_files = select_files_from(files: untracked_files, with_extension: options[:ext], that_start_with: ['A', '??']) - renamed_files = select_files_from(files: untracked_files, with_extension: options[:ext], that_start_with: 'R') - modified_files = select_files_from(files: untracked_files, with_extension: options[:ext], that_start_with: 'M') - - renamed_files.each do |renamed_file| - content = renamed_file.split.drop(1).join.split('->').map(&:strip) - deleted_files << content.first - added_files << content.last - end - { a: added_files, d: deleted_files, m: modified_files } -end - lane :sync_xcodeproj_references do Dir.chdir('..') do project = Xcodeproj::Project.open(xcode_project) @@ -691,21 +653,7 @@ lane :run_swift_format do |options| end lane :install_runtime do |options| - runtimes = `xcrun simctl runtime list -j` - UI.message("👉 Runtime list:\n#{runtimes}") - simulators = JSON.parse(runtimes).select do |_, sim| - sim['platformIdentifier'].end_with?('iphonesimulator') && sim['version'] == options[:ios] && sim['state'] == 'Ready' - end - - if simulators.empty? - Dir.chdir('..') do - sh("echo 'iOS #{options[:ios]} Simulator' | ipsw download xcode --sim") if Dir['*.dmg'].first.nil? - sh("./Scripts/install_ios_runtime.sh #{Dir['*.dmg'].first}") - UI.success("iOS #{options[:ios]} Runtime successfuly installed") - end - else - UI.important("iOS #{options[:ios]} Runtime already exists") - end + install_ios_runtime(version: options[:ios], custom_script: 'Scripts/install_ios_runtime.sh') end desc 'Remove UI Snapshots' @@ -729,6 +677,7 @@ lane :sources_matrix do swiftui_sample_apps: ['Sources', 'DemoApp', xcode_project], uikit_sample_apps: ['Sources', 'DemoAppUIKit', xcode_project], documentation_tests: ['Sources', 'DocumentationTests', 'docusaurus', xcode_project], + size: ['Sources', xcode_project], ruby: ['fastlane'] } end @@ -737,29 +686,57 @@ lane :copyright do update_copyright(ignore: [derived_data_path, source_packages_path, 'vendor/']) next unless is_ci - create_pr( + pr_create( title: '[CI] Update Copyright', head_branch: "ci/update-copyright-#{Time.now.to_i}" ) end -private_lane :create_pr do |options| - options[:base_branch] ||= 'develop' - sh("git checkout -b #{options[:head_branch]}") - sh('git add -A') - sh("git commit -m '#{options[:title]}'") - push_to_git_remote(tags: false) - - create_pull_request( - api_token: ENV.fetch('GITHUB_TOKEN', nil), - repo: github_repo, - title: options[:title], - head: options[:head_branch], - base: options[:base_branch], - body: 'This PR was created automatically by CI.' +lane :show_frameworks_sizes do |options| + next unless is_check_required(sources: sources_matrix[:size], force_check: @force_check) + + sizes = options[:sizes] || frameworks_sizes + show_sdk_size(branch_sizes: sizes, github_repo: github_repo) + update_img_shields_sdk_sizes(sizes: sizes, open_pr: options[:open_pr]) if options[:update_readme] +end + +lane :update_img_shields_sdk_sizes do |options| + update_sdk_size_in_readme( + open_pr: options[:open_pr] || false, + readme_path: 'README.md', + sizes: options[:sizes] || frameworks_sizes ) end -private_lane :current_branch do - ENV['BRANCH_NAME'].to_s.empty? ? git_branch : ENV.fetch('BRANCH_NAME') +def frameworks_sizes + root_dir = 'Build/SDKSize' + archive_dir = "#{root_dir}/DemoApp.xcarchive" + + FileUtils.rm_rf("../#{root_dir}/") + + match_me + + gym( + scheme: 'DemoAppUIKit', + archive_path: archive_dir, + export_method: 'ad-hoc', + export_options: 'fastlane/sdk_size_export_options.plist' + ) + + frameworks_path = "../#{archive_dir}/Products/Applications/DemoAppUIKit.app/Frameworks" + stream_video_size = File.size("#{frameworks_path}/StreamVideo.framework/StreamVideo") + stream_video_size_kb = stream_video_size / 1024.0 + stream_video_swiftui_size = File.size("#{frameworks_path}/StreamVideoSwiftUI.framework/StreamVideoSwiftUI") + stream_video_swiftui_size_kb = stream_video_swiftui_size / 1024.0 + stream_video_uikit_size = File.size("#{frameworks_path}/StreamVideoUIKit.framework/StreamVideoUIKit") + stream_video_uikit_size_kb = (stream_video_uikit_size + stream_video_swiftui_size) / 1024.0 + stream_web_rtc_size = File.size("#{frameworks_path}/StreamWebRTC.framework/StreamWebRTC") + stream_web_rtc_size_kb = stream_web_rtc_size / 1024.0 + + { + StreamVideo: stream_video_size_kb.round(0), + StreamVideoSwiftUI: stream_video_swiftui_size_kb.round(0), + StreamVideoUIKit: stream_video_uikit_size_kb.round(0), + StreamWebRTC: stream_web_rtc_size_kb.round(0) + } end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile index 0b9f84e1a..5ac3f66f1 100644 --- a/fastlane/Pluginfile +++ b/fastlane/Pluginfile @@ -1,3 +1,3 @@ gem 'fastlane-plugin-versioning' -gem 'fastlane-plugin-stream_actions', '0.3.38' gem 'fastlane-plugin-create_xcframework' +gem 'fastlane-plugin-stream_actions', '0.3.63' diff --git a/fastlane/sdk_size_export_options.plist b/fastlane/sdk_size_export_options.plist new file mode 100644 index 000000000..6b447de07 --- /dev/null +++ b/fastlane/sdk_size_export_options.plist @@ -0,0 +1,33 @@ + + + + + compileBitcode + + destination + export + method + release-testing + provisioningProfiles + + io.getstream.iOS.VideoDemoApp + match AdHoc io.getstream.iOS.VideoDemoApp + io.getstream.iOS.DemoAppUIKit + match AdHoc io.getstream.iOS.DemoAppUIKit + io.getstream.iOS.VideoDemoApp.CallIntent + match AdHoc io.getstream.iOS.VideoDemoApp.CallIntent + io.getstream.iOS.VideoDemoApp.ScreenSharing + match AdHoc io.getstream.iOS.VideoDemoApp.ScreenSharing + + signingCertificate + Apple Distribution + signingStyle + manual + stripSwiftSymbols + + teamID + EHV7XZLAHA + thinning + iPhone15,2 + +