├── Cartfile
├── Examples
└── iOS
│ ├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── 152.png
│ │ ├── 167.png
│ │ ├── 180.png
│ │ ├── 20.png
│ │ ├── 29.png
│ │ ├── 40.png
│ │ ├── 58.png
│ │ ├── 60.png
│ │ ├── 76.png
│ │ ├── 80.png
│ │ ├── 87.png
│ │ ├── 1024.png
│ │ ├── 120 1.png
│ │ ├── 120 2.png
│ │ ├── 40 1.png
│ │ ├── 40 2.png
│ │ ├── 58 1.png
│ │ ├── 80 1.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
│ ├── Info.plist
│ ├── ViewControllers
│ ├── SettingsViewController.swift
│ └── MainViewController.swift
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── SceneDelegate.swift
│ ├── SettingsManager.swift
│ └── Settings.bundle
│ └── Root.plist
├── ApiVideoLiveStream.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── Tests
└── ApiVideoLiveStream
│ ├── Parameters.swift
│ └── ApiVideoLiveStreamTests.swift
├── .github
└── workflows
│ ├── create-release-from-changelog.yml
│ ├── release.yml
│ ├── create-documentation-pr.yml
│ └── build.yml
├── Package.resolved
├── LICENSE.md
├── ApiVideoLiveStream.podspec
├── Package.swift
├── .swiftlint.yml
├── .swiftformat
├── Sources
└── ApiVideoLiveStream
│ ├── models
│ ├── Resolution.swift
│ └── Configuration.swift
│ └── ApiVideoLiveStream.swift
├── CHANGELOG.md
├── .gitignore
├── README.md
└── CONTRIBUTING.md
/Cartfile:
--------------------------------------------------------------------------------
1 | github "shogo4405/HaishinKit.swift" ~> 1.9.3
2 |
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/120 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/120 1.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/120 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/120 2.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/40 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/40 1.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/40 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/40 2.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/58 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/58 1.png
--------------------------------------------------------------------------------
/Examples/iOS/Assets.xcassets/AppIcon.appiconset/80 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-swift-live-stream/HEAD/Examples/iOS/Assets.xcassets/AppIcon.appiconset/80 1.png
--------------------------------------------------------------------------------
/ApiVideoLiveStream.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/iOS/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 |
--------------------------------------------------------------------------------
/Tests/ApiVideoLiveStream/Parameters.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum Parameters {
4 | public static var rtmpUrl: String = "INTEGRATION_TESTS_RTMP_URL"
5 | public static var streamKey: String = "INTEGRATION_TESTS_RTMP_STREAM_KEY"
6 | }
7 |
--------------------------------------------------------------------------------
/ApiVideoLiveStream.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/create-release-from-changelog.yml:
--------------------------------------------------------------------------------
1 | name: Create draft release from CHANGELOG.md
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'CHANGELOG.md'
7 |
8 | jobs:
9 | update-documentation:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Create draft release if needed
14 | uses: apivideo/api.video-release-from-changelog-action@main
15 | with:
16 | github-auth-token: ${{ secrets.GITHUB_TOKEN }}
17 | prefix: v
18 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release to Cocoapods
2 | on:
3 | release:
4 | types: [published]
5 | jobs:
6 | deploy:
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 | - name: Install Cocoapods
11 | run: gem install cocoapods
12 | - name: Deploy to Cocoapods
13 | run: |
14 | xcodebuild -downloadPlatform iOS
15 | pod trunk push --allow-warnings
16 | env:
17 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "haishinkit.swift",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/shogo4405/HaishinKit.swift",
7 | "state" : {
8 | "revision" : "3d69e5a8514d7aead27aa4241dd859fc219acd3f",
9 | "version" : "1.9.3"
10 | }
11 | },
12 | {
13 | "identity" : "logboard",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/shogo4405/Logboard.git",
16 | "state" : {
17 | "revision" : "272976e1f3e8873e60ffe4b08fe50df48a93751b",
18 | "version" : "2.5.0"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/create-documentation-pr.yml:
--------------------------------------------------------------------------------
1 | name: Create documentation PR
2 | on:
3 | # Trigger the workflow on pull requests targeting the main branch
4 | pull_request:
5 | types: [assigned, unassigned, opened, reopened, synchronize, edited, labeled, unlabeled, edited, closed]
6 | branches:
7 | - main
8 |
9 | jobs:
10 | create_documentation_pr:
11 | if: github.event.action != 'closed'
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Check out current repository code
17 | uses: actions/checkout@v4
18 |
19 | - name: Create the documentation pull request
20 | uses: apivideo/api.video-create-readme-file-pull-request-action@main
21 | with:
22 | source-file-path: "README.md"
23 | destination-repository: apivideo/api.video-documentation
24 | destination-path: sdks/livestream
25 | destination-filename: swift-livestream-library.md
26 | pat: "${{ secrets.PAT }}"
27 |
--------------------------------------------------------------------------------
/Examples/iOS/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 | Your own description of the purpose
26 | NSMicrophoneUsageDescription
27 | Your own description of the purpose
28 |
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 api.video
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ApiVideoLiveStream.podspec:
--------------------------------------------------------------------------------
1 |
2 |
3 | Pod::Spec.new do |spec|
4 | spec.name = "ApiVideoLiveStream"
5 | spec.version = "1.4.6"
6 | spec.summary = "The api.video live stream framework allow easy integration of a live stream broadcast into your application."
7 |
8 | spec.description = <<-DESC
9 | Quickly add the ability to broadcast a live stream from your application with this module. Within minutes, your app will be streaming RTMP live video to api.video where it can be broadcasted to anyone around the world.
10 | DESC
11 |
12 | spec.homepage = "https://docs.api.video"
13 | spec.license = { :type => 'MIT' }
14 |
15 | spec.author = { "Ecosystem Team" => "ecosystem@api.video" }
16 | spec.social_media_url = "https://x.com/api_video"
17 |
18 | spec.swift_versions = ["5.6"]
19 | spec.ios.deployment_target = "13.0"
20 |
21 | spec.source = { :git => "https://github.com/apivideo/api.video-swift-live-stream.git", :tag => "v" + spec.version.to_s }
22 |
23 | spec.source_files = "Sources/**/*.{h,m,swift}"
24 | spec.exclude_files = "Sources/Exclude"
25 |
26 | spec.dependency "HaishinKit", "1.9.3"
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/Tests/ApiVideoLiveStream/ApiVideoLiveStreamTests.swift:
--------------------------------------------------------------------------------
1 | @testable import ApiVideoLiveStream
2 | import XCTest
3 |
4 | class ApiVideoLiveStreamTests: XCTestCase {
5 | private let connectionExpectation = XCTestExpectation(description: "connectionExpectation")
6 | private let disconnectionExpectation = XCTestExpectation(description: "disconnectionExpectation")
7 |
8 | func testSingleLiveStream() throws {
9 | let liveStream = try ApiVideoLiveStream()
10 | liveStream.delegate = self
11 | try liveStream.startStreaming(streamKey: Parameters.streamKey, url: Parameters.rtmpUrl)
12 | wait(for: [self.connectionExpectation], timeout: 10.0)
13 | liveStream.stopStreaming()
14 | wait(for: [self.disconnectionExpectation], timeout: 10.0)
15 | }
16 | }
17 |
18 | // MARK: ApiVideoLiveStreamDelegate
19 |
20 | extension ApiVideoLiveStreamTests: ApiVideoLiveStreamDelegate {
21 | func connectionSuccess() {
22 | self.connectionExpectation.fulfill()
23 | }
24 |
25 | func connectionFailed(_: String) {}
26 |
27 | func disconnection() {
28 | self.disconnectionExpectation.fulfill()
29 | }
30 |
31 | func audioError(_: Error) {}
32 |
33 | func videoError(_: Error) {}
34 | }
35 |
--------------------------------------------------------------------------------
/Examples/iOS/ViewControllers/SettingsViewController.swift:
--------------------------------------------------------------------------------
1 | import ApiVideoLiveStream
2 | import Foundation
3 | import InAppSettingsKit
4 | import UIKit
5 |
6 | class SettingsViewController: IASKAppSettingsViewController {
7 |
8 | override func viewDidLoad() {
9 | super.viewDidLoad()
10 |
11 | showDoneButton = true
12 | showCreditsFooter = false
13 |
14 | NotificationCenter.default.addObserver(
15 | self,
16 | selector: #selector(self.settingDidChange(notification:)),
17 | name: Notification.Name.IASKSettingChanged,
18 | object: nil
19 | )
20 | }
21 |
22 | @objc
23 | func settingDidChange(notification: Notification?) {
24 | guard let notification = notification,
25 | let paramChanged = notification.userInfo?.first,
26 | let key = paramChanged.key as? String else
27 | {
28 | return
29 | }
30 |
31 | switch key {
32 | case "VideoBitrateSlider":
33 | if let value = paramChanged.value as? Float {
34 | UserDefaults.standard.set(Int(value), forKey: "VideoBitrateValue")
35 | }
36 |
37 | default:
38 | // Do nothing
39 | break
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Examples/iOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @main
4 | class AppDelegate: UIResponder, UIApplicationDelegate {
5 | func application(
6 | _: UIApplication,
7 | didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?
8 | ) -> Bool {
9 | // Override point for customization after application launch.
10 | return true
11 | }
12 |
13 | // MARK: UISceneSession Lifecycle
14 |
15 | func application(
16 | _: UIApplication,
17 | configurationForConnecting connectingSceneSession: UISceneSession,
18 | options _: UIScene.ConnectionOptions
19 | ) -> UISceneConfiguration {
20 | // Called when a new scene session is being created.
21 | // Use this method to select a configuration to create the new scene with.
22 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
23 | }
24 |
25 | func application(_: UIApplication, didDiscardSceneSessions _: Set) {
26 | // Called when the user discards a scene session.
27 | // If any sessions were discarded while the application was not running, this will be called shortly after
28 | // application:didFinishLaunchingWithOptions.
29 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.6
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "ApiVideoLiveStream",
8 | platforms: [
9 | .macOS(.v11), .iOS(.v13)
10 | ],
11 | products: [
12 | // Products define the executables and libraries a package produces, and make them visible to other packages.
13 | .library(
14 | name: "ApiVideoLiveStream",
15 | targets: ["ApiVideoLiveStream"]
16 | )
17 | ],
18 | dependencies: [
19 | // Dependencies declare other packages that this package depends on.
20 | // .package(url: /* package url */, from: "1.0.0"),
21 | .package(url: "https://github.com/shogo4405/HaishinKit.swift", exact: "1.9.3")
22 | ],
23 | targets: [
24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
25 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
26 | .target(
27 | name: "ApiVideoLiveStream",
28 | dependencies: [
29 | .product(name: "HaishinKit", package: "HaishinKit.swift")
30 | ]
31 | ),
32 | .testTarget(
33 | name: "ApiVideoLiveStreamTests",
34 | dependencies: ["ApiVideoLiveStream"],
35 | path: "Tests/ApiVideoLiveStream"
36 | )
37 | ]
38 | )
39 |
--------------------------------------------------------------------------------
/Examples/iOS/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 |
--------------------------------------------------------------------------------
/Examples/iOS/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
4 | var window: UIWindow?
5 |
6 | func scene(_: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
7 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene
8 | // `scene`.
9 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
10 | // This delegate does not imply the connecting scene or session are new (see
11 | // `application:configurationForConnectingSceneSession` instead).
12 | }
13 |
14 | func sceneDidDisconnect(_: UIScene) {
15 | // Called as the scene is being released by the system.
16 | // This occurs shortly after the scene enters the background, or when its session is discarded.
17 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
18 | // The scene may re-connect later, as its session was not necessarily discarded (see
19 | // `application:didDiscardSceneSessions` instead).
20 | }
21 |
22 | func sceneDidBecomeActive(_: UIScene) {
23 | // Called when the scene has moved from an inactive state to an active state.
24 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
25 | }
26 |
27 | func sceneWillResignActive(_: UIScene) {
28 | // Called when the scene will move from an active state to an inactive state.
29 | // This may occur due to temporary interruptions (ex. an incoming phone call).
30 | }
31 |
32 | func sceneWillEnterForeground(_: UIScene) {
33 | // Called as the scene transitions from the background to the foreground.
34 | // Use this method to undo the changes made on entering the background.
35 | }
36 |
37 | func sceneDidEnterBackground(_: UIScene) {
38 | // Called as the scene transitions from the foreground to the background.
39 | // Use this method to save data, release shared resources, and store enough scene-specific state information
40 | // to restore the scene back to its current state.
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | - push
4 | jobs:
5 | find_schemes:
6 | name: Find xcode schemes
7 | runs-on: macos-latest
8 | outputs:
9 | schemes: ${{ steps.getSchemes.outputs.schemes}}
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v4
13 | - name: xcode version
14 | uses: maxim-lobanov/setup-xcode@v1
15 | with:
16 | xcode-version: latest-stable
17 | - name: Get schemes
18 | id: getSchemes
19 | run: |
20 | TARGETS=$(xcodebuild -list -json | tr -d "\n")
21 | SCHEMES=$(echo $TARGETS | ruby -e "require 'json'; puts JSON.generate(:scheme => JSON.parse(STDIN.gets)['project']['schemes'])")
22 | echo Found schemes: $SCHEMES
23 | echo "schemes=$SCHEMES" >> $GITHUB_OUTPUT
24 | swift_build:
25 | name: Build with swift
26 | runs-on: macos-latest
27 | steps:
28 | - name: Checkout
29 | uses: actions/checkout@v4
30 | - name: xcode version
31 | uses: maxim-lobanov/setup-xcode@v1
32 | with:
33 | xcode-version: latest-stable
34 | - name: Build Package with swift
35 | run: swift build
36 | xcode_build:
37 | name: Build with xcode
38 | needs: find_schemes
39 | runs-on: macos-latest
40 | strategy:
41 | matrix: ${{ fromJson(needs.find_schemes.outputs.schemes) }}
42 | steps:
43 | - name: Checkout
44 | uses: actions/checkout@v4
45 | - name: xcode version
46 | uses: maxim-lobanov/setup-xcode@v1
47 | with:
48 | xcode-version: latest-stable
49 | - name: Build ${{matrix.scheme}}
50 | run: |
51 | xcodebuild -downloadPlatform iOS
52 | xcodebuild clean build -project ApiVideoLiveStream.xcodeproj -scheme "${{matrix.scheme}}" -sdk iphoneos CODE_SIGNING_ALLOWED=NO
53 | verify:
54 | name: Verify package sanity
55 | runs-on: macos-latest
56 | steps:
57 | - name: Checkout
58 | uses: actions/checkout@v4
59 | - name: xcode version
60 | uses: maxim-lobanov/setup-xcode@v1
61 | with:
62 | xcode-version: latest-stable
63 | - name: Verify cocoapods
64 | run: |
65 | xcodebuild -downloadPlatform iOS
66 | pod lib lint --allow-warnings
67 | - name: Install swiftlint
68 | run: brew install swiftlint
69 | - name: Execute swiftlint
70 | run: swiftlint
71 |
--------------------------------------------------------------------------------
/Examples/iOS/SettingsManager.swift:
--------------------------------------------------------------------------------
1 | import ApiVideoLiveStream
2 | import Foundation
3 |
4 | enum SettingsManager {
5 | // MARK: Endpoint
6 |
7 | static var rtmpUrl: String {
8 | UserDefaults.standard.string(forKey: "RtmpUrl") ?? "rtmp://broadcast.api.video/s/"
9 | }
10 |
11 | static var streamKey: String {
12 | UserDefaults.standard.string(forKey: "StreamKey") ?? ""
13 | }
14 |
15 | // MARK: Video
16 |
17 | private static var videoBitrate: Int {
18 | UserDefaults.standard.integer(forKey: "VideoBitrateValue")
19 | }
20 |
21 | private static var resolution: Resolution {
22 | do {
23 | return try UserDefaults.standard.string(forKey: "Resolution")?.toResolution() ?? Resolution
24 | .RESOLUTION_16_9_720P
25 | } catch {
26 | fatalError("Can't get resolution from user defaults")
27 | }
28 | }
29 |
30 | private static var framerate: Float64 {
31 | UserDefaults.standard.double(forKey: "Framerate")
32 | }
33 |
34 | public static var videoConfig: VideoConfig {
35 | VideoConfig(bitrate: videoBitrate * 1_000, resolution: resolution, fps: framerate)
36 | }
37 |
38 | // MARK: Audio
39 |
40 | private static var audioBitrate: Int {
41 | UserDefaults.standard.integer(forKey: "AudioBitrate")
42 | }
43 |
44 | public static var audioConfig: AudioConfig {
45 | AudioConfig(bitrate: audioBitrate)
46 | }
47 | }
48 |
49 | // MARK: Convert functions
50 |
51 | extension String {
52 | func toResolution() throws -> Resolution {
53 | let resolutionArray = self.components(separatedBy: "x")
54 | guard let width = Int(resolutionArray[0]) else {
55 | throw ParameterError.Invalid("Width is invalid")
56 | }
57 | guard let height = Int(resolutionArray[1]) else {
58 | throw ParameterError.Invalid("Height is invalid")
59 | }
60 | let resolution = Resolution(rawValue: CGSize(width: width, height: height))
61 | if let resolution {
62 | return resolution
63 | } else {
64 | throw ParameterError.Invalid("Resolution is invalid for \(width)x\(height)")
65 | }
66 | }
67 | }
68 |
69 | extension Resolution {
70 | func toString() -> String {
71 | "\(rawValue.width)x\(rawValue.height)"
72 | }
73 | }
74 |
75 | public enum ParameterError: Error {
76 | case Invalid(String)
77 | }
78 |
--------------------------------------------------------------------------------
/Examples/iOS/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" : "58 1.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "87.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "80 1.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "120 2.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "120 1.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "180.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "20.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "40 1.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "29.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "58.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "40 2.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "80.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "76.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "152.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "167.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "1024.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | }
111 | ],
112 | "info" : {
113 | "author" : "xcode",
114 | "version" : 1
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - opening_brace
3 | - todo
4 | - notification_center_detachment
5 | - block_based_kvo
6 | - identifier_name
7 |
8 | opt_in_rules:
9 | - anyobject_protocol
10 | - attributes
11 | - closure_end_indentation
12 | - closure_spacing
13 | - contains_over_filter_count
14 | - contains_over_filter_is_empty
15 | - contains_over_range_nil_comparison
16 | - convenience_type
17 | - discouraged_object_literal
18 | - empty_collection_literal
19 | - empty_count
20 | - empty_string
21 | - empty_xctest_method
22 | - enum_case_associated_values_count
23 | - explicit_init
24 | - explicit_self
25 | - fallthrough
26 | - fatal_error_message
27 | - first_where
28 | - flatmap_over_map_reduce
29 | - force_unwrapping
30 | - function_default_parameter_at_end
31 | - identical_operands
32 | - implicit_return
33 | - joined_default_parameter
34 | - last_where
35 | - legacy_multiple
36 | - modifier_order
37 | - operator_usage_whitespace
38 | - optional_enum_case_matching
39 | - overridden_super_call
40 | - pattern_matching_keywords
41 | - prefer_zero_over_explicit_init
42 | - prohibited_super_call
43 | - redundant_nil_coalescing
44 | - redundant_type_annotation
45 | - single_test_class
46 | - sorted_first_last
47 | - static_operator
48 | - toggle_bool
49 | - untyped_error_in_catch
50 | - unneeded_parentheses_in_closure_argument
51 | - unused_declaration
52 | - unused_import
53 | - vertical_whitespace_between_cases
54 | - yoda_condition
55 |
56 | excluded:
57 | - Pods
58 | - "*/Pods"
59 | - R.generated.swift
60 | - .build
61 |
62 | # We exclude function (which is present by default)
63 | implicit_return:
64 | included:
65 | - closure
66 | - getter
67 |
68 | private_over_fileprivate:
69 | validate_extensions: true
70 | severity: error
71 |
72 | force_unwrapping:
73 | severity: error
74 |
75 | redundant_type_annotation: error
76 |
77 | type_name:
78 | min_length: 3
79 | max_length:
80 | warning: 60
81 | error: 60
82 | excluded:
83 | - Id
84 |
85 | identifier_name:
86 | allowed_symbols: "_"
87 | min_length: 3
88 | max_length:
89 | warning: 60
90 | error: 60
91 | excluded:
92 | - id
93 | - x
94 | - y
95 |
96 | line_length:
97 | warning: 120
98 | error: 120
99 | ignores_function_declarations: true
100 | ignores_comments: true
101 | ignores_urls: true
102 |
103 | function_body_length:
104 | warning: 300
105 | error: 300
106 |
107 | function_parameter_count:
108 | warning: 8
109 | error: 8
110 |
111 | type_body_length:
112 | warning: 350
113 | error: 450
114 |
115 | file_length:
116 | warning: 1000
117 | error: 1000
118 | ignore_comment_only_lines: true
119 |
120 | enum_case_associated_values_count:
121 | warning: 3
122 | error: 3
123 |
124 | reporter: "xcode"
125 |
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | ## File options
2 |
3 | --exclude Pods
4 | --swiftversion 5.3
5 |
6 |
7 | ## Enabled rules
8 |
9 | --enable andOperator
10 | --enable anyObjectProtocol
11 | --enable blankLinesAroundMark
12 | --enable blankLinesBetweenScopes
13 | --enable braces
14 | --enable consecutiveBlankLines
15 | --enable consecutiveSpaces
16 | --enable duplicateimports
17 | --enable elseOnSameLine
18 | --enable emptybraces
19 | --enable enumnamespaces
20 | --enable extensionaccesscontrol
21 | --enable fileHeader
22 | --enable indent
23 | --enable initcoderunavailable
24 | --enable isempty
25 | --enable leadingdelimiters
26 | --enable linebreakAtEndOfFile
27 | --enable marktypes
28 | --enable numberFormatting
29 | --enable preferkeypath
30 | --enable redundantbackticks
31 | --enable redundantextensionacl
32 | --enable redundantfileprivate
33 | --enable redundantlet
34 | --enable redundantnilinit
35 | --enable redundantobjc
36 | --enable redundantparens
37 | --enable redundantpattern
38 | --enable redundantrawvalues
39 | --enable redundantself
40 | --enable redundanttype
41 | --enable redundantvoidreturntype
42 | --enable semicolons
43 | --enable sortedimports
44 | --enable spacearoundbraces
45 | --enable spacearoundbrackets
46 | --enable spacearoundcomments
47 | --enable spacearoundgenerics
48 | --enable spacearoundoperators
49 | --enable spacearoundparens
50 | --enable spaceinsidebraces
51 | --enable spaceinsidebrackets
52 | --enable spaceinsidecomments
53 | --enable spaceinsidegenerics
54 | --enable spaceinsideparens
55 | --enable trailingclosures
56 | --enable trailingCommas
57 | --enable typesugar
58 | --enable wrapArguments
59 | --enable wrapAttributes
60 | --enable wrapenumcases
61 | --enable wrapmultilinestatementbraces
62 | --enable yodaconditions
63 |
64 |
65 | ## Configuration of specific rules
66 |
67 | ### Number formatting
68 | --decimalgrouping 3
69 | --hexgrouping 4,8
70 | --binarygrouping 4
71 |
72 | ### MARK
73 | --marktypes never
74 |
75 | ### Wrap
76 | --wraparguments before-first
77 | --wrapparameters before-first
78 | --wrapcollections before-first
79 |
80 | ### Attributes
81 | --funcattributes prev-line
82 | --typeattributes prev-line
83 | --varattributes same-line
84 |
85 | ### Else
86 | --elseposition same-line
87 | --guardelse same-line
88 |
89 | ### Others
90 | --commas inline
91 | --ifdef noindent
92 | --indent 4
93 | --linebreaks lf ##default
94 | --maxwidth 120
95 | --patternlet hoist ##default
96 | --self insert
97 | --semicolons never
98 | --stripunusedargs always
99 | --trimwhitespace always
100 | --voidtype void
101 |
102 |
103 | ## Disabled rules
104 |
105 | ### SwiftFormat also removes from funcs, so let's let SwiftLint takes care of it.
106 | --disable redundantReturn
107 |
108 | ### SwiftFormat also removes from end of scope, which is against our empty line guidelines.
109 | --disable blankLinesAtEndOfScope
110 |
111 | ### SwiftFormat also removes from start of scope, which is against our empty line guidelines.
112 | --disable blankLinesAtStartOfScope
--------------------------------------------------------------------------------
/Sources/ApiVideoLiveStream/models/Resolution.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Resolution of the video
4 | public enum Resolution {
5 | // 16:9
6 | /// 426x240
7 | case RESOLUTION_16_9_240P
8 | /// 640x360: nHD
9 | case RESOLUTION_16_9_360P
10 | /// 854x480: FWVGA
11 | case RESOLUTION_16_9_480P
12 | /// 1280x720: WXGA
13 | case RESOLUTION_16_9_720P
14 | /// 1920x1080: FHD
15 | case RESOLUTION_16_9_1080P
16 |
17 | // 4:3
18 | /// 320x240: QVGA
19 | case RESOLUTION_4_3_240P
20 | /// 640x480: VGA
21 | case RESOLUTION_4_3_480P
22 | /// 800x600: SVGA
23 | case RESOLUTION_4_3_600P
24 | /// 1024x768: XGA
25 | case RESOLUTION_4_3_768P
26 | /// 1440x1080
27 | case RESOLUTION_4_3_1080P
28 | }
29 |
30 | // MARK: RawRepresentable
31 |
32 | extension Resolution: RawRepresentable {
33 | public typealias RawValue = CGSize
34 |
35 | public init?(rawValue: RawValue) {
36 | let widerWidth = max(rawValue.width, rawValue.height)
37 | let widerHeight = min(rawValue.width, rawValue.height)
38 | switch (widerWidth, widerHeight) {
39 | case (426, 240):
40 | self = .RESOLUTION_16_9_240P
41 |
42 | case (640, 360):
43 | self = .RESOLUTION_16_9_360P
44 |
45 | case (854, 480):
46 | self = .RESOLUTION_16_9_480P
47 |
48 | case (1_280, 720):
49 | self = .RESOLUTION_16_9_720P
50 |
51 | case (1_920, 1_080):
52 | self = .RESOLUTION_16_9_1080P
53 |
54 | case (320, 240):
55 | self = .RESOLUTION_4_3_240P
56 |
57 | case (640, 480):
58 | self = .RESOLUTION_4_3_480P
59 |
60 | case (800, 600):
61 | self = .RESOLUTION_4_3_600P
62 |
63 | case (1_024, 768):
64 | self = .RESOLUTION_4_3_768P
65 |
66 | case (1_440, 1_080):
67 | self = .RESOLUTION_4_3_1080P
68 |
69 | default: return nil
70 | }
71 | }
72 |
73 | public var rawValue: RawValue {
74 | switch self {
75 | case .RESOLUTION_16_9_240P:
76 | return CGSize(width: 426, height: 240)
77 |
78 | case .RESOLUTION_16_9_360P:
79 | return CGSize(width: 640, height: 360)
80 |
81 | case .RESOLUTION_16_9_480P:
82 | return CGSize(width: 854, height: 480)
83 |
84 | case .RESOLUTION_16_9_720P:
85 | return CGSize(width: 1_280, height: 720)
86 |
87 | case .RESOLUTION_16_9_1080P:
88 | return CGSize(width: 1_920, height: 1_080)
89 |
90 | case .RESOLUTION_4_3_240P:
91 | return CGSize(width: 320, height: 240)
92 |
93 | case .RESOLUTION_4_3_480P:
94 | return CGSize(width: 640, height: 480)
95 |
96 | case .RESOLUTION_4_3_600P:
97 | return CGSize(width: 800, height: 600)
98 |
99 | case .RESOLUTION_4_3_768P:
100 | return CGSize(width: 1_024, height: 768)
101 |
102 | case .RESOLUTION_4_3_1080P:
103 | return CGSize(width: 1_440, height: 1_080)
104 | }
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All changes to this project will be documented in this file.
3 |
4 | ## [1.4.6] - 2025-01-29
5 | - Fix `isMuted` property
6 |
7 | ## [1.4.5] - 2024-10-04
8 | - Add an API to get the zoom ratio range
9 | - Fix zoom ratio max value
10 |
11 | ## [1.4.4] - 2024-09-11
12 | - Upgrade HaishinKit to 1.9.3
13 |
14 | ## [1.4.3] - 2024-06-10
15 | - Add publish name to the stream to trigger `FCUnpublish`
16 |
17 | ## [1.4.2] - 2024-06-07
18 | - Fix settings keys on iOS example
19 | - Upgrade HaishinKit to 1.8.1
20 | - Create a global pbxproj
21 |
22 | ## [1.4.1] - 2024-02-12
23 | - Fix regression on device orientation
24 |
25 | ## [1.4.0] - 2024-02-09
26 | - Improve the way Resolution are handled to avoid misusage.
27 |
28 | ## [1.3.6] - 2024-02-08
29 | - Improve RTMP error handling
30 | - Avoid disptaching `connect` because it is slower
31 | - Fixes on zoom ratio
32 | - Upgrade HaishinKit to 1.7.3
33 | - Example: reduce default bitrate to improve experience
34 |
35 | ## [1.3.5] - 2023-06-21
36 | - Fix detach camera
37 | - Upgrade HaishinKit to 1.5.2
38 |
39 | ## [1.3.4] - 2023-03-02
40 | - Fix front camera mirroring
41 | - Upgrade HaishinKit to 1.4.3
42 |
43 | ## [1.3.3] - 2023-01-19
44 | - Fix streaming orientation when application is opened in portrait and device is turned to landscape
45 | - Upgrade HaishinKit to 1.4.2
46 |
47 | ## [1.3.2] - 2023-01-11
48 | - Fix `lastCamera` when camera is set with `cameraPosition`
49 | - Improve `startStreaming` errors
50 |
51 | ## [1.3.1] - 2023-01-09
52 | - Reduce startup time by fixing synchronization issues
53 | - Fix `isMuted` value. The property was inverted
54 |
55 | ## [1.3.0] - 2023-01-06
56 | - Add a camera parameter in the ApiVideoLiveStream constructors
57 | - Add an API to set the duration between two key frames
58 | - Introducing the new `camera` API. Previous `camera` has been renamed to `cameraPosition`
59 | - Add swift PM support
60 | - Upgrade HaishinKit to 1.4.1
61 | - Example: refactor and clean the UIKit example
62 |
63 | ## [1.2.2] - 2022-09-29
64 | - Only register event listener in constructor to avoid multiple callback called
65 |
66 | ## [1.2.1] - 2022-09-29
67 | - fix(lib): use RtmpStream lock instead of a custom lock
68 |
69 | ## [1.2.0] - 2022-09-29
70 | - Add startPreview/stopPreview API
71 | - Try to synchronize `startStreaming` abd video configuration
72 | - Force HaishinKit default resolution to 720p
73 | - Add constructor for HaishinKit NetStreamDrawable
74 | - Upgrade to HaishinKit 1.3.0
75 | - Release workflow is triggered on release published (instead of created)
76 |
77 | ## [1.1.0] - 2022-08-18
78 | - Adds API to set zoom ratio
79 |
80 | ## [1.0.0] - 2022-08-15
81 | - Allows nil initialVideoConfig and initialAudioConfig
82 | - Fix landscape orientation
83 | - Return onDisconnect when user call `stopStreaming`
84 |
85 | ## [0.2.1] - 2022-04-22
86 | - Stop streaming on didEnterBackgroundNotification
87 |
88 | ## [0.2.0] - 2022-04-13
89 | - Add default parameters for audio and video config
90 | - Fix continous auto focus
91 | - Call `onDisconnect` when connection is closed
92 | - Rename `Resolution` to `Size`
93 | - Few fixes on example
94 |
95 | ## [0.1.0] - 2022-01-27
96 | - Large refactor and clean
97 | - Add a sample application
98 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Swift ###
3 | # Xcode
4 | #
5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
6 |
7 | ## User settings
8 | xcuserdata/
9 |
10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
11 | *.xcscmblueprint
12 | *.xccheckout
13 |
14 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
15 | build/
16 | DerivedData/
17 | *.moved-aside
18 | *.pbxuser
19 | !default.pbxuser
20 | *.mode1v3
21 | !default.mode1v3
22 | *.mode2v3
23 | !default.mode2v3
24 | *.perspectivev3
25 | !default.perspectivev3
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 |
30 | ## App packaging
31 | *.ipa
32 | *.dSYM.zip
33 | *.dSYM
34 |
35 | ## Playgrounds
36 | timeline.xctimeline
37 | playground.xcworkspace
38 |
39 | # Swift Package Manager
40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
41 | # Packages/
42 | # Package.pins
43 | # Package.resolved
44 | # *.xcodeproj
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | # We recommend against adding the Pods directory to your .gitignore. However
53 | # you should judge for yourself, the pros and cons are mentioned at:
54 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
55 | Pods/
56 | # Add this line if you want to avoid checking in source code from the Xcode workspace
57 | # *.xcworkspace
58 |
59 | # Carthage
60 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
61 | # Carthage/Checkouts
62 |
63 | Carthage/Build/
64 |
65 | # Add this lines if you are using Accio dependency management (Deprecated since Xcode 12)
66 | # Dependencies/
67 | # .accio/
68 |
69 | # fastlane
70 | # It is recommended to not store the screenshots in the git repo.
71 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
72 | # For more information about the recommended setup visit:
73 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
74 |
75 | fastlane/report.xml
76 | fastlane/Preview.html
77 | fastlane/screenshots/**/*.png
78 | fastlane/test_output
79 |
80 | # Code Injection
81 | # After new code Injection tools there's a generated folder /iOSInjectionProject
82 | # https://github.com/johnno1962/injectionforxcode
83 |
84 | iOSInjectionProject/
85 |
86 | ### Xcode ###
87 | # Xcode
88 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
89 |
90 | ## Gcc Patch
91 | /*.gcno
92 |
93 | ### Xcode Patch ###
94 | *.xcodeproj/*
95 | !*.xcodeproj/project.pbxproj
96 | !*.xcodeproj/xcshareddata/
97 | !*.xcworkspace/contents.xcworkspacedata
98 | **/xcshareddata/WorkspaceSettings.xcsettings
99 |
100 | .DS_Store
101 | /.build
102 | /Packages
103 | /*.xcodeproj
104 | .swiftpm/config/registries.json
105 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
106 | .netrc
107 |
108 | # Android studio 3.1+ serialized cache file or AppCode
109 | .idea/
110 |
111 |
112 |
--------------------------------------------------------------------------------
/Sources/ApiVideoLiveStream/models/Configuration.swift:
--------------------------------------------------------------------------------
1 | import CoreGraphics
2 | import Foundation
3 | import Network
4 |
5 | public struct AudioConfig {
6 | public let bitrate: Int
7 |
8 | /// Creates an audio configuration object
9 | /// - Parameters:
10 | /// - bitrate: The audio bitrate in bits per second
11 | public init(bitrate: Int = 128_000) {
12 | self.bitrate = bitrate
13 | }
14 | }
15 |
16 | public struct VideoConfig {
17 | public let bitrate: Int
18 | public let resolution: CGSize
19 | public let fps: Float64
20 | public let gopDuration: TimeInterval
21 |
22 | /// Creates a video configuration object with explicit video bitrate and CGSize resolution
23 | /// - Parameters:
24 | /// - bitrate: The video bitrate in bits per second
25 | /// - resolution: The video resolution
26 | /// - fps: The video framerate
27 | /// - gopDuration: The time interval between two key frames
28 | public init(
29 | bitrate: Int,
30 | resolution: CGSize = CGSize(width: 1_280, height: 720),
31 | fps: Float64 = 30,
32 | gopDuration: TimeInterval = 1.0
33 | ) {
34 | self.bitrate = bitrate
35 | self.resolution = resolution
36 | self.fps = fps
37 | self.gopDuration = gopDuration
38 | }
39 |
40 | /// Creates a video configuration object with default video bitrate and CGSize resolution
41 | /// - Parameters:
42 | /// - resolution: The video resolution
43 | /// - fps: The video framerate
44 | /// - gopDuration: The time interval between two key frames
45 | public init(
46 | resolution: CGSize = CGSize(width: 1_280, height: 720),
47 | fps: Float64 = 30,
48 | gopDuration: TimeInterval = 1.0
49 | ) {
50 | self.bitrate = VideoConfig.getDefaultBitrate(resolution)
51 | self.resolution = resolution
52 | self.fps = fps
53 | self.gopDuration = gopDuration
54 | }
55 |
56 | /// Creates a video configuration object with default video bitrate
57 | /// - Parameters:
58 | /// - resolution: The video resolution
59 | /// - fps: The video framerate
60 | /// - gopDuration: The time interval between two key frames
61 | public init(
62 | resolution: Resolution,
63 | fps: Float64 = 30,
64 | gopDuration: TimeInterval = 1.0
65 | ) {
66 | self.init(resolution: resolution.rawValue, fps: fps, gopDuration: gopDuration)
67 | }
68 |
69 | /// Creates a video configuration object with explicit video bitrate
70 | /// - Parameters:
71 | /// - bitrate: The video bitrate in bits per second
72 | /// - resolution: The video resolution
73 | /// - fps: The video framerate
74 | /// - gopDuration: The time interval between two key frames
75 | public init(
76 | bitrate: Int,
77 | resolution: Resolution,
78 | fps: Float64 = 30,
79 | gopDuration: TimeInterval = 1.0
80 | ) {
81 | self.init(bitrate: bitrate, resolution: resolution.rawValue, fps: fps, gopDuration: gopDuration)
82 | }
83 |
84 | static func getDefaultBitrate(_ size: CGSize) -> Int {
85 | let numOfPixels = size.width * size.height
86 | switch numOfPixels {
87 | case 0 ... 102_240: return 800_000 // for 4/3 and 16/9 240p
88 | case 102_241 ... 230_400: return 1_000_000 // for 16/9 360p
89 | case 230_401 ... 409_920: return 1_300_000 // for 4/3 and 16/9 480p
90 | case 409_921 ... 921_600: return 2_000_000 // for 4/3 600p, 4/3 768p and 16/9 720p
91 | default: return 3_000_000 // for 16/9 1080p
92 | }
93 | }
94 | }
95 |
96 | enum ConfigurationError: Error {
97 | case invalidParameter(String)
98 | }
99 |
--------------------------------------------------------------------------------
/Examples/iOS/Settings.bundle/Root.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 | Settings
7 | StringsTable
8 | Root
9 | PreferenceSpecifiers
10 |
11 |
12 | Type
13 | PSGroupSpecifier
14 | Title
15 | Endpoint
16 |
17 |
18 | Type
19 | PSTextFieldSpecifier
20 | Title
21 | RTMP endpoint
22 | DefaultValue
23 | rtmp://broadcast.api.video/s/
24 | Key
25 | RtmpUrl
26 |
27 |
28 | Type
29 | PSTextFieldSpecifier
30 | Title
31 | Stream key
32 | DefaultValue
33 |
34 | Key
35 | StreamKey
36 |
37 |
38 | Type
39 | PSGroupSpecifier
40 | Title
41 | Video
42 |
43 |
44 | Type
45 | PSMultiValueSpecifier
46 | Title
47 | Resolution
48 | Key
49 | Resolution
50 | DefaultValue
51 | 854x480
52 | Values
53 |
54 | 426x240
55 | 640x360
56 | 854x480
57 | 1280x720
58 | 1920x1080
59 |
60 | Titles
61 |
62 | 426x240
63 | 640x360
64 | 854x480
65 | 1280x720
66 | 1920x1080
67 |
68 |
69 |
70 | Type
71 | PSMultiValueSpecifier
72 | Title
73 | Framerate
74 | Key
75 | Framerate
76 | DefaultValue
77 | 30
78 | Values
79 |
80 | 24
81 | 30
82 | 60
83 |
84 | Titles
85 |
86 | 24
87 | 30
88 | 60
89 |
90 |
91 |
92 | Type
93 | PSGroupSpecifier
94 | Title
95 | Bitrate
96 |
97 |
98 | Type
99 | PSTitleValueSpecifier
100 | Title
101 | Bitrate
102 | DefaultValue
103 | 1000
104 | Key
105 | VideoBitrateValue
106 |
107 |
108 | Type
109 | PSSliderSpecifier
110 | Key
111 | VideoBitrateSlider
112 | DefaultValue
113 | 1000
114 | MinimumValue
115 | 1000
116 | MaximumValue
117 | 10000
118 |
119 |
120 | Type
121 | PSGroupSpecifier
122 | Title
123 | Audio
124 |
125 |
126 | Type
127 | PSMultiValueSpecifier
128 | Title
129 | Bitrate
130 | Key
131 | AudioBitrate
132 | DefaultValue
133 | 128000
134 | Values
135 |
136 | 24000
137 | 64000
138 | 128000
139 | 192000
140 |
141 | Titles
142 |
143 | 24 Kbps
144 | 64 Kbps
145 | 128 Kbps
146 | 192 Kbps
147 |
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://twitter.com/intent/follow?screen_name=api_video) [](https://github.com/apivideo/api.video-swift-live-stream) [](https://community.api.video)
3 | 
4 | Swift RTMP live stream client for iOS
5 |
6 | [api.video](https://api.video) is the video infrastructure for product builders. Lightning fast video APIs for integrating, scaling, and managing on-demand & low latency live streaming features in your app.
7 |
8 | ## Table of contents
9 |
10 | - [Table of contents](#table-of-contents)
11 | - [Project description](#project-description)
12 | - [Getting started](#getting-started)
13 | - [Installation](#installation)
14 | - [With Cocoapods](#with-cocoapods)
15 | - [Permissions](#permissions)
16 | - [Code sample](#code-sample)
17 | - [Plugins](#plugins)
18 | - [FAQ](#faq)
19 |
20 |
21 |
33 | ## Project description
34 |
35 | This module is an easy way to broadcast RTMP live stream to [api.video](https://api.video) platform
36 |
37 | ## Getting started
38 |
39 | ### Installation
40 |
41 | #### Swift Package Manager
42 |
43 | In the Project Navigator select your own project. Then select the project in the Project section and click on the Package Dependencies tab. Click on the "+" button at the bottom. Paste the below url on the search bar on the top right. Finaly click on "Add package" button.
44 |
45 | ```
46 | https://github.com/api.video-swift-live-stream
47 | ```
48 | Or add this in your Package.swift
49 | ```
50 | dependencies: [
51 | .package(url: "https://github.com/api.video-swift-live-stream.git", from: "1.4.6"),
52 | ],
53 | ```
54 |
55 | #### Cocoapods
56 |
57 | 1. Add the following entry to your Podfile:
58 | ```swift
59 | pod 'ApiVideoLiveStream', '1.4.6'
60 | ```
61 | 2. Then run `pod install`
62 | 3. Don’t forget to import `ApiVideoLiveStream` in every file you’d like to use api.video live stream library
63 |
64 | #### Carthage
65 | ```
66 | github "apivideo/ApiVideoLiveStream.swift" ~> 1.4.6
67 | ```
68 |
69 | ### Permissions
70 | To be able to broadcast, you must update Info.plist with a usage description for camera and microphone
71 |
72 | ```xml
73 | ...
74 | NSCameraUsageDescription
75 | Your own description of the purpose
76 | NSMicrophoneUsageDescription
77 | Your own description of the purpose
78 | ...
79 | ```
80 |
81 | ### Code sample
82 | 1. In ViewController.swift import the library
83 | ```swift
84 | import ApiVideoLiveStream
85 | ```
86 | 2. Create a `ApiVideoLiveStream` object with your default audio and video configuration
87 | ```swift
88 | class ViewController: UIViewController {
89 | var liveStream: ApiVideoLiveStream?
90 | @IBOutlet var viewCamera: UIView!
91 | override func viewDidLoad() {
92 | super.viewDidLoad()
93 | let audioConfig = AudioConfig(bitrate: 32 * 1000)
94 | let videoConfig = VideoConfig(bitrate: 2 * 1024 * 1024, resolution: Resolutions.RESOLUTION_720, fps: 30)
95 | do {
96 | liveStream = try ApiVideoLiveStream(initialAudioConfig: audioConfig, initialVideoConfig: videoConfig, preview: preview)
97 | } catch {
98 | print (error)
99 | }
100 | }
101 | }
102 | ```
103 | 3. Start your live stream with `startStreaming`
104 | ```swift
105 | liveStream?.startStreaming(streamKey: "YOUR_STREAM_KEY")
106 | ```
107 | Alternatively, you can use `startStreaming` `url` parameter to set the URL of your RTMP server.
108 |
109 | ## Plugins
110 |
111 | api.video sdk is using external library
112 |
113 | | Plugin | README |
114 | | ------ | ------ |
115 | | HaishinKit | [https://github.com/shogo4405/HaishinKit.swift][HaishinKit] |
116 |
117 | ## FAQ
118 | If you have any questions, ask us here: https://community.api.video .
119 | Or use [Issues].
120 |
121 | Also feel free to test our [Sample app].
122 |
123 | [//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)
124 |
125 | [Issues]:
126 | [HaishinKit]:
127 | [Sample app]:
128 |
129 |
130 |
--------------------------------------------------------------------------------
/Examples/iOS/Base.lproj/Main.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 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/Examples/iOS/ViewControllers/MainViewController.swift:
--------------------------------------------------------------------------------
1 | import ApiVideoLiveStream
2 | import AVKit
3 | import InAppSettingsKit
4 | import UIKit
5 |
6 | class MainViewController: UIViewController {
7 | @IBOutlet private var preview: UIView!
8 | private lazy var liveStream: ApiVideoLiveStream = {
9 | var liveStream: ApiVideoLiveStream
10 | do {
11 | // Create liveStream object
12 | let liveStream = try ApiVideoLiveStream(
13 | preview: preview,
14 | initialAudioConfig: SettingsManager.audioConfig,
15 | initialVideoConfig: SettingsManager.videoConfig
16 | )
17 |
18 | return liveStream
19 | } catch {
20 | fatalError("Can't create liveStream: \(error)")
21 | }
22 | }()
23 |
24 | private let front = UIImage(systemName: "camera.rotate.fill")
25 | private let back = UIImage(systemName: "camera.rotate")
26 | private let mute = UIImage(systemName: "mic.slash.fill")
27 | private let unMute = UIImage(systemName: "mic.fill")
28 | private let parameter = UIImage(systemName: "ellipsis")
29 |
30 | private lazy var zoomGesture: UIPinchGestureRecognizer = .init(target: self, action: #selector(zoom(sender:)))
31 | private let pinchZoomMultiplier: CGFloat = 2.2
32 |
33 | private let muteButton: UIButton = {
34 | let muteButton = UIButton()
35 | muteButton.setTitleColor(.orange, for: .normal)
36 | return muteButton
37 | }()
38 |
39 | private let streamingButton: UIButton = {
40 | let start = UIButton()
41 | start.setTitle("Start", for: .normal)
42 | start.setTitleColor(.orange, for: .normal)
43 | return start
44 | }()
45 |
46 | private let switchButton: UIButton = {
47 | let switchBtn = UIButton()
48 | switchBtn.setTitleColor(.orange, for: .normal)
49 | return switchBtn
50 | }()
51 |
52 | private let parameterButton: UIButton = {
53 | let paramBtn = UIButton()
54 | paramBtn.setTitleColor(.orange, for: .normal)
55 | return paramBtn
56 | }()
57 |
58 | private func callAlert(_ message: String, title: String = "Error", action: @escaping () -> Void = {}) {
59 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
60 | let okAction = UIAlertAction(title: "OK", style: .default) { _ in
61 | action()
62 | }
63 |
64 | alert.addAction(okAction)
65 | DispatchQueue.main.async {
66 | self.present(alert, animated: true, completion: nil)
67 | }
68 | }
69 |
70 | override func viewDidLoad() {
71 | super.viewDidLoad()
72 |
73 | // Register default NSUserDefaults
74 | if let defaultDict = SettingsViewController().settingsReader?.gatherDefaultsLimited(toEditableFields: true) {
75 | UserDefaults.standard.register(defaults: defaultDict)
76 | }
77 |
78 | // Call to explicitly create liveStream
79 | self.liveStream.delegate = self
80 |
81 | view.addSubview(self.muteButton)
82 | view.addSubview(self.streamingButton)
83 | view.addSubview(self.switchButton)
84 | view.addSubview(self.parameterButton)
85 | self.muteButton.setImage(self.unMute, for: .normal)
86 | self.switchButton.setImage(self.back, for: .normal)
87 | self.parameterButton.setImage(self.parameter, for: .normal)
88 |
89 | self.switchButton.addTarget(self, action: #selector(self.toggleSwitch), for: .touchUpInside)
90 | self.muteButton.addTarget(self, action: #selector(self.toggleMute), for: .touchUpInside)
91 | self.streamingButton.addTarget(self, action: #selector(self.toggleStreaming), for: .touchUpInside)
92 | self.parameterButton.addTarget(self, action: #selector(self.navigateToParam), for: .touchUpInside)
93 |
94 | self.preview.addGestureRecognizer(self.zoomGesture)
95 |
96 | self.constraints()
97 | }
98 |
99 | func constraints() {
100 | self.muteButton.translatesAutoresizingMaskIntoConstraints = false
101 | self.streamingButton.translatesAutoresizingMaskIntoConstraints = false
102 | self.switchButton.translatesAutoresizingMaskIntoConstraints = false
103 | self.parameterButton.translatesAutoresizingMaskIntoConstraints = false
104 |
105 | self.muteButton.widthAnchor.constraint(equalToConstant: 70).isActive = true
106 | self.muteButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
107 | self.streamingButton.widthAnchor.constraint(equalToConstant: 70).isActive = true
108 | self.streamingButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
109 | self.switchButton.widthAnchor.constraint(equalToConstant: 80).isActive = true
110 | self.switchButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
111 | self.parameterButton.widthAnchor.constraint(equalToConstant: 40).isActive = true
112 | self.parameterButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
113 |
114 | self.parameterButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 70).isActive = true
115 | self.parameterButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
116 |
117 | self.streamingButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
118 | self.streamingButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40).isActive = true
119 |
120 | self.muteButton.centerYAnchor.constraint(equalTo: self.streamingButton.centerYAnchor).isActive = true
121 | self.switchButton.centerYAnchor.constraint(equalTo: self.streamingButton.centerYAnchor).isActive = true
122 |
123 | self.muteButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
124 | self.switchButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
125 | }
126 |
127 | private func resetStartButton() {
128 | DispatchQueue.main.async {
129 | self.streamingButton.setTitle("Start", for: [])
130 | self.streamingButton.isSelected = false
131 | }
132 | }
133 |
134 | @objc
135 | func toggleStreaming() {
136 | if self.streamingButton.isSelected {
137 | UIApplication.shared.isIdleTimerDisabled = false
138 | self.liveStream.stopStreaming()
139 | self.resetStartButton()
140 | } else {
141 | UIApplication.shared.isIdleTimerDisabled = true
142 | do {
143 | if SettingsManager.streamKey.isEmpty {
144 | self.callAlert("The stream key is not set. Please set it in Settings.")
145 | return
146 | }
147 | if SettingsManager.rtmpUrl.isEmpty {
148 | self.callAlert("The stream key is not set. Please set it in Settings.")
149 | return
150 | }
151 |
152 | try self.liveStream.startStreaming(streamKey: SettingsManager.streamKey, url: SettingsManager.rtmpUrl)
153 |
154 | self.streamingButton.setTitle("Stop", for: [])
155 | self.streamingButton.isSelected = true
156 | } catch {
157 | self.callAlert("Failed to start streaming: \(error)")
158 | }
159 | }
160 | }
161 |
162 | @objc
163 | func toggleSwitch() {
164 | if self.liveStream.cameraPosition == .front {
165 | self.liveStream.cameraPosition = .back
166 | self.switchButton.setImage(self.back, for: .normal)
167 | } else if self.liveStream.cameraPosition == .back {
168 | self.liveStream.cameraPosition = .front
169 | self.switchButton.setImage(self.front, for: .normal)
170 | }
171 | }
172 |
173 | @objc
174 | func toggleMute() {
175 | self.liveStream.isMuted.toggle()
176 | if self.liveStream.isMuted {
177 | self.muteButton.setImage(self.unMute, for: .normal)
178 | } else {
179 | self.muteButton.setImage(self.mute, for: .normal)
180 | }
181 | }
182 |
183 | @objc
184 | func navigateToParam() {
185 | performSegue(withIdentifier: "paramSegue", sender: self)
186 | }
187 |
188 | private func updateConfig() {
189 | self.liveStream.audioConfig = SettingsManager.audioConfig
190 | self.liveStream.videoConfig = SettingsManager.videoConfig
191 | }
192 |
193 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
194 | super.prepare(for: segue, sender: sender)
195 | if segue.identifier == "paramSegue" {
196 | guard let navigationController = segue.destination as? UINavigationController else {
197 | print("Can't get navigation controller")
198 | return
199 | }
200 | let settingsViewController = SettingsViewController()
201 | settingsViewController.delegate = self
202 | navigationController.pushViewController(settingsViewController, animated: true)
203 | }
204 | }
205 |
206 | @objc
207 | private func zoom(sender: UIPinchGestureRecognizer) {
208 | if sender.state == .changed {
209 | self.liveStream.zoomRatio += (sender.scale - 1) * self.pinchZoomMultiplier
210 | sender.scale = 1
211 | }
212 | }
213 | }
214 |
215 | // MARK: IASKSettingsDelegate
216 |
217 | extension MainViewController: IASKSettingsDelegate {
218 | func settingsViewControllerDidEnd(_ settingsViewController: IASKAppSettingsViewController) {
219 | settingsViewController.dismiss(animated: true, completion: nil)
220 | self.updateConfig()
221 | }
222 | }
223 |
224 | // MARK: ApiVideoLiveStreamDelegate
225 |
226 | extension MainViewController: ApiVideoLiveStreamDelegate {
227 | /// Called when the connection to the rtmp server is successful
228 | func connectionSuccess() {
229 | print("onConnectionSuccess")
230 | }
231 |
232 | /// Called when the connection to the rtmp server failed
233 | func connectionFailed(_: String) {
234 | self.callAlert("Failed to connect to the server")
235 | self.resetStartButton()
236 | }
237 |
238 | /// Called when the connection to the rtmp server is closed
239 | func disconnection() {
240 | print("onDisconnect")
241 | self.resetStartButton()
242 | }
243 |
244 | /// Called if an error happened during the audio configuration
245 | func audioError(_ error: Error) {
246 | self.callAlert("Audio error: \(error.localizedDescription)")
247 | }
248 |
249 | /// Called if an error happened during the video configuration
250 | func videoError(_ error: Error) {
251 | self.callAlert("Video error: \(error.localizedDescription)")
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to api.video
2 |
3 | :movie_camera::+1::tada: Thank you for taking the time to contribute and participate in the implementation of a Video First World! :tada::+1::movie_camera:
4 |
5 | The following is a set of guidelines for contributing to api.video and its packages, which are hosted in the [api.video Organization](https://github.com/apivideo) on GitHub.
6 |
7 | #### Table of contents
8 |
9 | - [Contributing to api.video](#contributing-to-apivideo)
10 | - [Table of contents](#table-of-contents)
11 | - [Code of conduct](#code-of-conduct)
12 | - [I just have a question!](#i-just-have-a-question)
13 | - [How can I contribute?](#how-can-i-contribute)
14 | - [Reporting bugs](#reporting-bugs)
15 | - [Before submitting a bug report](#before-submitting-a-bug-report)
16 | - [How do I submit a (good) bug report?](#how-do-i-submit-a-good-bug-report)
17 | - [Suggesting enhancements](#suggesting-enhancements)
18 | - [How do I submit a (good) enhancement suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
19 | - [Pull requests](#pull-requests)
20 | - [Style guides](#style-guides)
21 | - [Git commit messages](#git-commit-messages)
22 | - [Documentation style guide](#documentation-style-guide)
23 | - [Additional notes](#additional-notes)
24 | - [Issue and pull request labels](#issue-and-pull-request-labels)
25 | - [Type of issue and issue state](#type-of-issue-and-issue-state)
26 | - [Topic categories](#topic-categories)
27 | - [Pull request labels](#pull-request-labels)
28 |
29 | ## Code of conduct
30 |
31 | This project and everyone participating in it is governed by the [api.video Code of Conduct](https://github.com/apivideo/.github/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [help@api.video](mailto:help@api.video).
32 |
33 | ## I just have a question!
34 |
35 | > **Note:** [Please don't file an issue to ask a question.] You'll get faster results by using the resources below.
36 |
37 | We have an official message board with a detailed FAQ and where the community chimes in with helpful advice if you have questions.
38 |
39 | * [The official api.video's Community](https://community.api.video/)
40 | * [api.video FAQ](https://community.api.video/c/faq/)
41 |
42 |
43 | ## How can I contribute?
44 |
45 | ### Reporting bugs
46 |
47 | This section guides you through submitting a bug report for api.video. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer:, and find related reports :mag_right:.
48 |
49 | Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/bug_report.yml), the information it asks for helps us resolve issues faster.
50 |
51 | > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
52 |
53 | #### Before submitting a bug report
54 |
55 | * **Check the [The official api.video's Community](https://community.api.video/)** for a list of common questions and problems.
56 | * **Determine which repository the problem should be reported in**.
57 | * **Perform a [cursory search](https://github.com/search?q=is%3Aissue+user%3Aapivideo)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
58 |
59 | #### How do I submit a (good) bug report?
60 |
61 | Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined which repository your bug is related to, create an issue on that repository and provide the following information by filling in [the template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/bug_report.yml).
62 |
63 | Explain the problem and include additional details to help maintainers reproduce the problem:
64 |
65 | * **Use a clear and descriptive title** for the issue to identify the problem.
66 | * **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**.
67 | * **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
68 | * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
69 | * **Explain which behavior you expected to see instead and why.**
70 | * **Include screenshots or videos** which show you following the described steps and clearly demonstrate the problem.
71 | * **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
72 |
73 | Provide more context by answering these questions:
74 |
75 | * **Did the problem start happening recently** (e.g. after updating to a new version of api.video) or was this always a problem?
76 | * If the problem started happening recently.**
77 | * **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
78 |
79 | Include details about your configuration and environment:
80 |
81 | * **Which version of the api.video package are you using?**
82 | * **What's the name and version of the OS you're using?**
83 |
84 | ### Suggesting enhancements
85 |
86 | This section guides you through submitting an enhancement suggestion for api.video project, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
87 |
88 | When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/feature_request.yml), including the steps that you imagine you would take if the feature you're requesting existed.
89 |
90 |
91 | #### How do I submit a (good) enhancement suggestion?
92 |
93 | Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined which repository your enhancement suggestion is related to, create an issue on that repository and provide the following information:
94 |
95 | * **Use a clear and descriptive title** for the issue to identify the suggestion.
96 | * **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
97 | * **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
98 | * **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
99 | * **Include screenshots** which help you demonstrate the steps or point out the part of api.video which the suggestion is related to.
100 | * **Explain why this enhancement would be useful** to most api.video users and isn't something that can or should be implemented as a community package.
101 | * **Specify which version of the api.video package you're using.**
102 | * **Specify the name and version of the OS you're using.**
103 |
104 |
105 | ### Pull requests
106 |
107 | The process described here has several goals:
108 |
109 | - Maintain api.video's quality
110 | - Fix problems that are important to users
111 | - Engage the community in working toward the best possible api.video
112 | - Enable a sustainable system for api.video's maintainers to review contributions
113 |
114 | Please follow these steps to have your contribution considered by the maintainers:
115 |
116 | 1. Explain what, why and how you resolved the issue. If you have a related issue, please mention it.
117 | 2. Follow the [style guides](#style-guides)
118 | 3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing What if the status checks are failing?
If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.
119 |
120 | While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
121 |
122 | ## Style guides
123 |
124 | ### Git commit messages
125 |
126 | * Use the present tense ("Add feature" not "Added feature")
127 | * Limit the first line to 72 characters or less
128 | * Reference issues and pull requests after the first line
129 | * Consider starting the commit message with an applicable emoji:
130 | * :art: `:art:` when improving the format/structure of the code
131 | * :racehorse: `:racehorse:` when improving performance
132 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks
133 | * :memo: `:memo:` when writing docs
134 | * :penguin: `:penguin:` when fixing something on Linux
135 | * :apple: `:apple:` when fixing something on macOS
136 | * :checkered_flag: `:checkered_flag:` when fixing something on Windows
137 | * :bug: `:bug:` when fixing a bug
138 | * :fire: `:fire:` when removing code or files
139 | * :green_heart: `:green_heart:` when fixing the CI build
140 | * :white_check_mark: `:white_check_mark:` when adding tests
141 | * :lock: `:lock:` when dealing with security
142 | * :arrow_up: `:arrow_up:` when upgrading dependencies
143 | * :arrow_down: `:arrow_down:` when downgrading dependencies
144 | * :shirt: `:shirt:` when removing linter warnings
145 |
146 | ### Documentation style guide
147 |
148 | * Use [Markdown](https://daringfireball.net/projects/markdown).
149 |
150 |
151 | ## Additional notes
152 |
153 | ### Issue and pull request labels
154 |
155 | This section lists the labels we use to help us track and manage issues and pull requests on all api.video repositories.
156 |
157 | [GitHub search](https://help.github.com/articles/searching-issues/) makes it easy to use labels for finding groups of issues or pull requests you're interested in. We encourage you to read about [other search filters](https://help.github.com/articles/searching-issues/) which will help you write more focused queries.
158 |
159 |
160 | #### Type of issue and issue state
161 |
162 | | Label name | `apivideo` :mag_right: | Description |
163 | | --- | --- | --- |
164 | | `enhancement` | [search][search-apivideo-org-label-enhancement] | Feature requests. |
165 | | `bug` | [search][search-apivideo-org-label-bug] | Confirmed bugs or reports that are very likely to be bugs. |
166 | | `question` | [search][search-apivideo-org-label-question] | Questions more than bug reports or feature requests (e.g. how do I do X). |
167 | | `feedback` | [search][search-apivideo-org-label-feedback] | General feedback more than bug reports or feature requests. |
168 | | `help-wanted` | [search][search-apivideo-org-label-help-wanted] | The api.video team would appreciate help from the community in resolving these issues. |
169 | | `more-information-needed` | [search][search-apivideo-org-label-more-information-needed] | More information needs to be collected about these problems or feature requests (e.g. steps to reproduce). |
170 | | `needs-reproduction` | [search][search-apivideo-org-label-needs-reproduction] | Likely bugs, but haven't been reliably reproduced. |
171 | | `blocked` | [search][search-apivideo-org-label-blocked] | Issues blocked on other issues. |
172 | | `duplicate` | [search][search-apivideo-org-label-duplicate] | Issues which are duplicates of other issues, i.e. they have been reported before. |
173 | | `wontfix` | [search][search-apivideo-org-label-wontfix] | The api.video team has decided not to fix these issues for now, either because they're working as intended or for some other reason. |
174 | | `invalid` | [search][search-apivideo-org-label-invalid] | Issues which aren't valid (e.g. user errors). |
175 | | `package-idea` | [search][search-apivideo-org-label-package-idea] | Feature request which might be good candidates for new packages, instead of extending api.video packages. |
176 | | `wrong-repo` | [search][search-apivideo-org-label-wrong-repo] | Issues reported on the wrong repository. |
177 |
178 | #### Topic categories
179 |
180 | | Label name | `apivideo` :mag_right: | Description |
181 | | --- | --- | --- |
182 | | `windows` | [search][search-apivideo-org-label-windows] | Related to api.video running on Windows. |
183 | | `linux` | [search][search-apivideo-org-label-linux] | Related to api.video running on Linux. |
184 | | `mac` | [search][search-apivideo-org-label-mac] | Related to api.video running on macOS. |
185 | | `documentation` | [search][search-apivideo-org-label-documentation] | Related to any type of documentation. |
186 | | `performance` | [search][search-apivideo-org-label-performance] | Related to performance. |
187 | | `security` | [search][search-apivideo-org-label-security] | Related to security. |
188 | | `ui` | [search][search-apivideo-org-label-ui] | Related to visual design. |
189 | | `api` | [search][search-apivideo-org-label-api] | Related to api.video's public APIs. |
190 |
191 | #### Pull request labels
192 |
193 | | Label name | `apivideo` :mag_right: | Description
194 | | --- | --- | --- |
195 | | `work-in-progress` | [search][search-apivideo-org-label-work-in-progress] | Pull requests which are still being worked on, more changes will follow. |
196 | | `needs-review` | [search][search-apivideo-org-label-needs-review] | Pull requests which need code review, and approval from maintainers or api.video team. |
197 | | `under-review` | [search][search-apivideo-org-label-under-review] | Pull requests being reviewed by maintainers or api.video team. |
198 | | `requires-changes` | [search][search-apivideo-org-label-requires-changes] | Pull requests which need to be updated based on review comments and then reviewed again. |
199 | | `needs-testing` | [search][search-apivideo-org-label-needs-testing] | Pull requests which need manual testing. |
200 |
201 | [search-apivideo-org-label-enhancement]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Aenhancement
202 | [search-apivideo-org-label-bug]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Abug
203 | [search-apivideo-org-label-question]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Aquestion
204 | [search-apivideo-org-label-feedback]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Afeedback
205 | [search-apivideo-org-label-help-wanted]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Ahelp-wanted
206 | [search-apivideo-org-label-more-information-needed]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Amore-information-needed
207 | [search-apivideo-org-label-needs-reproduction]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Aneeds-reproduction
208 | [search-apivideo-org-label-windows]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Awindows
209 | [search-apivideo-org-label-linux]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Alinux
210 | [search-apivideo-org-label-mac]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Amac
211 | [search-apivideo-org-label-documentation]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Adocumentation
212 | [search-apivideo-org-label-performance]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Aperformance
213 | [search-apivideo-org-label-security]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Asecurity
214 | [search-apivideo-org-label-ui]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Aui
215 | [search-apivideo-org-label-api]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Aapi
216 | [search-apivideo-org-label-blocked]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Ablocked
217 | [search-apivideo-org-label-duplicate]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Aduplicate
218 | [search-apivideo-org-label-wontfix]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Awontfix
219 | [search-apivideo-org-label-invalid]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Ainvalid
220 | [search-apivideo-org-label-package-idea]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Apackage-idea
221 | [search-apivideo-org-label-wrong-repo]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3Aapivideo+label%3Awrong-repo
222 | [search-apivideo-org-label-work-in-progress]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aapivideo%2Fapivideo+label%3Awork-in-progress
223 | [search-apivideo-org-label-needs-review]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aapivideo%2Fapivideo+label%3Aneeds-review
224 | [search-apivideo-org-label-under-review]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aapivideo%2Fapivideo+label%3Aunder-review
225 | [search-apivideo-org-label-requires-changes]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aapivideo%2Fapivideo+label%3Arequires-changes
226 | [search-apivideo-org-label-needs-testing]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aapivideo%2Fapivideo+label%3Aneeds-testing
227 |
228 | [help-wanted]:https://github.com/search?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aapivideo+sort%3Acomments-desc
229 |
--------------------------------------------------------------------------------
/Sources/ApiVideoLiveStream/ApiVideoLiveStream.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApiVideoLiveStream.swift
3 | //
4 |
5 | import AVFoundation
6 | import Foundation
7 | import HaishinKit
8 | #if !os(macOS)
9 | import UIKit
10 | #endif
11 | import VideoToolbox
12 |
13 | public class ApiVideoLiveStream {
14 | private let rtmpStream: RTMPStream
15 | private let rtmpConnection = RTMPConnection()
16 |
17 | private var streamKey: String = ""
18 | private var url: String = ""
19 |
20 | private var isAudioConfigured = false
21 | private var isVideoConfigured = false
22 |
23 | /// The delegate of the ApiVideoLiveStream
24 | public weak var delegate: ApiVideoLiveStreamDelegate?
25 |
26 | /// Getter and Setter for an AudioConfig
27 | public var audioConfig: AudioConfig {
28 | get {
29 | AudioConfig(bitrate: self.rtmpStream.audioSettings.bitRate)
30 | }
31 | set {
32 | self.prepareAudio(audioConfig: newValue)
33 | }
34 | }
35 |
36 | /// Getter and Setter for a VideoConfig
37 | public var videoConfig: VideoConfig {
38 | get {
39 | VideoConfig(
40 | bitrate: Int(self.rtmpStream.videoSettings.bitRate),
41 | resolution: CGSize(
42 | width: Int(self.rtmpStream.videoSettings.videoSize.width),
43 | height: Int(self.rtmpStream.videoSettings.videoSize.height)
44 | ),
45 | fps: self.rtmpStream.frameRate,
46 | gopDuration: TimeInterval(self.rtmpStream.videoSettings.maxKeyFrameIntervalDuration)
47 | )
48 | }
49 | set {
50 | self.prepareVideo(videoConfig: newValue)
51 | }
52 | }
53 |
54 | /// Getter and Setter for the Bitrate number for the video
55 | public var videoBitrate: Int {
56 | get {
57 | self.rtmpStream.videoSettings.bitRate
58 | }
59 | set(newValue) {
60 | self.rtmpStream.videoSettings.bitRate = newValue
61 | }
62 | }
63 |
64 | private var lastCamera: AVCaptureDevice?
65 |
66 | /// Camera position
67 | public var cameraPosition: AVCaptureDevice.Position {
68 | get {
69 | guard let position = rtmpStream.videoCapture(for: 0)?.device?.position else {
70 | return AVCaptureDevice.Position.unspecified
71 | }
72 | return position
73 | }
74 | set(newValue) {
75 | self.attachCamera(newValue)
76 | }
77 | }
78 |
79 | /// Camera device
80 | public var camera: AVCaptureDevice? {
81 | get {
82 | self.rtmpStream.videoCapture(for: 0)?.device
83 | }
84 | set(newValue) {
85 | self.attachCamera(newValue)
86 | }
87 | }
88 |
89 | /// Mutes or unmutes audio capture.
90 | public var isMuted: Bool {
91 | get {
92 | self.rtmpStream.audioMixerSettings.isMuted
93 | }
94 | set(newValue) {
95 | self.rtmpStream.audioMixerSettings.isMuted = newValue
96 | }
97 | }
98 |
99 | #if os(iOS)
100 | public var zoomRatioRange: Range {
101 | guard let device = rtmpStream.videoCapture(for: 0)?.device else {
102 | return 1.0 ..< 1.0
103 | }
104 | return 1.0 ..< device.activeFormat.videoMaxZoomFactor
105 | }
106 |
107 | /// Zoom on the video capture
108 | public var zoomRatio: CGFloat {
109 | get {
110 | guard let device = rtmpStream.videoCapture(for: 0)?.device else {
111 | return 1.0
112 | }
113 | return device.videoZoomFactor
114 | }
115 | set(newValue) {
116 | guard let device = rtmpStream.videoCapture(for: 0)?.device, newValue >= 1,
117 | newValue <= device.activeFormat.videoMaxZoomFactor else
118 | {
119 | return
120 | }
121 | do {
122 | try device.lockForConfiguration()
123 | device.videoZoomFactor = newValue
124 | device.unlockForConfiguration()
125 | } catch let error as NSError {
126 | print("Error while locking device for zoom ramp: \(error)")
127 | }
128 | }
129 | }
130 | #endif
131 |
132 | /// Creates a new ApiVideoLiveStream object without a preview
133 | /// - Parameters:
134 | /// - initialAudioConfig: The ApiVideoLiveStream's initial AudioConfig
135 | /// - initialVideoConfig: The ApiVideoLiveStream's initial VideoConfig
136 | /// - initialCamera: The ApiVideoLiveStream's initial camera device
137 | public init(
138 | initialAudioConfig: AudioConfig? = AudioConfig(),
139 | initialVideoConfig: VideoConfig? = VideoConfig(),
140 | initialCamera: AVCaptureDevice? = AVCaptureDevice.default(
141 | .builtInWideAngleCamera,
142 | for: .video,
143 | position: .back
144 | )
145 | ) throws {
146 | #if os(iOS)
147 | let session = AVAudioSession.sharedInstance()
148 |
149 | // https://stackoverflow.com/questions/51010390/avaudiosession-setcategory-swift-4-2-ios-12-play-sound-on-silent
150 | try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth])
151 | try session.setActive(true)
152 | #endif
153 |
154 | self.rtmpStream = RTMPStream(connection: self.rtmpConnection)
155 |
156 | // Force default resolution because HK default resolution is not supported (480x272)
157 | self.rtmpStream.videoSettings = VideoCodecSettings(videoSize: .init(width: 1_280, height: 720))
158 |
159 | #if os(iOS)
160 | if let orientation = DeviceUtil.videoOrientation(by: UIApplication.shared.statusBarOrientation) {
161 | self.rtmpStream.videoOrientation = orientation
162 | }
163 | #endif
164 |
165 | if let initialCamera = initialCamera {
166 | self.attachCamera(initialCamera)
167 | }
168 | if let initialVideoConfig = initialVideoConfig {
169 | self.prepareVideo(videoConfig: initialVideoConfig)
170 | }
171 |
172 | self.attachAudio()
173 | if let initialAudioConfig = initialAudioConfig {
174 | self.prepareAudio(audioConfig: initialAudioConfig)
175 | }
176 |
177 | #if !os(macOS)
178 | NotificationCenter.default.addObserver(
179 | self,
180 | selector: #selector(self.didEnterBackground(_:)),
181 | name: UIApplication.didEnterBackgroundNotification,
182 | object: nil
183 | )
184 | #endif
185 |
186 | self.rtmpConnection.addEventListener(.rtmpStatus, selector: #selector(self.rtmpStatusHandler), observer: self)
187 | self.rtmpConnection.addEventListener(.ioError, selector: #selector(self.rtmpErrorHandler), observer: self)
188 |
189 | #if os(iOS)
190 | NotificationCenter.default.addObserver(
191 | self,
192 | selector: #selector(self.orientationDidChange(_:)),
193 | name: UIDevice.orientationDidChangeNotification,
194 | object: nil
195 | )
196 | #endif
197 | }
198 |
199 | #if !os(macOS)
200 | /// Creates a new ApiVideoLiveStream object with a UIView as preview
201 | /// - Parameters:
202 | /// - preview: The UIView where to display the preview of camera
203 | /// - initialAudioConfig: The ApiVideoLiveStream's new AudioConfig
204 | /// - initialVideoConfig: The ApiVideoLiveStream's new VideoConfig
205 | /// - initialCamera: The ApiVideoLiveStream's initial camera device
206 | public convenience init(
207 | preview: UIView,
208 | initialAudioConfig: AudioConfig? = AudioConfig(),
209 | initialVideoConfig: VideoConfig? = VideoConfig(),
210 | initialCamera: AVCaptureDevice? = AVCaptureDevice.default(
211 | .builtInWideAngleCamera,
212 | for: .video,
213 | position: .back
214 | )
215 | ) throws {
216 | try self.init(
217 | initialAudioConfig: initialAudioConfig,
218 | initialVideoConfig: initialVideoConfig,
219 | initialCamera: initialCamera
220 | )
221 |
222 | let mthkView = MTHKView(frame: preview.bounds)
223 | mthkView.translatesAutoresizingMaskIntoConstraints = false
224 | mthkView.videoGravity = AVLayerVideoGravity.resizeAspectFill
225 | mthkView.attachStream(self.rtmpStream)
226 |
227 | preview.addSubview(mthkView)
228 |
229 | let maxWidth = mthkView.widthAnchor.constraint(lessThanOrEqualTo: preview.widthAnchor)
230 | let maxHeight = mthkView.heightAnchor.constraint(lessThanOrEqualTo: preview.heightAnchor)
231 | let width = mthkView.widthAnchor.constraint(equalTo: preview.widthAnchor)
232 | let height = mthkView.heightAnchor.constraint(equalTo: preview.heightAnchor)
233 | let centerX = mthkView.centerXAnchor.constraint(equalTo: preview.centerXAnchor)
234 | let centerY = mthkView.centerYAnchor.constraint(equalTo: preview.centerYAnchor)
235 |
236 | width.priority = .defaultHigh
237 | height.priority = .defaultHigh
238 |
239 | NSLayoutConstraint.activate([
240 | maxWidth, maxHeight, width, height, centerX, centerY
241 | ])
242 | }
243 | #endif
244 |
245 | /// Creates a new ApiVideoLiveStream object with a NetStreamDrawable
246 | /// - Parameters:
247 | /// - preview: The NetStreamDrawable where to display the preview of camera
248 | /// - initialAudioConfig: The ApiVideoLiveStream's new AudioConfig
249 | /// - initialVideoConfig: The ApiVideoLiveStream's new VideoConfig
250 | /// - initialCamera: The ApiVideoLiveStream's initial camera device
251 | public convenience init(
252 | preview: IOStreamView,
253 | initialAudioConfig: AudioConfig? = AudioConfig(),
254 | initialVideoConfig: VideoConfig? = VideoConfig(),
255 | initialCamera: AVCaptureDevice? = AVCaptureDevice.default(
256 | .builtInWideAngleCamera,
257 | for: .video,
258 | position: .back
259 | )
260 | ) throws {
261 | try self.init(
262 | initialAudioConfig: initialAudioConfig,
263 | initialVideoConfig: initialVideoConfig,
264 | initialCamera: initialCamera
265 | )
266 | preview.attachStream(self.rtmpStream)
267 | }
268 |
269 | deinit {
270 | #if os(iOS)
271 | NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
272 | #endif
273 | #if !os(macOS)
274 | NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
275 | #endif
276 | rtmpConnection.removeEventListener(.rtmpStatus, selector: #selector(rtmpStatusHandler), observer: self)
277 | rtmpConnection.removeEventListener(.ioError, selector: #selector(rtmpErrorHandler), observer: self)
278 | }
279 |
280 | private func attachCamera(_ cameraPosition: AVCaptureDevice.Position) {
281 | let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: cameraPosition)
282 | self.attachCamera(camera)
283 | }
284 |
285 | private func attachCamera(_ camera: AVCaptureDevice?) {
286 | self.lastCamera = camera
287 |
288 | self.rtmpStream.attachCamera(camera) { videoCaptureUnit, error in
289 | if let error {
290 | print("======== Camera error ==========")
291 | print(error)
292 | self.delegate?.videoError(error)
293 | return
294 | }
295 |
296 | if let camera {
297 | videoCaptureUnit?.isVideoMirrored = camera.position == .front
298 | }
299 | #if os(iOS)
300 | // videoCaptureUnit.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode
301 | // .auto // Add latency to video
302 | #endif
303 |
304 | guard let device = videoCaptureUnit?.device else {
305 | return
306 | }
307 | self.rtmpStream.lockQueue.async {
308 | do {
309 | try device.lockForConfiguration()
310 | if device.isExposureModeSupported(.continuousAutoExposure) {
311 | device.exposureMode = .continuousAutoExposure
312 | }
313 | if device.isFocusModeSupported(.continuousAutoFocus) {
314 | device.focusMode = .continuousAutoFocus
315 | }
316 | device.unlockForConfiguration()
317 | } catch {
318 | print("Could not lock device for exposure and focus: \(error)")
319 | }
320 | }
321 | }
322 | }
323 |
324 | private func prepareVideo(videoConfig: VideoConfig) {
325 | self.rtmpStream.frameRate = videoConfig.fps
326 | self.rtmpStream.sessionPreset = AVCaptureSession.Preset.high
327 |
328 | let resolution = videoConfig.resolution
329 | let width = self.rtmpStream.videoOrientation
330 | .isLandscape ? max(resolution.width, resolution.height) : min(resolution.width, resolution.height)
331 | let height = self.rtmpStream.videoOrientation
332 | .isLandscape ? min(resolution.width, resolution.height) : max(resolution.width, resolution.height)
333 |
334 | self.rtmpStream.videoSettings = VideoCodecSettings(
335 | videoSize: CGSize(width: width, height: height),
336 | bitRate: videoConfig.bitrate,
337 | profileLevel: kVTProfileLevel_H264_Baseline_5_2 as String,
338 | maxKeyFrameIntervalDuration: Int32(videoConfig.gopDuration)
339 | )
340 |
341 | self.isVideoConfigured = true
342 | }
343 |
344 | private func attachAudio() {
345 | self.rtmpStream.attachAudio(AVCaptureDevice.default(for: AVMediaType.audio)) { _, error in
346 | if let error {
347 | print("======== Audio error ==========")
348 | print(error)
349 | self.delegate?.audioError(error)
350 | }
351 | }
352 | }
353 |
354 | private func prepareAudio(audioConfig: AudioConfig) {
355 | self.rtmpStream.audioSettings.bitRate = audioConfig.bitrate
356 |
357 | self.isAudioConfigured = true
358 | }
359 |
360 | /// Start your livestream
361 | /// - Parameters:
362 | /// - streamKey: The key of your live
363 | /// - url: The url of your rtmp server, by default it's rtmp://broadcast.api.video/s
364 | /// - Returns: Void
365 | public func startStreaming(streamKey: String, url: String = "rtmp://broadcast.api.video/s") throws {
366 | if streamKey.isEmpty {
367 | throw LiveStreamError.IllegalArgumentError("Stream key must not be empty")
368 | }
369 | if url.isEmpty {
370 | throw LiveStreamError.IllegalArgumentError("URL must not be empty")
371 | }
372 | if !self.isAudioConfigured || !self.isVideoConfigured {
373 | throw LiveStreamError.IllegalOperationError("Missing audio and/or video configuration")
374 | }
375 |
376 | self.streamKey = streamKey
377 | self.url = url
378 |
379 | self.rtmpStream.fcPublishName = streamKey
380 | self.rtmpConnection.connect(url)
381 | }
382 |
383 | /// Stop your livestream
384 | /// - Returns: Void
385 | public func stopStreaming() {
386 | let isConnected = self.rtmpConnection.connected
387 | self.rtmpConnection.close()
388 | if isConnected {
389 | self.delegate?.disconnection()
390 | }
391 | }
392 |
393 | public func startPreview() {
394 | guard let lastCamera = lastCamera else {
395 | print("No camera has been set")
396 | return
397 | }
398 | self.attachCamera(lastCamera)
399 | self.attachAudio()
400 | }
401 |
402 | public func stopPreview() {
403 | self.rtmpStream.attachCamera(nil)
404 | self.rtmpStream.attachAudio(nil)
405 | }
406 |
407 | @objc
408 | private func rtmpStatusHandler(_ notification: Notification) {
409 | let e = Event.from(notification)
410 | guard let data: ASObject = e.data as? ASObject,
411 | let code: String = data["code"] as? String,
412 | let level: String = data["level"] as? String else
413 | {
414 | print("rtmpStatusHandler: failed to parse event: \(e)")
415 | return
416 | }
417 | switch code {
418 | case RTMPConnection.Code.connectSuccess.rawValue:
419 | self.rtmpStream.publish(self.streamKey)
420 |
421 | case RTMPStream.Code.publishStart.rawValue:
422 | self.delegate?.connectionSuccess()
423 |
424 | case RTMPConnection.Code.connectClosed.rawValue:
425 | self.delegate?.disconnection()
426 |
427 | default:
428 | if level == "error" {
429 | self.delegate?.connectionFailed(code)
430 | }
431 | }
432 | }
433 |
434 | @objc
435 | private func rtmpErrorHandler(_ notification: Notification) {
436 | let e = Event.from(notification)
437 | print("rtmpErrorHandler: \(e)")
438 | DispatchQueue.main.async {
439 | self.rtmpConnection.connect(self.url)
440 | }
441 | }
442 |
443 | #if os(iOS)
444 | @objc
445 | private func orientationDidChange(_: Notification) {
446 | guard let orientation = DeviceUtil.videoOrientation(by: UIApplication.shared.statusBarOrientation) else {
447 | return
448 | }
449 |
450 | self.rtmpStream.lockQueue.async {
451 | self.rtmpStream.videoOrientation = orientation
452 |
453 | let currentVideoSize = self.rtmpStream.videoSettings.videoSize
454 | var newVideoSize: CGSize
455 | if self.rtmpStream.videoOrientation.isLandscape {
456 | newVideoSize = CGSize(
457 | width: max(currentVideoSize.width, currentVideoSize.height),
458 | height: min(currentVideoSize.width, currentVideoSize.height)
459 | )
460 | } else {
461 | newVideoSize = CGSize(
462 | width: min(currentVideoSize.width, currentVideoSize.height),
463 | height: max(currentVideoSize.width, currentVideoSize.height)
464 | )
465 | }
466 | self.rtmpStream.videoSettings.videoSize = newVideoSize
467 | }
468 | }
469 | #endif
470 |
471 | #if !os(macOS)
472 | @objc
473 | private func didEnterBackground(_: Notification) {
474 | self.stopStreaming()
475 | }
476 | #endif
477 | }
478 |
479 | public protocol ApiVideoLiveStreamDelegate: AnyObject {
480 | /// Called when the connection to the rtmp server is successful
481 | func connectionSuccess()
482 |
483 | /// Called when the connection to the rtmp server failed
484 | func connectionFailed(_ code: String)
485 |
486 | /// Called when the connection to the rtmp server is closed
487 | func disconnection()
488 |
489 | /// Called if an error happened during the audio configuration
490 | func audioError(_ error: Error)
491 |
492 | /// Called if an error happened during the video configuration
493 | func videoError(_ error: Error)
494 | }
495 |
496 | extension AVCaptureVideoOrientation {
497 | var isLandscape: Bool {
498 | self == .landscapeLeft || self == .landscapeRight
499 | }
500 | }
501 |
502 | public enum LiveStreamError: Error {
503 | case IllegalArgumentError(String)
504 | case IllegalOperationError(String)
505 | }
506 |
--------------------------------------------------------------------------------
/ApiVideoLiveStream.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2102CD142BA9E0E700D0EBAD /* ApiVideoLiveStream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2102CD092BA9E0E700D0EBAD /* ApiVideoLiveStream.framework */; };
11 | 21BF485B2C10B54300EAEB5B /* HaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 21BF485A2C10B54300EAEB5B /* HaishinKit */; };
12 | 21BF485E2C10BAAF00EAEB5B /* InAppSettingsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 21BF485D2C10BAAF00EAEB5B /* InAppSettingsKit */; };
13 | 21CCD6A02BBC44EE00E58F5D /* ApiVideoLiveStream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2102CD092BA9E0E700D0EBAD /* ApiVideoLiveStream.framework */; };
14 | 21CCD6A12BBC44EE00E58F5D /* ApiVideoLiveStream.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2102CD092BA9E0E700D0EBAD /* ApiVideoLiveStream.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15 | 21E3588F2C10A35300D856D9 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E358802C10A35300D856D9 /* MainViewController.swift */; };
16 | 21E358902C10A35300D856D9 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E358812C10A35300D856D9 /* SettingsViewController.swift */; };
17 | 21E358912C10A35300D856D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E358832C10A35300D856D9 /* AppDelegate.swift */; };
18 | 21E358922C10A35300D856D9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 21E358842C10A35300D856D9 /* Assets.xcassets */; };
19 | 21E358942C10A35300D856D9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 21E358872C10A35300D856D9 /* LaunchScreen.storyboard */; };
20 | 21E358952C10A35300D856D9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 21E358892C10A35300D856D9 /* Main.storyboard */; };
21 | 21E358962C10A35300D856D9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E3588A2C10A35300D856D9 /* SceneDelegate.swift */; };
22 | 21E358972C10A35300D856D9 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 21E3588B2C10A35300D856D9 /* Settings.bundle */; };
23 | 21E358982C10A35300D856D9 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E3588C2C10A35300D856D9 /* SettingsManager.swift */; };
24 | 21E3589D2C10A37100D856D9 /* ApiVideoLiveStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E358992C10A37000D856D9 /* ApiVideoLiveStreamTests.swift */; };
25 | 21E3589E2C10A37100D856D9 /* Parameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E3589A2C10A37000D856D9 /* Parameters.swift */; };
26 | 21E358A52C10A39400D856D9 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E3589F2C10A39400D856D9 /* Configuration.swift */; };
27 | 21E358A62C10A39400D856D9 /* Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E358A02C10A39400D856D9 /* Resolution.swift */; };
28 | 21E358A72C10A39400D856D9 /* ApiVideoLiveStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E358A22C10A39400D856D9 /* ApiVideoLiveStream.swift */; };
29 | /* End PBXBuildFile section */
30 |
31 | /* Begin PBXBuildRule section */
32 | 21CCD6A92BBC4A9800E58F5D /* PBXBuildRule */ = {
33 | isa = PBXBuildRule;
34 | compilerSpec = com.apple.compilers.proxy.script;
35 | fileType = text.plist;
36 | inputFiles = (
37 | );
38 | isEditable = 1;
39 | outputFiles = (
40 | );
41 | script = "# builtin-copyPlist\n";
42 | };
43 | /* End PBXBuildRule section */
44 |
45 | /* Begin PBXContainerItemProxy section */
46 | 2102CD152BA9E0E700D0EBAD /* PBXContainerItemProxy */ = {
47 | isa = PBXContainerItemProxy;
48 | containerPortal = 2102CD002BA9E0E600D0EBAD /* Project object */;
49 | proxyType = 1;
50 | remoteGlobalIDString = 2102CD082BA9E0E700D0EBAD;
51 | remoteInfo = Databus;
52 | };
53 | 21CCD6A22BBC44EF00E58F5D /* PBXContainerItemProxy */ = {
54 | isa = PBXContainerItemProxy;
55 | containerPortal = 2102CD002BA9E0E600D0EBAD /* Project object */;
56 | proxyType = 1;
57 | remoteGlobalIDString = 2102CD082BA9E0E700D0EBAD;
58 | remoteInfo = ApiVideoDatabus;
59 | };
60 | /* End PBXContainerItemProxy section */
61 |
62 | /* Begin PBXCopyFilesBuildPhase section */
63 | 21CCD6A42BBC44EF00E58F5D /* Embed Frameworks */ = {
64 | isa = PBXCopyFilesBuildPhase;
65 | buildActionMask = 2147483647;
66 | dstPath = "";
67 | dstSubfolderSpec = 10;
68 | files = (
69 | 21CCD6A12BBC44EE00E58F5D /* ApiVideoLiveStream.framework in Embed Frameworks */,
70 | );
71 | name = "Embed Frameworks";
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXCopyFilesBuildPhase section */
75 |
76 | /* Begin PBXFileReference section */
77 | 2102CD092BA9E0E700D0EBAD /* ApiVideoLiveStream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ApiVideoLiveStream.framework; sourceTree = BUILT_PRODUCTS_DIR; };
78 | 2102CD132BA9E0E700D0EBAD /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
79 | 2117CBCE2BB1EBCA00B85BF0 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; };
80 | 2117CBD02BB1EBCA00B85BF0 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; };
81 | 2117CBD12BB1EBCA00B85BF0 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
82 | 2117CBDC2BB1EE3100B85BF0 /* Example iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
83 | 2117CC072BB1F1F900B85BF0 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
84 | 2126C5132BCD47DA006BEDF3 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; };
85 | 2126C5142BCD4D6E006BEDF3 /* build.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = build.yml; sourceTree = ""; };
86 | 2126C5152BCD4D6E006BEDF3 /* create-documentation-pr.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "create-documentation-pr.yml"; sourceTree = ""; };
87 | 2126C5162BCD4D6E006BEDF3 /* create-release-from-changelog.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "create-release-from-changelog.yml"; sourceTree = ""; };
88 | 2126C5172BCD4D6E006BEDF3 /* release.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = release.yml; sourceTree = ""; };
89 | 21E3585C2C10A27000D856D9 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; };
90 | 21E3585D2C10A27000D856D9 /* ApiVideoLiveStream.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = ApiVideoLiveStream.podspec; sourceTree = ""; };
91 | 21E358802C10A35300D856D9 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
92 | 21E358812C10A35300D856D9 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; };
93 | 21E358832C10A35300D856D9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
94 | 21E358842C10A35300D856D9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
95 | 21E358852C10A35300D856D9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
96 | 21E358862C10A35300D856D9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97 | 21E358882C10A35300D856D9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
98 | 21E3588A2C10A35300D856D9 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
99 | 21E3588B2C10A35300D856D9 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; };
100 | 21E3588C2C10A35300D856D9 /* SettingsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = ""; };
101 | 21E358992C10A37000D856D9 /* ApiVideoLiveStreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiVideoLiveStreamTests.swift; sourceTree = ""; };
102 | 21E3589A2C10A37000D856D9 /* Parameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parameters.swift; sourceTree = ""; };
103 | 21E3589F2C10A39400D856D9 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; };
104 | 21E358A02C10A39400D856D9 /* Resolution.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resolution.swift; sourceTree = ""; };
105 | 21E358A22C10A39400D856D9 /* ApiVideoLiveStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiVideoLiveStream.swift; sourceTree = ""; };
106 | /* End PBXFileReference section */
107 |
108 | /* Begin PBXFrameworksBuildPhase section */
109 | 2102CD062BA9E0E700D0EBAD /* Frameworks */ = {
110 | isa = PBXFrameworksBuildPhase;
111 | buildActionMask = 2147483647;
112 | files = (
113 | 21BF485B2C10B54300EAEB5B /* HaishinKit in Frameworks */,
114 | );
115 | runOnlyForDeploymentPostprocessing = 0;
116 | };
117 | 2102CD102BA9E0E700D0EBAD /* Frameworks */ = {
118 | isa = PBXFrameworksBuildPhase;
119 | buildActionMask = 2147483647;
120 | files = (
121 | 2102CD142BA9E0E700D0EBAD /* ApiVideoLiveStream.framework in Frameworks */,
122 | );
123 | runOnlyForDeploymentPostprocessing = 0;
124 | };
125 | 2117CBD92BB1EE3100B85BF0 /* Frameworks */ = {
126 | isa = PBXFrameworksBuildPhase;
127 | buildActionMask = 2147483647;
128 | files = (
129 | 21CCD6A02BBC44EE00E58F5D /* ApiVideoLiveStream.framework in Frameworks */,
130 | 21BF485E2C10BAAF00EAEB5B /* InAppSettingsKit in Frameworks */,
131 | );
132 | runOnlyForDeploymentPostprocessing = 0;
133 | };
134 | /* End PBXFrameworksBuildPhase section */
135 |
136 | /* Begin PBXGroup section */
137 | 2102CCFF2BA9E0E600D0EBAD = {
138 | isa = PBXGroup;
139 | children = (
140 | 2126C51B2BCD4D6E006BEDF3 /* .github */,
141 | 21E358A42C10A39400D856D9 /* Sources */,
142 | 21E3588E2C10A35300D856D9 /* Examples */,
143 | 21E3589C2C10A37000D856D9 /* Tests */,
144 | 2117CBCE2BB1EBCA00B85BF0 /* CHANGELOG.md */,
145 | 2117CBD02BB1EBCA00B85BF0 /* CONTRIBUTING.md */,
146 | 2126C5132BCD47DA006BEDF3 /* LICENSE.md */,
147 | 2117CBD12BB1EBCA00B85BF0 /* README.md */,
148 | 21E3585C2C10A27000D856D9 /* Cartfile */,
149 | 21E3585D2C10A27000D856D9 /* ApiVideoLiveStream.podspec */,
150 | 2117CC072BB1F1F900B85BF0 /* Package.swift */,
151 | 2102CD0A2BA9E0E700D0EBAD /* Products */,
152 | 21CCD69F2BBC44EE00E58F5D /* Frameworks */,
153 | );
154 | sourceTree = "";
155 | };
156 | 2102CD0A2BA9E0E700D0EBAD /* Products */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 2102CD092BA9E0E700D0EBAD /* ApiVideoLiveStream.framework */,
160 | 2102CD132BA9E0E700D0EBAD /* Tests.xctest */,
161 | 2117CBDC2BB1EE3100B85BF0 /* Example iOS.app */,
162 | );
163 | name = Products;
164 | sourceTree = "";
165 | };
166 | 2126C51A2BCD4D6E006BEDF3 /* workflows */ = {
167 | isa = PBXGroup;
168 | children = (
169 | 2126C5142BCD4D6E006BEDF3 /* build.yml */,
170 | 2126C5152BCD4D6E006BEDF3 /* create-documentation-pr.yml */,
171 | 2126C5162BCD4D6E006BEDF3 /* create-release-from-changelog.yml */,
172 | 2126C5172BCD4D6E006BEDF3 /* release.yml */,
173 | );
174 | path = workflows;
175 | sourceTree = "";
176 | };
177 | 2126C51B2BCD4D6E006BEDF3 /* .github */ = {
178 | isa = PBXGroup;
179 | children = (
180 | 2126C51A2BCD4D6E006BEDF3 /* workflows */,
181 | );
182 | path = .github;
183 | sourceTree = "";
184 | };
185 | 21CCD69F2BBC44EE00E58F5D /* Frameworks */ = {
186 | isa = PBXGroup;
187 | children = (
188 | );
189 | name = Frameworks;
190 | sourceTree = "";
191 | };
192 | 21E358822C10A35300D856D9 /* ViewControllers */ = {
193 | isa = PBXGroup;
194 | children = (
195 | 21E358802C10A35300D856D9 /* MainViewController.swift */,
196 | 21E358812C10A35300D856D9 /* SettingsViewController.swift */,
197 | );
198 | path = ViewControllers;
199 | sourceTree = "";
200 | };
201 | 21E3588D2C10A35300D856D9 /* iOS */ = {
202 | isa = PBXGroup;
203 | children = (
204 | 21E358822C10A35300D856D9 /* ViewControllers */,
205 | 21E358832C10A35300D856D9 /* AppDelegate.swift */,
206 | 21E358842C10A35300D856D9 /* Assets.xcassets */,
207 | 21E358852C10A35300D856D9 /* Info.plist */,
208 | 21E358872C10A35300D856D9 /* LaunchScreen.storyboard */,
209 | 21E358892C10A35300D856D9 /* Main.storyboard */,
210 | 21E3588A2C10A35300D856D9 /* SceneDelegate.swift */,
211 | 21E3588B2C10A35300D856D9 /* Settings.bundle */,
212 | 21E3588C2C10A35300D856D9 /* SettingsManager.swift */,
213 | );
214 | path = iOS;
215 | sourceTree = "";
216 | };
217 | 21E3588E2C10A35300D856D9 /* Examples */ = {
218 | isa = PBXGroup;
219 | children = (
220 | 21E3588D2C10A35300D856D9 /* iOS */,
221 | );
222 | path = Examples;
223 | sourceTree = "";
224 | };
225 | 21E3589B2C10A37000D856D9 /* ApiVideoLiveStream */ = {
226 | isa = PBXGroup;
227 | children = (
228 | 21E358992C10A37000D856D9 /* ApiVideoLiveStreamTests.swift */,
229 | 21E3589A2C10A37000D856D9 /* Parameters.swift */,
230 | );
231 | path = ApiVideoLiveStream;
232 | sourceTree = "";
233 | };
234 | 21E3589C2C10A37000D856D9 /* Tests */ = {
235 | isa = PBXGroup;
236 | children = (
237 | 21E3589B2C10A37000D856D9 /* ApiVideoLiveStream */,
238 | );
239 | path = Tests;
240 | sourceTree = "";
241 | };
242 | 21E358A12C10A39400D856D9 /* models */ = {
243 | isa = PBXGroup;
244 | children = (
245 | 21E3589F2C10A39400D856D9 /* Configuration.swift */,
246 | 21E358A02C10A39400D856D9 /* Resolution.swift */,
247 | );
248 | path = models;
249 | sourceTree = "";
250 | };
251 | 21E358A32C10A39400D856D9 /* ApiVideoLiveStream */ = {
252 | isa = PBXGroup;
253 | children = (
254 | 21E358A12C10A39400D856D9 /* models */,
255 | 21E358A22C10A39400D856D9 /* ApiVideoLiveStream.swift */,
256 | );
257 | path = ApiVideoLiveStream;
258 | sourceTree = "";
259 | };
260 | 21E358A42C10A39400D856D9 /* Sources */ = {
261 | isa = PBXGroup;
262 | children = (
263 | 21E358A32C10A39400D856D9 /* ApiVideoLiveStream */,
264 | );
265 | path = Sources;
266 | sourceTree = "";
267 | };
268 | /* End PBXGroup section */
269 |
270 | /* Begin PBXHeadersBuildPhase section */
271 | 2102CD042BA9E0E700D0EBAD /* Headers */ = {
272 | isa = PBXHeadersBuildPhase;
273 | buildActionMask = 2147483647;
274 | files = (
275 | );
276 | runOnlyForDeploymentPostprocessing = 0;
277 | };
278 | /* End PBXHeadersBuildPhase section */
279 |
280 | /* Begin PBXNativeTarget section */
281 | 2102CD082BA9E0E700D0EBAD /* ApiVideoLiveStream */ = {
282 | isa = PBXNativeTarget;
283 | buildConfigurationList = 2102CD1D2BA9E0E700D0EBAD /* Build configuration list for PBXNativeTarget "ApiVideoLiveStream" */;
284 | buildPhases = (
285 | 2102CD042BA9E0E700D0EBAD /* Headers */,
286 | 2102CD052BA9E0E700D0EBAD /* Sources */,
287 | 2102CD062BA9E0E700D0EBAD /* Frameworks */,
288 | 2102CD072BA9E0E700D0EBAD /* Resources */,
289 | );
290 | buildRules = (
291 | );
292 | dependencies = (
293 | );
294 | name = ApiVideoLiveStream;
295 | packageProductDependencies = (
296 | 21BF485A2C10B54300EAEB5B /* HaishinKit */,
297 | );
298 | productName = Databus;
299 | productReference = 2102CD092BA9E0E700D0EBAD /* ApiVideoLiveStream.framework */;
300 | productType = "com.apple.product-type.framework";
301 | };
302 | 2102CD122BA9E0E700D0EBAD /* Tests */ = {
303 | isa = PBXNativeTarget;
304 | buildConfigurationList = 2102CD202BA9E0E700D0EBAD /* Build configuration list for PBXNativeTarget "Tests" */;
305 | buildPhases = (
306 | 2102CD0F2BA9E0E700D0EBAD /* Sources */,
307 | 2102CD102BA9E0E700D0EBAD /* Frameworks */,
308 | 2102CD112BA9E0E700D0EBAD /* Resources */,
309 | );
310 | buildRules = (
311 | );
312 | dependencies = (
313 | 2102CD162BA9E0E700D0EBAD /* PBXTargetDependency */,
314 | );
315 | name = Tests;
316 | productName = DatabusTests;
317 | productReference = 2102CD132BA9E0E700D0EBAD /* Tests.xctest */;
318 | productType = "com.apple.product-type.bundle.unit-test";
319 | };
320 | 2117CBDB2BB1EE3100B85BF0 /* Example iOS */ = {
321 | isa = PBXNativeTarget;
322 | buildConfigurationList = 2117CBED2BB1EE3300B85BF0 /* Build configuration list for PBXNativeTarget "Example iOS" */;
323 | buildPhases = (
324 | 2117CBD82BB1EE3100B85BF0 /* Sources */,
325 | 2117CBD92BB1EE3100B85BF0 /* Frameworks */,
326 | 2117CBDA2BB1EE3100B85BF0 /* Resources */,
327 | 21CCD6A42BBC44EF00E58F5D /* Embed Frameworks */,
328 | );
329 | buildRules = (
330 | 21CCD6A92BBC4A9800E58F5D /* PBXBuildRule */,
331 | );
332 | dependencies = (
333 | 21CCD6A32BBC44EF00E58F5D /* PBXTargetDependency */,
334 | );
335 | name = "Example iOS";
336 | packageProductDependencies = (
337 | 21BF485D2C10BAAF00EAEB5B /* InAppSettingsKit */,
338 | );
339 | productName = iOS;
340 | productReference = 2117CBDC2BB1EE3100B85BF0 /* Example iOS.app */;
341 | productType = "com.apple.product-type.application";
342 | };
343 | /* End PBXNativeTarget section */
344 |
345 | /* Begin PBXProject section */
346 | 2102CD002BA9E0E600D0EBAD /* Project object */ = {
347 | isa = PBXProject;
348 | attributes = {
349 | BuildIndependentTargetsInParallel = 1;
350 | LastSwiftUpdateCheck = 1530;
351 | LastUpgradeCheck = 1540;
352 | TargetAttributes = {
353 | 2102CD082BA9E0E700D0EBAD = {
354 | CreatedOnToolsVersion = 15.2;
355 | };
356 | 2102CD122BA9E0E700D0EBAD = {
357 | CreatedOnToolsVersion = 15.2;
358 | };
359 | 2117CBDB2BB1EE3100B85BF0 = {
360 | CreatedOnToolsVersion = 15.3;
361 | };
362 | };
363 | };
364 | buildConfigurationList = 2102CD032BA9E0E600D0EBAD /* Build configuration list for PBXProject "ApiVideoLiveStream" */;
365 | compatibilityVersion = "Xcode 14.0";
366 | developmentRegion = en;
367 | hasScannedForEncodings = 0;
368 | knownRegions = (
369 | en,
370 | Base,
371 | );
372 | mainGroup = 2102CCFF2BA9E0E600D0EBAD;
373 | packageReferences = (
374 | 21E358A82C10A54D00D856D9 /* XCRemoteSwiftPackageReference "HaishinKit" */,
375 | 21BF485C2C10BA9A00EAEB5B /* XCRemoteSwiftPackageReference "InAppSettingsKit" */,
376 | );
377 | productRefGroup = 2102CD0A2BA9E0E700D0EBAD /* Products */;
378 | projectDirPath = "";
379 | projectRoot = "";
380 | targets = (
381 | 2102CD082BA9E0E700D0EBAD /* ApiVideoLiveStream */,
382 | 2102CD122BA9E0E700D0EBAD /* Tests */,
383 | 2117CBDB2BB1EE3100B85BF0 /* Example iOS */,
384 | );
385 | };
386 | /* End PBXProject section */
387 |
388 | /* Begin PBXResourcesBuildPhase section */
389 | 2102CD072BA9E0E700D0EBAD /* Resources */ = {
390 | isa = PBXResourcesBuildPhase;
391 | buildActionMask = 2147483647;
392 | files = (
393 | );
394 | runOnlyForDeploymentPostprocessing = 0;
395 | };
396 | 2102CD112BA9E0E700D0EBAD /* Resources */ = {
397 | isa = PBXResourcesBuildPhase;
398 | buildActionMask = 2147483647;
399 | files = (
400 | );
401 | runOnlyForDeploymentPostprocessing = 0;
402 | };
403 | 2117CBDA2BB1EE3100B85BF0 /* Resources */ = {
404 | isa = PBXResourcesBuildPhase;
405 | buildActionMask = 2147483647;
406 | files = (
407 | 21E358942C10A35300D856D9 /* LaunchScreen.storyboard in Resources */,
408 | 21E358972C10A35300D856D9 /* Settings.bundle in Resources */,
409 | 21E358922C10A35300D856D9 /* Assets.xcassets in Resources */,
410 | 21E358952C10A35300D856D9 /* Main.storyboard in Resources */,
411 | );
412 | runOnlyForDeploymentPostprocessing = 0;
413 | };
414 | /* End PBXResourcesBuildPhase section */
415 |
416 | /* Begin PBXSourcesBuildPhase section */
417 | 2102CD052BA9E0E700D0EBAD /* Sources */ = {
418 | isa = PBXSourcesBuildPhase;
419 | buildActionMask = 2147483647;
420 | files = (
421 | 21E358A52C10A39400D856D9 /* Configuration.swift in Sources */,
422 | 21E358A62C10A39400D856D9 /* Resolution.swift in Sources */,
423 | 21E358A72C10A39400D856D9 /* ApiVideoLiveStream.swift in Sources */,
424 | );
425 | runOnlyForDeploymentPostprocessing = 0;
426 | };
427 | 2102CD0F2BA9E0E700D0EBAD /* Sources */ = {
428 | isa = PBXSourcesBuildPhase;
429 | buildActionMask = 2147483647;
430 | files = (
431 | 21E3589D2C10A37100D856D9 /* ApiVideoLiveStreamTests.swift in Sources */,
432 | 21E3589E2C10A37100D856D9 /* Parameters.swift in Sources */,
433 | );
434 | runOnlyForDeploymentPostprocessing = 0;
435 | };
436 | 2117CBD82BB1EE3100B85BF0 /* Sources */ = {
437 | isa = PBXSourcesBuildPhase;
438 | buildActionMask = 2147483647;
439 | files = (
440 | 21E358912C10A35300D856D9 /* AppDelegate.swift in Sources */,
441 | 21E358982C10A35300D856D9 /* SettingsManager.swift in Sources */,
442 | 21E3588F2C10A35300D856D9 /* MainViewController.swift in Sources */,
443 | 21E358902C10A35300D856D9 /* SettingsViewController.swift in Sources */,
444 | 21E358962C10A35300D856D9 /* SceneDelegate.swift in Sources */,
445 | );
446 | runOnlyForDeploymentPostprocessing = 0;
447 | };
448 | /* End PBXSourcesBuildPhase section */
449 |
450 | /* Begin PBXTargetDependency section */
451 | 2102CD162BA9E0E700D0EBAD /* PBXTargetDependency */ = {
452 | isa = PBXTargetDependency;
453 | target = 2102CD082BA9E0E700D0EBAD /* ApiVideoLiveStream */;
454 | targetProxy = 2102CD152BA9E0E700D0EBAD /* PBXContainerItemProxy */;
455 | };
456 | 21CCD6A32BBC44EF00E58F5D /* PBXTargetDependency */ = {
457 | isa = PBXTargetDependency;
458 | target = 2102CD082BA9E0E700D0EBAD /* ApiVideoLiveStream */;
459 | targetProxy = 21CCD6A22BBC44EF00E58F5D /* PBXContainerItemProxy */;
460 | };
461 | /* End PBXTargetDependency section */
462 |
463 | /* Begin PBXVariantGroup section */
464 | 21E358872C10A35300D856D9 /* LaunchScreen.storyboard */ = {
465 | isa = PBXVariantGroup;
466 | children = (
467 | 21E358862C10A35300D856D9 /* Base */,
468 | );
469 | name = LaunchScreen.storyboard;
470 | sourceTree = "";
471 | };
472 | 21E358892C10A35300D856D9 /* Main.storyboard */ = {
473 | isa = PBXVariantGroup;
474 | children = (
475 | 21E358882C10A35300D856D9 /* Base */,
476 | );
477 | name = Main.storyboard;
478 | sourceTree = "";
479 | };
480 | /* End PBXVariantGroup section */
481 |
482 | /* Begin XCBuildConfiguration section */
483 | 2102CD1B2BA9E0E700D0EBAD /* Debug */ = {
484 | isa = XCBuildConfiguration;
485 | buildSettings = {
486 | ALWAYS_SEARCH_USER_PATHS = NO;
487 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
488 | CLANG_ANALYZER_NONNULL = YES;
489 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
490 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
491 | CLANG_ENABLE_MODULES = YES;
492 | CLANG_ENABLE_OBJC_ARC = YES;
493 | CLANG_ENABLE_OBJC_WEAK = YES;
494 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
495 | CLANG_WARN_BOOL_CONVERSION = YES;
496 | CLANG_WARN_COMMA = YES;
497 | CLANG_WARN_CONSTANT_CONVERSION = YES;
498 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
499 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
500 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
501 | CLANG_WARN_EMPTY_BODY = YES;
502 | CLANG_WARN_ENUM_CONVERSION = YES;
503 | CLANG_WARN_INFINITE_RECURSION = YES;
504 | CLANG_WARN_INT_CONVERSION = YES;
505 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
506 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
507 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
508 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
509 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
510 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
511 | CLANG_WARN_STRICT_PROTOTYPES = YES;
512 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
513 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
514 | CLANG_WARN_UNREACHABLE_CODE = YES;
515 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
516 | COPY_PHASE_STRIP = NO;
517 | CURRENT_PROJECT_VERSION = 1;
518 | DEAD_CODE_STRIPPING = YES;
519 | DEBUG_INFORMATION_FORMAT = dwarf;
520 | ENABLE_STRICT_OBJC_MSGSEND = YES;
521 | ENABLE_TESTABILITY = YES;
522 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
523 | GCC_C_LANGUAGE_STANDARD = gnu17;
524 | GCC_DYNAMIC_NO_PIC = NO;
525 | GCC_NO_COMMON_BLOCKS = YES;
526 | GCC_OPTIMIZATION_LEVEL = 0;
527 | GCC_PREPROCESSOR_DEFINITIONS = (
528 | "DEBUG=1",
529 | "$(inherited)",
530 | );
531 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
532 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
533 | GCC_WARN_UNDECLARED_SELECTOR = YES;
534 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
535 | GCC_WARN_UNUSED_FUNCTION = YES;
536 | GCC_WARN_UNUSED_VARIABLE = YES;
537 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
538 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
539 | MTL_FAST_MATH = YES;
540 | ONLY_ACTIVE_ARCH = YES;
541 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
542 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
543 | VERSIONING_SYSTEM = "apple-generic";
544 | VERSION_INFO_PREFIX = "";
545 | };
546 | name = Debug;
547 | };
548 | 2102CD1C2BA9E0E700D0EBAD /* Release */ = {
549 | isa = XCBuildConfiguration;
550 | buildSettings = {
551 | ALWAYS_SEARCH_USER_PATHS = NO;
552 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
553 | CLANG_ANALYZER_NONNULL = YES;
554 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
555 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
556 | CLANG_ENABLE_MODULES = YES;
557 | CLANG_ENABLE_OBJC_ARC = YES;
558 | CLANG_ENABLE_OBJC_WEAK = YES;
559 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
560 | CLANG_WARN_BOOL_CONVERSION = YES;
561 | CLANG_WARN_COMMA = YES;
562 | CLANG_WARN_CONSTANT_CONVERSION = YES;
563 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
564 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
565 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
566 | CLANG_WARN_EMPTY_BODY = YES;
567 | CLANG_WARN_ENUM_CONVERSION = YES;
568 | CLANG_WARN_INFINITE_RECURSION = YES;
569 | CLANG_WARN_INT_CONVERSION = YES;
570 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
571 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
572 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
573 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
574 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
575 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
576 | CLANG_WARN_STRICT_PROTOTYPES = YES;
577 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
578 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
579 | CLANG_WARN_UNREACHABLE_CODE = YES;
580 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
581 | COPY_PHASE_STRIP = NO;
582 | CURRENT_PROJECT_VERSION = 1;
583 | DEAD_CODE_STRIPPING = YES;
584 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
585 | ENABLE_NS_ASSERTIONS = NO;
586 | ENABLE_STRICT_OBJC_MSGSEND = YES;
587 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
588 | GCC_C_LANGUAGE_STANDARD = gnu17;
589 | GCC_NO_COMMON_BLOCKS = YES;
590 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
591 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
592 | GCC_WARN_UNDECLARED_SELECTOR = YES;
593 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
594 | GCC_WARN_UNUSED_FUNCTION = YES;
595 | GCC_WARN_UNUSED_VARIABLE = YES;
596 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
597 | MTL_ENABLE_DEBUG_INFO = NO;
598 | MTL_FAST_MATH = YES;
599 | SWIFT_COMPILATION_MODE = wholemodule;
600 | VERSIONING_SYSTEM = "apple-generic";
601 | VERSION_INFO_PREFIX = "";
602 | };
603 | name = Release;
604 | };
605 | 2102CD1E2BA9E0E700D0EBAD /* Debug */ = {
606 | isa = XCBuildConfiguration;
607 | buildSettings = {
608 | CODE_SIGN_STYLE = Automatic;
609 | CURRENT_PROJECT_VERSION = 1;
610 | DEAD_CODE_STRIPPING = YES;
611 | DEFINES_MODULE = YES;
612 | DYLIB_COMPATIBILITY_VERSION = 1;
613 | DYLIB_CURRENT_VERSION = 1;
614 | DYLIB_INSTALL_NAME_BASE = "@rpath";
615 | ENABLE_MODULE_VERIFIER = YES;
616 | GENERATE_INFOPLIST_FILE = YES;
617 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
618 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
619 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
620 | LD_RUNPATH_SEARCH_PATHS = (
621 | "@executable_path/Frameworks",
622 | "@loader_path/Frameworks",
623 | );
624 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
625 | "@executable_path/../Frameworks",
626 | "@loader_path/Frameworks",
627 | );
628 | MACOSX_DEPLOYMENT_TARGET = 14.1;
629 | MARKETING_VERSION = 1.0;
630 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
631 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
632 | PRODUCT_BUNDLE_IDENTIFIER = video.api.Databus;
633 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
634 | SDKROOT = auto;
635 | SKIP_INSTALL = YES;
636 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
637 | SWIFT_EMIT_LOC_STRINGS = YES;
638 | SWIFT_VERSION = 5.0;
639 | TARGETED_DEVICE_FAMILY = "1,2";
640 | };
641 | name = Debug;
642 | };
643 | 2102CD1F2BA9E0E700D0EBAD /* Release */ = {
644 | isa = XCBuildConfiguration;
645 | buildSettings = {
646 | CODE_SIGN_STYLE = Automatic;
647 | CURRENT_PROJECT_VERSION = 1;
648 | DEAD_CODE_STRIPPING = YES;
649 | DEFINES_MODULE = YES;
650 | DYLIB_COMPATIBILITY_VERSION = 1;
651 | DYLIB_CURRENT_VERSION = 1;
652 | DYLIB_INSTALL_NAME_BASE = "@rpath";
653 | ENABLE_MODULE_VERIFIER = YES;
654 | GENERATE_INFOPLIST_FILE = YES;
655 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
656 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
657 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
658 | LD_RUNPATH_SEARCH_PATHS = (
659 | "@executable_path/Frameworks",
660 | "@loader_path/Frameworks",
661 | );
662 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
663 | "@executable_path/../Frameworks",
664 | "@loader_path/Frameworks",
665 | );
666 | MACOSX_DEPLOYMENT_TARGET = 14.1;
667 | MARKETING_VERSION = 1.0;
668 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
669 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
670 | PRODUCT_BUNDLE_IDENTIFIER = video.api.Databus;
671 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
672 | SDKROOT = auto;
673 | SKIP_INSTALL = YES;
674 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
675 | SWIFT_EMIT_LOC_STRINGS = YES;
676 | SWIFT_VERSION = 5.0;
677 | TARGETED_DEVICE_FAMILY = "1,2";
678 | };
679 | name = Release;
680 | };
681 | 2102CD212BA9E0E700D0EBAD /* Debug */ = {
682 | isa = XCBuildConfiguration;
683 | buildSettings = {
684 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
685 | CODE_SIGN_STYLE = Automatic;
686 | CURRENT_PROJECT_VERSION = 1;
687 | DEAD_CODE_STRIPPING = YES;
688 | GENERATE_INFOPLIST_FILE = YES;
689 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
690 | MACOSX_DEPLOYMENT_TARGET = 14.1;
691 | MARKETING_VERSION = 1.0;
692 | PRODUCT_BUNDLE_IDENTIFIER = video.api.DatabusTests;
693 | PRODUCT_NAME = "$(TARGET_NAME)";
694 | SDKROOT = auto;
695 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
696 | SWIFT_EMIT_LOC_STRINGS = NO;
697 | SWIFT_VERSION = 5.0;
698 | TARGETED_DEVICE_FAMILY = "1,2";
699 | };
700 | name = Debug;
701 | };
702 | 2102CD222BA9E0E700D0EBAD /* Release */ = {
703 | isa = XCBuildConfiguration;
704 | buildSettings = {
705 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
706 | CODE_SIGN_STYLE = Automatic;
707 | CURRENT_PROJECT_VERSION = 1;
708 | DEAD_CODE_STRIPPING = YES;
709 | GENERATE_INFOPLIST_FILE = YES;
710 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
711 | MACOSX_DEPLOYMENT_TARGET = 14.1;
712 | MARKETING_VERSION = 1.0;
713 | PRODUCT_BUNDLE_IDENTIFIER = video.api.DatabusTests;
714 | PRODUCT_NAME = "$(TARGET_NAME)";
715 | SDKROOT = auto;
716 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
717 | SWIFT_EMIT_LOC_STRINGS = NO;
718 | SWIFT_VERSION = 5.0;
719 | TARGETED_DEVICE_FAMILY = "1,2";
720 | };
721 | name = Release;
722 | };
723 | 2117CBEE2BB1EE3300B85BF0 /* Debug */ = {
724 | isa = XCBuildConfiguration;
725 | buildSettings = {
726 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
727 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
728 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
729 | CODE_SIGN_STYLE = Automatic;
730 | CURRENT_PROJECT_VERSION = 1;
731 | DEVELOPMENT_TEAM = GBC36KP98K;
732 | GENERATE_INFOPLIST_FILE = YES;
733 | INFOPLIST_FILE = Examples/iOS/Info.plist;
734 | INFOPLIST_KEY_CFBundleDisplayName = ApiVideoLiveStream;
735 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
736 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
737 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
738 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
739 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
740 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
741 | LD_RUNPATH_SEARCH_PATHS = (
742 | "$(inherited)",
743 | "@executable_path/Frameworks",
744 | );
745 | MARKETING_VERSION = 1.0;
746 | PRODUCT_BUNDLE_IDENTIFIER = video.api.livestream.examples.iOS;
747 | PRODUCT_NAME = "$(TARGET_NAME)";
748 | SDKROOT = iphoneos;
749 | SWIFT_EMIT_LOC_STRINGS = YES;
750 | SWIFT_VERSION = 5.0;
751 | TARGETED_DEVICE_FAMILY = "1,2";
752 | };
753 | name = Debug;
754 | };
755 | 2117CBEF2BB1EE3300B85BF0 /* Release */ = {
756 | isa = XCBuildConfiguration;
757 | buildSettings = {
758 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
759 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
760 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
761 | CODE_SIGN_STYLE = Automatic;
762 | CURRENT_PROJECT_VERSION = 1;
763 | DEVELOPMENT_TEAM = GBC36KP98K;
764 | GENERATE_INFOPLIST_FILE = YES;
765 | INFOPLIST_FILE = Examples/iOS/Info.plist;
766 | INFOPLIST_KEY_CFBundleDisplayName = ApiVideoLiveStream;
767 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
768 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
769 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
770 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
771 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
772 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
773 | LD_RUNPATH_SEARCH_PATHS = (
774 | "$(inherited)",
775 | "@executable_path/Frameworks",
776 | );
777 | MARKETING_VERSION = 1.0;
778 | PRODUCT_BUNDLE_IDENTIFIER = video.api.livestream.examples.iOS;
779 | PRODUCT_NAME = "$(TARGET_NAME)";
780 | SDKROOT = iphoneos;
781 | SWIFT_EMIT_LOC_STRINGS = YES;
782 | SWIFT_VERSION = 5.0;
783 | TARGETED_DEVICE_FAMILY = "1,2";
784 | VALIDATE_PRODUCT = YES;
785 | };
786 | name = Release;
787 | };
788 | /* End XCBuildConfiguration section */
789 |
790 | /* Begin XCConfigurationList section */
791 | 2102CD032BA9E0E600D0EBAD /* Build configuration list for PBXProject "ApiVideoLiveStream" */ = {
792 | isa = XCConfigurationList;
793 | buildConfigurations = (
794 | 2102CD1B2BA9E0E700D0EBAD /* Debug */,
795 | 2102CD1C2BA9E0E700D0EBAD /* Release */,
796 | );
797 | defaultConfigurationIsVisible = 0;
798 | defaultConfigurationName = Release;
799 | };
800 | 2102CD1D2BA9E0E700D0EBAD /* Build configuration list for PBXNativeTarget "ApiVideoLiveStream" */ = {
801 | isa = XCConfigurationList;
802 | buildConfigurations = (
803 | 2102CD1E2BA9E0E700D0EBAD /* Debug */,
804 | 2102CD1F2BA9E0E700D0EBAD /* Release */,
805 | );
806 | defaultConfigurationIsVisible = 0;
807 | defaultConfigurationName = Release;
808 | };
809 | 2102CD202BA9E0E700D0EBAD /* Build configuration list for PBXNativeTarget "Tests" */ = {
810 | isa = XCConfigurationList;
811 | buildConfigurations = (
812 | 2102CD212BA9E0E700D0EBAD /* Debug */,
813 | 2102CD222BA9E0E700D0EBAD /* Release */,
814 | );
815 | defaultConfigurationIsVisible = 0;
816 | defaultConfigurationName = Release;
817 | };
818 | 2117CBED2BB1EE3300B85BF0 /* Build configuration list for PBXNativeTarget "Example iOS" */ = {
819 | isa = XCConfigurationList;
820 | buildConfigurations = (
821 | 2117CBEE2BB1EE3300B85BF0 /* Debug */,
822 | 2117CBEF2BB1EE3300B85BF0 /* Release */,
823 | );
824 | defaultConfigurationIsVisible = 0;
825 | defaultConfigurationName = Release;
826 | };
827 | /* End XCConfigurationList section */
828 |
829 | /* Begin XCRemoteSwiftPackageReference section */
830 | 21BF485C2C10BA9A00EAEB5B /* XCRemoteSwiftPackageReference "InAppSettingsKit" */ = {
831 | isa = XCRemoteSwiftPackageReference;
832 | repositoryURL = "https://github.com/futuretap/InAppSettingsKit.git";
833 | requirement = {
834 | kind = upToNextMajorVersion;
835 | minimumVersion = 3.7.1;
836 | };
837 | };
838 | 21E358A82C10A54D00D856D9 /* XCRemoteSwiftPackageReference "HaishinKit" */ = {
839 | isa = XCRemoteSwiftPackageReference;
840 | repositoryURL = "https://github.com/shogo4405/HaishinKit.swift";
841 | requirement = {
842 | kind = exactVersion;
843 | version = 1.9.3;
844 | };
845 | };
846 | /* End XCRemoteSwiftPackageReference section */
847 |
848 | /* Begin XCSwiftPackageProductDependency section */
849 | 21BF485A2C10B54300EAEB5B /* HaishinKit */ = {
850 | isa = XCSwiftPackageProductDependency;
851 | package = 21E358A82C10A54D00D856D9 /* XCRemoteSwiftPackageReference "HaishinKit" */;
852 | productName = HaishinKit;
853 | };
854 | 21BF485D2C10BAAF00EAEB5B /* InAppSettingsKit */ = {
855 | isa = XCSwiftPackageProductDependency;
856 | package = 21BF485C2C10BA9A00EAEB5B /* XCRemoteSwiftPackageReference "InAppSettingsKit" */;
857 | productName = InAppSettingsKit;
858 | };
859 | /* End XCSwiftPackageProductDependency section */
860 | };
861 | rootObject = 2102CD002BA9E0E600D0EBAD /* Project object */;
862 | }
863 |
--------------------------------------------------------------------------------