@capgo/capacitor-video-player 8.1.22 → 8.1.24
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/README.md +62 -29
- package/android/src/main/AndroidManifest.xml +6 -2
- package/android/src/main/java/com/capgo/videoplayer/FullscreenExoPlayerFragment.java +53 -6
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayer.java +2 -0
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayerCastOptionsProvider.java +26 -0
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +83 -1
- package/dist/docs.json +46 -1
- package/dist/esm/definitions.d.ts +33 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web-utils/videoplayer.d.ts +4 -0
- package/dist/esm/web-utils/videoplayer.js +29 -0
- package/dist/esm/web-utils/videoplayer.js.map +1 -1
- package/dist/esm/web.d.ts +2 -0
- package/dist/esm/web.js +36 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +65 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +65 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/VideoPlayerPlugin/FullscreenVideoPlayer.swift +227 -18
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerCastController.swift +8 -2
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +69 -3
- package/package.json +1 -1
|
@@ -14,10 +14,14 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
14
14
|
private var pipEnabled: Bool
|
|
15
15
|
private var showControls: Bool
|
|
16
16
|
private var chromecast: Bool
|
|
17
|
+
private var chromecastUrl: String?
|
|
17
18
|
private var title: String?
|
|
18
19
|
private var smallTitle: String?
|
|
19
20
|
private var artwork: String?
|
|
20
21
|
private var rate: Float
|
|
22
|
+
private var audioCategory: String?
|
|
23
|
+
private var didActivateAudioSession: Bool = false
|
|
24
|
+
private var didEmitExit: Bool = false
|
|
21
25
|
private var timeObserver: Any?
|
|
22
26
|
private var onPlay: (() -> Void)?
|
|
23
27
|
private var onPause: (() -> Void)?
|
|
@@ -26,6 +30,8 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
26
30
|
private var onExit: ((Double) -> Void)?
|
|
27
31
|
private var fairplayCertificateUrl: String?
|
|
28
32
|
private var fairplayContentKeySpcUrl: String?
|
|
33
|
+
private var fairplayAssetId: String?
|
|
34
|
+
private var widevineLicenseUrl: String?
|
|
29
35
|
private var contentKeySession: AVContentKeySession?
|
|
30
36
|
private var castController: VideoPlayerCastController?
|
|
31
37
|
private weak var presentingViewController: UIViewController?
|
|
@@ -42,13 +48,17 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
42
48
|
pipEnabled: Bool,
|
|
43
49
|
showControls: Bool,
|
|
44
50
|
chromecast: Bool,
|
|
51
|
+
chromecastUrl: String? = nil,
|
|
45
52
|
title: String? = nil,
|
|
46
53
|
smallTitle: String? = nil,
|
|
47
54
|
artwork: String? = nil,
|
|
48
55
|
subtitleUrl: String? = nil,
|
|
49
56
|
subtitleLanguage: String? = nil,
|
|
50
57
|
fairplayCertificateUrl: String? = nil,
|
|
51
|
-
fairplayContentKeySpcUrl: String? = nil
|
|
58
|
+
fairplayContentKeySpcUrl: String? = nil,
|
|
59
|
+
fairplayAssetId: String? = nil,
|
|
60
|
+
widevineLicenseUrl: String? = nil,
|
|
61
|
+
audioCategory: String? = nil
|
|
52
62
|
) {
|
|
53
63
|
self.playerId = playerId
|
|
54
64
|
self.videoUrl = url
|
|
@@ -58,6 +68,7 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
58
68
|
self.pipEnabled = pipEnabled
|
|
59
69
|
self.showControls = showControls
|
|
60
70
|
self.chromecast = chromecast
|
|
71
|
+
self.chromecastUrl = chromecastUrl
|
|
61
72
|
self.title = title
|
|
62
73
|
self.smallTitle = smallTitle
|
|
63
74
|
self.artwork = artwork
|
|
@@ -65,6 +76,9 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
65
76
|
self.subtitleLanguage = subtitleLanguage
|
|
66
77
|
self.fairplayCertificateUrl = fairplayCertificateUrl
|
|
67
78
|
self.fairplayContentKeySpcUrl = fairplayContentKeySpcUrl
|
|
79
|
+
self.fairplayAssetId = fairplayAssetId
|
|
80
|
+
self.widevineLicenseUrl = widevineLicenseUrl
|
|
81
|
+
self.audioCategory = audioCategory
|
|
68
82
|
super.init()
|
|
69
83
|
}
|
|
70
84
|
|
|
@@ -74,6 +88,8 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
74
88
|
return
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
configureAudioSession()
|
|
92
|
+
|
|
77
93
|
guard let subtitleUrlString = subtitleUrl,
|
|
78
94
|
!subtitleUrlString.isEmpty,
|
|
79
95
|
let subtitleURL = URL(string: subtitleUrlString) else {
|
|
@@ -113,7 +129,8 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
113
129
|
hlsResourceLoader = result.resourceLoader
|
|
114
130
|
let asset = result.asset
|
|
115
131
|
|
|
116
|
-
if let certUrl = fairplayCertificateUrl, !certUrl.isEmpty
|
|
132
|
+
if let certUrl = fairplayCertificateUrl, !certUrl.isEmpty,
|
|
133
|
+
let spcUrl = fairplayContentKeySpcUrl, !spcUrl.isEmpty {
|
|
117
134
|
let session = AVContentKeySession(keySystem: .fairPlayStreaming)
|
|
118
135
|
session.setDelegate(self, queue: DispatchQueue.global(qos: .default))
|
|
119
136
|
session.addContentKeyRecipient(asset)
|
|
@@ -132,9 +149,7 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
132
149
|
playerViewController?.player = player
|
|
133
150
|
playerViewController?.showsPlaybackControls = showControls
|
|
134
151
|
playerViewController?.allowsPictureInPicturePlayback = pipEnabled
|
|
135
|
-
|
|
136
|
-
playerViewController?.delegate = self
|
|
137
|
-
}
|
|
152
|
+
playerViewController?.delegate = self
|
|
138
153
|
|
|
139
154
|
setupChromecast()
|
|
140
155
|
setupObservers()
|
|
@@ -147,11 +162,19 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
147
162
|
return
|
|
148
163
|
}
|
|
149
164
|
|
|
165
|
+
let castVideoUrl: String
|
|
166
|
+
if let chromecastUrl = chromecastUrl, !chromecastUrl.isEmpty {
|
|
167
|
+
castVideoUrl = chromecastUrl
|
|
168
|
+
} else {
|
|
169
|
+
castVideoUrl = videoUrl
|
|
170
|
+
}
|
|
171
|
+
|
|
150
172
|
castController = VideoPlayerCastController(
|
|
151
|
-
videoUrl:
|
|
173
|
+
videoUrl: castVideoUrl,
|
|
152
174
|
title: title,
|
|
153
175
|
smallTitle: smallTitle,
|
|
154
|
-
artwork: artwork
|
|
176
|
+
artwork: artwork,
|
|
177
|
+
widevineLicenseUrl: widevineLicenseUrl
|
|
155
178
|
)
|
|
156
179
|
castController?.setOnPlay { [weak self] in
|
|
157
180
|
self?.onPlay?()
|
|
@@ -235,23 +258,54 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
235
258
|
|
|
236
259
|
presentingViewController = viewController
|
|
237
260
|
viewController.present(playerVC, animated: true) {
|
|
261
|
+
playerVC.presentationController?.delegate = self
|
|
238
262
|
self.play()
|
|
239
263
|
completion()
|
|
240
264
|
}
|
|
241
265
|
}
|
|
242
266
|
|
|
267
|
+
func show(on viewController: UIViewController, completion: @escaping () -> Void) {
|
|
268
|
+
guard let playerVC = playerViewController else {
|
|
269
|
+
completion()
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
if playerVC.presentingViewController != nil {
|
|
273
|
+
completion()
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
presentingViewController = viewController
|
|
277
|
+
viewController.present(playerVC, animated: true) {
|
|
278
|
+
playerVC.presentationController?.delegate = self
|
|
279
|
+
completion()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
func hide(completion: @escaping () -> Void) {
|
|
284
|
+
guard let playerVC = playerViewController else {
|
|
285
|
+
completion()
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if playerVC.presentingViewController == nil {
|
|
289
|
+
completion()
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
playerVC.dismiss(animated: true) {
|
|
293
|
+
completion()
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
243
297
|
func dismiss() {
|
|
244
298
|
let currentTime = getCurrentTime()
|
|
245
|
-
castController?.detach(stopRemoteMedia: true)
|
|
246
299
|
playerViewController?.dismiss(animated: true) { [weak self] in
|
|
247
|
-
self?.
|
|
248
|
-
self?.onExit?(currentTime)
|
|
300
|
+
self?.emitExitIfNeeded(currentTime: currentTime)
|
|
249
301
|
}
|
|
250
302
|
}
|
|
251
303
|
|
|
252
|
-
private func cleanup() {
|
|
253
|
-
castController?.detach(stopRemoteMedia:
|
|
304
|
+
private func cleanup(stopRemoteMedia: Bool = false) {
|
|
305
|
+
castController?.detach(stopRemoteMedia: stopRemoteMedia)
|
|
254
306
|
castController = nil
|
|
307
|
+
contentKeySession?.setDelegate(nil, queue: nil)
|
|
308
|
+
contentKeySession = nil
|
|
255
309
|
hlsResourceLoader = nil
|
|
256
310
|
if let observer = timeObserver {
|
|
257
311
|
player?.removeObserver(self, forKeyPath: "rate")
|
|
@@ -264,6 +318,58 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
264
318
|
player = nil
|
|
265
319
|
playerItem = nil
|
|
266
320
|
playerViewController = nil
|
|
321
|
+
deactivateAudioSessionIfNeeded()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private func configureAudioSession() {
|
|
325
|
+
guard let audioCategory else { return }
|
|
326
|
+
|
|
327
|
+
let session = AVAudioSession.sharedInstance()
|
|
328
|
+
do {
|
|
329
|
+
switch audioCategory {
|
|
330
|
+
case "ambient":
|
|
331
|
+
try session.setCategory(.ambient, mode: .default, options: [.mixWithOthers])
|
|
332
|
+
case "playback":
|
|
333
|
+
try session.setCategory(.playback, mode: .default, options: [.mixWithOthers])
|
|
334
|
+
case "moviePlayback":
|
|
335
|
+
if #available(iOS 13.0, *) {
|
|
336
|
+
try session.setCategory(
|
|
337
|
+
.playback,
|
|
338
|
+
mode: .moviePlayback,
|
|
339
|
+
policy: .longFormVideo,
|
|
340
|
+
options: [.mixWithOthers]
|
|
341
|
+
)
|
|
342
|
+
} else {
|
|
343
|
+
try session.setCategory(.playback, mode: .moviePlayback, options: [.mixWithOthers])
|
|
344
|
+
}
|
|
345
|
+
default:
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try session.setActive(true)
|
|
350
|
+
didActivateAudioSession = true
|
|
351
|
+
} catch {
|
|
352
|
+
print("Error configuring AVAudioSession: \(error)")
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private func deactivateAudioSessionIfNeeded() {
|
|
357
|
+
guard didActivateAudioSession else { return }
|
|
358
|
+
do {
|
|
359
|
+
try AVAudioSession.sharedInstance().setActive(false, options: [.notifyOthersOnDeactivation])
|
|
360
|
+
didActivateAudioSession = false
|
|
361
|
+
} catch {
|
|
362
|
+
print("Error deactivating AVAudioSession: \(error)")
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private func emitExitIfNeeded(currentTime: Double? = nil) {
|
|
367
|
+
guard !didEmitExit else { return }
|
|
368
|
+
didEmitExit = true
|
|
369
|
+
|
|
370
|
+
let time = currentTime ?? getCurrentTime()
|
|
371
|
+
cleanup(stopRemoteMedia: true)
|
|
372
|
+
onExit?(time)
|
|
267
373
|
}
|
|
268
374
|
|
|
269
375
|
// MARK: - Playback Control
|
|
@@ -396,6 +502,19 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
396
502
|
// MARK: - AVPlayerViewControllerDelegate (Picture in Picture)
|
|
397
503
|
|
|
398
504
|
extension FullscreenVideoPlayer: AVPlayerViewControllerDelegate {
|
|
505
|
+
func playerViewControllerWillEndFullScreenPresentation(
|
|
506
|
+
_ playerViewController: AVPlayerViewController,
|
|
507
|
+
withAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
|
|
508
|
+
) {
|
|
509
|
+
coordinator.animate(alongsideTransition: nil) { [weak self] _ in
|
|
510
|
+
self?.emitExitIfNeeded()
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
func playerViewControllerDidEndFullScreenPresentation(_ playerViewController: AVPlayerViewController) {
|
|
515
|
+
emitExitIfNeeded()
|
|
516
|
+
}
|
|
517
|
+
|
|
399
518
|
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(
|
|
400
519
|
_ playerViewController: AVPlayerViewController
|
|
401
520
|
) -> Bool {
|
|
@@ -423,6 +542,14 @@ extension FullscreenVideoPlayer: AVPlayerViewControllerDelegate {
|
|
|
423
542
|
}
|
|
424
543
|
}
|
|
425
544
|
|
|
545
|
+
// MARK: - UIAdaptivePresentationControllerDelegate
|
|
546
|
+
|
|
547
|
+
extension FullscreenVideoPlayer: UIAdaptivePresentationControllerDelegate {
|
|
548
|
+
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
549
|
+
emitExitIfNeeded()
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
426
553
|
// MARK: - AVContentKeySessionDelegate (FairPlay DRM)
|
|
427
554
|
|
|
428
555
|
extension FullscreenVideoPlayer: AVContentKeySessionDelegate {
|
|
@@ -434,6 +561,61 @@ extension FullscreenVideoPlayer: AVContentKeySessionDelegate {
|
|
|
434
561
|
handleFairPlayKeyRequest(keyRequest)
|
|
435
562
|
}
|
|
436
563
|
|
|
564
|
+
private func fairPlayContentIdentifierData(from identifier: Any?) -> Data? {
|
|
565
|
+
if let data = identifier as? Data {
|
|
566
|
+
return data.isEmpty ? nil : data
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
let identifierString: String?
|
|
570
|
+
if let url = identifier as? URL {
|
|
571
|
+
identifierString = url.absoluteString
|
|
572
|
+
} else if let string = identifier as? String {
|
|
573
|
+
identifierString = string
|
|
574
|
+
} else {
|
|
575
|
+
identifierString = nil
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
guard var string = identifierString?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
579
|
+
!string.isEmpty else {
|
|
580
|
+
return nil
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if string.hasPrefix("skd://") {
|
|
584
|
+
string.removeFirst("skd://".count)
|
|
585
|
+
} else if string.hasPrefix("skd:") {
|
|
586
|
+
string.removeFirst("skd:".count)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
while string.hasPrefix("/") {
|
|
590
|
+
string.removeFirst()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return string.data(using: .utf8)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private func normalizeFairPlayCkcData(_ data: Data) -> Data {
|
|
597
|
+
if let object = try? JSONSerialization.jsonObject(with: data),
|
|
598
|
+
let dict = object as? [String: Any] {
|
|
599
|
+
let candidateKeys = ["ckc", "CKC", "license", "License", "data"]
|
|
600
|
+
for key in candidateKeys {
|
|
601
|
+
if let value = dict[key] as? String,
|
|
602
|
+
let decoded = Data(base64Encoded: value) {
|
|
603
|
+
return decoded
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if let string = String(data: data, encoding: .utf8) {
|
|
609
|
+
let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
610
|
+
.trimmingCharacters(in: CharacterSet(charactersIn: "\"'"))
|
|
611
|
+
if let decoded = Data(base64Encoded: trimmed) {
|
|
612
|
+
return decoded
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return data
|
|
617
|
+
}
|
|
618
|
+
|
|
437
619
|
private func handleFairPlayKeyRequest(_ keyRequest: AVContentKeyRequest) {
|
|
438
620
|
guard let certUrlString = fairplayCertificateUrl,
|
|
439
621
|
let certUrl = URL(string: certUrlString),
|
|
@@ -447,7 +629,17 @@ extension FullscreenVideoPlayer: AVContentKeySessionDelegate {
|
|
|
447
629
|
}
|
|
448
630
|
|
|
449
631
|
// 1. Fetch the FairPlay certificate
|
|
450
|
-
URLSession.shared.dataTask(with: certUrl) { certData,
|
|
632
|
+
URLSession.shared.dataTask(with: certUrl) { [self] certData, certResponse, certError in
|
|
633
|
+
if let httpResponse = certResponse as? HTTPURLResponse,
|
|
634
|
+
!(200...299).contains(httpResponse.statusCode) {
|
|
635
|
+
keyRequest.processContentKeyResponseError(
|
|
636
|
+
NSError(domain: "VideoPlayer", code: -2, userInfo: [
|
|
637
|
+
NSLocalizedDescriptionKey: "Failed to fetch FairPlay certificate (HTTP \(httpResponse.statusCode))"
|
|
638
|
+
])
|
|
639
|
+
)
|
|
640
|
+
return
|
|
641
|
+
}
|
|
642
|
+
|
|
451
643
|
guard let certData = certData else {
|
|
452
644
|
keyRequest.processContentKeyResponseError(
|
|
453
645
|
certError ?? NSError(domain: "VideoPlayer", code: -2,
|
|
@@ -457,11 +649,17 @@ extension FullscreenVideoPlayer: AVContentKeySessionDelegate {
|
|
|
457
649
|
}
|
|
458
650
|
|
|
459
651
|
// 2. Create SPC (Server Playback Context) using the certificate
|
|
460
|
-
|
|
461
|
-
|
|
652
|
+
let contentIdentifier: Data? = {
|
|
653
|
+
if let assetId = fairplayAssetId?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
654
|
+
!assetId.isEmpty {
|
|
655
|
+
return assetId.data(using: .utf8)
|
|
656
|
+
}
|
|
657
|
+
return fairPlayContentIdentifierData(from: keyRequest.identifier)
|
|
658
|
+
}()
|
|
659
|
+
|
|
462
660
|
keyRequest.makeStreamingContentKeyRequestData(
|
|
463
661
|
forApp: certData,
|
|
464
|
-
contentIdentifier:
|
|
662
|
+
contentIdentifier: contentIdentifier,
|
|
465
663
|
options: [AVContentKeyRequestProtocolVersionsKey: [1]]
|
|
466
664
|
) { spcData, spcError in
|
|
467
665
|
guard let spcData = spcData else {
|
|
@@ -478,7 +676,17 @@ extension FullscreenVideoPlayer: AVContentKeySessionDelegate {
|
|
|
478
676
|
spcRequest.httpBody = spcData
|
|
479
677
|
spcRequest.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
|
480
678
|
|
|
481
|
-
URLSession.shared.dataTask(with: spcRequest) { ckcData,
|
|
679
|
+
URLSession.shared.dataTask(with: spcRequest) { [self] ckcData, ckcResponse, ckcError in
|
|
680
|
+
if let httpResponse = ckcResponse as? HTTPURLResponse,
|
|
681
|
+
!(200...299).contains(httpResponse.statusCode) {
|
|
682
|
+
keyRequest.processContentKeyResponseError(
|
|
683
|
+
NSError(domain: "VideoPlayer", code: -3, userInfo: [
|
|
684
|
+
NSLocalizedDescriptionKey: "Failed to obtain FairPlay CKC (HTTP \(httpResponse.statusCode))"
|
|
685
|
+
])
|
|
686
|
+
)
|
|
687
|
+
return
|
|
688
|
+
}
|
|
689
|
+
|
|
482
690
|
guard let ckcData = ckcData else {
|
|
483
691
|
keyRequest.processContentKeyResponseError(
|
|
484
692
|
ckcError ?? NSError(domain: "VideoPlayer", code: -3,
|
|
@@ -488,7 +696,8 @@ extension FullscreenVideoPlayer: AVContentKeySessionDelegate {
|
|
|
488
696
|
}
|
|
489
697
|
|
|
490
698
|
// 4. Provide the CKC to AVFoundation to decrypt the content
|
|
491
|
-
let
|
|
699
|
+
let normalizedCkcData = normalizeFairPlayCkcData(ckcData)
|
|
700
|
+
let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: normalizedCkcData)
|
|
492
701
|
keyRequest.processContentKeyResponse(keyResponse)
|
|
493
702
|
}.resume()
|
|
494
703
|
}
|
|
@@ -20,6 +20,7 @@ final class VideoPlayerCastController: NSObject {
|
|
|
20
20
|
private let title: String?
|
|
21
21
|
private let smallTitle: String?
|
|
22
22
|
private let artwork: String?
|
|
23
|
+
private let widevineLicenseUrl: String?
|
|
23
24
|
|
|
24
25
|
private weak var player: AVPlayer?
|
|
25
26
|
private weak var playerViewController: AVPlayerViewController?
|
|
@@ -52,11 +53,12 @@ final class VideoPlayerCastController: NSObject {
|
|
|
52
53
|
return GCKCastContext.sharedInstance().sessionManager.currentCastSession?.remoteMediaClient
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
init(videoUrl: String, title: String?, smallTitle: String?, artwork: String?) {
|
|
56
|
+
init(videoUrl: String, title: String?, smallTitle: String?, artwork: String?, widevineLicenseUrl: String?) {
|
|
56
57
|
self.videoUrl = videoUrl
|
|
57
58
|
self.title = title
|
|
58
59
|
self.smallTitle = smallTitle
|
|
59
60
|
self.artwork = artwork
|
|
61
|
+
self.widevineLicenseUrl = widevineLicenseUrl
|
|
60
62
|
super.init()
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -529,6 +531,10 @@ private extension VideoPlayerCastController {
|
|
|
529
531
|
builder.streamType = .buffered
|
|
530
532
|
builder.contentType = contentType(for: url)
|
|
531
533
|
builder.metadata = metadata
|
|
534
|
+
if let widevineLicenseUrl = widevineLicenseUrl,
|
|
535
|
+
!widevineLicenseUrl.isEmpty {
|
|
536
|
+
builder.customData = ["laurl": widevineLicenseUrl]
|
|
537
|
+
}
|
|
532
538
|
if let duration = player?.currentItem?.duration.seconds,
|
|
533
539
|
duration.isFinite,
|
|
534
540
|
duration > 0 {
|
|
@@ -737,7 +743,7 @@ final class VideoPlayerCastController {
|
|
|
737
743
|
return false
|
|
738
744
|
}
|
|
739
745
|
|
|
740
|
-
init(videoUrl: String, title: String?, smallTitle: String?, artwork: String?) {
|
|
746
|
+
init(videoUrl: String, title: String?, smallTitle: String?, artwork: String?, widevineLicenseUrl: String?) {
|
|
741
747
|
_ = videoUrl
|
|
742
748
|
_ = title
|
|
743
749
|
_ = smallTitle
|
|
@@ -8,7 +8,7 @@ import AVKit
|
|
|
8
8
|
*/
|
|
9
9
|
@objc(VideoPlayerPlugin)
|
|
10
10
|
public class VideoPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11
|
-
private let pluginVersion: String = "8.1.
|
|
11
|
+
private let pluginVersion: String = "8.1.24"
|
|
12
12
|
public let identifier = "VideoPlayerPlugin"
|
|
13
13
|
public let jsName = "VideoPlayer"
|
|
14
14
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -29,7 +29,9 @@ public class VideoPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
29
29
|
CAPPluginMethod(name: "stopAllPlayers", returnType: CAPPluginReturnPromise),
|
|
30
30
|
CAPPluginMethod(name: "showController", returnType: CAPPluginReturnPromise),
|
|
31
31
|
CAPPluginMethod(name: "isControllerIsFullyVisible", returnType: CAPPluginReturnPromise),
|
|
32
|
-
CAPPluginMethod(name: "exitPlayer", returnType: CAPPluginReturnPromise)
|
|
32
|
+
CAPPluginMethod(name: "exitPlayer", returnType: CAPPluginReturnPromise),
|
|
33
|
+
CAPPluginMethod(name: "hidePlayer", returnType: CAPPluginReturnPromise),
|
|
34
|
+
CAPPluginMethod(name: "showPlayer", returnType: CAPPluginReturnPromise)
|
|
33
35
|
]
|
|
34
36
|
|
|
35
37
|
private var videoPlayers: [String: FullscreenVideoPlayer] = [:]
|
|
@@ -79,6 +81,8 @@ extension VideoPlayerPlugin {
|
|
|
79
81
|
let title = call.getString("title")
|
|
80
82
|
let smallTitle = call.getString("smallTitle")
|
|
81
83
|
let artwork = call.getString("artwork")
|
|
84
|
+
let chromecastUrl = call.getString("chromecastUrl")
|
|
85
|
+
let audioCategory = call.getString("audioCategory")
|
|
82
86
|
let subtitle = call.getString("subtitle")
|
|
83
87
|
let language = call.getString("language")
|
|
84
88
|
|
|
@@ -87,6 +91,9 @@ extension VideoPlayerPlugin {
|
|
|
87
91
|
let fairplay = drm?["fairplay"] as? [String: Any]
|
|
88
92
|
let fairplayCertificateUrl = fairplay?["certificateUrl"] as? String
|
|
89
93
|
let fairplayContentKeySpcUrl = fairplay?["contentKeySpcUrl"] as? String
|
|
94
|
+
let fairplayAssetId = fairplay?["assetId"] as? String
|
|
95
|
+
let widevine = drm?["widevine"] as? [String: Any]
|
|
96
|
+
let widevineLicenseUrl = widevine?["certificateUrl"] as? String
|
|
90
97
|
|
|
91
98
|
// Create video player
|
|
92
99
|
let player = FullscreenVideoPlayer(
|
|
@@ -98,13 +105,17 @@ extension VideoPlayerPlugin {
|
|
|
98
105
|
pipEnabled: pipEnabled,
|
|
99
106
|
showControls: showControls,
|
|
100
107
|
chromecast: chromecast,
|
|
108
|
+
chromecastUrl: chromecastUrl,
|
|
101
109
|
title: title,
|
|
102
110
|
smallTitle: smallTitle,
|
|
103
111
|
artwork: artwork,
|
|
104
112
|
subtitleUrl: subtitle,
|
|
105
113
|
subtitleLanguage: language,
|
|
106
114
|
fairplayCertificateUrl: fairplayCertificateUrl,
|
|
107
|
-
fairplayContentKeySpcUrl: fairplayContentKeySpcUrl
|
|
115
|
+
fairplayContentKeySpcUrl: fairplayContentKeySpcUrl,
|
|
116
|
+
fairplayAssetId: fairplayAssetId,
|
|
117
|
+
widevineLicenseUrl: widevineLicenseUrl,
|
|
118
|
+
audioCategory: audioCategory
|
|
108
119
|
)
|
|
109
120
|
|
|
110
121
|
// Present player
|
|
@@ -509,3 +520,58 @@ extension VideoPlayerPlugin {
|
|
|
509
520
|
])
|
|
510
521
|
}
|
|
511
522
|
}
|
|
523
|
+
|
|
524
|
+
extension VideoPlayerPlugin {
|
|
525
|
+
@objc func hidePlayer(_ call: CAPPluginCall) {
|
|
526
|
+
guard let playerId = currentPlayerId,
|
|
527
|
+
let player = videoPlayers[playerId] else {
|
|
528
|
+
call.resolve([
|
|
529
|
+
"result": false,
|
|
530
|
+
"method": "hidePlayer",
|
|
531
|
+
"message": "No active player"
|
|
532
|
+
])
|
|
533
|
+
return
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
DispatchQueue.main.async {
|
|
537
|
+
player.hide {
|
|
538
|
+
call.resolve([
|
|
539
|
+
"result": true,
|
|
540
|
+
"method": "hidePlayer",
|
|
541
|
+
"value": true
|
|
542
|
+
])
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
@objc func showPlayer(_ call: CAPPluginCall) {
|
|
548
|
+
guard let playerId = currentPlayerId,
|
|
549
|
+
let player = videoPlayers[playerId] else {
|
|
550
|
+
call.resolve([
|
|
551
|
+
"result": false,
|
|
552
|
+
"method": "showPlayer",
|
|
553
|
+
"message": "No active player"
|
|
554
|
+
])
|
|
555
|
+
return
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
DispatchQueue.main.async {
|
|
559
|
+
guard let viewController = self.bridge?.viewController else {
|
|
560
|
+
call.resolve([
|
|
561
|
+
"result": false,
|
|
562
|
+
"method": "showPlayer",
|
|
563
|
+
"message": "Unable to get view controller"
|
|
564
|
+
])
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
player.show(on: viewController) {
|
|
569
|
+
call.resolve([
|
|
570
|
+
"result": true,
|
|
571
|
+
"method": "showPlayer",
|
|
572
|
+
"value": true
|
|
573
|
+
])
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|