@jwplayer/jwplayer-react-native 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/ISSUE_TEMPLATE/question.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/LICENSE +21 -0
- package/README.md +425 -0
- package/RNJWPlayer.podspec +44 -0
- package/android/.gradle/8.1.1/checksums/checksums.lock +0 -0
- package/android/.gradle/8.1.1/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.1.1/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.1.1/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.1.1/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.1.1/gc.properties +0 -0
- package/android/.gradle/8.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.2/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.2/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.2/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.2/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/config.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/.idea/gradle.xml +12 -0
- package/android/.idea/migrations.xml +10 -0
- package/android/.idea/misc.xml +10 -0
- package/android/.idea/vcs.xml +6 -0
- package/android/.idea/workspace.xml +54 -0
- package/android/build.gradle +110 -0
- package/android/local.properties +8 -0
- package/android/src/main/AndroidManifest.xml +25 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/ArrayUtil.java +129 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/CastOptionsProvider.java +55 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/MapUtil.java +136 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayer.java +76 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerAds.java +239 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerModule.java +526 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerPackage.java +30 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +1499 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +171 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +219 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/WidevineCallback.java +62 -0
- package/badges/license.svg +1 -0
- package/badges/version.svg +1 -0
- package/docs/legacy_readme.md +634 -0
- package/docs/props.md +43 -0
- package/docs/types.md +254 -0
- package/index.d.ts +564 -0
- package/index.js +699 -0
- package/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift +119 -0
- package/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h +5 -0
- package/ios/RNJWPlayer/RNJWPlayerAds.swift +260 -0
- package/ios/RNJWPlayer/RNJWPlayerModels.swift +149 -0
- package/ios/RNJWPlayer/RNJWPlayerView.swift +1837 -0
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +616 -0
- package/ios/RNJWPlayer/RNJWPlayerViewManager.m +132 -0
- package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +500 -0
- package/ios/RNJWPlayer.xcodeproj/project.pbxproj +323 -0
- package/package.json +45 -0
|
@@ -0,0 +1,1837 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RNJWPlayerViewController.m
|
|
3
|
+
// RNJWPlayer
|
|
4
|
+
//
|
|
5
|
+
// Created by Chaim Paneth on 3/30/22.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import UIKit
|
|
9
|
+
import AVFoundation
|
|
10
|
+
import AVKit
|
|
11
|
+
import MediaPlayer
|
|
12
|
+
import React
|
|
13
|
+
import GoogleCast
|
|
14
|
+
import JWPlayerKit
|
|
15
|
+
|
|
16
|
+
class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDelegate, JWCastDelegate, JWAVDelegate, JWPlayerViewDelegate, JWPlayerViewControllerDelegate, JWDRMContentKeyDataSource, AVPictureInPictureControllerDelegate {
|
|
17
|
+
|
|
18
|
+
// MARK: - RNJWPlayer allocation
|
|
19
|
+
|
|
20
|
+
var playerViewController:RNJWPlayerViewController!
|
|
21
|
+
var playerView: JWPlayerView!
|
|
22
|
+
var audioSession: AVAudioSession!
|
|
23
|
+
var pipEnabled: Bool = true
|
|
24
|
+
var backgroundAudioEnabled: Bool = true
|
|
25
|
+
var userPaused: Bool = false
|
|
26
|
+
var wasInterrupted: Bool = false
|
|
27
|
+
var interfaceBehavior: JWInterfaceBehavior!
|
|
28
|
+
var fairplayCertUrl: String!
|
|
29
|
+
var processSpcUrl: String!
|
|
30
|
+
var contentUUID: String!
|
|
31
|
+
var audioCategory: String!
|
|
32
|
+
var audioMode: String!
|
|
33
|
+
var audioCategoryOptions: [String]!
|
|
34
|
+
var settingConfig: Bool = false
|
|
35
|
+
var pendingConfig: Bool = false
|
|
36
|
+
var currentConfig: [String : Any]!
|
|
37
|
+
var playerFailed = false
|
|
38
|
+
var castController: JWCastController!
|
|
39
|
+
var isCasting: Bool = false
|
|
40
|
+
var availableDevices: [AnyObject]!
|
|
41
|
+
|
|
42
|
+
@objc var onBuffer:RCTDirectEventBlock?
|
|
43
|
+
@objc var onUpdateBuffer:RCTDirectEventBlock?
|
|
44
|
+
@objc var onPlay:RCTDirectEventBlock?
|
|
45
|
+
@objc var onBeforePlay:RCTDirectEventBlock?
|
|
46
|
+
@objc var onAttemptPlay:RCTDirectEventBlock?
|
|
47
|
+
@objc var onPause:RCTDirectEventBlock?
|
|
48
|
+
@objc var onIdle:RCTDirectEventBlock?
|
|
49
|
+
@objc var onPlaylistItem:RCTDirectEventBlock?
|
|
50
|
+
@objc var onLoaded:RCTDirectEventBlock?
|
|
51
|
+
@objc var onVisible:RCTDirectEventBlock?
|
|
52
|
+
@objc var onTime:RCTDirectEventBlock?
|
|
53
|
+
@objc var onSeek:RCTDirectEventBlock?
|
|
54
|
+
@objc var onSeeked:RCTDirectEventBlock?
|
|
55
|
+
@objc var onRateChanged:RCTDirectEventBlock?
|
|
56
|
+
@objc var onPlaylist:RCTDirectEventBlock?
|
|
57
|
+
@objc var onPlaylistComplete:RCTDirectEventBlock?
|
|
58
|
+
@objc var onBeforeComplete:RCTDirectEventBlock?
|
|
59
|
+
@objc var onComplete:RCTDirectEventBlock?
|
|
60
|
+
@objc var onAudioTracks:RCTDirectEventBlock?
|
|
61
|
+
@objc var onPlayerReady:RCTDirectEventBlock?
|
|
62
|
+
@objc var onSetupPlayerError:RCTDirectEventBlock?
|
|
63
|
+
@objc var onPlayerError:RCTDirectEventBlock?
|
|
64
|
+
@objc var onPlayerWarning:RCTDirectEventBlock?
|
|
65
|
+
@objc var onPlayerAdWarning:RCTDirectEventBlock?
|
|
66
|
+
@objc var onPlayerAdError:RCTDirectEventBlock?
|
|
67
|
+
@objc var onAdEvent:RCTDirectEventBlock?
|
|
68
|
+
@objc var onAdTime:RCTDirectEventBlock?
|
|
69
|
+
@objc var onScreenTapped:RCTDirectEventBlock?
|
|
70
|
+
@objc var onControlBarVisible:RCTDirectEventBlock?
|
|
71
|
+
@objc var onFullScreen:RCTDirectEventBlock?
|
|
72
|
+
@objc var onFullScreenRequested:RCTDirectEventBlock?
|
|
73
|
+
@objc var onFullScreenExit:RCTDirectEventBlock?
|
|
74
|
+
@objc var onFullScreenExitRequested:RCTDirectEventBlock?
|
|
75
|
+
@objc var onPlayerSizeChange:RCTDirectEventBlock?
|
|
76
|
+
@objc var onCastingDevicesAvailable:RCTDirectEventBlock?
|
|
77
|
+
@objc var onConnectedToCastingDevice:RCTDirectEventBlock?
|
|
78
|
+
@objc var onDisconnectedFromCastingDevice:RCTDirectEventBlock?
|
|
79
|
+
@objc var onConnectionTemporarilySuspended:RCTDirectEventBlock?
|
|
80
|
+
@objc var onConnectionRecovered:RCTDirectEventBlock?
|
|
81
|
+
@objc var onConnectionFailed:RCTDirectEventBlock?
|
|
82
|
+
@objc var onCasting:RCTDirectEventBlock?
|
|
83
|
+
@objc var onCastingEnded:RCTDirectEventBlock?
|
|
84
|
+
@objc var onCastingFailed:RCTDirectEventBlock?
|
|
85
|
+
|
|
86
|
+
init() {
|
|
87
|
+
super.init(frame: CGRect(x: 20, y: 0, width: UIScreen.main.bounds.width - 40, height: 300))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
required init?(coder: NSCoder) {
|
|
91
|
+
super.init(coder: coder)
|
|
92
|
+
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
deinit {
|
|
96
|
+
self.startDeinitProcess()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override func removeFromSuperview() {
|
|
100
|
+
self.startDeinitProcess()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func startDeinitProcess() {
|
|
104
|
+
NotificationCenter.default.removeObserver(self, name:UIDevice.orientationDidChangeNotification, object:nil)
|
|
105
|
+
|
|
106
|
+
self.reset()
|
|
107
|
+
super.removeFromSuperview()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func reset() {
|
|
111
|
+
NotificationCenter.default.removeObserver(self, name:AVAudioSession.mediaServicesWereResetNotification, object:audioSession)
|
|
112
|
+
NotificationCenter.default.removeObserver(self, name:AVAudioSession.interruptionNotification, object:audioSession)
|
|
113
|
+
|
|
114
|
+
NotificationCenter.default.removeObserver(self, name:UIApplication.willResignActiveNotification, object:nil)
|
|
115
|
+
NotificationCenter.default.removeObserver(self, name:UIApplication.didEnterBackgroundNotification, object:nil)
|
|
116
|
+
NotificationCenter.default.removeObserver(self, name:UIApplication.willEnterForegroundNotification, object:nil)
|
|
117
|
+
NotificationCenter.default.removeObserver(self, name:AVAudioSession.routeChangeNotification, object:nil)
|
|
118
|
+
|
|
119
|
+
if (playerViewController != nil) || (playerView != nil) {
|
|
120
|
+
// playerViewController.player.currentItem!.removeObserver(self, forKeyPath:"playbackLikelyToKeepUp", context:nil)
|
|
121
|
+
if (playerView != nil) {
|
|
122
|
+
NotificationCenter.default.removeObserver(self, forKeyPath:"isPictureInPicturePossible", context:nil)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
self.removePlayerView()
|
|
127
|
+
self.dismissPlayerViewController()
|
|
128
|
+
|
|
129
|
+
self.deinitAudioSession()
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
override func layoutSubviews() {
|
|
134
|
+
super.layoutSubviews()
|
|
135
|
+
|
|
136
|
+
if self.playerView != nil {
|
|
137
|
+
self.playerView.frame = self.frame
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if self.playerViewController != nil {
|
|
141
|
+
self.playerViewController.view.frame = self.frame
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@objc func rotated(notification: Notification) {
|
|
146
|
+
if UIDevice.current.orientation.isLandscape {
|
|
147
|
+
print("Landscape")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if UIDevice.current.orientation.isPortrait {
|
|
151
|
+
print("Portrait")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
self.layoutSubviews()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func shouldAutorotate() -> Bool {
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// MARK: - RNJWPlayer props
|
|
162
|
+
|
|
163
|
+
func setLicense(license:String?) {
|
|
164
|
+
if (license != nil) {
|
|
165
|
+
JWPlayerKitLicense.setLicenseKey(license!)
|
|
166
|
+
} else {
|
|
167
|
+
print("JW SDK License key not set.")
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func keysForDifferingValues(in dict1: [String: Any], and dict2: [String: Any]) -> [String] {
|
|
172
|
+
var diffKeys = [String]()
|
|
173
|
+
|
|
174
|
+
for key in dict1.keys {
|
|
175
|
+
if let value1 = dict1[key], let value2 = dict2[key] {
|
|
176
|
+
if !areEqual(value1, value2) {
|
|
177
|
+
diffKeys.append(key)
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
// One of the dictionaries does not contain the key
|
|
181
|
+
diffKeys.append(key)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return diffKeys
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func areEqual(_ value1: Any, _ value2: Any) -> Bool {
|
|
189
|
+
let mirror1 = Mirror(reflecting: value1)
|
|
190
|
+
let mirror2 = Mirror(reflecting: value2)
|
|
191
|
+
|
|
192
|
+
if mirror1.displayStyle != mirror2.displayStyle {
|
|
193
|
+
// Different types
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
switch (value1, value2) {
|
|
198
|
+
case let (dict1 as [String: Any], dict2 as [String: Any]):
|
|
199
|
+
// Both are dictionaries, recursively compare
|
|
200
|
+
return keysForDifferingValues(in: dict1, and: dict2).isEmpty
|
|
201
|
+
case let (array1 as [Any], array2 as [Any]):
|
|
202
|
+
// Both are arrays, compare elements
|
|
203
|
+
return array1.count == array2.count && zip(array1, array2).allSatisfy(areEqual)
|
|
204
|
+
default:
|
|
205
|
+
// Use default String description for comparison
|
|
206
|
+
return String(describing: value1) == String(describing: value2)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
func dictionariesAreEqual(_ dict1: [String: Any]?, _ dict2: [String: Any]?) -> Bool {
|
|
212
|
+
return NSDictionary(dictionary: dict1 ?? [:]).isEqual(to: dict2 ?? [:])
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@objc func setJsonConfig(_ config: JSONObject) {
|
|
216
|
+
do {
|
|
217
|
+
let configuration = try JWPlayerConfigurationBuilder().configuration(json: config).build()
|
|
218
|
+
playerViewController.player.configurePlayer(with: configuration)
|
|
219
|
+
} catch {
|
|
220
|
+
print(error)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@objc func setConfig(_ config: [String: Any]) {
|
|
225
|
+
if (playerFailed) {
|
|
226
|
+
playerFailed = false
|
|
227
|
+
setNewConfig(config: config)
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Create mutable copies of the dictionaries
|
|
232
|
+
var configCopy = config
|
|
233
|
+
var currentConfigCopy = currentConfig
|
|
234
|
+
|
|
235
|
+
// Remove the playlist key
|
|
236
|
+
configCopy.removeValue(forKey: "playlist")
|
|
237
|
+
currentConfigCopy?.removeValue(forKey: "playlist")
|
|
238
|
+
|
|
239
|
+
// Compare dictionaries without the playlist key
|
|
240
|
+
if (currentConfigCopy == nil) || !dictionariesAreEqual(configCopy, currentConfigCopy!) {
|
|
241
|
+
print("There are differences other than the 'playlist' key.")
|
|
242
|
+
|
|
243
|
+
if (currentConfigCopy != nil) {
|
|
244
|
+
let diffKeys = keysForDifferingValues(in: configCopy, and: currentConfigCopy!)
|
|
245
|
+
print("There are differences in these keys: \(diffKeys)")
|
|
246
|
+
} else {
|
|
247
|
+
print("It's a new config")
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
setNewConfig(config: config)
|
|
251
|
+
} else {
|
|
252
|
+
// Compare original dictionaries
|
|
253
|
+
if !dictionariesAreEqual(currentConfig, config) {
|
|
254
|
+
print("The only difference is the 'playlist' key.")
|
|
255
|
+
|
|
256
|
+
var playlistArray = [JWPlayerItem]()
|
|
257
|
+
|
|
258
|
+
if let playlist = config["playlist"] as? [AnyObject] {
|
|
259
|
+
for item in playlist {
|
|
260
|
+
if let playerItem = try? getPlayerItem(item: item as! [String: Any]) {
|
|
261
|
+
playlistArray.append(playerItem)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if let playerViewController = playerViewController {
|
|
267
|
+
playerViewController.player.loadPlaylist(items: playlistArray)
|
|
268
|
+
} else if let playerView = playerView {
|
|
269
|
+
playerView.player.loadPlaylist(items: playlistArray)
|
|
270
|
+
} else {
|
|
271
|
+
setNewConfig(config: config)
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
print("There are no differences.")
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
func setNewConfig(config: [String : Any]) {
|
|
280
|
+
let forceLegacyConfig = config["forceLegacyConfig"] as? Bool?
|
|
281
|
+
let data:Data! = try? JSONSerialization.data(withJSONObject: config, options:.prettyPrinted)
|
|
282
|
+
let jwConfig = try? JWJSONParser.configFromJSON(data)
|
|
283
|
+
|
|
284
|
+
currentConfig = config
|
|
285
|
+
|
|
286
|
+
if !settingConfig {
|
|
287
|
+
pendingConfig = false
|
|
288
|
+
settingConfig = true
|
|
289
|
+
|
|
290
|
+
let license = config["license"] as? String
|
|
291
|
+
self.setLicense(license: license)
|
|
292
|
+
|
|
293
|
+
if let bae = config["backgroundAudioEnabled"] as? Bool, let pe = config["pipEnabled"] as? Bool {
|
|
294
|
+
backgroundAudioEnabled = bae
|
|
295
|
+
pipEnabled = pe
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if backgroundAudioEnabled || pipEnabled {
|
|
299
|
+
let category = config["category"] as? String
|
|
300
|
+
let categoryOptions = config["categoryOptions"] as? [String]
|
|
301
|
+
let mode = config["mode"] as? String
|
|
302
|
+
|
|
303
|
+
self.initAudioSession(category: category, categoryOptions: categoryOptions, mode: mode)
|
|
304
|
+
} else {
|
|
305
|
+
self.deinitAudioSession()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if forceLegacyConfig == true {
|
|
309
|
+
// Pull from top level of config
|
|
310
|
+
processSpcUrl = config["processSpcUrl"] as? String
|
|
311
|
+
fairplayCertUrl = config["certificateUrl"] as? String
|
|
312
|
+
contentUUID = config["contentUUID"] as? String
|
|
313
|
+
|
|
314
|
+
// Dangerous: check playlist for processSpcUrl / fairplayCertUrl in playlist
|
|
315
|
+
// Only checks first playlist item as multi-item DRM playlists are ill advised
|
|
316
|
+
if let playlist = config["playlist"] as? [AnyObject] {
|
|
317
|
+
let item = playlist.first
|
|
318
|
+
if let itemMap = item as? [String: Any] {
|
|
319
|
+
if itemMap["processSpcUrl"] != nil {
|
|
320
|
+
processSpcUrl = itemMap["processSpcUrl"] as? String
|
|
321
|
+
}
|
|
322
|
+
if itemMap["certificateUrl"] != nil {
|
|
323
|
+
fairplayCertUrl = itemMap["certificateUrl"] as? String
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
// TODO -- Ensure JWJSONParser pulls out cert/spc for sources (Expected in JW iOS SDK v4.19.0)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
do {
|
|
332
|
+
let viewOnly = config["viewOnly"] as? Bool
|
|
333
|
+
if viewOnly == true {
|
|
334
|
+
if forceLegacyConfig == true {
|
|
335
|
+
self.setupPlayerView(config: config, playerConfig: try self.getPlayerConfiguration(config: config))
|
|
336
|
+
} else {
|
|
337
|
+
self.setupPlayerView(config: config, playerConfig: jwConfig!)
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
if forceLegacyConfig == true {
|
|
341
|
+
self.setupPlayerViewController(config: config, playerConfig: try self.getPlayerConfiguration(config: config))
|
|
342
|
+
} else {
|
|
343
|
+
self.setupPlayerViewController(config: config, playerConfig: jwConfig!)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
print(error)
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
pendingConfig = true
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
@objc func setControls(_ controls:Bool) {
|
|
355
|
+
if let playerViewControllerView = playerViewController?.view {
|
|
356
|
+
self.toggleUIGroup(view: playerViewControllerView, name: "JWPlayerKit.InterfaceView", ofSubview: nil, show: controls)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// MARK: - RNJWPlayer styling
|
|
361
|
+
|
|
362
|
+
func colorWithHexString(hex:String!) -> UIColor! {
|
|
363
|
+
var cString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
|
|
364
|
+
|
|
365
|
+
// String should be 6 or 8 characters
|
|
366
|
+
if cString.count < 6 {
|
|
367
|
+
return UIColor.gray
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Strip 0X if it appears
|
|
371
|
+
if cString.hasPrefix("0X") {
|
|
372
|
+
cString = String(cString.dropFirst(2))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if cString.count != 6 {
|
|
376
|
+
return UIColor.gray
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Separate into r, g, b substrings
|
|
380
|
+
let startIndex = cString.startIndex
|
|
381
|
+
let endIndex = cString.index(startIndex, offsetBy: 2)
|
|
382
|
+
let rString = String(cString[startIndex..<endIndex])
|
|
383
|
+
|
|
384
|
+
let startIndexG = cString.index(startIndex, offsetBy: 2)
|
|
385
|
+
let endIndexG = cString.index(startIndexG, offsetBy: 2)
|
|
386
|
+
let gString = String(cString[startIndexG..<endIndexG])
|
|
387
|
+
|
|
388
|
+
let startIndexB = cString.index(startIndexG, offsetBy: 2)
|
|
389
|
+
let endIndexB = cString.index(startIndexB, offsetBy: 2)
|
|
390
|
+
let bString = String(cString[startIndexB..<endIndexB])
|
|
391
|
+
|
|
392
|
+
// Scan values
|
|
393
|
+
var r: UInt64 = 0, g: UInt64 = 0, b: UInt64 = 0
|
|
394
|
+
Scanner(string: rString).scanHexInt64(&r)
|
|
395
|
+
Scanner(string: gString).scanHexInt64(&g)
|
|
396
|
+
Scanner(string: bString).scanHexInt64(&b)
|
|
397
|
+
|
|
398
|
+
return UIColor(
|
|
399
|
+
red: CGFloat(r) / 255.0,
|
|
400
|
+
green: CGFloat(g) / 255.0,
|
|
401
|
+
blue: CGFloat(b) / 255.0,
|
|
402
|
+
alpha: 1.0
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
func setStyling(styling: [String: Any]) {
|
|
407
|
+
let skinStylingBuilder = JWPlayerSkinBuilder()
|
|
408
|
+
|
|
409
|
+
let colors: AnyObject! = styling["colors"] as AnyObject
|
|
410
|
+
if let colorsDict = colors as? [String: AnyObject] {
|
|
411
|
+
let timeSlider: AnyObject! = colorsDict["timeslider"] as AnyObject
|
|
412
|
+
if let timeSliderDict = timeSlider as? [String: AnyObject] {
|
|
413
|
+
let timeSliderStyleBuilder = JWTimeSliderStyleBuilder()
|
|
414
|
+
|
|
415
|
+
if let progress = timeSliderDict["progress"] as? String {
|
|
416
|
+
timeSliderStyleBuilder.minimumTrackColor(self.colorWithHexString(hex: progress))
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if let rail = timeSliderDict["rail"] as? String {
|
|
420
|
+
timeSliderStyleBuilder.maximumTrackColor(self.colorWithHexString(hex: rail))
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if let thumb = timeSliderDict["thumb"] as? String {
|
|
424
|
+
timeSliderStyleBuilder.thumbColor(self.colorWithHexString(hex: thumb))
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Build the time slider style and set it in the skin styling builder
|
|
428
|
+
do {
|
|
429
|
+
let timeSliderStyle: JWTimeSliderStyle = try timeSliderStyleBuilder.build()
|
|
430
|
+
skinStylingBuilder.timeSliderStyle(timeSliderStyle)
|
|
431
|
+
} catch let error {
|
|
432
|
+
// Handle error when building time slider style
|
|
433
|
+
print("Error building time slider style: \(error.localizedDescription)")
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let buttons: AnyObject! = colorsDict["buttons"] as AnyObject
|
|
437
|
+
if let buttonsColor = buttons as? String {
|
|
438
|
+
skinStylingBuilder.buttonsColor(self.colorWithHexString(hex: buttonsColor))
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let backgroundColor: AnyObject! = colorsDict["backgroundColor"] as AnyObject
|
|
442
|
+
if let bgColor = backgroundColor as? String {
|
|
443
|
+
skinStylingBuilder.backgroundColor(self.colorWithHexString(hex: bgColor))
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let fontColor: AnyObject! = colorsDict["fontColor"] as AnyObject
|
|
447
|
+
if let fontColorValue = fontColor as? String {
|
|
448
|
+
skinStylingBuilder.fontColor(self.colorWithHexString(hex: fontColorValue))
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let font: AnyObject! = styling["font"] as AnyObject
|
|
454
|
+
if let fontDict = font as? [String: AnyObject] {
|
|
455
|
+
let name: AnyObject! = fontDict["name"] as AnyObject
|
|
456
|
+
let size: AnyObject! = fontDict["size"] as AnyObject
|
|
457
|
+
|
|
458
|
+
if let fontName = name as? String, let fontSize = size as? CGFloat {
|
|
459
|
+
skinStylingBuilder.font(UIFont(name: fontName, size: fontSize)!)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let showTitle: AnyObject! = styling["displayTitle"] as AnyObject
|
|
464
|
+
if let titleVisible = showTitle as? Bool {
|
|
465
|
+
skinStylingBuilder.titleIsVisible(titleVisible)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let showDesc: AnyObject! = styling["displayDescription"] as AnyObject
|
|
469
|
+
if let descVisible = showDesc as? Bool {
|
|
470
|
+
skinStylingBuilder.descriptionIsVisible(descVisible)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
let capStyle: AnyObject! = styling["captionsStyle"] as AnyObject
|
|
474
|
+
if let capStyleDict = capStyle as? [String: AnyObject] {
|
|
475
|
+
let capStyleBuilder: JWCaptionStyleBuilder! = JWCaptionStyleBuilder()
|
|
476
|
+
|
|
477
|
+
let font: AnyObject! = capStyleDict["font"] as AnyObject
|
|
478
|
+
if let fontDict = font as? [String: AnyObject] {
|
|
479
|
+
let name: AnyObject! = fontDict["name"] as AnyObject
|
|
480
|
+
let size: AnyObject! = fontDict["size"] as AnyObject
|
|
481
|
+
if let fontName = name as? String, let fontSize = size as? CGFloat {
|
|
482
|
+
capStyleBuilder.font(UIFont(name: fontName, size: fontSize)!)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
let fontColor: AnyObject! = capStyleDict["fontColor"] as AnyObject
|
|
487
|
+
if let fontColorString = fontColor as? String {
|
|
488
|
+
capStyleBuilder.fontColor(self.colorWithHexString(hex: fontColorString))
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let backgroundColor: AnyObject! = capStyleDict["backgroundColor"] as AnyObject
|
|
492
|
+
if let bgColorString = backgroundColor as? String {
|
|
493
|
+
capStyleBuilder.backgroundColor(self.colorWithHexString(hex: bgColorString))
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let highlightColor: AnyObject! = capStyleDict["highlightColor"] as AnyObject
|
|
497
|
+
if let highlightColorString = highlightColor as? String {
|
|
498
|
+
capStyleBuilder.highlightColor(self.colorWithHexString(hex: highlightColorString))
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
let edgeStyle: AnyObject! = capStyleDict["edgeStyle"] as AnyObject
|
|
502
|
+
if let edgeStyleString = edgeStyle as? String {
|
|
503
|
+
capStyleBuilder.edgeStyle(RCTConvert.JWCaptionEdgeStyle(edgeStyleString))
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
do {
|
|
507
|
+
let captionStyle = try capStyleBuilder.build()
|
|
508
|
+
skinStylingBuilder.captionStyle(captionStyle)
|
|
509
|
+
} catch let error {
|
|
510
|
+
// Handle error when building time slider style
|
|
511
|
+
print("Error building caption style: \(error.localizedDescription)")
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
let menuStyle: AnyObject! = styling["menuStyle"] as AnyObject
|
|
516
|
+
if let menuStyleDict = menuStyle as? [String: AnyObject] {
|
|
517
|
+
let menuStyleBuilder: JWMenuStyleBuilder! = JWMenuStyleBuilder()
|
|
518
|
+
|
|
519
|
+
if let font = menuStyleDict["font"] as? [String: AnyObject],
|
|
520
|
+
let name = font["name"] as? String,
|
|
521
|
+
let size = font["size"] as? CGFloat {
|
|
522
|
+
menuStyleBuilder.font(UIFont(name: name, size: size)!)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if let fontColorString = menuStyleDict["fontColor"] as? String {
|
|
526
|
+
menuStyleBuilder.fontColor(self.colorWithHexString(hex: fontColorString))
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if let backgroundColorString = menuStyleDict["backgroundColor"] as? String {
|
|
530
|
+
menuStyleBuilder.backgroundColor(self.colorWithHexString(hex: backgroundColorString))
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
do {
|
|
534
|
+
let jwMenuStyle = try menuStyleBuilder.build()
|
|
535
|
+
skinStylingBuilder.menuStyle(jwMenuStyle)
|
|
536
|
+
} catch let error {
|
|
537
|
+
// Handle error when building time slider style
|
|
538
|
+
print("Error building menu style: \(error.localizedDescription)")
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// TODO this was broken when using `.playlist(string)`
|
|
543
|
+
do {
|
|
544
|
+
let skinStyling = try skinStylingBuilder.build()
|
|
545
|
+
DispatchQueue.main.async { [self] in
|
|
546
|
+
playerViewController.styling = skinStyling
|
|
547
|
+
}
|
|
548
|
+
} catch {
|
|
549
|
+
print(error)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// MARK: - RNJWPlayer config helpers
|
|
554
|
+
|
|
555
|
+
func getPlayerItem(item: [String: Any]) throws -> JWPlayerItem {
|
|
556
|
+
let itemBuilder = JWPlayerItemBuilder()
|
|
557
|
+
|
|
558
|
+
if let newFile = item["file"] as? String,
|
|
559
|
+
let url = URL(string: newFile) {
|
|
560
|
+
|
|
561
|
+
if url.scheme != nil && url.host != nil {
|
|
562
|
+
itemBuilder.file(url)
|
|
563
|
+
} else {
|
|
564
|
+
if let encodedString = newFile.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
|
|
565
|
+
let encodedUrl = URL(string: encodedString) {
|
|
566
|
+
itemBuilder.file(encodedUrl)
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Process sources
|
|
572
|
+
if let itemSources = item["sources"] as? [AnyObject], !itemSources.isEmpty {
|
|
573
|
+
var sourcesArray = [JWVideoSource]()
|
|
574
|
+
|
|
575
|
+
for source in itemSources.compactMap({ $0 as? [String: Any] }) {
|
|
576
|
+
if let file = source["file"] as? String,
|
|
577
|
+
let fileURL = URL(string: file),
|
|
578
|
+
let label = source["label"] as? String,
|
|
579
|
+
let isDefault = source["default"] as? Bool {
|
|
580
|
+
|
|
581
|
+
let sourceBuilder = JWVideoSourceBuilder()
|
|
582
|
+
sourceBuilder.file(fileURL)
|
|
583
|
+
sourceBuilder.label(label)
|
|
584
|
+
sourceBuilder.defaultVideo(isDefault)
|
|
585
|
+
|
|
586
|
+
if let sourceItem = try? sourceBuilder.build() {
|
|
587
|
+
sourcesArray.append(sourceItem)
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
itemBuilder.videoSources(sourcesArray)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Process other properties
|
|
596
|
+
if let mediaId = item["mediaId"] as? String {
|
|
597
|
+
itemBuilder.mediaId(mediaId)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if let title = item["title"] as? String {
|
|
601
|
+
itemBuilder.title(title)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if let description = item["description"] as? String {
|
|
605
|
+
itemBuilder.description(description)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if let image = item["image"] as? String, let imageURL = URL(string: image) {
|
|
609
|
+
itemBuilder.posterImage(imageURL)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if let startTime = item["startTime"] as? Double {
|
|
613
|
+
itemBuilder.startTime(startTime)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if let recommendations = item["recommendations"] as? String, let recURL = URL(string: recommendations) {
|
|
617
|
+
itemBuilder.recommendations(recURL)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Process tracks
|
|
621
|
+
if let tracksItem = item["tracks"] as? [AnyObject], !tracksItem.isEmpty {
|
|
622
|
+
var tracksArray = [JWMediaTrack]()
|
|
623
|
+
|
|
624
|
+
for trackItem in tracksItem.compactMap({ $0 as? [String: Any] }) {
|
|
625
|
+
if let file = trackItem["file"] as? String,
|
|
626
|
+
let fileURL = URL(string: file),
|
|
627
|
+
let label = trackItem["label"] as? String,
|
|
628
|
+
let isDefault = trackItem["default"] as? Bool {
|
|
629
|
+
|
|
630
|
+
let trackBuilder = JWCaptionTrackBuilder()
|
|
631
|
+
trackBuilder.file(fileURL)
|
|
632
|
+
trackBuilder.label(label)
|
|
633
|
+
trackBuilder.defaultTrack(isDefault)
|
|
634
|
+
|
|
635
|
+
if let track = try? trackBuilder.build() {
|
|
636
|
+
tracksArray.append(track)
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
itemBuilder.mediaTracks(tracksArray)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Process adSchedule
|
|
645
|
+
if let adsItem = item["adSchedule"] as? [AnyObject], !adsItem.isEmpty {
|
|
646
|
+
var adsArray = [JWAdBreak]()
|
|
647
|
+
|
|
648
|
+
for adItem in adsItem.compactMap({ $0 as? [String: Any] }) {
|
|
649
|
+
if let offsetString = adItem["offset"] as? String,
|
|
650
|
+
let tag = adItem["tag"] as? String,
|
|
651
|
+
let encodedString = tag.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
|
|
652
|
+
let tagURL = URL(string: encodedString),
|
|
653
|
+
let offset = JWAdOffset.from(string: offsetString) {
|
|
654
|
+
let adBreakBuilder = JWAdBreakBuilder()
|
|
655
|
+
adBreakBuilder.offset(offset)
|
|
656
|
+
adBreakBuilder.tags([tagURL])
|
|
657
|
+
|
|
658
|
+
if let adBreak = try? adBreakBuilder.build() {
|
|
659
|
+
adsArray.append(adBreak)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if !adsArray.isEmpty {
|
|
665
|
+
itemBuilder.adSchedule(breaks: adsArray)
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Process adVmap
|
|
670
|
+
if let adVmap = item["adVmap"] as? String, let adVmapURL = URL(string: adVmap) {
|
|
671
|
+
itemBuilder.adSchedule(vmapURL: adVmapURL)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
let playerItem = try itemBuilder.build()
|
|
675
|
+
|
|
676
|
+
return playerItem
|
|
677
|
+
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
func getPlayerConfiguration(config: [String: Any]) throws -> JWPlayerConfiguration {
|
|
681
|
+
let configBuilder:JWPlayerConfigurationBuilder! = JWPlayerConfigurationBuilder()
|
|
682
|
+
|
|
683
|
+
var playlistArray = [JWPlayerItem]()
|
|
684
|
+
|
|
685
|
+
if let playlist = config["playlist"] as? [[String: Any]] {
|
|
686
|
+
for item in playlist {
|
|
687
|
+
if let playerItem = try? self.getPlayerItem(item: item) {
|
|
688
|
+
playlistArray.append(playerItem)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
configBuilder.playlist(items: playlistArray)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if let autostart = config["autostart"] as? Bool {
|
|
695
|
+
configBuilder.autostart(autostart)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if let repeatContent = config["repeat"] as? Bool, repeatContent {
|
|
699
|
+
configBuilder.repeatContent(repeatContent)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if let preload = config["preload"] as? String {
|
|
703
|
+
configBuilder.preload(RCTConvert.JWPreload(preload))
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if let related = config["related"] as? [String: Any] {
|
|
707
|
+
let relatedBuilder = JWRelatedContentConfigurationBuilder()
|
|
708
|
+
|
|
709
|
+
relatedBuilder.onClick(RCTConvert.JWRelatedOnClick(related["onClick"] as! String))
|
|
710
|
+
relatedBuilder.onComplete(RCTConvert.JWRelatedOnComplete(related["onComplete"] as! String))
|
|
711
|
+
relatedBuilder.heading(related["heading"] as! String)
|
|
712
|
+
relatedBuilder.url(URL(string: related["url"] as? String ?? "")!)
|
|
713
|
+
relatedBuilder.autoplayMessage(related["autoplayMessage"] as! String)
|
|
714
|
+
relatedBuilder.autoplayTimer(related["autoplayTimer"] as? Int ?? 0)
|
|
715
|
+
|
|
716
|
+
let relatedContent = relatedBuilder.build()
|
|
717
|
+
configBuilder.related(relatedContent)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if let ads = config["advertising"] as? [String: Any] {
|
|
721
|
+
var advertisingConfig: JWAdvertisingConfig?
|
|
722
|
+
|
|
723
|
+
var jwAdClient = JWAdClient.unknown
|
|
724
|
+
if let adClientString = ads["adClient"] as? String {
|
|
725
|
+
jwAdClient = RCTConvert.JWAdClient(adClientString)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
switch jwAdClient {
|
|
729
|
+
case .JWPlayer:
|
|
730
|
+
advertisingConfig = RNJWPlayerAds.configureVAST(with: ads)
|
|
731
|
+
case .GoogleIMA:
|
|
732
|
+
advertisingConfig = RNJWPlayerAds.configureIMA(with: ads)
|
|
733
|
+
case .GoogleIMADAI:
|
|
734
|
+
advertisingConfig = RNJWPlayerAds.configureIMADAI(with: ads)
|
|
735
|
+
default:
|
|
736
|
+
advertisingConfig = RNJWPlayerAds.configureVAST(with: ads)
|
|
737
|
+
break
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (advertisingConfig != nil) {
|
|
741
|
+
configBuilder.advertising(advertisingConfig!)
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
let playerConfig = try configBuilder.build()
|
|
746
|
+
|
|
747
|
+
return playerConfig
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// MARK: - JWPlayer View Controller helpers
|
|
751
|
+
|
|
752
|
+
func setupPlayerViewController(config: [String: Any], playerConfig: JWPlayerConfiguration) {
|
|
753
|
+
if playerViewController == nil {
|
|
754
|
+
playerViewController = RNJWPlayerViewController()
|
|
755
|
+
playerViewController.parentView = self
|
|
756
|
+
|
|
757
|
+
DispatchQueue.main.async { [self] in
|
|
758
|
+
if self.reactViewController() != nil {
|
|
759
|
+
self.reactViewController()!.addChild(self.playerViewController)
|
|
760
|
+
self.playerViewController.didMove(toParent: self.reactViewController())
|
|
761
|
+
} else {
|
|
762
|
+
self.reactAddController(toClosestParent: self.playerViewController)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
playerViewController.view.frame = self.frame
|
|
767
|
+
self.addSubview(playerViewController.view)
|
|
768
|
+
playerViewController.setDelegates()
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if let ib = config["interfaceBehavior"] as? String {
|
|
772
|
+
interfaceBehavior = RCTConvert.JWInterfaceBehavior(ib)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if let interfaceFadeDelay = config["interfaceFadeDelay"] as? NSNumber {
|
|
776
|
+
playerViewController.interfaceFadeDelay = interfaceFadeDelay.doubleValue
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if let forceFullScreenOnLandscape = config["fullScreenOnLandscape"] as? Bool {
|
|
780
|
+
playerViewController.forceFullScreenOnLandscape = forceFullScreenOnLandscape
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if let forceLandscapeOnFullScreen = config["landscapeOnFullScreen"] as? Bool {
|
|
784
|
+
playerViewController.forceLandscapeOnFullScreen = forceLandscapeOnFullScreen
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if let enableLockScreenControls = config["enableLockScreenControls"] as? Bool {
|
|
788
|
+
playerViewController.enableLockScreenControls = enableLockScreenControls && backgroundAudioEnabled
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if let allowsPictureInPicturePlayback = config["allowsPictureInPicturePlayback"] as? Bool {
|
|
792
|
+
playerViewController.allowsPictureInPicturePlayback = allowsPictureInPicturePlayback
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if let styling = config["styling"] as? [String: Any] {
|
|
796
|
+
self.setStyling(styling: styling)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if let nextUpStyle = config["nextUpStyle"] as? [String: Any] {
|
|
800
|
+
let nextUpBuilder = JWNextUpStyleBuilder()
|
|
801
|
+
|
|
802
|
+
if let offsetSeconds = nextUpStyle["offsetSeconds"] as? Double {
|
|
803
|
+
nextUpBuilder.timeOffset(seconds: offsetSeconds)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if let offsetPercentage = nextUpStyle["offsetPercentage"] as? Double {
|
|
807
|
+
nextUpBuilder.timeOffset(seconds: offsetPercentage)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
do {
|
|
811
|
+
playerViewController.nextUpStyle = try nextUpBuilder.build()
|
|
812
|
+
} catch {
|
|
813
|
+
print(error)
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// playerViewController.adInterfaceStyle
|
|
818
|
+
// playerViewController.logo
|
|
819
|
+
// playerView.videoGravity = 0;
|
|
820
|
+
// playerView.captionStyle
|
|
821
|
+
|
|
822
|
+
if let offlineMsg = config["offlineMessage"] as? String {
|
|
823
|
+
playerViewController.offlineMessage = offlineMsg
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if let offlineImg = config["offlineImage"] as? String {
|
|
827
|
+
if let imageUrl = URL(string: offlineImg), imageUrl.isFileURL {
|
|
828
|
+
if let imageData = try? Data(contentsOf: imageUrl),
|
|
829
|
+
let image = UIImage(data: imageData) {
|
|
830
|
+
playerViewController.offlinePosterImage = image
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
self.presentPlayerViewController(configuration: playerConfig)
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
func dismissPlayerViewController() {
|
|
839
|
+
if (playerViewController != nil) {
|
|
840
|
+
playerViewController.player.pause() // hack for stop not always stopping on unmount
|
|
841
|
+
playerViewController.player.stop()
|
|
842
|
+
playerViewController.enableLockScreenControls = false
|
|
843
|
+
|
|
844
|
+
// hack for stop not always stopping on unmount
|
|
845
|
+
let configBuilder:JWPlayerConfigurationBuilder! = JWPlayerConfigurationBuilder()
|
|
846
|
+
configBuilder.playlist(items: [])
|
|
847
|
+
|
|
848
|
+
do {
|
|
849
|
+
let configuration: JWPlayerConfiguration = try configBuilder.build()
|
|
850
|
+
playerViewController.player.configurePlayer(with: configuration)
|
|
851
|
+
} catch {
|
|
852
|
+
print(error)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
playerViewController.parentView = nil
|
|
857
|
+
playerViewController.setVisibility(.hidden, for:[.pictureInPictureButton])
|
|
858
|
+
playerViewController.view.removeFromSuperview()
|
|
859
|
+
playerViewController.removeFromParent()
|
|
860
|
+
playerViewController.willMove(toParent: nil)
|
|
861
|
+
playerViewController.removeDelegates()
|
|
862
|
+
playerViewController = nil
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
func presentPlayerViewController(configuration: JWPlayerConfiguration!) {
|
|
867
|
+
if configuration != nil {
|
|
868
|
+
playerViewController.player.configurePlayer(with: configuration)
|
|
869
|
+
if (interfaceBehavior != nil) {
|
|
870
|
+
playerViewController.interfaceBehavior = interfaceBehavior
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// MARK: - JWPlayer View helpers
|
|
876
|
+
|
|
877
|
+
func setupPlayerView(config: [String: Any], playerConfig: JWPlayerConfiguration) {
|
|
878
|
+
playerView = JWPlayerView(frame:self.superview!.frame)
|
|
879
|
+
|
|
880
|
+
playerView.delegate = self
|
|
881
|
+
playerView.player.delegate = self
|
|
882
|
+
playerView.player.playbackStateDelegate = self
|
|
883
|
+
playerView.player.adDelegate = self
|
|
884
|
+
playerView.player.avDelegate = self
|
|
885
|
+
playerView.player.contentKeyDataSource = self
|
|
886
|
+
|
|
887
|
+
playerView.player.configurePlayer(with: playerConfig)
|
|
888
|
+
|
|
889
|
+
if pipEnabled {
|
|
890
|
+
let pipController:AVPictureInPictureController! = playerView.pictureInPictureController
|
|
891
|
+
pipController.delegate = self
|
|
892
|
+
|
|
893
|
+
pipController.addObserver(self, forKeyPath:"isPictureInPicturePossible", options:[.new, .initial], context:nil)
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
self.addSubview(self.playerView)
|
|
897
|
+
|
|
898
|
+
if let autostart = config["autostart"] as? Bool, autostart {
|
|
899
|
+
playerView.player.play()
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Time observers
|
|
903
|
+
weak var weakSelf:RNJWPlayerView! = self
|
|
904
|
+
playerView.player.adTimeObserver = { (time:JWTimeData!) in
|
|
905
|
+
weakSelf.onAdTime?(["position": time.position, "duration": time.duration])
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
playerView.player.mediaTimeObserver = { (time:JWTimeData!) in
|
|
909
|
+
weakSelf.onTime?(["position": time.position, "duration": time.duration])
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
func removePlayerView() {
|
|
914
|
+
if (playerView != nil) {
|
|
915
|
+
playerView.player.stop()
|
|
916
|
+
playerView.removeFromSuperview()
|
|
917
|
+
playerView = nil
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
func toggleUIGroup(view: UIView, name: String, ofSubview: String?, show: Bool) {
|
|
922
|
+
let subviews = view.subviews
|
|
923
|
+
|
|
924
|
+
for subview in subviews {
|
|
925
|
+
if NSStringFromClass(subview.classForCoder) == name && (ofSubview == nil || NSStringFromClass(subview.superview!.classForCoder) == name) {
|
|
926
|
+
subview.isHidden = !show
|
|
927
|
+
} else {
|
|
928
|
+
toggleUIGroup(view: subview, name: name, ofSubview: ofSubview, show: show)
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
func setVisibility(isVisible:Bool, forControls controls:[String]) {
|
|
934
|
+
var _controls:[JWControlType]! = [JWControlType]()
|
|
935
|
+
|
|
936
|
+
for control:String? in controls {
|
|
937
|
+
if (control != nil && !control!.isEmpty) {
|
|
938
|
+
let type:JWControlType = RCTConvert.JWControlType(control!)
|
|
939
|
+
_controls.append(type)
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (_controls.count > 0) {
|
|
944
|
+
playerViewController.setVisibility(isVisible ? .visible : .hidden, for: _controls!)
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// MARK: - JWPlayer Delegate
|
|
949
|
+
|
|
950
|
+
func jwplayerIsReady(_ player:JWPlayer) {
|
|
951
|
+
settingConfig = false
|
|
952
|
+
self.onPlayerReady?([:])
|
|
953
|
+
|
|
954
|
+
if pendingConfig && currentConfig != nil {
|
|
955
|
+
self.setConfig(currentConfig)
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
func jwplayer(_ player:JWPlayer, failedWithError code:UInt, message:String) {
|
|
960
|
+
self.onPlayerError?(["error": message])
|
|
961
|
+
playerFailed = true
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
func jwplayer(_ player:JWPlayer, failedWithSetupError code:UInt, message:String) {
|
|
965
|
+
self.onSetupPlayerError?(["error": message])
|
|
966
|
+
playerFailed = true
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
func jwplayer(_ player:JWPlayer, encounteredWarning code:UInt, message:String) {
|
|
970
|
+
self.onPlayerWarning?(["warning": message])
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
func jwplayer(_ player:JWPlayer, encounteredAdError code:UInt, message:String) {
|
|
974
|
+
self.onPlayerAdError?(["error": message])
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
func jwplayer(_ player:JWPlayer, encounteredAdWarning code:UInt, message:String) {
|
|
979
|
+
self.onPlayerAdWarning?(["warning": message])
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
// MARK: - JWPlayer View Delegate
|
|
984
|
+
|
|
985
|
+
func playerView(_ view:JWPlayerView, sizeChangedFrom oldSize:CGSize, to newSize:CGSize) {
|
|
986
|
+
let oldSizeDict: [String: Any] = [
|
|
987
|
+
"width": oldSize.width,
|
|
988
|
+
"height": oldSize.height
|
|
989
|
+
]
|
|
990
|
+
|
|
991
|
+
let newSizeDict: [String: Any] = [
|
|
992
|
+
"width": newSize.width,
|
|
993
|
+
"height": newSize.height
|
|
994
|
+
]
|
|
995
|
+
|
|
996
|
+
let sizesDict: [String: Any] = [
|
|
997
|
+
"oldSize": oldSizeDict,
|
|
998
|
+
"newSize": newSizeDict
|
|
999
|
+
]
|
|
1000
|
+
|
|
1001
|
+
do {
|
|
1002
|
+
let data = try JSONSerialization.data(withJSONObject: sizesDict, options: .prettyPrinted)
|
|
1003
|
+
self.onPlayerSizeChange?(["sizes": data])
|
|
1004
|
+
} catch {
|
|
1005
|
+
print("Error converting dictionary to JSON data: \(error)")
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// MARK: - JWPlayer View Controller Delegate
|
|
1010
|
+
|
|
1011
|
+
func playerViewController(_ controller:JWPlayerViewController, sizeChangedFrom oldSize:CGSize, to newSize:CGSize) {
|
|
1012
|
+
let oldSizeDict: [String: Any] = [
|
|
1013
|
+
"width": oldSize.width,
|
|
1014
|
+
"height": oldSize.height
|
|
1015
|
+
]
|
|
1016
|
+
|
|
1017
|
+
let newSizeDict: [String: Any] = [
|
|
1018
|
+
"width": newSize.width,
|
|
1019
|
+
"height": newSize.height
|
|
1020
|
+
]
|
|
1021
|
+
|
|
1022
|
+
let sizesDict: [String: Any] = [
|
|
1023
|
+
"oldSize": oldSizeDict,
|
|
1024
|
+
"newSize": newSizeDict
|
|
1025
|
+
]
|
|
1026
|
+
|
|
1027
|
+
do {
|
|
1028
|
+
let data = try JSONSerialization.data(withJSONObject: sizesDict, options: .prettyPrinted)
|
|
1029
|
+
self.onPlayerSizeChange?(["sizes": data])
|
|
1030
|
+
} catch {
|
|
1031
|
+
print("Error converting dictionary to JSON data: \(error)")
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
func playerViewController(_ controller:JWPlayerViewController, screenTappedAt position:CGPoint) {
|
|
1036
|
+
self.onScreenTapped?(["x": position.x, "y": position.y])
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
func playerViewController(_ controller:JWPlayerViewController, controlBarVisibilityChanged isVisible:Bool, frame:CGRect) {
|
|
1040
|
+
self.onControlBarVisible?(["visible": isVisible])
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
func playerViewControllerWillGoFullScreen(_ controller:JWPlayerViewController) -> JWFullScreenViewController? {
|
|
1044
|
+
self.onFullScreenRequested?([:])
|
|
1045
|
+
return nil
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
func playerViewControllerDidGoFullScreen(_ controller:JWPlayerViewController) {
|
|
1049
|
+
self.onFullScreen?([:])
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
func playerViewControllerWillDismissFullScreen(_ controller:JWPlayerViewController) {
|
|
1053
|
+
self.onFullScreenExitRequested?([:])
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
func playerViewControllerDidDismissFullScreen(_ controller:JWPlayerViewController) {
|
|
1057
|
+
self.onFullScreenExit?([:])
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
func playerViewController(_ controller:JWPlayerViewController, relatedMenuClosedWithMethod method: JWRelatedInteraction) {
|
|
1061
|
+
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
func playerViewController(_ controller: JWPlayerViewController, relatedMenuOpenedWithItems items: [JWPlayerItem], withMethod method: JWRelatedInteraction) {
|
|
1065
|
+
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
func playerViewController(_ controller: JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerItem, atIndex index: Int, withMethod method: JWRelatedMethod) {
|
|
1069
|
+
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// MARK: Time events
|
|
1073
|
+
|
|
1074
|
+
func onAdTimeEvent(time:JWTimeData) {
|
|
1075
|
+
self.onAdTime?(["position": time.position, "duration": time.duration])
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
func onMediaTimeEvent(time:JWTimeData) {
|
|
1079
|
+
self.onTime?(["position": time.position, "duration": time.duration])
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// MARK: - DRM Delegate
|
|
1083
|
+
|
|
1084
|
+
func contentIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) {
|
|
1085
|
+
let data:Data! = url.host?.data(using: String.Encoding.utf8)
|
|
1086
|
+
handler(data)
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
func appIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) {
|
|
1090
|
+
guard let fairplayCertUrlString = fairplayCertUrl, let finalUrl = URL(string: fairplayCertUrlString) else {
|
|
1091
|
+
return
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
let request = URLRequest(url: finalUrl)
|
|
1095
|
+
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
|
|
1096
|
+
if let error = error {
|
|
1097
|
+
print("DRM cert request error - \(error.localizedDescription)")
|
|
1098
|
+
}
|
|
1099
|
+
handler(data)
|
|
1100
|
+
}
|
|
1101
|
+
task.resume()
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
func contentKeyWithSPCData(_ spcData: Data, completionHandler handler: @escaping (Data?, Date?, String?) -> Void) {
|
|
1105
|
+
if processSpcUrl == nil {
|
|
1106
|
+
return
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
guard let processSpcUrl = URL(string: processSpcUrl) else {
|
|
1110
|
+
print("Invalid processSpcUrl")
|
|
1111
|
+
handler(nil, nil, nil)
|
|
1112
|
+
return
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
var ckcRequest = URLRequest(url: processSpcUrl)
|
|
1116
|
+
ckcRequest.httpMethod = "POST"
|
|
1117
|
+
ckcRequest.httpBody = spcData
|
|
1118
|
+
ckcRequest.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
|
1119
|
+
|
|
1120
|
+
URLSession.shared.dataTask(with: ckcRequest) { (data, response, error) in
|
|
1121
|
+
if let httpResponse = response as? HTTPURLResponse, (error != nil || httpResponse.statusCode != 200) {
|
|
1122
|
+
print("DRM ckc request error - %@", error?.localizedDescription ?? "Unknown error")
|
|
1123
|
+
handler(nil, nil, nil)
|
|
1124
|
+
return
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
handler(data, nil, "application/octet-stream")
|
|
1128
|
+
}.resume()
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// MARK: - AV Picture In Picture Delegate
|
|
1132
|
+
|
|
1133
|
+
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
|
1134
|
+
// if let playerView = playerView {
|
|
1135
|
+
// if keyPath == "playbackLikelyToKeepUp" {
|
|
1136
|
+
// playerView.player.play()
|
|
1137
|
+
// }
|
|
1138
|
+
// } else if let playerViewController = playerViewController {
|
|
1139
|
+
// if keyPath == "playbackLikelyToKeepUp" {
|
|
1140
|
+
// playerViewController.player.play()
|
|
1141
|
+
// }
|
|
1142
|
+
// }
|
|
1143
|
+
//
|
|
1144
|
+
if let keyPath = keyPath, keyPath == "isPictureInPicturePossible", let playerView = playerView, object as? AVPictureInPictureController == playerView.pictureInPictureController {
|
|
1145
|
+
// Your code here for handling isPictureInPicturePossible
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
1150
|
+
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
1154
|
+
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
1158
|
+
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
func pictureInPictureController(pictureInPictureController:AVPictureInPictureController!, failedToStartPictureInPictureWithError error:NSError!) {
|
|
1162
|
+
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
1166
|
+
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
func pictureInPictureController(_ pictureInPictureController:AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler:@escaping (Bool) -> Void) {
|
|
1170
|
+
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
|
|
1174
|
+
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// MARK: - JWPlayer State Delegate
|
|
1178
|
+
|
|
1179
|
+
func jwplayerContentIsBuffering(_ player:JWPlayer) {
|
|
1180
|
+
self.onBuffer?([:])
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
func jwplayer(_ player:JWPlayer, isBufferingWithReason reason:JWBufferReason) {
|
|
1184
|
+
self.onBuffer?([:])
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
func jwplayer(_ player:JWPlayer, updatedBuffer percent:Double, position time:JWTimeData) {
|
|
1188
|
+
self.onUpdateBuffer?(["percent": percent, "position": time])
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
func jwplayer(_ player:JWPlayer, didFinishLoadingWithTime loadTime:TimeInterval) {
|
|
1192
|
+
self.onLoaded?([:])
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
func jwplayer(_ player:JWPlayer, isAttemptingToPlay playlistItem:JWPlayerItem, reason:JWPlayReason) {
|
|
1196
|
+
self.onAttemptPlay?([:])
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
func jwplayer(_ player:JWPlayer, isPlayingWithReason reason:JWPlayReason) {
|
|
1200
|
+
self.onPlay?([:])
|
|
1201
|
+
|
|
1202
|
+
userPaused = false
|
|
1203
|
+
wasInterrupted = false
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
func jwplayer(_ player:JWPlayer, willPlayWithReason reason:JWPlayReason) {
|
|
1207
|
+
self.onBeforePlay?([:])
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
func jwplayer(_ player:JWPlayer, didPauseWithReason reason:JWPauseReason) {
|
|
1211
|
+
self.onPause?([:])
|
|
1212
|
+
|
|
1213
|
+
if !wasInterrupted {
|
|
1214
|
+
userPaused = true
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
func jwplayer(_ player:JWPlayer, didBecomeIdleWithReason reason:JWIdleReason) {
|
|
1219
|
+
self.onIdle?([:])
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
func jwplayer(_ player:JWPlayer, isVisible:Bool) {
|
|
1223
|
+
self.onVisible?(["visible": isVisible])
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
func jwplayerContentWillComplete(_ player:JWPlayer) {
|
|
1227
|
+
self.onBeforeComplete?([:])
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
func jwplayerContentDidComplete(_ player:JWPlayer) {
|
|
1231
|
+
self.onComplete?([:])
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
func jwplayer(_ player:JWPlayer, didLoadPlaylistItem item:JWPlayerItem, at index:UInt) {
|
|
1235
|
+
// var sourceDict: [String: Any] = [:]
|
|
1236
|
+
// var file: String?
|
|
1237
|
+
//
|
|
1238
|
+
// for source in item.videoSources {
|
|
1239
|
+
// sourceDict["file"] = source.file?.absoluteString
|
|
1240
|
+
// sourceDict["label"] = source.label
|
|
1241
|
+
// sourceDict["default"] = source.defaultVideo
|
|
1242
|
+
//
|
|
1243
|
+
// if source.defaultVideo {
|
|
1244
|
+
// file = source.file?.absoluteString
|
|
1245
|
+
// }
|
|
1246
|
+
// }
|
|
1247
|
+
|
|
1248
|
+
// var schedDict: [String: Any] = [:]
|
|
1249
|
+
//
|
|
1250
|
+
// if let schedules = item.adSchedule {
|
|
1251
|
+
// for sched in schedules {
|
|
1252
|
+
// schedDict["offset"] = sched.offset
|
|
1253
|
+
// schedDict["tags"] = sched.tags
|
|
1254
|
+
// schedDict["type"] = sched.type
|
|
1255
|
+
// }
|
|
1256
|
+
// }
|
|
1257
|
+
|
|
1258
|
+
// var trackDict: [String: Any] = [:]
|
|
1259
|
+
|
|
1260
|
+
// if let tracks = item.mediaTracks {
|
|
1261
|
+
// for track in tracks {
|
|
1262
|
+
// trackDict["file"] = track.file?.absoluteString
|
|
1263
|
+
// trackDict["label"] = track.label
|
|
1264
|
+
// trackDict["default"] = track.defaultTrack
|
|
1265
|
+
// }
|
|
1266
|
+
// }
|
|
1267
|
+
|
|
1268
|
+
// let itemDict: [String: Any] = [
|
|
1269
|
+
// "file": file ?? "",
|
|
1270
|
+
// "mediaId": item.mediaId as Any,
|
|
1271
|
+
// "title": item.title as Any,
|
|
1272
|
+
// "description": item.description,
|
|
1273
|
+
// "image": item.posterImage?.absoluteString ?? "",
|
|
1274
|
+
// "startTime": item.startTime,
|
|
1275
|
+
// "adVmap": item.vmapURL?.absoluteString ?? "",
|
|
1276
|
+
// "recommendations": item.recommendations?.absoluteString ?? "",
|
|
1277
|
+
// "sources": sourceDict,
|
|
1278
|
+
// "adSchedule": schedDict,
|
|
1279
|
+
// "tracks": trackDict
|
|
1280
|
+
// ]
|
|
1281
|
+
|
|
1282
|
+
do {
|
|
1283
|
+
let data:Data! = try JSONSerialization.data(withJSONObject: item.toJSONObject(), options:.prettyPrinted)
|
|
1284
|
+
self.onPlaylistItem?(["playlistItem": String(data:data, encoding:String.Encoding.utf8) as Any, "index": index])
|
|
1285
|
+
} catch {
|
|
1286
|
+
print("Error converting dictionary to JSON data: \(error)")
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// item.addObserver(self, forKeyPath:"playbackLikelyToKeepUp", options:.new, context:nil)
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
func jwplayer(_ player:JWPlayer, didLoadPlaylist playlist:[JWPlayerItem]) {
|
|
1293
|
+
let playlistArray:NSMutableArray! = NSMutableArray()
|
|
1294
|
+
|
|
1295
|
+
for item:JWPlayerItem? in playlist {
|
|
1296
|
+
// var file:String!
|
|
1297
|
+
//
|
|
1298
|
+
// var sourceDict: [String: Any] = [:]
|
|
1299
|
+
//
|
|
1300
|
+
// for source in item?.videoSources ?? [] {
|
|
1301
|
+
// sourceDict["file"] = source.file?.absoluteString
|
|
1302
|
+
// sourceDict["label"] = source.label
|
|
1303
|
+
// sourceDict["default"] = source.defaultVideo
|
|
1304
|
+
//
|
|
1305
|
+
// if source.defaultVideo {
|
|
1306
|
+
// file = source.file?.absoluteString ?? ""
|
|
1307
|
+
// }
|
|
1308
|
+
// }
|
|
1309
|
+
//
|
|
1310
|
+
// var schedDict: [String: Any] = [:]
|
|
1311
|
+
// if let adSchedule = item?.adSchedule {
|
|
1312
|
+
// for sched in adSchedule {
|
|
1313
|
+
// schedDict["offset"] = sched.offset
|
|
1314
|
+
// schedDict["tags"] = sched.tags
|
|
1315
|
+
// schedDict["type"] = sched.type
|
|
1316
|
+
// }
|
|
1317
|
+
// }
|
|
1318
|
+
//
|
|
1319
|
+
// var trackDict: [String: Any] = [:]
|
|
1320
|
+
//
|
|
1321
|
+
// if let mediaTracks = item?.mediaTracks {
|
|
1322
|
+
// for track in mediaTracks {
|
|
1323
|
+
// trackDict["file"] = track.file?.absoluteString
|
|
1324
|
+
// trackDict["label"] = track.label
|
|
1325
|
+
// trackDict["default"] = track.defaultTrack
|
|
1326
|
+
// }
|
|
1327
|
+
// }
|
|
1328
|
+
//
|
|
1329
|
+
// let itemDict: [String: Any] = [
|
|
1330
|
+
// "file": file ?? "",
|
|
1331
|
+
// "mediaId": item?.mediaId ?? "",
|
|
1332
|
+
// "title": item?.title ?? "",
|
|
1333
|
+
// "description": item?.description ?? "",
|
|
1334
|
+
// "image": item?.posterImage?.absoluteString ?? "",
|
|
1335
|
+
// "startTime": item?.startTime ?? 0,
|
|
1336
|
+
// "adVmap": item?.vmapURL?.absoluteString ?? "",
|
|
1337
|
+
// "recommendations": item?.recommendations?.absoluteString ?? "",
|
|
1338
|
+
// "sources": sourceDict as Any,
|
|
1339
|
+
// "adSchedule": trackDict,
|
|
1340
|
+
// "tracks": schedDict
|
|
1341
|
+
// ]
|
|
1342
|
+
|
|
1343
|
+
playlistArray.add(item?.toJSONObject() as Any)
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
do {
|
|
1347
|
+
let data:Data! = try JSONSerialization.data(withJSONObject: playlistArray as Any, options:.prettyPrinted)
|
|
1348
|
+
|
|
1349
|
+
self.onPlaylist?(["playlist": String(data:data as Data, encoding:String.Encoding.utf8) as Any])
|
|
1350
|
+
} catch {
|
|
1351
|
+
print("Error converting dictionary to JSON data: \(error)")
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
func jwplayerPlaylistHasCompleted(_ player:JWPlayer) {
|
|
1356
|
+
self.onPlaylistComplete?([:])
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
func jwplayer(_ player:JWPlayer, usesMediaType type:JWMediaType) {
|
|
1360
|
+
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
func jwplayer(_ player: JWPlayer, playbackRateChangedTo rate: Double, at time: TimeInterval) {
|
|
1364
|
+
self.onRateChanged?(["rate": rate, "at": time])
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
func jwplayerHasSeeked(_ player:JWPlayer) {
|
|
1368
|
+
self.onSeeked?([:])
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
func jwplayer(_ player: JWPlayer, seekedFrom oldPosition: TimeInterval, to newPosition: TimeInterval) {
|
|
1372
|
+
self.onSeek?(["from": oldPosition, "to": newPosition])
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
func jwplayer(_ player:JWPlayer, updatedCues cues:[JWCue]) {
|
|
1376
|
+
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// MARK: - JWPlayer Ad Delegate
|
|
1380
|
+
|
|
1381
|
+
func jwplayer(_ player:JWPlayer, adEvent event:JWAdEvent) {
|
|
1382
|
+
self.onAdEvent?(["client": event.client, "type": event.type])
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// pragma Mark - Casting methods
|
|
1386
|
+
|
|
1387
|
+
func setUpCastController() {
|
|
1388
|
+
if (playerView != nil) && playerView.player as! Bool && (castController == nil) {
|
|
1389
|
+
castController = JWCastController(player:playerView.player)
|
|
1390
|
+
castController.delegate = self
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
self.scanForDevices()
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
func scanForDevices() {
|
|
1397
|
+
if castController != nil {
|
|
1398
|
+
castController.startDiscovery()
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
func stopScanForDevices() {
|
|
1403
|
+
if castController != nil {
|
|
1404
|
+
castController.stopDiscovery()
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
func presentCastDialog() {
|
|
1409
|
+
GCKCastContext.sharedInstance().presentCastDialog()
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
func startDiscovery() {
|
|
1413
|
+
GCKCastContext.sharedInstance().discoveryManager.startDiscovery()
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
func stopDiscovery() {
|
|
1417
|
+
GCKCastContext.sharedInstance().discoveryManager.stopDiscovery()
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
func discoveryActive() -> Bool {
|
|
1421
|
+
return GCKCastContext.sharedInstance().discoveryManager.discoveryActive
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
func hasDiscoveredDevices() -> Bool {
|
|
1425
|
+
return GCKCastContext.sharedInstance().discoveryManager.hasDiscoveredDevices
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
func discoveryState() -> GCKDiscoveryState {
|
|
1429
|
+
return GCKCastContext.sharedInstance().discoveryManager.discoveryState
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
func setPassiveScan(passive:Bool) {
|
|
1433
|
+
GCKCastContext.sharedInstance().discoveryManager.passiveScan = passive
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
func castState() -> GCKCastState {
|
|
1437
|
+
return GCKCastContext.sharedInstance().castState
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
func deviceCount() -> UInt {
|
|
1441
|
+
return GCKCastContext.sharedInstance().discoveryManager.deviceCount
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
func getAvailableDevices() -> [JWCastingDevice]! {
|
|
1445
|
+
return castController.availableDevices
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
func connectedDevice() -> JWCastingDevice! {
|
|
1449
|
+
return castController.connectedDevice
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
func connectToDevice(device:JWCastingDevice!) {
|
|
1453
|
+
return castController.connectToDevice(device)
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
func cast() {
|
|
1457
|
+
return castController.cast()
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
func stopCasting() {
|
|
1461
|
+
return castController.stopCasting()
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// MARK: - JWPlayer Cast Delegate
|
|
1465
|
+
|
|
1466
|
+
func castController(_ controller: JWCastController, castingBeganWithDevice device: JWCastingDevice) {
|
|
1467
|
+
self.onCasting?([:])
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
func castController(_ controller:JWCastController, castingEndedWithError error: Error?) {
|
|
1471
|
+
self.onCastingEnded?(["error": error as Any])
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
func castController(_ controller:JWCastController, castingFailedWithError error: Error) {
|
|
1475
|
+
self.onCastingFailed?(["error": error as Any])
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
func castController(_ controller:JWCastController, connectedTo device: JWCastingDevice) {
|
|
1479
|
+
let dict:NSMutableDictionary! = NSMutableDictionary()
|
|
1480
|
+
|
|
1481
|
+
dict.setObject(device.name, forKey:"name" as NSCopying)
|
|
1482
|
+
dict.setObject(device.identifier, forKey:"identifier" as NSCopying)
|
|
1483
|
+
|
|
1484
|
+
do {
|
|
1485
|
+
let data:Data! = try JSONSerialization.data(withJSONObject: dict as Any, options:.prettyPrinted)
|
|
1486
|
+
|
|
1487
|
+
self.onConnectedToCastingDevice?(["device": String(data:data as Data, encoding:String.Encoding.utf8) as Any])
|
|
1488
|
+
} catch {
|
|
1489
|
+
print("Error converting dictionary to JSON data: \(error)")
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
func castController(_ controller:JWCastController, connectionFailedWithError error: Error) {
|
|
1494
|
+
self.onConnectionFailed?(["error": error as Any])
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
func castController(_ controller: JWCastController, connectionRecoveredWithDevice device:JWCastingDevice) {
|
|
1498
|
+
self.onConnectionRecovered?([:])
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
func castController(_ controller: JWCastController, connectionSuspendedWithDevice device: JWCastingDevice) {
|
|
1502
|
+
self.onConnectionTemporarilySuspended?([:])
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
func castController(_ controller: JWCastController, devicesAvailable devices:[JWCastingDevice]) {
|
|
1506
|
+
self.availableDevices = devices
|
|
1507
|
+
|
|
1508
|
+
var devicesInfo: [[String: Any]] = []
|
|
1509
|
+
for device in devices {
|
|
1510
|
+
var dict: [String: Any] = [:]
|
|
1511
|
+
|
|
1512
|
+
dict["name"] = device.name
|
|
1513
|
+
dict["identifier"] = device.identifier
|
|
1514
|
+
|
|
1515
|
+
devicesInfo.append(dict)
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
do {
|
|
1519
|
+
let data:Data! = try JSONSerialization.data(withJSONObject: devicesInfo as Any, options:.prettyPrinted)
|
|
1520
|
+
|
|
1521
|
+
self.onCastingDevicesAvailable?(["devices": String(data:data as Data, encoding:String.Encoding.utf8) as Any])
|
|
1522
|
+
} catch {
|
|
1523
|
+
print("Error converting dictionary to JSON data: \(error)")
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
func castController(_ controller: JWCastController, disconnectedWithError error: (Error)?) {
|
|
1528
|
+
self.onDisconnectedFromCastingDevice?(["error": error as Any])
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// MARK: - JWPlayer AV Delegate
|
|
1532
|
+
|
|
1533
|
+
func jwplayer(_ player:JWPlayer, audioTracksUpdated levels:[JWMediaSelectionOption]) {
|
|
1534
|
+
self.onAudioTracks?([:])
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
func jwplayer(_ player:JWPlayer, audioTrackChanged currentLevel:Int) {
|
|
1538
|
+
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
func jwplayer(_ player: JWPlayer, captionPresented caption: [String], at time: JWTimeData) {
|
|
1542
|
+
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
func jwplayer(_ player:JWPlayer, captionTrackChanged index:Int) {
|
|
1546
|
+
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
func jwplayer(_ player: JWPlayer, visualQualityChanged currentVisualQuality: JWVisualQuality) {
|
|
1550
|
+
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
func jwplayer(_ player:JWPlayer, qualityLevelChanged currentLevel:Int) {
|
|
1554
|
+
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
func jwplayer(_ player:JWPlayer, qualityLevelsUpdated levels:[JWVideoSource]) {
|
|
1558
|
+
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
func jwplayer(_ player:JWPlayer, updatedCaptionList options:[JWMediaSelectionOption]) {
|
|
1562
|
+
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// MARK: - JWPlayer audio session && interruption handling
|
|
1566
|
+
|
|
1567
|
+
func initAudioSession(category:String?, categoryOptions:[String]?, mode:String?) {
|
|
1568
|
+
self.setObservers()
|
|
1569
|
+
|
|
1570
|
+
var somethingChanged:Bool = false
|
|
1571
|
+
|
|
1572
|
+
if !(category == audioCategory) || (categoryOptions != nil && !categoryOptions!.elementsEqual(audioCategoryOptions)) {
|
|
1573
|
+
somethingChanged = true
|
|
1574
|
+
audioCategory = category
|
|
1575
|
+
audioCategoryOptions = categoryOptions
|
|
1576
|
+
self.setCategory(categoryName: category, categoryOptions:categoryOptions)
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
if !(mode == audioMode) {
|
|
1580
|
+
somethingChanged = true
|
|
1581
|
+
audioMode = mode
|
|
1582
|
+
self.setMode(modeName: mode)
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
if somethingChanged {
|
|
1586
|
+
do {
|
|
1587
|
+
try audioSession.setActive(true)
|
|
1588
|
+
print("setActive - success")
|
|
1589
|
+
} catch {
|
|
1590
|
+
print("setActive - error: @%@", error)
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
func deinitAudioSession() {
|
|
1596
|
+
do {
|
|
1597
|
+
try audioSession?.setActive(false, options: .notifyOthersOnDeactivation)
|
|
1598
|
+
print("setUnactive - success")
|
|
1599
|
+
} catch {
|
|
1600
|
+
print("setUnactive - error: @%@", error)
|
|
1601
|
+
}
|
|
1602
|
+
audioSession = nil
|
|
1603
|
+
MPNowPlayingInfoCenter.default().nowPlayingInfo = [:]
|
|
1604
|
+
UIApplication.shared.endReceivingRemoteControlEvents()
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
func setObservers() {
|
|
1608
|
+
if audioSession == nil {
|
|
1609
|
+
audioSession = AVAudioSession.sharedInstance()
|
|
1610
|
+
|
|
1611
|
+
NotificationCenter.default.addObserver(self,
|
|
1612
|
+
selector:#selector(handleMediaServicesReset),
|
|
1613
|
+
name:AVAudioSession.mediaServicesWereResetNotification,
|
|
1614
|
+
object:audioSession)
|
|
1615
|
+
|
|
1616
|
+
NotificationCenter.default.addObserver(self,
|
|
1617
|
+
selector: #selector(audioSessionInterrupted(_:)),
|
|
1618
|
+
name: AVAudioSession.interruptionNotification,
|
|
1619
|
+
object: audioSession)
|
|
1620
|
+
|
|
1621
|
+
NotificationCenter.default.addObserver(self,
|
|
1622
|
+
selector:#selector(applicationWillResignActive(_:)),
|
|
1623
|
+
name:UIApplication.willResignActiveNotification, object:nil)
|
|
1624
|
+
|
|
1625
|
+
NotificationCenter.default.addObserver(self,
|
|
1626
|
+
selector:#selector(applicationDidEnterBackground(_:)),
|
|
1627
|
+
name:UIApplication.didEnterBackgroundNotification,
|
|
1628
|
+
object:nil)
|
|
1629
|
+
|
|
1630
|
+
NotificationCenter.default.addObserver(self,
|
|
1631
|
+
selector:#selector(applicationWillEnterForeground(_:)),
|
|
1632
|
+
name:UIApplication.willEnterForegroundNotification, object:nil)
|
|
1633
|
+
|
|
1634
|
+
NotificationCenter.default.addObserver(self,
|
|
1635
|
+
selector: #selector(audioRouteChanged(_:)),
|
|
1636
|
+
name: AVAudioSession.routeChangeNotification,
|
|
1637
|
+
object: nil)
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
func setCategory(categoryName:String!, categoryOptions:[String]!) {
|
|
1642
|
+
if (audioSession == nil) {
|
|
1643
|
+
audioSession = AVAudioSession.sharedInstance()
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
var category:AVAudioSession.Category! = nil
|
|
1647
|
+
if categoryName.isEqual("Ambient") {
|
|
1648
|
+
category = .ambient
|
|
1649
|
+
} else if categoryName.isEqual("SoloAmbient") {
|
|
1650
|
+
category = .soloAmbient
|
|
1651
|
+
} else if categoryName.isEqual("Playback") {
|
|
1652
|
+
category = .playback
|
|
1653
|
+
} else if categoryName.isEqual("Record") {
|
|
1654
|
+
category = .record
|
|
1655
|
+
} else if categoryName.isEqual("PlayAndRecord") {
|
|
1656
|
+
category = .playAndRecord
|
|
1657
|
+
} else if categoryName.isEqual("MultiRoute") {
|
|
1658
|
+
category = .multiRoute
|
|
1659
|
+
} else {
|
|
1660
|
+
category = .playback
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
var options: AVAudioSession.CategoryOptions = []
|
|
1664
|
+
if categoryOptions.contains("MixWithOthers") {
|
|
1665
|
+
options.insert(.mixWithOthers)
|
|
1666
|
+
}
|
|
1667
|
+
if categoryOptions.contains("DuckOthers") {
|
|
1668
|
+
options.insert(.duckOthers)
|
|
1669
|
+
}
|
|
1670
|
+
if categoryOptions.contains("AllowBluetooth") {
|
|
1671
|
+
options.insert(.allowBluetooth)
|
|
1672
|
+
}
|
|
1673
|
+
if categoryOptions.contains("InterruptSpokenAudioAndMix") {
|
|
1674
|
+
options.insert(.interruptSpokenAudioAndMixWithOthers)
|
|
1675
|
+
}
|
|
1676
|
+
if categoryOptions.contains("AllowBluetoothA2DP") {
|
|
1677
|
+
options.insert(.allowBluetoothA2DP)
|
|
1678
|
+
}
|
|
1679
|
+
if categoryOptions.contains("AllowAirPlay") {
|
|
1680
|
+
options.insert(.allowAirPlay)
|
|
1681
|
+
}
|
|
1682
|
+
if categoryOptions.contains("OverrideMutedMicrophone") {
|
|
1683
|
+
if #available(iOS 14.5, *) {
|
|
1684
|
+
options.insert(.overrideMutedMicrophoneInterruption)
|
|
1685
|
+
} else {
|
|
1686
|
+
// Handle the case for earlier versions if needed
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
do {
|
|
1691
|
+
try audioSession.setCategory(category, options: options)
|
|
1692
|
+
print("setCategory - success")
|
|
1693
|
+
} catch {
|
|
1694
|
+
print("setCategory - error: @%@", error)
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
func setMode(modeName:String!) {
|
|
1699
|
+
if (audioSession == nil) {
|
|
1700
|
+
audioSession = AVAudioSession.sharedInstance()
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
var mode:AVAudioSession.Mode! = nil
|
|
1704
|
+
|
|
1705
|
+
if modeName.isEqual("Default") {
|
|
1706
|
+
mode = .default
|
|
1707
|
+
} else if modeName.isEqual("VoiceChat") {
|
|
1708
|
+
mode = .voiceChat
|
|
1709
|
+
} else if modeName.isEqual("VideoChat") {
|
|
1710
|
+
mode = .videoChat
|
|
1711
|
+
} else if modeName.isEqual("GameChat") {
|
|
1712
|
+
mode = .gameChat
|
|
1713
|
+
} else if modeName.isEqual("VideoRecording") {
|
|
1714
|
+
mode = .videoRecording
|
|
1715
|
+
} else if modeName.isEqual("Measurement") {
|
|
1716
|
+
mode = .measurement
|
|
1717
|
+
} else if modeName.isEqual("MoviePlayback") {
|
|
1718
|
+
mode = .moviePlayback
|
|
1719
|
+
} else if modeName.isEqual("SpokenAudio") {
|
|
1720
|
+
mode = .spokenAudio
|
|
1721
|
+
} else if modeName.isEqual("VoicePrompt") {
|
|
1722
|
+
if #available(iOS 12.0, *) {
|
|
1723
|
+
mode = .voicePrompt
|
|
1724
|
+
} else {
|
|
1725
|
+
// Fallback on earlier versions
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
if (mode != nil) {
|
|
1730
|
+
do {
|
|
1731
|
+
try audioSession.setMode(mode)
|
|
1732
|
+
print("setMode - success")
|
|
1733
|
+
} catch {
|
|
1734
|
+
print("setMode - error: @%@", error)
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
@objc func audioSessionInterrupted(_ notification: Notification) {
|
|
1740
|
+
guard let userInfo = notification.userInfo,
|
|
1741
|
+
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
1742
|
+
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
|
1743
|
+
return
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
switch type {
|
|
1747
|
+
case .began:
|
|
1748
|
+
DispatchQueue.main.async { [self] in
|
|
1749
|
+
wasInterrupted = true
|
|
1750
|
+
|
|
1751
|
+
if (playerView != nil) {
|
|
1752
|
+
playerView.player.pause()
|
|
1753
|
+
} else if (playerViewController != nil) {
|
|
1754
|
+
playerViewController.player.pause()
|
|
1755
|
+
}
|
|
1756
|
+
print("handleInterruption :- Pause")
|
|
1757
|
+
}
|
|
1758
|
+
case .ended:
|
|
1759
|
+
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
|
1760
|
+
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
|
1761
|
+
if options.contains(.shouldResume) && !userPaused && backgroundAudioEnabled {
|
|
1762
|
+
// Interruption ended. Playback should resume.
|
|
1763
|
+
DispatchQueue.main.async { [self] in
|
|
1764
|
+
if (playerView != nil) {
|
|
1765
|
+
playerView.player.play()
|
|
1766
|
+
} else if (playerViewController != nil) {
|
|
1767
|
+
playerViewController.player.play()
|
|
1768
|
+
}
|
|
1769
|
+
print("handleInterruption :- Play")
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
// Interruption ended. Playback should not resume.
|
|
1774
|
+
DispatchQueue.main.async { [self] in
|
|
1775
|
+
wasInterrupted = true
|
|
1776
|
+
|
|
1777
|
+
if (playerView != nil) {
|
|
1778
|
+
playerView.player.pause()
|
|
1779
|
+
} else if (playerViewController != nil) {
|
|
1780
|
+
playerViewController.player.pause()
|
|
1781
|
+
}
|
|
1782
|
+
print("handleInterruption :- Pause")
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
@unknown default:
|
|
1786
|
+
break
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// Service reset
|
|
1791
|
+
@objc func handleMediaServicesReset() {
|
|
1792
|
+
// • Handle this notification by fully reconfiguring audio
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// Inactive
|
|
1796
|
+
// Hack for ios 14 stopping audio when going to background
|
|
1797
|
+
@objc func applicationWillResignActive(_ notification: Notification) {
|
|
1798
|
+
if !userPaused && backgroundAudioEnabled {
|
|
1799
|
+
if (playerView != nil) && playerView.player.getState() == .playing {
|
|
1800
|
+
playerView.player.play()
|
|
1801
|
+
} else if (playerViewController != nil) && playerViewController.player.getState() == .playing {
|
|
1802
|
+
playerViewController.player.play()
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// Background
|
|
1808
|
+
@objc func applicationDidEnterBackground(_ notification: Notification) {
|
|
1809
|
+
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// Active
|
|
1813
|
+
@objc func applicationWillEnterForeground(_ notification: Notification) {
|
|
1814
|
+
if !userPaused && backgroundAudioEnabled {
|
|
1815
|
+
if (playerView != nil) && playerView.player.getState() == .playing {
|
|
1816
|
+
playerView.player.play()
|
|
1817
|
+
} else if (playerViewController != nil) && playerViewController.player.getState() == .playing {
|
|
1818
|
+
playerViewController.player.play()
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// Route change
|
|
1824
|
+
@objc func audioRouteChanged(_ notification: Notification) {
|
|
1825
|
+
guard let userInfo = notification.userInfo else { return }
|
|
1826
|
+
guard let reason = userInfo[AVAudioSessionRouteChangeReasonKey] as? Int else { return }
|
|
1827
|
+
|
|
1828
|
+
if reason == AVAudioSession.RouteChangeReason.oldDeviceUnavailable.hashValue {
|
|
1829
|
+
if (playerView != nil) {
|
|
1830
|
+
playerView.player.pause()
|
|
1831
|
+
} else if (playerViewController != nil) {
|
|
1832
|
+
playerViewController.player.pause()
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
}
|