├── Classes
├── .gitkeep
├── Player
│ ├── PKController.swift
│ ├── VR
│ │ ├── VRData.swift
│ │ └── VRPlayerEngine.swift
│ ├── Tracks
│ │ ├── PKTracks.swift
│ │ └── Track.swift
│ ├── PKAsset.swift
│ ├── Protocols
│ │ ├── PlayerEngine.swift
│ │ └── BasicPlayer.swift
│ ├── Media
│ │ ├── PKMediaEntry+Copying.swift
│ │ └── SourceSelector.swift
│ ├── Playlist
│ │ └── PKPlaylist.swift
│ ├── PlayerState.swift
│ ├── Ads
│ │ ├── AdsDAIPlugin.swift
│ │ └── AdsPlugin.swift
│ ├── AVAsset
│ │ └── PKAssetResourceLoaderDelegate.swift
│ ├── PlayerController+TimeMonitor.swift
│ ├── PlayerConfig.swift
│ ├── PKVRController.swift
│ └── PlayerView.swift
├── Providers
│ ├── PlaylistProvider.swift
│ ├── MediaEntryProviderResponseDelegate.swift
│ ├── MediaEntryProvider.swift
│ └── Mock
│ │ └── MockMediaProvider.swift
├── Platform.swift
├── Plugins
│ ├── TimeIntervalExtension.swift
│ ├── PlayerPluginsDataSource.swift
│ ├── BasePlugin.swift
│ └── PKPlugin.swift
├── Events
│ ├── PKMessageBus.swift
│ ├── EventsPayloads
│ │ ├── PKAdDAICuePoints.swift
│ │ ├── PKAdCuePoints.swift
│ │ └── PKPlaybackInfo.swift
│ ├── PluginEvent.swift
│ ├── InterceptorEvent.swift
│ └── PlaylistEvent.swift
├── Extensions
│ └── ArrayExtension.swift
├── Models
│ ├── PKTimeRange.swift
│ └── PKBoundary.swift
├── PKRequestParams.swift
├── PKLog.swift
├── Managers
│ ├── NotificationsManager.swift
│ ├── LocalDataStore.swift
│ ├── AppState
│ │ └── AppStateProvider.swift
│ └── PlayKitManager.swift
├── PKStateMachine.swift
├── Utils
│ └── NetworkUtils.swift
├── FairPlayDRM
│ └── FPSContentKeyManager.swift
├── Network
│ ├── KalturaPlaybackRequestAdapter.swift
│ └── KalturaUDRMLicenseRequestAdapter.swift
└── MessageBus.swift
├── icon.png
├── TestApp
├── Default-568h@2x.png
├── TestApp
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── ViewController.swift
│ ├── Info.plist
│ ├── TestApp_v5-Info.plist
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── AppDelegate.swift
├── TestApp.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── TestApp_v5.xcscheme
│ │ └── TestApp_v4_2.xcscheme
├── TestApp.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Podfile
├── tvOSTestApp
├── tvOSTestApp
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── App Icon & Top Shelf Image.brandassets
│ │ │ ├── App Icon.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Middle.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── App Icon - App Store.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Middle.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Top Shelf Image.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── Top Shelf Image Wide.imageset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ └── AccentColor.colorset
│ │ │ └── Contents.json
│ ├── ViewController.swift
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ └── AppDelegate.swift
├── tvOSTestApp.xcodeproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Podfile
└── tvOSTestApp.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Example
├── Tests
│ ├── Resources
│ │ └── Video
│ │ │ └── big_buck_bunny_short.mp4
│ ├── Info.plist
│ ├── Helpers
│ │ └── PluginTestConfiguration.swift
│ ├── Analytics
│ │ ├── AnalyticsPluginConfig.swift
│ │ ├── TVPAPI
│ │ │ └── TVPAPIPluginTest.swift
│ │ └── Phoenix
│ │ │ └── PhoenixPluginTest.swift
│ ├── Basic
│ │ ├── SourceSelectorTest.swift
│ │ └── TracksTest.swift
│ └── MediaEntryProvider
│ │ ├── RequestBuilder
│ │ └── KalturaRequestBuilderTest.swift
│ │ ├── MediaEntryProviderMock.swift
│ │ └── PhoenixMediaProviderTest.swift
├── PlayKit.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── PlayKit-Tests.xcscheme
├── PlayKit.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Sources
│ └── Entries.json
├── PlayKit
│ ├── ViewController.swift
│ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.xib
│ └── AppDelegate.swift
├── Podfile
└── Podfile.lock
├── .github
├── release_notes_template.md
├── cocoapods_publish.sh
├── release_mode.sh
├── release_notes.sh
├── PULL_REQUEST_TEMPLATE.md
├── tag.sh
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── ci.yml
│ ├── pr.yml
│ └── release.yml
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── .gitignore
├── Plugins
└── AnalyticsCommon
│ ├── AnalyticsConfig.swift
│ ├── AnalyticsPluginProtocol.swift
│ └── BaseAnalyticsPlugin.swift
├── travis-build.sh
├── banner-helper.py
├── PlayKit.podspec
├── Package.swift
└── README.md
/Classes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaltura/playkit-ios/HEAD/icon.png
--------------------------------------------------------------------------------
/TestApp/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaltura/playkit-ios/HEAD/TestApp/Default-568h@2x.png
--------------------------------------------------------------------------------
/TestApp/TestApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Tests/Resources/Video/big_buck_bunny_short.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaltura/playkit-ios/HEAD/Example/Tests/Resources/Video/big_buck_bunny_short.mp4
--------------------------------------------------------------------------------
/.github/release_notes_template.md:
--------------------------------------------------------------------------------
1 | ## New in this version:
2 |
3 | @@RELEASE_NOTES@@
4 |
5 |
6 | ## Cocoapods install
7 | `pod '@@POD_NAME@@', '~> @@POD_PREFERRED_VERSION@@'`
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/PlayKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TestApp/TestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/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 |
--------------------------------------------------------------------------------
/.github/cocoapods_publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -u
4 |
5 | cat << EOF > ~/.netrc
6 | machine trunk.cocoapods.org
7 | login $PODS_USER
8 | password $PODS_PASS
9 | EOF
10 |
11 | chmod 0600 ~/.netrc
12 |
13 | pod trunk push --verbose --allow-warnings
14 |
--------------------------------------------------------------------------------
/tvOSTestApp/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :tvos, '9.0'
3 |
4 | target 'tvOSTestApp' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | pod 'PlayKit', :path => '..'
9 |
10 | end
11 |
--------------------------------------------------------------------------------
/Example/PlayKit.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TestApp/TestApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/PlayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TestApp/TestApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TestApp/TestApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/release_mode.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -u
4 |
5 | PODSPEC=*.podspec
6 | POD=$(basename $PODSPEC .podspec)
7 |
8 | sed -e "s#.0000##1" -i '' $POD.podspec
9 |
10 | cat $POD.podspec
11 |
12 | git add .
13 | git config user.name "Kaltura Dev"
14 | git config user.email dev@kaltura.com
15 |
16 | git commit -m "Set to release mode"
17 |
18 | git push
19 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // tvOSTestApp
4 | //
5 | // Created by Nilit Danan on 14/06/2022.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewController: UIViewController {
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | // Do any additional setup after loading the view.
15 | }
16 |
17 |
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | osx_image: xcode13.1
3 | before_install:
4 | - gem install cocoapods xcpretty
5 | - pod repo update
6 | script:
7 | - ./travis-build.sh
8 | notifications:
9 | email:
10 | recipients:
11 | - noam.tamim@kaltura.com
12 | - nilit.danan@kaltura.com
13 | - gilad.nadav@kaltura.com
14 | - oren.melamed@kaltura.com
15 | - sergey.chausov@kaltura.com
16 | on_success: change
17 | on_failure: always
18 |
--------------------------------------------------------------------------------
/TestApp/TestApp/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // TestApp
4 | //
5 | // Created by Noam Tamim on 21/10/2018.
6 | // Copyright © 2018 Kaltura. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 |
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/.github/release_notes.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -u
4 |
5 | PODSPEC=*.podspec
6 | POD=$(basename $PODSPEC .podspec)
7 |
8 | pod ipc spec $POD.podspec > spec.json
9 |
10 | POD_NAME=$(jq '.name' --raw-output spec.json)
11 | VERSION=$(jq '.version' --raw-output spec.json)
12 |
13 | sed -e "s#@@RELEASE_NOTES@@#- TBA#g" -e "s#@@POD_NAME@@#$POD_NAME#g" -e "s#@@POD_PREFERRED_VERSION@@#$VERSION#g" -i '' .github/release_notes_template.md
14 |
15 | cat .github/release_notes_template.md
16 |
--------------------------------------------------------------------------------
/TestApp/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :ios, '9.0'
3 |
4 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
5 | use_frameworks!
6 |
7 | def sharedPods
8 | pod 'PlayKit', :path => '..'
9 | #pod 'KalturaNetKit'#, :path => '../../netkit-ios'
10 | #pod 'PlayKitUtils'#, :path => '../../playkit-ios-utils'
11 | end
12 |
13 | target 'TestApp_v4_2' do
14 | sharedPods
15 | end
16 |
17 | target 'TestApp_v5' do
18 | sharedPods
19 | end
20 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description of the Changes
2 |
3 | Please add a detailed description of the change, weather it's an enhancement or a bugfix.
4 | If the PR is related to an open issue please link to it.
5 |
6 | ### CheckLists
7 |
8 | - [ ] changes have been done against master branch, and PR does not conflict
9 | - [ ] new unit / functional tests have been added (whenever applicable)
10 | - [ ] test are passing in local environment
11 | - [ ] Travis tests are passing (or test results are not worse than on master branch :))
12 | - [ ] Docs have been updated
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | ## Build generated
4 | build/
5 | DerivedData/
6 |
7 | ## Various settings
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata/
17 |
18 | ## Other
19 | *.moved-aside
20 | *.xccheckout
21 | *.xcscmblueprint
22 |
23 | # CocoaPods
24 | Pods/
25 |
26 | # Swift Package Manager
27 | #
28 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
29 | # Packages/
30 | # Package.pins
31 | Package.resolved
32 | .build/
33 |
--------------------------------------------------------------------------------
/Example/Sources/Entries.json:
--------------------------------------------------------------------------------
1 | {
2 | "m001": {
3 | "duration": 60000,
4 | "id": "m001",
5 | "name": "demo1",
6 | "sources": [
7 | {
8 | "id": "m001s001",
9 | "mimeType": "application/dash+xml",
10 | "url": "http://cfvod.kaltura.com/dasha/p/1851571/sp/185157100/serveFlavor/entryId/0_pl5lbfo0/v/2/flavorId/0_,zwq3l44r,otmaqpnf,ywkmqnkg,/forceproxy/true/name/a.mp4.urlset/manifest.mpd"
11 | }
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Classes/Player/PKController.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | @objc public protocol PKController {
14 | init(player: PlayerEngine?)
15 | }
16 |
--------------------------------------------------------------------------------
/Classes/Providers/PlaylistProvider.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2021 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 | //
11 |
12 | import Foundation
13 |
14 | @objc public protocol PlaylistProvider {
15 |
16 | func loadPlaylist(callback: @escaping (PKPlaylist?, Error?) -> Void)
17 |
18 | func cancel()
19 | }
20 |
--------------------------------------------------------------------------------
/Classes/Player/VR/VRData.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 |
14 | /// This class will be filled with some relevant properties at the future.
15 | /// For now this class is allocated and uses as indicator when VR content is detected.
16 | class VRData {
17 | }
18 |
--------------------------------------------------------------------------------
/Classes/Platform.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | struct Platform {
14 | static let isSimulator: Bool = {
15 | var isSim = false
16 | #if arch(i386) || arch(x86_64)
17 | isSim = true
18 | #endif
19 | return isSim
20 | }()
21 | }
22 |
--------------------------------------------------------------------------------
/Classes/Plugins/TimeIntervalExtension.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 |
13 | extension TimeInterval {
14 |
15 | public func toInt32() -> Int32 {
16 | if !self.isNaN && !self.isInfinite {
17 | return Int32(self)
18 | }
19 | return 0
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/tag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -u
4 |
5 | PODSPEC=*.podspec
6 | POD=$(basename $PODSPEC .podspec)
7 |
8 | pod ipc spec $POD.podspec > spec.json
9 |
10 | TARGET_TAG=$(jq '.source.tag' --raw-output spec.json)
11 | NAME=$(jq '.name' --raw-output spec.json)
12 | COMMIT_SHA=$(git rev-parse HEAD)
13 |
14 | cat << EOF > post.json
15 | {
16 | "ref": "refs/tags/$TARGET_TAG",
17 | "sha": "$COMMIT_SHA"
18 | }
19 | EOF
20 |
21 | POST_URL=https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs
22 |
23 | curl $POST_URL -X POST -H "Content-Type: application/json" -H "authorization: Bearer $GITHUB_TOKEN" -d@post.json
24 |
25 | #Add current tag to job output
26 | echo "tag=${TARGET_TAG}" >> $GITHUB_OUTPUT
27 |
28 | echo "$NAME release tag added and it is ready for CocoaPods distribution, upcoming version is going to be $TARGET_TAG"
29 |
--------------------------------------------------------------------------------
/Example/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Classes/Player/Tracks/PKTracks.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | @objc public class PKTracks: NSObject {
14 | @objc public var audioTracks: [Track]?
15 | @objc public var textTracks: [Track]?
16 |
17 | init(audioTracks: [Track]?, textTracks: [Track]?) {
18 | PKLog.verbose("init::")
19 | self.audioTracks = audioTracks
20 | self.textTracks = textTracks
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/Classes/Events/PKMessageBus.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2021 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 | //
11 | // PKMessageBus.swift
12 | // PlayKit
13 | //
14 | // Created by Sergii Chausov on 07.11.2021.
15 | //
16 |
17 | import Foundation
18 |
19 | public protocol PKMessageBus {
20 | func getMessageBus() -> MessageBus
21 | }
22 |
23 | extension PlayerLoader: PKMessageBus {
24 |
25 | func getMessageBus() -> MessageBus {
26 | return self.messageBus
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Plugins/AnalyticsCommon/AnalyticsConfig.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 |
13 | @objc public class AnalyticsConfig: NSObject {
14 |
15 | @objc public var params: [String: Any]
16 |
17 | @objc public init(params: [String: Any]) {
18 | self.params = params
19 | super.init()
20 | }
21 |
22 | @objc public override init() {
23 | self.params = [:]
24 | super.init()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | ##### Prerequisites
3 | - [ ] Have you checked for duplicate [issues](https://github.com/kaltura/playkit-ios/issues): ______
4 | - [ ] Which Player [version](https://github.com/kaltura/playkit-ios/releases) are you using: ______
5 | - [ ] Can you reproduce the issue with our latest release version: ______
6 | - [ ] Can you reproduce the issue with the latest code from master: ______
7 | - [ ] What devices and OS versions are you using: ______
8 | - [ ] If applicable, add test code or test page to reproduce:
9 | ```
10 | Paste test code here
11 | ```
12 |
13 | ##### Expected behavior
14 | What you expected to happen
15 |
16 | ##### Actual behavior
17 | What actually happened
18 |
19 | ##### Console output
20 | ```
21 | Paste the contents of the Xcode console here.
22 | ```
23 |
--------------------------------------------------------------------------------
/Example/PlayKit/ViewController.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 | import PlayKit
13 |
14 |
15 |
16 |
17 | class ViewController: UIViewController {
18 |
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 |
23 | }
24 |
25 | override func didReceiveMemoryWarning() {
26 | super.didReceiveMemoryWarning()
27 | // Dispose of any resources that can be recreated.
28 | }
29 |
30 | }
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Classes/Providers/MediaEntryProviderResponseDelegate.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import KalturaNetKit
13 |
14 | //This protocol provides a way to use the response data of the requests are being sent by MediaEntryProvider.
15 | //For example the response of getPlaybackContext, or additional meta data.
16 |
17 | public protocol PKMediaEntryProviderResponseDelegate: AnyObject {
18 |
19 | func providerGotResponse(sender: MediaEntryProvider?, response: Response) -> Void
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Example/PlayKit/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | }
43 | ],
44 | "info" : {
45 | "version" : 1,
46 | "author" : "xcode"
47 | }
48 | }
--------------------------------------------------------------------------------
/Classes/Events/EventsPayloads/PKAdDAICuePoints.swift:
--------------------------------------------------------------------------------
1 |
2 |
3 | import Foundation
4 |
5 | @objc public class CuePoint: NSObject {
6 | @objc public private(set) var startTime: TimeInterval
7 | @objc public private(set) var endTime: TimeInterval
8 | @objc public private(set) var played: Bool
9 |
10 | @objc public init(startTime: TimeInterval, endTime: TimeInterval, played: Bool) {
11 | self.startTime = startTime
12 | self.endTime = endTime
13 | self.played = played
14 | }
15 |
16 | public override var description: String {
17 | return "StartTime:\(startTime) EndTime:\(endTime) Played:\(played)"
18 | }
19 | }
20 |
21 | @objc public class PKAdDAICuePoints: NSObject {
22 |
23 | @objc public private(set) var cuePoints: [CuePoint]
24 |
25 | @objc public init(_ cuePoints: [CuePoint]) {
26 | self.cuePoints = cuePoints
27 | }
28 |
29 | @objc public var hasPreRoll: Bool {
30 | return self.cuePoints.filter { $0.startTime == 0 }.count > 0 // pre-roll ads values = 0
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import PlayKit
13 |
14 | @objc public protocol AnalyticsPluginProtocol: PKPlugin {
15 |
16 | /// Indicates if it first play.
17 | var isFirstPlay: Bool { get set }
18 |
19 | /// List of events should be handled on plugin.
20 | var playerEventsToRegister: [PlayerEvent.Type] { get }
21 |
22 | /// Event registrasion based on playerEventsToRegister array.
23 | func registerEvents()
24 |
25 | /// unregister all registered events.
26 | func unregisterEvents()
27 | }
28 |
--------------------------------------------------------------------------------
/Classes/Events/PluginEvent.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2018 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | //
12 | // PluginEvent.swift
13 | // PlayKit
14 | //
15 | // Created by Nilit Danan on 11/14/18.
16 | //
17 |
18 | import Foundation
19 |
20 | @objc public class PluginEvent: PKEvent {
21 |
22 | /// Sent when a plugin error occurs.
23 | @objc public static let error: PluginEvent.Type = Error.self
24 |
25 | public class Error: PluginEvent {
26 | public convenience init(nsError: NSError) {
27 | self.init([EventDataKeys.error: nsError])
28 | }
29 |
30 | public convenience init(error: PKError) {
31 | self.init([EventDataKeys.error: error.asNSError])
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Classes/Player/Tracks/Track.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | internal enum TrackType {
14 | case audio
15 | case text
16 | }
17 |
18 | @objc public class Track: NSObject {
19 | @objc public var id: String
20 | @objc public var title: String
21 | @objc public var language: String?
22 |
23 | var type: TrackType
24 |
25 | init(id: String, title: String, type: TrackType, language: String?) {
26 | PKLog.verbose("init:: id:\(String(describing: id)) title:\(String(describing: title)) language: \(String(describing: language))")
27 |
28 | self.id = id
29 | self.title = title
30 | self.type = type
31 | self.language = language
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Classes/Plugins/PlayerPluginsDataSource.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2020 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | public protocol PlayerPluginsDataSource {
14 | /// Filtering loaded plugins by certain type.
15 | func getLoadedPlugins(ofType type: T.Type) -> [T]
16 |
17 | func isPluginLoaded(pluginName name: String) -> Bool
18 | }
19 |
20 | extension PlayerLoader: PlayerPluginsDataSource {
21 | public func getLoadedPlugins(ofType type: T.Type) -> [T] {
22 | return self.loadedPlugins
23 | .filter { $0.value.plugin is T }
24 | .compactMap { $0.value.plugin as? T }
25 | }
26 |
27 | public func isPluginLoaded(pluginName name: String) -> Bool {
28 | return self.loadedPlugins.contains { $0.key == name }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | source 'https://github.com/CocoaPods/Specs.git'
4 |
5 | platform :ios, '9.0'
6 |
7 | abstract_target 'PlayKit' do
8 |
9 | pod 'PlayKit', :path => '../../playkit-ios'
10 | pod 'PlayKit/YouboraPlugin', :path => '../../playkit-ios'
11 | pod 'PlayKit/PhoenixPlugin', :path => '../../playkit-ios'
12 | pod 'PlayKit/KalturaStatsPlugin', :path => '../../playkit-ios'
13 | pod 'PlayKit/KalturaLiveStatsPlugin', :path => '../../playkit-ios'
14 | pod 'PlayKit/GoogleCastAddon', :path => '../../playkit-ios'
15 | target 'PlayKit_Example' do
16 | end
17 |
18 | target 'PlayKit_Tests' do
19 | pod 'Quick', '1.1.0'
20 | pod 'Nimble', '7.0.1'
21 | end
22 |
23 | end
24 |
25 | pre_install do |installer|
26 | def installer.verify_no_static_framework_transitive_dependencies; end
27 | end
28 |
29 | post_install do |installer|
30 | installer.pods_project.targets.each do |target|
31 | target.build_configurations.each do |config|
32 | config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = 'NO'
33 | if target.name == 'PlayKit'
34 | config.build_settings['SWIFT_VERSION'] = '4.0'
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/Classes/Extensions/ArrayExtension.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | extension Array where Element: Equatable {
14 |
15 | /**
16 | Remove an element from the array and invalidates all indexes.
17 |
18 | - parameter element: The element to remove.
19 | */
20 | mutating func remove(element: Element) {
21 | if let index = self.firstIndex(of: element) {
22 | self.remove(at: index)
23 | }
24 | }
25 |
26 | /**
27 | Removes elements from the array and invalidates all indexes.
28 |
29 | - parameter elements: The array with the elements to delete.
30 | */
31 | mutating func remove(elements: [Element]) {
32 | for element in elements {
33 | remove(element: element)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Classes/Events/EventsPayloads/PKAdCuePoints.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | @objc public class PKAdCuePoints: NSObject {
14 |
15 | @objc public private(set) var cuePoints: [TimeInterval]
16 |
17 | @objc public init(cuePoints: [TimeInterval]) {
18 | self.cuePoints = cuePoints.sorted() // makes sure array is sorted
19 | }
20 |
21 | @objc public var count: Int {
22 | return self.cuePoints.count
23 | }
24 |
25 | @objc public var hasPreRoll: Bool {
26 | return self.cuePoints.filter { $0 == 0 }.count > 0 // pre-roll ads values = 0
27 | }
28 |
29 | @objc public var hasMidRoll: Bool {
30 | return self.cuePoints.filter { $0 > 0 }.count > 0
31 | }
32 |
33 | @objc public var hasPostRoll: Bool {
34 | return self.cuePoints.filter { $0 < 0 }.count > 0 // post-roll ads values = -1
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Classes/Models/PKTimeRange.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import CoreMedia
13 |
14 | @objc public class PKTimeRange: NSObject {
15 | @objc public let start: TimeInterval
16 | @objc public let end: TimeInterval
17 | @objc public let duration: TimeInterval
18 |
19 | @objc public override var description: String {
20 | return "[\(String(describing: type(of: self)))] - start: \(self.start), end: \(self.end), duration: \(self.duration)"
21 | }
22 |
23 | init(start: TimeInterval, duration: TimeInterval) {
24 | self.start = start
25 | self.duration = duration
26 | self.end = start + duration
27 | }
28 |
29 | convenience init(timeRange: CMTimeRange) {
30 | let start = CMTimeGetSeconds(timeRange.start)
31 | let duration = CMTimeGetSeconds(timeRange.duration)
32 | self.init(start: start, duration: duration)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Classes/Player/PKAsset.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import AVFoundation
13 |
14 | @objc enum PKAssetStatus: Int, CustomStringConvertible {
15 | case new
16 | case preparing
17 | case prepared
18 | case faild
19 |
20 | var description: String {
21 | switch self {
22 | case .new: return "new"
23 | case .preparing: return "preparing"
24 | case .prepared: return "prepared"
25 | case .faild: return "faild"
26 | }
27 | }
28 | }
29 |
30 | class PKAsset: NSObject {
31 | let avAsset: AVURLAsset
32 | let playerSettings: PKPlayerSettings
33 | let autoBuffer: Bool
34 | @objc dynamic var status: PKAssetStatus = .new
35 |
36 | init(avAsset: AVURLAsset, playerSettings: PKPlayerSettings, autoBuffer: Bool) {
37 | self.avAsset = avAsset
38 | self.playerSettings = playerSettings.createCopy()
39 | self.autoBuffer = autoBuffer
40 |
41 | super.init()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Classes/Player/Protocols/PlayerEngine.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | @objc public protocol PlayerEngine: BasicPlayer {
14 | /// Fired when an event is triggred.
15 | var onEventBlock: ((PKEvent) -> Void)? { get set }
16 |
17 | /// The player's start position.
18 | var startPosition: TimeInterval { get set }
19 |
20 | /// The player's current position.
21 | var currentPosition: TimeInterval { get set }
22 |
23 | /// The current media config that was set.
24 | var mediaConfig: MediaConfig? { get set }
25 |
26 | /// The media playback type.
27 | var playbackType: String? { get }
28 |
29 | /// Load the media to the player.
30 | func loadMedia(from mediaSource: PKMediaSource?, handler: AssetHandler)
31 |
32 | /// Plays the live media from the live edge.
33 | func playFromLiveEdge()
34 |
35 | /// Update the text tracks styling.
36 | func updateTextTrackStyling(_ textTrackStyling: PKTextTrackStyling)
37 | }
38 |
--------------------------------------------------------------------------------
/Example/PlayKit/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | PlayKit
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 3.2.x-dev
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 |
32 | UILaunchStoryboardName
33 | LaunchScreen
34 | UIMainStoryboardFile
35 | Main
36 | UIRequiredDeviceCapabilities
37 |
38 | armv7
39 |
40 | UISupportedInterfaceOrientations
41 |
42 | UIInterfaceOrientationPortrait
43 | UIInterfaceOrientationLandscapeLeft
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/travis-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eou pipefail
4 |
5 | # Travis aborts the build if it doesn't get output for 10 minutes.
6 | keepAlive() {
7 | while [ -f $1 ]
8 | do
9 | sleep 10
10 | echo .
11 | done
12 | }
13 |
14 | buildiOSApp() {
15 | echo Building the iOS TestApp
16 | cd TestApp
17 | pod install
18 | CODE=0
19 | xcodebuild clean build -workspace TestApp.xcworkspace -scheme TestApp_v4_2 ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,name=iPhone 11' | tee xcodebuild.log | xcpretty -r html || CODE=$?
20 | xcodebuild clean build -workspace TestApp.xcworkspace -scheme TestApp_v5 ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,name=iPhone 11' | tee xcodebuild.log | xcpretty -r html || CODE=$?
21 | cd ../
22 | export CODE
23 | }
24 |
25 | buildtvOSApp() {
26 | echo Building the tvOS TestApp
27 | cd tvOSTestApp
28 | pod install
29 | CODE=0
30 | xcodebuild clean build -workspace tvOSTestApp.xcworkspace -scheme tvOSTestApp ONLY_ACTIVE_ARCH=NO -destination 'platform=tvOS Simulator,name=Apple TV' | tee xcodebuild.log | xcpretty -r html || CODE=$?
31 | cd ../
32 | export CODE
33 | }
34 |
35 | libLint() {
36 | echo Linting the pod
37 | pod lib lint --fail-fast --allow-warnings
38 | }
39 |
40 | FLAG=$(mktemp)
41 |
42 | if [ "$TRAVIS_EVENT_TYPE" == "cron" ] || [ -n "$TRAVIS_TAG" ]; then
43 | # Full build at night and tags
44 | keepAlive $FLAG &
45 | libLint
46 |
47 | else
48 | # Else just build the test app
49 | buildiOSApp
50 | buildtvOSApp
51 | fi
52 |
53 | rm $FLAG # stop keepAlive
54 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ "develop" ]
6 | paths-ignore:
7 | - 'docs/**'
8 | - 'iOSTestApp/**'
9 | - 'tvOSTestApp/**'
10 | - 'Example/**'
11 | - 'TestApp/**'
12 | - 'LICENSE'
13 | - '*.md'
14 |
15 | concurrency:
16 | group: ci-${{ github.ref }}
17 | cancel-in-progress: true
18 |
19 | jobs:
20 | build:
21 | name: "CocoaPods linting"
22 | runs-on: macOS-13
23 |
24 | steps:
25 | - uses: actions/checkout@v4
26 |
27 | - name: Updating Cocoapods
28 | run: gem install cocoapods
29 |
30 | - name: Updating CocoaPods repo
31 | run: pod repo update
32 |
33 | - name: Pod linting
34 | run: pod lib lint --fail-fast --verbose --allow-warnings
35 |
36 | SPM:
37 | name: "Build SPM"
38 | runs-on: macOS-13
39 |
40 | strategy:
41 | fail-fast: false
42 | matrix:
43 | include:
44 | - destination: "OS=17.2,name=iPhone 15"
45 | name: "iOS"
46 | scheme: "PlayKit-Package"
47 | - destination: "OS=17.0,name=Apple TV"
48 | name: "tvOS"
49 | scheme: "PlayKit-Package"
50 | steps:
51 | - name: Force Xcode 15
52 | run: sudo xcode-select -switch /Applications/Xcode_15.2.app
53 | - uses: actions/checkout@v4
54 | - name: ${{ matrix.name }}
55 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -scheme "${{ matrix.scheme }}" -destination "${{ matrix.destination }}" clean test | xcpretty
56 |
57 |
--------------------------------------------------------------------------------
/Classes/Providers/MediaEntryProvider.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 |
13 | @objc public protocol MediaEntryProvider {
14 | /**
15 | This method is triggering the creation of media base on custom parameters and actions.
16 |
17 | ## Important:
18 | - In order to write custom provider you should implement this method
19 | - In order to send an informative error in the ResponseElement
20 | you should implement an error enum with the relevnat errors
21 |
22 | - parameter callback - a block that called on completion and returing response object wich contain the PKMediaEntry
23 | ```
24 | // example of usage:
25 | let cp : MediaEntryProvider =
26 | CustomMediaEntryProvider(customParameters)
27 |
28 | customMediaProvider.loadMedia {
29 | (r:ResponseElement) in
30 | if (r.succedded){
31 | ...
32 | ```
33 |
34 | */
35 | func loadMedia(callback: @escaping (PKMediaEntry?, Error?) -> Void)
36 |
37 | func cancel()
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/banner-helper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | BANNER = """// ===================================================================================================
4 | // Copyright (C) %d Kaltura Inc.
5 | //
6 | // Licensed under the AGPLv3 license, unless a different license for a
7 | // particular library is specified in the applicable library path.
8 | //
9 | // You may obtain a copy of the License at
10 | // https://www.gnu.org/licenses/agpl-3.0.html
11 | // ===================================================================================================
12 | """
13 |
14 | import os
15 | from sys import exit
16 | from datetime import datetime
17 |
18 | FIRST_YEAR = 2017
19 | THIS_YEAR = datetime.now().year
20 |
21 | def has_banner(d):
22 | d = d.lstrip()
23 | for year in xrange(FIRST_YEAR, THIS_YEAR + 1):
24 | if d.startswith(BANNER % year):
25 | return True
26 | return False
27 |
28 | def process(dir):
29 | global modified_files
30 | banner = BANNER % THIS_YEAR
31 | for root, dirs, files in os.walk(dir):
32 | for name in files:
33 | fileExt = os.path.splitext(name)[1]
34 | if fileExt != '.swift':
35 | continue
36 |
37 | fullPath = os.path.join(root, name)
38 | d = file(fullPath, 'rb').read()
39 |
40 | if has_banner(d):
41 | continue
42 |
43 | d = banner + '\n' + d
44 |
45 | file(fullPath, 'wb').write(d)
46 |
47 | modified_files += 1
48 |
49 |
50 | modified_files = 0
51 |
52 | for dir in ('Addons', 'Classes', 'Plugins', 'Widevine'):
53 | process(dir)
54 |
55 | # exit with 0 (OK) if there was at least one change.
56 | exit(0 if modified_files > 0 else 1)
57 |
--------------------------------------------------------------------------------
/Example/Tests/Helpers/PluginTestConfiguration.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | enum PluginTestConfiguration {
14 | case TVPAPI
15 | case Phoenix
16 |
17 | mutating func next() {
18 | switch self {
19 | case .TVPAPI: self = .Phoenix
20 | case .Phoenix: self = .TVPAPI
21 | }
22 | }
23 |
24 | var pluginName: String {
25 | switch self {
26 | case .TVPAPI: return "TVPAPIAnalyticsPluginMock"
27 | case .Phoenix: return "PhoenixAnalyticsPluginMock"
28 | }
29 | }
30 |
31 | var paramsDict: [String : Any] {
32 | switch self {
33 | case .TVPAPI: return [
34 | "fileId": "464302",
35 | "baseUrl": "http://tvpapi-preprod.ott.kaltura.com/v3_9/gateways/jsonpostgw.aspx?",
36 | "timerInterval":30,
37 | "initObj": ["": ""]
38 | ]
39 | case .Phoenix: return [
40 | "fileId": "464302",
41 | "baseUrl": "http://api-preprod.ott.kaltura.com/v4_1/api_v3/",
42 | "partnerId": 198,
43 | "timerInterval": 30
44 | ]
45 | }
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/TestApp/TestApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/TestApp/TestApp/TestApp_v5-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: PR
2 |
3 | on:
4 | pull_request:
5 | branches: [ "develop" ]
6 | paths-ignore:
7 | - 'docs/**'
8 | - 'iOSTestApp/**'
9 | - 'tvOSTestApp/**'
10 | - 'Example/**'
11 | - 'TestApp/**'
12 | - 'LICENSE'
13 | - '*.md'
14 |
15 | concurrency:
16 | group: PR_${{ github.head_ref }}
17 | cancel-in-progress: true
18 |
19 | jobs:
20 | build:
21 | name: "CocoaPods linting"
22 | environment: PR
23 | runs-on: macos-13
24 |
25 | steps:
26 | - uses: actions/checkout@v4
27 |
28 | - name: Updating Cocoapods
29 | run: gem install cocoapods
30 |
31 | - name: Updating CocoaPods repo
32 | run: pod repo update
33 |
34 | - name: Pod linting
35 | run: pod lib lint --fail-fast --verbose --allow-warnings
36 |
37 | SPM:
38 | name: "Build SPM"
39 | environment: PR
40 | runs-on: macos-13
41 | strategy:
42 | fail-fast: false
43 | matrix:
44 | include:
45 | - destination: "OS=17.2,name=iPhone 15"
46 | name: "iOS"
47 | scheme: "PlayKit-Package"
48 | - destination: "OS=17.0,name=Apple TV"
49 | name: "tvOS"
50 | scheme: "PlayKit-Package"
51 | steps:
52 | - name: Force Xcode 15
53 | run: sudo xcode-select -switch /Applications/Xcode_15.2.app
54 | - uses: actions/checkout@v4
55 | - name: ${{ matrix.name }}
56 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -scheme "${{ matrix.scheme }}" -destination "${{ matrix.destination }}" clean test | xcpretty
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Example/PlayKit/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 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/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 |
--------------------------------------------------------------------------------
/TestApp/TestApp/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 |
--------------------------------------------------------------------------------
/Classes/PKRequestParams.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | /// `PKRequestParamsDecorator` used for getting updated request info
14 | @objc public protocol PKRequestParamsAdapter {
15 | /// Called when need to update the request adapter with information from the player.
16 | /// Use this to update the adapter with any information available from the player.
17 | ///
18 | /// For example, when media session id changes.
19 | @objc func updateRequestAdapter(with player: Player)
20 | @objc func adapt(requestParams: PKRequestParams) -> PKRequestParams
21 | }
22 |
23 | @objc public class PKRequestParams: NSObject {
24 |
25 | @objc public let url: URL
26 | @objc public let headers: [String: String]?
27 |
28 | @objc public init(url: URL, headers: [String: String]?) {
29 | self.url = url
30 | self.headers = headers
31 | }
32 | }
33 |
34 | extension PKRequestParamsAdapter {
35 | func base64(from: String) -> String {
36 | return from.data(using: .utf8)?.base64EncodedString() ?? ""
37 | }
38 |
39 | func percentEncoded(_ string: String) -> String {
40 | string.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowedCharacterSet) ?? string
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/TestApp/TestApp/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 |
--------------------------------------------------------------------------------
/Example/Tests/Analytics/AnalyticsPluginConfig.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | enum AnalyticsPluginConfig {
14 | case TVPAPI
15 | case Phoenix
16 |
17 | mutating func next() {
18 | switch self {
19 | case .TVPAPI: self = .Phoenix
20 | case .Phoenix: self = .TVPAPI
21 | }
22 | }
23 |
24 | var pluginName: String {
25 | switch self {
26 | case .TVPAPI: return PhoenixAnalyticsPluginMock.pluginName
27 | case .Phoenix: return TVPAPIAnalyticsPluginMock.pluginName
28 | }
29 | }
30 |
31 | var paramsDict: [String : Any] {
32 | switch self {
33 | case .TVPAPI: return [
34 | "fileId": "464302",
35 | "baseUrl": "http://tvpapi-preprod.ott.kaltura.com/v3_9/gateways/jsonpostgw.aspx?",
36 | "timerInterval":30000,
37 | "initObj": ""
38 | ]
39 | case .Phoenix: return [
40 | "fileId": "464302",
41 | "baseUrl": "http://api-preprod.ott.kaltura.com/v4_1/api_v3/",
42 | "ks": "djJ8MTk4fL1W9Rs4udDqNt_CpUT9dJKk1laPk9_XnBtUaq7PXVcVPYrXz2shTbKSW1G5Lhn_Hvbbnh0snheANOmSodl7Puowxhk2WYkpmNugi9vNAg5C",
43 | "partnerId": 198,
44 | "timerInterval": 30
45 | ]
46 | }
47 | }
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/PlayKit.podspec:
--------------------------------------------------------------------------------
1 | suffix = '.0000' # Dev mode
2 | # suffix = '' # Release
3 |
4 | Pod::Spec.new do |s|
5 |
6 | s.name = 'PlayKit'
7 | s.version = '3.31.0' + suffix
8 | s.summary = 'PlayKit: Kaltura Mobile Player SDK - iOS'
9 | s.homepage = 'https://github.com/kaltura/playkit-ios'
10 | s.license = { :type => 'AGPLv3', :text => 'AGPLv3' }
11 | s.author = { 'Kaltura' => 'community@kaltura.com' }
12 | s.source = { :git => 'https://github.com/kaltura/playkit-ios.git', :tag => 'v' + s.version.to_s }
13 | s.swift_version = '5.0'
14 |
15 | s.ios.deployment_target = '15.0'
16 | s.tvos.deployment_target = '15.0'
17 |
18 | s.subspec 'Core' do |sp|
19 | sp.source_files = 'Classes/**/*'
20 | sp.dependency 'kSwiftyJSON', '5.0.8'
21 | sp.dependency 'XCGLogger', '7.1.5'
22 | sp.dependency 'KalturaNetKit', '~> 1.7.0'
23 | sp.dependency 'PlayKitUtils', '~> 0.7'
24 | end
25 |
26 | s.subspec 'WidevineClassic' do |ssp|
27 | ssp.ios.deployment_target = '15.0'
28 | ssp.source_files = 'Widevine'
29 | ssp.dependency 'PlayKit/Core'
30 | #ssp.dependency 'PlayKitWV'
31 | #ssp.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'GCC_PREPROCESSOR_DEFINITIONS'=>'WIDEVINE_ENABLED=1',
32 | # 'OTHER_SWIFT_FLAGS' => '$(inherited) -DWIDEVINE_ENABLED' }
33 | end
34 |
35 | s.subspec 'AnalyticsCommon' do |ssp|
36 | ssp.source_files = 'Plugins/AnalyticsCommon'
37 | ssp.xcconfig = {
38 | 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES',
39 | 'OTHER_LDFLAGS' => '$(inherited)',
40 | 'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "${PODS_ROOT}"/**',
41 | 'LIBRARY_SEARCH_PATHS' => '$(inherited) "${PODS_ROOT}"/**'
42 | }
43 | ssp.dependency 'PlayKit/Core'
44 | end
45 |
46 | s.default_subspec = 'Core'
47 |
48 | end
49 |
--------------------------------------------------------------------------------
/Example/Tests/Basic/SourceSelectorTest.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import XCTest
12 | import AVFoundation
13 | @testable import PlayKit
14 |
15 | class SourceSelectorTest: XCTestCase {
16 |
17 | let mp4 = PKMediaSource("mp4", contentUrl: URL(string: "https://example.com/a.mp4"), mediaFormat: .mp4)
18 | let hls = PKMediaSource("hls", contentUrl: URL(string: "https://example.com/hls.m3u8"), mediaFormat: .hls)
19 | let fps = PKMediaSource("fps", contentUrl: URL(string: "https://example.com/fps.m3u8"), mediaFormat: .hls)
20 | let wvm = PKMediaSource("wvm", contentUrl: URL(string: "https://example.com/a.wvm"), mediaFormat: .wvm )
21 |
22 | func testSelectedSource() {
23 |
24 | guard let preferredMedia = DefaultAssetHandler.getPreferredMediaSource(from: PKMediaEntry("e",
25 | sources: [mp4, hls, fps])) else {
26 | XCTFail()
27 | return
28 | }
29 |
30 | DefaultAssetHandler().build(from: preferredMedia.0) { (error: Error?, asset: AVURLAsset?) in
31 | XCTAssertNil(error)
32 |
33 | guard let asset = asset else {
34 | XCTFail()
35 | return
36 | }
37 |
38 | XCTAssertEqual(asset.url.lastPathComponent, "hls.m3u8")
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Classes/Player/Media/PKMediaEntry+Copying.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | extension PKMediaEntry: NSCopying {
14 |
15 | public func copy(with zone: NSZone? = nil) -> Any {
16 |
17 | let sources = self.sources?.compactMap{ $0.copy() as? PKMediaSource }
18 |
19 | let entry = PKMediaEntry(self.id,
20 | sources: sources,
21 | duration: self.duration)
22 | entry.mediaType = self.mediaType
23 | entry.metadata = self.metadata
24 | entry.name = self.name
25 | entry.externalSubtitles = self.externalSubtitles
26 | entry.thumbnailUrl = self.thumbnailUrl
27 | entry.tags = self.tags
28 |
29 | return entry
30 | }
31 | }
32 |
33 | extension PKMediaSource: NSCopying {
34 |
35 | public func copy(with zone: NSZone? = nil) -> Any {
36 | let source = PKMediaSource(self.id,
37 | contentUrl: self.contentUrl,
38 | mimeType: self.mimeType,
39 | drmData: self.drmData,
40 | mediaFormat: self.mediaFormat)
41 |
42 | source.externalSubtitle = self.externalSubtitle
43 | source.contentRequestAdapter = self.contentRequestAdapter
44 |
45 | return source
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Classes/Player/Playlist/PKPlaylist.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2021 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 | //
11 |
12 | import Foundation
13 | import kSwiftyJSON
14 |
15 | fileprivate let idKey = "id"
16 | fileprivate let nameKey = "name"
17 | fileprivate let thumbnailUrlKey = "thumbnailUrl"
18 | fileprivate let mediasKey = "medias"
19 |
20 | @objc public class PKPlaylist: NSObject {
21 |
22 | @objc public var id: String?
23 | @objc public var name: String?
24 | @objc public var thumbnailUrl: String?
25 | @objc public var medias: [PKMediaEntry]?
26 |
27 | @objc override public var description: String {
28 | get {
29 | return "id : \(self.id ?? "empty")," +
30 | " name: \(self.name ?? "empty")," +
31 | " thumbnailUrl: \(self.thumbnailUrl ?? "empty")"
32 | }
33 | }
34 |
35 | internal init(id: String?) {
36 | self.id = id
37 | super.init()
38 | }
39 |
40 | @objc public init(id: String?, name: String?, thumbnailUrl: String?, medias: [PKMediaEntry]) {
41 | self.id = id
42 | self.name = name
43 | self.thumbnailUrl = thumbnailUrl
44 | self.medias = medias
45 | super.init()
46 | }
47 |
48 | @objc public init(json: Any) {
49 | let jsonObject = json as? JSON ?? JSON(json)
50 | self.id = jsonObject[idKey].string
51 | self.name = jsonObject[nameKey].string
52 |
53 | super.init()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Example/Tests/MediaEntryProvider/RequestBuilder/KalturaRequestBuilderTest.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import XCTest
12 | import Quick
13 | import Nimble
14 | @testable import PlayKit
15 | @testable import KalturaNetKit
16 | import kSwiftyJSON
17 |
18 | class KalturaRequestBuilderTest: QuickSpec {
19 |
20 | override func spec() {
21 | describe("KalturaRequestBuilder Test") {
22 | it("should build a request") {
23 | let expectedUrl = "https://cdnapisec.kaltura.com/service/test/action/add"
24 | let expectedBody = JSON(["test": "test"])
25 |
26 | let url = "https://cdnapisec.kaltura.com"
27 | let service = "test"
28 | let action = "add"
29 | let kalturaRequestBuilder: KalturaRequestBuilder! = KalturaRequestBuilder(url: url, service: service, action: action)
30 | expect(kalturaRequestBuilder).toNot(beNil())
31 | // the result
32 | let request = kalturaRequestBuilder.setBody(key: "test", value: "test").build()
33 | // check the result against expected
34 | let parsedRequestBody = JSON.init(parseJSON: String.init(data: request.dataBody!, encoding: .utf8)!)
35 | expect(expectedBody).to(equal(parsedRequestBody))
36 | expect(expectedUrl).to(equal(request.url.absoluteString))
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // tvOSTestApp
4 | //
5 | // Created by Nilit Danan on 14/06/2022.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | func applicationWillResignActive(_ application: UIApplication) {
22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
24 | }
25 |
26 | func applicationDidEnterBackground(_ application: UIApplication) {
27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
28 | }
29 |
30 | func applicationWillEnterForeground(_ application: UIApplication) {
31 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
32 | }
33 |
34 | func applicationDidBecomeActive(_ application: UIApplication) {
35 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
36 | }
37 |
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Classes/PKLog.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import XCGLogger
12 | import Foundation
13 |
14 | /// `PKLogLevel` describes the available log levels.
15 | @objc public enum PKLogLevel: Int, CustomStringConvertible {
16 | case verbose, debug, info, warning, error
17 |
18 | static let `default` = PKLogLevel.debug
19 |
20 | public var description: String {
21 | return String(describing: self).uppercased()
22 | }
23 |
24 | /// converts our levels to the levels of logger we wrap
25 | var toLoggerLevel: XCGLogger.Level {
26 | switch self {
27 | case .verbose: return .verbose
28 | case .debug: return .debug
29 | case .info: return .info
30 | case .warning: return .warning
31 | case .error: return .error
32 | }
33 | }
34 | }
35 |
36 | public let PKLog: XCGLogger = {
37 | let logger = XCGLogger(identifier: "PlayKit")
38 | logger.outputLevel = PKLogLevel.default.toLoggerLevel
39 | return logger
40 | }()
41 |
42 | // For compatibility with the old logger -- in XCGLogger 'trace' is called 'verbose'.
43 | public extension XCGLogger {
44 | func trace(_ closure: @autoclosure () -> Any?, functionName: StaticString = #function, fileName: StaticString = #file, lineNumber: Int = #line, userInfo: [String: Any] = [:]) {
45 | self.logln(.verbose, functionName: functionName, fileName: fileName, lineNumber: lineNumber, userInfo: userInfo, closure: closure)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tvOSTestApp/tvOSTestApp/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 |
--------------------------------------------------------------------------------
/TestApp/TestApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Classes/Player/PlayerState.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2018 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | // ===================================================================================================
12 | // Copyright (C) 2017 Kaltura Inc.
13 | //
14 | // Licensed under the AGPLv3 license, unless a different license for a
15 | // particular library is specified in the applicable library path.
16 | //
17 | // You may obtain a copy of the License at
18 | // https://www.gnu.org/licenses/agpl-3.0.html
19 | // ===================================================================================================
20 |
21 | import Foundation
22 |
23 | /// An PlayerState is an enum of different player states
24 | @objc public enum PlayerState: Int, CustomStringConvertible {
25 | /// Sent when player's state idle.
26 | case idle
27 | /// Sent when player's state ready.
28 | case ready
29 | /// Sent when player's state buffering.
30 | case buffering
31 | /// Sent when player's state ended.
32 | /// Same event sent when observing PlayerEvent.ended.
33 | /// This state was attached to reflect current state and avoid unrelevant boolean.
34 | case ended
35 | /// Sent when player's state errored.
36 | case error
37 | /// Sent when player's state unknown.
38 | case unknown = -1
39 |
40 | public var description: String {
41 | switch self {
42 | case .idle: return "Idle"
43 | case .ready: return "Ready"
44 | case .buffering: return "Buffering"
45 | case .ended: return "Ended"
46 | case .error: return "Error"
47 | case .unknown: return "Unknown"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Classes/Plugins/BasePlugin.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | /// class `BasePlugin` is a base plugin object used for plugin subclasses
14 | @objc open class BasePlugin: NSObject, PKPlugin {
15 |
16 | /// abstract implementation subclasses will have names
17 | @objc open class var pluginName: String {
18 | fatalError("abstract property must be overriden in subclass")
19 | }
20 |
21 | @objc open class var pluginVersion: String {
22 | guard let version = Bundle(for: self).object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else {
23 | return "?.?.?"
24 | }
25 | return version
26 | }
27 |
28 | @objc public weak var player: Player?
29 | @objc public weak var messageBus: MessageBus?
30 |
31 | @objc public required init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws {
32 | let pluginClass = type(of: self)
33 | PKLog.verbose("initializing plugin \(pluginClass)")
34 | self.player = player
35 | self.messageBus = messageBus
36 | }
37 |
38 | @objc open func onUpdateMedia(mediaConfig: MediaConfig) {
39 | PKLog.verbose("plugin \(type(of:self)) onUpdateMedia with media config: \(String(describing: mediaConfig))")
40 | }
41 |
42 | @objc open func onUpdateConfig(pluginConfig: Any) {
43 | PKLog.verbose("plugin \(type(of:self)) onUpdateConfig with media config: \(String(describing: pluginConfig))")
44 | }
45 |
46 | @objc open func destroy() {
47 | PKLog.verbose("destroying plugin \(type(of:self))")
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "PlayKit",
7 | platforms: [.iOS(.v14),
8 | .tvOS(.v14)],
9 | products: [.library(name: "PlayKit",
10 | targets: ["PlayKit"]),
11 | .library(name: "AnalyticsCommon",
12 | targets: ["AnalyticsCommon"])],
13 | dependencies: [
14 | .package(url: "https://github.com/imberezin/kSwiftyJSON.git", .upToNextMajor(from: "5.0.8")),
15 | .package(url: "https://github.com/DaveWoodCom/XCGLogger.git", .upToNextMajor(from: "7.1.5")),
16 | .package(name: "PlayKitUtils",
17 | url: "https://github.com/kaltura/playkit-ios-utils.git",
18 | .upToNextMinor(from: "0.7.0")),
19 | .package(name: "KalturaNetKit",
20 | url: "https://github.com/kaltura/netkit-ios.git",
21 | .upToNextMinor(from: "1.7.0")),
22 | .package(url: "https://github.com/Quick/Quick.git", .upToNextMajor(from: "7.5.0")),
23 | .package(url: "https://github.com/Quick/Nimble.git", .upToNextMajor(from: "13.3.0")),
24 | ],
25 | targets: [.target(name: "PlayKit",
26 | dependencies:
27 | [
28 | "kSwiftyJSON",
29 | "XCGLogger",
30 | .product(name: "PlayKitUtils", package: "PlayKitUtils"),
31 | .product(name: "KalturaNetKit", package: "KalturaNetKit"),
32 | ],
33 | path: "Classes/"),
34 | .target(name: "AnalyticsCommon",
35 | dependencies: ["PlayKit"],
36 | path: "Plugins/AnalyticsCommon/"),
37 | .testTarget(name: "PlayKitTests",
38 | dependencies: ["PlayKit", "Quick", "Nimble"],
39 | path: "Example/Tests/Basic/",
40 | exclude: [
41 |
42 | ])
43 | ]
44 | )
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 | [](https://github.com/kaltura/playkit-ios/actions/workflows/ci.yml)
4 | [](https://cocoapods.org/pods/PlayKit)
5 | [](https://cocoapods.org/pods/PlayKit)
6 | [](https://cocoapods.org/pods/PlayKit)
7 | [](https://www.codacy.com/app/PlayKit/playkit-ios?utm_source=github.com&utm_medium=referral&utm_content=kaltura/playkit-ios&utm_campaign=badger)
8 |
9 | # Kaltura Player SDK
10 |
11 | ### Demo: [Demo repo](https://github.com/kaltura/playkit-ios-samples).
12 |
13 | *If you are a Kaltura customer, please contact your Kaltura Customer Success Manager to help facilitate use of this component.*
14 |
15 | ## Overview
16 | The **Kaltura Player SDK** is fully native and introduces significant performance improvements. The SDK is intended to be integrated in any iOS application and includes the following features:
17 |
18 | * Online and Offline Playback
19 | * Live
20 | * Multi audio tracks
21 | * Multiple captions
22 | * Kaltura’s uDRM support (FairPlay, Widevine Classic)
23 | * VAST Support (IMA)
24 | * Kaltura analytics
25 | * Youbora
26 | * Chromecast and AirPlay support
27 |
28 | ## Usage Guides
29 | Please see our [VPaaS Documentation site](https://vpaas.kaltura.com/documentation/Mobile-Video-Player-SDKs/v3_iOS_Introduction.html).
30 |
31 | ## License and Copyright Information
32 | All code in this project is released under the [AGPLv3 license](http://www.gnu.org/licenses/agpl-3.0.html) unless a different license for a particular library is specified in the applicable library path.
33 |
34 | Copyright © Kaltura Inc. All rights reserved.
35 | Authors and contributors: See [GitHub contributors list](https://github.com/kaltura/playkit-ios/graphs/contributors).
36 |
--------------------------------------------------------------------------------
/Classes/Player/Ads/AdsDAIPlugin.swift:
--------------------------------------------------------------------------------
1 |
2 |
3 | import Foundation
4 |
5 | public protocol AdsDAIPlugin: AdsPlugin {
6 |
7 | /**
8 | * Returns the content time without ads for a given stream time. Returns the given stream time
9 | * for live streams.
10 | *
11 | * @param streamTime the stream time with inserted ads (in seconds)
12 | *
13 | * @return the content time that corresponds with the given stream time once ads are removed
14 | */
15 | func contentTime(forStreamTime streamTime: TimeInterval) -> TimeInterval
16 |
17 | /**
18 | * Returns the stream time with ads for a given content time. Returns the given content time
19 | * for live streams.
20 | *
21 | * @param contentTime the content time without any ads (in seconds)
22 | *
23 | * @return the stream time that corresponds with the given content time once ads are inserted
24 | */
25 | func streamTime(forContentTime contentTime: TimeInterval) -> TimeInterval
26 |
27 | /**
28 | * Returns the previous cuepoint for the given stream time. Retuns nil if no such cuepoint exists.
29 | * This is used to implement features like snap back, and called when the publisher detects that
30 | * the user seeked in order to force the user to watch an ad break they may have skipped over.
31 | *
32 | * @param streamTime the stream time that was seeked to.
33 | *
34 | * @return the previous Cuepoint for the given stream time.
35 | */
36 | func previousCuepoint(forStreamTime streamTime: TimeInterval) -> CuePoint?
37 |
38 | /**
39 | * Returns if the upcoming ad can be played, the duration of the ad and the ad's end time.
40 | * Returns nil in case no ad was found.
41 | *
42 | * @param streamTime the stream time to check for specific ad.
43 | *
44 | * @return (canPlay, duration, endTime) if the ad can be played or not, duration of the ad, the end time of the ad.
45 | */
46 | func canPlayAd(atStreamTime streamTime: TimeInterval) -> (canPlay: Bool, duration: TimeInterval, endTime: TimeInterval)?
47 | }
48 |
--------------------------------------------------------------------------------
/Classes/Plugins/PKPlugin.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 | import AVFoundation
13 |
14 | /// The `PKPlugin` protocol defines all the properties and methods required to define a plugin object.
15 | @objc public protocol PKPlugin {
16 | /// The plugin name.
17 | static var pluginName: String { get }
18 |
19 | /**
20 | The plugin version. The default (implemented in BasePlugin) is the plugin's bundle `CFBundleShortVersionString`:
21 | `Bundle(for: pluginClass).object(forInfoDictionaryKey: "CFBundleShortVersionString")`
22 | Override this function to provide a version from a different source.
23 |
24 | Example for overriding:
25 | ```
26 | @objc override public class var pluginVersion: String {
27 | return "1.2.3"
28 | }
29 | ```
30 |
31 | If `CFBundleShortVersionString` wasn't found (or is not a string), and no alternative implementation is
32 | provided, the string "?.?.?" is used.
33 |
34 | */
35 | static var pluginVersion: String { get }
36 |
37 | /// The player associated with the plugin
38 | weak var player: Player? { get }
39 | /// The messageBus associated with the plugin
40 | weak var messageBus: MessageBus? { get }
41 | /// On first load. used for doing initialization for the first time with the media config.
42 | init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws
43 | /// On update media. used to update the plugin with new media config when available.
44 | func onUpdateMedia(mediaConfig: MediaConfig)
45 | /// On update config. used to update the plugin config.
46 | func onUpdateConfig(pluginConfig: Any)
47 | /// Called on player destroy.
48 | func destroy()
49 | }
50 |
51 | @objc public protocol PKPluginWarmUp {
52 | static func warmUp()
53 | }
54 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 |
8 | SPM:
9 | name: "Build SPM"
10 | environment: Tag
11 | runs-on: macos-13
12 |
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | include:
17 | - destination: "OS=17.2,name=iPhone 15"
18 | name: "iOS"
19 | scheme: "PlayKit-Package"
20 | - destination: "OS=17.0,name=Apple TV"
21 | name: "tvOS"
22 | scheme: "PlayKit-Package"
23 | steps:
24 | - name: Force Xcode 15
25 | run: sudo xcode-select -switch /Applications/Xcode_15.2.app
26 | - uses: actions/checkout@v4
27 | - name: ${{ matrix.name }}
28 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild docbuild -scheme "${{ matrix.scheme }}" -destination "${{ matrix.destination }}" clean test | xcpretty
29 |
30 | TAGGING:
31 | name: "Add Git Tag"
32 | runs-on: macos-13
33 | environment: Tag
34 | needs: SPM
35 |
36 | outputs:
37 | output1: ${{ steps.tagging.outputs.tag }}
38 |
39 | steps:
40 | - uses: actions/checkout@v4
41 |
42 | - name: Switching Podspec to release mode
43 | run: |
44 | sh .github/release_mode.sh
45 |
46 | - id: tagging
47 | name: Add git tag
48 | run: |
49 | GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} sh .github/tag.sh
50 |
51 | PODS_PUSH:
52 | name: "CocoaPods push"
53 | runs-on: macos-13
54 | environment: CocoaPods
55 | needs: TAGGING
56 |
57 | steps:
58 | - uses: actions/checkout@v4
59 | - run: git pull
60 |
61 | - name: Updating Cocoapods
62 | run: gem install cocoapods
63 |
64 | - name: Updating CocoaPods repo
65 | run: pod repo update
66 |
67 | - name: CocoaPods push
68 | run: |
69 | PODS_USER=${{ secrets.PODS_USER }} PODS_PASS=${{ secrets.PODS_PASS }} sh .github/cocoapods_publish.sh
70 |
71 | - name: Prepare release notes
72 | run: |
73 | sh .github/release_notes.sh
74 |
75 | - uses: ncipollo/release-action@v1
76 | with:
77 | tag: ${{needs.TAGGING.outputs.output1}}
78 | bodyFile: ".github/release_notes_template.md"
79 | token: ${{ secrets.GITHUB_TOKEN }}
80 | draft: true
81 |
--------------------------------------------------------------------------------
/TestApp/TestApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TestApp
4 | //
5 | // Created by Noam Tamim on 21/10/2018.
6 | // Copyright © 2018 Kaltura. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Classes/Player/VR/VRPlayerEngine.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import AVFoundation
13 | import UIKit
14 |
15 | /************************************************************/
16 | // MARK: - ViewState
17 | /************************************************************/
18 |
19 | @objc public enum ViewState: Int, CustomStringConvertible {
20 | /// Sent when player's view state panorama.
21 | case panorama
22 | /// Sent when player's view state vr.
23 | case stereo
24 | /// Sent when player's view state errored.
25 | case error
26 | /// Sent when player's view state unknown.
27 | case unknown = -1
28 |
29 | public var description: String {
30 | switch self {
31 | case .panorama: return "Panorama"
32 | case .stereo: return "Stereo"
33 | case .error: return "Error"
34 | case .unknown: return "Unknown"
35 | }
36 | }
37 | }
38 |
39 | /************************************************************/
40 | // MARK: - VRPlayerEngine
41 | /************************************************************/
42 |
43 | /// `VRPlayerEngine` protocol defines the methods needed to implement in order to work with the vr player engine.
44 | @objc public protocol VRPlayerEngine: PlayerEngine {
45 | /// VRPlayerEngine initializer
46 | ///
47 | init()
48 |
49 | /// Current View State
50 | var currentViewState: ViewState { get }
51 |
52 | /// Enable VR Mode - Stereo display for Google's Cardboard.
53 | ///
54 | /// - Parameter isEnabled: Toggle to enable vr mode.
55 | func setVRModeEnabled(_ isEnabled: Bool)
56 |
57 | /// Requests reset of rotation in the next rendering frame.
58 | func centerViewPoint()
59 |
60 | /// Creates the orientation indicator view.
61 | ///
62 | /// - Parameter frame: The frame of orientation indicator view.
63 | func createOrientationIndicatorView(frame: CGRect) -> UIView?
64 | }
65 |
--------------------------------------------------------------------------------
/Example/PlayKit.xcodeproj/xcshareddata/xcschemes/PlayKit-Tests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
44 |
45 |
46 |
47 |
53 |
54 |
56 |
57 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - google-cast-sdk (3.5.0):
3 | - google-cast-sdk/Core (= 3.5.0)
4 | - google-cast-sdk/Core (3.5.0)
5 | - KalturaNetKit (0.0.17):
6 | - KalturaNetKit/Core (= 0.0.17)
7 | - KalturaNetKit/Core (0.0.17):
8 | - SwiftyJSON
9 | - Log (1.0)
10 | - Nimble (7.0.1)
11 | - PlayKit (3.4.x-dev):
12 | - PlayKit/Core (= 3.4.x-dev)
13 | - PlayKit/AnalyticsCommon (3.4.x-dev):
14 | - PlayKit/Core
15 | - PlayKit/Core (3.4.x-dev):
16 | - KalturaNetKit (~> 0.0)
17 | - Log (= 1.0)
18 | - PlayKitUtils (= 0.1.4)
19 | - SwiftyJSON (= 3.1.4)
20 | - SwiftyXMLParser (= 3.0.3)
21 | - PlayKit/GoogleCastAddon (3.4.x-dev):
22 | - google-cast-sdk (= 3.5)
23 | - PlayKit/Core
24 | - PlayKit/KalturaLiveStatsPlugin (3.4.x-dev):
25 | - PlayKit/AnalyticsCommon
26 | - PlayKit/Core
27 | - PlayKit/KalturaStatsPlugin (3.4.x-dev):
28 | - PlayKit/AnalyticsCommon
29 | - PlayKit/Core
30 | - PlayKit/PhoenixPlugin (3.4.x-dev):
31 | - PlayKit/AnalyticsCommon
32 | - PlayKit/Core
33 | - PlayKit/YouboraPlugin (3.4.x-dev):
34 | - PlayKit/AnalyticsCommon
35 | - PlayKit/Core
36 | - Youbora-AVPlayer/dynamic (= 5.4.18)
37 | - PlayKitUtils (0.1.4)
38 | - Quick (1.1.0)
39 | - SwiftyJSON (3.1.4)
40 | - SwiftyXMLParser (3.0.3)
41 | - Youbora-AVPlayer/dynamic (5.4.18):
42 | - Youbora-YouboraLib/dynamic (= 5.4.4)
43 | - Youbora-YouboraLib/dynamic (5.4.4)
44 |
45 | DEPENDENCIES:
46 | - Nimble (= 7.0.1)
47 | - PlayKit (from `../../playkit-ios`)
48 | - PlayKit/GoogleCastAddon (from `../../playkit-ios`)
49 | - PlayKit/KalturaLiveStatsPlugin (from `../../playkit-ios`)
50 | - PlayKit/KalturaStatsPlugin (from `../../playkit-ios`)
51 | - PlayKit/PhoenixPlugin (from `../../playkit-ios`)
52 | - PlayKit/YouboraPlugin (from `../../playkit-ios`)
53 | - Quick (= 1.1.0)
54 |
55 | EXTERNAL SOURCES:
56 | PlayKit:
57 | :path: ../../playkit-ios
58 |
59 | SPEC CHECKSUMS:
60 | google-cast-sdk: fc3728ead66150ac746e001ba03a44e4b10e359e
61 | KalturaNetKit: d37b3c6fc08da9935677fc547c59b25abc3574a3
62 | Log: 5e368c9528db07517d18d2d04ff5fe2b6f5a1e21
63 | Nimble: 657d000e11df8aebe27cdaf9d244de7f30ed87f7
64 | PlayKit: 099466211a5f9df804308d170cbf6a1cec8c3bda
65 | PlayKitUtils: 8f03f72690805e20718be6f913547e0966c835f9
66 | Quick: dafc587e21eed9f4cab3249b9f9015b0b7a7f71d
67 | SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220
68 | SwiftyXMLParser: 9c6cc0310e778266a63d8eba50d9e07925aab0fb
69 | Youbora-AVPlayer: e824d4332e65825e9e79347bc4990a407b96ca9d
70 | Youbora-YouboraLib: 90bfccf52b9e918c5cb8543a5a5c015c0f02c4f2
71 |
72 | PODFILE CHECKSUM: 24ed6305dcb645a9df435e307d6a6f160e7b6259
73 |
74 | COCOAPODS: 1.4.0
75 |
--------------------------------------------------------------------------------
/Example/PlayKit/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 |
13 | @UIApplicationMain
14 | class AppDelegate: UIResponder, UIApplicationDelegate {
15 |
16 | var window: UIWindow?
17 |
18 |
19 |
20 |
21 |
22 |
23 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
24 | // Override point for customization after application launch.
25 | return true
26 | }
27 |
28 | func applicationWillResignActive(_ application: UIApplication) {
29 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
30 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
31 | }
32 |
33 | func applicationDidEnterBackground(_ application: UIApplication) {
34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
36 | }
37 |
38 | func applicationWillEnterForeground(_ application: UIApplication) {
39 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
40 | }
41 |
42 | func applicationDidBecomeActive(_ application: UIApplication) {
43 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
44 | }
45 |
46 | func applicationWillTerminate(_ application: UIApplication) {
47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Example/Tests/Analytics/TVPAPI/TVPAPIPluginTest.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import XCTest
12 | @testable import PlayKit
13 | import Quick
14 | import Nimble
15 | import KalturaNetKit
16 |
17 | class TVPAPIPluginTest: QuickSpec {
18 |
19 | /************************************************************/
20 | // MARK: - Mocks
21 | /************************************************************/
22 |
23 | class TVPAPIAnalyticsPluginMock: TVPAPIAnalyticsPlugin {
24 |
25 | public override class var pluginName: String { return PluginTestConfiguration.TVPAPI.pluginName }
26 | }
27 |
28 | /************************************************************/
29 | // MARK: - Tests
30 | /************************************************************/
31 |
32 | override func spec() {
33 | describe("TVPAPIPluginTest") {
34 | var player: PlayerLoader!
35 | var tvpapiPluginMock: TVPAPIPluginTest.TVPAPIAnalyticsPluginMock!
36 |
37 | beforeEach {
38 | PlayKitManager.shared.registerPlugin(TVPAPIPluginTest.TVPAPIAnalyticsPluginMock.self)
39 | player = self.createPlayerForTVPAPI()
40 | tvpapiPluginMock = player.loadedPlugins[TVPAPIPluginTest.TVPAPIAnalyticsPluginMock.pluginName]!.plugin as! TVPAPIPluginTest.TVPAPIAnalyticsPluginMock
41 | }
42 |
43 | afterEach {
44 | self.destroyPlayer(player)
45 | }
46 |
47 | it("can build play event request") {
48 | let expectedDataBody = "{\"iMediaID\":\"test\",\"mediaType\":0,\"Action\":\"play\",\"initObj\":{\"\":\"\"},\"iLocation\":0,\"iFileID\":\"464302\"}".data(using: .utf8)
49 | let expectedUrl = "http://tvpapi-preprod.ott.kaltura.com/v3_9/gateways/jsonpostgw.aspx?m=MediaMark"
50 | let expectedMethod: RequestMethod = .post
51 |
52 | let request = tvpapiPluginMock.buildRequest(ofType: .play)
53 |
54 | expect(expectedUrl).to(equal(request?.url.absoluteString))
55 | expect(expectedDataBody).to(equal(request?.dataBody))
56 | expect(expectedMethod).to(equal(request?.method))
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Classes/Player/AVAsset/PKAssetResourceLoaderDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PKAssetResourceLoaderDelegate.swift
3 | // PlayKit
4 | //
5 | // Created by Nilit Danan on 8/28/19.
6 | //
7 |
8 | import Foundation
9 | import AVFoundation
10 |
11 | class PKAssetResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate {
12 |
13 | var delegates: [String : AVAssetResourceLoaderDelegate] = [:]
14 |
15 | func setDelegate(_ delegate : AVAssetResourceLoaderDelegate, forScheme scheme: String) {
16 | delegates[scheme] = delegate
17 | }
18 |
19 | // MARK: - AVAssetResourceLoaderDelegate
20 |
21 | func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
22 |
23 | PKLog.verbose("\(#function) was called in PKAssetResourceLoaderDelegate with loadingRequest: \(loadingRequest)")
24 |
25 | guard let scheme = loadingRequest.request.url?.scheme else { return false }
26 | guard let delegate = delegates[scheme] else { return false }
27 |
28 | return delegate.resourceLoader?(resourceLoader, shouldWaitForLoadingOfRequestedResource: loadingRequest) ?? false
29 | }
30 |
31 | func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForRenewalOfRequestedResource renewalRequest: AVAssetResourceRenewalRequest) -> Bool {
32 |
33 | PKLog.verbose("\(#function) was called in PKAssetResourceLoaderDelegate with renewalRequest: \(renewalRequest)")
34 |
35 | guard let scheme = renewalRequest.request.url?.scheme else { return false }
36 | guard let delegate = delegates[scheme] else { return false }
37 |
38 | return delegate.resourceLoader?(resourceLoader, shouldWaitForRenewalOfRequestedResource: renewalRequest) ?? false
39 | }
40 |
41 | func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
42 |
43 | PKLog.verbose("\(#function) was called in PKAssetResourceLoaderDelegate with loadingRequest: \(loadingRequest)")
44 | }
45 |
46 | func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForResponseTo authenticationChallenge: URLAuthenticationChallenge) -> Bool {
47 |
48 | PKLog.verbose("\(#function) was called in PKAssetResourceLoaderDelegate with authenticationChallenge: \(authenticationChallenge)")
49 | return false
50 | }
51 |
52 | func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel authenticationChallenge: URLAuthenticationChallenge) {
53 |
54 | PKLog.verbose("\(#function) was called in PKAssetResourceLoaderDelegate with authenticationChallenge: \(authenticationChallenge)")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Example/Tests/Analytics/Phoenix/PhoenixPluginTest.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import Quick
13 | import Nimble
14 | @testable import PlayKit
15 |
16 | class PhoenixPluginTest: QuickSpec {
17 |
18 | /************************************************************/
19 | // MARK: - Mocks
20 | /************************************************************/
21 |
22 | class PhoenixAnalyticsPluginMock: PhoenixAnalyticsPlugin {
23 |
24 | public override class var pluginName: String { return PluginTestConfiguration.Phoenix.pluginName }
25 | }
26 |
27 | /************************************************************/
28 | // MARK: - Tests
29 | /************************************************************/
30 |
31 | override func spec() {
32 | describe("PhoenixPluginTest") {
33 | var player: PlayerLoader!
34 | var phoenixPluginMock: PhoenixPluginTest.PhoenixAnalyticsPluginMock!
35 |
36 | beforeEach {
37 | PlayKitManager.shared.registerPlugin(PhoenixPluginTest.PhoenixAnalyticsPluginMock.self)
38 | player = self.createPlayerForPhoenix()
39 | phoenixPluginMock = player.loadedPlugins[PhoenixPluginTest.PhoenixAnalyticsPluginMock.pluginName]!.plugin as! PhoenixPluginTest.PhoenixAnalyticsPluginMock
40 | }
41 |
42 | afterEach {
43 | self.destroyPlayer(player)
44 | }
45 |
46 | it("can build play event request") {
47 | let expectedDataBody = "{\"clientTag\":\"java:16-09-10\",\"apiVersion\":\"3.6.1078.11798\",\"bookmark\":{\"position\":0,\"objectType\":\"KalturaBookmark\",\"type\":\"media\",\"id\":\"test\",\"playerData\":{\"fileId\":\"464302\",\"action\":\"PLAY\",\"objectType\":\"KalturaBookmarkPlayerData\"}},\"ks\":\"\"}".data(using: .utf8)
48 | let expectedUrl = "http://api-preprod.ott.kaltura.com/v4_1/api_v3//service/bookmark/action/add"
49 |
50 | let request = phoenixPluginMock.buildRequest(ofType: .play)
51 |
52 | expect(expectedUrl).to(equal(request?.url.absoluteString))
53 | expect(expectedDataBody).to(equal(request?.dataBody))
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Classes/Player/PlayerController+TimeMonitor.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | extension PlayerController: TimeMonitor, TimeProvider {
14 |
15 | @objc public func addPeriodicObserver(interval: TimeInterval, observeOn dispatchQueue: DispatchQueue?, using eventHandler: @escaping (TimeInterval) -> Void) -> UUID {
16 | PKLog.debug("add periodic observer with interval: \(interval), on queue: \(String(describing: dispatchQueue))")
17 | let token = self.timeObserver.addPeriodicObserver(interval: interval, observeOn: dispatchQueue, using: eventHandler)
18 | PKLog.debug("periodic observer added with token: \(token.uuidString)")
19 | return token
20 | }
21 |
22 | @objc public func addBoundaryObserver(boundaries: [PKBoundary], observeOn dispatchQueue: DispatchQueue?, using block: @escaping (TimeInterval, Double) -> Void) -> UUID {
23 | return self.addBoundaryObserver(times: boundaries.map { $0.time }, observeOn: dispatchQueue, using: block)
24 | }
25 |
26 | @objc public func addBoundaryObserver(times: [TimeInterval], observeOn dispatchQueue: DispatchQueue?, using eventHandler: @escaping (TimeInterval, Double) -> Void) -> UUID {
27 | PKLog.debug("add boundary observer with times: \(times), on queue: \(String(describing: dispatchQueue))")
28 | let token = self.timeObserver.addBoundaryObserver(times: times, observeOn: dispatchQueue, using: eventHandler)
29 | PKLog.debug("boundary observer added with token: \(token.uuidString)")
30 | return token
31 | }
32 |
33 | @objc public func removePeriodicObserver(_ token: UUID) {
34 | PKLog.debug("remove periodic observer with token: \(token.uuidString)")
35 | self.timeObserver.removePeriodicObserver(token)
36 | }
37 |
38 | @objc public func removeBoundaryObserver(_ token: UUID) {
39 | PKLog.debug("remove boundary observer with token: \(token.uuidString)")
40 | self.timeObserver.removeBoundaryObserver(token)
41 | }
42 |
43 | func removePeriodicObservers() {
44 | PKLog.debug("remove all periodic observers")
45 | self.timeObserver.removePeriodicObservers()
46 | }
47 |
48 | func removeBoundaryObservers() {
49 | PKLog.debug("remove all boundary observers")
50 | self.timeObserver.removeBoundaryObservers()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Example/Tests/Basic/TracksTest.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import XCTest
12 | import PlayKit
13 | import kSwiftyJSON
14 |
15 | class TracksTest: XCTestCase {
16 | var player : Player!
17 | var tracks: PKTracks?
18 |
19 | override func setUp() {
20 | super.setUp()
21 |
22 | let url = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8")
23 |
24 | let mediaEntry = PKMediaEntry("test-id",
25 | sources: [PKMediaSource("test", contentUrl: url)],
26 | duration: -1)
27 |
28 | self.player = PlayKitManager.shared.loadPlayer(pluginConfig: nil)
29 | self.player.prepare(MediaConfig(mediaEntry: mediaEntry))
30 | }
31 |
32 | func testGetTracksByEvent() {
33 | self.player.play()
34 |
35 | let theExeption = expectation(description: "get tracks")
36 |
37 | self.player.addObserver(self,
38 | events: [PlayerEvent.tracksAvailable]) { event in
39 |
40 | if event is PlayerEvent.TracksAvailable {
41 | self.tracks = event.tracks
42 |
43 | theExeption.fulfill()
44 | } else {
45 | XCTFail()
46 | }
47 | }
48 |
49 | waitForExpectations(timeout: 30.0) { (_) -> Void in}
50 | }
51 |
52 | func testSelectTrack() {
53 |
54 | self.player.play()
55 |
56 | let theExeption = expectation(description: "select track")
57 |
58 | self.player.addObserver(self,
59 | events: [PlayerEvent.tracksAvailable]) { event in
60 | if event is PlayerEvent.TracksAvailable {
61 | print(event)
62 | self.player.selectTrack(trackId: "sbtl:0")
63 |
64 | theExeption.fulfill()
65 | } else {
66 | XCTFail()
67 | }
68 | }
69 |
70 | waitForExpectations(timeout: 30.0) { (_) -> Void in}
71 | }
72 |
73 | override func tearDown() {
74 | // Put teardown code here. This method is called after the invocation of each test method in the class.
75 | super.tearDown()
76 | self.destroyPlayer(player)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Classes/Managers/NotificationsManager.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | /// The `NotificationsManager` objects provides a mechanism for adding/removing observers within a program
14 | public final class NotificationsManager {
15 |
16 | let notificationCenter = NotificationCenter.default
17 |
18 | /// lock object for synchronizing access
19 | let lock: AnyObject = UUID().uuidString as AnyObject
20 |
21 | /// Holds all the notification observers tokens
22 | var observerTokens = [NSObjectProtocol]()
23 |
24 |
25 | /// Adds an observer for notification name, appends the token to the token list and returns it.
26 | /// - returns: The added observer token
27 | @discardableResult
28 | func addObserver(notificationName: Notification.Name,
29 | object: Any? = nil,
30 | queue: DispatchQueue = DispatchQueue.main,
31 | using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
32 |
33 | let observerToken = notificationCenter.addObserver(forName: notificationName,
34 | object: object,
35 | queue: OperationQueue.main,
36 | using: block)
37 | sync {
38 | observerTokens.append(observerToken)
39 | }
40 | return observerToken
41 | }
42 |
43 | /// Removes an observer
44 | func remove(observer: AnyObject) {
45 | sync {
46 | notificationCenter.removeObserver(observer)
47 | for i in 0.. ()) {
69 | objc_sync_enter(lock)
70 | block()
71 | objc_sync_exit(lock)
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/Classes/Models/PKBoundary.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | /// `PKBoundary` used as abstract for boundary types (% and time).
14 | @objc public protocol PKBoundary {
15 | var time: TimeInterval { get }
16 | }
17 |
18 | /// `PKBoundaryFactory` factory class used to create boundary objects easily.
19 | @objc public class PKBoundaryFactory: NSObject {
20 |
21 | let duration: TimeInterval
22 |
23 | @objc public init (duration: TimeInterval) {
24 | self.duration = duration
25 | }
26 |
27 | @objc public func percentageTimeBoundary(boundary: Int) -> PKPercentageTimeBoundary {
28 | return PKPercentageTimeBoundary(boundary: boundary, duration: self.duration)
29 | }
30 |
31 | @objc public func timeBoundary(boundaryTime: TimeInterval) -> PKTimeBoundary {
32 | return PKTimeBoundary(boundaryTime: boundaryTime, duration: self.duration)
33 | }
34 | }
35 |
36 | /// `PKPercentageTimeBoundary` represents a time boundary in % against the media duration.
37 | @objc public class PKPercentageTimeBoundary: NSObject, PKBoundary {
38 |
39 | /// The time to set the boundary on.
40 | public let time: TimeInterval
41 |
42 | /// Creates a new `PKPercentageTimeBoundary` object from %.
43 | /// - Attention: boundary value should be between 1 and 100 otherwise will use default values!
44 | @objc public init(boundary: Int, duration: TimeInterval) {
45 | switch boundary {
46 | case 1...100: self.time = duration * TimeInterval(boundary) / TimeInterval(100)
47 | case Int.min...0: self.time = 0
48 | case 101...Int.max: self.time = duration
49 | default: self.time = 0
50 | }
51 | }
52 | }
53 |
54 | /// `PKTimeBoundary` represents a time boundary in seconds.
55 | @objc public class PKTimeBoundary: NSObject, PKBoundary {
56 |
57 | /// The time to set the boundary on.
58 | @objc public let time: TimeInterval
59 |
60 | /// Creates a new `PKTimeBoundary` object from seconds.
61 | /// - Attention: boundary value should be between 0 and duration otherwise will use default values!
62 | @objc public init(boundaryTime: TimeInterval, duration: TimeInterval) {
63 | if boundaryTime <= 0 {
64 | self.time = 0
65 | } else if boundaryTime >= duration {
66 | self.time = duration
67 | } else {
68 | self.time = boundaryTime
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Classes/Player/Ads/AdsPlugin.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import AVKit
13 |
14 | public enum PlayType: CustomStringConvertible {
15 | case play, resume
16 |
17 | public var description: String {
18 | switch self {
19 | case .play:
20 | return "Play"
21 | case .resume:
22 | return "Resume"
23 | }
24 | }
25 | }
26 |
27 | public protocol AdsPluginDataSource: AnyObject {
28 | /// The player's media config start time.
29 | var playAdsAfterTime: TimeInterval { get }
30 | }
31 |
32 | public protocol AdsPluginDelegate: AnyObject {
33 | func adsPlugin(_ adsPlugin: AdsPlugin, loaderFailedWith error: String)
34 | func adsPlugin(_ adsPlugin: AdsPlugin, managerFailedWith error: String)
35 | func adsPlugin(_ adsPlugin: AdsPlugin, didReceive event: PKEvent)
36 |
37 | /// called when ads request was timed out, telling the player if it should start play afterwards.
38 | func adsRequestTimedOut(shouldPlay: Bool)
39 |
40 | /// called when the plugin wants the player to start play.
41 | func play(_ playType: PlayType)
42 | }
43 |
44 | public protocol AdsPlugin: PKPlugin {
45 | var dataSource: AdsPluginDataSource? { get set }
46 | var delegate: AdsPluginDelegate? { get set }
47 | /// Is ad currently playing.
48 | var isAdPlaying: Bool { get }
49 | /// Whether or not the pre-roll should be played upon start position different than 0.
50 | var startWithPreroll: Bool { get }
51 | /// Request ads from the server.
52 | func requestAds() throws
53 | /// Resume ad.
54 | func resume()
55 | /// Pause ad.
56 | func pause()
57 | /// Ad content complete.
58 | func contentComplete()
59 | /// Destroy the ads manager.
60 | func destroyManager()
61 | /// Called after player called `super.play()`.
62 | func didPlay()
63 | /// Called when play() or resume() was called.
64 | /// Used to make the neccery checks with the ads plugin if can play or resume the content.
65 | func didRequestPlay(ofType type: PlayType)
66 | /// Called when entering the background.
67 | func didEnterBackground()
68 | /// Called when coming back from background.
69 | func willEnterForeground()
70 | }
71 |
72 | #if os(iOS)
73 | public protocol PIPEnabledAdsPlugin: AdsPlugin, AVPictureInPictureControllerDelegate {
74 | var pipDelegate: AVPictureInPictureControllerDelegate? { get set }
75 | }
76 | #endif
77 |
--------------------------------------------------------------------------------
/Classes/Events/InterceptorEvent.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2021 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 | //
11 | // InterceptorEvent.swift
12 | // PlayKit
13 | //
14 | // Created by Sergey Chausov on 07.07.2021.
15 | //
16 |
17 | import Foundation
18 |
19 | /// InterceptorEvent is a class that is used to reflect specific PKMediaEntryInterceptor events.
20 | @objc public class InterceptorEvent: PKEvent {
21 |
22 | @objc public static let allEventTypes: [InterceptorEvent.Type] = [
23 | cdnSwitched,
24 | sourceUrlSwitched
25 | ]
26 |
27 | // MARK: - Interceptor Events Static References.
28 |
29 | /// Sent by SmartSwitch interceptor plugin, when the playback URL changed with attached CDN code.
30 | /// Currently used by Youbora contentCdn options.
31 | @objc public static let cdnSwitched: InterceptorEvent.Type = CDNSwitched.self
32 |
33 | public class CDNSwitched: InterceptorEvent {
34 | public convenience init(cdnCode: String) {
35 | self.init([InterceptorEventDataKeys.cdnCode: cdnCode])
36 | }
37 | }
38 |
39 | // Can be sent by any interceptor plugin, when the playback URL is changed with updated url.
40 | @objc public static let sourceUrlSwitched: InterceptorEvent.Type = SourceUrlSwitched.self
41 |
42 | public class SourceUrlSwitched: InterceptorEvent {
43 | public convenience init(originalUrl: String, updatedUrl: String) {
44 | self.init([InterceptorEventDataKeys.originalUrl: originalUrl, InterceptorEventDataKeys.updatedUrl: updatedUrl])
45 | }
46 | }
47 | }
48 |
49 | @objc public class InterceptorEventDataKeys: NSObject {
50 |
51 | public static let cdnCode = "CDNCode"
52 | public static let originalUrl = "OriginalUrl"
53 | public static let updatedUrl = "UpdatedUrl"
54 | }
55 |
56 | // MARK: - CDNSwitched
57 | extension PKEvent {
58 |
59 | /// CDN code provided by plugins (SmartSwitch)
60 | @objc public var cdnCode: String? {
61 | return self.data?[InterceptorEventDataKeys.cdnCode] as? String
62 | }
63 | }
64 |
65 | // MARK: - SourceUrlSwitched
66 | extension PKEvent {
67 |
68 | /// SourceUrlSwitched values provided by interceptor plugins
69 | @objc public var originalUrl: String? {
70 | return self.data?[InterceptorEventDataKeys.originalUrl] as? String
71 | }
72 |
73 | @objc public var updatedUrl: String? {
74 | return self.data?[InterceptorEventDataKeys.updatedUrl] as? String
75 | }
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/Classes/PKStateMachine.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | public protocol IntRawRepresentable: RawRepresentable {
14 | var rawValue: Int { get }
15 | }
16 |
17 | public protocol StateProtocol: IntRawRepresentable, Hashable {}
18 |
19 | extension StateProtocol {
20 | var hashValue: Int {
21 | return rawValue
22 | }
23 | }
24 |
25 | public class BasicStateMachine {
26 | /// the current state.
27 | private var state: T
28 | /// the queue to make changes and fetches on.
29 | let dispatchQueue: DispatchQueue
30 | /// the initial state of the state machine.
31 | let initialState: T
32 | /// indicates whether it is allowed to change the state to the initial one.
33 | var allowTransitionToInitialState: Bool
34 | /// a block to perform on every state changing (performed on the main queue).
35 | var onStateChange: ((T) -> Void)?
36 |
37 | public init(initialState: T, allowTransitionToInitialState: Bool = true) {
38 | self.state = initialState
39 | self.initialState = initialState
40 | self.allowTransitionToInitialState = allowTransitionToInitialState
41 | self.dispatchQueue = DispatchQueue(label: "com.kaltura.playkit.dispatch-queue.\(String(describing: type(of: self)))")
42 | }
43 |
44 | /// gets the current state.
45 | public func getState() -> T {
46 | return self.dispatchQueue.sync {
47 | return self.state
48 | }
49 | }
50 |
51 | /// sets the state to a new value.
52 | public func set(state: T) {
53 | self.dispatchQueue.sync {
54 | if state == self.initialState && !self.allowTransitionToInitialState {
55 | PKLog.error("\(String(describing: type(of: self))) was set to initial state, this is not allowed")
56 | return
57 | }
58 | // only set state when changed
59 | if self.state != state {
60 | self.state = state
61 | DispatchQueue.main.async {
62 | self.onStateChange?(state)
63 | }
64 | }
65 | }
66 | }
67 |
68 | /// sets the state machine to the initial value.
69 | public func reset() {
70 | dispatchQueue.sync {
71 | self.state = self.initialState
72 | DispatchQueue.main.async {
73 | self.onStateChange?(self.state)
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Classes/Player/PlayerConfig.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | /// A `MediaConfig` object defines behavior and info to use when preparing a `Player` object.
14 | @objc public class MediaConfig: NSObject {
15 |
16 | @objc public var mediaEntry: PKMediaEntry
17 | @objc public var startTime: TimeInterval = TimeInterval.nan
18 |
19 | @objc public override var description: String {
20 | return "Media config, mediaEntry: \(self.mediaEntry) startTime: \(self.startTime)"
21 | }
22 |
23 | @objc public init(mediaEntry: PKMediaEntry) {
24 | self.mediaEntry = mediaEntry
25 | }
26 |
27 | @objc public init(mediaEntry: PKMediaEntry, startTime: TimeInterval) {
28 | self.mediaEntry = mediaEntry
29 | self.startTime = startTime
30 | }
31 |
32 | @objc public static func config(mediaEntry: PKMediaEntry) -> MediaConfig {
33 | return MediaConfig.init(mediaEntry: mediaEntry)
34 | }
35 |
36 | @objc public static func config(mediaEntry: PKMediaEntry, startTime: TimeInterval) -> MediaConfig {
37 | return MediaConfig.init(mediaEntry: mediaEntry, startTime: startTime)
38 | }
39 |
40 | /// Private init.
41 | private override init() {
42 | fatalError("Private initializer, use `init(mediaEntry:startTime:)`")
43 | }
44 | }
45 |
46 | extension MediaConfig: NSCopying {
47 |
48 | @objc public func copy(with zone: NSZone? = nil) -> Any {
49 | let copy = MediaConfig(mediaEntry: self.mediaEntry, startTime: self.startTime)
50 | return copy
51 | }
52 | }
53 |
54 | /// A `PluginConfig` object defines config to use when loading a plugin object.
55 | @objc public class PluginConfig: NSObject {
56 | /// Plugins config dictionary holds [plugin name : plugin config]
57 | @objc public var config: [String: Any]
58 |
59 | @objc public override var description: String {
60 | return "Plugin config:\n\(self.config)"
61 | }
62 |
63 | @objc public init(config: [String: Any]) {
64 | self.config = config
65 | }
66 |
67 | /// Private init.
68 | private override init() {
69 | fatalError("Private initializer, use `init(config:)`")
70 | }
71 | }
72 |
73 | extension PluginConfig: NSCopying {
74 |
75 | @objc public func copy(with zone: NSZone? = nil) -> Any {
76 | let copy = PluginConfig(config: self.config)
77 | return copy
78 | }
79 | }
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/Classes/Player/PKVRController.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import UIKit
13 |
14 | @objc public class PKVRController: NSObject, PKController {
15 | /************************************************************/
16 | // MARK: - Properties
17 | /************************************************************/
18 |
19 | var currentPlayer: VRPlayerEngine?
20 |
21 | /// Represents current PKVRController view state
22 | ///
23 | /// ViewState Options
24 | /// - panorama
25 | /// - stereo
26 | /// - error
27 | /// - unknown
28 | @objc public var currentViewState: ViewState {
29 |
30 | guard let player = self.currentPlayer else {
31 | PKLog.warning("player doesn't exist")
32 | return ViewState.error
33 | }
34 |
35 | PKLog.debug("currentViewState: \(player.currentViewState)")
36 | return player.currentViewState
37 | }
38 |
39 | /************************************************************/
40 | // MARK: - Initialization
41 | /************************************************************/
42 |
43 | @objc required public init(player: PlayerEngine?) {
44 | self.currentPlayer = player as? VRPlayerEngine
45 | }
46 |
47 | /************************************************************/
48 | // MARK: - Functions
49 | /************************************************************/
50 |
51 | /// Enable VR Mode - For stereo display for Google's Cardboard.
52 | ///
53 | /// - Parameter isVREnabled: Toggle to enable vr mode.
54 | @objc public func setVRModeEnabled(_ isVREnabled: Bool) {
55 | PKLog.debug("isVREnabled: \(isVREnabled)")
56 | self.currentPlayer?.setVRModeEnabled(isVREnabled)
57 | }
58 |
59 | /// Requests reset of rotation in the next rendering frame.
60 | @objc public func centerViewPoint() {
61 | self.currentPlayer?.centerViewPoint()
62 | }
63 |
64 | /// Creates eye view indicator.
65 | ///
66 | /// - Parameter frame: eye view frame
67 | /// - Returns: eye view indicator
68 | @objc public func createOrientationIndicatorView(frame: CGRect) -> UIView? {
69 | guard let player = self.currentPlayer else {
70 | PKLog.warning("player doesn't exist")
71 | return nil
72 | }
73 |
74 | PKLog.debug("frame: \(frame)")
75 | return (player.createOrientationIndicatorView(frame: frame))
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Classes/Player/Media/SourceSelector.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | /// Media Source Type
14 | enum SourceType {
15 | case mp3
16 | case mp4
17 | case m3u8
18 | case wvm
19 |
20 | var asString: String {
21 | switch self {
22 | case .mp3:
23 | return "mp3"
24 | case .mp4:
25 | return "mp4"
26 | case .m3u8:
27 | return "m3u8"
28 | case .wvm:
29 | return "wvm"
30 | }
31 | }
32 | }
33 |
34 | /// Selects the preffered media source
35 | class SourceSelector {
36 | static func selectSource(from mediaEntry: PKMediaEntry) -> (PKMediaSource, AssetHandler)? {
37 | guard let sources = mediaEntry.sources else {
38 | PKLog.error("no media sources in mediaEntry!")
39 | return nil
40 | }
41 |
42 | let defaultHandler = DefaultAssetHandler.self
43 |
44 | // Preference: Local, HLS, FPS*, MP4, WVM*, MP3
45 |
46 | if let source = sources.first(where: {$0 is LocalMediaSource}) {
47 | if source.mediaFormat == .wvm {
48 | return (source, DRMSupport.widevineClassicHandler!.init())
49 | } else {
50 | return (source, defaultHandler.init())
51 | }
52 | }
53 |
54 | if DRMSupport.fairplay {
55 | if let source = sources.first(where: {$0.mediaFormat == .hls}) {
56 | return (source, defaultHandler.init())
57 | }
58 | } else {
59 | if let source = sources.first(where: {$0.mediaFormat == .hls && ($0.drmData == nil || $0.drmData!.isEmpty) }) {
60 | return (source, defaultHandler.init())
61 | }
62 | }
63 |
64 | if let source = sources.first(where: {$0.mediaFormat == .mp4}) {
65 | return (source, defaultHandler.init())
66 | }
67 |
68 | if DRMSupport.widevineClassic, let source = sources.first(where: {$0.mediaFormat == .wvm}) {
69 | return (source, DRMSupport.widevineClassicHandler!.init())
70 | }
71 |
72 | if let source = sources.first(where: {$0.mediaFormat == .mp3}) {
73 | return (source, defaultHandler.init())
74 | }
75 |
76 | PKLog.error("No playable media sources!")
77 | if !DRMSupport.fairplay {
78 | PKLog.warning("Note: FairPlay is not supported on simulators")
79 | }
80 | return nil
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Classes/Player/PlayerView.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 | import AVFoundation
13 |
14 | /// A simple `UIView` subclass that is backed by an `AVPlayerLayer` layer.
15 | @objc public class PlayerView: UIView {
16 |
17 | var player: AVPlayer? {
18 | get {
19 | return playerLayer.player
20 | }
21 | set {
22 | playerLayer.player = newValue
23 | }
24 | }
25 |
26 | public override var contentMode: UIView.ContentMode {
27 | didSet {
28 | switch self.contentMode {
29 | case .scaleAspectFill:
30 | playerLayer.videoGravity = .resizeAspectFill
31 | case .scaleAspectFit:
32 | playerLayer.videoGravity = .resizeAspect
33 | case .scaleToFill:
34 | playerLayer.videoGravity = .resize
35 | default:
36 | playerLayer.videoGravity = .resizeAspect
37 | }
38 | }
39 | }
40 |
41 | var playerLayer: AVPlayerLayer {
42 | return self.layer as! AVPlayerLayer
43 | }
44 |
45 | // Override UIView property
46 | override public static var layerClass: AnyClass {
47 | return AVPlayerLayer.self
48 | }
49 |
50 | /// adds the player view as a subview to the container view and sets up constraints
51 | @objc public func add(toContainer container: UIView) {
52 | self.translatesAutoresizingMaskIntoConstraints = false
53 | container.addSubview(self)
54 |
55 | let views = ["playerView": self]
56 |
57 | let horizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[playerView]-0-|", options: [], metrics: nil, views: views)
58 | let verticalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[playerView]-0-|", options: [], metrics: nil, views: views)
59 |
60 | container.addConstraints(horizontalConstraint)
61 | container.addConstraints(verticalConstraint)
62 | }
63 |
64 | /// creates a new `PlayerView` instance and connects it to the player
65 | /// - important: make sure to keep strong reference for the player view instance (either from adding as subview or property),
66 | /// otherwise it will be deallocated as the framework holds a weak reference to it
67 | @objc public static func createPlayerView(forPlayer player: Player) -> PlayerView {
68 | let playerView = PlayerView()
69 | player.view = playerView
70 | return playerView
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Classes/Managers/LocalDataStore.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2018 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | @objc public protocol LocalDataStore {
14 | func save(key: String, value: Data) throws
15 | func load(key: String) throws -> Data
16 | func remove(key: String) throws
17 | func exists(key: String) -> Bool
18 | }
19 |
20 | /// Implementation of LocalDataStore that saves data to files in the Library directory.
21 | @objc public class DefaultLocalDataStore: NSObject, LocalDataStore {
22 |
23 | static let pkLocalDataStore = "pkLocalDataStore"
24 | let storageDirectory: URL
25 |
26 | @objc public static func defaultDataStore() -> DefaultLocalDataStore? {
27 | return try? DefaultLocalDataStore(directory: .libraryDirectory)
28 | }
29 |
30 | private override init() {
31 | fatalError("Private initializer, use a factory or `init(directory:)`")
32 | }
33 |
34 | static func storageDir(_ directory: FileManager.SearchPathDirectory = .libraryDirectory) throws -> URL {
35 | let baseDir = try FileManager.default.url(for: directory, in: .userDomainMask, appropriateFor: nil, create: false)
36 | let storageDirectory = baseDir.appendingPathComponent(DefaultLocalDataStore.pkLocalDataStore, isDirectory: true)
37 |
38 | try FileManager.default.createDirectory(at: storageDirectory, withIntermediateDirectories: true, attributes: nil)
39 |
40 | return storageDirectory
41 | }
42 |
43 | @objc public init(directory: FileManager.SearchPathDirectory) throws {
44 | try self.storageDirectory = type(of: self).storageDir(directory)
45 | }
46 |
47 | private func file(_ key: String) -> URL {
48 | return self.storageDirectory.appendingPathComponent(key)
49 | }
50 |
51 | @objc public func save(key: String, value: Data) throws {
52 | let f = file(key)
53 | PKLog.debug("Saving key to \(f)")
54 | try value.write(to: f, options: .atomic)
55 | }
56 |
57 | @objc public func load(key: String) throws -> Data {
58 | let f = file(key)
59 | PKLog.debug("Loading key from \(f)")
60 | return try Data.init(contentsOf: f, options: [])
61 | }
62 |
63 | @objc public func exists(key: String) -> Bool {
64 | return FileManager.default.fileExists(atPath: file(key).path)
65 | }
66 |
67 | @objc public func remove(key: String) throws {
68 | let f = file(key)
69 | PKLog.debug("Removing key at \(f)")
70 | try FileManager.default.removeItem(at: f)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Classes/Managers/AppState/AppStateProvider.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import UIKit
13 |
14 | /// The delegate of `AppStateProvider`, allows the delegate to inform on app state notifications.
15 | public protocol AppStateProviderDelegate: AnyObject {
16 | /// fire this delegate function when received observation event.
17 | /// for every observer with the same observation event process the on observe block.
18 | func appStateEventPosted(name: ObservationName)
19 | }
20 |
21 | /// The interface of `AppStateProvider`, allows us to better divide the logic and mock easier.
22 | public protocol AppStateProviderProtocol {
23 | var notificationsManager: NotificationsManager { get }
24 | /// Holds all the observation names we will be observing.
25 | /// If you want to observe more events add them here.
26 | var observationNames: Set { get }
27 | var delegate: AppStateProviderDelegate? { get }
28 | }
29 |
30 | extension AppStateProviderProtocol {
31 | /// Add observers for the provided notification names.
32 | func addObservers() {
33 | observationNames.forEach { name in
34 | notificationsManager.addObserver(notificationName: name) { notification in
35 | self.delegate?.appStateEventPosted(name: notification.name)
36 | }
37 | }
38 | }
39 |
40 | /// Remove observers for the provided notification names.
41 | func removeObservers() {
42 | notificationsManager.removeAllObservers()
43 | }
44 |
45 | }
46 |
47 | /************************************************************/
48 | // MARK: - AppStateProvider
49 | /************************************************************/
50 |
51 | /// The `AppStateProvider` is a provider for receiving events from the system about app states.
52 | /// Used to seperate the events providing from the app state subject and enabling us to mock better.
53 | public final class AppStateProvider: AppStateProviderProtocol {
54 |
55 | public init(delegate: AppStateProviderDelegate? = nil) {
56 | self.delegate = delegate
57 | }
58 |
59 | public weak var delegate: AppStateProviderDelegate?
60 |
61 | public let notificationsManager = NotificationsManager()
62 |
63 | public let observationNames: Set = [
64 | UIApplication.willTerminateNotification,
65 | UIApplication.didEnterBackgroundNotification,
66 | UIApplication.didBecomeActiveNotification,
67 | UIApplication.willResignActiveNotification,
68 | UIApplication.willEnterForegroundNotification
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import PlayKit
13 |
14 | /************************************************************/
15 | // MARK: - BaseAnalyticsPlugin
16 | /************************************************************/
17 |
18 | /// class `BaseAnalyticsPlugin` is a base plugin object used for analytics plugin subclasses
19 | @objc public class BaseAnalyticsPlugin: BasePlugin, AnalyticsPluginProtocol {
20 |
21 | var config: AnalyticsConfig?
22 | /// indicates whether we played for the first time or not.
23 | @objc public var isFirstPlay: Bool = true
24 |
25 | /************************************************************/
26 | // MARK: - PKPlugin
27 | /************************************************************/
28 |
29 | @objc public required init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws {
30 | try super.init(player: player, pluginConfig: pluginConfig, messageBus: messageBus)
31 | if let aConfig = pluginConfig as? AnalyticsConfig {
32 | self.config = aConfig
33 | } else {
34 | PKLog.warning("There is no Analytics Config! for \(type(of: self))")
35 | }
36 | self.registerEvents()
37 | }
38 |
39 | public override func onUpdateMedia(mediaConfig: MediaConfig) {
40 | super.onUpdateMedia(mediaConfig: mediaConfig)
41 | self.isFirstPlay = true
42 | }
43 |
44 | public override func onUpdateConfig(pluginConfig: Any) {
45 | super.onUpdateConfig(pluginConfig: pluginConfig)
46 |
47 | guard let config = pluginConfig as? AnalyticsConfig else {
48 | PKLog.error("plugin configis wrong")
49 | return
50 | }
51 |
52 | PKLog.verbose("new config::\(String(describing: config))")
53 | self.config = config
54 | }
55 |
56 | public override func destroy() {
57 | self.messageBus?.removeObserver(self, events: playerEventsToRegister)
58 | super.destroy()
59 | }
60 |
61 | /************************************************************/
62 | // MARK: - AnalyticsPluginProtocol
63 | /************************************************************/
64 |
65 | /// default events to register
66 | @objc public var playerEventsToRegister: [PlayerEvent.Type] {
67 | fatalError("abstract property should be overriden in subclass")
68 | }
69 |
70 | @objc public func registerEvents() {
71 | fatalError("abstract func should be overriden in subclass")
72 | }
73 |
74 | @objc public func unregisterEvents() {
75 | fatalError("abstract func should be overriden in subclass")
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Classes/Utils/NetworkUtils.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2021 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import KalturaNetKit
13 |
14 | @objc public class NetworkUtils: NSObject {
15 |
16 | let defaultKavaBaseUrl: String = "https://analytics.kaltura.com/api_v3/index.php"
17 | let defaultKavaPartnerId: Int = 2504201
18 | let defaultKavaEntryId: String = "1_3bwzbc9o"
19 |
20 | static public let kavaEventImpression = "1"
21 | static public let kavaEventPlayRequest = "2"
22 |
23 | public func sendKavaAnalytics(forPartnerId partnerId: Int?, entryId: String?, eventType: String, sessionId: String) {
24 | guard let request: KalturaRequestBuilder = KalturaRequestBuilder(url: defaultKavaBaseUrl, service: nil, action: nil) else { return }
25 |
26 | var defPartnerId: Int = defaultKavaPartnerId
27 | var defEntryId: String = defaultKavaEntryId
28 |
29 | if let partnerId = partnerId, partnerId > 0,
30 | let entryId = entryId, !entryId.isEmpty {
31 | defPartnerId = partnerId
32 | defEntryId = entryId
33 | }
34 |
35 | request.set(method: .get)
36 | request.add(headerKey: "User-Agent", headerValue: PlayKitManager.userAgent)
37 |
38 | request.setParam(key: "service", value: "analytics")
39 | request.setParam(key: "action", value: "trackEvent")
40 | request.setParam(key: "eventType", value: eventType)
41 | request.setParam(key: "eventIndex", value: "1")
42 | request.setParam(key: "partnerId", value: String(defPartnerId))
43 | request.setParam(key: "entryId", value: defEntryId)
44 | request.setParam(key: "sessionId", value: sessionId)
45 | request.setParam(key: "referrer", value: self.base64(from: Bundle.main.bundleIdentifier ?? ""))
46 | request.setParam(key: "deliveryType", value: "url")
47 | request.setParam(key: "playbackType", value: "vod")
48 | request.setParam(key: "clientVer", value: "\(PlayKitManager.clientTag)")
49 | request.setParam(key: "position", value: "0")
50 | if let bundleId = Bundle.main.bundleIdentifier {
51 | request.setParam(key: "application", value: "\(bundleId)")
52 | }
53 |
54 | request.set { (response: Response) in
55 | PKLog.debug("Response:\nStatus Code: \(response.statusCode)\nError: \(response.error?.localizedDescription ?? "")\nData: \(response.data ?? "")")
56 | }
57 | PKLog.debug("Sending Kava Event type: " + eventType)
58 | KNKRequestExecutor.shared.send(request: request.build())
59 | }
60 |
61 | func base64(from: String) -> String {
62 | return from.data(using: .utf8)?.base64EncodedString() ?? ""
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Classes/FairPlayDRM/FPSContentKeyManager.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2018 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import AVFoundation
12 |
13 | #if os(iOS)
14 | @available(iOS 10.3, *)
15 | class FPSContentKeyManager {
16 |
17 | static let shared: FPSContentKeyManager = FPSContentKeyManager()
18 |
19 | /// The instance of `AVContentKeySession` that is used for managing and preloading content keys.
20 | let contentKeySession: AVContentKeySession
21 |
22 | let contentKeyDelegate: FPSContentKeySessionDelegate
23 |
24 | /// The DispatchQueue to use for delegate callbacks.
25 | let contentKeyDelegateQueue = DispatchQueue(label: "com.kaltura.playkit.contentKeyDelegateQueue")
26 |
27 | // MARK: Initialization.
28 |
29 | private init() {
30 | if #available(iOS 11.0, *) {
31 | contentKeySession = AVContentKeySession(keySystem: .fairPlayStreaming)
32 | } else {
33 | // iOS 10.3
34 | let storageUrl = try! FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("pkLegacyExpiredContentKeySessionReports", isDirectory: true)
35 | try? FileManager.default.createDirectory(at: storageUrl, withIntermediateDirectories: true, attributes: nil)
36 | contentKeySession = AVContentKeySession(keySystem: .fairPlayStreaming, storageDirectoryAt: storageUrl)
37 | }
38 | contentKeyDelegate = FPSContentKeySessionDelegate()
39 |
40 | contentKeySession.setDelegate(contentKeyDelegate, queue: contentKeyDelegateQueue)
41 | }
42 |
43 | func installOfflineLicense(for location: URL, mediaSource: PKMediaSource, dataStore: LocalDataStore, callback: @escaping (Error?) -> Void) throws {
44 |
45 | let drmParams = try mediaSource.fairPlayParams()
46 |
47 | guard let id = FPSUtils.extractAssetId(at: location) else {
48 | PKLog.error("Asset at \(location.absoluteString) is missing the asset id")
49 | throw FPSError.missingAssetId(location)
50 | }
51 |
52 | let skdUrl = "skd://" + id
53 | if let assetHelper = contentKeyDelegate.assetHelpersMap[skdUrl],
54 | let avContentKeyRequest = assetHelper.avContentKeyRequest as? AVContentKeyRequest {
55 | assetHelper.doneCallback = callback
56 | contentKeySession.renewExpiringResponseData(for: avContentKeyRequest)
57 | } else {
58 | let helper = FPSLicenseHelper(assetId: id, params: drmParams, dataStore: dataStore, forceDownload: true)
59 | helper?.doneCallback = callback
60 | contentKeyDelegate.assetHelpersMap[skdUrl] = helper
61 |
62 | contentKeySession.processContentKeyRequest(withIdentifier: skdUrl, initializationData: nil, options: nil)
63 | }
64 | }
65 | }
66 | #endif
67 |
--------------------------------------------------------------------------------
/Classes/Providers/Mock/MockMediaProvider.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 | import kSwiftyJSON
13 |
14 | @objc public class MockMediaEntryProvider: NSObject, MediaEntryProvider {
15 |
16 | public enum MockError: Error {
17 | case invalidParam(paramName:String)
18 | case fileIsEmptyOrNotFound
19 | case unableToParseJSON
20 | case mediaNotFound
21 | }
22 |
23 | @objc public var id: String?
24 | @objc public var url: URL?
25 | @objc public var content: Any?
26 |
27 | @discardableResult
28 | @nonobjc public func set(id: String?) -> Self {
29 | self.id = id
30 | return self
31 | }
32 |
33 | @discardableResult
34 | @nonobjc public func set(url: URL?) -> Self {
35 | self.url = url
36 | return self
37 | }
38 |
39 | @discardableResult
40 | @nonobjc public func set(content: Any?) -> Self {
41 | self.content = content
42 | return self
43 | }
44 |
45 | public override init() {
46 |
47 | }
48 |
49 | struct LoaderInfo {
50 | var id: String
51 | var content: JSON
52 | }
53 |
54 | @objc public func loadMedia(callback: @escaping (PKMediaEntry?, Error?) -> Void) {
55 |
56 | guard let id = self.id else {
57 | callback(nil, MockError.invalidParam(paramName: "id"))
58 | return
59 | }
60 |
61 | var json: JSON? = nil
62 | if let content = self.content {
63 | json = JSON(content)
64 | } else if self.url != nil {
65 | guard let stringPath = self.url?.absoluteString else {
66 | callback(nil, MockError.invalidParam(paramName: "url"))
67 | return
68 | }
69 | guard let data = NSData(contentsOfFile: stringPath) else {
70 | callback(nil, MockError.fileIsEmptyOrNotFound)
71 | return
72 | }
73 | json = try? JSON(data: data as Data)
74 | }
75 |
76 | guard let jsonContent = json else {
77 | callback(nil, MockError.unableToParseJSON)
78 | return
79 | }
80 |
81 | let loderInfo = LoaderInfo(id: id, content: jsonContent)
82 |
83 | guard loderInfo.content != .null else {
84 | callback(nil, MockError.unableToParseJSON)
85 | return
86 | }
87 |
88 | let jsonObject: JSON = loderInfo.content[loderInfo.id]
89 | guard jsonObject != .null else {
90 | callback(nil, MockError.mediaNotFound)
91 | return
92 | }
93 |
94 | let mediaEntry = PKMediaEntry(json: jsonObject.object)
95 | callback(mediaEntry, nil)
96 | }
97 |
98 | @objc public func cancel() {
99 |
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Classes/Player/Protocols/BasicPlayer.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2019 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | @objc public protocol BasicPlayer {
14 | /// The player's duration.
15 | @objc var duration: TimeInterval { get }
16 |
17 | /// The player's currentState.
18 | @objc var currentState: PlayerState { get }
19 |
20 | /// Indicates if the player is playing.
21 | @objc var isPlaying: Bool { get }
22 |
23 | /// The player's view component.
24 | @objc weak var view: PlayerView? { get set }
25 |
26 | /// The current player's time.
27 | @objc var currentTime: TimeInterval { get set }
28 |
29 | /// The current program time (PROGRAM-DATE-TIME).
30 | @objc var currentProgramTime: Date? { get }
31 |
32 | /// Get the player's current audio track.
33 | @objc var currentAudioTrack: String? { get }
34 |
35 | /// Get the player's current text track.
36 | @objc var currentTextTrack: String? { get }
37 |
38 | /// Indicates the desired rate of playback, 0.0 means "paused", 1.0 indicates a desire to play at the natural rate of the current item.
39 | /// Note: Do not use the rate to indicate whether to play or pause! Use the isPlaying property.
40 | @objc var rate: Float { get set }
41 |
42 | /// The audio playback volume for the player, ranging from 0.0 through 1.0 on a linear scale.
43 | @objc var volume: Float { get set }
44 |
45 | /// Provides a collection of time ranges for which the player has the media data readily available. The ranges provided might be discontinuous.
46 | @objc var loadedTimeRanges: [PKTimeRange]? { get }
47 |
48 | /// The current player's buffered time.
49 | @objc var bufferedTime: TimeInterval { get }
50 |
51 | /// Send a play action for the player.
52 | @objc func play()
53 |
54 | /// Send a pause action for the player.
55 | @objc func pause()
56 |
57 | /// Send a resume action for the player.
58 | @objc func resume()
59 |
60 | /// Send a stop action for the player.
61 | @objc func stop()
62 |
63 | /// Send a replay action for the player.
64 | @objc func replay()
65 |
66 | /// Send a seek action for the player.
67 | @objc func seek(to time: TimeInterval)
68 |
69 | /// Seek to the live edje.
70 | @objc func seekToLiveEdge()
71 |
72 | /// Select a Track
73 | @objc func selectTrack(trackId: String)
74 |
75 | /// Release the player's resources.
76 | @objc func destroy()
77 |
78 | /// Prepare for playing an entry.
79 | /// If player network setting autoBuffer is set to true, prepare starts buffering the entry.
80 | /// Otherwise, if autoBuffer is set to false, need to call startBuffering manually.
81 | @objc func prepare(_ config: MediaConfig)
82 |
83 | /// Starts buffering the entry.
84 | @objc func startBuffering()
85 | }
86 |
--------------------------------------------------------------------------------
/Classes/Events/EventsPayloads/PKPlaybackInfo.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 | import AVFoundation
13 |
14 | /// `PKPlaybackInfo` represents a playback info object.
15 | @objc public class PKPlaybackInfo: NSObject {
16 |
17 | /// The actual bitrate of the playback.
18 | @objc public let bitrate: Double
19 | /// The selected track indicated bitrate.
20 | @objc public let indicatedBitrate: Double
21 | /// The throughput of the playback (download speed)
22 | @objc public let observedBitrate: Double
23 | /// The average bitrate of video track if it is unmuxed
24 | @objc public let averageVideoBitrate: Double
25 | /// The average bitrate of audio track. This is not available if audio is muxed with video.
26 | @objc public let averageAudioBitrate: Double
27 | /// The URI of the playback item
28 | @objc public let uri: String?
29 |
30 | init(bitrate: Double, indicatedBitrate: Double, observedBitrate: Double, averageVideoBitrate: Double, averageAudioBitrate: Double, uri: String?) {
31 | self.bitrate = bitrate
32 | self.indicatedBitrate = indicatedBitrate
33 | self.observedBitrate = observedBitrate
34 | self.averageVideoBitrate = averageVideoBitrate
35 | self.averageAudioBitrate = averageAudioBitrate
36 | self.uri = uri
37 | }
38 |
39 | convenience init(logEvent: AVPlayerItemAccessLogEvent) {
40 | let bitrate: Double
41 | if logEvent.segmentsDownloadedDuration > 0 {
42 | // bitrate is equal to:
43 | // (amount of bytes transfered) * 8 (bits in byte) / (amount of time took to download the transfered bytes)
44 | bitrate = Double(logEvent.numberOfBytesTransferred * 8) / logEvent.segmentsDownloadedDuration
45 | } else {
46 | bitrate = logEvent.indicatedBitrate
47 | }
48 | let indicatedBitrate = logEvent.indicatedBitrate
49 | let observedBitrate = logEvent.observedBitrate
50 | let averageAudioBitrate: Double
51 | let averageVideoBitrate: Double
52 | if #available(iOS 10.0, tvOS 10.0, *) {
53 | if (logEvent.averageVideoBitrate > 0) {
54 | averageVideoBitrate = logEvent.averageVideoBitrate
55 | } else {
56 | averageVideoBitrate = bitrate
57 | }
58 | if (logEvent.averageAudioBitrate > 0) {
59 | averageAudioBitrate = logEvent.averageAudioBitrate
60 | } else {
61 | averageAudioBitrate = -1
62 | }
63 | } else {
64 | averageVideoBitrate = bitrate
65 | averageAudioBitrate = -1
66 | }
67 |
68 | self.init(bitrate: bitrate,
69 | indicatedBitrate: indicatedBitrate,
70 | observedBitrate: observedBitrate,
71 | averageVideoBitrate: averageVideoBitrate,
72 | averageAudioBitrate: averageAudioBitrate,
73 | uri: logEvent.uri ?? "")
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Classes/Events/PlaylistEvent.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2021 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 | //
11 | // PlaylistEvent.swift
12 | // PlayKit
13 | //
14 | // Created by Sergii Chausov on 27.10.2021.
15 | //
16 |
17 | import Foundation
18 |
19 | @objc public class PlaylistEvent: PKEvent {
20 |
21 | @objc public static let allEventTypes: [PlaylistEvent.Type] = [
22 | playlistLoaded, playlistStarted, playlistEnded, playlistCountdownStart, playlistCountdownEnd, playlistLoopStateChanged, playlistLoopStateChanged, playlistAutoContinueStateChanged, playlistError, playlistLoadMediaError, playlistCurrentPlayingItemChanged
23 | ]
24 |
25 | @objc public static let playlistLoaded: PlaylistEvent.Type = PlaylistLoaded.self
26 | @objc public static let playlistStarted: PlaylistEvent.Type = PlaylistStarted.self
27 | @objc public static let playlistEnded: PlaylistEvent.Type = PlaylistEnded.self
28 | @objc public static let playlistCountdownStart: PlaylistEvent.Type = PlaylistCountdownStart.self
29 | @objc public static let playlistCountdownEnd: PlaylistEvent.Type = PlaylistCountdownEnd.self
30 | @objc public static let playlistLoopStateChanged: PlaylistEvent.Type = PlaylistLoopStateChanged.self
31 | @objc public static let playlistAutoContinueStateChanged: PlaylistEvent.Type = PlaylistAutoContinueStateChanged.self
32 | @objc public static let playlistError: PlaylistEvent.Type = PlaylistError.self
33 | @objc public static let playlistLoadMediaError: PlaylistEvent.Type = PlaylistLoadMediaError.self
34 | @objc public static let playlistCurrentPlayingItemChanged: PlaylistEvent.Type = PlaylistCurrentPlayingItemChanged.self
35 |
36 | public class PlaylistLoaded: PlaylistEvent {}
37 |
38 | public class PlaylistStarted: PlaylistEvent {}
39 |
40 | public class PlaylistEnded: PlaylistEvent {}
41 |
42 | public class PlaylistCountdownStart: PlaylistEvent {
43 | public convenience init(countDownDuration: TimeInterval) {
44 | self.init([EventDataKeys.duration: NSNumber(value: countDownDuration)])
45 | }
46 | }
47 |
48 | public class PlaylistCountdownEnd: PlaylistEvent {}
49 |
50 | public class PlaylistLoopStateChanged: PlaylistEvent {}
51 |
52 | public class PlaylistAutoContinueStateChanged: PlaylistEvent {}
53 |
54 | public class PlaylistError: PlaylistEvent {
55 | public convenience init(nsError: NSError) {
56 | self.init([EventDataKeys.error: nsError])
57 | }
58 |
59 | public convenience init(error: PKError) {
60 | self.init([EventDataKeys.error: error.asNSError])
61 | }
62 | }
63 |
64 | public class PlaylistLoadMediaError: PlaylistEvent {
65 | public convenience init(entryId: String, nsError: NSError) {
66 | self.init([EventDataKeys.entryId: entryId, EventDataKeys.error: nsError])
67 | }
68 |
69 | public convenience init(error: PKError) {
70 | self.init([EventDataKeys.error: error.asNSError])
71 | }
72 | }
73 |
74 | public class PlaylistCurrentPlayingItemChanged: PlaylistEvent {}
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/Example/Tests/MediaEntryProvider/MediaEntryProviderMock.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 | import PlayKit
13 | import KalturaNetKit
14 |
15 |
16 |
17 | class MediaEntryProviderMockExecutor: RequestExecutor {
18 |
19 | var entryID: String
20 | var domain: String
21 |
22 | init(entryID:String,domain:String) {
23 | self.entryID = entryID
24 | self.domain = domain
25 | }
26 |
27 | let serviceKey = "service"
28 | let actionKey = "action"
29 |
30 |
31 | func send(request:Request){
32 |
33 | var fileName = self.domain
34 |
35 | let urlComponent = request.url.absoluteString.components(separatedBy: "/")
36 |
37 | var serviceName: String? = nil
38 | var actionName: String? = nil
39 |
40 | let serviceKeyIndex = urlComponent.index(of: serviceKey)
41 |
42 | if let serviceKeyIndex = serviceKeyIndex{
43 | serviceName = urlComponent[serviceKeyIndex + 1]
44 | }
45 |
46 |
47 | let actionKeyIndex = urlComponent.index(of: actionKey)
48 | if let actionKeyIndex = actionKeyIndex{
49 | actionName = urlComponent[actionKeyIndex+1]
50 | }
51 |
52 |
53 | if let service = serviceName{
54 | fileName.append(".\(service)")
55 | }
56 | else{
57 | fileName.append("._")
58 | }
59 |
60 |
61 | if let action = actionName{
62 | fileName.append(".\(action)")
63 | }
64 | else{
65 | fileName.append("._")
66 | }
67 |
68 | fileName.append(".\(entryID)")
69 |
70 |
71 | let bundle = Bundle.main
72 | let path = bundle.path(forResource: fileName, ofType: "json")
73 | guard let filePath = path else {
74 |
75 | if let completion = request.completion{
76 | completion(Response(data: nil, error: nil))
77 | }
78 | return
79 | }
80 |
81 | let content = NSData(contentsOfFile:filePath) as Data?
82 |
83 | guard let contentFile = content else {
84 | if let completion = request.completion{
85 | completion(Response(data: nil, error: nil))
86 | }
87 | return
88 | }
89 |
90 | do{
91 | let result = try JSONSerialization.jsonObject(with: contentFile, options: JSONSerialization.ReadingOptions())
92 | if let completion = request.completion{
93 | completion(Response(data: result, error: nil))
94 | }
95 |
96 | }catch{
97 | if let completion = request.completion{
98 | completion(Response(data: nil, error: nil))
99 | }
100 |
101 | }
102 |
103 | }
104 |
105 | func cancel(request:Request){
106 |
107 | }
108 |
109 | func clean(){
110 |
111 | }
112 | }
113 |
114 |
115 |
--------------------------------------------------------------------------------
/TestApp/TestApp.xcodeproj/xcshareddata/xcschemes/TestApp_v5.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Classes/Managers/PlayKitManager.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import UIKit
12 | import PlayKitUtils
13 |
14 | /**
15 | Manager class used for:
16 | - creating `Player` objects.
17 | - creating and registering plugins.
18 | */
19 | @objc public class PlayKitManager: NSObject {
20 |
21 | @objc public static let versionString: String = Bundle(for: PlayKitManager.self).object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "dev"
22 | @objc public static let clientTag = "playkit/ios-\(versionString)"
23 | @objc public static let userAgent = UserAgent.build(clientTag: clientTag)
24 |
25 | @objc(sharedInstance) public static let shared: PlayKitManager = PlayKitManager()
26 |
27 | var pluginRegistry = Dictionary()
28 |
29 | // Private init to prevent initializing this singleton
30 | private override init() {
31 | if type(of: self) != PlayKitManager.self {
32 | fatalError("Private initializer, use shared instance instead")
33 | }
34 | }
35 |
36 | func createPlugin(name: String, player: Player, pluginConfig: Any?, messageBus: MessageBus) throws -> PKPlugin {
37 | guard let pluginClass = pluginRegistry[name] else {
38 | PKLog.error("plugin with name: \(name) doesn't exist in pluginRegistry")
39 | throw PKPluginError.failedToCreatePlugin(pluginName: name).asNSError
40 | }
41 | return try pluginClass.init(player: player, pluginConfig: pluginConfig, messageBus: messageBus)
42 | }
43 |
44 | // MARK: - Public
45 |
46 | /// Loads and returns a player object using a provided configuration.
47 | ///
48 | /// - Important: In order to start buffering the video after loading the player
49 | /// you must call prepare on the player with the same configuration.
50 | /// ````
51 | /// player = PlayKitManager.sharedInstance.loadPlayer(config: config)
52 | /// player.prepare(config)
53 | /// ````
54 | ///
55 | /// - Parameter pluginConfig: The configuration object to load the player with.
56 | /// - Returns: A player loaded using the provided configuration.
57 | @objc public func loadPlayer(pluginConfig: PluginConfig?) -> Player {
58 | let loader = PlayerLoader()
59 | loader.load(pluginConfig: pluginConfig)
60 | return loader
61 | }
62 |
63 | @objc public func registerPlugin(_ pluginClass: BasePlugin.Type) {
64 | PKLog.info("Registering plugin \(pluginClass.pluginName)/\(pluginClass.pluginVersion)")
65 |
66 | if let pluginWarmUp = pluginClass as? PKPluginWarmUp.Type {
67 | pluginWarmUp.warmUp()
68 | }
69 | pluginRegistry[pluginClass.pluginName] = pluginClass
70 | }
71 |
72 | @objc public func registeredPlugins() -> [String: String] {
73 | var dict = [String: String]()
74 | for (name, type) in pluginRegistry {
75 | dict[name] = type.pluginVersion
76 | }
77 | return dict
78 | }
79 |
80 | /// Sets the logging level for our logger.
81 | @objc public static var logLevel: PKLogLevel = .default {
82 | didSet {
83 | PKLog.outputLevel = logLevel.toLoggerLevel
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/TestApp/TestApp.xcodeproj/xcshareddata/xcschemes/TestApp_v4_2.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Example/PlayKit/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Classes/Network/KalturaPlaybackRequestAdapter.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | @objc public class KalturaPlaybackRequestAdapter: NSObject, PKRequestParamsAdapter {
14 |
15 | private var applicationName: String?
16 | private var sessionId: String?
17 |
18 | /// Installs a new kaltura request adapter on the provided player with custom application name.
19 | ///
20 | /// - Parameters:
21 | /// - player: The player you want to use with the request adapter
22 | /// - appName: the application name, if `nil` will use the bundle identifier.
23 | @objc public static func install(in player: Player, withAppName appName: String?) {
24 | let requestAdapter = KalturaPlaybackRequestAdapter()
25 | requestAdapter.sessionId = player.sessionId
26 | requestAdapter.applicationName = appName
27 | player.settings.contentRequestAdapter = requestAdapter
28 | }
29 |
30 | /// Updates the request adapter with info from the player
31 | @objc public func updateRequestAdapter(with player: Player) {
32 | self.sessionId = player.sessionId
33 | }
34 |
35 | /// Adapts the request params
36 | @objc public func adapt(requestParams: PKRequestParams) -> PKRequestParams {
37 | guard let sessionId = self.sessionId else { return requestParams }
38 | guard requestParams.url.path.contains("/playManifest/") else { return requestParams }
39 | guard var urlComponents = URLComponents(url: requestParams.url, resolvingAgainstBaseURL: false) else { return requestParams }
40 | // add query items to the request
41 | let queryItems = [
42 | URLQueryItem(name: "playSessionId", value: sessionId),
43 | URLQueryItem(name: "clientTag", value: PlayKitManager.clientTag),
44 | URLQueryItem(name: "referrer", value: self.applicationName == nil ? self.base64(from: Bundle.main.bundleIdentifier ?? "") : self.base64(from: self.applicationName!))
45 | ]
46 | if var urlQueryItems = urlComponents.queryItems {
47 | urlQueryItems += queryItems
48 | urlComponents.queryItems = urlQueryItems
49 | } else {
50 | urlComponents.queryItems = queryItems
51 | }
52 | // create the url
53 | guard let url = urlComponents.url else {
54 | PKLog.debug("failed to create url after appending query items")
55 | return requestParams
56 | }
57 | return PKRequestParams(url: url, headers: requestParams.headers)
58 | }
59 |
60 | }
61 |
62 | @objc public class CustomPlaybackRequestAdapter: KalturaPlaybackRequestAdapter {
63 |
64 | @objc public var httpHeaders: [String: String]?
65 |
66 | public override func adapt(requestParams: PKRequestParams) -> PKRequestParams {
67 | let parameters = super.adapt(requestParams: requestParams)
68 |
69 | if let customHeaders = self.httpHeaders {
70 | var newHeaders: [String: String] = [:]
71 | if let headers = parameters.headers {
72 | newHeaders = headers
73 | }
74 |
75 | for (key, value) in customHeaders {
76 | newHeaders[key] = value
77 | }
78 | return PKRequestParams(url: parameters.url, headers: newHeaders)
79 | }
80 |
81 | return parameters
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/Example/Tests/MediaEntryProvider/PhoenixMediaProviderTest.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license,
5 | // unless a different license for a particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import XCTest
12 | import PlayKit
13 | import KalturaNetKit
14 | import Quick
15 | import Nimble
16 |
17 | class PhoenixMediaProviderTest: QuickSpec, SessionProvider {
18 |
19 | /************************************************************/
20 | // MARK: - Mocks
21 | /************************************************************/
22 |
23 | fileprivate class RequestExecutorMock: RequestExecutor {
24 |
25 | let onRequestSend: (Request) -> Void
26 |
27 | init(onRequestSend: @escaping (Request) -> Void) {
28 | self.onRequestSend = onRequestSend
29 | }
30 |
31 | func send(request: Request) {
32 | onRequestSend(request)
33 | }
34 |
35 | func cancel(request: Request) {}
36 | func clean() {}
37 | }
38 |
39 | /************************************************************/
40 | // MARK: - SessionProvider
41 | /************************************************************/
42 |
43 | let mediaID = "485293"
44 | var partnerId: Int64 = 198
45 | var serverURL: String = "http://api-preprod.ott.kaltura.com/v4_2/api_v3"
46 |
47 | public func loadKS(completion: @escaping (String?, Error?) -> Void) {
48 | completion(nil, nil)
49 | }
50 |
51 | /************************************************************/
52 | // MARK: - Tests
53 | /************************************************************/
54 |
55 | override func spec() {
56 | describe("PhoenixMediaProviderTest") {
57 | it("should make a request") {
58 | let expectedUrl = URL(string: "http://api-preprod.ott.kaltura.com/v4_2/api_v3/service/multirequest")!
59 | let expectedBodyData = "{\"1\":{\"service\":\"ottUser\",\"action\":\"anonymousLogin\",\"partnerId\":198},\"2\":{\"service\":\"asset\",\"assetType\":\"media\",\"action\":\"getPlaybackContext\",\"assetId\":\"485293\",\"ks\":\"{1:result:ks}\",\"contextDataParams\":{\"context\":\"PLAYBACK\",\"mediaProtocols\":[\"https\"]}},\"clientTag\":\"java:16-09-10\",\"apiVersion\":\"3.6.1078.11798\"}".data(using: .utf8)
60 | let expectedRequestMethod: RequestMethod = .post
61 | let expectedHeaders = ["Content-Type": "application/json", "Accept": "application/json"]
62 |
63 | waitUntil(timeout: 10) { (done) in
64 | let mockExecutor = RequestExecutorMock { (request) in
65 | expect(expectedUrl).to(equal(request.url))
66 | expect(expectedRequestMethod).to(equal(request.method))
67 | expect(expectedBodyData).to(equal(request.dataBody))
68 | expect(expectedHeaders).to(equal(request.headers))
69 | done()
70 | }
71 |
72 | let provider = PhoenixMediaProvider()
73 | .set(sessionProvider: self)
74 | .set(assetId: self.mediaID)
75 | .set(type: .media)
76 | .set(playbackContextType: .playback)
77 | .set(executor: mockExecutor)
78 |
79 | provider.loadMedia { (media, error) in }
80 | }
81 | }
82 | }
83 | }
84 | }
85 |
86 |
87 |
--------------------------------------------------------------------------------
/Classes/Network/KalturaUDRMLicenseRequestAdapter.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2018 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 |
14 | @objc public class KalturaUDRMLicenseRequestAdapter: NSObject, PKRequestParamsAdapter {
15 |
16 | private var applicationName: String?
17 | @objc public var sessionId: String?
18 |
19 | /// Installs a new kaltura request adapter on the provided player with custom application name.
20 | ///
21 | /// - Parameters:
22 | /// - player: The player you want to use with the request adapter
23 | /// - appName: the application name, if `nil` will use the bundle identifier.
24 | @objc public static func install(in player: Player, withAppName appName: String?) {
25 | let requestAdapter = KalturaUDRMLicenseRequestAdapter()
26 | requestAdapter.sessionId = player.sessionId
27 | requestAdapter.applicationName = appName
28 | player.settings.licenseRequestAdapter = requestAdapter
29 | }
30 |
31 | /// Updates the request adapter with info from the player
32 | @objc public func updateRequestAdapter(with player: Player) {
33 | self.sessionId = player.sessionId
34 | }
35 |
36 | /// Adapts the request params
37 | @objc public func adapt(requestParams: PKRequestParams) -> PKRequestParams {
38 | guard let sessionId = self.sessionId else { return requestParams }
39 | guard let urlComponents = URLComponents(url: requestParams.url, resolvingAgainstBaseURL: false) else { return requestParams }
40 | // Prepare clientTag
41 | let clientTag = PlayKitManager.clientTag
42 | // Prepare referrer
43 | var referrer = Bundle.main.bundleIdentifier ?? ""
44 | if let appName = self.applicationName {
45 | referrer = appName
46 | }
47 | referrer = self.base64(from: referrer)
48 |
49 | if #available(iOS 11.0, tvOS 11.0, *) {
50 | let url = requestParams.url
51 | .appendingPercentEncodedQueryComponent(key: "playSessionId", value: sessionId)
52 | .appendingPercentEncodedQueryComponent(key: "clientTag", value: clientTag)
53 | .appendingPercentEncodedQueryComponent(key: "referrer", value: referrer)
54 |
55 | return PKRequestParams(url: url, headers: requestParams.headers)
56 | } else {
57 | var licenceUrlString = requestParams.url.absoluteString
58 |
59 | var queryParameters: [String: String] = [
60 | "playSessionId": percentEncoded(sessionId),
61 | "clientTag": percentEncoded(clientTag),
62 | "referrer": percentEncoded(referrer)
63 | ]
64 |
65 | if urlComponents.queryItems == nil,
66 | let firstKey = queryParameters.keys.first {
67 | licenceUrlString = licenceUrlString.appending("?\(firstKey)=\(queryParameters.removeValue(forKey: firstKey) ?? "")")
68 | }
69 |
70 | for item in queryParameters {
71 | licenceUrlString = licenceUrlString.appending("&\(item.key)=\(item.value)")
72 | }
73 |
74 | // create the url
75 | guard let url = URL(string: licenceUrlString) else {
76 | PKLog.debug("failed to create url after appending query items")
77 | return requestParams
78 | }
79 | return PKRequestParams(url: url, headers: requestParams.headers)
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/Classes/MessageBus.swift:
--------------------------------------------------------------------------------
1 | // ===================================================================================================
2 | // Copyright (C) 2017 Kaltura Inc.
3 | //
4 | // Licensed under the AGPLv3 license, unless a different license for a
5 | // particular library is specified in the applicable library path.
6 | //
7 | // You may obtain a copy of the License at
8 | // https://www.gnu.org/licenses/agpl-3.0.html
9 | // ===================================================================================================
10 |
11 | import Foundation
12 |
13 | private struct Observation {
14 | /// the observer of the event
15 | weak var observer: AnyObject?
16 | /// the dispatchQueue to observe the events on.
17 | let observeOn: DispatchQueue
18 | /// the block of code to be performed when event fires.
19 | let block: (PKEvent) -> Void
20 | }
21 |
22 | /// `MessageBus` object handles all event message observing and posting
23 | @objc public class MessageBus: NSObject {
24 |
25 | private var observations = [String: [Observation]]()
26 | private let dispatchQueue = DispatchQueue(label: "com.kaltura.playkit.message-bus")
27 |
28 | @objc public func addObserver(_ observer: AnyObject, events: [PKEvent.Type], block: @escaping (PKEvent) -> Void) {
29 | self.add(observer: observer, events: events, block: block)
30 | }
31 |
32 | @objc public func addObserver(_ observer: AnyObject, events: [PKEvent.Type], observeOn dispatchQueue: DispatchQueue, block: @escaping (PKEvent) -> Void) {
33 | self.add(observer: observer, events: events, observeOn: dispatchQueue, block: block)
34 | }
35 |
36 | private func add(observer: AnyObject, events: [PKEvent.Type], observeOn dispatchQueue: DispatchQueue = DispatchQueue.main, block: @escaping (PKEvent) -> Void) {
37 | self.dispatchQueue.sync {
38 | PKLog.verbose("Add observer: \(String(describing: observer)) for events: \(String(describing: events))")
39 | events.forEach { (et) in
40 | let typeId = NSStringFromClass(et)
41 | var observationList: [Observation] = observations[typeId] ?? []
42 | observationList.append(Observation(observer: observer, observeOn: dispatchQueue, block: block))
43 | observations[typeId] = observationList
44 | }
45 | }
46 | }
47 |
48 | @objc public func removeObserver(_ observer: AnyObject, events: [PKEvent.Type]) {
49 | self.dispatchQueue.sync {
50 | PKLog.verbose("Remove observer: \(String(describing: observer)) for events: \(String(describing: events))")
51 | events.forEach { (et) in
52 | let typeId = NSStringFromClass(et)
53 | guard let array = observations[typeId], !array.isEmpty else {
54 | PKLog.debug("removeObserver:: array is empty")
55 | return
56 | }
57 |
58 | observations[typeId] = array.filter {
59 | if let item = $0.observer {
60 | return item !== observer
61 | }
62 | return false
63 | }
64 | }
65 | }
66 | }
67 |
68 | @objc public func post(_ event: PKEvent) {
69 | self.dispatchQueue.sync { [weak self] in
70 | guard let self = self else { return }
71 | PKLog.verbose("post event: \(event), with data: \(event.data ?? [:])")
72 | let typeId = NSStringFromClass(type(of: event))
73 |
74 | if let array = self.observations[typeId] {
75 | // remove nil observers replace current observations with new ones, and call block with the event
76 | let newObservations = array.filter { $0.observer != nil }
77 | self.observations[typeId] = newObservations
78 | newObservations.forEach { observation in
79 | observation.observeOn.async {
80 | observation.block(event)
81 | }
82 | }
83 | }
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------