From 2cac72cee7c39737ac467add57ddc87b3833e320 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Wed, 24 Jul 2024 13:51:36 -0700 Subject: [PATCH 01/32] chore: add error detail collection hooks to fairplaysession manager --- .../FairPlay/ErrorDispatcher.swift | 14 ++++++++ .../FairPlay/FairPlaySessionManager.swift | 13 ++++++-- .../GlobalLifecycle/PlayerSDK.swift | 19 +++++++---- .../MuxPlayerSwift/Monitoring/Monitor.swift | 22 ++++++++++++- .../FairPlaySessionManagerTests.swift | 9 +++-- Tests/MuxPlayerSwift/MonitorTests.swift | 33 +++++++++++++++++++ 6 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift diff --git a/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift b/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift new file mode 100644 index 00000000..78d10244 --- /dev/null +++ b/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift @@ -0,0 +1,14 @@ +// +// ErrorDispatcher.swift +// +// + +import Foundation + +protocol ErrorDispatcher { + func dispatchError( + errorCode: String, + errorMessage: String, + playerObjectIdentifier: ObjectIdentifier + ) +} diff --git a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift index ee7c1d7c..942d340d 100644 --- a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift +++ b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift @@ -90,7 +90,8 @@ class DefaultFairPlayStreamingSessionManager< var playbackOptionsByPlaybackID: [String: PlaybackOptions] = [:] let contentKeySession: ContentKeySession - + let errorDispatcher: (any ErrorDispatcher) + #if DEBUG var logger: Logger = Logger( OSLog( @@ -276,6 +277,12 @@ class DefaultFairPlayStreamingSessionManager< requestCompletion(Result.failure( FairPlaySessionError.because(cause: error) )) +// // TODO: Confirm error code +// self.errorDispatcher.dispatchError( +// errorCode: "5001", +// errorMessage: error.localizedDescription, +// playerObjectIdentifier: <#T##ObjectIdentifier#> +// ) return } @@ -349,10 +356,12 @@ class DefaultFairPlayStreamingSessionManager< init( contentKeySession: ContentKeySession, - urlSession: URLSession + urlSession: URLSession, + errorDispatcher: any ErrorDispatcher ) { self.contentKeySession = contentKeySession self.urlSession = urlSession + self.errorDispatcher = errorDispatcher } } diff --git a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift index a893e673..871dfeb3 100644 --- a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift +++ b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift @@ -28,32 +28,39 @@ class PlayerSDK { let fairPlaySessionManager: FairPlayStreamingSessionManager convenience init() { + let monitor = Monitor() + #if targetEnvironment(simulator) self.init( fairPlayStreamingSessionManager: DefaultFairPlayStreamingSessionManager( contentKeySession: AVContentKeySession(keySystem: .clearKey), - urlSession: .shared - ) + urlSession: .shared, + errorDispatcher: monitor + ), + monitor: monitor ) #else let sessionManager = DefaultFairPlayStreamingSessionManager( contentKeySession: AVContentKeySession(keySystem: .fairPlayStreaming), - urlSession: .shared + urlSession: .shared, + errorDispatcher: monitor ) sessionManager.sessionDelegate = ContentKeySessionDelegate( sessionManager: sessionManager ) self.init( - fairPlayStreamingSessionManager: sessionManager + fairPlayStreamingSessionManager: sessionManager, + monitor: monitor ) #endif } init( - fairPlayStreamingSessionManager: FairPlayStreamingSessionManager + fairPlayStreamingSessionManager: FairPlayStreamingSessionManager, + monitor: Monitor ) { - self.monitor = Monitor() self.fairPlaySessionManager = fairPlayStreamingSessionManager + self.monitor = monitor #if DEBUG self.abrLogger = Logger( diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 3671371e..1f3a987e 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -9,7 +9,7 @@ import Foundation import MuxCore import MUXSDKStats -class Monitor { +class Monitor: ErrorDispatcher { struct MonitoredPlayer { var name: String @@ -207,4 +207,24 @@ class Monitor { bindings.removeValue(forKey: objectIdentifier) } + + // MARK: - Error Dispatch + + func dispatchError( + errorCode: String, + errorMessage: String, + playerObjectIdentifier: ObjectIdentifier + ) { + guard let monitoredPlayer = self.bindings[playerObjectIdentifier] else { + return + } + + let playerName = monitoredPlayer.name + + MUXSDKStats.dispatchError( + errorCode, + withMessage: errorMessage, + forPlayer: playerName + ) + } } diff --git a/Tests/MuxPlayerSwift/FairPlay/FairPlaySessionManagerTests.swift b/Tests/MuxPlayerSwift/FairPlay/FairPlaySessionManagerTests.swift index 482be2dc..503e284d 100644 --- a/Tests/MuxPlayerSwift/FairPlay/FairPlaySessionManagerTests.swift +++ b/Tests/MuxPlayerSwift/FairPlay/FairPlaySessionManagerTests.swift @@ -29,7 +29,8 @@ class FairPlaySessionManagerTests : XCTestCase { let defaultFairPlaySessionManager = DefaultFairPlayStreamingSessionManager( // .clearKey is used because .fairPlay requires a physical device contentKeySession: session, - urlSession: mockURLSession + urlSession: mockURLSession, + errorDispatcher: Monitor() ) self.sessionManager = defaultFairPlaySessionManager defaultFairPlaySessionManager.sessionDelegate = ContentKeySessionDelegate( @@ -576,7 +577,8 @@ class FairPlaySessionManagerTests : XCTestCase { ) let defaultFairPlaySessionManager = DefaultFairPlayStreamingSessionManager( contentKeySession: session, - urlSession: mockURLSession + urlSession: mockURLSession, + errorDispatcher: Monitor() ) self.sessionManager = defaultFairPlaySessionManager let sessionDelegate = ContentKeySessionDelegate( @@ -616,7 +618,8 @@ class FairPlaySessionManagerTests : XCTestCase { PlayerSDK.shared = PlayerSDK( - fairPlayStreamingSessionManager: defaultFairPlaySessionManager + fairPlayStreamingSessionManager: defaultFairPlaySessionManager, + monitor: Monitor() ) let _ = AVPlayerItem( diff --git a/Tests/MuxPlayerSwift/MonitorTests.swift b/Tests/MuxPlayerSwift/MonitorTests.swift index be61e436..b9b554d5 100644 --- a/Tests/MuxPlayerSwift/MonitorTests.swift +++ b/Tests/MuxPlayerSwift/MonitorTests.swift @@ -31,6 +31,12 @@ class TestMonitor: Monitor { options: MonitoringOptions, usingDRM: Bool = false ) { + super.setupMonitoring( + playerViewController: playerViewController, + options: options, + usingDRM: usingDRM + ) + monitoringRegistrations.append( (options, usingDRM) ) @@ -41,6 +47,12 @@ class TestMonitor: Monitor { options: MonitoringOptions, usingDRM: Bool = false ) { + super.setupMonitoring( + playerLayer: playerLayer, + options: options, + usingDRM: usingDRM + ) + monitoringRegistrations.append( (options, usingDRM) ) @@ -137,6 +149,13 @@ class MonitorTests: XCTestCase { ) ) + let playerBinding = try XCTUnwrap( + testMonitor.bindings[ObjectIdentifier(playerViewController)] + ) + XCTAssertNotNil( + playerBinding + ) + let registration = try XCTUnwrap( testMonitor.monitoringRegistrations.first ) @@ -158,6 +177,13 @@ class MonitorTests: XCTestCase { ) ) + let playerBinding = try XCTUnwrap( + testMonitor.bindings[ObjectIdentifier(playerLayer)] + ) + XCTAssertNotNil( + playerBinding + ) + let registration = try XCTUnwrap( testMonitor.monitoringRegistrations.first ) @@ -184,6 +210,13 @@ class MonitorTests: XCTestCase { ) ) + let playerBinding = try XCTUnwrap( + testMonitor.bindings[ObjectIdentifier(preexistingPlayerLayer)] + ) + XCTAssertNotNil( + playerBinding + ) + let registration = try XCTUnwrap( testMonitor.monitoringRegistrations.first ) From 462b0e72583942dc059899a2fb5cbe0aae2d51f5 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Mon, 26 Aug 2024 12:26:05 -0700 Subject: [PATCH 02/32] chore: inject PlayerSDK instance when initializing AVPlayerItem to ease unit testing --- .../InternalExtensions/AVPlayerItem+Mux.swift | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift b/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift index ff8c4014..cfe64687 100644 --- a/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift +++ b/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift @@ -28,7 +28,8 @@ internal extension AVPlayerItem { convenience init(playbackID: String) { self.init( playbackID: playbackID, - playbackOptions: PlaybackOptions() + playbackOptions: PlaybackOptions(), + playerSDK: .shared ) } @@ -42,6 +43,18 @@ internal extension AVPlayerItem { convenience init( playbackID: String, playbackOptions: PlaybackOptions + ) { + self.init( + playbackID: playbackID, + playbackOptions: playbackOptions, + playerSDK: .shared + ) + } + + convenience init( + playbackID: String, + playbackOptions: PlaybackOptions, + playerSDK: PlayerSDK ) { // Create a new `AVAsset` that has been prepared // for playback @@ -60,7 +73,7 @@ internal extension AVPlayerItem { asset: asset ) - PlayerSDK.shared.registerPlayerItem( + playerSDK.registerPlayerItem( self, playbackID: playbackID, playbackOptions: playbackOptions From a8ffcce9a66cf7ea0ef0188d9aabc892e8dee463 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Mon, 26 Aug 2024 12:26:56 -0700 Subject: [PATCH 03/32] chore: support multiple observations for one AVPlayer --- .../GlobalLifecycle/PlayerSDK.swift | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift index 871dfeb3..1efe25fb 100644 --- a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift +++ b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift @@ -231,7 +231,7 @@ class PlayerSDK { } class KeyValueObservation { - var observations: [ObjectIdentifier: NSKeyValueObservation] = [:] + var observations: [ObjectIdentifier: Set] = [:] func register( _ player: AVPlayer, @@ -239,18 +239,27 @@ class PlayerSDK { options: NSKeyValueObservingOptions, changeHandler: @escaping (AVPlayer, NSKeyValueObservedChange) -> Void ) { - let observation = player.observe(keyPath, - options: options, - changeHandler: changeHandler + let observation = player.observe( + keyPath, + options: options, + changeHandler: changeHandler ) - observations[ObjectIdentifier(player)] = observation + + if var o = observations[ObjectIdentifier(player)] { + o.insert(observation) + observations[ObjectIdentifier(player)] = o + } else { + observations[ObjectIdentifier(player)] = Set(arrayLiteral: observation) + } } func unregister( _ player: AVPlayer ) { - if let observation = observations[ObjectIdentifier(player)] { - observation.invalidate() + if let o = observations[ObjectIdentifier(player)] { + o.forEach { observation in + observation.invalidate() + } observations.removeValue(forKey: ObjectIdentifier(player)) } } From a320faa875aa54796f3e9de62eb16b355045bf1e Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Mon, 26 Aug 2024 12:27:18 -0700 Subject: [PATCH 04/32] chore: remove unused code (should this be called somewhere?) --- .../GlobalLifecycle/PlayerSDK.swift | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift index 1efe25fb..a1f1a364 100644 --- a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift +++ b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift @@ -265,22 +265,3 @@ class PlayerSDK { } } } - -// MARK extension for observations for DRM -extension PlayerSDK { - func observePlayerForDRM(_ player: AVPlayer) { - keyValueObservation.register( - player, - for: \AVPlayer.currentItem, - options: [.old, .new] - ) { player, change in - if let oldAsset = change.oldValue??.asset as? AVURLAsset { - PlayerSDK.shared.fairPlaySessionManager.removeContentKeyRecipient(oldAsset) - } - } - } - - func stopObservingPlayerForDrm(_ player: AVPlayer) { - keyValueObservation.unregister(player) - } -} From 41c70ba2c4bd19e794d2949ed8b371462f0c658b Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Mon, 26 Aug 2024 12:28:53 -0700 Subject: [PATCH 05/32] player item handle scaffolding --- .../GlobalLifecycle/PlayerSDK.swift | 19 +++++++++++++++++++ .../MuxPlayerSwift/Monitoring/Monitor.swift | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift index a1f1a364..939bd6df 100644 --- a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift +++ b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift @@ -155,6 +155,25 @@ class PlayerSDK { usingDRM: usingDRM ) + if let player = playerLayer.player { + keyValueObservation.register( + player, + for: \.currentItem, + options: [.initial, .new, .old] + ) { player, change in + if let newValue = change.newValue { + if let n = newValue { + self.monitor.handleUpdatedCurrentPlayerItem( + n, + for: player + ) + } else { + // Handle nil case + } + } + } + } + if let player = playerLayer.player, requiresReverseProxying == true { keyValueObservation.register( diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 1f3a987e..1cb1c387 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -208,6 +208,15 @@ class Monitor: ErrorDispatcher { bindings.removeValue(forKey: objectIdentifier) } + // MARK: - Player Item Tracking + + func handleUpdatedCurrentPlayerItem( + _ playerItem: AVPlayerItem, + for player: AVPlayer + ) { + + } + // MARK: - Error Dispatch func dispatchError( From 34a8d585000e3eefe1518f673c21a5b7be0dcef4 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Tue, 27 Aug 2024 16:40:59 -0700 Subject: [PATCH 06/32] chore: handle nil inside monitor --- .../GlobalLifecycle/PlayerSDK.swift | 27 +++++++++++++------ .../MuxPlayerSwift/Monitoring/Monitor.swift | 14 +++++++++- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift index 939bd6df..2adffd1f 100644 --- a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift +++ b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift @@ -162,14 +162,10 @@ class PlayerSDK { options: [.initial, .new, .old] ) { player, change in if let newValue = change.newValue { - if let n = newValue { - self.monitor.handleUpdatedCurrentPlayerItem( - n, - for: player - ) - } else { - // Handle nil case - } + self.monitor.handleUpdatedCurrentPlayerItem( + newValue, + for: player + ) } } } @@ -202,6 +198,21 @@ class PlayerSDK { usingDRM: usingDRM ) + if let player = playerViewController.player { + keyValueObservation.register( + player, + for: \.currentItem, + options: [.initial, .new, .old] + ) { player, change in + if let newValue = change.newValue { + self.monitor.handleUpdatedCurrentPlayerItem( + newValue, + for: player + ) + } + } + } + if let player = playerViewController.player, requiresReverseProxying == true { keyValueObservation.register( diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 1cb1c387..fff3d658 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -211,7 +211,7 @@ class Monitor: ErrorDispatcher { // MARK: - Player Item Tracking func handleUpdatedCurrentPlayerItem( - _ playerItem: AVPlayerItem, + _ playerItem: AVPlayerItem?, for player: AVPlayer ) { @@ -219,6 +219,18 @@ class Monitor: ErrorDispatcher { // MARK: - Error Dispatch + func dispatchApplicationCertificateRequestError( + _ error: FairPlaySessionError + ) { + + } + + func dispatchLicenseRequestError( + _ error: FairPlaySessionError + ) { + + } + func dispatchError( errorCode: String, errorMessage: String, From 179f1310d5dc48bfb89e0feecd6e271d0ebee7a6 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Tue, 27 Aug 2024 16:41:18 -0700 Subject: [PATCH 07/32] better error instrumentation --- .../FairPlay/ErrorDispatcher.swift | 13 +++- .../FairPlay/FairPlaySessionManager.swift | 75 +++++++++++++------ 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift b/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift index 78d10244..1bf193f8 100644 --- a/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift +++ b/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift @@ -6,9 +6,14 @@ import Foundation protocol ErrorDispatcher { - func dispatchError( - errorCode: String, - errorMessage: String, - playerObjectIdentifier: ObjectIdentifier + func dispatchApplicationCertificateRequestError( + _ error: FairPlaySessionError ) + + func dispatchLicenseRequestError( + _ error: FairPlaySessionError + ) + + + } diff --git a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift index 942d340d..e5848567 100644 --- a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift +++ b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift @@ -143,13 +143,17 @@ class DefaultFairPlayStreamingSessionManager< logger.debug( "Invalid FairPlay certificate domain \(rootDomain, privacy: .auto(mask: .hash))" ) + let error = FairPlaySessionError.unexpected( + message: "Invalid certificate domain" + ) requestCompletion( Result.failure( - FairPlaySessionError.unexpected( - message: "Invalid certificate domain" - ) + error ) ) + errorDispatcher.dispatchApplicationCertificateRequestError( + error + ) return } var request = URLRequest(url: url) @@ -188,9 +192,13 @@ class DefaultFairPlayStreamingSessionManager< self.logger.debug( "Applicate certificate request failed with error: \(error.localizedDescription)" ) + let error = FairPlaySessionError.because(cause: error) requestCompletion(Result.failure( - FairPlaySessionError.because(cause: error) + error )) + self.errorDispatcher.dispatchApplicationCertificateRequestError( + error + ) return } // error case: I/O finished with non-successful response @@ -198,28 +206,36 @@ class DefaultFairPlayStreamingSessionManager< self.logger.debug( "Applicate certificate request failed with response code: \(String(describing: responseCode))" ) + let error = FairPlaySessionError.httpFailed( + responseStatusCode: responseCode ?? 0 + ) requestCompletion( Result.failure( - FairPlaySessionError.httpFailed( - responseStatusCode: responseCode ?? 0 - ) + error ) ) + self.errorDispatcher.dispatchApplicationCertificateRequestError( + error + ) return } // this edge case (200 with invalid data) is possible from our DRM vendor guard let data = data, data.count > 0 else { + let error = FairPlaySessionError.unexpected( + message: "No cert data with 200 OK respone" + ) self.logger.debug( "Applicate certificate request completed with missing data and response code \(responseCode.debugDescription)" ) requestCompletion( Result.failure( - FairPlaySessionError.unexpected( - message: "No cert data with 200 OK respone" - ) + error ) ) + self.errorDispatcher.dispatchApplicationCertificateRequestError( + error + ) return } @@ -246,13 +262,17 @@ class DefaultFairPlayStreamingSessionManager< drmToken: drmToken, licenseHostSuffix: rootDomain ).url else { + let error = FairPlaySessionError.unexpected( + message: "Invalid FairPlay license domain" + ) requestCompletion( Result.failure( - FairPlaySessionError.unexpected( - message: "Invalid FairPlay license domain" - ) + error ) ) + errorDispatcher.dispatchLicenseRequestError( + error + ) return } @@ -274,15 +294,13 @@ class DefaultFairPlayStreamingSessionManager< self.logger.debug( "URL Session Task Failed: \(error.localizedDescription)" ) + let error = FairPlaySessionError.because(cause: error) requestCompletion(Result.failure( - FairPlaySessionError.because(cause: error) + error )) -// // TODO: Confirm error code -// self.errorDispatcher.dispatchError( -// errorCode: "5001", -// errorMessage: error.localizedDescription, -// playerObjectIdentifier: <#T##ObjectIdentifier#> -// ) + self.errorDispatcher.dispatchLicenseRequestError( + error + ) return } @@ -301,11 +319,16 @@ class DefaultFairPlayStreamingSessionManager< self.logger.debug( "CKC request failed: \(String(describing: responseCode))" ) + let error = FairPlaySessionError.httpFailed( + responseStatusCode: responseCode ?? 0 + ) requestCompletion(Result.failure( - FairPlaySessionError.httpFailed( - responseStatusCode: responseCode ?? 0 - ) + error )) + self.errorDispatcher.dispatchLicenseRequestError( + error + ) + return } // strange edge case: 200 with no response body @@ -314,10 +337,14 @@ class DefaultFairPlayStreamingSessionManager< guard let data = data, data.count > 0 else { + let error = FairPlaySessionError.unexpected(message: "No license data with 200 response") self.logger.debug("No CKC data despite server returning success") requestCompletion(Result.failure( - FairPlaySessionError.unexpected(message: "No license data with 200 response") + error )) + self.errorDispatcher.dispatchLicenseRequestError( + error + ) return } From 901ab8996a6312e0c93cbb3474ba11591967e409 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Tue, 27 Aug 2024 16:57:42 -0700 Subject: [PATCH 08/32] chore: inject playerSDK when preparing existing AVPlayerLayer and AVPlayerViewController --- .../PublicAPI/Extensions/AVPlayerLayer+Mux.swift | 5 +++-- .../PublicAPI/Extensions/AVPlayerViewController+Mux.swift | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift index 04f8437c..34d537dd 100644 --- a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift +++ b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift @@ -225,7 +225,8 @@ extension AVPlayerLayer { internal func prepare( playerItem: AVPlayerItem, playbackOptions: PlaybackOptions = PlaybackOptions(), - monitoringOptions: MonitoringOptions + monitoringOptions: MonitoringOptions, + playerSDK: PlayerSDK = .shared ) { if let player { player.replaceCurrentItem( @@ -245,7 +246,7 @@ extension AVPlayerLayer { usingDRM = false } - PlayerSDK.shared.registerPlayerLayer( + playerSDK.registerPlayerLayer( playerLayer: self, monitoringOptions: monitoringOptions, requiresReverseProxying: playbackOptions.enableSmartCache, diff --git a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift index 26740190..b00dc086 100644 --- a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift +++ b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift @@ -264,7 +264,8 @@ extension AVPlayerViewController { internal func prepare( playerItem: AVPlayerItem, playbackOptions: PlaybackOptions = PlaybackOptions(), - monitoringOptions: MonitoringOptions + monitoringOptions: MonitoringOptions, + playerSDK: PlayerSDK = .shared ) { if let player { player.replaceCurrentItem( @@ -284,7 +285,7 @@ extension AVPlayerViewController { usingDRM = false } - PlayerSDK.shared.registerPlayerViewController( + playerSDK.registerPlayerViewController( playerViewController: self, monitoringOptions: monitoringOptions, requiresReverseProxying: playbackOptions.enableSmartCache, From 530fdf61fcd12929c9f09cc2f94f3b8dfaf16479 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Tue, 27 Aug 2024 16:58:42 -0700 Subject: [PATCH 09/32] chore: thread playbackID through --- .../FairPlay/ErrorDispatcher.swift | 6 +++-- .../FairPlay/FairPlaySessionManager.swift | 24 ++++++++++++------- .../GlobalLifecycle/PlayerSDK.swift | 4 ++++ .../MuxPlayerSwift/Monitoring/Monitor.swift | 12 ++++++---- .../Extensions/AVPlayerLayer+Mux.swift | 14 ++++++++++- .../AVPlayerViewController+Mux.swift | 16 +++++++++++-- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift b/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift index 1bf193f8..fcfa79b8 100644 --- a/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift +++ b/Sources/MuxPlayerSwift/FairPlay/ErrorDispatcher.swift @@ -7,11 +7,13 @@ import Foundation protocol ErrorDispatcher { func dispatchApplicationCertificateRequestError( - _ error: FairPlaySessionError + error: FairPlaySessionError, + playbackID: String ) func dispatchLicenseRequestError( - _ error: FairPlaySessionError + error: FairPlaySessionError, + playbackID: String ) diff --git a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift index e5848567..5e3d0446 100644 --- a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift +++ b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift @@ -152,7 +152,8 @@ class DefaultFairPlayStreamingSessionManager< ) ) errorDispatcher.dispatchApplicationCertificateRequestError( - error + error: error, + playbackID: playbackID ) return } @@ -197,7 +198,8 @@ class DefaultFairPlayStreamingSessionManager< error )) self.errorDispatcher.dispatchApplicationCertificateRequestError( - error + error: error, + playbackID: playbackID ) return } @@ -215,7 +217,8 @@ class DefaultFairPlayStreamingSessionManager< ) ) self.errorDispatcher.dispatchApplicationCertificateRequestError( - error + error: error, + playbackID: playbackID ) return } @@ -234,7 +237,8 @@ class DefaultFairPlayStreamingSessionManager< ) ) self.errorDispatcher.dispatchApplicationCertificateRequestError( - error + error: error, + playbackID: playbackID ) return } @@ -271,7 +275,8 @@ class DefaultFairPlayStreamingSessionManager< ) ) errorDispatcher.dispatchLicenseRequestError( - error + error: error, + playbackID: playbackID ) return } @@ -299,7 +304,8 @@ class DefaultFairPlayStreamingSessionManager< error )) self.errorDispatcher.dispatchLicenseRequestError( - error + error: error, + playbackID: playbackID ) return } @@ -326,7 +332,8 @@ class DefaultFairPlayStreamingSessionManager< error )) self.errorDispatcher.dispatchLicenseRequestError( - error + error: error, + playbackID: playbackID ) return @@ -343,7 +350,8 @@ class DefaultFairPlayStreamingSessionManager< error )) self.errorDispatcher.dispatchLicenseRequestError( - error + error: error, + playbackID: playbackID ) return } diff --git a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift index 2adffd1f..1090b838 100644 --- a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift +++ b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift @@ -142,6 +142,7 @@ class PlayerSDK { func registerPlayerLayer( playerLayer: AVPlayerLayer, monitoringOptions: MonitoringOptions, + playbackID: String, requiresReverseProxying: Bool = false, usingDRM: Bool = false ) { @@ -151,6 +152,7 @@ class PlayerSDK { monitor.setupMonitoring( playerLayer: playerLayer, + playbackID: playbackID, options: monitoringOptions, usingDRM: usingDRM ) @@ -185,6 +187,7 @@ class PlayerSDK { func registerPlayerViewController( playerViewController: AVPlayerViewController, monitoringOptions: MonitoringOptions, + playbackID: String, requiresReverseProxying: Bool = false, usingDRM: Bool = false ) { @@ -194,6 +197,7 @@ class PlayerSDK { monitor.setupMonitoring( playerViewController: playerViewController, + playbackID: playbackID, options: monitoringOptions, usingDRM: usingDRM ) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index fff3d658..c1cdd31b 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -20,6 +20,7 @@ class Monitor: ErrorDispatcher { func setupMonitoring( playerViewController: AVPlayerViewController, + playbackID: String, options: MonitoringOptions, usingDRM: Bool = false ) { @@ -116,6 +117,7 @@ class Monitor: ErrorDispatcher { func setupMonitoring( playerLayer: AVPlayerLayer, + playbackID: String, options: MonitoringOptions, usingDRM: Bool = false ) { @@ -214,21 +216,23 @@ class Monitor: ErrorDispatcher { _ playerItem: AVPlayerItem?, for player: AVPlayer ) { - + } // MARK: - Error Dispatch func dispatchApplicationCertificateRequestError( - _ error: FairPlaySessionError + error: FairPlaySessionError, + playbackID: String ) { } func dispatchLicenseRequestError( - _ error: FairPlaySessionError + error: FairPlaySessionError, + playbackID: String ) { - + } func dispatchError( diff --git a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift index 34d537dd..2c80912f 100644 --- a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift +++ b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift @@ -15,7 +15,9 @@ extension AVPlayerLayer { public convenience init(playbackID: String) { self.init() - let playerItem = AVPlayerItem(playbackID: playbackID) + let playerItem = AVPlayerItem( + playbackID: playbackID + ) let player = AVPlayer(playerItem: playerItem) @@ -27,6 +29,7 @@ extension AVPlayerLayer { PlayerSDK.shared.monitor.setupMonitoring( playerLayer: self, + playbackID: playbackID, options: monitoringOptions ) } @@ -62,6 +65,7 @@ extension AVPlayerLayer { PlayerSDK.shared.registerPlayerLayer( playerLayer: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: true ) @@ -69,6 +73,7 @@ extension AVPlayerLayer { PlayerSDK.shared.registerPlayerLayer( playerLayer: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: false ) @@ -104,6 +109,7 @@ extension AVPlayerLayer { PlayerSDK.shared.registerPlayerLayer( playerLayer: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache ) } @@ -132,6 +138,7 @@ extension AVPlayerLayer { playerItem: AVPlayerItem( playbackID: playbackID ), + playbackID: playbackID, monitoringOptions: MonitoringOptions( playbackID: playbackID ) @@ -160,6 +167,7 @@ extension AVPlayerLayer { playbackID: playbackID, playbackOptions: playbackOptions ), + playbackID: playbackID, playbackOptions: playbackOptions, monitoringOptions: MonitoringOptions( playbackID: playbackID @@ -188,6 +196,7 @@ extension AVPlayerLayer { playerItem: AVPlayerItem( playbackID: playbackID ), + playbackID: playbackID, monitoringOptions: monitoringOptions ) } @@ -217,6 +226,7 @@ extension AVPlayerLayer { playbackID: playbackID, playbackOptions: playbackOptions ), + playbackID: playbackID, playbackOptions: playbackOptions, monitoringOptions: monitoringOptions ) @@ -224,6 +234,7 @@ extension AVPlayerLayer { internal func prepare( playerItem: AVPlayerItem, + playbackID: String, playbackOptions: PlaybackOptions = PlaybackOptions(), monitoringOptions: MonitoringOptions, playerSDK: PlayerSDK = .shared @@ -249,6 +260,7 @@ extension AVPlayerLayer { playerSDK.registerPlayerLayer( playerLayer: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: usingDRM ) diff --git a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift index b00dc086..71fb7f6b 100644 --- a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift +++ b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift @@ -27,7 +27,8 @@ extension AVPlayerViewController { PlayerSDK.shared.registerPlayerViewController( playerViewController: self, - monitoringOptions: monitoringOptions + monitoringOptions: monitoringOptions, + playbackID: playbackID ) } @@ -53,7 +54,8 @@ extension AVPlayerViewController { PlayerSDK.shared.registerPlayerViewController( playerViewController: self, - monitoringOptions: monitoringOptions + monitoringOptions: monitoringOptions, + playbackID: playbackID ) } @@ -88,6 +90,7 @@ extension AVPlayerViewController { PlayerSDK.shared.registerPlayerViewController( playerViewController: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: true ) @@ -95,6 +98,7 @@ extension AVPlayerViewController { PlayerSDK.shared.registerPlayerViewController( playerViewController: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: false ) @@ -131,6 +135,7 @@ extension AVPlayerViewController { PlayerSDK.shared.registerPlayerViewController( playerViewController: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: true ) @@ -138,6 +143,7 @@ extension AVPlayerViewController { PlayerSDK.shared.registerPlayerViewController( playerViewController: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: false ) @@ -168,6 +174,7 @@ extension AVPlayerViewController { playerItem: AVPlayerItem( playbackID: playbackID ), + playbackID: playbackID, monitoringOptions: MonitoringOptions( playbackID: playbackID ) @@ -197,6 +204,7 @@ extension AVPlayerViewController { playbackID: playbackID, playbackOptions: playbackOptions ), + playbackID: playbackID, playbackOptions: playbackOptions, monitoringOptions: MonitoringOptions( playbackID: playbackID @@ -226,6 +234,7 @@ extension AVPlayerViewController { playerItem: AVPlayerItem( playbackID: playbackID ), + playbackID: playbackID, monitoringOptions: monitoringOptions ) } @@ -256,6 +265,7 @@ extension AVPlayerViewController { playbackID: playbackID, playbackOptions: playbackOptions ), + playbackID: playbackID, playbackOptions: playbackOptions, monitoringOptions: monitoringOptions ) @@ -263,6 +273,7 @@ extension AVPlayerViewController { internal func prepare( playerItem: AVPlayerItem, + playbackID: String, playbackOptions: PlaybackOptions = PlaybackOptions(), monitoringOptions: MonitoringOptions, playerSDK: PlayerSDK = .shared @@ -288,6 +299,7 @@ extension AVPlayerViewController { playerSDK.registerPlayerViewController( playerViewController: self, monitoringOptions: monitoringOptions, + playbackID: playbackID, requiresReverseProxying: playbackOptions.enableSmartCache, usingDRM: usingDRM ) From 095c04bb702c69b98474122413c1efbe5ef84912 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Tue, 27 Aug 2024 16:59:42 -0700 Subject: [PATCH 10/32] fix tests chore: remove unneeded method --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 18 ------------------ Tests/MuxPlayerSwift/MonitorTests.swift | 6 +++++- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index c1cdd31b..b0bc933a 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -234,22 +234,4 @@ class Monitor: ErrorDispatcher { ) { } - - func dispatchError( - errorCode: String, - errorMessage: String, - playerObjectIdentifier: ObjectIdentifier - ) { - guard let monitoredPlayer = self.bindings[playerObjectIdentifier] else { - return - } - - let playerName = monitoredPlayer.name - - MUXSDKStats.dispatchError( - errorCode, - withMessage: errorMessage, - forPlayer: playerName - ) - } } diff --git a/Tests/MuxPlayerSwift/MonitorTests.swift b/Tests/MuxPlayerSwift/MonitorTests.swift index b9b554d5..715bda6e 100644 --- a/Tests/MuxPlayerSwift/MonitorTests.swift +++ b/Tests/MuxPlayerSwift/MonitorTests.swift @@ -28,11 +28,13 @@ class TestMonitor: Monitor { override func setupMonitoring( playerViewController: AVPlayerViewController, + playbackID: String, options: MonitoringOptions, usingDRM: Bool = false ) { super.setupMonitoring( playerViewController: playerViewController, + playbackID: playbackID, options: options, usingDRM: usingDRM ) @@ -44,11 +46,13 @@ class TestMonitor: Monitor { override func setupMonitoring( playerLayer: AVPlayerLayer, + playbackID: String, options: MonitoringOptions, usingDRM: Bool = false ) { super.setupMonitoring( - playerLayer: playerLayer, + playerLayer: playerLayer, + playbackID: playbackID, options: options, usingDRM: usingDRM ) From cda061147a76c27bc32473bf312e6d4224063375 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Wed, 28 Aug 2024 09:07:47 -0700 Subject: [PATCH 11/32] feat: opt-out of automatic error tracking if using FairPlay --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 110 ++++++++---------- 1 file changed, 49 insertions(+), 61 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index b0bc933a..18222b55 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -25,25 +25,25 @@ class Monitor: ErrorDispatcher { usingDRM: Bool = false ) { - let monitoredPlayer: MonitoredPlayer + let customerData: MUXSDKCustomerData - if let customerData = options.customerData { + if let externallySpecifiedCustomerData = options.customerData { - let customerDataCopy = MUXSDKCustomerData() + let modifiedCustomerData = MUXSDKCustomerData() - if let customerVideoData = customerData.customerVideoData { + if let customerVideoData = externallySpecifiedCustomerData.customerVideoData { let customerVideoDataCopy = MUXSDKCustomerVideoData() customerVideoDataCopy.setQuery(customerVideoData.toQuery()) - customerDataCopy.customerVideoData = customerVideoData + modifiedCustomerData.customerVideoData = customerVideoData } - if let customerViewData = customerData.customerViewData { + if let customerViewData = externallySpecifiedCustomerData.customerViewData { let customerViewDataCopy = MUXSDKCustomerViewData() customerViewDataCopy.setQuery(customerViewData.toQuery()) - customerDataCopy.customerViewData = customerViewData + modifiedCustomerData.customerViewData = customerViewData } - if let customerViewerData = customerData.customerViewerData { + if let customerViewerData = externallySpecifiedCustomerData.customerViewerData { let customerViewerDataCopy = MUXSDKCustomerViewerData() customerViewerDataCopy.viewerApplicationName = customerViewerData.viewerApplicationName customerViewerDataCopy.viewerDeviceCategory = customerViewerData.viewerDeviceCategory @@ -51,38 +51,29 @@ class Monitor: ErrorDispatcher { customerViewerDataCopy.viewerDeviceModel = customerViewerData.viewerDeviceModel customerViewerDataCopy.viewerOsFamily = customerViewerData.viewerOsFamily customerViewerDataCopy.viewerOsVersion = customerViewerData.viewerOsVersion - customerDataCopy.customerViewerData = customerViewerData + modifiedCustomerData.customerViewerData = customerViewerData } - if let customerPlayerData = customerData.customerPlayerData { + if let customerPlayerData = externallySpecifiedCustomerData.customerPlayerData { let customerPlayerDataCopy = MUXSDKCustomerPlayerData() customerPlayerDataCopy.setQuery( customerPlayerData.toQuery() ) customerPlayerDataCopy.playerSoftwareVersion = SemanticVersion.versionString customerPlayerDataCopy.playerSoftwareName = "MuxPlayerSwiftAVPlayerViewController" - customerDataCopy.customerPlayerData = customerPlayerDataCopy + modifiedCustomerData.customerPlayerData = customerPlayerDataCopy } else { let customerPlayerData = MUXSDKCustomerPlayerData() customerPlayerData.playerSoftwareVersion = SemanticVersion.versionString customerPlayerData.playerSoftwareName = "MuxPlayerSwiftAVPlayerViewController" - customerDataCopy.customerPlayerData = customerPlayerData + modifiedCustomerData.customerPlayerData = customerPlayerData } - let binding = MUXSDKStats.monitorAVPlayerViewController( - playerViewController, - withPlayerName: options.playerName, - customerData: customerDataCopy - ) - - monitoredPlayer = MonitoredPlayer( - name: options.playerName, - binding: binding! - ) - + customerData = modifiedCustomerData + } else { - let customerData = MUXSDKCustomerData() + customerData = MUXSDKCustomerData() let customerPlayerData = MUXSDKCustomerPlayerData() customerPlayerData.playerSoftwareVersion = SemanticVersion.versionString customerPlayerData.playerSoftwareName = "MuxPlayerSwiftAVPlayerViewController" @@ -97,18 +88,21 @@ class Monitor: ErrorDispatcher { customerViewData.viewDrmType = "fairplay" customerData.customerViewData = customerViewData } + } - let binding = MUXSDKStats.monitorAVPlayerViewController( - playerViewController, - withPlayerName: options.playerName, - customerData: customerData - ) + let shouldTrackErrorsAutomatically = !usingDRM - monitoredPlayer = MonitoredPlayer( - name: options.playerName, - binding: binding! - ) - } + let binding = MUXSDKStats.monitorAVPlayerViewController( + playerViewController, + withPlayerName: options.playerName, + customerData: customerData, + automaticErrorTracking: shouldTrackErrorsAutomatically + ) + + let monitoredPlayer = MonitoredPlayer( + name: options.playerName, + binding: binding! + ) let objectIdentifier = ObjectIdentifier(playerViewController) @@ -121,41 +115,32 @@ class Monitor: ErrorDispatcher { options: MonitoringOptions, usingDRM: Bool = false ) { - let monitoredPlayer: MonitoredPlayer + let customerData: MUXSDKCustomerData - if let customerData = options.customerData { + if let externallySpecifiedCustomerData = options.customerData { - let customerDataCopy = MUXSDKCustomerData() + let modifiedCustomerData = MUXSDKCustomerData() - if let customerPlayerData = customerData.customerPlayerData { + if let customerPlayerData = externallySpecifiedCustomerData.customerPlayerData { let customerPlayerDataCopy = MUXSDKCustomerPlayerData() customerPlayerDataCopy.setQuery( customerPlayerData.toQuery() ) customerPlayerDataCopy.playerSoftwareVersion = SemanticVersion.versionString customerPlayerData.playerSoftwareName = "MuxPlayerSwiftAVPlayerLayer" - customerDataCopy.customerPlayerData = customerPlayerDataCopy + modifiedCustomerData.customerPlayerData = customerPlayerDataCopy } else { let customerPlayerData = MUXSDKCustomerPlayerData() customerPlayerData.playerSoftwareVersion = SemanticVersion.versionString customerPlayerData.playerSoftwareName = "MuxPlayerSwiftAVPlayerLayer" - customerDataCopy.customerPlayerData = customerPlayerData + modifiedCustomerData.customerPlayerData = customerPlayerData } - let binding = MUXSDKStats.monitorAVPlayerLayer( - playerLayer, - withPlayerName: options.playerName, - customerData: customerDataCopy - ) - - monitoredPlayer = MonitoredPlayer( - name: options.playerName, - binding: binding! - ) + customerData = modifiedCustomerData } else { - let customerData = MUXSDKCustomerData() + customerData = MUXSDKCustomerData() let customerPlayerData = MUXSDKCustomerPlayerData() customerPlayerData.playerSoftwareVersion = SemanticVersion.versionString customerPlayerData.playerSoftwareName = "MuxPlayerSwiftAVPlayerLayer" @@ -170,18 +155,21 @@ class Monitor: ErrorDispatcher { customerViewData.viewDrmType = "fairplay" customerData.customerViewData = customerViewData } + } - let binding = MUXSDKStats.monitorAVPlayerLayer( - playerLayer, - withPlayerName: options.playerName, - customerData: customerData - ) + let shouldTrackErrorsAutomatically = !usingDRM - monitoredPlayer = MonitoredPlayer( - name: options.playerName, - binding: binding! - ) - } + let binding = MUXSDKStats.monitorAVPlayerLayer( + playerLayer, + withPlayerName: options.playerName, + customerData: customerData, + automaticErrorTracking: shouldTrackErrorsAutomatically + ) + + let monitoredPlayer = MonitoredPlayer( + name: options.playerName, + binding: binding! + ) let objectIdentifier = ObjectIdentifier(playerLayer) From 1d93657d42ea2b913de7c670ec3fab0a35209e1a Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Wed, 4 Sep 2024 11:53:28 -0700 Subject: [PATCH 12/32] extract playback ID from AVPlayerItem if present --- .../InternalExtensions/AVPlayerItem+Mux.swift | 33 +++++++++++++++++++ Tests/MuxPlayerSwift/PlaybackURLTests.swift | 18 ++++++++++ 2 files changed, 51 insertions(+) diff --git a/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift b/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift index cfe64687..0b94905b 100644 --- a/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift +++ b/Sources/MuxPlayerSwift/InternalExtensions/AVPlayerItem+Mux.swift @@ -80,3 +80,36 @@ internal extension AVPlayerItem { ) } } + +internal extension AVPlayerItem { + + // Extracts Mux playback ID from remote AVAsset, if possible + var playbackID: String? { + guard let remoteAsset = asset as? AVURLAsset else { + return nil + } + + guard let components = URLComponents( + url: remoteAsset.url, + resolvingAgainstBaseURL: false + ) else { + return nil + } + + guard let host = components.host, host.contains("stream.") else { + return nil + } + + guard components.path.hasSuffix(".m3u8") else { + return nil + } + + var path = components.path + + path.removeLast(5) + + path.removeFirst(1) + + return path + } +} diff --git a/Tests/MuxPlayerSwift/PlaybackURLTests.swift b/Tests/MuxPlayerSwift/PlaybackURLTests.swift index 75ec2439..43a61b8e 100644 --- a/Tests/MuxPlayerSwift/PlaybackURLTests.swift +++ b/Tests/MuxPlayerSwift/PlaybackURLTests.swift @@ -483,4 +483,22 @@ final class PlaybackURLTests: XCTestCase { }) ) } + + func testPlaybackIDExtraction() throws { + let playerItem = AVPlayerItem(playbackID: "abc123") + + XCTAssertEqual(playerItem.playbackID, "abc123") + + let customDomainPlayerItem = AVPlayerItem( + playbackID: "def456", + playbackOptions: PlaybackOptions( + customDomain: "media.example.com" + ) + ) + + XCTAssertEqual( + customDomainPlayerItem.playbackID, + "def456" + ) + } } From 66f355ecbd0187ea30462601ab7f79e81d63e35d Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 09:04:48 -0700 Subject: [PATCH 13/32] record player object identifier --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 18222b55..23e45554 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -14,6 +14,7 @@ class Monitor: ErrorDispatcher { struct MonitoredPlayer { var name: String var binding: MUXSDKPlayerBinding + var playerIdentifier: ObjectIdentifier } var bindings: [ObjectIdentifier: MonitoredPlayer] = [:] @@ -99,14 +100,41 @@ class Monitor: ErrorDispatcher { automaticErrorTracking: shouldTrackErrorsAutomatically ) + guard let binding else { + return + } + let monitoredPlayer = MonitoredPlayer( name: options.playerName, - binding: binding! + binding: binding, + playerIdentifier: ObjectIdentifier(playerViewController.player!) ) let objectIdentifier = ObjectIdentifier(playerViewController) bindings[objectIdentifier] = monitoredPlayer + + if let player = playerViewController.player { + let observation = player.observe( + \.error, + options: [.new, .old] + ) { player, observation in + if let error = (observation.newValue ?? nil) as? NSError, + ((observation.oldValue ?? nil) == nil) { + binding.dispatchError( + "\(error.code)", + withMessage: error.localizedFailureReason + ) + } + } + + if let existingObservation = playersToObservations[ObjectIdentifier(player)] { + existingObservation.invalidate() + playersToObservations.removeValue(forKey: ObjectIdentifier(player)) + } + + playersToObservations[ObjectIdentifier(player)] = observation + } } func setupMonitoring( @@ -166,14 +194,41 @@ class Monitor: ErrorDispatcher { automaticErrorTracking: shouldTrackErrorsAutomatically ) + guard let binding else { + return + } + let monitoredPlayer = MonitoredPlayer( name: options.playerName, - binding: binding! + binding: binding, + playerIdentifier: ObjectIdentifier(playerLayer.player!) ) let objectIdentifier = ObjectIdentifier(playerLayer) bindings[objectIdentifier] = monitoredPlayer + + if let player = playerLayer.player { + let observation = player.observe( + \.error, + options: [.new, .old] + ) { player, observation in + if let error = (observation.newValue ?? nil) as? NSError, + ((observation.oldValue ?? nil) == nil) { + binding.dispatchError( + "\(error.code)", + withMessage: error.localizedFailureReason + ) + } + } + + if let existingObservation = playersToObservations[ObjectIdentifier(player)] { + existingObservation.invalidate() + playersToObservations.removeValue(forKey: ObjectIdentifier(player)) + } + + playersToObservations[ObjectIdentifier(player)] = observation + } } func tearDownMonitoring(playerViewController: AVPlayerViewController) { From 7139cde2d6b1066bc3a71adf98e019af71de70d6 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 09:27:40 -0700 Subject: [PATCH 14/32] this makes more sense here --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 23e45554..cac56d2c 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -115,25 +115,25 @@ class Monitor: ErrorDispatcher { bindings[objectIdentifier] = monitoredPlayer if let player = playerViewController.player { - let observation = player.observe( - \.error, - options: [.new, .old] - ) { player, observation in - if let error = (observation.newValue ?? nil) as? NSError, - ((observation.oldValue ?? nil) == nil) { + // TODO: Add a better way to protect against + // dual registrations for the same player + keyValueObservation.unregister( + player + ) + + keyValueObservation.register( + player, + for: \.error, + options: [.new, .old] + ) { player, change in + if let error = (change.newValue ?? nil) as? NSError, + ((change.oldValue ?? nil) == nil) { binding.dispatchError( "\(error.code)", withMessage: error.localizedFailureReason ) } } - - if let existingObservation = playersToObservations[ObjectIdentifier(player)] { - existingObservation.invalidate() - playersToObservations.removeValue(forKey: ObjectIdentifier(player)) - } - - playersToObservations[ObjectIdentifier(player)] = observation } } @@ -209,25 +209,25 @@ class Monitor: ErrorDispatcher { bindings[objectIdentifier] = monitoredPlayer if let player = playerLayer.player { - let observation = player.observe( - \.error, - options: [.new, .old] - ) { player, observation in - if let error = (observation.newValue ?? nil) as? NSError, - ((observation.oldValue ?? nil) == nil) { + // TODO: Add a better way to protect against + // dual registrations for the same player + keyValueObservation.unregister( + player + ) + + keyValueObservation.register( + player, + for: \.error, + options: [.new, .old] + ) { player, change in + if let error = (change.newValue ?? nil) as? NSError, + ((change.oldValue ?? nil) == nil) { binding.dispatchError( "\(error.code)", withMessage: error.localizedFailureReason ) } } - - if let existingObservation = playersToObservations[ObjectIdentifier(player)] { - existingObservation.invalidate() - playersToObservations.removeValue(forKey: ObjectIdentifier(player)) - } - - playersToObservations[ObjectIdentifier(player)] = observation } } @@ -276,5 +276,38 @@ class Monitor: ErrorDispatcher { playbackID: String ) { + class KeyValueObservation { + var observations: [ObjectIdentifier: Set] = [:] + + func register( + _ player: AVPlayer, + for keyPath: KeyPath, + options: NSKeyValueObservingOptions, + changeHandler: @escaping (AVPlayer, NSKeyValueObservedChange) -> Void + ) { + let observation = player.observe( + keyPath, + options: options, + changeHandler: changeHandler + ) + + if var o = observations[ObjectIdentifier(player)] { + o.insert(observation) + observations[ObjectIdentifier(player)] = o + } else { + observations[ObjectIdentifier(player)] = Set(arrayLiteral: observation) + } + } + + func unregister( + _ player: AVPlayer + ) { + if let o = observations[ObjectIdentifier(player)] { + o.forEach { observation in + observation.invalidate() + } + observations.removeValue(forKey: ObjectIdentifier(player)) + } + } } } From d5fe4755039b9803f50fd4a66d7c1950645154e6 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 13:55:30 -0700 Subject: [PATCH 15/32] weak references --- Sources/MuxPlayerSwift/Monitoring/Monitor.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index cac56d2c..2c53437a 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -125,7 +125,12 @@ class Monitor: ErrorDispatcher { player, for: \.error, options: [.new, .old] - ) { player, change in + ) { [weak binding] player, change in + + guard let binding else { + return + } + if let error = (change.newValue ?? nil) as? NSError, ((change.oldValue ?? nil) == nil) { binding.dispatchError( @@ -219,7 +224,12 @@ class Monitor: ErrorDispatcher { player, for: \.error, options: [.new, .old] - ) { player, change in + ) { [weak binding] player, change in + + guard let binding else { + return + } + if let error = (change.newValue ?? nil) as? NSError, ((change.oldValue ?? nil) == nil) { binding.dispatchError( From fd76c038451f37ee2ebb8a4fbe33eec728dfc94c Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 13:56:52 -0700 Subject: [PATCH 16/32] move current item observation --- .../GlobalLifecycle/PlayerSDK.swift | 30 ---------------- .../MuxPlayerSwift/Monitoring/Monitor.swift | 35 ++++++++++++++++++- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift index 1090b838..8a27686f 100644 --- a/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift +++ b/Sources/MuxPlayerSwift/GlobalLifecycle/PlayerSDK.swift @@ -157,21 +157,6 @@ class PlayerSDK { usingDRM: usingDRM ) - if let player = playerLayer.player { - keyValueObservation.register( - player, - for: \.currentItem, - options: [.initial, .new, .old] - ) { player, change in - if let newValue = change.newValue { - self.monitor.handleUpdatedCurrentPlayerItem( - newValue, - for: player - ) - } - } - } - if let player = playerLayer.player, requiresReverseProxying == true { keyValueObservation.register( @@ -202,21 +187,6 @@ class PlayerSDK { usingDRM: usingDRM ) - if let player = playerViewController.player { - keyValueObservation.register( - player, - for: \.currentItem, - options: [.initial, .new, .old] - ) { player, change in - if let newValue = change.newValue { - self.monitor.handleUpdatedCurrentPlayerItem( - newValue, - for: player - ) - } - } - } - if let player = playerViewController.player, requiresReverseProxying == true { keyValueObservation.register( diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 2c53437a..f6d9e763 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -139,6 +139,22 @@ class Monitor: ErrorDispatcher { ) } } + + keyValueObservation.register( + player, + for: \.currentItem, + options: [.initial, .new, .old] + ) { [weak self] player, change in + guard let self else { + return + } + + self.handleUpdatedCurrentPlayerItem( + previousPlayerItem: change.oldValue ?? nil, + updatedPlayerItem: change.newValue ?? nil, + for: player + ) + } } } @@ -238,6 +254,22 @@ class Monitor: ErrorDispatcher { ) } } + + keyValueObservation.register( + player, + for: \.currentItem, + options: [.initial, .new, .old] + ) { [weak self] player, change in + guard let self else { + return + } + + self.handleUpdatedCurrentPlayerItem( + previousPlayerItem: change.oldValue ?? nil, + updatedPlayerItem: change.newValue ?? nil, + for: player + ) + } } } @@ -266,7 +298,8 @@ class Monitor: ErrorDispatcher { // MARK: - Player Item Tracking func handleUpdatedCurrentPlayerItem( - _ playerItem: AVPlayerItem?, + previousPlayerItem: AVPlayerItem?, + updatedPlayerItem: AVPlayerItem?, for player: AVPlayer ) { From 5dfe61fe887918068ad3d4ecc4261adf152bfbfa Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 13:58:43 -0700 Subject: [PATCH 17/32] selective error fwding --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index f6d9e763..749f6174 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -121,22 +121,24 @@ class Monitor: ErrorDispatcher { player ) - keyValueObservation.register( - player, - for: \.error, - options: [.new, .old] - ) { [weak binding] player, change in - - guard let binding else { - return - } - - if let error = (change.newValue ?? nil) as? NSError, - ((change.oldValue ?? nil) == nil) { - binding.dispatchError( - "\(error.code)", - withMessage: error.localizedFailureReason - ) + if usingDRM { + keyValueObservation.register( + player, + for: \.error, + options: [.new, .old] + ) { [weak binding] player, change in + + guard let binding else { + return + } + + if let error = (change.newValue ?? nil) as? NSError, + ((change.oldValue ?? nil) == nil) { + binding.dispatchError( + "\(error.code)", + withMessage: error.localizedFailureReason + ) + } } } @@ -236,22 +238,24 @@ class Monitor: ErrorDispatcher { player ) - keyValueObservation.register( - player, - for: \.error, - options: [.new, .old] - ) { [weak binding] player, change in - - guard let binding else { - return - } - - if let error = (change.newValue ?? nil) as? NSError, - ((change.oldValue ?? nil) == nil) { - binding.dispatchError( - "\(error.code)", - withMessage: error.localizedFailureReason - ) + if usingDRM { + keyValueObservation.register( + player, + for: \.error, + options: [.new, .old] + ) { [weak binding] player, change in + + guard let binding else { + return + } + + if let error = (change.newValue ?? nil) as? NSError, + ((change.oldValue ?? nil) == nil) { + binding.dispatchError( + "\(error.code)", + withMessage: error.localizedFailureReason + ) + } } } From 77003fadb6be0f6420fb9e585aaaa41366e409bf Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 13:59:02 -0700 Subject: [PATCH 18/32] property --- Sources/MuxPlayerSwift/Monitoring/Monitor.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 749f6174..ffbcb419 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -19,6 +19,8 @@ class Monitor: ErrorDispatcher { var bindings: [ObjectIdentifier: MonitoredPlayer] = [:] + let keyValueObservation = KeyValueObservation() + func setupMonitoring( playerViewController: AVPlayerViewController, playbackID: String, From d156dbc32782069a451742636e235cc3e06f1123 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 14:05:36 -0700 Subject: [PATCH 19/32] mappings --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index ffbcb419..803076c3 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -19,6 +19,10 @@ class Monitor: ErrorDispatcher { var bindings: [ObjectIdentifier: MonitoredPlayer] = [:] + var playersToPlaybackIDs: [ObjectIdentifier: String] = [:] + + var playersToObservedObjectIdentifier: [ObjectIdentifier: ObjectIdentifier] = [:] + let keyValueObservation = KeyValueObservation() func setupMonitoring( @@ -27,6 +31,10 @@ class Monitor: ErrorDispatcher { options: MonitoringOptions, usingDRM: Bool = false ) { + guard let player = playerViewController.player else { + // TODO: Log + return + } let customerData: MUXSDKCustomerData @@ -109,12 +117,15 @@ class Monitor: ErrorDispatcher { let monitoredPlayer = MonitoredPlayer( name: options.playerName, binding: binding, - playerIdentifier: ObjectIdentifier(playerViewController.player!) + playerIdentifier: ObjectIdentifier( + player + ) ) let objectIdentifier = ObjectIdentifier(playerViewController) bindings[objectIdentifier] = monitoredPlayer + playersToObservedObjectIdentifier[ObjectIdentifier(player)] = objectIdentifier if let player = playerViewController.player { // TODO: Add a better way to protect against @@ -168,6 +179,11 @@ class Monitor: ErrorDispatcher { options: MonitoringOptions, usingDRM: Bool = false ) { + guard let player = playerLayer.player else { + // TODO: Log + return + } + let customerData: MUXSDKCustomerData if let externallySpecifiedCustomerData = options.customerData { @@ -226,12 +242,15 @@ class Monitor: ErrorDispatcher { let monitoredPlayer = MonitoredPlayer( name: options.playerName, binding: binding, - playerIdentifier: ObjectIdentifier(playerLayer.player!) + playerIdentifier: ObjectIdentifier( + player + ) ) let objectIdentifier = ObjectIdentifier(playerLayer) bindings[objectIdentifier] = monitoredPlayer + playersToObservedObjectIdentifier[ObjectIdentifier(player)] = objectIdentifier if let player = playerLayer.player { // TODO: Add a better way to protect against @@ -308,7 +327,9 @@ class Monitor: ErrorDispatcher { updatedPlayerItem: AVPlayerItem?, for player: AVPlayer ) { - + if let updatedPlaybackID = updatedPlayerItem?.playbackID { + self.playersToPlaybackIDs[ObjectIdentifier(player)] = updatedPlaybackID + } } // MARK: - Error Dispatch From 9724a904b2ed5108144dbd5ddda577893b97c28f Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 14:21:33 -0700 Subject: [PATCH 20/32] invert mapping to simplify routing error to the right player --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 803076c3..eab860cf 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -19,7 +19,7 @@ class Monitor: ErrorDispatcher { var bindings: [ObjectIdentifier: MonitoredPlayer] = [:] - var playersToPlaybackIDs: [ObjectIdentifier: String] = [:] + var playbackIDsToPlayers: [String: ObjectIdentifier] = [:] var playersToObservedObjectIdentifier: [ObjectIdentifier: ObjectIdentifier] = [:] @@ -328,7 +328,11 @@ class Monitor: ErrorDispatcher { for player: AVPlayer ) { if let updatedPlaybackID = updatedPlayerItem?.playbackID { - self.playersToPlaybackIDs[ObjectIdentifier(player)] = updatedPlaybackID + playbackIDsToPlayers[updatedPlaybackID] = ObjectIdentifier(player) + } + + if let previousPlaybackID = previousPlayerItem?.playbackID { + playbackIDsToPlayers[previousPlaybackID] = ObjectIdentifier(player) } } @@ -339,12 +343,29 @@ class Monitor: ErrorDispatcher { playbackID: String ) { + if let playerObjectIdentifier = playbackIDsToPlayers[playbackID], + let bindingReferenceIdentifier = playersToObservedObjectIdentifier[playerObjectIdentifier], + let monitoredPlayer = bindings[bindingReferenceIdentifier] { + monitoredPlayer.binding.dispatchError( + "", + withMessage: "" + ) + } } func dispatchLicenseRequestError( error: FairPlaySessionError, playbackID: String ) { + if let playerObjectIdentifier = playbackIDsToPlayers[playbackID], + let bindingReferenceIdentifier = playersToObservedObjectIdentifier[playerObjectIdentifier], + let monitoredPlayer = bindings[bindingReferenceIdentifier] { + monitoredPlayer.binding.dispatchError( + "", + withMessage: "" + ) + } + } class KeyValueObservation { var observations: [ObjectIdentifier: Set] = [:] From 9116546bfc85cd537bae1f7334588b703facde85 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 14:24:17 -0700 Subject: [PATCH 21/32] typo --- Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift index 5e3d0446..0ba1dd8b 100644 --- a/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift +++ b/Sources/MuxPlayerSwift/FairPlay/FairPlaySessionManager.swift @@ -226,7 +226,7 @@ class DefaultFairPlayStreamingSessionManager< guard let data = data, data.count > 0 else { let error = FairPlaySessionError.unexpected( - message: "No cert data with 200 OK respone" + message: "No cert data with 200 OK response" ) self.logger.debug( "Applicate certificate request completed with missing data and response code \(responseCode.debugDescription)" From 77a87b24257915bd8132adc641c271068558ba5f Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 14:35:44 -0700 Subject: [PATCH 22/32] more descriptive names --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index eab860cf..3bc2ee88 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -19,9 +19,9 @@ class Monitor: ErrorDispatcher { var bindings: [ObjectIdentifier: MonitoredPlayer] = [:] - var playbackIDsToPlayers: [String: ObjectIdentifier] = [:] + var playbackIDsToPlayerObjectIdentifier: [String: ObjectIdentifier] = [:] - var playersToObservedObjectIdentifier: [ObjectIdentifier: ObjectIdentifier] = [:] + var playerObjectIdentifiersToBindingReferenceObjectIdentifier: [ObjectIdentifier: ObjectIdentifier] = [:] let keyValueObservation = KeyValueObservation() @@ -125,7 +125,9 @@ class Monitor: ErrorDispatcher { let objectIdentifier = ObjectIdentifier(playerViewController) bindings[objectIdentifier] = monitoredPlayer - playersToObservedObjectIdentifier[ObjectIdentifier(player)] = objectIdentifier + playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] = objectIdentifier if let player = playerViewController.player { // TODO: Add a better way to protect against @@ -250,7 +252,7 @@ class Monitor: ErrorDispatcher { let objectIdentifier = ObjectIdentifier(playerLayer) bindings[objectIdentifier] = monitoredPlayer - playersToObservedObjectIdentifier[ObjectIdentifier(player)] = objectIdentifier + playerObjectIdentifiersToBindingReferenceObjectIdentifier[ObjectIdentifier(player)] = objectIdentifier if let player = playerLayer.player { // TODO: Add a better way to protect against @@ -328,11 +330,11 @@ class Monitor: ErrorDispatcher { for player: AVPlayer ) { if let updatedPlaybackID = updatedPlayerItem?.playbackID { - playbackIDsToPlayers[updatedPlaybackID] = ObjectIdentifier(player) + playbackIDsToPlayerObjectIdentifier[updatedPlaybackID] = ObjectIdentifier(player) } if let previousPlaybackID = previousPlayerItem?.playbackID { - playbackIDsToPlayers[previousPlaybackID] = ObjectIdentifier(player) + playbackIDsToPlayerObjectIdentifier[previousPlaybackID] = ObjectIdentifier(player) } } @@ -343,8 +345,8 @@ class Monitor: ErrorDispatcher { playbackID: String ) { - if let playerObjectIdentifier = playbackIDsToPlayers[playbackID], - let bindingReferenceIdentifier = playersToObservedObjectIdentifier[playerObjectIdentifier], + if let playerObjectIdentifier = playbackIDsToPlayerObjectIdentifier[playbackID], + let bindingReferenceIdentifier = playerObjectIdentifiersToBindingReferenceObjectIdentifier[playerObjectIdentifier], let monitoredPlayer = bindings[bindingReferenceIdentifier] { monitoredPlayer.binding.dispatchError( "", @@ -357,8 +359,8 @@ class Monitor: ErrorDispatcher { error: FairPlaySessionError, playbackID: String ) { - if let playerObjectIdentifier = playbackIDsToPlayers[playbackID], - let bindingReferenceIdentifier = playersToObservedObjectIdentifier[playerObjectIdentifier], + if let playerObjectIdentifier = playbackIDsToPlayerObjectIdentifier[playbackID], + let bindingReferenceIdentifier = playerObjectIdentifiersToBindingReferenceObjectIdentifier[playerObjectIdentifier], let monitoredPlayer = bindings[bindingReferenceIdentifier] { monitoredPlayer.binding.dispatchError( "", From 669cb64a4d1f7a2618d13868a65788847fcdae4e Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 14:46:21 -0700 Subject: [PATCH 23/32] add context with comment --- Sources/MuxPlayerSwift/Monitoring/Monitor.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 3bc2ee88..e98871be 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -21,6 +21,16 @@ class Monitor: ErrorDispatcher { var playbackIDsToPlayerObjectIdentifier: [String: ObjectIdentifier] = [:] + /// Either AVPlayerViewController, AVPlayerLayer, or AVPlayer + /// may be used to register bindings with Data. + /// KVO notices for player updates only have a pointer + /// to the AVPlayer instance. + /// + /// Routing error updates to the right player binding + /// requires a series of lookups. This is tedious for + /// AVPlayerViewController, AVPlayerLayer and gets easier + /// if there's a mapping kept between either of them as + /// we go along. var playerObjectIdentifiersToBindingReferenceObjectIdentifier: [ObjectIdentifier: ObjectIdentifier] = [:] let keyValueObservation = KeyValueObservation() From 654916b4c1ced35a88aaa77f4fcdc94e55df26ac Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 6 Sep 2024 14:50:02 -0700 Subject: [PATCH 24/32] fix warning --- .../FairPlay/ContentKeySessionDelegate.swift | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/Sources/MuxPlayerSwift/FairPlay/ContentKeySessionDelegate.swift b/Sources/MuxPlayerSwift/FairPlay/ContentKeySessionDelegate.swift index 531c4348..25eec7a2 100644 --- a/Sources/MuxPlayerSwift/FairPlay/ContentKeySessionDelegate.swift +++ b/Sources/MuxPlayerSwift/FairPlay/ContentKeySessionDelegate.swift @@ -58,30 +58,29 @@ class ContentKeySessionDelegate Date: Mon, 9 Sep 2024 08:26:09 -0700 Subject: [PATCH 25/32] apply and fix stash --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 153 ++++++++++++++---- 1 file changed, 120 insertions(+), 33 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index e98871be..62ee9815 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -21,6 +21,8 @@ class Monitor: ErrorDispatcher { var playbackIDsToPlayerObjectIdentifier: [String: ObjectIdentifier] = [:] + var playbackIDsToFairPlaySessionErrors: [String: FairPlaySessionError] = [:] + /// Either AVPlayerViewController, AVPlayerLayer, or AVPlayer /// may be used to register bindings with Data. /// KVO notices for player updates only have a pointer @@ -151,19 +153,22 @@ class Monitor: ErrorDispatcher { player, for: \.error, options: [.new, .old] - ) { [weak binding] player, change in + ) { [weak binding, weak self] player, change in guard let binding else { return } - if let error = (change.newValue ?? nil) as? NSError, - ((change.oldValue ?? nil) == nil) { - binding.dispatchError( - "\(error.code)", - withMessage: error.localizedFailureReason - ) + guard let self else { + return } + + self.handleUpdatedPlayerError( + updatedPlayerError: change.newValue ?? nil, + previousPlayerError: change.oldValue ?? nil, + for: player, + using: binding + ) } } @@ -262,7 +267,9 @@ class Monitor: ErrorDispatcher { let objectIdentifier = ObjectIdentifier(playerLayer) bindings[objectIdentifier] = monitoredPlayer - playerObjectIdentifiersToBindingReferenceObjectIdentifier[ObjectIdentifier(player)] = objectIdentifier + playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] = objectIdentifier if let player = playerLayer.player { // TODO: Add a better way to protect against @@ -276,19 +283,22 @@ class Monitor: ErrorDispatcher { player, for: \.error, options: [.new, .old] - ) { [weak binding] player, change in + ) { [weak binding, weak self] player, change in guard let binding else { return } - if let error = (change.newValue ?? nil) as? NSError, - ((change.oldValue ?? nil) == nil) { - binding.dispatchError( - "\(error.code)", - withMessage: error.localizedFailureReason - ) + guard let self else { + return } + + self.handleUpdatedPlayerError( + updatedPlayerError: change.newValue ?? nil, + previousPlayerError: change.oldValue ?? nil, + for: player, + using: binding + ) } } @@ -314,7 +324,19 @@ class Monitor: ErrorDispatcher { let objectIdentifier = ObjectIdentifier(playerViewController) - guard let playerName = bindings[objectIdentifier]?.name else { return } + guard let playerName = bindings[objectIdentifier]?.name else { + return + } + + if let player = playerViewController.player { + keyValueObservation.unregister( + player + ) + } else if let playerObjectIdentifier = bindings[objectIdentifier]?.playerIdentifier { + keyValueObservation.unregister( + playerObjectIdentifier + ) + } MUXSDKStats.destroyPlayer(playerName) @@ -325,7 +347,19 @@ class Monitor: ErrorDispatcher { let objectIdentifier = ObjectIdentifier(playerLayer) - guard let playerName = bindings[objectIdentifier]?.name else { return } + guard let playerName = bindings[objectIdentifier]?.name else { + return + } + + if let player = playerLayer.player { + keyValueObservation.unregister( + player + ) + } else if let playerObjectIdentifier = bindings[objectIdentifier]?.playerIdentifier { + keyValueObservation.unregister( + playerObjectIdentifier + ) + } MUXSDKStats.destroyPlayer(playerName) @@ -354,31 +388,73 @@ class Monitor: ErrorDispatcher { error: FairPlaySessionError, playbackID: String ) { - - if let playerObjectIdentifier = playbackIDsToPlayerObjectIdentifier[playbackID], - let bindingReferenceIdentifier = playerObjectIdentifiersToBindingReferenceObjectIdentifier[playerObjectIdentifier], - let monitoredPlayer = bindings[bindingReferenceIdentifier] { - monitoredPlayer.binding.dispatchError( - "", - withMessage: "" - ) - } + playbackIDsToFairPlaySessionErrors[playbackID] = error } func dispatchLicenseRequestError( error: FairPlaySessionError, playbackID: String ) { - if let playerObjectIdentifier = playbackIDsToPlayerObjectIdentifier[playbackID], - let bindingReferenceIdentifier = playerObjectIdentifiersToBindingReferenceObjectIdentifier[playerObjectIdentifier], - let monitoredPlayer = bindings[bindingReferenceIdentifier] { - monitoredPlayer.binding.dispatchError( - "", - withMessage: "" - ) + playbackIDsToFairPlaySessionErrors[playbackID] = error + } + + func handleUpdatedPlayerError( + updatedPlayerError: Error?, + previousPlayerError: Error?, + for player: AVPlayer, + using binding: MUXSDKPlayerBinding + ) { + if let updatedPlayerError = updatedPlayerError as? NSError, + previousPlayerError == nil { + if let currentPlayerItem = player.currentItem, + let playbackID = currentPlayerItem.playbackID, + let fairPlaySessionError = self.playbackIDsToFairPlaySessionErrors[ + playbackID + ] { + let enrichedErrorMessage = self.enrichErrorMessageIfNecessary( + playerError: updatedPlayerError, + fairPlaySessionError: fairPlaySessionError + ) + + binding.dispatchError( + "\(updatedPlayerError.code)", + withMessage: enrichedErrorMessage + ) + } else { + binding.dispatchError( + "\(updatedPlayerError.code)", + withMessage: updatedPlayerError.localizedFailureReason + ) + } + } } + func enrichErrorMessageIfNecessary( + playerError: NSError, + fairPlaySessionError: FairPlaySessionError + ) -> String? { + + if case let FairPlaySessionError.httpFailed( + responseStatusCode + ) = fairPlaySessionError { + switch responseStatusCode { + case 400: + return "The URL or playback ID was invalid. You may have used an invalid value as a playback ID." + case 403: + return "The video's secured drm-token has expired." + case 404: + return "This URL or playback ID does not exist. You may have used an Asset ID or an ID from a different resource." + case 412: + return "This playback ID may belong to a live stream that is not currently active or an asset that is not ready." + default: + break + } + } + + return playerError.localizedFailureReason + } + class KeyValueObservation { var observations: [ObjectIdentifier: Set] = [:] @@ -412,5 +488,16 @@ class Monitor: ErrorDispatcher { observations.removeValue(forKey: ObjectIdentifier(player)) } } + + func unregister( + _ objectIdentifier: ObjectIdentifier + ) { + if let o = observations[objectIdentifier] { + o.forEach { observation in + observation.invalidate() + } + observations.removeValue(forKey: objectIdentifier) + } + } } } From 54efc162d214039065bfb215603563e2d6f68611 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Mon, 9 Sep 2024 08:26:19 -0700 Subject: [PATCH 26/32] tests wrap MUXSDKStats to validate Mux Data configuration include test fixture variant with stubs that record call arguments more tests --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 53 +- Tests/MuxPlayerSwift/MonitorTests.swift | 897 ++++++++++++++++++ 2 files changed, 946 insertions(+), 4 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 62ee9815..56baa7bb 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -9,6 +9,43 @@ import Foundation import MuxCore import MUXSDKStats +/// Wrapper around MUXSDKStats +class PlayerObservationContext { + func monitorAVPlayerViewController( + _ player: AVPlayerViewController, + withPlayerName name: String, + customerData: MUXSDKCustomerData, + automaticErrorTracking: Bool + ) -> MUXSDKPlayerBinding? { + MUXSDKStats.monitorAVPlayerViewController( + player, + withPlayerName: name, + customerData: customerData, + automaticErrorTracking: automaticErrorTracking + ) + } + + func monitorAVPlayerLayer( + _ player: AVPlayerLayer, + withPlayerName name: String, + customerData: MUXSDKCustomerData, + automaticErrorTracking: Bool + ) -> MUXSDKPlayerBinding? { + MUXSDKStats.monitorAVPlayerLayer( + player, + withPlayerName: name, + customerData: customerData, + automaticErrorTracking: automaticErrorTracking + ) + } + + func destroyPlayer( + _ name: String + ) { + MUXSDKStats.destroyPlayer(name) + } +} + class Monitor: ErrorDispatcher { struct MonitoredPlayer { @@ -37,6 +74,14 @@ class Monitor: ErrorDispatcher { let keyValueObservation = KeyValueObservation() + let playerObservationContext: PlayerObservationContext + + init( + playerObservationContext: PlayerObservationContext = PlayerObservationContext() + ) { + self.playerObservationContext = playerObservationContext + } + func setupMonitoring( playerViewController: AVPlayerViewController, playbackID: String, @@ -115,7 +160,7 @@ class Monitor: ErrorDispatcher { let shouldTrackErrorsAutomatically = !usingDRM - let binding = MUXSDKStats.monitorAVPlayerViewController( + let binding = playerObservationContext.monitorAVPlayerViewController( playerViewController, withPlayerName: options.playerName, customerData: customerData, @@ -245,7 +290,7 @@ class Monitor: ErrorDispatcher { let shouldTrackErrorsAutomatically = !usingDRM - let binding = MUXSDKStats.monitorAVPlayerLayer( + let binding = playerObservationContext.monitorAVPlayerLayer( playerLayer, withPlayerName: options.playerName, customerData: customerData, @@ -338,7 +383,7 @@ class Monitor: ErrorDispatcher { ) } - MUXSDKStats.destroyPlayer(playerName) + playerObservationContext.destroyPlayer(playerName) bindings.removeValue(forKey: objectIdentifier) } @@ -361,7 +406,7 @@ class Monitor: ErrorDispatcher { ) } - MUXSDKStats.destroyPlayer(playerName) + playerObservationContext.destroyPlayer(playerName) bindings.removeValue(forKey: objectIdentifier) } diff --git a/Tests/MuxPlayerSwift/MonitorTests.swift b/Tests/MuxPlayerSwift/MonitorTests.swift index 715bda6e..5b97549a 100644 --- a/Tests/MuxPlayerSwift/MonitorTests.swift +++ b/Tests/MuxPlayerSwift/MonitorTests.swift @@ -6,6 +6,8 @@ import AVKit import Foundation import XCTest +import MUXSDKStats + @testable import MuxPlayerSwift class PlayerLayerBackedView: UIView { @@ -63,6 +65,79 @@ class TestMonitor: Monitor { } } +class TestPlayerObservationContext: PlayerObservationContext { + + struct MonitorCall { + // AVPlayerViewController or AVPlayerLayer or AVPlayer + // Only included to validate pointer equality, for + // which NSObject suffices + let player: NSObject + let playerName: String + let customerData: MUXSDKCustomerData + let automaticErrorTracking: Bool + } + + var monitorCalls: [MonitorCall] = [] + + override func monitorAVPlayerViewController( + _ player: AVPlayerViewController, + withPlayerName name: String, + customerData: MUXSDKCustomerData, + automaticErrorTracking: Bool + ) -> MUXSDKPlayerBinding? { + + monitorCalls.append( + MonitorCall( + player: player, + playerName: name, + customerData: customerData, + automaticErrorTracking: automaticErrorTracking + ) + ) + + // In its current state this test class is only + // useful for validating Mux Data inputs. + // + // This needs to return a non-nil value in order to + // validate side effects in this SDK. + return nil + } + + override func monitorAVPlayerLayer( + _ player: AVPlayerLayer, + withPlayerName name: String, + customerData: MUXSDKCustomerData, + automaticErrorTracking: Bool + ) -> MUXSDKPlayerBinding? { + + monitorCalls.append( + MonitorCall( + player: player, + playerName: name, + customerData: customerData, + automaticErrorTracking: automaticErrorTracking + ) + ) + + // In its current state this test class is only + // useful for validating Mux Data inputs. + // + // This needs to return a non-nil value in order to + // validate side effects in this SDK. + return nil +// return MUXSDKPlayerBinding( +// name: "", +// andSoftware: "" +// ) + } + + override func destroyPlayer( + _ name: String + ) { + + } +} + class MonitorTests: XCTestCase { override func setUp() { @@ -228,4 +303,826 @@ class MonitorTests: XCTestCase { XCTAssertTrue(registration.1) testMonitor.monitoringRegistrations.removeAll() } + + func testInitializedPlayerViewControllerKVORegistrationNonDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerViewController = AVPlayerViewController( + playbackID: "abc123" + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerViewController.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 1 + ) + + let playerViewControllerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerViewControllerObjectIdentifier, + ObjectIdentifier(playerViewController) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerViewController: playerViewController + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testInitializedPlayerLayerKVORegistrationNonDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerLayer = AVPlayerLayer( + playbackID: "abc123" + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerLayer.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 1 + ) + + let playerLayerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerLayerObjectIdentifier, + ObjectIdentifier(playerLayer) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerLayer: playerLayer + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testPreexistingPlayerViewControllerKVORegistrationNonDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerViewController = AVPlayerViewController() + playerViewController.prepare( + playbackID: "abc123" + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerViewController.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 1 + ) + + let playerViewControllerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerViewControllerObjectIdentifier, + ObjectIdentifier(playerViewController) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerViewController: playerViewController + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testPreexistingPlayerLayerKVORegistrationNonDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerLayer = AVPlayerLayer() + playerLayer.prepare( + playbackID: "abc123" + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerLayer.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 1 + ) + + let playerLayerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerLayerObjectIdentifier, + ObjectIdentifier(playerLayer) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerLayer: playerLayer + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testInitializedPlayerViewControllerKVORegistrationDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerViewController = AVPlayerViewController( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerViewController.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 2 + ) + + let playerViewControllerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerViewControllerObjectIdentifier, + ObjectIdentifier(playerViewController) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerViewController: playerViewController + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testInitializedPlayerLayerKVORegistrationDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerLayer = AVPlayerLayer( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerLayer.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 2 + ) + + let playerLayerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerLayerObjectIdentifier, + ObjectIdentifier(playerLayer) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerLayer: playerLayer + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testPreexistingPlayerViewControllerKVORegistrationDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerViewController = AVPlayerViewController() + playerViewController.prepare( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerViewController.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 2 + ) + + let playerViewControllerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerViewControllerObjectIdentifier, + ObjectIdentifier(playerViewController) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerViewController: playerViewController + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testPreexistingPlayerLayerKVORegistrationDRM() throws { + PlayerSDK.shared.monitor = Monitor() + + let playerLayer = AVPlayerLayer() + playerLayer.prepare( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + XCTAssertEqual( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations + .count, + 1 + ) + + let player = try XCTUnwrap( + playerLayer.player + ) + + let observations = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + observations.count, + 2 + ) + + let playerLayerObjectIdentifier = try XCTUnwrap( + PlayerSDK + .shared + .monitor + .playerObjectIdentifiersToBindingReferenceObjectIdentifier[ + ObjectIdentifier(player) + ] + ) + + XCTAssertEqual( + playerLayerObjectIdentifier, + ObjectIdentifier(playerLayer) + ) + + PlayerSDK.shared.monitor.tearDownMonitoring( + playerLayer: playerLayer + ) + + XCTAssertNil( + PlayerSDK + .shared + .monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] + ) + } + + func testPlayerMonitoringInputs_PlayerViewController_NonDRM() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerViewController = AVPlayerViewController( + playbackID: "abc123" + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + XCTAssertEqual( + monitorCall.player, + playerViewController + ) + + XCTAssertTrue(monitorCall.automaticErrorTracking) + + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerViewController" + ) + + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + } + + func testPlayerMonitoringInputs_PlayerViewController_NonDRM_CustomEnvironmentKey() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerViewController = AVPlayerViewController( + playbackID: "abc123", + monitoringOptions: MonitoringOptions( + environmentKey: "xyz321", + playerName: "test-player-name" + ) + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + XCTAssertEqual( + monitorCall.player, + playerViewController + ) + + XCTAssertEqual( + monitorCall.playerName, + "test-player-name" + ) + + XCTAssertTrue(monitorCall.automaticErrorTracking) + + let environmentKey = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.environmentKey + ) + + XCTAssertEqual( + environmentKey, + "xyz321" + ) + + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerViewController" + ) + + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + } + + func testPlayerMonitoringInputs_PlayerLayer_NonDRM() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerLayer = AVPlayerLayer( + playbackID: "abc123" + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + XCTAssertEqual( + monitorCall.player, + playerLayer + ) + + XCTAssertTrue(monitorCall.automaticErrorTracking) + + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerLayer" + ) + + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + } + + func testPlayerMonitoringInputs_PlayerLayer_NonDRM_CustomEnvironmentKey() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerLayer = AVPlayerLayer( + playbackID: "abc123", + playbackOptions: PlaybackOptions(), + monitoringOptions: MonitoringOptions( + environmentKey: "xyz321", + playerName: "test-player-name" + ) + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + XCTAssertEqual( + monitorCall.player, + playerLayer + ) + + XCTAssertEqual( + monitorCall.playerName, + "test-player-name" + ) + + XCTAssertTrue(monitorCall.automaticErrorTracking) + + let environmentKey = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.environmentKey + ) + + XCTAssertEqual( + environmentKey, + "xyz321" + ) + + XCTAssertTrue(monitorCall.automaticErrorTracking) + + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerLayer" + ) + + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + } + + func testPlayerMonitoringInputsPlayerViewControllerDRM() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerViewController = AVPlayerViewController( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + XCTAssertEqual( + monitorCall.player, + playerViewController + ) + } + + func testPlayerMonitoringInputsPlayerLayerDRM() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerLayer = AVPlayerLayer( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + XCTAssertEqual( + monitorCall.player, + playerLayer + ) + } + +// func testPlayerItemUpdates() throws { +// PlayerSDK.shared.monitor = Monitor() +// +// let playerLayer = AVPlayerLayer( +// playbackID: "abc123", +// playbackOptions: PlaybackOptions( +// playbackToken: "def456", +// drmToken: "ghi789" +// ) +// ) +// +// +// +// playerLayer.prepare( +// playbackID: "jkl123", +// playbackOptions: PlaybackOptions( +// playbackToken: "mno456", +// drmToken: "pqr789" +// ) +// ) +// +// +// +// } } From 6dc6f9f11f00c302087ab1f3303e684ab9f4a44a Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Wed, 11 Sep 2024 14:20:49 -0700 Subject: [PATCH 27/32] pass DRM hint accurately --- .../Extensions/AVPlayerLayer+Mux.swift | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift index 2c80912f..631090af 100644 --- a/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift +++ b/Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerLayer+Mux.swift @@ -106,12 +106,23 @@ extension AVPlayerLayer { self.player = player - PlayerSDK.shared.registerPlayerLayer( - playerLayer: self, - monitoringOptions: monitoringOptions, - playbackID: playbackID, - requiresReverseProxying: playbackOptions.enableSmartCache - ) + if case PlaybackOptions.PlaybackPolicy.drm(_) = playbackOptions.playbackPolicy { + PlayerSDK.shared.registerPlayerLayer( + playerLayer: self, + monitoringOptions: monitoringOptions, + playbackID: playbackID, + requiresReverseProxying: playbackOptions.enableSmartCache, + usingDRM: true + ) + } else { + PlayerSDK.shared.registerPlayerLayer( + playerLayer: self, + monitoringOptions: monitoringOptions, + playbackID: playbackID, + requiresReverseProxying: playbackOptions.enableSmartCache, + usingDRM: false + ) + } } /// Stops monitoring the player From dddf65341553a2b47114e0603425e580f41310e9 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Wed, 11 Sep 2024 14:20:59 -0700 Subject: [PATCH 28/32] even more tests --- Tests/MuxPlayerSwift/MonitorTests.swift | 199 +++++++++++++++++++++++- 1 file changed, 197 insertions(+), 2 deletions(-) diff --git a/Tests/MuxPlayerSwift/MonitorTests.swift b/Tests/MuxPlayerSwift/MonitorTests.swift index 5b97549a..e88dc607 100644 --- a/Tests/MuxPlayerSwift/MonitorTests.swift +++ b/Tests/MuxPlayerSwift/MonitorTests.swift @@ -1051,7 +1051,7 @@ class MonitorTests: XCTestCase { ) } - func testPlayerMonitoringInputsPlayerViewControllerDRM() throws { + func testPlayerMonitoringInputs_PlayerViewController_DRM() throws { let testPlayerObservationContext = TestPlayerObservationContext() let testMonitor = Monitor( playerObservationContext: testPlayerObservationContext @@ -1070,13 +1070,159 @@ class MonitorTests: XCTestCase { testPlayerObservationContext.monitorCalls.first ) + XCTAssertFalse(monitorCall.automaticErrorTracking) + + XCTAssertEqual( + monitorCall.player, + playerViewController + ) + + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerViewController" + ) + + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + + // Validate DRM flag + + let viewDRMType = try XCTUnwrap( + monitorCall.customerData.customerViewData?.viewDrmType + ) + + XCTAssertEqual( + viewDRMType, + "fairplay" + ) + } + + func testPlayerMonitoringInputs_PlayerViewController_DRM_CustomEnvironmentKey() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerViewController = AVPlayerViewController( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + XCTAssertFalse(monitorCall.automaticErrorTracking) + XCTAssertEqual( monitorCall.player, playerViewController ) + + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerViewController" + ) + + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + + // Validate DRM flag + + let viewDRMType = try XCTUnwrap( + monitorCall.customerData.customerViewData?.viewDrmType + ) + + XCTAssertEqual( + viewDRMType, + "fairplay" + ) + } + + func testPlayerMonitoringInputs_PlayerLayer_DRM() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let playerLayer = AVPlayerLayer( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ) + ) + + let monitorCall = try XCTUnwrap( + testPlayerObservationContext.monitorCalls.first + ) + + // Validate player reference + XCTAssertEqual( + monitorCall.player, + playerLayer + ) + + // Validate automatic error tracking + XCTAssertFalse(monitorCall.automaticErrorTracking) + + // Validate customer data fields passed along to Mux Data + + // Validate player software name and version + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerLayer" + ) + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + + // Validate DRM flag + let viewDRMType = try XCTUnwrap( + monitorCall.customerData.customerViewData?.viewDrmType + ) + XCTAssertEqual( + viewDRMType, + "fairplay" + ) } - func testPlayerMonitoringInputsPlayerLayerDRM() throws { + func testPlayerMonitoringInputs_PlayerLayer_DRM_CustomEnvironmentKey() throws { let testPlayerObservationContext = TestPlayerObservationContext() let testMonitor = Monitor( playerObservationContext: testPlayerObservationContext @@ -1088,6 +1234,10 @@ class MonitorTests: XCTestCase { playbackOptions: PlaybackOptions( playbackToken: "def456", drmToken: "ghi789" + ), + monitoringOptions: MonitoringOptions( + environmentKey: "xyz321", + playerName: "test-player-name" ) ) @@ -1099,6 +1249,51 @@ class MonitorTests: XCTestCase { monitorCall.player, playerLayer ) + + XCTAssertEqual( + monitorCall.playerName, + "test-player-name" + ) + + XCTAssertFalse(monitorCall.automaticErrorTracking) + + let environmentKey = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.environmentKey + ) + XCTAssertEqual( + environmentKey, + "xyz321" + ) + + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + + XCTAssertEqual( + playerSoftwareName, + "MuxPlayerSwiftAVPlayerLayer" + ) + + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + + XCTAssertEqual( + playerSoftwareVersion, + SemanticVersion.versionString + ) + + // Validate DRM flag + + let viewDRMType = try XCTUnwrap( + monitorCall.customerData.customerViewData?.viewDrmType + ) + + XCTAssertEqual( + viewDRMType, + "fairplay" + ) } // func testPlayerItemUpdates() throws { From 3dcc32d338f43d2856ca0379f1c16bccfb651146 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Wed, 11 Sep 2024 14:56:54 -0700 Subject: [PATCH 29/32] DRYer tests --- Tests/MuxPlayerSwift/MonitorTests.swift | 487 ++++++++++-------------- 1 file changed, 200 insertions(+), 287 deletions(-) diff --git a/Tests/MuxPlayerSwift/MonitorTests.swift b/Tests/MuxPlayerSwift/MonitorTests.swift index e88dc607..033b0278 100644 --- a/Tests/MuxPlayerSwift/MonitorTests.swift +++ b/Tests/MuxPlayerSwift/MonitorTests.swift @@ -145,6 +145,118 @@ class MonitorTests: XCTestCase { PlayerSDK.shared.monitor.bindings.removeAll() } + func validate( + context: TestPlayerObservationContext, + monitorCallIndex: Int = 0, + playerReference: NSObject, + automaticErrorTracking: Bool + ) { + let monitorCall = context.monitorCalls[monitorCallIndex] + + // Validate player reference + XCTAssertEqual( + monitorCall.player, + playerReference + ) + + // Validate automatic error tracking + XCTAssertEqual( + monitorCall.automaticErrorTracking, + automaticErrorTracking + ) + } + + func validate( + context: TestPlayerObservationContext, + monitorCallIndex: Int = 0, + playerReference: NSObject, + automaticErrorTracking: Bool, + expectedPlayerSoftwareName: String, + expectedPlayerSoftwareVersion: String + ) throws { + let monitorCall = context.monitorCalls[monitorCallIndex] + + // Validate player reference + XCTAssertEqual( + monitorCall.player, + playerReference + ) + + // Validate automatic error tracking + XCTAssertEqual( + monitorCall.automaticErrorTracking, + automaticErrorTracking + ) + + // Validate player software name and version + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + XCTAssertEqual( + playerSoftwareName, + expectedPlayerSoftwareName + ) + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + XCTAssertEqual( + playerSoftwareVersion, + expectedPlayerSoftwareVersion + ) + } + + func validate( + context: TestPlayerObservationContext, + monitorCallIndex: Int = 0, + expectedPlayerName: String + ) throws { + let monitorCall = context.monitorCalls[monitorCallIndex] + + // Validate player name (used by Mux Data as key) + let environmentKey = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.environmentKey + ) + XCTAssertEqual( + monitorCall.playerName, + expectedPlayerName + ) + } + + func validate( + context: TestPlayerObservationContext, + monitorCallIndex: Int = 0, + expectedEnvironmentKey: String + ) throws { + let monitorCall = context.monitorCalls[monitorCallIndex] + + // Validate environment key to match custom value + let environmentKey = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.environmentKey + ) + XCTAssertEqual( + environmentKey, + expectedEnvironmentKey + ) + } + + func validate( + context: TestPlayerObservationContext, + monitorCallIndex: Int = 0, + expectedViewDRMType: String + ) throws { + let monitorCall = context.monitorCalls[monitorCallIndex] + + // Validate DRM type + let viewDRMType = try XCTUnwrap( + monitorCall.customerData.customerViewData?.viewDrmType + ) + XCTAssertEqual( + viewDRMType, + "fairplay" + ) + } + func testPlayerViewControllerMonitoringLifecycle() throws { PlayerSDK.shared.monitor = Monitor() @@ -855,34 +967,12 @@ class MonitorTests: XCTestCase { playbackID: "abc123" ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first - ) - - XCTAssertEqual( - monitorCall.player, - playerViewController - ) - - XCTAssertTrue(monitorCall.automaticErrorTracking) - - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerViewController" - ) - - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion - ) - - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString + try validate( + context: testPlayerObservationContext, + playerReference: playerViewController, + automaticErrorTracking: true, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerViewController", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) } @@ -901,48 +991,22 @@ class MonitorTests: XCTestCase { ) ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first - ) - - XCTAssertEqual( - monitorCall.player, - playerViewController - ) - - XCTAssertEqual( - monitorCall.playerName, - "test-player-name" - ) - - XCTAssertTrue(monitorCall.automaticErrorTracking) - - let environmentKey = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.environmentKey - ) - - XCTAssertEqual( - environmentKey, - "xyz321" - ) - - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerViewController" + try validate( + context: testPlayerObservationContext, + playerReference: playerViewController, + automaticErrorTracking: true, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerViewController", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + try validate( + context: testPlayerObservationContext, + expectedPlayerName: "test-player-name" ) - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString + try validate( + context: testPlayerObservationContext, + expectedEnvironmentKey: "xyz321" ) } @@ -957,34 +1021,12 @@ class MonitorTests: XCTestCase { playbackID: "abc123" ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first - ) - - XCTAssertEqual( - monitorCall.player, - playerLayer - ) - - XCTAssertTrue(monitorCall.automaticErrorTracking) - - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerLayer" - ) - - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion - ) - - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString + try validate( + context: testPlayerObservationContext, + playerReference: playerLayer, + automaticErrorTracking: true, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerLayer", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) } @@ -1004,50 +1046,22 @@ class MonitorTests: XCTestCase { ) ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first - ) - - XCTAssertEqual( - monitorCall.player, - playerLayer - ) - - XCTAssertEqual( - monitorCall.playerName, - "test-player-name" - ) - - XCTAssertTrue(monitorCall.automaticErrorTracking) - - let environmentKey = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.environmentKey - ) - - XCTAssertEqual( - environmentKey, - "xyz321" + try validate( + context: testPlayerObservationContext, + playerReference: playerLayer, + automaticErrorTracking: true, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerLayer", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) - XCTAssertTrue(monitorCall.automaticErrorTracking) - - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerLayer" - ) - - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + try validate( + context: testPlayerObservationContext, + expectedPlayerName: "test-player-name" ) - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString + try validate( + context: testPlayerObservationContext, + expectedEnvironmentKey: "xyz321" ) } @@ -1066,45 +1080,17 @@ class MonitorTests: XCTestCase { ) ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first + try validate( + context: testPlayerObservationContext, + playerReference: playerViewController, + automaticErrorTracking: false, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerViewController", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) - XCTAssertFalse(monitorCall.automaticErrorTracking) - - XCTAssertEqual( - monitorCall.player, - playerViewController - ) - - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerViewController" - ) - - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion - ) - - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString - ) - - // Validate DRM flag - - let viewDRMType = try XCTUnwrap( - monitorCall.customerData.customerViewData?.viewDrmType - ) - - XCTAssertEqual( - viewDRMType, - "fairplay" + try validate( + context: testPlayerObservationContext, + expectedViewDRMType: "fairplay" ) } @@ -1120,48 +1106,34 @@ class MonitorTests: XCTestCase { playbackOptions: PlaybackOptions( playbackToken: "def456", drmToken: "ghi789" + ), + monitoringOptions: MonitoringOptions( + environmentKey: "xyz321", + playerName: "test-player-name" ) ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first + try validate( + context: testPlayerObservationContext, + playerReference: playerViewController, + automaticErrorTracking: false, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerViewController", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) - XCTAssertFalse(monitorCall.automaticErrorTracking) - - XCTAssertEqual( - monitorCall.player, - playerViewController - ) - - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerViewController" + try validate( + context: testPlayerObservationContext, + expectedPlayerName: "test-player-name" ) - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + try validate( + context: testPlayerObservationContext, + expectedEnvironmentKey: "xyz321" ) - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString - ) - - // Validate DRM flag - - let viewDRMType = try XCTUnwrap( - monitorCall.customerData.customerViewData?.viewDrmType - ) - - XCTAssertEqual( - viewDRMType, - "fairplay" + try validate( + context: testPlayerObservationContext, + expectedViewDRMType: "fairplay" ) } @@ -1180,45 +1152,17 @@ class MonitorTests: XCTestCase { ) ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first + try validate( + context: testPlayerObservationContext, + playerReference: playerLayer, + automaticErrorTracking: false, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerLayer", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) - // Validate player reference - XCTAssertEqual( - monitorCall.player, - playerLayer - ) - - // Validate automatic error tracking - XCTAssertFalse(monitorCall.automaticErrorTracking) - - // Validate customer data fields passed along to Mux Data - - // Validate player software name and version - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerLayer" - ) - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion - ) - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString - ) - - // Validate DRM flag - let viewDRMType = try XCTUnwrap( - monitorCall.customerData.customerViewData?.viewDrmType - ) - XCTAssertEqual( - viewDRMType, - "fairplay" + try validate( + context: testPlayerObservationContext, + expectedViewDRMType: "fairplay" ) } @@ -1241,58 +1185,27 @@ class MonitorTests: XCTestCase { ) ) - let monitorCall = try XCTUnwrap( - testPlayerObservationContext.monitorCalls.first + try validate( + context: testPlayerObservationContext, + playerReference: playerLayer, + automaticErrorTracking: false, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerLayer", + expectedPlayerSoftwareVersion: SemanticVersion.versionString ) - XCTAssertEqual( - monitorCall.player, - playerLayer + try validate( + context: testPlayerObservationContext, + expectedPlayerName: "test-player-name" ) - XCTAssertEqual( - monitorCall.playerName, - "test-player-name" + try validate( + context: testPlayerObservationContext, + expectedEnvironmentKey: "xyz321" ) - XCTAssertFalse(monitorCall.automaticErrorTracking) - - let environmentKey = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.environmentKey - ) - XCTAssertEqual( - environmentKey, - "xyz321" - ) - - let playerSoftwareName = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareName - ) - - XCTAssertEqual( - playerSoftwareName, - "MuxPlayerSwiftAVPlayerLayer" - ) - - - let playerSoftwareVersion = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.playerSoftwareVersion - ) - - XCTAssertEqual( - playerSoftwareVersion, - SemanticVersion.versionString - ) - - // Validate DRM flag - - let viewDRMType = try XCTUnwrap( - monitorCall.customerData.customerViewData?.viewDrmType - ) - - XCTAssertEqual( - viewDRMType, - "fairplay" + try validate( + context: testPlayerObservationContext, + expectedViewDRMType: "fairplay" ) } From 24d4b76d89c07c606a20f10ed848d78685111124 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Wed, 11 Sep 2024 16:02:30 -0700 Subject: [PATCH 30/32] Improve defensive copying Make a defensive copy of MUXSDKCustomerData when it is provided. Since it is a reference, this prevents alterations the SDK makes to bubble up to client code. As well as guards against the player software name and version from being altered after they're set. Improve readability. Add customer data tests to validate dimensions are passed through and check for pointer equality --- .../MuxPlayerSwift/Monitoring/Monitor.swift | 77 ++++-- Tests/MuxPlayerSwift/MonitorTests.swift | 230 +++++++++++++++++- 2 files changed, 285 insertions(+), 22 deletions(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 56baa7bb..95db1056 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -100,36 +100,44 @@ class Monitor: ErrorDispatcher { let modifiedCustomerData = MUXSDKCustomerData() if let customerVideoData = externallySpecifiedCustomerData.customerVideoData { - let customerVideoDataCopy = MUXSDKCustomerVideoData() - customerVideoDataCopy.setQuery(customerVideoData.toQuery()) - modifiedCustomerData.customerVideoData = customerVideoData + let copiedCustomerVideoDataCopy = MUXSDKCustomerVideoData() + copiedCustomerVideoDataCopy.setQuery(customerVideoData.toQuery()) + modifiedCustomerData.customerVideoData = copiedCustomerVideoDataCopy } if let customerViewData = externallySpecifiedCustomerData.customerViewData { - let customerViewDataCopy = MUXSDKCustomerViewData() - customerViewDataCopy.setQuery(customerViewData.toQuery()) - modifiedCustomerData.customerViewData = customerViewData + let copiedCustomerViewData = MUXSDKCustomerViewData() + copiedCustomerViewData.setQuery(customerViewData.toQuery()) + modifiedCustomerData.customerViewData = copiedCustomerViewData } if let customerViewerData = externallySpecifiedCustomerData.customerViewerData { - let customerViewerDataCopy = MUXSDKCustomerViewerData() - customerViewerDataCopy.viewerApplicationName = customerViewerData.viewerApplicationName - customerViewerDataCopy.viewerDeviceCategory = customerViewerData.viewerDeviceCategory - customerViewerDataCopy.viewerDeviceManufacturer = customerViewerData.viewerDeviceManufacturer - customerViewerDataCopy.viewerDeviceModel = customerViewerData.viewerDeviceModel - customerViewerDataCopy.viewerOsFamily = customerViewerData.viewerOsFamily - customerViewerDataCopy.viewerOsVersion = customerViewerData.viewerOsVersion + let copiedCustomerViewerData = MUXSDKCustomerViewerData() + copiedCustomerViewerData.viewerApplicationName = customerViewerData.viewerApplicationName + copiedCustomerViewerData.viewerDeviceCategory = customerViewerData.viewerDeviceCategory + copiedCustomerViewerData.viewerDeviceManufacturer = customerViewerData.viewerDeviceManufacturer + copiedCustomerViewerData.viewerDeviceModel = customerViewerData.viewerDeviceModel + copiedCustomerViewerData.viewerOsFamily = customerViewerData.viewerOsFamily + copiedCustomerViewerData.viewerOsVersion = customerViewerData.viewerOsVersion modifiedCustomerData.customerViewerData = customerViewerData } + if let customData = externallySpecifiedCustomerData.customData { + let copiedCustomData = MUXSDKCustomData() + copiedCustomData.setQuery( + customData.toQuery() + ) + modifiedCustomerData.customData = copiedCustomData + } + if let customerPlayerData = externallySpecifiedCustomerData.customerPlayerData { - let customerPlayerDataCopy = MUXSDKCustomerPlayerData() - customerPlayerDataCopy.setQuery( + let copiedCustomerPlayerData = MUXSDKCustomerPlayerData() + copiedCustomerPlayerData.setQuery( customerPlayerData.toQuery() ) - customerPlayerDataCopy.playerSoftwareVersion = SemanticVersion.versionString - customerPlayerDataCopy.playerSoftwareName = "MuxPlayerSwiftAVPlayerViewController" - modifiedCustomerData.customerPlayerData = customerPlayerDataCopy + copiedCustomerPlayerData.playerSoftwareVersion = SemanticVersion.versionString + copiedCustomerPlayerData.playerSoftwareName = "MuxPlayerSwiftAVPlayerViewController" + modifiedCustomerData.customerPlayerData = copiedCustomerPlayerData } else { let customerPlayerData = MUXSDKCustomerPlayerData() customerPlayerData.playerSoftwareVersion = SemanticVersion.versionString @@ -252,13 +260,44 @@ class Monitor: ErrorDispatcher { let modifiedCustomerData = MUXSDKCustomerData() + if let customerVideoData = externallySpecifiedCustomerData.customerVideoData { + let copiedCustomerVideoData = MUXSDKCustomerVideoData() + copiedCustomerVideoData.setQuery(customerVideoData.toQuery()) + modifiedCustomerData.customerVideoData = copiedCustomerVideoData + } + + if let customerViewData = externallySpecifiedCustomerData.customerViewData { + let copiedCustomerViewData = MUXSDKCustomerViewData() + copiedCustomerViewData.setQuery(customerViewData.toQuery()) + modifiedCustomerData.customerViewData = copiedCustomerViewData + } + + if let customerViewerData = externallySpecifiedCustomerData.customerViewerData { + let copiedCustomerViewerData = MUXSDKCustomerViewerData() + copiedCustomerViewerData.viewerApplicationName = customerViewerData.viewerApplicationName + copiedCustomerViewerData.viewerDeviceCategory = customerViewerData.viewerDeviceCategory + copiedCustomerViewerData.viewerDeviceManufacturer = customerViewerData.viewerDeviceManufacturer + copiedCustomerViewerData.viewerDeviceModel = customerViewerData.viewerDeviceModel + copiedCustomerViewerData.viewerOsFamily = customerViewerData.viewerOsFamily + copiedCustomerViewerData.viewerOsVersion = customerViewerData.viewerOsVersion + modifiedCustomerData.customerViewerData = customerViewerData + } + + if let customData = externallySpecifiedCustomerData.customData { + let copiedCustomData = MUXSDKCustomData() + copiedCustomData.setQuery( + customData.toQuery() + ) + modifiedCustomerData.customData = copiedCustomData + } + if let customerPlayerData = externallySpecifiedCustomerData.customerPlayerData { let customerPlayerDataCopy = MUXSDKCustomerPlayerData() customerPlayerDataCopy.setQuery( customerPlayerData.toQuery() ) customerPlayerDataCopy.playerSoftwareVersion = SemanticVersion.versionString - customerPlayerData.playerSoftwareName = "MuxPlayerSwiftAVPlayerLayer" + customerPlayerDataCopy.playerSoftwareName = "MuxPlayerSwiftAVPlayerLayer" modifiedCustomerData.customerPlayerData = customerPlayerDataCopy } else { let customerPlayerData = MUXSDKCustomerPlayerData() diff --git a/Tests/MuxPlayerSwift/MonitorTests.swift b/Tests/MuxPlayerSwift/MonitorTests.swift index 033b0278..78e6d322 100644 --- a/Tests/MuxPlayerSwift/MonitorTests.swift +++ b/Tests/MuxPlayerSwift/MonitorTests.swift @@ -214,9 +214,6 @@ class MonitorTests: XCTestCase { let monitorCall = context.monitorCalls[monitorCallIndex] // Validate player name (used by Mux Data as key) - let environmentKey = try XCTUnwrap( - monitorCall.customerData.customerPlayerData?.environmentKey - ) XCTAssertEqual( monitorCall.playerName, expectedPlayerName @@ -257,6 +254,113 @@ class MonitorTests: XCTestCase { ) } + func validate( + context: TestPlayerObservationContext, + monitorCallIndex: Int = 0, + playerReference: NSObject, + providedCustomerData: MUXSDKCustomerData, + expectedPlayerSoftwareName: String, + expectedPlayerSoftwareVersion: String, + expectedEnvironmentKey: String, + expectedPlayerName: String, + expectedPlayerVersion: String, + expectedVideoTitle: String, + expectedSessionID: String, + expectedViewerApplicationName: String, + expectedCustomData1: String + ) throws { + let monitorCall = context.monitorCalls[monitorCallIndex] + + XCTAssertNotEqual( + providedCustomerData, + monitorCall.customerData + ) + + XCTAssertNotEqual( + providedCustomerData.customerPlayerData, + monitorCall.customerData.customerPlayerData + ) + + // Validate no DRM type set + XCTAssertNil( + monitorCall.customerData.customerViewData?.viewDrmType + ) + + // Validate player software name and version + let playerSoftwareName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareName + ) + XCTAssertEqual( + playerSoftwareName, + expectedPlayerSoftwareName + ) + + let playerSoftwareVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerSoftwareVersion + ) + XCTAssertEqual( + playerSoftwareVersion, + expectedPlayerSoftwareVersion + ) + + // Validate custom dimensions passed through + let environmentKey = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.environmentKey + ) + XCTAssertEqual( + environmentKey, + expectedEnvironmentKey + ) + + let playerName = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerName + ) + XCTAssertEqual( + playerName, + expectedPlayerName + ) + + let playerVersion = try XCTUnwrap( + monitorCall.customerData.customerPlayerData?.playerVersion + ) + XCTAssertEqual( + playerVersion, + expectedPlayerVersion + ) + + let videoTitle = try XCTUnwrap( + monitorCall.customerData.customerVideoData?.videoTitle + ) + XCTAssertEqual( + videoTitle, + expectedVideoTitle + ) + + let videoSessionID = try XCTUnwrap( + monitorCall.customerData.customerViewData?.viewSessionId + ) + XCTAssertEqual( + videoSessionID, + expectedSessionID + ) + + let viewerApplicationName = try XCTUnwrap( + monitorCall.customerData.customerViewerData?.viewerApplicationName + ) + XCTAssertEqual( + viewerApplicationName, + expectedViewerApplicationName + ) + + let customData1 = try XCTUnwrap( + monitorCall.customerData.customData?.customData1 + ) + XCTAssertEqual( + customData1, + expectedCustomData1 + ) + } + func testPlayerViewControllerMonitoringLifecycle() throws { PlayerSDK.shared.monitor = Monitor() @@ -1010,6 +1114,66 @@ class MonitorTests: XCTestCase { ) } + func testPlayerMonitoringInputs_PlayerViewController_NonDRM_CustomCustomerData() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let customerPlayerData = MUXSDKCustomerPlayerData() + customerPlayerData.environmentKey = "wuv654" + customerPlayerData.experimentName = "experiment-42" + customerPlayerData.playerName = "my-shiny-player" + customerPlayerData.playerVersion = "v1.0.0" + + let customerVideoData = MUXSDKCustomerVideoData() + customerVideoData.videoTitle = "my-video" + + let customerViewData = MUXSDKCustomerViewData() + customerViewData.viewSessionId = "abcdefghi" + + let customData = MUXSDKCustomData() + customData.customData1 = "baz" + + let customViewerData = MUXSDKCustomerViewerData() + customViewerData.viewerApplicationName = "FooBarApp" + + let customerData = try XCTUnwrap( + MUXSDKCustomerData( + customerPlayerData: customerPlayerData, + videoData: customerVideoData, + viewData: customerViewData, + customData: customData, + viewerData: customViewerData + ) + ) + + let playerViewController = AVPlayerViewController( + playbackID: "abc123", + monitoringOptions: MonitoringOptions( + customerData: customerData, + playerName: "test-player-name-custom-data" + ) + ) + + try validate( + context: testPlayerObservationContext, + playerReference: playerViewController, + providedCustomerData: customerData, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerViewController", + expectedPlayerSoftwareVersion: SemanticVersion.versionString, + expectedEnvironmentKey: "wuv654", + expectedPlayerName: "my-shiny-player", + expectedPlayerVersion: "v1.0.0", + expectedVideoTitle: "my-video", + expectedSessionID: "abcdefghi", + expectedViewerApplicationName: "FooBarApp", + expectedCustomData1: "baz" + ) + + } + func testPlayerMonitoringInputs_PlayerLayer_NonDRM() throws { let testPlayerObservationContext = TestPlayerObservationContext() let testMonitor = Monitor( @@ -1065,6 +1229,66 @@ class MonitorTests: XCTestCase { ) } + func testPlayerMonitoringInputs_PlayerLayer_NonDRM_CustomCustomerData() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let customerPlayerData = MUXSDKCustomerPlayerData() + customerPlayerData.environmentKey = "wuv654" + customerPlayerData.experimentName = "experiment-42" + customerPlayerData.playerName = "my-shiny-player" + customerPlayerData.playerVersion = "v1.0.0" + + let customerVideoData = MUXSDKCustomerVideoData() + customerVideoData.videoTitle = "my-video" + + let customerViewData = MUXSDKCustomerViewData() + customerViewData.viewSessionId = "abcdefghi" + + let customData = MUXSDKCustomData() + customData.customData1 = "baz" + + let customViewerData = MUXSDKCustomerViewerData() + customViewerData.viewerApplicationName = "FooBarApp" + + let customerData = try XCTUnwrap( + MUXSDKCustomerData( + customerPlayerData: customerPlayerData, + videoData: customerVideoData, + viewData: customerViewData, + customData: customData, + viewerData: customViewerData + ) + ) + + let playerLayer = AVPlayerLayer( + playbackID: "abc123", + playbackOptions: PlaybackOptions(), + monitoringOptions: MonitoringOptions( + customerData: customerData, + playerName: "test-player-name-custom-data" + ) + ) + + try validate( + context: testPlayerObservationContext, + playerReference: playerLayer, + providedCustomerData: customerData, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerLayer", + expectedPlayerSoftwareVersion: SemanticVersion.versionString, + expectedEnvironmentKey: "wuv654", + expectedPlayerName: "my-shiny-player", + expectedPlayerVersion: "v1.0.0", + expectedVideoTitle: "my-video", + expectedSessionID: "abcdefghi", + expectedViewerApplicationName: "FooBarApp", + expectedCustomData1: "baz" + ) + } + func testPlayerMonitoringInputs_PlayerViewController_DRM() throws { let testPlayerObservationContext = TestPlayerObservationContext() let testMonitor = Monitor( From d19ab545cec814cb025436d53f4ed94d2287a5c0 Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Thu, 12 Sep 2024 14:40:09 -0700 Subject: [PATCH 31/32] tests clean-up --- .../MuxPlayerSwift/Helpers/TestMonitor.swift | 49 ++ .../TestPlayerObservationContext.swift | 78 +++ Tests/MuxPlayerSwift/MonitorTests.swift | 490 +++++++----------- 3 files changed, 314 insertions(+), 303 deletions(-) create mode 100644 Tests/MuxPlayerSwift/Helpers/TestMonitor.swift create mode 100644 Tests/MuxPlayerSwift/Helpers/TestPlayerObservationContext.swift diff --git a/Tests/MuxPlayerSwift/Helpers/TestMonitor.swift b/Tests/MuxPlayerSwift/Helpers/TestMonitor.swift new file mode 100644 index 00000000..15fcc279 --- /dev/null +++ b/Tests/MuxPlayerSwift/Helpers/TestMonitor.swift @@ -0,0 +1,49 @@ +// +// TestMonitor.swift +// + +import AVKit +import Foundation +import XCTest + +@testable import MuxPlayerSwift + +class TestMonitor: Monitor { + var monitoringRegistrations: [(MonitoringOptions, Bool)] = [] + + override func setupMonitoring( + playerViewController: AVPlayerViewController, + playbackID: String, + options: MonitoringOptions, + usingDRM: Bool = false + ) { + super.setupMonitoring( + playerViewController: playerViewController, + playbackID: playbackID, + options: options, + usingDRM: usingDRM + ) + + monitoringRegistrations.append( + (options, usingDRM) + ) + } + + override func setupMonitoring( + playerLayer: AVPlayerLayer, + playbackID: String, + options: MonitoringOptions, + usingDRM: Bool = false + ) { + super.setupMonitoring( + playerLayer: playerLayer, + playbackID: playbackID, + options: options, + usingDRM: usingDRM + ) + + monitoringRegistrations.append( + (options, usingDRM) + ) + } +} diff --git a/Tests/MuxPlayerSwift/Helpers/TestPlayerObservationContext.swift b/Tests/MuxPlayerSwift/Helpers/TestPlayerObservationContext.swift new file mode 100644 index 00000000..2eda1d4a --- /dev/null +++ b/Tests/MuxPlayerSwift/Helpers/TestPlayerObservationContext.swift @@ -0,0 +1,78 @@ +// +// TestPlayerObservationContext.swift +// + +import Foundation + +import MUXSDKStats + +@testable import MuxPlayerSwift + +class TestPlayerObservationContext: PlayerObservationContext { + + struct MonitorCall { + // AVPlayerViewController or AVPlayerLayer or AVPlayer + // Only included to validate pointer equality, for + // which NSObject suffices + let player: NSObject + let playerName: String + let customerData: MUXSDKCustomerData + let automaticErrorTracking: Bool + } + + var monitorCalls: [MonitorCall] = [] + + override func monitorAVPlayerViewController( + _ player: AVPlayerViewController, + withPlayerName name: String, + customerData: MUXSDKCustomerData, + automaticErrorTracking: Bool + ) -> MUXSDKPlayerBinding? { + + monitorCalls.append( + MonitorCall( + player: player, + playerName: name, + customerData: customerData, + automaticErrorTracking: automaticErrorTracking + ) + ) + + // In its current state this test class is only + // useful for validating Mux Data inputs. + // + // This needs to return a non-nil value in order to + // validate side effects in this SDK. + return nil + } + + override func monitorAVPlayerLayer( + _ player: AVPlayerLayer, + withPlayerName name: String, + customerData: MUXSDKCustomerData, + automaticErrorTracking: Bool + ) -> MUXSDKPlayerBinding? { + + monitorCalls.append( + MonitorCall( + player: player, + playerName: name, + customerData: customerData, + automaticErrorTracking: automaticErrorTracking + ) + ) + + // In its current state this test class is only + // useful for validating Mux Data inputs. + // + // This needs to return a non-nil value in order to + // validate side effects in this SDK. + return nil + } + + override func destroyPlayer( + _ name: String + ) { + + } +} diff --git a/Tests/MuxPlayerSwift/MonitorTests.swift b/Tests/MuxPlayerSwift/MonitorTests.swift index 78e6d322..ee0f3712 100644 --- a/Tests/MuxPlayerSwift/MonitorTests.swift +++ b/Tests/MuxPlayerSwift/MonitorTests.swift @@ -25,124 +25,39 @@ class PlayerLayerBackedView: UIView { } } -class TestMonitor: Monitor { - var monitoringRegistrations: [(MonitoringOptions, Bool)] = [] - - override func setupMonitoring( - playerViewController: AVPlayerViewController, - playbackID: String, - options: MonitoringOptions, - usingDRM: Bool = false - ) { - super.setupMonitoring( - playerViewController: playerViewController, - playbackID: playbackID, - options: options, - usingDRM: usingDRM - ) +class MonitorTests: XCTestCase { - monitoringRegistrations.append( - (options, usingDRM) - ) + override func setUp() { + super.setUp() + PlayerSDK.shared.monitor.bindings.removeAll() } - override func setupMonitoring( - playerLayer: AVPlayerLayer, - playbackID: String, - options: MonitoringOptions, - usingDRM: Bool = false - ) { - super.setupMonitoring( - playerLayer: playerLayer, - playbackID: playbackID, - options: options, - usingDRM: usingDRM - ) + func validate( + monitor: Monitor, + player: AVPlayer, + expectedKVORegistrationsCountForPlayer: Int = 1, + expectedTotalKVORegistrations: Int = 1 + ) throws { - monitoringRegistrations.append( - (options, usingDRM) + XCTAssertEqual( + monitor + .keyValueObservation + .observations + .count, + expectedTotalKVORegistrations ) - } -} - -class TestPlayerObservationContext: PlayerObservationContext { - struct MonitorCall { - // AVPlayerViewController or AVPlayerLayer or AVPlayer - // Only included to validate pointer equality, for - // which NSObject suffices - let player: NSObject - let playerName: String - let customerData: MUXSDKCustomerData - let automaticErrorTracking: Bool - } - - var monitorCalls: [MonitorCall] = [] - - override func monitorAVPlayerViewController( - _ player: AVPlayerViewController, - withPlayerName name: String, - customerData: MUXSDKCustomerData, - automaticErrorTracking: Bool - ) -> MUXSDKPlayerBinding? { - - monitorCalls.append( - MonitorCall( - player: player, - playerName: name, - customerData: customerData, - automaticErrorTracking: automaticErrorTracking - ) + let observations = try XCTUnwrap( + monitor + .keyValueObservation + .observations[ + ObjectIdentifier(player) + ] ) - - // In its current state this test class is only - // useful for validating Mux Data inputs. - // - // This needs to return a non-nil value in order to - // validate side effects in this SDK. - return nil - } - - override func monitorAVPlayerLayer( - _ player: AVPlayerLayer, - withPlayerName name: String, - customerData: MUXSDKCustomerData, - automaticErrorTracking: Bool - ) -> MUXSDKPlayerBinding? { - - monitorCalls.append( - MonitorCall( - player: player, - playerName: name, - customerData: customerData, - automaticErrorTracking: automaticErrorTracking - ) + XCTAssertEqual( + observations.count, + expectedKVORegistrationsCountForPlayer ) - - // In its current state this test class is only - // useful for validating Mux Data inputs. - // - // This needs to return a non-nil value in order to - // validate side effects in this SDK. - return nil -// return MUXSDKPlayerBinding( -// name: "", -// andSoftware: "" -// ) - } - - override func destroyPlayer( - _ name: String - ) { - - } -} - -class MonitorTests: XCTestCase { - - override func setUp() { - super.setUp() - PlayerSDK.shared.monitor.bindings.removeAll() } func validate( @@ -520,40 +435,20 @@ class MonitorTests: XCTestCase { testMonitor.monitoringRegistrations.removeAll() } - func testInitializedPlayerViewControllerKVORegistrationNonDRM() throws { + func testInitializedPlayerViewController_KVORegistration_NonDRM() throws { PlayerSDK.shared.monitor = Monitor() let playerViewController = AVPlayerViewController( playbackID: "abc123" ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerViewController.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 1 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player ) let playerViewControllerObjectIdentifier = try XCTUnwrap( @@ -585,40 +480,20 @@ class MonitorTests: XCTestCase { ) } - func testInitializedPlayerLayerKVORegistrationNonDRM() throws { + func testInitializedPlayerLayer_KVORegistration_NonDRM() throws { PlayerSDK.shared.monitor = Monitor() let playerLayer = AVPlayerLayer( playbackID: "abc123" ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerLayer.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 1 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player ) let playerLayerObjectIdentifier = try XCTUnwrap( @@ -650,7 +525,7 @@ class MonitorTests: XCTestCase { ) } - func testPreexistingPlayerViewControllerKVORegistrationNonDRM() throws { + func testPreexistingPlayerViewController_KVORegistration_NonDRM() throws { PlayerSDK.shared.monitor = Monitor() let playerViewController = AVPlayerViewController() @@ -658,33 +533,13 @@ class MonitorTests: XCTestCase { playbackID: "abc123" ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerViewController.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 1 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player ) let playerViewControllerObjectIdentifier = try XCTUnwrap( @@ -716,7 +571,7 @@ class MonitorTests: XCTestCase { ) } - func testPreexistingPlayerLayerKVORegistrationNonDRM() throws { + func testPreexistingPlayerLayer_KVORegistration_NonDRM() throws { PlayerSDK.shared.monitor = Monitor() let playerLayer = AVPlayerLayer() @@ -724,33 +579,13 @@ class MonitorTests: XCTestCase { playbackID: "abc123" ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerLayer.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 1 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player ) let playerLayerObjectIdentifier = try XCTUnwrap( @@ -782,7 +617,7 @@ class MonitorTests: XCTestCase { ) } - func testInitializedPlayerViewControllerKVORegistrationDRM() throws { + func testInitializedPlayerViewController_KVORegistration_DRM() throws { PlayerSDK.shared.monitor = Monitor() let playerViewController = AVPlayerViewController( @@ -793,33 +628,14 @@ class MonitorTests: XCTestCase { ) ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerViewController.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 2 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player, + expectedKVORegistrationsCountForPlayer: 2 ) let playerViewControllerObjectIdentifier = try XCTUnwrap( @@ -851,7 +667,7 @@ class MonitorTests: XCTestCase { ) } - func testInitializedPlayerLayerKVORegistrationDRM() throws { + func testInitializedPlayerLayer_KVORegistration_DRM() throws { PlayerSDK.shared.monitor = Monitor() let playerLayer = AVPlayerLayer( @@ -862,33 +678,14 @@ class MonitorTests: XCTestCase { ) ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerLayer.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 2 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player, + expectedKVORegistrationsCountForPlayer: 2 ) let playerLayerObjectIdentifier = try XCTUnwrap( @@ -920,7 +717,7 @@ class MonitorTests: XCTestCase { ) } - func testPreexistingPlayerViewControllerKVORegistrationDRM() throws { + func testPreexistingPlayerViewController_KVORegistration_DRM() throws { PlayerSDK.shared.monitor = Monitor() let playerViewController = AVPlayerViewController() @@ -932,33 +729,14 @@ class MonitorTests: XCTestCase { ) ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerViewController.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 2 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player, + expectedKVORegistrationsCountForPlayer: 2 ) let playerViewControllerObjectIdentifier = try XCTUnwrap( @@ -990,7 +768,7 @@ class MonitorTests: XCTestCase { ) } - func testPreexistingPlayerLayerKVORegistrationDRM() throws { + func testPreexistingPlayerLayer_KVORegistration_DRM() throws { PlayerSDK.shared.monitor = Monitor() let playerLayer = AVPlayerLayer() @@ -1002,33 +780,14 @@ class MonitorTests: XCTestCase { ) ) - XCTAssertEqual( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations - .count, - 1 - ) - let player = try XCTUnwrap( playerLayer.player ) - let observations = try XCTUnwrap( - PlayerSDK - .shared - .monitor - .keyValueObservation - .observations[ - ObjectIdentifier(player) - ] - ) - - XCTAssertEqual( - observations.count, - 2 + try validate( + monitor: PlayerSDK.shared.monitor, + player: player, + expectedKVORegistrationsCountForPlayer: 2 ) let playerLayerObjectIdentifier = try XCTUnwrap( @@ -1171,7 +930,6 @@ class MonitorTests: XCTestCase { expectedViewerApplicationName: "FooBarApp", expectedCustomData1: "baz" ) - } func testPlayerMonitoringInputs_PlayerLayer_NonDRM() throws { @@ -1361,6 +1119,69 @@ class MonitorTests: XCTestCase { ) } + func testPlayerMonitoringInputs_PlayerViewController_DRM_CustomCustomerData() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let customerPlayerData = MUXSDKCustomerPlayerData() + customerPlayerData.environmentKey = "wuv654" + customerPlayerData.experimentName = "experiment-42" + customerPlayerData.playerName = "my-shiny-player" + customerPlayerData.playerVersion = "v1.0.0" + + let customerVideoData = MUXSDKCustomerVideoData() + customerVideoData.videoTitle = "my-video" + + let customerViewData = MUXSDKCustomerViewData() + customerViewData.viewSessionId = "abcdefghi" + + let customData = MUXSDKCustomData() + customData.customData1 = "baz" + + let customViewerData = MUXSDKCustomerViewerData() + customViewerData.viewerApplicationName = "FooBarApp" + + let customerData = try XCTUnwrap( + MUXSDKCustomerData( + customerPlayerData: customerPlayerData, + videoData: customerVideoData, + viewData: customerViewData, + customData: customData, + viewerData: customViewerData + ) + ) + + let playerViewController = AVPlayerViewController( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ), + monitoringOptions: MonitoringOptions( + customerData: customerData, + playerName: "test-player-name-custom-data" + ) + ) + + try validate( + context: testPlayerObservationContext, + playerReference: playerViewController, + providedCustomerData: customerData, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerViewController", + expectedPlayerSoftwareVersion: SemanticVersion.versionString, + expectedEnvironmentKey: "wuv654", + expectedPlayerName: "my-shiny-player", + expectedPlayerVersion: "v1.0.0", + expectedVideoTitle: "my-video", + expectedSessionID: "abcdefghi", + expectedViewerApplicationName: "FooBarApp", + expectedCustomData1: "baz" + ) + } + func testPlayerMonitoringInputs_PlayerLayer_DRM() throws { let testPlayerObservationContext = TestPlayerObservationContext() let testMonitor = Monitor( @@ -1433,6 +1254,69 @@ class MonitorTests: XCTestCase { ) } + func testPlayerMonitoringInputs_PlayerLayer_DRM_CustomCustomerData() throws { + let testPlayerObservationContext = TestPlayerObservationContext() + let testMonitor = Monitor( + playerObservationContext: testPlayerObservationContext + ) + PlayerSDK.shared.monitor = testMonitor + + let customerPlayerData = MUXSDKCustomerPlayerData() + customerPlayerData.environmentKey = "wuv654" + customerPlayerData.experimentName = "experiment-42" + customerPlayerData.playerName = "my-shiny-player" + customerPlayerData.playerVersion = "v1.0.0" + + let customerVideoData = MUXSDKCustomerVideoData() + customerVideoData.videoTitle = "my-video" + + let customerViewData = MUXSDKCustomerViewData() + customerViewData.viewSessionId = "abcdefghi" + + let customData = MUXSDKCustomData() + customData.customData1 = "baz" + + let customViewerData = MUXSDKCustomerViewerData() + customViewerData.viewerApplicationName = "FooBarApp" + + let customerData = try XCTUnwrap( + MUXSDKCustomerData( + customerPlayerData: customerPlayerData, + videoData: customerVideoData, + viewData: customerViewData, + customData: customData, + viewerData: customViewerData + ) + ) + + let playerLayer = AVPlayerLayer( + playbackID: "abc123", + playbackOptions: PlaybackOptions( + playbackToken: "def456", + drmToken: "ghi789" + ), + monitoringOptions: MonitoringOptions( + customerData: customerData, + playerName: "test-player-name-custom-data" + ) + ) + + try validate( + context: testPlayerObservationContext, + playerReference: playerLayer, + providedCustomerData: customerData, + expectedPlayerSoftwareName: "MuxPlayerSwiftAVPlayerLayer", + expectedPlayerSoftwareVersion: SemanticVersion.versionString, + expectedEnvironmentKey: "wuv654", + expectedPlayerName: "my-shiny-player", + expectedPlayerVersion: "v1.0.0", + expectedVideoTitle: "my-video", + expectedSessionID: "abcdefghi", + expectedViewerApplicationName: "FooBarApp", + expectedCustomData1: "baz" + ) + } + // func testPlayerItemUpdates() throws { // PlayerSDK.shared.monitor = Monitor() // From 45f1917f3ebe78429e94b637c63694f8b8635c0f Mon Sep 17 00:00:00 2001 From: AJ Lauer Barinov Date: Fri, 20 Sep 2024 13:14:29 -0700 Subject: [PATCH 32/32] generic error message --- Sources/MuxPlayerSwift/Monitoring/Monitor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift index 95db1056..547079d4 100644 --- a/Sources/MuxPlayerSwift/Monitoring/Monitor.swift +++ b/Sources/MuxPlayerSwift/Monitoring/Monitor.swift @@ -526,7 +526,7 @@ class Monitor: ErrorDispatcher { case 400: return "The URL or playback ID was invalid. You may have used an invalid value as a playback ID." case 403: - return "The video's secured drm-token has expired." + return "The video's secured drm-token is not authorized for this request. It may be expired or a token for another resource." case 404: return "This URL or playback ID does not exist. You may have used an Asset ID or an ID from a different resource." case 412: