diff --git a/Classes/Network/KalturaMultiRequestBuilder.swift b/Classes/Network/KalturaMultiRequestBuilder.swift index 00339297..57d5fc00 100644 --- a/Classes/Network/KalturaMultiRequestBuilder.swift +++ b/Classes/Network/KalturaMultiRequestBuilder.swift @@ -26,7 +26,7 @@ class KalturaMultiRequestBuilder: KalturaRequestBuilder { override public func build() -> Request { let data = self.kalturaMultiRequestData() - let request = RequestElement(requestId: self.requestId, method: self.method, url: self.url, dataBody: data, headers: self.headers, timeout: self.timeout, configuration: self.configuration, completion: self.completion) + let request = RequestElement(requestId: self.requestId, method: self.method, url: self.url, dataBody: data, headers: self.headers, timeout: self.timeout, configuration: self.configuration, responseSerializer: self.responseSerializer, completion: self.completion) return request } diff --git a/Classes/Network/Request.swift b/Classes/Network/Request.swift index 2e793aa0..c83f0daf 100644 --- a/Classes/Network/Request.swift +++ b/Classes/Network/Request.swift @@ -34,6 +34,7 @@ public protocol Request { var timeout: Double { get } var configuration: RequestConfiguration? { get } var completion: completionClosures? { get } + var responseSerializer: ResponseSerializer { get } } public struct RequestElement: Request { @@ -45,6 +46,7 @@ public struct RequestElement: Request { public var headers: [String:String]? public var timeout: Double public var configuration: RequestConfiguration? + public var responseSerializer: ResponseSerializer public var completion: completionClosures? } @@ -62,7 +64,7 @@ public struct RequestElement: Request { public var configuration: RequestConfiguration? = nil public var completion: completionClosures? = nil public var urlParams: [String: String]? = nil - + public var responseSerializer : ResponseSerializer = JSONSerializer() public init?(url: String){ if let path = URL(string: url) { self.url = path @@ -101,6 +103,12 @@ public struct RequestElement: Request { return self } + @discardableResult + public func set(responseSerializer: ResponseSerializer) -> Self{ + self.responseSerializer = responseSerializer + return self + } + @discardableResult public func set(completion:completionClosures?) -> Self{ self.completion = completion @@ -170,7 +178,7 @@ public struct RequestElement: Request { } - return RequestElement(requestId: self.requestId, method:self.method , url: self.url, dataBody: bodyData, headers: self.headers, timeout: self.timeout, configuration: self.configuration, completion: self.completion) + return RequestElement(requestId: self.requestId, method:self.method , url: self.url, dataBody: bodyData, headers: self.headers, timeout: self.timeout, configuration: self.configuration,responseSerializer: self.responseSerializer, completion: self.completion) } } diff --git a/Classes/Network/ResponseSerializer.swift b/Classes/Network/ResponseSerializer.swift new file mode 100644 index 00000000..c6b06567 --- /dev/null +++ b/Classes/Network/ResponseSerializer.swift @@ -0,0 +1,51 @@ +// +// ResponseSerializer.swift +// Pods +// +// Created by Rivka Peleg on 14/03/2017. +// +// + +import Foundation + +enum SerializerError: Error { + case serializationError +} + + +public protocol ResponseSerializer { + /** + This fuction will serialize the response data of certin request to the expected type according to the serializer type + */ + func serialize(data: Data) throws -> Any +} + + + class JSONSerializer: ResponseSerializer { + + func serialize(data: Data) throws -> Any { + let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) + return json + } +} + + + class IntSerializer: ResponseSerializer { + + func serialize(data: Data) throws -> Any { + guard let int8 = [UInt8](data).last else { + throw SerializerError.serializationError + } + + let int: Int = Int(int8) + return int + } +} + + class StringSerializer: ResponseSerializer { + + func serialize(data: Data) throws -> Any { + let string = String(data: data, encoding: .utf8) + return string + } +} diff --git a/Classes/Network/USRExecutor.swift b/Classes/Network/USRExecutor.swift index ddd5ede6..4268deba 100644 --- a/Classes/Network/USRExecutor.swift +++ b/Classes/Network/USRExecutor.swift @@ -78,7 +78,7 @@ import UIKit if let d = data { do { - let json = try JSONSerialization.jsonObject(with: d, options: JSONSerialization.ReadingOptions()) + let json = try r.responseSerializer.serialize(data: d) let result = Response(data: json, error:nil) completion(result) } catch { diff --git a/Classes/Player/Player.swift b/Classes/Player/Player.swift index 3c281ba8..57c1138a 100644 --- a/Classes/Player/Player.swift +++ b/Classes/Player/Player.swift @@ -16,7 +16,7 @@ import AVKit @objc public protocol Player: NSObjectProtocol { - @objc var delegate: PlayerDelegate? { get set } + @objc weak var delegate: PlayerDelegate? { get set } /// The player's associated media entry. weak var mediaEntry: MediaEntry? { get } diff --git a/Classes/Player/PlayerController.swift b/Classes/Player/PlayerController.swift index a2569cb0..2f71b5ae 100644 --- a/Classes/Player/PlayerController.swift +++ b/Classes/Player/PlayerController.swift @@ -14,7 +14,7 @@ class PlayerController: NSObject, Player { var onEventBlock: ((PKEvent)->Void)? - var delegate: PlayerDelegate? + weak var delegate: PlayerDelegate? fileprivate var currentPlayer: AVPlayerEngine fileprivate var assetBuilder: AssetBuilder? diff --git a/Classes/Plugins/BasePlugin.swift b/Classes/Plugins/BasePlugin.swift index 863440e3..67b77a04 100644 --- a/Classes/Plugins/BasePlugin.swift +++ b/Classes/Plugins/BasePlugin.swift @@ -16,8 +16,8 @@ import Foundation fatalError("abstract property should be overriden in subclass") } - @objc public unowned var player: Player - @objc public unowned var messageBus: MessageBus + @objc public weak var player: Player? + @objc public weak var messageBus: MessageBus? @objc public required init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws { PKLog.info("initializing plugin \(type(of:self))") diff --git a/Classes/Plugins/PKPlugin.swift b/Classes/Plugins/PKPlugin.swift index 1559afb8..0887e201 100644 --- a/Classes/Plugins/PKPlugin.swift +++ b/Classes/Plugins/PKPlugin.swift @@ -15,8 +15,9 @@ public protocol PKPlugin { static var pluginName: String { get } /// The player associated with the plugin - unowned var player: Player { get } - unowned var messageBus: MessageBus { get } + weak var player: Player? { get } + /// The messageBus associated with the plugin + weak var messageBus: MessageBus? { get } init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws diff --git a/Classes/WidevineClassicHelper.swift b/Classes/WidevineClassicHelper.swift index e5cff835..377c5856 100644 --- a/Classes/WidevineClassicHelper.swift +++ b/Classes/WidevineClassicHelper.swift @@ -18,12 +18,12 @@ typealias LocalAssetStatusBlock = (Error?, TimeInterval, TimeInterval) -> Void // MARK: - WidevineClassicError /************************************************************/ - enum WidevineClassicError: PKError { + enum WidevineClassicError: PKError { case invalidDRMData case missingWidevineFile - static let Domain = "com.kaltura.playkit.error.drm.widevine" + static let domain = "com.kaltura.playkit.error.drm.widevine" var code: Int { switch self { @@ -48,7 +48,7 @@ typealias LocalAssetStatusBlock = (Error?, TimeInterval, TimeInterval) -> Void } extension PKErrorDomain { - @objc public static let Widevine = WidevineClassicError.Domain + @objc public static let Widevine = WidevineClassicError.domain } extension PKErrorCode { diff --git a/Example/PlayKit/Info.plist b/Example/PlayKit/Info.plist index 5c1ca7e5..f7769bc4 100644 --- a/Example/PlayKit/Info.plist +++ b/Example/PlayKit/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.1.23 + 0.1.25 CFBundleSignature ???? CFBundleVersion diff --git a/PlayKit.podspec b/PlayKit.podspec index 1aefa0d9..04c13c0e 100644 --- a/PlayKit.podspec +++ b/PlayKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'PlayKit' -s.version = '0.1.23' +s.version = '0.1.25' s.summary = 'PlayKit: Kaltura Mobile Player SDK - iOS' diff --git a/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift b/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift index a48e98de..d0ab4d3d 100644 --- a/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift +++ b/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift @@ -10,8 +10,6 @@ import Foundation protocol AnalyticsPluginProtocol: PKPlugin { - unowned var player: Player { get set } - unowned var messageBus: MessageBus { get set } var config: AnalyticsConfig? { get set } var isFirstPlay: Bool { get set } var playerEventsToRegister: [PlayerEvent.Type] { get } diff --git a/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift b/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift index ea890bd2..baf696b1 100644 --- a/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift +++ b/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift @@ -73,8 +73,8 @@ extension PKErrorCode { } public override func destroy() { + self.messageBus?.removeObserver(self, events: playerEventsToRegister) super.destroy() - self.messageBus.removeObserver(self, events: playerEventsToRegister) } /************************************************************/ diff --git a/Plugins/IMA/IMAPlugin.swift b/Plugins/IMA/IMAPlugin.swift index 8d2919b8..43b8c4dc 100644 --- a/Plugins/IMA/IMAPlugin.swift +++ b/Plugins/IMA/IMAPlugin.swift @@ -20,7 +20,6 @@ extension IMAAdsManager { weak var dataSource: AdsPluginDataSource? { didSet { PKLog.debug("data source set") - self.setupMainView() } } weak var delegate: AdsPluginDelegate? @@ -28,7 +27,6 @@ extension IMAAdsManager { private var contentPlayhead: IMAAVPlayerContentPlayhead? private var adsManager: IMAAdsManager? - private var companionSlot: IMACompanionAdSlot? private var renderingSettings: IMAAdsRenderingSettings! = IMAAdsRenderingSettings() private static var loader: IMAAdsLoader! @@ -94,9 +92,9 @@ extension IMAAdsManager { throw PKPluginError.missingPluginConfig(pluginName: IMAPlugin.pluginName) } - self.messageBus.addObserver(self, events: [PlayerEvent.ended], block: { (data: Any) -> Void in - self.contentComplete() - }) + self.messageBus?.addObserver(self, events: [PlayerEvent.ended]) { [weak self] event in + self?.contentComplete() + } self.timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(IMAPlugin.update), userInfo: nil, repeats: true) } @@ -120,11 +118,23 @@ extension IMAAdsManager { /************************************************************/ func requestAds() { + guard let playerView = player?.view else { return } + if self.adTagUrl != nil && self.adTagUrl != "" { self.startAdCalled = false + // setup ad display container and companion if exists, needs to create a new ad container for each request. + var companionAdSlot: IMACompanionAdSlot? = nil + let adDisplayContainer: IMAAdDisplayContainer + if let companionView = self.config?.companionView { + companionAdSlot = IMACompanionAdSlot(view: companionView, width: Int32(companionView.frame.size.width), height: Int32(companionView.frame.size.height)) + adDisplayContainer = IMAAdDisplayContainer(adContainer: playerView, companionSlots: [companionAdSlot]) + } else { + adDisplayContainer = IMAAdDisplayContainer(adContainer: playerView, companionSlots: []) + } + var request: IMAAdsRequest - request = IMAAdsRequest(adTagUrl: self.adTagUrl, adDisplayContainer: self.createAdDisplayContainer(), contentPlayhead: self, userContext: nil) + request = IMAAdsRequest(adTagUrl: self.adTagUrl, adDisplayContainer: adDisplayContainer, contentPlayhead: self, userContext: nil) IMAPlugin.loader.requestAds(with: request) PKLog.trace("request Ads") @@ -176,16 +186,6 @@ extension IMAAdsManager { imaSettings.autoPlayAdBreaks = config.autoPlayAdBreaks IMAPlugin.loader = IMAAdsLoader(settings: imaSettings) } - - private func setupMainView() { -// if let _ = self.player.playerEngine { -// self.pictureInPictureProxy = IMAPictureInPictureProxy(avPictureInPictureControllerDelegate: self) -// } - - if let companionView = self.config?.companionView { - self.companionSlot = IMACompanionAdSlot(view: companionView, width: Int32(companionView.frame.size.width), height: Int32(companionView.frame.size.height)) - } - } private func setupLoadingView() { self.loadingView = UIView(frame: CGRect.zero) @@ -202,16 +202,13 @@ extension IMAAdsManager { self.loadingView!.addConstraint(NSLayoutConstraint(item: self.loadingView!, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: indicator, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)) self.loadingView!.addConstraint(NSLayoutConstraint(item: self.loadingView!, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: indicator, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)) - let videoView = self.player.view - videoView?.addSubview(self.loadingView!) - videoView?.addConstraint(NSLayoutConstraint(item: videoView!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)) - videoView?.addConstraint(NSLayoutConstraint(item: videoView!, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 0)) - videoView?.addConstraint(NSLayoutConstraint(item: videoView!, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)) - videoView?.addConstraint(NSLayoutConstraint(item: videoView!, attribute: NSLayoutAttribute.right, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.right, multiplier: 1, constant: 0)) - } - - private func createAdDisplayContainer() -> IMAAdDisplayContainer { - return IMAAdDisplayContainer(adContainer: self.player.view, companionSlots: self.config?.companionView != nil ? [self.companionSlot!] : nil) + if let videoView = self.player?.view { + videoView.addSubview(self.loadingView!) + videoView.addConstraint(NSLayoutConstraint(item: videoView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)) + videoView.addConstraint(NSLayoutConstraint(item: videoView, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 0)) + videoView.addConstraint(NSLayoutConstraint(item: videoView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)) + videoView.addConstraint(NSLayoutConstraint(item: videoView, attribute: NSLayoutAttribute.right, relatedBy: NSLayoutRelation.equal, toItem: self.loadingView!, attribute: NSLayoutAttribute.right, multiplier: 1, constant: 0)) + } } private func loadAdsIfNeeded() { @@ -251,10 +248,7 @@ extension IMAAdsManager { @objc private func update() { if !self.isAdPlayback { - let currentTime = self.player.currentTime - if currentTime.isNaN { - return - } + guard let currentTime = self.player?.currentTime, !currentTime.isNaN else { return } self.currentPlaybackTime = currentTime self.loadAdsIfNeeded() } @@ -281,12 +275,12 @@ extension IMAAdsManager { self.loadingView!.alpha = alpha self.loadingView!.isHidden = !show - self.player.view?.bringSubview(toFront: self.loadingView!) + self.player?.view?.bringSubview(toFront: self.loadingView!) } private func notify(event: AdEvent) { self.delegate?.adsPlugin(self, didReceive: event) - self.messageBus.post(event) + self.messageBus?.post(event) } private func notifyAdCuePoints(fromAdsManager adsManager: IMAAdsManager) { @@ -324,7 +318,7 @@ extension IMAAdsManager { self.loaderFailed = true self.showLoadingView(false, alpha: 0) PKLog.error(adErrorData.adError.message) - self.messageBus.post(AdEvent.Error(nsError: IMAPluginError(adError: adErrorData.adError).asNSError)) + self.messageBus?.post(AdEvent.Error(nsError: IMAPluginError(adError: adErrorData.adError).asNSError)) self.delegate?.adsPlugin(self, loaderFailedWith: adErrorData.adError.message) } @@ -392,7 +386,7 @@ extension IMAAdsManager { public func adsManager(_ adsManager: IMAAdsManager!, didReceive error: IMAAdError!) { self.showLoadingView(false, alpha: 0) PKLog.error(error.message) - self.messageBus.post(AdEvent.Error(nsError: IMAPluginError(adError: error).asNSError)) + self.messageBus?.post(AdEvent.Error(nsError: IMAPluginError(adError: error).asNSError)) self.delegate?.adsPlugin(self, managerFailedWith: error.message) } diff --git a/Plugins/KalturaLiveStats/KalturaLiveStatsPlugin.swift b/Plugins/KalturaLiveStats/KalturaLiveStatsPlugin.swift index 0cfbe3e3..001a3045 100644 --- a/Plugins/KalturaLiveStats/KalturaLiveStatsPlugin.swift +++ b/Plugins/KalturaLiveStats/KalturaLiveStatsPlugin.swift @@ -63,38 +63,42 @@ public class KalturaLiveStatsPlugin: BaseAnalyticsPlugin { switch event { case let e where e.self == PlayerEvent.play: - self.messageBus.addObserver(self, events: [e.self]){ [unowned self] event in + self.messageBus?.addObserver(self, events: [e.self]){ [weak self] event in + guard let strongSelf = self, let player = self?.player else { return } PKLog.debug("play event: \(event)") - self.lastReportedStartTime = self.player.currentTime.toInt32() - self.startLiveEvents() + strongSelf.lastReportedStartTime = player.currentTime.toInt32() + strongSelf.startLiveEvents() } case let e where e.self == PlayerEvent.pause: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("pause event: \(event)") - self.stopLiveEvents() + strongSelf.stopLiveEvents() } case let e where e.self == PlayerEvent.playbackParamsUpdated: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("playbackParamsUpdated event: \(event)") if type(of: event) == PlayerEvent.playbackParamsUpdated { - self.lastReportedBitrate = Int32(event.currentBitrate!) + strongSelf.lastReportedBitrate = Int32(event.currentBitrate!) } } case let e where e.self == PlayerEvent.stateChanged: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("playbackParamsUpdated event: \(event)") if type(of: event) == PlayerEvent.stateChanged { switch event.newState { case .ready: - self.startTimer() - if self.isBuffering { - self.isBuffering = false - self.sendLiveEvent(theBufferTime: self.calculateBuffer(isBuffering: false)) + strongSelf.startTimer() + if strongSelf.isBuffering { + strongSelf.isBuffering = false + strongSelf.sendLiveEvent(theBufferTime: strongSelf.calculateBuffer(isBuffering: false)) } case .buffering: - self.isBuffering = true - self.bufferStartTime = Date().timeIntervalSince1970.toInt32() + strongSelf.isBuffering = true + strongSelf.bufferStartTime = Date().timeIntervalSince1970.toInt32() default: break } } @@ -134,7 +138,6 @@ public class KalturaLiveStatsPlugin: BaseAnalyticsPlugin { } self.timer = Timer.scheduledTimer(timeInterval: TimeInterval(self.interval), target: self, selector: #selector(KalturaLiveStatsPlugin.timerHit), userInfo: nil, repeats: true) - } @objc private func timerHit() { @@ -176,7 +179,7 @@ public class KalturaLiveStatsPlugin: BaseAnalyticsPlugin { private func sendLiveEvent(theBufferTime: Int32) { PKLog.debug("sendLiveEvent - Buffer Time: \(bufferTime)") - guard let mediaEntry = self.player.mediaEntry else { return } + guard let mediaEntry = self.player?.mediaEntry else { return } var sessionId = "" var baseUrl = "https://stats.kaltura.com/api_v3/index.php" diff --git a/Plugins/KalturaStats/KalturaStatsPlugin.swift b/Plugins/KalturaStats/KalturaStatsPlugin.swift index 3cbe9abc..afef85ae 100644 --- a/Plugins/KalturaStats/KalturaStatsPlugin.swift +++ b/Plugins/KalturaStats/KalturaStatsPlugin.swift @@ -97,65 +97,66 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { switch event { case let e where e.self == PlayerEvent.canPlay: - self.messageBus.addObserver(self, events: [e.self], block: { [unowned self] (event) in + self.messageBus?.addObserver(self, events: [e.self], block: { [weak self] (event) in + guard let strongSelf = self else { return } PKLog.debug("canPlay event: \(event)") - self.sendMediaLoaded() + strongSelf.sendMediaLoaded() }) case let e where e.self == PlayerEvent.ended || e.self == PlayerEvent.pause || e.self == PlayerEvent.seeking: - self.messageBus.addObserver(self, events: [e.self], block: { (event) in + self.messageBus?.addObserver(self, events: [e.self], block: { [weak self] (event) in PKLog.debug("\(e.self) event: \(event)") }) case let e where e.self == PlayerEvent.seeked: - self.messageBus.addObserver(self, events: [e.self], block: { [unowned self] (event) in + self.messageBus?.addObserver(self, events: [e.self], block: { [weak self] (event) in + guard let strongSelf = self, let player = self?.player else { return } PKLog.debug("seeked event: \(event)") - self.hasSeeked = true - self.seekPercent = Float(self.player.currentTime) / Float(self.player.duration) - self.sendAnalyticsEvent(action: .SEEK) + strongSelf.hasSeeked = true + strongSelf.seekPercent = Float(player.currentTime) / Float(player.duration) + strongSelf.sendAnalyticsEvent(action: .SEEK) }) case let e where e.self == PlayerEvent.playing: - self.messageBus.addObserver(self, events: [e.self], block: { [unowned self] (event) in + self.messageBus?.addObserver(self, events: [e.self], block: { [weak self] (event) in + guard let strongSelf = self else { return } PKLog.debug("play event: \(event)") - if self.isFirstPlay { - self.sendAnalyticsEvent(action: .PLAY) - self.isFirstPlay = false + if strongSelf.isFirstPlay { + strongSelf.sendAnalyticsEvent(action: .PLAY) + strongSelf.isFirstPlay = false } }) case let e where e.self == PlayerEvent.error: - self.messageBus.addObserver(self, events: [e.self], block: { [unowned self] (event) in + self.messageBus?.addObserver(self, events: [e.self], block: { [weak self] (event) in + guard let strongSelf = self else { return } PKLog.debug("error event: \(event)") - self.sendAnalyticsEvent(action: .ERROR) + strongSelf.sendAnalyticsEvent(action: .ERROR) }) case let e where e.self == PlayerEvent.stateChanged: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("state changed event: \(event)") if let stateChanged = event as? PlayerEvent.StateChanged { switch stateChanged.newState { case .idle: - self.sendWidgetLoaded() - break + strongSelf.sendWidgetLoaded() case .loading: - self.sendWidgetLoaded() - if self.isBuffering { - self.isBuffering = false - self.sendAnalyticsEvent(action: .BUFFER_END) + strongSelf.sendWidgetLoaded() + if strongSelf.isBuffering { + strongSelf.isBuffering = false + strongSelf.sendAnalyticsEvent(action: .BUFFER_END) } - break case .ready: - if self.isBuffering { - self.isBuffering = false - self.sendAnalyticsEvent(action: .BUFFER_END) + if strongSelf.isBuffering { + strongSelf.isBuffering = false + strongSelf.sendAnalyticsEvent(action: .BUFFER_END) } - if !self.intervalOn { - self.intervalOn = true - self.createTimer() + if !strongSelf.intervalOn { + strongSelf.intervalOn = true + strongSelf.createTimer() } - self.sendMediaLoaded() - break + strongSelf.sendMediaLoaded() case .buffering: - self.sendWidgetLoaded() - self.isBuffering = true - self.sendAnalyticsEvent(action: .BUFFER_START) - break + strongSelf.sendWidgetLoaded() + strongSelf.isBuffering = true + strongSelf.sendAnalyticsEvent(action: .BUFFER_START) case .error: break case .unknown: break } @@ -223,7 +224,8 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { } @objc private func timerHit() { - let progress = Float(self.player.currentTime) / Float(self.player.duration) + guard let player = self.player else { return } + let progress = Float(player.currentTime) / Float(player.duration) PKLog.debug("Progress is \(progress)") if progress >= 0.25 && !playReached25 && seekPercent <= 0.25 { @@ -239,7 +241,6 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { playReached100 = true sendAnalyticsEvent(action: .PLAY_REACHED_100) } - } private func startTimer() { @@ -260,7 +261,7 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { } private func sendAnalyticsEvent(action: KStatsEventType) { - + guard let player = self.player else { return } PKLog.debug("Action: \(action)") var sessionId = "" @@ -284,7 +285,7 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { parterId = String(pId) } - guard let mediaEntry = self.player.mediaEntry else { + guard let mediaEntry = player.mediaEntry else { PKLog.error("send analytics failed due to nil mediaEntry") return } @@ -293,9 +294,9 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { partnerId: parterId, eventType: action.rawValue, clientVer: PlayKitManager.clientTag, - duration: Float(self.player.duration), + duration: Float(player.duration), sessionId: sessionId, - position: self.player.currentTime.toInt32(), + position: player.currentTime.toInt32(), uiConfId: confId, entryId: mediaEntry.id, widgetId: "_\(parterId)", diff --git a/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift b/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift index 84cf23c3..b83bf1ff 100644 --- a/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift +++ b/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift @@ -72,49 +72,53 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt switch event { case let e where e.self == PlayerEvent.ended: - self.messageBus.addObserver(self, events: [e.self], block: { event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("ended event: \(event)") - self.stopTimer() - self.sendAnalyticsEvent(ofType: .finish) - }) + strongSelf.stopTimer() + strongSelf.sendAnalyticsEvent(ofType: .finish) + } case let e where e.self == PlayerEvent.error: - self.messageBus.addObserver(self, events: [e.self], block: { event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("error event: \(event)") - self.sendAnalyticsEvent(ofType: .error) - }) + strongSelf.sendAnalyticsEvent(ofType: .error) + } case let e where e.self == PlayerEvent.pause: - self.messageBus.addObserver(self, events: [e.self], block: { event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("pause event: \(event)") // invalidate timer when receiving pause event only after first play // and set intervalOn to false in order to start timer again on play event. - if !self.isFirstPlay { - self.stopTimer() - self.intervalOn = false + if !strongSelf.isFirstPlay { + strongSelf.stopTimer() + strongSelf.intervalOn = false } - - self.sendAnalyticsEvent(ofType: .pause) - }) + strongSelf.sendAnalyticsEvent(ofType: .pause) + } case let e where e.self == PlayerEvent.loadedMetadata: - self.messageBus.addObserver(self, events: [e.self], block: { event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("loadedMetadata event: \(event)") - self.sendAnalyticsEvent(ofType: .load) - }) + strongSelf.sendAnalyticsEvent(ofType: .load) + } case let e where e.self == PlayerEvent.playing: - self.messageBus.addObserver(self, events: [e.self], block: { event in + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } PKLog.debug("play event: \(event)") - if !self.intervalOn { - self.createTimer() - self.intervalOn = true + if !strongSelf.intervalOn { + strongSelf.createTimer() + strongSelf.intervalOn = true } - if self.isFirstPlay { - self.isFirstPlay = false - self.sendAnalyticsEvent(ofType: .first_play); + if strongSelf.isFirstPlay { + strongSelf.isFirstPlay = false + strongSelf.sendAnalyticsEvent(ofType: .first_play); } else { - self.sendAnalyticsEvent(ofType: .play); + strongSelf.sendAnalyticsEvent(ofType: .play); } - }) + } default: assertionFailure("plugin \(type(of:self)) all events must be handled") } } @@ -144,7 +148,7 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt /************************************************************/ func reportConcurrencyEvent() { - self.messageBus.post(OttEvent.Concurrency()) + self.messageBus?.post(OttEvent.Concurrency()) } } @@ -179,9 +183,10 @@ extension BaseOTTAnalyticsPlugin { } fileprivate func sendProgressEvent() { + guard let player = self.player else { return } self.sendAnalyticsEvent(ofType: .hit); - let progress = Float(self.player.currentTime) / Float(self.player.duration) + let progress = Float(player.currentTime) / Float(player.duration) PKLog.debug("Progress is \(progress)") if progress > 0.98 { diff --git a/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift b/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift index 8e7cf55b..f968ad46 100644 --- a/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift +++ b/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift @@ -38,9 +38,14 @@ public class PhoenixAnalyticsPlugin: BaseOTTAnalyticsPlugin { parterId = pId } - guard let mediaEntry = self.player.mediaEntry else { + guard let player = self.player else { + PKLog.error("send analytics failed due to nil associated player") + return nil + } + + guard let mediaEntry = player.mediaEntry else { PKLog.error("send analytics failed due to nil mediaEntry") - self.messageBus.post(PlayerEvent.PluginError(error: AnalyticsPluginError.missingMediaEntry)) + self.messageBus?.post(PlayerEvent.PluginError(error: AnalyticsPluginError.missingMediaEntry)) return nil } @@ -48,7 +53,7 @@ public class PhoenixAnalyticsPlugin: BaseOTTAnalyticsPlugin { partnerId: parterId, ks: ks, eventType: type.rawValue.uppercased(), - currentTime: self.player.currentTime.toInt32(), + currentTime: player.currentTime.toInt32(), assetId: mediaEntry.id, fileId: fileId) else { return nil diff --git a/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift b/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift index c5a169f0..d8f72ac4 100644 --- a/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift +++ b/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift @@ -18,18 +18,20 @@ public class TVPAPIAnalyticsPlugin: BaseOTTAnalyticsPlugin { /************************************************************/ override func buildRequest(ofType type: OTTAnalyticsEventType) -> Request? { + guard let player = self.player else { return nil } + var fileId = "" var baseUrl = "" guard let initObj = self.config?.params["initObj"] as? [String: Any] else { PKLog.error("send analytics failed due to no initObj data") - self.messageBus.post(PlayerEvent.PluginError(error: AnalyticsPluginError.missingInitObject)) + self.messageBus?.post(PlayerEvent.PluginError(error: AnalyticsPluginError.missingInitObject)) return nil } - guard let mediaEntry = self.player.mediaEntry else { + guard let mediaEntry = player.mediaEntry else { PKLog.error("send analytics failed due to nil mediaEntry") - self.messageBus.post(PlayerEvent.PluginError(error: AnalyticsPluginError.missingMediaEntry)) + self.messageBus?.post(PlayerEvent.PluginError(error: AnalyticsPluginError.missingMediaEntry)) return nil } @@ -47,17 +49,17 @@ public class TVPAPIAnalyticsPlugin: BaseOTTAnalyticsPlugin { guard let requestBuilder: RequestBuilder = MediaMarkService.sendTVPAPIEVent(baseURL: baseUrl, initObj: initObj, eventType: type.rawValue, - currentTime: self.player.currentTime.toInt32(), + currentTime: player.currentTime.toInt32(), assetId: mediaEntry.id, fileId: fileId) else { return nil } - + requestBuilder.set(responseSerializer: StringSerializer()) requestBuilder.set { (response: Response) in PKLog.trace("Response: \(response)") if response.statusCode == 0 { PKLog.trace("\(response.data)") - guard let data = response.data as? String, data.lowercased() == "concurrent" else { return } + guard let data = response.data as? String, data.lowercased() == "\"concurrent\"" else { return } self.reportConcurrencyEvent() } } diff --git a/Plugins/Youbora/YouboraPlugin.swift b/Plugins/Youbora/YouboraPlugin.swift index 0560e3a1..2c6c2d62 100644 --- a/Plugins/Youbora/YouboraPlugin.swift +++ b/Plugins/Youbora/YouboraPlugin.swift @@ -75,8 +75,8 @@ public class YouboraPlugin: BaseAnalyticsPlugin { public override func onLoad(mediaConfig: MediaConfig) { super.onLoad(mediaConfig: mediaConfig) self.setupYouboraManager() { succeeded in - if succeeded { - self.startMonitoring(player: self.player) + if let player = self.player, succeeded { + self.startMonitoring(player: player) } } } @@ -117,66 +117,75 @@ public class YouboraPlugin: BaseAnalyticsPlugin { switch event { case let e where e.self == PlayerEvent.canPlay: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in - self.postEventLogWithMessage(message: "canPlay event: \(event)") + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + strongSelf.postEventLogWithMessage(message: "canPlay event: \(event)") } case let e where e.self == PlayerEvent.play: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in - guard let youboraManager = self.youboraManager else { return } + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let youboraManager = strongSelf.youboraManager else { return } youboraManager.playHandler() - self.postEventLogWithMessage(message: "play event: \(event)") + strongSelf.postEventLogWithMessage(message: "play event: \(event)") } case let e where e.self == PlayerEvent.pause: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in - guard let youboraManager = self.youboraManager else { return } + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let youboraManager = strongSelf.youboraManager else { return } youboraManager.pauseHandler() - self.postEventLogWithMessage(message: "pause event: \(event)") + strongSelf.postEventLogWithMessage(message: "pause event: \(event)") } case let e where e.self == PlayerEvent.playing: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in - self.postEventLogWithMessage(message: "playing event: \(event)") + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + strongSelf.postEventLogWithMessage(message: "playing event: \(event)") - guard let youboraManager = self.youboraManager else { return } + guard let youboraManager = strongSelf.youboraManager else { return } - if self.isFirstPlay { + if strongSelf.isFirstPlay { youboraManager.joinHandler() youboraManager.bufferedHandler() - self.isFirstPlay = false + strongSelf.isFirstPlay = false } else { youboraManager.resumeHandler() } } case let e where e.self == PlayerEvent.seeking: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] event in - guard let youboraManager = self.youboraManager else { return } + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let youboraManager = strongSelf.youboraManager else { return } youboraManager.seekingHandler() - self.postEventLogWithMessage(message: "seeking event: \(event)") + strongSelf.postEventLogWithMessage(message: "seeking event: \(event)") } case let e where e.self == PlayerEvent.seeked: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] (event) in - guard let youboraManager = self.youboraManager else { return } + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let youboraManager = strongSelf.youboraManager else { return } youboraManager.seekedHandler() - self.postEventLogWithMessage(message: "seeked event: \(event)") + strongSelf.postEventLogWithMessage(message: "seeked event: \(event)") } case let e where e.self == PlayerEvent.ended: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] (event) in - guard let youboraManager = self.youboraManager else { return } + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let youboraManager = strongSelf.youboraManager else { return } youboraManager.endedHandler() - self.postEventLogWithMessage(message: "ended event: \(event)") + strongSelf.postEventLogWithMessage(message: "ended event: \(event)") } case let e where e.self == PlayerEvent.playbackParamsUpdated: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] (event) in - guard let youboraManager = self.youboraManager else { return } + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let youboraManager = strongSelf.youboraManager else { return } youboraManager.currentBitrate = event.currentBitrate?.doubleValue - self.postEventLogWithMessage(message: "playbackParamsUpdated event: \(event)") + strongSelf.postEventLogWithMessage(message: "playbackParamsUpdated event: \(event)") } case let e where e.self == PlayerEvent.stateChanged: - self.messageBus.addObserver(self, events: [e.self]) { [unowned self] (event) in - guard let youboraManager = self.youboraManager else { return } + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let youboraManager = strongSelf.youboraManager else { return } switch event.newState { case .buffering: youboraManager.bufferingHandler() - self.postEventLogWithMessage(message: "Buffering event: ֿ\(event)") + strongSelf.postEventLogWithMessage(message: "Buffering event: ֿ\(event)") break default: break } @@ -184,7 +193,7 @@ public class YouboraPlugin: BaseAnalyticsPlugin { switch event.oldState { case .buffering: youboraManager.bufferedHandler() - self.postEventLogWithMessage(message: "Buffered event: \(event)") + strongSelf.postEventLogWithMessage(message: "Buffered event: \(event)") break default: break } @@ -194,8 +203,8 @@ public class YouboraPlugin: BaseAnalyticsPlugin { } PKLog.debug("register ads events") - self.messageBus.addObserver(self, events: AdEvent.allEventTypes) { [unowned self] (event) in - self.postEventLogWithMessage(message: "Ads event event: \(event)") + self.messageBus?.addObserver(self, events: AdEvent.allEventTypes) { [weak self] event in + self?.postEventLogWithMessage(message: "Ads event event: \(event)") } } @@ -204,17 +213,17 @@ public class YouboraPlugin: BaseAnalyticsPlugin { /************************************************************/ private func setupYouboraManager(completionHandler: ((_ succeeded: Bool) -> Void)? = nil) { - - guard let mediaEntry = self.player.mediaEntry else { + guard let player = self.player else { return } + guard let mediaEntry = player.mediaEntry else { PKLog.error("missing MediaEntry, could not setup youbora manager") - self.messageBus.post(PlayerEvent.PluginError(nsError: YouboraPluginError.failedToSetupYouboraManager.asNSError)) + self.messageBus?.post(PlayerEvent.PluginError(nsError: YouboraPluginError.failedToSetupYouboraManager.asNSError)) completionHandler?(false) return } guard let config = self.config else { PKLog.error("config params doesn't exist, could not setup youbora manager") - self.messageBus.post(PlayerEvent.PluginError(nsError: YouboraPluginError.failedToSetupYouboraManager.asNSError)) + self.messageBus?.post(PlayerEvent.PluginError(nsError: YouboraPluginError.failedToSetupYouboraManager.asNSError)) completionHandler?(false) return } @@ -225,7 +234,7 @@ public class YouboraPlugin: BaseAnalyticsPlugin { if var media = config.params["media"] as? [String: Any] { media["resource"] = mediaEntry.id media["title"] = mediaEntry.id - media["duration"] = self.player.duration + media["duration"] = player.duration config.params["media"] = media } else { config.params["media"] = [ @@ -260,7 +269,7 @@ public class YouboraPlugin: BaseAnalyticsPlugin { private func postEventLogWithMessage(message: String) { PKLog.debug(message) let eventLog = YouboraEvent.YouboraReportSent(message: message as NSString) - self.messageBus.post(eventLog) + self.messageBus?.post(eventLog) } }