├── .github └── workflows │ ├── data-channel-sample.yml │ ├── deco-streaming-sample.yml │ ├── screencast-sample.yml │ ├── simulcast-sample.yml │ ├── spotlight-sample.yml │ └── video-chat-sample.yml ├── .gitignore ├── CHANGES.md ├── DataChannelSample ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── DataChannelSample.xcodeproj │ └── project.pbxproj ├── DataChannelSample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── ConfigViewController.swift │ ├── Environment.example.swift │ ├── Info.plist │ ├── SceneDelegate.swift │ ├── SoraSDKManager.swift │ └── VideoChatRoomViewController.swift ├── Podfile └── README.md ├── DecoStreamingSample ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── DecoStreamingSample.xcodeproj │ └── project.pbxproj ├── DecoStreamingSample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Classes │ │ ├── AVCaptureVideoOrientation+Extension.swift │ │ ├── AVCaptureVideoPreviewView.swift │ │ ├── PublisherConfigViewController.swift │ │ ├── PublisherVideoViewController.swift │ │ └── SoraSDKManager.swift │ ├── Environment.example.swift │ ├── Info.plist │ └── InfoPlist.strings ├── Podfile └── README.md ├── LICENSE ├── README.md ├── ScreenCastSample ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── Podfile ├── README.md ├── ScreenCastSample.xcodeproj │ └── project.pbxproj └── ScreenCastSample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Classes │ ├── GameViewController.swift │ ├── PublisherConfigViewController.swift │ ├── ScreenRecorder.swift │ └── SoraSDKManager.swift │ ├── Environment.example.swift │ ├── Info.plist │ └── InfoPlist.strings ├── SimulcastSample ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── Podfile ├── README.md ├── SimulcastSample.xcodeproj │ └── project.pbxproj └── SimulcastSample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Classes │ ├── ConfigViewController.swift │ ├── SoraSDKManager.swift │ └── VideoChatRoomViewController.swift │ ├── Environment.example.swift │ ├── Info.plist │ ├── InfoPlist.strings │ └── SceneDelegate.swift ├── SpotlightSample ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── Podfile ├── README.md ├── SpotlightSample.xcodeproj │ └── project.pbxproj └── SpotlightSample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Classes │ ├── ConfigViewController.swift │ ├── SoraSDKManager.swift │ └── VideoChatRoomViewController.swift │ ├── Environment.example.swift │ ├── Info.plist │ └── SceneDelegate.swift ├── VideoChatSample ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── Podfile ├── README.md ├── VideoChatSample.xcodeproj │ └── project.pbxproj └── VideoChatSample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Classes │ ├── ConfigViewController.swift │ ├── SoraSDKManager.swift │ └── VideoChatRoomViewController.swift │ ├── Environment.example.swift │ ├── Info.plist │ └── InfoPlist.strings └── lint-format.sh /.github/workflows/data-channel-sample.yml: -------------------------------------------------------------------------------- 1 | name: Build DataChannelSample 2 | 3 | defaults: 4 | run: 5 | working-directory: ./DataChannelSample 6 | 7 | on: 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - 'CHANGES.md' 12 | - 'LICENSE' 13 | schedule: 14 | - cron: "0 0 * * *" 15 | 16 | jobs: 17 | build: 18 | runs-on: macos-14 19 | env: 20 | XCODE: /Applications/Xcode_15.4.app 21 | XCODE_SDK: iphoneos17.5 22 | WORKSPACE: DataChannelSample 23 | SCHEME: DataChannelSample 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Select Xcode Version 27 | run: sudo xcode-select -s '${{ env.XCODE }}/Contents/Developer' 28 | - name: Show Xcode Version 29 | run: xcodebuild -version 30 | - name: Show CocoaPods Version 31 | run: pod --version 32 | - name: Restore Pods 33 | uses: actions/cache@v4 34 | with: 35 | path: Pods 36 | key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-pods- 39 | - name: Install Dependences 40 | run: | 41 | pod repo update 42 | pod install 43 | - name: Create Environment.swift 44 | run: | 45 | cp DataChannelSample/Environment.example.swift DataChannelSample/Environment.swift 46 | - name: Build Xcode Project 47 | run: | 48 | set -o pipefail && \ 49 | xcodebuild \ 50 | -workspace '${{ env.WORKSPACE }}.xcworkspace' \ 51 | -scheme '${{ env.SCHEME }}' \ 52 | -sdk ${{ env.XCODE_SDK }} \ 53 | -arch arm64 \ 54 | -configuration Release \ 55 | -derivedDataPath build \ 56 | clean build \ 57 | CODE_SIGNING_REQUIRED=NO \ 58 | CODE_SIGNING_ALLOWED=NO \ 59 | CODE_SIGN_IDENTITY= \ 60 | PROVISIONING_PROFILE= \ 61 | ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS=NO 62 | - name: Check uncommitted unformatted code 63 | run: | 64 | ../lint-format.sh -------------------------------------------------------------------------------- /.github/workflows/deco-streaming-sample.yml: -------------------------------------------------------------------------------- 1 | name: Build DecoStreamingSample 2 | 3 | defaults: 4 | run: 5 | working-directory: ./DecoStreamingSample 6 | 7 | on: 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - 'CHANGES.md' 12 | - 'LICENSE' 13 | schedule: 14 | - cron: "0 0 * * *" 15 | 16 | jobs: 17 | build: 18 | runs-on: macos-14 19 | env: 20 | XCODE: /Applications/Xcode_15.4.app 21 | XCODE_SDK: iphoneos17.5 22 | WORKSPACE: DecoStreamingSample 23 | SCHEME: DecoStreamingSample 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Select Xcode Version 27 | run: sudo xcode-select -s '${{ env.XCODE }}/Contents/Developer' 28 | - name: Show Xcode Version 29 | run: xcodebuild -version 30 | - name: Show CocoaPods Version 31 | run: pod --version 32 | - name: Restore Pods 33 | uses: actions/cache@v4 34 | with: 35 | path: Pods 36 | key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-pods- 39 | - name: Install Dependences 40 | run: | 41 | pod repo update 42 | pod install 43 | - name: Create Environment.swift 44 | run: | 45 | cp DecoStreamingSample/Environment.example.swift DecoStreamingSample/Environment.swift 46 | - name: Build Xcode Project 47 | run: | 48 | set -o pipefail && \ 49 | xcodebuild \ 50 | -workspace '${{ env.WORKSPACE }}.xcworkspace' \ 51 | -scheme '${{ env.SCHEME }}' \ 52 | -sdk ${{ env.XCODE_SDK }} \ 53 | -arch arm64 \ 54 | -configuration Release \ 55 | -derivedDataPath build \ 56 | clean build \ 57 | CODE_SIGNING_REQUIRED=NO \ 58 | CODE_SIGNING_ALLOWED=NO \ 59 | CODE_SIGN_IDENTITY= \ 60 | PROVISIONING_PROFILE= \ 61 | ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS=NO 62 | - name: Check uncommitted unformatted code 63 | run: | 64 | ../lint-format.sh -------------------------------------------------------------------------------- /.github/workflows/screencast-sample.yml: -------------------------------------------------------------------------------- 1 | name: Build ScreenCastSample 2 | 3 | defaults: 4 | run: 5 | working-directory: ./ScreenCastSample 6 | 7 | on: 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - 'CHANGES.md' 12 | - 'LICENSE' 13 | schedule: 14 | - cron: "0 0 * * *" 15 | 16 | jobs: 17 | build: 18 | runs-on: macos-14 19 | env: 20 | XCODE: /Applications/Xcode_15.4.app 21 | XCODE_SDK: iphoneos17.5 22 | WORKSPACE: ScreenCastSample 23 | SCHEME: ScreenCastSample 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Select Xcode Version 27 | run: sudo xcode-select -s '${{ env.XCODE }}/Contents/Developer' 28 | - name: Show Xcode Version 29 | run: xcodebuild -version 30 | - name: Show CocoaPods Version 31 | run: pod --version 32 | - name: Restore Pods 33 | uses: actions/cache@v4 34 | with: 35 | path: Pods 36 | key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-pods- 39 | - name: Install Dependences 40 | run: | 41 | pod repo update 42 | pod install 43 | - name: Create Environment.swift 44 | run: | 45 | cp ScreenCastSample/Environment.example.swift ScreenCastSample/Environment.swift 46 | - name: Build Xcode Project 47 | run: | 48 | set -o pipefail && \ 49 | xcodebuild \ 50 | -workspace '${{ env.WORKSPACE }}.xcworkspace' \ 51 | -scheme '${{ env.SCHEME }}' \ 52 | -sdk ${{ env.XCODE_SDK }} \ 53 | -arch arm64 \ 54 | -configuration Release \ 55 | -derivedDataPath build \ 56 | clean build \ 57 | CODE_SIGNING_REQUIRED=NO \ 58 | CODE_SIGNING_ALLOWED=NO \ 59 | CODE_SIGN_IDENTITY= \ 60 | PROVISIONING_PROFILE= \ 61 | ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS=NO 62 | - name: Check uncommitted unformatted code 63 | run: | 64 | ../lint-format.sh -------------------------------------------------------------------------------- /.github/workflows/simulcast-sample.yml: -------------------------------------------------------------------------------- 1 | name: Build SimulcastSample 2 | 3 | defaults: 4 | run: 5 | working-directory: ./SimulcastSample 6 | 7 | on: 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - 'CHANGES.md' 12 | - 'LICENSE' 13 | schedule: 14 | - cron: "0 0 * * *" 15 | 16 | jobs: 17 | build: 18 | runs-on: macos-14 19 | env: 20 | XCODE: /Applications/Xcode_15.4.app 21 | XCODE_SDK: iphoneos17.5 22 | WORKSPACE: SimulcastSample 23 | SCHEME: SimulcastSample 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Select Xcode Version 27 | run: sudo xcode-select -s '${{ env.XCODE }}/Contents/Developer' 28 | - name: Show Xcode Version 29 | run: xcodebuild -version 30 | - name: Show CocoaPods Version 31 | run: pod --version 32 | - name: Restore Pods 33 | uses: actions/cache@v4 34 | with: 35 | path: Pods 36 | key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-pods- 39 | - name: Install Dependences 40 | run: | 41 | pod repo update 42 | pod install 43 | - name: Create Environment.swift 44 | run: | 45 | cp SimulcastSample/Environment.example.swift SimulcastSample/Environment.swift 46 | - name: Build Xcode Project 47 | run: | 48 | set -o pipefail && \ 49 | xcodebuild \ 50 | -workspace '${{ env.WORKSPACE }}.xcworkspace' \ 51 | -scheme '${{ env.SCHEME }}' \ 52 | -sdk ${{ env.XCODE_SDK }} \ 53 | -arch arm64 \ 54 | -configuration Release \ 55 | -derivedDataPath build \ 56 | clean build \ 57 | CODE_SIGNING_REQUIRED=NO \ 58 | CODE_SIGNING_ALLOWED=NO \ 59 | CODE_SIGN_IDENTITY= \ 60 | PROVISIONING_PROFILE= \ 61 | ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS=NO 62 | - name: Check uncommitted unformatted code 63 | run: | 64 | ../lint-format.sh -------------------------------------------------------------------------------- /.github/workflows/spotlight-sample.yml: -------------------------------------------------------------------------------- 1 | name: Build SpotlightSample 2 | 3 | defaults: 4 | run: 5 | working-directory: ./SpotlightSample 6 | 7 | on: 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - 'CHANGES.md' 12 | - 'LICENSE' 13 | schedule: 14 | - cron: "0 0 * * *" 15 | 16 | jobs: 17 | build: 18 | runs-on: macos-14 19 | env: 20 | XCODE: /Applications/Xcode_15.4.app 21 | XCODE_SDK: iphoneos17.5 22 | WORKSPACE: SpotlightSample 23 | SCHEME: SpotlightSample 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Select Xcode Version 27 | run: sudo xcode-select -s '${{ env.XCODE }}/Contents/Developer' 28 | - name: Show Xcode Version 29 | run: xcodebuild -version 30 | - name: Show CocoaPods Version 31 | run: pod --version 32 | - name: Restore Pods 33 | uses: actions/cache@v4 34 | with: 35 | path: Pods 36 | key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-pods- 39 | - name: Install Dependences 40 | run: | 41 | pod repo update 42 | pod install 43 | - name: Create Environment.swift 44 | run: | 45 | cp SpotlightSample/Environment.example.swift SpotlightSample/Environment.swift 46 | - name: Build Xcode Project 47 | run: | 48 | set -o pipefail && \ 49 | xcodebuild \ 50 | -workspace '${{ env.WORKSPACE }}.xcworkspace' \ 51 | -scheme '${{ env.SCHEME }}' \ 52 | -sdk ${{ env.XCODE_SDK }} \ 53 | -arch arm64 \ 54 | -configuration Release \ 55 | -derivedDataPath build \ 56 | clean build \ 57 | CODE_SIGNING_REQUIRED=NO \ 58 | CODE_SIGNING_ALLOWED=NO \ 59 | CODE_SIGN_IDENTITY= \ 60 | PROVISIONING_PROFILE= \ 61 | ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS=NO 62 | - name: Check uncommitted unformatted code 63 | run: | 64 | ../lint-format.sh -------------------------------------------------------------------------------- /.github/workflows/video-chat-sample.yml: -------------------------------------------------------------------------------- 1 | name: Build VideoChatSample 2 | 3 | defaults: 4 | run: 5 | working-directory: ./VideoChatSample 6 | 7 | on: 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - 'CHANGES.md' 12 | - 'LICENSE' 13 | schedule: 14 | - cron: "0 0 * * *" 15 | 16 | jobs: 17 | build: 18 | runs-on: macos-14 19 | env: 20 | XCODE: /Applications/Xcode_15.4.app 21 | XCODE_SDK: iphoneos17.5 22 | WORKSPACE: VideoChatSample 23 | SCHEME: VideoChatSample 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Select Xcode Version 27 | run: sudo xcode-select -s '${{ env.XCODE }}/Contents/Developer' 28 | - name: Show Xcode Version 29 | run: xcodebuild -version 30 | - name: Show CocoaPods Version 31 | run: pod --version 32 | - name: Restore Pods 33 | uses: actions/cache@v4 34 | with: 35 | path: Pods 36 | key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-pods- 39 | - name: Install Dependences 40 | run: | 41 | pod repo update 42 | pod install 43 | - name: Create Environment.swift 44 | run: | 45 | cp VideoChatSample/Environment.example.swift VideoChatSample/Environment.swift 46 | - name: Build Xcode Project 47 | run: | 48 | set -o pipefail && \ 49 | xcodebuild \ 50 | -workspace '${{ env.WORKSPACE }}.xcworkspace' \ 51 | -scheme '${{ env.SCHEME }}' \ 52 | -sdk ${{ env.XCODE_SDK }} \ 53 | -arch arm64 \ 54 | -configuration Release \ 55 | -derivedDataPath build \ 56 | clean build \ 57 | CODE_SIGNING_REQUIRED=NO \ 58 | CODE_SIGNING_ALLOWED=NO \ 59 | CODE_SIGN_IDENTITY= \ 60 | PROVISIONING_PROFILE= \ 61 | ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS=NO 62 | - name: Check uncommitted unformatted code 63 | run: | 64 | ../lint-format.sh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | project.xcworkspace 20 | Environment.swift 21 | 22 | ## Other 23 | .DS_Store 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | *.swp 29 | doc 30 | 31 | ## Obj-C/Swift specific 32 | *.hmap 33 | *.ipa 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | # Swift Package Manager 40 | # 41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 42 | # Packages/ 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | # Pods/ 52 | **/Pods 53 | **/Podfile.lock 54 | 55 | # Carthage 56 | # 57 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 58 | # Carthage/Checkouts 59 | 60 | */Carthage/Build 61 | */Carthage/Checkouts 62 | 63 | # fastlane 64 | # 65 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 66 | # screenshots whenever they are needed. 67 | # For more information about the recommended setup visit: 68 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 69 | 70 | fastlane/report.xml 71 | fastlane/screenshots 72 | 73 | **.xcworkspace 74 | DataChannelSample/DataChannelSample/Environment.swift 75 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 変更履歴 2 | 3 | - UPDATE 4 | - 下位互換がある変更 5 | - ADD 6 | - 下位互換がある追加 7 | - CHANGE 8 | - 下位互換のない変更 9 | - FIX 10 | - バグ修正 11 | 12 | ## develop 13 | 14 | ## sora-ios-sdk-2025.1.2 15 | 16 | **リリース日**: 2025-05-07 17 | 18 | - [UPDATE] Sora iOS SDK を 2025.1.2 にあげる 19 | - @zztkm 20 | 21 | ## sora-ios-sdk-2025.1.1 22 | 23 | **リリース日**: 2025-01-23 24 | 25 | - [UPDATE] Sora iOS SDK を 2025.1.1 にあげる 26 | - @miosakuma 27 | 28 | ## sora-ios-sdk-2025.1.0 29 | 30 | **リリース日**: 2025-01-21 31 | 32 | - [UPDATE] CocoaPods の platform の設定を 14.0 に上げる 33 | - @miosakuma 34 | - [UPDATE] システム条件を変更する 35 | - iOS 14 以降 36 | - macOS 15.0 以降 37 | - Xcode 16.0 38 | - @miosakuma 39 | - [UPDATE] 映像コーデックタイプの並び順を変更する 40 | - VP8 / VP9 / AV1 / H.264/ H.265 の順番になるように変更 41 | - @zztkm 42 | - [ADD] 映像コーデックタイプに H.265 を追加する 43 | - @zztkm 44 | - [ADD] none 項目がなかった `SimulcastSample` と `SpotlightSample` に none を追加 45 | - @zztkm 46 | 47 | ## sora-ios-sdk-2024.3.0 48 | 49 | **リリース日**: 2024-09-06 50 | 51 | - [UPDATE] 各サンプルの libwebrtc のログレベルを RTCLoggingSeverityNone から RTCLoggingSeverityInfo にする 52 | - libwebrtc のログを INFO レベルで出力するようにする 53 | - @zztkm 54 | - [UPDATE] GitHub Actions の Xcode のバージョンを 15.4 にあげる 55 | - 合わせて iOS の SDK を iphoneos17.5 にあげる 56 | - @miosakuma 57 | - [UPDATE] システム条件を変更する 58 | - macOS 14.6.1 以降 59 | - Xcode 15.4 60 | - WebRTC SFU Sora 2024.1.0 以降 61 | - @miosakuma 62 | 63 | ## sora-ios-sdk-2024.2.0 64 | 65 | - [UPDATE] Github Actions を actions/cache@v4 にあげる 66 | - @miosakuma 67 | - [UPDATE] Github Actions を macos-14 にあげる 68 | - @miosakuma 69 | - [UPDATE] Github Actions を Xcode 15.2, iphoneos17.2 にあげる 70 | - @miosakuma 71 | - [UPDATE] Github Actions のビルドオプションに `ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS=NO` を追加する 72 | - Xcode 15 で Asset のシンボルである、GeneratedAssetSymbols.swift が生成されるようになったがこのファイルが SwiftFormat エラー対象となる 73 | - CI では Asset のシンボル生成は不要であるため生成しないようオプション指定を行う 74 | - [Xcode 15 リリースノート - Asset Catalogs](https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Asset-Catalogs) 75 | - @miosakuma 76 | - [UPDATE] システム条件を変更する 77 | - macOS 14.4.1 以降 78 | - Xcode 15.3 79 | - Swift 5.10 80 | - @miosakuma 81 | 82 | ## sora-ios-sdk-2024.1.0 83 | 84 | - [UPDATE] システム条件を変更する 85 | - macOS 14.3.1 以降 86 | - WebRTC SFU Sora 2023.2.0 以降 87 | - Xcode 15.2 88 | - Swift 5.9.2 89 | - CocoaPods 1.15.2 以降 90 | - @miosakuma 91 | 92 | ## sora-ios-sdk-2023.3.1 93 | 94 | - [UPDATE] Sora iOS SDK を 2023.3.1 にあげる 95 | - @miosakuma 96 | 97 | ## sora-ios-sdk-2023.3.1 98 | 99 | - [UPDATE] SwiftLint, SwiftFormat/CLI を一時的にコメントアウトする 100 | - SwiftLint, SwiftFormat/CLI が Swift Swift 5.9 に対応できていないため 101 | - 対応が完了したら戻す 102 | - @miosakuma 103 | - [FIX] VideoChatSample で signalingConnectMetadata が設定できない不具合を修正 104 | - @miosakuma 105 | 106 | ## sora-ios-sdk-2023.2.0 107 | 108 | - [ADD] DataChannelSample の映像コーデックに AV1 を追加する 109 | - @miosakuma 110 | - [ADD] DecoStreamingSample の映像コーデックに AV1 を追加する 111 | - @miosakuma 112 | - [ADD] ScreenCastSample の映像コーデックに AV1 を追加する 113 | - @miosakuma 114 | - [ADD] SimulcastSample の映像コーデックに VP9 と AV1 を追加する 115 | - @miosakuma 116 | - [ADD] VideoChatSample の映像コーデックに AV1 を追加する 117 | - @miosakuma 118 | - [ADD] VideoChatSample に映像コーデックプロファイル設定を追加する 119 | - @miosakuma 120 | - [UPDATE] システム条件を変更する 121 | - macOS 13.4.1 以降 122 | - Xcode 14.3.1 123 | - Swift 5.8.1 124 | - WebRTC SFU Sora 2023.1.0 以降 125 | - CocoaPods 1.12.1 以降 126 | - @miosakuma 127 | - [FIX] ScreenCastSample の H.264 の映像が送信されない不具合を修正する 128 | - 画像を半分にリサイズしてエンコード可能なサイズとする 129 | - @szktty 130 | 131 | ## sora-ios-sdk-2023.1.0 132 | 133 | - [UPDATE] システム条件を変更する 134 | - macOS 13.3 以降 135 | - Xcode 14.3 136 | - Swift 5.8 137 | - WebRTC SFU Sora 2022.2.0 以降 138 | - CocoaPods 1.12.0 以降 139 | - @miosakuma 140 | 141 | ## sora-ios-sdk-2022.6.0 142 | 143 | - [CHANGE] システム条件を変更する 144 | - アーキテクチャ から x86_64 を削除 145 | - macOS 12.6 以降 146 | - Xcode 14.0 147 | - Swift 5.7 148 | - CocoaPods 1.11.3 以降 149 | - @miosakuma 150 | 151 | ## sora-ios-sdk-2022.5.0 152 | 153 | - [UPDATE] システム条件を変更する 154 | - WebRTC SFU Sora 2022.1.1 以降 155 | - Xcode 13.4.1 156 | - @miosakuma 157 | - [FIX] DecoStreamingSample の iOS 14 初期に発生していたクラッシュ不具合の暫定処理を削除 158 | - iOS 14.6 で問題が解消されていたため当初の処理に戻す 159 | - @szktty 160 | 161 | ## sora-ios-sdk-2022.4.0 162 | 163 | - [UPDATE] システム条件を変更する 164 | - macOS 12.3 以降 165 | - WebRTC SFU Sora 2022.1.0 以降 166 | - @miosakuma 167 | - [ADD] VideoChatSample, SimulcastSample, SpotlightSample で bundle_id を設定する 168 | - @enm10k 169 | - [ADD] SpotlightSample の映像コーデックに VP9 を追加する 170 | - @enm10k 171 | - [ADD] SpotlightSample サイマルキャストの有効・無効を切り替える設定を追加する 172 | - @enm10k 173 | 174 | ## sora-ios-sdk-2022.3.0 175 | 176 | - [UPDATE] DecoStreamingSample をマルチストリームにする 177 | - @szktty 178 | - [UPDATE] ScreenCastSample をマルチストリームにする 179 | - @miosakuma 180 | - [ADD] サーバから切断されたとき接続前の状態に戻る 181 | - @szktty 182 | - [CHANGE] RealTimeStreamingSample を廃止する 183 | - @miosakuma 184 | 185 | ## sora-ios-sdk-2022.2.0 186 | 187 | - [UPDATE] Github Actions でのビルド処理で利用する Podfile を Podfile.dev から Podfile に変更する 188 | - @miosakuma 189 | - [UPDATE] Environment.example.swift に signalingConnectMetadata を追加する 190 | - @miosakuma 191 | - [ADD] DataChannelSample の追加する 192 | - @szktty 193 | - [FIX] 接続ボタンを連打された際に複数の接続が作成される不具合を修正する 194 | - @szktty, @miosakuma 195 | -------------------------------------------------------------------------------- /DataChannelSample/.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 2 | -------------------------------------------------------------------------------- /DataChannelSample/.swiftformat: -------------------------------------------------------------------------------- 1 | --exclude Pods 2 | --indent 4 3 | --semicolons inline 4 | --trailingclosures 5 | --wrapparameters after-first 6 | --header strip 7 | 8 | --disable redundantInit,sortedSwitchCases,strongOutlets,unusedArguments,wrapSwitchCases 9 | -------------------------------------------------------------------------------- /DataChannelSample/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - DataChannelSample 3 | excluded: 4 | - Pods 5 | disabled_rules: 6 | - identifier_name 7 | - force_cast 8 | - force_try 9 | - cyclomatic_complexity 10 | - function_body_length 11 | - file_length 12 | - line_length 13 | - type_body_length 14 | - weak_delegate 15 | - opening_brace 16 | - closing_brace 17 | - anonymous_argument_in_multiline_closure 18 | - conditional_returns_on_newline 19 | - multiline_arguments 20 | - multiline_arguments_brackets 21 | - multiline_literal_brackets 22 | - multiline_parameters 23 | - multiline_parameters_brackets 24 | - vertical_parameter_alignment 25 | - vertical_parameter_alignment_on_call 26 | - vertical_whitespace 27 | - vertical_whitespace_between_cases 28 | - vertical_whitespace_closing_braces 29 | - vertical_whitespace_opening_braces 30 | - colon 31 | - comma 32 | - comment_spacing 33 | - trailing_comma 34 | - trailing_newline 35 | - trailing_whitespace 36 | - closure_parameter_position 37 | - closure_end_indentation 38 | - closure_spacing 39 | - for_where 40 | - large_tuple 41 | - todo 42 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 6 | // Override point for customization after application launch. 7 | true 8 | } 9 | 10 | // MARK: UISceneSession Lifecycle 11 | 12 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 13 | // Called when a new scene session is being created. 14 | // Use this method to select a configuration to create the new scene with. 15 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 16 | } 17 | 18 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 19 | // Called when the user discards a scene session. 20 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 21 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Environment.example.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | 環境設定を記述します。 5 | このファイルをコピーして、ファイル名を Environment.swift に変更してください。 6 | */ 7 | enum Environment { 8 | // Sora SDKの接続先URL。複数指定可能 9 | static let urls: [URL] = [URL(string: "wss://sora.example.com/signaling")!] 10 | 11 | // チャネル ID 12 | static let channelId = "sora" 13 | 14 | // type: connect に含めるメタデータ 15 | static let signalingConnectMetadata: Encodable? = nil 16 | 17 | // DataChannel メッセージングに使うラベル 18 | static let dataChannelLabels = ["#spam", "#egg"] 19 | } 20 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | NSCameraUsageDescription 25 | WebRTC 26 | NSMicrophoneUsageDescription 27 | WebRTC 28 | 29 | 30 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 4 | var window: UIWindow? 5 | 6 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 7 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 8 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 9 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 10 | // guard let _ = (scene as? UIWindowScene) else { return } 11 | } 12 | 13 | func sceneDidDisconnect(_ scene: UIScene) { 14 | // Called as the scene is being released by the system. 15 | // This occurs shortly after the scene enters the background, or when its session is discarded. 16 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 17 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 18 | } 19 | 20 | func sceneDidBecomeActive(_ scene: UIScene) { 21 | // Called when the scene has moved from an inactive state to an active state. 22 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 23 | } 24 | 25 | func sceneWillResignActive(_ scene: UIScene) { 26 | // Called when the scene will move from an active state to an inactive state. 27 | // This may occur due to temporary interruptions (ex. an incoming phone call). 28 | } 29 | 30 | func sceneWillEnterForeground(_ scene: UIScene) { 31 | // Called as the scene transitions from the background to the foreground. 32 | // Use this method to undo the changes made on entering the background. 33 | } 34 | 35 | func sceneDidEnterBackground(_ scene: UIScene) { 36 | // Called as the scene transitions from the foreground to the background. 37 | // Use this method to save data, release shared resources, and store enough scene-specific state information 38 | // to restore the scene back to its current state. 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DataChannelSample/DataChannelSample/SoraSDKManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Sora 3 | 4 | /** 5 | Sora SDK関連の、アプリケーション全体で共通して行いたい処理を行うシングルトン・マネージャ・クラスです。 6 | 7 | このようなクラスを用意しておくと、Sora SDKのConnectionをアプリケーション全体で一つだけ確実に管理する事が可能になるため、おすすめです。 8 | */ 9 | class SoraSDKManager { 10 | /** 11 | SoraSDKManagerのシングルトンインスタンスです。 12 | */ 13 | static let shared = SoraSDKManager() 14 | 15 | /** 16 | 現在接続中のSora SDKのMediaChannelです。 17 | 18 | 殆どの場合、アプリケーション全体で一つだけ同時にMediaChannelに接続することになるので、シングルトンとして用意すると便利に使えます。 19 | */ 20 | private(set) var currentMediaChannel: MediaChannel? 21 | 22 | /** 23 | DataChannel メッセージでランダムなバイナリを送信するかどうかを指定します。 24 | VideoChatViewController から参照されます。 25 | */ 26 | var dataChannelRandomBinary: Bool = false 27 | 28 | /** 29 | シングルトンにしたいので、イニシャライザはprivateにしてあります。 30 | */ 31 | private init() { 32 | // SDK のログを表示します。 33 | // 送受信されるシグナリングの内容や接続エラーを確認できます。 34 | Logger.shared.level = .debug 35 | Sora.setWebRTCLogLevel(.info) 36 | } 37 | 38 | /** 39 | 新たにSoraに接続を試みます。接続に成功した場合、currentMediaChannelが更新されます。 40 | 41 | 既に接続されており、currentMediaChannelが設定されている場合は新たに接続ができないようにしてあります。 42 | その場合は、一旦先に `disconnect()` を呼び出して、現在の接続を終了してください。 43 | */ 44 | func connect(with configuration: Configuration, 45 | completionHandler: ((Error?) -> Void)?) 46 | { 47 | // 既にcurrentMediaChannelが設定されている場合は、接続済みとみなし、何もしないで終了します。 48 | guard currentMediaChannel == nil else { 49 | return 50 | } 51 | // Soraに接続を試みます。 52 | _ = Sora.shared.connect(configuration: configuration) { [weak self] mediaChannel, error in 53 | // 接続に成功した場合は、mediaChannelに値が返され、errorがnilになります。 54 | // 一方、接続に失敗した場合は、mediaChannelはnilとなり、errorが返されます。 55 | self?.currentMediaChannel = mediaChannel 56 | completionHandler?(error) 57 | } 58 | } 59 | 60 | /** 61 | 既に接続済みのmediaChannelから切断します。 62 | 63 | currentMediaChannelがnilで、まだ接続されていないときは、何もしないで終了します。 64 | */ 65 | func disconnect() { 66 | guard let mediaChannel = currentMediaChannel else { 67 | return 68 | } 69 | mediaChannel.disconnect(error: nil) 70 | currentMediaChannel = nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /DataChannelSample/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/shiguredo/sora-ios-sdk-specs.git' 2 | source 'https://cdn.cocoapods.org/' 3 | 4 | platform :ios, '14.0' 5 | 6 | target 'DataChannelSample' do 7 | use_frameworks! 8 | pod 'Sora', '2025.1.2' 9 | pod 'SwiftLint' 10 | pod 'SwiftFormat/CLI' 11 | end 12 | -------------------------------------------------------------------------------- /DataChannelSample/README.md: -------------------------------------------------------------------------------- 1 | # DataChannelSample (DataChannel メッセージング機能サンプル) 2 | 3 | このサンプルでは、 DataChannel メッセージング機能でメッセージを送受信するアプリを Sora iOS SDK を用いて実装する方法を説明しています。 4 | 映像とメッセージを同時に送受信するサンプルアプリです。 5 | 6 | 7 | ## ビルド環境 8 | 9 | サンプルアプリをビルドする際の環境については [システム条件](../README.md#システム条件) をご確認ください。 10 | 11 | ## ビルド方法 12 | 13 | 1. CocoaPods でライブラリを取得します。 14 | 15 | ``` 16 | $ pod install 17 | ``` 18 | 19 | 2. ``DataChannelSample/Environment.example.swift`` のファイル名を ``DataChannelSample/Environment.swift`` に変更し、接続情報を設定します。 20 | 21 | ``` 22 | $ cp DataChannelSample/Environment.example.swift DataChannelSample/Environment.swift 23 | ``` 24 | 25 | 3. ``DataChannelSample.xcworkspace`` を Xcode で開いてビルドします。 26 | 27 | ## サンプルアプリの使い方 28 | 29 | このサンプルアプリでは、ビデオチャットと同様の仕様に加えて任意のメッセージを送受信できます。 30 | メッセージの送受信に使うラベルは `Environment.swift` で変更できます。 31 | 32 | 映像とラベルは関連していません。同一のチャネルに接続したどのクライアントも任意のラベルでメッセージを送信できます。 33 | 34 | 35 | ## 実装上の詳細について 36 | 37 | 実装上の詳細につきましてはサンプルアプリのソースコード上に詳細なコメントを用意してありますので、適時そちらをご確認ください。 38 | -------------------------------------------------------------------------------- /DecoStreamingSample/.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 2 | -------------------------------------------------------------------------------- /DecoStreamingSample/.swiftformat: -------------------------------------------------------------------------------- 1 | --exclude Pods 2 | --indent 4 3 | --semicolons inline 4 | --trailingclosures 5 | --wrapparameters after-first 6 | --header strip 7 | 8 | --disable redundantInit,sortedSwitchCases,strongOutlets,unusedArguments,wrapSwitchCases 9 | -------------------------------------------------------------------------------- /DecoStreamingSample/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - DecoStreamingSample 3 | excluded: 4 | - Pods 5 | disabled_rules: 6 | - identifier_name 7 | - force_cast 8 | - force_try 9 | - cyclomatic_complexity 10 | - function_body_length 11 | - file_length 12 | - line_length 13 | - type_body_length 14 | - weak_delegate 15 | - opening_brace 16 | - closing_brace 17 | - anonymous_argument_in_multiline_closure 18 | - conditional_returns_on_newline 19 | - multiline_arguments 20 | - multiline_arguments_brackets 21 | - multiline_literal_brackets 22 | - multiline_parameters 23 | - multiline_parameters_brackets 24 | - vertical_parameter_alignment 25 | - vertical_parameter_alignment_on_call 26 | - vertical_whitespace 27 | - vertical_whitespace_between_cases 28 | - vertical_whitespace_closing_braces 29 | - vertical_whitespace_opening_braces 30 | - colon 31 | - comma 32 | - comment_spacing 33 | - trailing_comma 34 | - trailing_newline 35 | - trailing_whitespace 36 | - closure_parameter_position 37 | - closure_end_indentation 38 | - closure_spacing 39 | - for_where 40 | - large_tuple 41 | - todo 42 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | var window: UIWindow? 6 | 7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 8 | true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Classes/AVCaptureVideoOrientation+Extension.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import UIKit 3 | 4 | /** 5 | サンプルアプリ内で使うための、便利なExtensionを定義します。 6 | */ 7 | extension AVCaptureVideoOrientation { 8 | /** 9 | UIDeviceOrientationを元にAVCaptureVideoOrientationを返します。 10 | */ 11 | init?(deviceOrientation: UIDeviceOrientation) { 12 | switch deviceOrientation { 13 | case .portrait: self = .portrait 14 | case .portraitUpsideDown: self = .portraitUpsideDown 15 | case .landscapeLeft: self = .landscapeRight 16 | case .landscapeRight: self = .landscapeLeft 17 | default: return nil 18 | } 19 | } 20 | 21 | /** 22 | UIInterfaceOrientationを元にAVCaptureVideoOrientationを返します。 23 | */ 24 | init?(interfaceOrientation: UIInterfaceOrientation) { 25 | switch interfaceOrientation { 26 | case .portrait: self = .portrait 27 | case .portraitUpsideDown: self = .portraitUpsideDown 28 | case .landscapeLeft: self = .landscapeLeft 29 | case .landscapeRight: self = .landscapeRight 30 | default: return nil 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Classes/AVCaptureVideoPreviewView.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import UIKit 3 | 4 | /** 5 | AVFoundationの機能を用いて、カメラがキャプチャしている現在の映像をそのままプレビューとして表示するためのビューです。 6 | このビューはあくまでカメラがキャプチャしている映像のプレビューのみを表示することができ、外部で加工されたCVPixelBufferを表示することはできません。 7 | 現在Sora SDKによって配信されている映像をそのまま表示したい場合には、Sora.VideoViewを使用してください。 8 | */ 9 | class AVCaptureVideoPreviewView: UIView { 10 | override class var layerClass: AnyClass { 11 | AVCaptureVideoPreviewLayer.self 12 | } 13 | 14 | var previewLayer: AVCaptureVideoPreviewLayer { 15 | layer as! AVCaptureVideoPreviewLayer 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill 21 | } 22 | 23 | override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill 26 | } 27 | 28 | func updateVideoOrientationUsingStatusBarOrientation() { 29 | let statusBarOrientation = UIApplication.shared.statusBarOrientation 30 | let videoOrientation = AVCaptureVideoOrientation(interfaceOrientation: statusBarOrientation) ?? .portrait 31 | previewLayer.connection?.videoOrientation = videoOrientation 32 | } 33 | 34 | func updateVideoOrientationUsingDeviceOrientation() { 35 | let deviceOrientation = UIDevice.current.orientation 36 | guard deviceOrientation.isPortrait || deviceOrientation.isLandscape else { 37 | return 38 | } 39 | guard let videoOrientation = AVCaptureVideoOrientation(deviceOrientation: deviceOrientation) else { 40 | return 41 | } 42 | previewLayer.connection?.videoOrientation = videoOrientation 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Classes/PublisherConfigViewController.swift: -------------------------------------------------------------------------------- 1 | import Sora 2 | import UIKit 3 | 4 | /** 5 | 配信設定画面です。 6 | */ 7 | class PublisherConfigViewController: UITableViewController { 8 | /// チャンネルIDを入力させる欄です。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 9 | @IBOutlet var channelIdTextField: UITextField! 10 | /// 動画のコーデックを指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 11 | @IBOutlet var videoCodecSegmentedControl: UISegmentedControl! 12 | 13 | /// 接続試行中かどうかを表します。 14 | var isConnecting = false 15 | 16 | /** 17 | 画面起動時の処理を記述します。 18 | */ 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | channelIdTextField.text = Environment.channelId 23 | } 24 | 25 | /** 26 | 行がタップされたときの処理を記述します。 27 | */ 28 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 29 | // まず最初にタップされた行の選択状態を解除します。 30 | tableView.deselectRow(at: indexPath, animated: true) 31 | 32 | // 選択された行が「接続」ボタンでない限り無視します。 33 | guard indexPath.section == 2, indexPath.row == 0 else { 34 | return 35 | } 36 | 37 | // チャンネルIDが入力されていない限り無視します。 38 | guard let channelId = channelIdTextField.text, !channelId.isEmpty else { 39 | return 40 | } 41 | // 接続試行中なら無視します。 42 | if isConnecting { 43 | return 44 | } 45 | 46 | isConnecting = true 47 | 48 | // ユーザーが選択した設定をUIコントロールから取得します。 49 | let videoCodec: VideoCodec 50 | switch videoCodecSegmentedControl.selectedSegmentIndex { 51 | case 0: videoCodec = .default 52 | case 1: videoCodec = .vp8 53 | case 2: videoCodec = .vp9 54 | case 3: videoCodec = .av1 55 | case 4: videoCodec = .h264 56 | case 5: videoCodec = .h265 57 | default: fatalError() 58 | } 59 | 60 | // 入力された設定を元にSoraへ接続を行います。 61 | // この画面からは配信側に接続を行うため、role引数には .sendonly を指定しています。 62 | // また今回のサンプルアプリでは、デフォルトのカメラ映像のキャプチャではなく、加工されたカメラ映像のキャプチャを使用したいため、 63 | // videoCapturerOptionをカスタムに設定しておきます。 64 | SoraSDKManager.shared.connect( 65 | channelId: channelId, 66 | role: .sendonly, 67 | multistreamEnabled: true, 68 | videoCodec: videoCodec 69 | ) { [weak self] error in 70 | // 接続処理が終了したので false にします。 71 | self?.isConnecting = false 72 | 73 | if let error { 74 | // errorがnilでないばあいは、接続に失敗しています。 75 | // この場合は、エラー表示をユーザーに返すのが親切です。 76 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 77 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 78 | NSLog("[sample] SoraSDKManager connection error: \(error)") 79 | DispatchQueue.main.async { 80 | let alertController = UIAlertController(title: "接続に失敗しました", 81 | message: error.localizedDescription, 82 | preferredStyle: .alert) 83 | alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 84 | self?.present(alertController, animated: true, completion: nil) 85 | } 86 | } else { 87 | // errorがnilの場合は、接続に成功しています。 88 | NSLog("[sample] SoraSDKManager connected.") 89 | 90 | // 次の配信画面に遷移します。 91 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 92 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 93 | DispatchQueue.main.async { 94 | // ConnectセグエはMain.storyboard内で定義されているので、そちらをご確認ください。 95 | self?.performSegue(withIdentifier: "Connect", sender: self) 96 | } 97 | } 98 | } 99 | } 100 | 101 | /** 102 | 配信画面からのUnwind Segueの着地地点として定義してあります。 103 | 詳細はMain.storyboardの設定をご確認ください。 104 | */ 105 | @IBAction func onUnwindToPublisherConfig(_ segue: UIStoryboardSegue) { 106 | // 前の画面から戻ってきても、特に処理は何も行いません。 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Classes/SoraSDKManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Sora 3 | 4 | /** 5 | Sora SDK関連の、アプリケーション全体で共通して行いたい処理を行うシングルトン・マネージャ・クラスです。 6 | 7 | このようなクラスを用意しておくと、Sora SDKのConnectionをアプリケーション全体で一つだけ確実に管理する事が可能になるため、おすすめです。 8 | */ 9 | class SoraSDKManager { 10 | /** 11 | SoraSDKManagerのシングルトンインスタンスです。 12 | */ 13 | static let shared = SoraSDKManager() 14 | 15 | /** 16 | 現在接続中のSora SDKのMediaChannelです。 17 | 18 | 殆どの場合、アプリケーション全体で一つだけ同時にMediaChannelに接続することになるので、シングルトンとして用意すると便利に使えます。 19 | */ 20 | private(set) var currentMediaChannel: MediaChannel? 21 | 22 | /** 23 | シングルトンにしたいので、イニシャライザはprivateにしてあります。 24 | */ 25 | private init() { 26 | // SDK のログを表示します。 27 | // 送受信されるシグナリングの内容や接続エラーを確認できます。 28 | Logger.shared.level = .debug 29 | Sora.setWebRTCLogLevel(.info) 30 | } 31 | 32 | /** 33 | 新たにSoraに接続を試みます。接続に成功した場合、currentMediaChannelが更新されます。 34 | 35 | 既に接続されており、currentMediaChannelが設定されている場合は新たに接続ができないようにしてあります。 36 | その場合は、一旦先に `disconnect()` を呼び出して、現在の接続を終了してください。 37 | */ 38 | func connect(channelId: String, 39 | role: Role, 40 | multistreamEnabled: Bool, 41 | videoCodec: VideoCodec = .default, 42 | completionHandler: ((Error?) -> Void)?) 43 | { 44 | // 既にcurrentMediaChannelが設定されている場合は、接続済みとみなし、何もしないで終了します。 45 | guard currentMediaChannel == nil else { 46 | return 47 | } 48 | 49 | // Configurationを生成して、接続設定を行います。 50 | // 必須となる設定は URL、チャネル ID、ロール、マルチストリームの可否です。 51 | // その他の設定にはデフォルト値が指定されていますが、ここで必要に応じて自由に調整することが可能です。 52 | var configuration = Configuration(urlCandidates: Environment.urls, 53 | channelId: channelId, 54 | role: role, 55 | multistreamEnabled: multistreamEnabled) 56 | 57 | // 引数で指定された値を設定します。 58 | configuration.videoCodec = videoCodec 59 | configuration.cameraSettings.isEnabled = false 60 | configuration.signalingConnectMetadata = Environment.signalingConnectMetadata 61 | 62 | // Soraに接続を試みます。 63 | _ = Sora.shared.connect(configuration: configuration) { [weak self] mediaChannel, error in 64 | // 接続に成功した場合は、mediaChannelに値が返され、errorがnilになります。 65 | // 一方、接続に失敗した場合は、mediaChannelはnilとなり、errorが返されます。 66 | self?.currentMediaChannel = mediaChannel 67 | completionHandler?(error) 68 | NSLog("[sample] mediaChannel.connectedUrl: \(String(describing: mediaChannel?.connectedUrl))") 69 | } 70 | } 71 | 72 | /** 73 | 既に接続済みのmediaChannelから切断します。 74 | 75 | currentMediaChannelがnilで、まだ接続されていないときは、何もしないで終了します。 76 | */ 77 | func disconnect() { 78 | guard let mediaChannel = currentMediaChannel else { 79 | return 80 | } 81 | mediaChannel.disconnect(error: nil) 82 | currentMediaChannel = nil 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Environment.example.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Environment { 4 | // 接続するサーバーのシグナリング URL 5 | // 配列で複数の URL を指定することが可能です 6 | static let urls = [URL(string: "wss://sora.example.com/signaling")!] 7 | 8 | // チャネル ID 9 | static let channelId = "sora" 10 | 11 | // type: connect に含めるメタデータ 12 | static let signalingConnectMetadata: Encodable? = nil 13 | } 14 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 2.0.0 19 | CFBundleVersion 20 | 200.0.0 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | NSCameraUsageDescription 28 | NSCameraUsageDescription 29 | NSMicrophoneUsageDescription 30 | NSMicrophoneUsageDescription 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /DecoStreamingSample/DecoStreamingSample/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | CFBundleDisplayName = "デコ動画配信"; 2 | NSCameraUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSCameraUsageDescription を設定する必要があります。"; 3 | NSMicrophoneUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSMicrophoneUsageDescription を設定する必要があります。"; 4 | -------------------------------------------------------------------------------- /DecoStreamingSample/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/shiguredo/sora-ios-sdk-specs.git' 2 | source 'https://cdn.cocoapods.org/' 3 | 4 | platform :ios, '14.0' 5 | 6 | target 'DecoStreamingSample' do 7 | use_frameworks! 8 | pod 'Sora', '2025.1.2' 9 | 10 | pod 'SwiftLint' 11 | pod 'SwiftFormat/CLI' 12 | end 13 | -------------------------------------------------------------------------------- /DecoStreamingSample/README.md: -------------------------------------------------------------------------------- 1 | # DecoStreamingSample (デコ動画配信サンプル) 2 | 3 | このサンプルでは、カメラで撮影した動画をクライアントサイドで加工して動画配信するアプリを、 4 | Sora iOS SDK を用いて実装する方法を説明しています。 5 | カメラで撮影した動画を自由に加工して配信できるため、アプリの幅が広がります。 6 | 7 | ## ビルド環境 8 | 9 | サンプルアプリをビルドする際の環境については [システム条件](../README.md#システム条件) をご確認ください。 10 | 11 | ## ビルド方法 12 | 13 | 1. CocoaPods でライブラリを取得します。 14 | 15 | ``` 16 | $ pod install 17 | ``` 18 | 19 | 2. ``DecoStreamingSample/Environment.example.swift`` のファイル名を ``DecoStreamingSample/Environment.swift`` に変更し、接続情報を設定します。 20 | 21 | ``` 22 | $ cp DecoStreamingSample/Environment.example.swift DecoStreamingSample/Environment.swift 23 | ``` 24 | 25 | 3. ``DecoStreamingSample.xcworkspace`` を Xcode で開いてビルドします。 26 | 27 | ## サンプルアプリの使い方 28 | 29 | 配信を開始したあと、右上のカメラアイコンをタッチすると、自由に動画にフィルタをかけることができます。 30 | 31 | このサンプルアプリは配信専用となっています。 32 | このサンプルアプリで配信している動画を閲覧するには、 33 | 別途 RealTimeStreamingSample (生放送配信サンプルアプリ) などの視聴環境が必要です。 34 | 35 | このサンプルアプリでは、同時に複数のクライアントが同じクライアントIDに接続して配信を行う事はできません。 36 | 最初に接続した配信者が優先され、後から同じクライアントIDに接続した配信者はエラーになります。 37 | 視聴者側には特に制限がなく、無制限に配信を見る事ができます。 38 | 39 | ## 実装上の詳細について 40 | 41 | 実装上の詳細につきましてはサンプルアプリのソースコード上に詳細なコメントを用意してありますので、適時そちらをご確認ください。 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sora iOS SDK サンプル集 2 | 3 | このリポジトリには、 [Sora iOS SDK](https://github.com/shiguredo/sora-ios-sdk) を利用したサンプルアプリを掲載しています。実際の利用シーンに即したサンプルをご用意しておりますので、目的に応じた Sora iOS SDK の使い方を簡単に学ぶことができます。 4 | 5 | ## About Shiguredo's open source software 6 | 7 | We will not respond to PRs or issues that have not been discussed on Discord. Also, Discord is only available in Japanese. 8 | 9 | Please read https://github.com/shiguredo/oss before use. 10 | 11 | ## 時雨堂のオープンソースソフトウェアについて 12 | 13 | 利用前に https://github.com/shiguredo/oss をお読みください。 14 | 15 | ## システム条件 16 | 17 | このリポジトリの全てのサンプルアプリは、 [Sora iOS SDK 2022.6.0](https://github.com/shiguredo/sora-ios-sdk/releases/tag/2022.6.0) を使用しています。 18 | 19 | - iOS 15 以降 20 | - アーキテクチャ arm64 (シミュレーターの動作は未保証) 21 | - macOS 15.0 以降 22 | - Xcode 16.2 23 | - Swift 5.10 24 | - CocoaPods 1.15.2 以降 25 | - WebRTC SFU Sora 2024.2.0 以降 26 | 27 | Xcode と Swift のバージョンによっては、 CocoaPods で取得できるバイナリに互換性がない可能性があります。 28 | 29 | ## サンプルの紹介 30 | 31 | ### VideoChatSample (ビデオチャット) 32 | 33 | [VideoChatSample (ビデオチャット)](/VideoChatSample) 34 | 35 | このサンプルでは、複数人が同時に参加できるビデオチャットアプリを Sora iOS SDK を用いて実装する方法を説明しています。 36 | 37 | ### DecoStreamingSample (デコ動画配信) 38 | 39 | [DecoStreamingSample (デコ動画配信)](/DecoStreamingSample) 40 | 41 | このサンプルでは、カメラで撮影した動画をクライアントサイドで加工して動画配信するアプリを Sora iOS SDK を用いて実装する方法を説明しています。 42 | 43 | ### ScreenCastSample (スクリーンキャスト) 44 | 45 | [ScreenCastSample (スクリーンキャスト)](/ScreenCastSample) 46 | 47 | このサンプルでは、クライアントアプリの画面を動画配信するアプリを Sora iOS SDK を用いて実装する方法を説明しています。 48 | 49 | ### SimulcastSample (サイマルキャスト) 50 | 51 | [SimulcastSample (サイマルキャスト)](/SimulcastSample) 52 | 53 | このサンプルでは、Sora のサイマルキャスト機能を利用するアプリを Sora iOS SDK を用いて実装する方法を説明しています。 54 | 55 | ### SpotlightSample (スポットライト) 56 | 57 | [SpotlightSample (スポットライト)](/SpotlightSample) 58 | 59 | このサンプルでは、Sora のスポットライト機能をを利用したアプリを Sora iOS SDK を用いて実装する方法を説明しています。 60 | 61 | ### DataChannelSample (メッセージング) 62 | 63 | [DataChannelSample (メッセージング)](/DataChannelSample) 64 | 65 | このサンプルでは、Sora のメッセージング機能をを利用したアプリを Sora iOS SDK を用いて実装する方法を説明しています。 66 | 67 | ## ライセンス 68 | 69 | Apache License 2.0 70 | 71 | ``` 72 | Copyright 2017-2018, Masashi Ono (akisute) 73 | Copyright 2017-2023, Shiguredo Inc. 74 | 75 | 76 | Licensed under the Apache License, Version 2.0 (the "License"); 77 | you may not use this file except in compliance with the License. 78 | You may obtain a copy of the License at 79 | 80 | http://www.apache.org/licenses/LICENSE-2.0 81 | 82 | Unless required by applicable law or agreed to in writing, software 83 | distributed under the License is distributed on an "AS IS" BASIS, 84 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 85 | See the License for the specific language governing permissions and 86 | limitations under the License. 87 | ``` 88 | 89 | このリポジトリに含まれるすべてのアプリアイコン画像(すべての PNG 形式ファイル)のライセンスは [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.ja) です。 90 | -------------------------------------------------------------------------------- /ScreenCastSample/.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 2 | -------------------------------------------------------------------------------- /ScreenCastSample/.swiftformat: -------------------------------------------------------------------------------- 1 | --exclude Pods 2 | --indent 4 3 | --semicolons inline 4 | --trailingclosures 5 | --wrapparameters after-first 6 | --header strip 7 | 8 | --disable redundantInit,sortedSwitchCases,strongOutlets,unusedArguments,wrapSwitchCases 9 | -------------------------------------------------------------------------------- /ScreenCastSample/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - ScreenCastSample 3 | excluded: 4 | - Pods 5 | disabled_rules: 6 | - identifier_name 7 | - force_cast 8 | - force_try 9 | - cyclomatic_complexity 10 | - function_body_length 11 | - file_length 12 | - line_length 13 | - type_body_length 14 | - weak_delegate 15 | - opening_brace 16 | - closing_brace 17 | - anonymous_argument_in_multiline_closure 18 | - conditional_returns_on_newline 19 | - multiline_arguments 20 | - multiline_arguments_brackets 21 | - multiline_literal_brackets 22 | - multiline_parameters 23 | - multiline_parameters_brackets 24 | - vertical_parameter_alignment 25 | - vertical_parameter_alignment_on_call 26 | - vertical_whitespace 27 | - vertical_whitespace_between_cases 28 | - vertical_whitespace_closing_braces 29 | - vertical_whitespace_opening_braces 30 | - colon 31 | - comma 32 | - comment_spacing 33 | - trailing_comma 34 | - trailing_newline 35 | - trailing_whitespace 36 | - closure_parameter_position 37 | - closure_end_indentation 38 | - closure_spacing 39 | - for_where 40 | - large_tuple 41 | - todo 42 | -------------------------------------------------------------------------------- /ScreenCastSample/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/shiguredo/sora-ios-sdk-specs.git' 2 | source 'https://cdn.cocoapods.org/' 3 | 4 | platform :ios, '14.0' 5 | 6 | target 'ScreenCastSample' do 7 | use_frameworks! 8 | pod 'Sora', '2025.1.2' 9 | 10 | pod 'SwiftLint' 11 | pod 'SwiftFormat/CLI' 12 | end 13 | -------------------------------------------------------------------------------- /ScreenCastSample/README.md: -------------------------------------------------------------------------------- 1 | # ScreenCastSample (スクリーンキャストサンプル) 2 | 3 | このサンプルでは、クライアントアプリの画面を動画配信するアプリを、 Sora iOS SDK を用いて実装する方法を説明しています。 4 | ゲームなどの動画実況配信を行ったり、アプリの使い方を説明する配信を行ったりする事ができます。 5 | 6 | ## ビルド環境 7 | 8 | サンプルアプリをビルドする際の環境については [システム条件](../README.md#システム条件) をご確認ください。 9 | 10 | ## ビルド方法 11 | 12 | 1. CocoaPods でライブラリを取得します。 13 | 14 | ``` 15 | $ pod install 16 | ``` 17 | 18 | 2. ``ScreenCastSample/Environment.example.swift`` のファイル名を ``ScreenCastSample/Environment.swift`` に変更し、接続情報を設定します。 19 | 20 | ``` 21 | $ cp ScreenCastSample/Environment.example.swift ScreenCastSample/Environment.swift 22 | ``` 23 | 24 | 3. ``ScreenCastSample.xcworkspace`` を Xcode で開いてビルドします。 25 | 26 | ## サンプルアプリの使い方 27 | 28 | アプリを起動するとサンプルのゲームが起動します。 29 | このゲームは画面をタッチすると物理演算された箱が次々と現れて積み上がっていくゲームになっています。 30 | 31 | アプリを起動してから、右上のカメラアイコンをタッチすると、現在のアプリの画面を配信することができます。 32 | 33 | このサンプルアプリは配信専用となっています。 34 | このサンプルアプリで配信している動画を閲覧するには、 35 | 別途 RealTimeStreamingSample (生放送配信サンプルアプリ) などの視聴環境が必要です。 36 | 37 | このサンプルアプリでは、同時に複数のクライアントが同じクライアントIDに接続して配信を行う事はできません。 38 | 最初に接続した配信者が優先され、後から同じクライアントIDに接続した配信者はエラーになります。 39 | 視聴者側には特に制限がなく、無制限に配信を見る事ができます。 40 | 41 | 現在、サンプルアプリで使用している ReplayKit の実装都合上、 42 | H.264 形式のビデオフォーマットを使用すると配信が途中で止まる不具合があります。 43 | 申し訳ありませんが、 ReplayKit と同時に使用する場合は、 VP9 などの他のビデオフォーマットでご利用ください。 44 | 45 | ## 実装上の詳細について 46 | 47 | 実装上の詳細につきましてはサンプルアプリのソースコード上に詳細なコメントを用意してありますので、適時そちらをご確認ください。 48 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | var window: UIWindow? 6 | 7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 8 | true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Classes/PublisherConfigViewController.swift: -------------------------------------------------------------------------------- 1 | import Sora 2 | import UIKit 3 | 4 | /** 5 | 配信設定画面です。 6 | */ 7 | class PublisherConfigViewController: UITableViewController { 8 | /// チャンネルIDを入力させる欄です。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 9 | @IBOutlet var channelIdTextField: UITextField! 10 | /// 動画のコーデックを指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 11 | @IBOutlet var videoCodecSegmentedControl: UISegmentedControl! 12 | 13 | /// 接続試行中かどうかを表します。 14 | var isConnecting = false 15 | 16 | /** 17 | 画面起動時の処理を記述します。 18 | */ 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | channelIdTextField.text = Environment.channelId 23 | } 24 | 25 | /** 26 | 行がタップされたときの処理を記述します。 27 | */ 28 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 29 | // まず最初にタップされた行の選択状態を解除します。 30 | tableView.deselectRow(at: indexPath, animated: true) 31 | 32 | // 選択された行が「接続」ボタンでない限り無視します。 33 | guard indexPath.section == 2, indexPath.row == 0 else { 34 | return 35 | } 36 | 37 | // チャンネルIDが入力されていない限り無視します。 38 | guard let channelId = channelIdTextField.text, !channelId.isEmpty else { 39 | return 40 | } 41 | 42 | // 接続試行中なら無視します。 43 | if isConnecting { 44 | return 45 | } 46 | isConnecting = true 47 | 48 | // ユーザーが選択した設定をUIコントロールから取得します。 49 | let videoCodec: VideoCodec 50 | switch videoCodecSegmentedControl.selectedSegmentIndex { 51 | case 0: videoCodec = .default 52 | case 1: videoCodec = .vp8 53 | case 2: videoCodec = .vp9 54 | case 3: videoCodec = .av1 55 | case 4: videoCodec = .h264 56 | case 5: videoCodec = .h265 57 | default: fatalError() 58 | } 59 | 60 | // 入力された設定を元にSoraへ接続を行います。 61 | // この画面からは配信側に接続を行うため、role引数には .publisher を指定しています。 62 | // また今回のサンプルアプリでは、デフォルトのカメラ映像のキャプチャではなく、ReplayKit経由で取得したスクリーンキャストを使用したいため、 63 | // videoCapturerOptionをカスタムに設定しておきます。 64 | SoraSDKManager.shared.connect( 65 | channelId: channelId, 66 | role: .sendonly, 67 | multistreamEnabled: true, 68 | videoCodec: videoCodec 69 | ) { [weak self] error in 70 | // 接続処理が終了したので false にします。 71 | self?.isConnecting = false 72 | 73 | if let error { 74 | // errorがnilでないばあいは、接続に失敗しています。 75 | // この場合は、エラー表示をユーザーに返すのが親切です。 76 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 77 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 78 | NSLog("[sample] SoraSDKManager connection error: \(error)") 79 | DispatchQueue.main.async { 80 | let alertController = UIAlertController(title: "接続に失敗しました", 81 | message: error.localizedDescription, 82 | preferredStyle: .alert) 83 | alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 84 | self?.present(alertController, animated: true, completion: nil) 85 | } 86 | } else { 87 | // errorがnilの場合は、接続に成功しています。 88 | NSLog("[sample] SoraSDKManager connected.") 89 | 90 | // 接続が完了したので、ゲーム画面に戻ります。 91 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 92 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 93 | DispatchQueue.main.async { 94 | // ConnectセグエはMain.storyboard内で定義されているので、そちらをご確認ください。 95 | self?.performSegue(withIdentifier: "Connect", sender: self) 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Classes/ScreenRecorder.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import QuartzCore 3 | import Sora 4 | import UIKit 5 | import WebRTC 6 | 7 | class ScreenRecorder { 8 | let frameRendererQueue = DispatchQueue(label: "ScreenCastSample.ScreenRenderer.frameRendererQueue", qos: .userInteractive) 9 | var frameRendererSemaphore = DispatchSemaphore(value: 1) 10 | var displayLink: CADisplayLink? 11 | var outputBufferPool: CVPixelBufferPool? 12 | var outputColorSpace: CGColorSpace? 13 | var inputViewSize: CGSize = .zero 14 | var inputScale: CGFloat = 1.0 15 | 16 | var firstTimestamp: CFTimeInterval = 0 17 | 18 | var captureHandler: ((VideoFrame) -> Void)? 19 | 20 | func startCapture(handler: ((VideoFrame) -> Void)?) { 21 | guard displayLink == nil else { 22 | return 23 | } 24 | captureHandler = handler 25 | setupOutputBuffer() 26 | displayLink = CADisplayLink(target: self, selector: #selector(onDisplayLink(_:))) 27 | displayLink?.preferredFramesPerSecond = 15 // 15FPSで描画するように指定しています。 28 | displayLink?.add(to: RunLoop.main, forMode: RunLoop.Mode.common) 29 | } 30 | 31 | func stopCapture() { 32 | if let displayLink { 33 | displayLink.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) 34 | } 35 | displayLink = nil 36 | firstTimestamp = 0 37 | cleanupOutputBuffer() 38 | } 39 | 40 | @objc 41 | private func onDisplayLink(_ displayLink: CADisplayLink) { 42 | // displayLink側に指定したFPSの時間内にフレームのレンダリングが終わっていない場合はこのセマフォでブロックして、フレームの描画をスキップします。 43 | // このスキップを含めないとどんどんフレームのレンダリングが滞留して遅延していき、最終的には固まってしまいます。 44 | // またここから先の実行は専用のQueue上で実行させ、少しでもパフォーマンスを稼ぎます。 45 | guard frameRendererSemaphore.wait(timeout: DispatchTime.now()) == .success else { 46 | return 47 | } 48 | frameRendererQueue.async { 49 | defer { 50 | self.frameRendererSemaphore.signal() 51 | } 52 | 53 | // レンダリングに使うピクセルバッファと、ビットマップコンテキストの準備をします。 54 | guard let pixelBufferPool = self.outputBufferPool, 55 | let colorSpace = self.outputColorSpace 56 | else { 57 | return 58 | } 59 | 60 | var pb: CVPixelBuffer? 61 | CVPixelBufferPoolCreatePixelBuffer(nil, pixelBufferPool, &pb) 62 | guard let pixelBuffer = pb else { 63 | return 64 | } 65 | CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) 66 | defer { 67 | CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) 68 | } 69 | 70 | guard let bitmapContext = CGContext.init(data: CVPixelBufferGetBaseAddress(pixelBuffer), 71 | width: CVPixelBufferGetWidth(pixelBuffer), 72 | height: CVPixelBufferGetHeight(pixelBuffer), 73 | bitsPerComponent: 8, 74 | bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), 75 | space: colorSpace, 76 | bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) 77 | else { 78 | return 79 | } 80 | bitmapContext.scaleBy(x: self.inputScale, y: self.inputScale) 81 | bitmapContext.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: self.inputViewSize.height)) // 縦方向反転用のTransformを適用する。CGContextの座標系は左下基準なので、上下反転させる必要がある。 82 | 83 | // 現在のフレーム時間を計算します。 84 | if !(self.firstTimestamp > 0) { 85 | self.firstTimestamp = displayLink.timestamp 86 | } 87 | let elapsed = displayLink.timestamp - self.firstTimestamp 88 | let time = CMTimeMakeWithSeconds(elapsed, preferredTimescale: 1000) 89 | 90 | // UIWindowヒエラルキーをメインスレッド上でレンダリングさせ、その結果を待ちます。 91 | DispatchQueue.main.sync { 92 | UIGraphicsPushContext(bitmapContext) 93 | defer { 94 | UIGraphicsPopContext() 95 | } 96 | for window in UIApplication.shared.windows { 97 | window.drawHierarchy(in: CGRect(origin: .zero, size: self.inputViewSize), afterScreenUpdates: false) 98 | } 99 | } 100 | 101 | // レンダリング完了したので、コールバックにVideoFrameを返します。 102 | self.captureHandler?(self.createViewFrame(pixelBuffer: pixelBuffer, time: time)) 103 | } 104 | } 105 | 106 | private func setupOutputBuffer() { 107 | guard outputBufferPool == nil else { 108 | return 109 | } 110 | guard let mainWindow = UIApplication.shared.keyWindow else { 111 | return 112 | } 113 | inputViewSize = mainWindow.bounds.size 114 | inputScale = 1.0 // UIScreen.main.scale のほうが良いのだが、3x Retinaのような巨大な画面で実行するとあまりにも遅すぎるので、一律1.0倍にしてます。 115 | 116 | let attributes = [ 117 | kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA, 118 | kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue as Any, 119 | kCVPixelBufferWidthKey: (inputViewSize.width * inputScale) as CFNumber, 120 | kCVPixelBufferHeightKey: (inputViewSize.height * inputScale) as CFNumber, 121 | kCVPixelBufferBytesPerRowAlignmentKey: (inputViewSize.width * inputScale * 4) as CFNumber, 122 | ] as CFDictionary 123 | CVPixelBufferPoolCreate(nil, nil, attributes, &outputBufferPool) 124 | 125 | outputColorSpace = CGColorSpaceCreateDeviceRGB() 126 | } 127 | 128 | private func cleanupOutputBuffer() { 129 | outputBufferPool = nil 130 | outputColorSpace = nil 131 | inputViewSize = .zero 132 | inputScale = 1.0 133 | } 134 | 135 | private func createViewFrame(pixelBuffer: CVPixelBuffer, time: CMTime) -> VideoFrame { 136 | let timeStamp = CMTimeGetSeconds(time) 137 | let timeStampNs = Int64(timeStamp * 1_000_000_000) 138 | let frame = RTCVideoFrame(buffer: RTCCVPixelBuffer(pixelBuffer: pixelBuffer), 139 | rotation: RTCVideoRotation._0, 140 | timeStampNs: timeStampNs) 141 | return VideoFrame.native(capturer: nil, frame: frame) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Classes/SoraSDKManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Sora 3 | 4 | /** 5 | Sora SDK関連の、アプリケーション全体で共通して行いたい処理を行うシングルトン・マネージャ・クラスです。 6 | 7 | このようなクラスを用意しておくと、Sora SDKのConnectionをアプリケーション全体で一つだけ確実に管理する事が可能になるため、おすすめです。 8 | */ 9 | class SoraSDKManager { 10 | /** 11 | SoraSDKManagerのシングルトンインスタンスです。 12 | */ 13 | static let shared = SoraSDKManager() 14 | 15 | /** 16 | 現在接続中のSora SDKのMediaChannelです。 17 | 18 | 殆どの場合、アプリケーション全体で一つだけ同時にMediaChannelに接続することになるので、シングルトンとして用意すると便利に使えます。 19 | */ 20 | private(set) var currentMediaChannel: MediaChannel? 21 | 22 | /** 23 | シングルトンにしたいので、イニシャライザはprivateにしてあります。 24 | */ 25 | private init() { 26 | // SDK のログを表示します。 27 | // 送受信されるシグナリングの内容や接続エラーを確認できます。 28 | Logger.shared.level = .debug 29 | Sora.setWebRTCLogLevel(.info) 30 | } 31 | 32 | /** 33 | 新たにSoraに接続を試みます。接続に成功した場合、currentMediaChannelが更新されます。 34 | 35 | 既に接続されており、currentMediaChannelが設定されている場合は新たに接続ができないようにしてあります。 36 | その場合は、一旦先に `disconnect()` を呼び出して、現在の接続を終了してください。 37 | */ 38 | func connect(channelId: String, 39 | role: Role, 40 | multistreamEnabled: Bool, 41 | videoCodec: VideoCodec = .default, 42 | completionHandler: ((Error?) -> Void)?) 43 | { 44 | // 既にcurrentMediaChannelが設定されている場合は、接続済みとみなし、何もしないで終了します。 45 | guard currentMediaChannel == nil else { 46 | return 47 | } 48 | 49 | // Configurationを生成して、接続設定を行います。 50 | // 必須となる設定はurl, channelId, role, multistreamEnabledのみです。 51 | // その他の設定にはデフォルト値が指定されていますが、ここで必要に応じて自由に調整することが可能です。 52 | var configuration = Configuration(urlCandidates: Environment.urls, 53 | channelId: channelId, 54 | role: role, 55 | multistreamEnabled: multistreamEnabled) 56 | 57 | // 引数で指定された値を設定します。 58 | configuration.videoCodec = videoCodec 59 | configuration.cameraSettings.isEnabled = false 60 | configuration.signalingConnectMetadata = Environment.signalingConnectMetadata 61 | 62 | // Soraに接続を試みます。 63 | _ = Sora.shared.connect(configuration: configuration) { [weak self] mediaChannel, error in 64 | // 接続に成功した場合は、mediaChannelに値が返され、errorがnilになります。 65 | // 一方、接続に失敗した場合は、mediaChannelはnilとなり、errorが返されます。 66 | self?.currentMediaChannel = mediaChannel 67 | completionHandler?(error) 68 | NSLog("[sample] mediaChannel.connectedUrl: \(String(describing: mediaChannel?.connectedUrl))") 69 | } 70 | } 71 | 72 | /** 73 | 既に接続済みのmediaChannelから切断します。 74 | 75 | currentMediaChannelがnilで、まだ接続されていないときは、何もしないで終了します。 76 | */ 77 | func disconnect() { 78 | guard let mediaChannel = currentMediaChannel else { 79 | return 80 | } 81 | mediaChannel.disconnect(error: nil) 82 | currentMediaChannel = nil 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Environment.example.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Environment { 4 | // 接続するサーバーのシグナリング URL 5 | // 配列で複数の URL を指定することが可能です 6 | static let urls = [URL(string: "wss://sora.example.com/signaling")!] 7 | 8 | // チャネル ID 9 | static let channelId = "sora" 10 | 11 | // type: connect に含めるメタデータ 12 | static let signalingConnectMetadata: Encodable? = nil 13 | } 14 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 2.0.0 19 | CFBundleVersion 20 | 200.0.0 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | NSCameraUsageDescription 28 | NSCameraUsageDescription 29 | NSMicrophoneUsageDescription 30 | NSMicrophoneUsageDescription 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ScreenCastSample/ScreenCastSample/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | CFBundleDisplayName = "スクリーンキャスト"; 2 | NSCameraUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSCameraUsageDescription を設定する必要があります。"; 3 | NSMicrophoneUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSMicrophoneUsageDescription を設定する必要があります。"; 4 | -------------------------------------------------------------------------------- /SimulcastSample/.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 2 | -------------------------------------------------------------------------------- /SimulcastSample/.swiftformat: -------------------------------------------------------------------------------- 1 | --exclude Pods 2 | --indent 4 3 | --semicolons inline 4 | --trailingclosures 5 | --wrapparameters after-first 6 | --header strip 7 | 8 | --disable redundantInit,sortedSwitchCases,strongOutlets,unusedArguments,wrapSwitchCases 9 | -------------------------------------------------------------------------------- /SimulcastSample/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - SimulcastSample 3 | excluded: 4 | - Pods 5 | disabled_rules: 6 | - identifier_name 7 | - force_cast 8 | - force_try 9 | - cyclomatic_complexity 10 | - function_body_length 11 | - file_length 12 | - line_length 13 | - type_body_length 14 | - weak_delegate 15 | - opening_brace 16 | - closing_brace 17 | - anonymous_argument_in_multiline_closure 18 | - conditional_returns_on_newline 19 | - multiline_arguments 20 | - multiline_arguments_brackets 21 | - multiline_literal_brackets 22 | - multiline_parameters 23 | - multiline_parameters_brackets 24 | - vertical_parameter_alignment 25 | - vertical_parameter_alignment_on_call 26 | - vertical_whitespace 27 | - vertical_whitespace_between_cases 28 | - vertical_whitespace_closing_braces 29 | - vertical_whitespace_opening_braces 30 | - colon 31 | - comma 32 | - comment_spacing 33 | - trailing_comma 34 | - trailing_newline 35 | - trailing_whitespace 36 | - closure_parameter_position 37 | - closure_end_indentation 38 | - closure_spacing 39 | - for_where 40 | - large_tuple 41 | - todo 42 | -------------------------------------------------------------------------------- /SimulcastSample/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/shiguredo/sora-ios-sdk-specs.git' 2 | source 'https://cdn.cocoapods.org/' 3 | 4 | platform :ios, '14.0' 5 | 6 | target 'SimulcastSample' do 7 | use_frameworks! 8 | pod 'Sora', '2025.1.2' 9 | 10 | pod 'SwiftLint' 11 | pod 'SwiftFormat/CLI' 12 | end 13 | -------------------------------------------------------------------------------- /SimulcastSample/README.md: -------------------------------------------------------------------------------- 1 | # SimulcastSample (サイマルキャスト) 2 | 3 | このサンプルでは、サイマルキャスト機能を使用する方法を説明しています。 VideoChatSample のコードをベースにしています。 4 | 5 | ## ビルド環境 6 | 7 | サンプルアプリをビルドする際の環境については [システム条件](../README.md#システム条件) をご確認ください。 8 | 9 | ## ビルド方法 10 | 11 | 1. CocoaPods でライブラリを取得します。 12 | 13 | ``` 14 | $ pod install 15 | ``` 16 | 17 | 2. ``SimulcastSample/Environment.example.swift`` のファイル名を ``SimulcastSample/Environment.swift`` に変更し、接続情報を設定します。 18 | 19 | ``` 20 | $ cp SimulcastSample/Environment.example.swift SimulcastSample/Environment.swift 21 | ``` 22 | 23 | 3. ``SimulcastSample.xcworkspace`` を Xcode で開いてビルドします。 24 | 25 | ## サンプルアプリの使い方 26 | 27 | このサンプルアプリでは、同じクライアントIDに対して最大で12人までが接続し、同時にビデオチャットに参加できます。 28 | それ以上の人数が同時に接続する場合の挙動については保証されません。 29 | 実際に配信されていることを確認したい場合には、複数台のデバイスにこのサンプルアプリをインストールするか、 30 | または他の Sora クライアントを用いて同時に接続する必要があります。 31 | 32 | 三人以上が同じクライアントIDに対して接続されている場合、画面が複数人で分割されます。 33 | 34 | 自分自身の配信している動画はポップアップで表示されます。 35 | 36 | ## 実装上の詳細について 37 | 38 | 実装上の詳細につきましてはサンプルアプリのソースコード上に詳細なコメントを用意してありますので、適時そちらをご確認ください。 39 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 6 | // Override point for customization after application launch. 7 | true 8 | } 9 | 10 | // MARK: UISceneSession Lifecycle 11 | 12 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 13 | // Called when a new scene session is being created. 14 | // Use this method to select a configuration to create the new scene with. 15 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 16 | } 17 | 18 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 19 | // Called when the user discards a scene session. 20 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 21 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Classes/ConfigViewController.swift: -------------------------------------------------------------------------------- 1 | import Sora 2 | import UIKit 3 | 4 | /** 5 | チャット接続設定画面です。 6 | */ 7 | class ConfigViewController: UITableViewController { 8 | /// チャンネルIDを入力させる欄です。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 9 | @IBOutlet var channelIdTextField: UITextField! 10 | 11 | /// 動画のコーデックを指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 12 | @IBOutlet var videoCodecSegmentedControl: UISegmentedControl! 13 | 14 | /// 配信開始時のサイマルキャスト rid を指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 15 | @IBOutlet var simulcastRidSegmentedControl: UISegmentedControl! 16 | 17 | /// 接続試行中かどうかを表します。 18 | var isConnecting = false 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | channelIdTextField.text = Environment.channelId 24 | } 25 | 26 | /// データチャンネルシグナリング機能を有効にするためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 27 | @IBOutlet var dataChannelSignalingSegmentedControl: UISegmentedControl! 28 | /// データチャンネルシグナリング機能を有効時に WebSoket 切断を許容するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 29 | 30 | @IBOutlet var ignoreDisconnectWebSocketSegmentedControl: UISegmentedControl! 31 | 32 | /** 33 | 行がタップされたときの処理を記述します。 34 | */ 35 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 36 | // まず最初にタップされた行の選択状態を解除します。 37 | tableView.deselectRow(at: indexPath, animated: true) 38 | 39 | // 選択された行が「接続」ボタンでない限り無視します。 40 | guard indexPath.section == 4, indexPath.row == 0 else { 41 | return 42 | } 43 | 44 | // チャンネルIDが入力されていない限り無視します。 45 | guard let channelId = channelIdTextField.text, !channelId.isEmpty else { 46 | return 47 | } 48 | 49 | // 接続試行中なら無視します。 50 | if isConnecting { 51 | return 52 | } 53 | isConnecting = true 54 | 55 | // ユーザーが選択した設定をUIコントロールから取得します。 56 | let videoCodec: VideoCodec 57 | switch videoCodecSegmentedControl.selectedSegmentIndex { 58 | case 0: videoCodec = .default 59 | case 1: videoCodec = .vp8 60 | case 2: videoCodec = .vp9 61 | case 3: videoCodec = .av1 62 | case 4: videoCodec = .h264 63 | case 5: videoCodec = .h265 64 | default: fatalError() 65 | } 66 | 67 | let simulcastRid: SimulcastRid? 68 | switch simulcastRidSegmentedControl.selectedSegmentIndex { 69 | case 0: simulcastRid = nil 70 | case 1: simulcastRid = .r0 71 | case 2: simulcastRid = .r1 72 | case 3: simulcastRid = .r2 73 | default: fatalError() 74 | } 75 | 76 | let dataChannelSignaling: Bool? 77 | switch dataChannelSignalingSegmentedControl.selectedSegmentIndex { 78 | case 0: dataChannelSignaling = nil 79 | case 1: dataChannelSignaling = false 80 | case 2: dataChannelSignaling = true 81 | default: fatalError() 82 | } 83 | 84 | let ignoreDisconnectWebSocket: Bool? 85 | switch ignoreDisconnectWebSocketSegmentedControl.selectedSegmentIndex { 86 | case 0: ignoreDisconnectWebSocket = nil 87 | case 1: ignoreDisconnectWebSocket = false 88 | case 2: ignoreDisconnectWebSocket = true 89 | default: fatalError() 90 | } 91 | 92 | // 入力された設定を元にSoraへ接続を行います。 93 | // ビデオチャットアプリでは複数のユーザーが同時に配信を行う必要があるため、 94 | // role 引数には .sendrecv を指定し、マルチストリームを有効にします。 95 | SoraSDKManager.shared.connect( 96 | channelId: channelId, 97 | videoCodec: videoCodec, 98 | simulcastRid: simulcastRid, 99 | dataChannelSignaling: dataChannelSignaling, 100 | ignoreDisconnectWebSocket: ignoreDisconnectWebSocket 101 | ) { [weak self] error in 102 | // 接続処理が終了したので false にします。 103 | self?.isConnecting = false 104 | 105 | if let error { 106 | // errorがnilでないばあいは、接続に失敗しています。 107 | // この場合は、エラー表示をユーザーに返すのが親切です。 108 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 109 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 110 | NSLog("[sample] SoraSDKManager connection error: \(error)") 111 | DispatchQueue.main.async { 112 | let alertController = UIAlertController(title: "接続に失敗しました", 113 | message: error.localizedDescription, 114 | preferredStyle: .alert) 115 | alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 116 | self?.present(alertController, animated: true, completion: nil) 117 | } 118 | } else { 119 | // errorがnilの場合は、接続に成功しています。 120 | NSLog("[sample] SoraSDKManager connected.") 121 | 122 | // 次の配信画面に遷移します。 123 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 124 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 125 | DispatchQueue.main.async { 126 | // ConnectセグエはMain.storyboard内で定義されているので、そちらをご確認ください。 127 | self?.performSegue(withIdentifier: "Connect", sender: self) 128 | } 129 | } 130 | } 131 | } 132 | 133 | /** 134 | 配信画面からのUnwind Segueの着地地点として定義してあります。 135 | 詳細はMain.storyboardの設定をご確認ください。 136 | */ 137 | @IBAction func onUnwindToConfig(_ segue: UIStoryboardSegue) { 138 | // 前の画面から戻ってきても、特に処理は何も行いません。 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Classes/SoraSDKManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Sora 3 | 4 | /** 5 | Sora SDK関連の、アプリケーション全体で共通して行いたい処理を行うシングルトン・マネージャ・クラスです。 6 | 7 | このようなクラスを用意しておくと、Sora SDKのConnectionをアプリケーション全体で一つだけ確実に管理する事が可能になるため、おすすめです。 8 | */ 9 | class SoraSDKManager { 10 | /** 11 | SoraSDKManagerのシングルトンインスタンスです。 12 | */ 13 | static let shared = SoraSDKManager() 14 | 15 | /** 16 | 現在接続中のSora SDKのMediaChannelです。 17 | 18 | 殆どの場合、アプリケーション全体で一つだけ同時にMediaChannelに接続することになるので、シングルトンとして用意すると便利に使えます。 19 | */ 20 | private(set) var currentMediaChannel: MediaChannel? 21 | 22 | /** 23 | シングルトンにしたいので、イニシャライザはprivateにしてあります。 24 | */ 25 | private init() { 26 | // SDK のログを表示します。 27 | // 送受信されるシグナリングの内容や接続エラーを確認できます。 28 | Logger.shared.level = .debug 29 | Sora.setWebRTCLogLevel(.info) 30 | } 31 | 32 | /** 33 | 新たにSoraに接続を試みます。接続に成功した場合、currentMediaChannelが更新されます。 34 | 35 | 既に接続されており、currentMediaChannelが設定されている場合は新たに接続ができないようにしてあります。 36 | その場合は、一旦先に `disconnect()` を呼び出して、現在の接続を終了してください。 37 | */ 38 | func connect(channelId: String, 39 | videoCodec: VideoCodec, 40 | simulcastRid: SimulcastRid?, 41 | dataChannelSignaling: Bool? = nil, 42 | ignoreDisconnectWebSocket: Bool? = nil, 43 | completionHandler: ((Error?) -> Void)?) 44 | { 45 | // 既にcurrentMediaChannelが設定されている場合は、接続済みとみなし、何もしないで終了します。 46 | guard currentMediaChannel == nil else { 47 | return 48 | } 49 | 50 | // Configurationを生成して、接続設定を行います。 51 | // 必須となる設定はurl, channelId, roleのみです。 52 | // その他の設定にはデフォルト値が指定されていますが、ここで必要に応じて自由に調整することが可能です。 53 | var configuration = Configuration(urlCandidates: Environment.urls, channelId: channelId, role: .sendrecv, 54 | multistreamEnabled: true) 55 | // 引数で指定された値を設定します。 56 | configuration.videoCodec = videoCodec 57 | configuration.simulcastRid = simulcastRid 58 | configuration.dataChannelSignaling = dataChannelSignaling 59 | configuration.ignoreDisconnectWebSocket = ignoreDisconnectWebSocket 60 | configuration.signalingConnectMetadata = Environment.signalingConnectMetadata 61 | 62 | // bundle_id を指定します。 63 | configuration.bundleId = UUID().uuidString 64 | 65 | // サイマルキャストを有効にします。 66 | configuration.simulcastEnabled = true 67 | 68 | // サイマルキャスト用にビットレートを高めに設定します。 69 | configuration.videoBitRate = 3000 70 | 71 | // Soraに接続を試みます。 72 | _ = Sora.shared.connect(configuration: configuration) { [weak self] mediaChannel, error in 73 | // 接続に成功した場合は、mediaChannelに値が返され、errorがnilになります。 74 | // 一方、接続に失敗した場合は、mediaChannelはnilとなり、errorが返されます。 75 | self?.currentMediaChannel = mediaChannel 76 | completionHandler?(error) 77 | NSLog("[sample] mediaChannel.connectedUrl: \(String(describing: mediaChannel?.connectedUrl))") 78 | } 79 | } 80 | 81 | /** 82 | 既に接続済みのmediaChannelから切断します。 83 | 84 | currentMediaChannelがnilで、まだ接続されていないときは、何もしないで終了します。 85 | */ 86 | func disconnect() { 87 | guard let mediaChannel = currentMediaChannel else { 88 | return 89 | } 90 | mediaChannel.disconnect(error: nil) 91 | currentMediaChannel = nil 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Environment.example.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Environment { 4 | // 接続するサーバーのシグナリング URL 5 | // 配列で複数の URL を指定することが可能です 6 | static let urls = [URL(string: "wss://sora.example.com/signaling")!] 7 | 8 | // チャネル ID 9 | static let channelId = "sora" 10 | 11 | // type: connect に含めるメタデータ 12 | static let signalingConnectMetadata: Encodable? = nil 13 | } 14 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | NSAppTransportSecurity 66 | 67 | NSAllowsArbitraryLoads 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | CFBundleDisplayName = "サイマルキャスト"; 2 | NSCameraUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSCameraUsageDescription を設定する必要があります。"; 3 | NSMicrophoneUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSMicrophoneUsageDescription を設定する必要があります。"; 4 | -------------------------------------------------------------------------------- /SimulcastSample/SimulcastSample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 4 | var window: UIWindow? 5 | 6 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 7 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 8 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 9 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 10 | guard let _ = (scene as? UIWindowScene) else { return } 11 | } 12 | 13 | func sceneDidDisconnect(_ scene: UIScene) { 14 | // Called as the scene is being released by the system. 15 | // This occurs shortly after the scene enters the background, or when its session is discarded. 16 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 17 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 18 | } 19 | 20 | func sceneDidBecomeActive(_ scene: UIScene) { 21 | // Called when the scene has moved from an inactive state to an active state. 22 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 23 | } 24 | 25 | func sceneWillResignActive(_ scene: UIScene) { 26 | // Called when the scene will move from an active state to an inactive state. 27 | // This may occur due to temporary interruptions (ex. an incoming phone call). 28 | } 29 | 30 | func sceneWillEnterForeground(_ scene: UIScene) { 31 | // Called as the scene transitions from the background to the foreground. 32 | // Use this method to undo the changes made on entering the background. 33 | } 34 | 35 | func sceneDidEnterBackground(_ scene: UIScene) { 36 | // Called as the scene transitions from the foreground to the background. 37 | // Use this method to save data, release shared resources, and store enough scene-specific state information 38 | // to restore the scene back to its current state. 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SpotlightSample/.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 2 | -------------------------------------------------------------------------------- /SpotlightSample/.swiftformat: -------------------------------------------------------------------------------- 1 | --exclude Pods 2 | --indent 4 3 | --semicolons inline 4 | --trailingclosures 5 | --wrapparameters after-first 6 | --header strip 7 | 8 | --disable redundantInit,sortedSwitchCases,strongOutlets,unusedArguments,wrapSwitchCases 9 | -------------------------------------------------------------------------------- /SpotlightSample/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - SpotlightSample 3 | excluded: 4 | - Pods 5 | disabled_rules: 6 | - identifier_name 7 | - force_cast 8 | - force_try 9 | - cyclomatic_complexity 10 | - function_body_length 11 | - file_length 12 | - line_length 13 | - type_body_length 14 | - weak_delegate 15 | - opening_brace 16 | - closing_brace 17 | - anonymous_argument_in_multiline_closure 18 | - conditional_returns_on_newline 19 | - multiline_arguments 20 | - multiline_arguments_brackets 21 | - multiline_literal_brackets 22 | - multiline_parameters 23 | - multiline_parameters_brackets 24 | - vertical_parameter_alignment 25 | - vertical_parameter_alignment_on_call 26 | - vertical_whitespace 27 | - vertical_whitespace_between_cases 28 | - vertical_whitespace_closing_braces 29 | - vertical_whitespace_opening_braces 30 | - colon 31 | - comma 32 | - comment_spacing 33 | - trailing_comma 34 | - trailing_newline 35 | - trailing_whitespace 36 | - closure_parameter_position 37 | - closure_end_indentation 38 | - closure_spacing 39 | - for_where 40 | - large_tuple 41 | - todo 42 | -------------------------------------------------------------------------------- /SpotlightSample/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/shiguredo/sora-ios-sdk-specs.git' 2 | source 'https://cdn.cocoapods.org/' 3 | 4 | platform :ios, '14.0' 5 | 6 | target 'SpotlightSample' do 7 | use_frameworks! 8 | pod 'Sora', '2025.1.2' 9 | 10 | pod 'SwiftLint' 11 | pod 'SwiftFormat/CLI' 12 | end 13 | -------------------------------------------------------------------------------- /SpotlightSample/README.md: -------------------------------------------------------------------------------- 1 | # SpotlightSample (スポットライト) 2 | 3 | このサンプルでは、スポットライト機能を使用する方法を説明しています。 VideoChatSample と SimulcastSample のコードをベースにしています。 4 | 5 | ## ビルド環境 6 | 7 | サンプルアプリをビルドする際の環境については [システム条件](../README.md#システム条件) をご確認ください。 8 | 9 | ## ビルド方法 10 | 11 | 1. CocoaPods でライブラリを取得します。 12 | 13 | ``` 14 | $ pod install 15 | ``` 16 | 17 | 2. ``SpotlightSample/Environment.example.swift`` のファイル名を ``SpotlightSample/Environment.swift`` に変更し、接続情報を設定します。 18 | 19 | ``` 20 | $ cp SpotlightSample/Environment.example.swift SpotlightSample/Environment.swift 21 | ``` 22 | 23 | 3. ``SpotlightSample.xcworkspace`` を Xcode で開いてビルドします。 24 | 25 | ## サンプルアプリの使い方 26 | 27 | このサンプルアプリでは、同じクライアントIDに対して最大で12人までが接続し、同時にビデオチャットに参加できます。 28 | それ以上の人数が同時に接続する場合の挙動については保証されません。 29 | 実際に配信されていることを確認したい場合には、複数台のデバイスにこのサンプルアプリをインストールするか、 30 | または他の Sora クライアントを用いて同時に接続する必要があります。 31 | 32 | 三人以上が同じクライアントIDに対して接続されている場合、画面が複数人で分割されます。 33 | 34 | 自分自身の配信している動画はポップアップで表示されます。 35 | 36 | ## 実装上の詳細について 37 | 38 | 実装上の詳細につきましてはサンプルアプリのソースコード上に詳細なコメントを用意してありますので、適時そちらをご確認ください。 39 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 6 | // Override point for customization after application launch. 7 | true 8 | } 9 | 10 | // MARK: UISceneSession Lifecycle 11 | 12 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 13 | // Called when a new scene session is being created. 14 | // Use this method to select a configuration to create the new scene with. 15 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 16 | } 17 | 18 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 19 | // Called when the user discards a scene session. 20 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 21 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Classes/ConfigViewController.swift: -------------------------------------------------------------------------------- 1 | import Sora 2 | import UIKit 3 | 4 | /** 5 | チャット接続設定画面です。 6 | */ 7 | class ConfigViewController: UITableViewController { 8 | /// チャンネルIDを入力させる欄です。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 9 | @IBOutlet var channelIdTextField: UITextField! 10 | 11 | /// 動画のコーデックを指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 12 | @IBOutlet var videoCodecSegmentedControl: UISegmentedControl! 13 | 14 | /// アクティブ配信数を指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 15 | @IBOutlet var spotlightNumberSegmentedControl: UISegmentedControl! 16 | 17 | /// フォーカスされた映像の Rid を指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 18 | @IBOutlet var spotlightFocusRidSegmentedControl: UISegmentedControl! 19 | 20 | /// フォーカスされた映像の Rid を指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 21 | @IBOutlet var spotlightUnfocusRidSegmentedControl: UISegmentedControl! 22 | 23 | /// 接続試行中かどうかを表します。 24 | var isConnecting = false 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | channelIdTextField.text = Environment.channelId 30 | } 31 | 32 | /// サイマルキャスト 33 | @IBOutlet var simulcastSegmentedControl: UISegmentedControl! 34 | 35 | /// データチャンネルシグナリング機能を有効にするためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 36 | @IBOutlet var dataChannelSignalingSegmentedControl: UISegmentedControl! 37 | 38 | /// データチャンネルシグナリング機能を有効時に WebSoket 切断を許容するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 39 | @IBOutlet var ignoreDisconnectWebSocketSegmentedControl: UISegmentedControl! 40 | 41 | /** 42 | 行がタップされたときの処理を記述します。 43 | */ 44 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 45 | // まず最初にタップされた行の選択状態を解除します。 46 | tableView.deselectRow(at: indexPath, animated: true) 47 | 48 | // 選択された行が「接続」ボタンでない限り無視します。 49 | guard indexPath.section == 6, indexPath.row == 0 else { 50 | return 51 | } 52 | 53 | // チャンネルIDが入力されていない限り無視します。 54 | guard let channelId = channelIdTextField.text, !channelId.isEmpty else { 55 | return 56 | } 57 | 58 | // 接続試行中なら無視します。 59 | if isConnecting { 60 | return 61 | } 62 | isConnecting = true 63 | 64 | // ユーザーが選択した設定をUIコントロールから取得します。 65 | let videoCodec: VideoCodec 66 | switch videoCodecSegmentedControl.selectedSegmentIndex { 67 | case 0: videoCodec = .default 68 | case 1: videoCodec = .vp8 69 | case 2: videoCodec = .vp9 70 | case 3: videoCodec = .av1 71 | case 4: videoCodec = .h264 72 | case 5: videoCodec = .h265 73 | default: fatalError() 74 | } 75 | 76 | let spotlightFocusRid: SpotlightRid 77 | switch spotlightFocusRidSegmentedControl.selectedSegmentIndex { 78 | case 0: spotlightFocusRid = .unspecified 79 | case 1: spotlightFocusRid = .none 80 | case 2: spotlightFocusRid = .r0 81 | case 3: spotlightFocusRid = .r1 82 | case 4: spotlightFocusRid = .r2 83 | default: fatalError() 84 | } 85 | 86 | let spotlightUnfocusRid: SpotlightRid 87 | switch spotlightUnfocusRidSegmentedControl.selectedSegmentIndex { 88 | case 0: spotlightUnfocusRid = .unspecified 89 | case 1: spotlightUnfocusRid = .none 90 | case 2: spotlightUnfocusRid = .r0 91 | case 3: spotlightUnfocusRid = .r1 92 | case 4: spotlightUnfocusRid = .r2 93 | default: fatalError() 94 | } 95 | 96 | let spotlightNumber: Int? 97 | if spotlightNumberSegmentedControl.selectedSegmentIndex == 0 { 98 | spotlightNumber = nil 99 | } else { 100 | spotlightNumber = spotlightNumberSegmentedControl.selectedSegmentIndex 101 | } 102 | 103 | let simulcast: Bool 104 | switch simulcastSegmentedControl.selectedSegmentIndex { 105 | case 0: simulcast = false 106 | case 1: simulcast = true 107 | default: fatalError() 108 | } 109 | 110 | let dataChannelSignaling: Bool? 111 | switch dataChannelSignalingSegmentedControl.selectedSegmentIndex { 112 | case 0: dataChannelSignaling = nil 113 | case 1: dataChannelSignaling = false 114 | case 2: dataChannelSignaling = true 115 | default: fatalError() 116 | } 117 | 118 | let ignoreDisconnectWebSocket: Bool? 119 | switch ignoreDisconnectWebSocketSegmentedControl.selectedSegmentIndex { 120 | case 0: ignoreDisconnectWebSocket = nil 121 | case 1: ignoreDisconnectWebSocket = false 122 | case 2: ignoreDisconnectWebSocket = true 123 | default: fatalError() 124 | } 125 | 126 | // 入力された設定を元にSoraへ接続を行います。 127 | // ビデオチャットアプリでは複数のユーザーが同時に配信を行う必要があるため、 128 | // role 引数には .sendrecv を指定し、マルチストリームを有効にします。 129 | SoraSDKManager.shared.connect( 130 | channelId: channelId, 131 | videoCodec: videoCodec, 132 | spotlightFocusRid: spotlightFocusRid, 133 | spotlightUnfocusRid: spotlightUnfocusRid, 134 | spotlightNumber: spotlightNumber, 135 | simulcast: simulcast, 136 | dataChannelSignaling: dataChannelSignaling, 137 | ignoreDisconnectWebSocket: ignoreDisconnectWebSocket 138 | ) { [weak self] error in 139 | // 接続処理が終了したので false にします。 140 | self?.isConnecting = false 141 | 142 | if let error { 143 | // errorがnilでないばあいは、接続に失敗しています。 144 | // この場合は、エラー表示をユーザーに返すのが親切です。 145 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 146 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 147 | NSLog("[sample] SoraSDKManager connection error: \(error)") 148 | DispatchQueue.main.async { 149 | let alertController = UIAlertController(title: "接続に失敗しました", 150 | message: error.localizedDescription, 151 | preferredStyle: .alert) 152 | alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 153 | self?.present(alertController, animated: true, completion: nil) 154 | } 155 | } else { 156 | // errorがnilの場合は、接続に成功しています。 157 | NSLog("[sample] SoraSDKManager connected.") 158 | 159 | // 次の配信画面に遷移します。 160 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 161 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 162 | DispatchQueue.main.async { 163 | // ConnectセグエはMain.storyboard内で定義されているので、そちらをご確認ください。 164 | self?.performSegue(withIdentifier: "Connect", sender: self) 165 | } 166 | } 167 | } 168 | } 169 | 170 | /** 171 | 配信画面からのUnwind Segueの着地地点として定義してあります。 172 | 詳細はMain.storyboardの設定をご確認ください。 173 | */ 174 | @IBAction func onUnwindToConfig(_ segue: UIStoryboardSegue) { 175 | // 前の画面から戻ってきても、特に処理は何も行いません。 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Classes/SoraSDKManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Sora 3 | 4 | /** 5 | Sora SDK関連の、アプリケーション全体で共通して行いたい処理を行うシングルトン・マネージャ・クラスです。 6 | 7 | このようなクラスを用意しておくと、Sora SDKのConnectionをアプリケーション全体で一つだけ確実に管理する事が可能になるため、おすすめです。 8 | */ 9 | class SoraSDKManager { 10 | /** 11 | SoraSDKManagerのシングルトンインスタンスです。 12 | */ 13 | static let shared = SoraSDKManager() 14 | 15 | /** 16 | 現在接続中のSora SDKのMediaChannelです。 17 | 18 | 殆どの場合、アプリケーション全体で一つだけ同時にMediaChannelに接続することになるので、シングルトンとして用意すると便利に使えます。 19 | */ 20 | private(set) var currentMediaChannel: MediaChannel? 21 | 22 | /** 23 | シングルトンにしたいので、イニシャライザはprivateにしてあります。 24 | */ 25 | private init() { 26 | // SDK のログを表示します。 27 | // 送受信されるシグナリングの内容や接続エラーを確認できます。 28 | Logger.shared.level = .debug 29 | Sora.setWebRTCLogLevel(.info) 30 | } 31 | 32 | /** 33 | 新たにSoraに接続を試みます。接続に成功した場合、currentMediaChannelが更新されます。 34 | 35 | 既に接続されており、currentMediaChannelが設定されている場合は新たに接続ができないようにしてあります。 36 | その場合は、一旦先に `disconnect()` を呼び出して、現在の接続を終了してください。 37 | */ 38 | func connect(channelId: String, 39 | videoCodec: VideoCodec, 40 | spotlightFocusRid: SpotlightRid, 41 | spotlightUnfocusRid: SpotlightRid, 42 | spotlightNumber: Int?, 43 | simulcast: Bool, 44 | dataChannelSignaling: Bool? = nil, 45 | ignoreDisconnectWebSocket: Bool? = nil, 46 | completionHandler: ((Error?) -> Void)?) 47 | { 48 | // 既にcurrentMediaChannelが設定されている場合は、接続済みとみなし、何もしないで終了します。 49 | guard currentMediaChannel == nil else { 50 | return 51 | } 52 | 53 | // Configurationを生成して、接続設定を行います。 54 | // 必須となる設定はurl, channelId, roleのみです。 55 | // その他の設定にはデフォルト値が指定されていますが、ここで必要に応じて自由に調整することが可能です。 56 | var configuration = Configuration(urlCandidates: Environment.urls, channelId: channelId, role: .sendrecv, 57 | multistreamEnabled: true) 58 | // 引数で指定された値を設定します。 59 | configuration.videoCodec = videoCodec 60 | configuration.spotlightFocusRid = spotlightFocusRid 61 | configuration.spotlightUnfocusRid = spotlightUnfocusRid 62 | configuration.spotlightNumber = spotlightNumber 63 | configuration.simulcastEnabled = simulcast 64 | configuration.dataChannelSignaling = dataChannelSignaling 65 | configuration.ignoreDisconnectWebSocket = ignoreDisconnectWebSocket 66 | configuration.signalingConnectMetadata = Environment.signalingConnectMetadata 67 | 68 | // bundle_id を設定します。 69 | configuration.bundleId = UUID().uuidString 70 | 71 | // スポットライトを有効にします。 72 | configuration.spotlightEnabled = .enabled 73 | 74 | // スポットライト用にビットレートを高めに設定します。 75 | configuration.videoBitRate = 3000 76 | 77 | // Soraに接続を試みます。 78 | _ = Sora.shared.connect(configuration: configuration) { [weak self] mediaChannel, error in 79 | // 接続に成功した場合は、mediaChannelに値が返され、errorがnilになります。 80 | // 一方、接続に失敗した場合は、mediaChannelはnilとなり、errorが返されます。 81 | self?.currentMediaChannel = mediaChannel 82 | completionHandler?(error) 83 | NSLog("[sample] mediaChannel.connectedUrl: \(String(describing: mediaChannel?.connectedUrl))") 84 | } 85 | } 86 | 87 | /** 88 | 既に接続済みのmediaChannelから切断します。 89 | 90 | currentMediaChannelがnilで、まだ接続されていないときは、何もしないで終了します。 91 | */ 92 | func disconnect() { 93 | guard let mediaChannel = currentMediaChannel else { 94 | return 95 | } 96 | mediaChannel.disconnect(error: nil) 97 | currentMediaChannel = nil 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Environment.example.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Environment { 4 | // 接続するサーバーのシグナリング URL 5 | // 配列で複数の URL を指定することが可能です 6 | static let urls = [URL(string: "wss://sora.example.com/signaling")!] 7 | 8 | // チャネル ID 9 | static let channelId = "sora" 10 | 11 | // type: connect に含めるメタデータ 12 | static let signalingConnectMetadata: Encodable? = nil 13 | } 14 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | NSAppTransportSecurity 66 | 67 | NSAllowsArbitraryLoads 68 | 69 | 70 | NSCameraUsageDescription 71 | WebRTC 72 | NSMicrophoneUsageDescription 73 | WebRTC 74 | 75 | 76 | -------------------------------------------------------------------------------- /SpotlightSample/SpotlightSample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 4 | var window: UIWindow? 5 | 6 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 7 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 8 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 9 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 10 | guard let _ = (scene as? UIWindowScene) else { return } 11 | } 12 | 13 | func sceneDidDisconnect(_ scene: UIScene) { 14 | // Called as the scene is being released by the system. 15 | // This occurs shortly after the scene enters the background, or when its session is discarded. 16 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 17 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 18 | } 19 | 20 | func sceneDidBecomeActive(_ scene: UIScene) { 21 | // Called when the scene has moved from an inactive state to an active state. 22 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 23 | } 24 | 25 | func sceneWillResignActive(_ scene: UIScene) { 26 | // Called when the scene will move from an active state to an inactive state. 27 | // This may occur due to temporary interruptions (ex. an incoming phone call). 28 | } 29 | 30 | func sceneWillEnterForeground(_ scene: UIScene) { 31 | // Called as the scene transitions from the background to the foreground. 32 | // Use this method to undo the changes made on entering the background. 33 | } 34 | 35 | func sceneDidEnterBackground(_ scene: UIScene) { 36 | // Called as the scene transitions from the foreground to the background. 37 | // Use this method to save data, release shared resources, and store enough scene-specific state information 38 | // to restore the scene back to its current state. 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /VideoChatSample/.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 2 | -------------------------------------------------------------------------------- /VideoChatSample/.swiftformat: -------------------------------------------------------------------------------- 1 | --exclude Pods 2 | --indent 4 3 | --semicolons inline 4 | --trailingclosures 5 | --wrapparameters after-first 6 | --header strip 7 | 8 | --disable redundantInit,sortedSwitchCases,strongOutlets,unusedArguments,wrapSwitchCases 9 | -------------------------------------------------------------------------------- /VideoChatSample/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - VideoChatSample 3 | excluded: 4 | - Pods 5 | disabled_rules: 6 | - identifier_name 7 | - force_cast 8 | - force_try 9 | - cyclomatic_complexity 10 | - function_body_length 11 | - file_length 12 | - line_length 13 | - type_body_length 14 | - weak_delegate 15 | - opening_brace 16 | - closing_brace 17 | - anonymous_argument_in_multiline_closure 18 | - conditional_returns_on_newline 19 | - multiline_arguments 20 | - multiline_arguments_brackets 21 | - multiline_literal_brackets 22 | - multiline_parameters 23 | - multiline_parameters_brackets 24 | - vertical_parameter_alignment 25 | - vertical_parameter_alignment_on_call 26 | - vertical_whitespace 27 | - vertical_whitespace_between_cases 28 | - vertical_whitespace_closing_braces 29 | - vertical_whitespace_opening_braces 30 | - colon 31 | - comma 32 | - comment_spacing 33 | - trailing_comma 34 | - trailing_newline 35 | - trailing_whitespace 36 | - closure_parameter_position 37 | - closure_end_indentation 38 | - closure_spacing 39 | - for_where 40 | - large_tuple 41 | - todo 42 | -------------------------------------------------------------------------------- /VideoChatSample/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/shiguredo/sora-ios-sdk-specs.git' 2 | source 'https://cdn.cocoapods.org/' 3 | 4 | platform :ios, '14.0' 5 | 6 | target 'VideoChatSample' do 7 | use_frameworks! 8 | pod 'Sora', '2025.1.2' 9 | 10 | pod 'SwiftLint' 11 | pod 'SwiftFormat/CLI' 12 | end 13 | -------------------------------------------------------------------------------- /VideoChatSample/README.md: -------------------------------------------------------------------------------- 1 | # VideoChatSample (ビデオチャット) 2 | 3 | このサンプルでは、複数人が同時に参加できるビデオチャットアプリを Sora iOS SDK を用いて実装する方法を説明しています。 4 | より発展的な、一つのクライアントが同時に動画の配信と視聴を行い、かつ複数人が同時に参加する Multistreaming 機能を用いたサンプルアプリです。 5 | 6 | ## ビルド環境 7 | 8 | サンプルアプリをビルドする際の環境については [システム条件](../README.md#システム条件) をご確認ください。 9 | 10 | ## ビルド方法 11 | 12 | 1. CocoaPods でライブラリを取得します。 13 | 14 | ``` 15 | $ pod install 16 | ``` 17 | 18 | 2. ``VideoChatSample/Environment.example.swift`` のファイル名を ``VideoChatSample/Environment.swift`` に変更し、接続情報を設定します。 19 | 20 | ``` 21 | $ cp VideoChatSample/Environment.example.swift VideoChatSample/Environment.swift 22 | ``` 23 | 24 | 3. ``VideoChatSample.xcworkspace`` を Xcode で開いてビルドします。 25 | 26 | ## サンプルアプリの使い方 27 | 28 | このサンプルアプリでは、同じクライアントIDに対して最大で12人までが接続し、同時にビデオチャットに参加できます。 29 | それ以上の人数が同時に接続する場合の挙動については保証されません。 30 | 実際に配信されていることを確認したい場合には、複数台のデバイスにこのサンプルアプリをインストールするか、 31 | または他の Sora クライアントを用いて同時に接続する必要があります。 32 | 33 | 三人以上が同じクライアントIDに対して接続されている場合、画面が複数人で分割されます。 34 | 35 | 自分自身の配信している動画はポップアップで表示されます。 36 | 37 | ## 実装上の詳細について 38 | 39 | 実装上の詳細につきましてはサンプルアプリのソースコード上に詳細なコメントを用意してありますので、適時そちらをご確認ください。 40 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | var window: UIWindow? 6 | 7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 8 | true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguredo/sora-ios-sdk-samples/33350858251df0c77db564da0393e4f3a87c691c/VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Classes/ConfigViewController.swift: -------------------------------------------------------------------------------- 1 | import Sora 2 | import UIKit 3 | 4 | /** 5 | チャット接続設定画面です。 6 | */ 7 | class ConfigViewController: UITableViewController { 8 | /// チャンネルIDを入力させる欄です。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 9 | @IBOutlet var channelIdTextField: UITextField! 10 | 11 | /// 動画のコーデックを指定するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 12 | @IBOutlet var videoCodecSegmentedControl: UISegmentedControl! 13 | 14 | /// データチャンネルシグナリング機能を有効にするためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 15 | @IBOutlet var dataChannelSignalingSegmentedControl: UISegmentedControl! 16 | 17 | /// データチャンネルシグナリング機能を有効時に WebSoket 切断を許容するためのコントロールです。Main.storyboardから設定されていますので、詳細はそちらをご確認ください。 18 | @IBOutlet var ignoreDisconnectWebSocketSegmentedControl: UISegmentedControl! 19 | 20 | @IBOutlet var vp9ProfileIdSegmentedControl: UISegmentedControl! 21 | 22 | @IBOutlet var av1ProfileSegmentedControl: UISegmentedControl! 23 | 24 | @IBOutlet var h264ProfileLevelIdTextField: UITextField! 25 | 26 | /// 接続試行中かどうかを表します。 27 | var isConnecting = false 28 | 29 | /** 30 | 画面起動時の処理を記述します。 31 | */ 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | channelIdTextField.text = Environment.channelId 35 | h264ProfileLevelIdTextField.text = "" 36 | } 37 | 38 | /** 39 | 行がタップされたときの処理を記述します。 40 | */ 41 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 42 | // まず最初にタップされた行の選択状態を解除します。 43 | tableView.deselectRow(at: indexPath, animated: true) 44 | 45 | // 選択された行が「接続」ボタンでない限り無視します。 46 | guard indexPath.section == 4, indexPath.row == 0 else { 47 | return 48 | } 49 | 50 | // チャンネルIDが入力されていない限り無視します。 51 | guard let channelId = channelIdTextField.text, !channelId.isEmpty else { 52 | return 53 | } 54 | 55 | // 接続試行中なら無視します。 56 | if isConnecting { 57 | return 58 | } 59 | isConnecting = true 60 | 61 | // ユーザーが選択した設定をUIコントロールから取得します。 62 | let videoCodec: VideoCodec 63 | switch videoCodecSegmentedControl.selectedSegmentIndex { 64 | case 0: videoCodec = .default 65 | case 1: videoCodec = .vp8 66 | case 2: videoCodec = .vp9 67 | case 3: videoCodec = .av1 68 | case 4: videoCodec = .h264 69 | case 5: videoCodec = .h265 70 | default: fatalError() 71 | } 72 | 73 | let dataChannelSignaling: Bool? 74 | switch dataChannelSignalingSegmentedControl.selectedSegmentIndex { 75 | case 0: dataChannelSignaling = nil 76 | case 1: dataChannelSignaling = false 77 | case 2: dataChannelSignaling = true 78 | default: fatalError() 79 | } 80 | 81 | let ignoreDisconnectWebSocket: Bool? 82 | switch ignoreDisconnectWebSocketSegmentedControl.selectedSegmentIndex { 83 | case 0: ignoreDisconnectWebSocket = nil 84 | case 1: ignoreDisconnectWebSocket = false 85 | case 2: ignoreDisconnectWebSocket = true 86 | default: fatalError() 87 | } 88 | 89 | let vp9ProfileId: Int? 90 | switch vp9ProfileIdSegmentedControl.selectedSegmentIndex { 91 | case 0: vp9ProfileId = nil 92 | case 1: vp9ProfileId = 0 93 | case 2: vp9ProfileId = 1 94 | case 3: vp9ProfileId = 2 95 | default: fatalError() 96 | } 97 | 98 | let av1Profile: Int? 99 | switch av1ProfileSegmentedControl.selectedSegmentIndex { 100 | case 0: av1Profile = nil 101 | case 1: av1Profile = 0 102 | case 2: av1Profile = 1 103 | case 3: av1Profile = 2 104 | default: fatalError() 105 | } 106 | 107 | let h264ProfileLevelId = h264ProfileLevelIdTextField.text!.trimmingCharacters(in: .whitespaces).isEmpty ? nil : h264ProfileLevelIdTextField.text!.trimmingCharacters(in: .whitespaces) 108 | var configuration = Configuration(urlCandidates: Environment.urls, channelId: channelId, role: .sendrecv, multistreamEnabled: true) 109 | configuration.videoCodec = videoCodec 110 | configuration.dataChannelSignaling = dataChannelSignaling 111 | configuration.ignoreDisconnectWebSocket = ignoreDisconnectWebSocket 112 | 113 | let videoVp9Params = vp9ProfileId != nil ? ["profile_id": vp9ProfileId!] : nil 114 | configuration.videoVp9Params = videoVp9Params 115 | 116 | let videoAv1Params = av1Profile != nil ? ["profile": av1Profile!] : nil 117 | configuration.videoAv1Params = videoAv1Params 118 | 119 | let videoH264Params = h264ProfileLevelId != nil ? ["profile_level_id": h264ProfileLevelId!] : nil 120 | configuration.videoH264Params = videoH264Params 121 | 122 | configuration.signalingConnectMetadata = Environment.signalingConnectMetadata 123 | 124 | // 入力された設定を元にSoraへ接続を行います。 125 | // ビデオチャットアプリでは複数のユーザーが同時に配信を行う必要があるため、 126 | // role 引数には .sendrecv を指定し、マルチストリームを有効にします。 127 | SoraSDKManager.shared.connect( 128 | with: configuration 129 | ) { [weak self] error in 130 | // 接続処理が終了したので false にします。 131 | self?.isConnecting = false 132 | 133 | if let error { 134 | // errorがnilでないばあいは、接続に失敗しています。 135 | // この場合は、エラー表示をユーザーに返すのが親切です。 136 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 137 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 138 | NSLog("[sample] SoraSDKManager connection error: \(error)") 139 | DispatchQueue.main.async { 140 | let alertController = UIAlertController(title: "接続に失敗しました", 141 | message: error.localizedDescription, 142 | preferredStyle: .alert) 143 | alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 144 | self?.present(alertController, animated: true, completion: nil) 145 | } 146 | } else { 147 | // errorがnilの場合は、接続に成功しています。 148 | NSLog("[sample] SoraSDKManager connected.") 149 | 150 | // 次の配信画面に遷移します。 151 | // なお、このコールバックはメインスレッド以外のスレッドから呼び出される可能性があるので、 152 | // UI操作を行う際には必ずDispatchQueue.main.asyncを使用してメインスレッドでUI処理を呼び出すようにしてください。 153 | DispatchQueue.main.async { 154 | // ConnectセグエはMain.storyboard内で定義されているので、そちらをご確認ください。 155 | self?.performSegue(withIdentifier: "Connect", sender: self) 156 | } 157 | } 158 | } 159 | } 160 | 161 | /** 162 | 配信画面からのUnwind Segueの着地地点として定義してあります。 163 | 詳細はMain.storyboardの設定をご確認ください。 164 | */ 165 | @IBAction func onUnwindToConfig(_ segue: UIStoryboardSegue) { 166 | // 前の画面から戻ってきても、特に処理は何も行いません。 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Classes/SoraSDKManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Sora 3 | 4 | /** 5 | Sora SDK関連の、アプリケーション全体で共通して行いたい処理を行うシングルトン・マネージャ・クラスです。 6 | 7 | このようなクラスを用意しておくと、Sora SDKのConnectionをアプリケーション全体で一つだけ確実に管理する事が可能になるため、おすすめです。 8 | */ 9 | class SoraSDKManager { 10 | /** 11 | SoraSDKManagerのシングルトンインスタンスです。 12 | */ 13 | static let shared = SoraSDKManager() 14 | 15 | /** 16 | 現在接続中のSora SDKのMediaChannelです。 17 | 18 | 殆どの場合、アプリケーション全体で一つだけ同時にMediaChannelに接続することになるので、シングルトンとして用意すると便利に使えます。 19 | */ 20 | private(set) var currentMediaChannel: MediaChannel? 21 | 22 | /** 23 | シングルトンにしたいので、イニシャライザはprivateにしてあります。 24 | */ 25 | private init() { 26 | // SDK のログを表示します。 27 | // 送受信されるシグナリングの内容や接続エラーを確認できます。 28 | Logger.shared.level = .debug 29 | Sora.setWebRTCLogLevel(.info) 30 | } 31 | 32 | /** 33 | 新たにSoraに接続を試みます。接続に成功した場合、currentMediaChannelが更新されます。 34 | 35 | 既に接続されており、currentMediaChannelが設定されている場合は新たに接続ができないようにしてあります。 36 | その場合は、一旦先に `disconnect()` を呼び出して、現在の接続を終了してください。 37 | */ 38 | func connect(with configuration: Configuration, 39 | completionHandler: ((Error?) -> Void)?) 40 | { 41 | // 既にcurrentMediaChannelが設定されている場合は、接続済みとみなし、何もしないで終了します。 42 | guard currentMediaChannel == nil else { 43 | return 44 | } 45 | // Soraに接続を試みます。 46 | _ = Sora.shared.connect(configuration: configuration) { [weak self] mediaChannel, error in 47 | // 接続に成功した場合は、mediaChannelに値が返され、errorがnilになります。 48 | // 一方、接続に失敗した場合は、mediaChannelはnilとなり、errorが返されます。 49 | self?.currentMediaChannel = mediaChannel 50 | completionHandler?(error) 51 | NSLog("[sample] mediaChannel.connectedUrl: \(String(describing: mediaChannel?.connectedUrl))") 52 | } 53 | } 54 | 55 | /** 56 | 既に接続済みのmediaChannelから切断します。 57 | 58 | currentMediaChannelがnilで、まだ接続されていないときは、何もしないで終了します。 59 | */ 60 | func disconnect() { 61 | guard let mediaChannel = currentMediaChannel else { 62 | return 63 | } 64 | mediaChannel.disconnect(error: nil) 65 | currentMediaChannel = nil 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Classes/VideoChatRoomViewController.swift: -------------------------------------------------------------------------------- 1 | import Sora 2 | import UIKit 3 | 4 | /** 5 | ビデオチャットを行う画面です。 6 | */ 7 | class VideoChatRoomViewController: UIViewController { 8 | /** ビデオチャットの、配信者以外の参加者の映像を表示するためのViewです。 */ 9 | private var downstreamVideoViews: [VideoView] = [] 10 | 11 | /** ビデオチャットの、配信者自身の映像を表示するためのViewです。 */ 12 | private var upstreamVideoView: VideoView? 13 | 14 | override func viewWillAppear(_ animated: Bool) { 15 | super.viewWillAppear(animated) 16 | 17 | // チャット画面に遷移する直前に、タイトルを現在のチャンネルIDを使用して書き換えています。 18 | if let mediaChannel = SoraSDKManager.shared.currentMediaChannel { 19 | navigationItem.title = "チャット中: \(mediaChannel.configuration.channelId)" 20 | } 21 | } 22 | 23 | override func viewDidAppear(_ animated: Bool) { 24 | super.viewDidAppear(animated) 25 | 26 | // このビデオチャットではチャット中に別のクライアントが入室したり退室したりする可能性があります。 27 | // 入室退室が発生したら都度動画の表示を更新しなければなりませんので、そのためのコールバックを設定します。 28 | if let mediaChannel = SoraSDKManager.shared.currentMediaChannel { 29 | mediaChannel.handlers.onAddStream = { [weak self] _ in 30 | NSLog("[sample] mediaChannel.handlers.onAddStream") 31 | DispatchQueue.main.async { 32 | self?.handleUpdateStreams() 33 | } 34 | } 35 | mediaChannel.handlers.onRemoveStream = { [weak self] _ in 36 | NSLog("[sample] mediaChannel.handlers.onRemoveStream") 37 | DispatchQueue.main.async { 38 | self?.handleUpdateStreams() 39 | } 40 | } 41 | 42 | // サーバーから切断されたときのコールバックを設定します。 43 | mediaChannel.handlers.onDisconnect = { [weak self] _ in 44 | NSLog("[sample] mediaChannel.handlers.onDisconnect") 45 | DispatchQueue.main.async { 46 | self?.handleDisconnect() 47 | } 48 | } 49 | } 50 | 51 | // その後、動画の表示を初回更新します。次回以降の更新は直前に設定したコールバックが行います。 52 | handleUpdateStreams() 53 | } 54 | 55 | override func viewWillDisappear(_ animated: Bool) { 56 | super.viewWillDisappear(animated) 57 | 58 | // viewDidAppearで設定したコールバックを、対になるここで削除します。 59 | if let mediaChannel = SoraSDKManager.shared.currentMediaChannel { 60 | mediaChannel.handlers.onAddStream = nil 61 | mediaChannel.handlers.onRemoveStream = nil 62 | mediaChannel.handlers.onDisconnect = nil 63 | } 64 | } 65 | 66 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 67 | // 画面のサイズクラスが変更になるとき(画面回転などが対象です)、 68 | // 再レイアウトが必要になるので、アニメーションに合わせて画面の再レイアウトを粉います。 69 | coordinator.animate(alongsideTransition: { [weak self] _ in 70 | self?.layoutVideoViews(for: size) 71 | }) 72 | } 73 | 74 | fileprivate func layoutVideoViews(for size: CGSize) { 75 | // 画面が縦方向に長いか横方向に長いかによってレイアウトを分けることにしたいので、最初に判定します。 76 | let isPortrait = size.height > size.width 77 | 78 | // 同室の他のユーザーの配信のVideoViewをレイアウトします。 79 | // このレイアウトは現在同室に入っているユーザーの数に応じて変化します。 80 | // ここでは最大で12ユーザーまでをサポートすることにします。 81 | let videoViews = downstreamVideoViews.prefix(12) 82 | switch videoViews.count { 83 | case 1: 84 | // 1ユーザの場合は画面全体に表示します。 85 | let videoView = videoViews[0] 86 | videoView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) 87 | case 2: 88 | // 2ユーザの場合は二分割します。 89 | let videoView0 = videoViews[0] 90 | let videoView1 = videoViews[1] 91 | if isPortrait { 92 | videoView0.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height / 2) 93 | videoView1.frame = CGRect(x: 0, y: size.height / 2, width: size.width, height: size.height / 2) 94 | } else { 95 | videoView0.frame = CGRect(x: 0, y: 0, width: size.width / 2, height: size.height) 96 | videoView1.frame = CGRect(x: size.width / 2, y: 0, width: size.width / 2, height: size.height) 97 | } 98 | case 3 ... 4: 99 | // 3~4ユーザーの場合は四等分します。 100 | let videoView0 = videoViews[0] 101 | let videoView1 = videoViews[1] 102 | let videoView2 = videoViews[2] 103 | videoView0.frame = CGRect(x: 0, y: 0, width: size.width / 2, height: size.height / 2) 104 | videoView1.frame = CGRect(x: size.width / 2, y: 0, width: size.width / 2, height: size.height / 2) 105 | videoView2.frame = CGRect(x: 0, y: size.height / 2, width: size.width / 2, height: size.height / 2) 106 | if videoViews.count == 4 { 107 | let videoView3 = videoViews[3] 108 | videoView3.frame = CGRect(x: size.width / 2, y: size.height / 2, width: size.width / 2, height: size.height / 2) 109 | } 110 | case 5 ... 12: 111 | // それ以上の場合には、長辺を4等分、短辺を2〜3等分して、左上から順番に、最大8〜12個を並べるようにします。 112 | // 最初にX方向の分割数mxとY方向の分割数myを計算します。 113 | // 条件として、(縦向きか否か && videoViewの枚数は8枚以下かそれ以上か)によって分岐させます。 114 | let mx: Int 115 | let my: Int 116 | switch (isPortrait, videoViews.count > 8) { 117 | case (true, true): (mx, my) = (3, 4) // 縦向き、最大12枚 118 | case (true, false): (mx, my) = (2, 4) // 縦向き、8枚まで 119 | case (false, true): (mx, my) = (4, 3) // 横向き、最大12枚 120 | case (false, false): (mx, my) = (4, 2) // 横向き、8枚まで 121 | } 122 | // あとはループを回して1枚ずつ左上から右下方向にvideoViewsをタイル状に並べていくだけです。 123 | // このときタイル(x, y)は、videoViews[y * my + x]番目に相当します。 124 | // そこで(y * my + x)がvideoViewsの実際の枚数を超えない間だけループを回すようにしています。 125 | for y in 0 ..< my { 126 | for x in 0 ..< mx where (y * my + x) < videoViews.count { 127 | let videoView = videoViews[y * my + x] 128 | let width = size.width / CGFloat(mx) 129 | let height = size.height / CGFloat(my) 130 | videoView.frame = CGRect(x: CGFloat(x) * width, 131 | y: CGFloat(y) * height, 132 | width: width, 133 | height: height) 134 | } 135 | } 136 | default: 137 | // このケースは存在しえません。 138 | break 139 | } 140 | 141 | // 自分自身の配信を写すためのVideoViewを設定します。 142 | // このVideoViewは最前面にフロートして表示されるようになります。 143 | if let videoView = upstreamVideoView { 144 | let floatingSize = CGSize(width: 100, height: 150) 145 | videoView.frame = CGRect(x: size.width - floatingSize.width - 20.0, 146 | y: size.height - floatingSize.height - 20.0, 147 | width: floatingSize.width, 148 | height: floatingSize.height) 149 | view.bringSubviewToFront(videoView) 150 | } 151 | } 152 | } 153 | 154 | // MARK: - Sora SDKのイベントハンドリング 155 | 156 | extension VideoChatRoomViewController { 157 | /** 158 | 接続されている配信者の数が変化したときに呼び出されるべき処理をまとめています。 159 | */ 160 | private func handleUpdateStreams() { 161 | // まずはmediaPublisherのmediaStreamを取得します。 162 | guard (SoraSDKManager.shared.currentMediaChannel?.streams) != nil else { 163 | return 164 | } 165 | 166 | // mediaStreamを端末とそれ以外のユーザーのリストに分けます。 167 | // CameraVideoCapturer が管理するストリームと同一の ID であれば端末の配信ストリームです。 168 | let upstream = SoraSDKManager.shared.currentMediaChannel?.senderStream 169 | let downstreams = SoraSDKManager.shared.currentMediaChannel?.receiverStreams ?? [] 170 | 171 | // 同室の他のユーザーの配信を見るためのVideoViewを設定します。 172 | if downstreamVideoViews.count < downstreams.count { 173 | // 用意されているVideoViewの数が足りないので、新たに追加します。 174 | // このとき、VideoView.contentModeを変化させることで、描画モードを調整することができます。 175 | // 今回は枠に合わせてアスペクト比を保ったまま領域全体を埋めたいので、.scaleAspectFillを指定しています。 176 | for _ in downstreams[downstreamVideoViews.count ..< downstreams.count] { 177 | let videoView = VideoView() 178 | videoView.contentMode = .scaleAspectFill 179 | view.addSubview(videoView) 180 | downstreamVideoViews.append(videoView) 181 | } 182 | } else if downstreamVideoViews.count > downstreams.count { 183 | // 人が抜けたためにVideoViewが余っているので、削除します。 184 | for videoView in downstreamVideoViews[downstreams.count ..< downstreamVideoViews.count] { 185 | videoView.removeFromSuperview() 186 | } 187 | downstreamVideoViews.removeSubrange(downstreams.count ..< downstreamVideoViews.count) 188 | } else { 189 | // 既に全員分のVideoViewの準備が出来ているので、VideoViewの追加削除は必要ありません。 190 | } 191 | for (downstream, videoView) in zip(downstreams, downstreamVideoViews) { 192 | downstream.videoRenderer = videoView 193 | } 194 | 195 | // 自分自身の配信を写すためのVideoViewを設定します。 196 | // このとき、VideoView.contentModeを変化させることで、描画モードを調整することができます。 197 | // 今回は枠に合わせてアスペクト比を保ったまま領域全体を埋めたいので、.scaleAspectFillを指定しています。 198 | if upstreamVideoView == nil { 199 | let videoView = VideoView(frame: .zero) 200 | videoView.contentMode = .scaleAspectFill 201 | videoView.layer.borderColor = UIColor.white.cgColor 202 | videoView.layer.borderWidth = 1.0 203 | view.addSubview(videoView) 204 | upstreamVideoView = videoView 205 | } 206 | upstream?.videoRenderer = upstreamVideoView 207 | 208 | // 最後に今セットアップしたVideoViewを正しく画面上でレイアウトします。 209 | layoutVideoViews(for: view.bounds.size) 210 | } 211 | 212 | /** 213 | 接続が切断されたときに呼び出されるべき処理をまとめています。 214 | この切断は、能動的にこちらから切断した場合も、受動的に何らかのエラーなどが原因で切断されてしまった場合も、 215 | いずれの場合も含めます。 216 | */ 217 | private func handleDisconnect() { 218 | // 明示的に配信をストップしてから、画面を閉じるようにしています。 219 | SoraSDKManager.shared.disconnect() 220 | // ExitセグエはMain.storyboard内で定義されているので、そちらをご確認ください。 221 | performSegue(withIdentifier: "Exit", sender: self) 222 | } 223 | } 224 | 225 | // MARK: - Interface Builderのための実装 226 | 227 | extension VideoChatRoomViewController { 228 | /** 229 | カメラボタンを押したときの挙動を定義します。 230 | 詳しくはMain.storyboard内の定義をご覧ください。 231 | */ 232 | @IBAction func onCameraButton(_ sender: UIBarButtonItem) { 233 | guard let current = CameraVideoCapturer.current else { 234 | return 235 | } 236 | 237 | guard current.isRunning else { 238 | return 239 | } 240 | 241 | CameraVideoCapturer.flip(current) { error in 242 | if let error { 243 | NSLog("[sample] " + error.localizedDescription) 244 | } 245 | } 246 | } 247 | 248 | /** 249 | 閉じるボタンを押したときの挙動を定義します。 250 | 詳しくはMain.storyboard内の定義をご覧ください。 251 | */ 252 | @IBAction func onExitButton(_ sender: UIBarButtonItem) { 253 | handleDisconnect() 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Environment.example.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Environment { 4 | // 接続するサーバーのシグナリング URL 5 | // 配列で複数の URL を指定することが可能です 6 | static let urls = [URL(string: "wss://sora.example.com/signaling")!] 7 | 8 | // チャネル ID 9 | static let channelId = "sora" 10 | 11 | // type: connect に含めるメタデータ 12 | static let signalingConnectMetadata: Encodable? = nil 13 | } 14 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 2.0.0 19 | CFBundleVersion 20 | 200.0.0 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /VideoChatSample/VideoChatSample/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | CFBundleDisplayName = "ビデオチャット"; 2 | NSCameraUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSCameraUsageDescription を設定する必要があります。"; 3 | NSMicrophoneUsageDescription = "SoraSDK を使用するためには、 Info.plist に NSMicrophoneUsageDescription を設定する必要があります。"; 4 | -------------------------------------------------------------------------------- /lint-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ローカルで lint と formatter を実行するスクリプト 4 | # 未フォーマットか lint でルール違反を検出したら終了ステータス 1 を返す 5 | # GitHub Actions では未フォーマット箇所の有無の確認に使う 6 | 7 | PODS_ROOT=Pods 8 | SRCROOT=. 9 | FORMAT=${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat 10 | LINT=${PODS_ROOT}/SwiftLint/swiftlint 11 | 12 | # フォーマットの必要性を確認する 13 | $FORMAT --lint $SRCROOT 14 | format=$? 15 | 16 | $FORMAT $SRCROOT 17 | $LINT --fix $SRCROOT 18 | $LINT $SRCROOT 19 | lint=$? 20 | 21 | test $format -eq 0 -a $lint -eq 0 22 | exit $? --------------------------------------------------------------------------------