From 3a7bc122a40fccf0a18336cbb4706118babfe475 Mon Sep 17 00:00:00 2001 From: Gal Orlanczyk Date: Mon, 19 Jun 2017 10:45:57 +0300 Subject: [PATCH 1/7] Player Error Handling Enhancements (#188) * Enhanced player error handling. Used to handle player failed status which means the player needs to be recreated. In addition, added new player error called PlayerError.failed to observe the new enhanced handling. * Removed player kvo observers only on deinit and not on destroy. * updated parsing of status changes for player and player item. Use change values in case instance property was changed before handling the new value and we will use a wrong value (changes dict for KVO always delivers the needed value). * removed `isDestroyed`, not need removing observers will be done only in deinit. * Removed unnecessary `public` modifiers. * fixed small issue with PR --- Classes/PKError.swift | 5 +++ .../AVPlayerEngine+Observation.swift | 43 +++++++++++++++---- .../AVPlayerEngine/AVPlayerEngine.swift | 33 ++++++-------- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/Classes/PKError.swift b/Classes/PKError.swift index b8b8acf7..25dfca54 100644 --- a/Classes/PKError.swift +++ b/Classes/PKError.swift @@ -19,6 +19,7 @@ enum PlayerError: PKError { case failedToLoadAssetFromKeys(rootError: NSError?) case assetNotPlayable case playerItemFailed(rootError: NSError) + case failed(rootError: NSError) static let domain = "com.kaltura.playkit.error.player" @@ -27,6 +28,7 @@ enum PlayerError: PKError { case .failedToLoadAssetFromKeys: return PKErrorCode.failedToLoadAssetFromKeys case .assetNotPlayable: return PKErrorCode.assetNotPlayable case .playerItemFailed: return PKErrorCode.playerItemFailed + case .failed: return PKErrorCode.playerFailed } } @@ -35,6 +37,7 @@ enum PlayerError: PKError { case .failedToLoadAssetFromKeys: return "Can't use this AVAsset because one of it's keys failed to load" case .assetNotPlayable: return "Can't use this AVAsset because it isn't playable" case .playerItemFailed: return "Player item failed to play" + case .failed: return "Player failed, you can no longer use the player for playback and need to recreate it" } } @@ -47,6 +50,7 @@ enum PlayerError: PKError { return [:] case .assetNotPlayable: return [:] case .playerItemFailed(let rootError): return [PKErrorKeys.RootErrorKey: rootError] + case .failed(let rootError): return [PKErrorKeys.RootErrorKey: rootError] } } } @@ -237,6 +241,7 @@ public struct PKErrorKeys { @objc(FailedToLoadAssetFromKeys) public static let failedToLoadAssetFromKeys = 7000 @objc(AssetNotPlayable) public static let assetNotPlayable = 7001 @objc(PlayerItemFailed) public static let playerItemFailed = 7002 + @objc(PlayerFailed) public static let playerFailed = 7003 // PlayerErrorLog @objc(PlayerItemErrorLogEvent) public static let playerItemErrorLogEvent = 7100 // PKPluginError diff --git a/Classes/Player/AVPlayerEngine/AVPlayerEngine+Observation.swift b/Classes/Player/AVPlayerEngine/AVPlayerEngine+Observation.swift index c090cf49..350b35a4 100644 --- a/Classes/Player/AVPlayerEngine/AVPlayerEngine+Observation.swift +++ b/Classes/Player/AVPlayerEngine/AVPlayerEngine+Observation.swift @@ -16,6 +16,7 @@ extension AVPlayerEngine { private var observedKeyPaths: [String] { return [ #keyPath(rate), + #keyPath(status), #keyPath(currentItem.status), #keyPath(currentItem), #keyPath(currentItem.playbackLikelyToKeepUp), @@ -34,8 +35,8 @@ extension AVPlayerEngine { addObserver(self, forKeyPath: keyPath, options: [.new, .initial], context: &observerContext) } - NotificationCenter.default.addObserver(self, selector: #selector(self.playerFailed(notification:)), name: .AVPlayerItemFailedToPlayToEndTime, object: self.currentItem) - NotificationCenter.default.addObserver(self, selector: #selector(self.playerPlayedToEnd(notification:)), name: .AVPlayerItemDidPlayToEndTime, object: self.currentItem) // TODO: check if fired same as playerItem.status == failed if yes then remove this notificaiton observation. + NotificationCenter.default.addObserver(self, selector: #selector(self.didFailToPlayToEndTime(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: self.currentItem) + NotificationCenter.default.addObserver(self, selector: #selector(self.didPlayToEndTime(_:)), name: .AVPlayerItemDidPlayToEndTime, object: self.currentItem) NotificationCenter.default.addObserver(self, selector: #selector(self.onAccessLogEntryNotification), name: .AVPlayerItemNewAccessLogEntry, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.onErrorLogEntryNotification), name: .AVPlayerItemNewErrorLogEntry, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.timebaseChanged), name: Notification.Name(kCMTimebaseNotification_EffectiveRateChanged as String), object: self.currentItem?.timebase) @@ -76,7 +77,7 @@ extension AVPlayerEngine { self.post(event: PlayerEvent.ErrorLog(error: PlayerErrorLog(errorLogEvent: lastEvent))) } - public func playerFailed(notification: NSNotification) { + func didFailToPlayToEndTime(_ notification: NSNotification) { let newState = PlayerState.error self.postStateChange(newState: newState, oldState: self.currentState) self.currentState = newState @@ -88,7 +89,7 @@ extension AVPlayerEngine { } } - public func playerPlayedToEnd(notification: NSNotification) { + func didPlayToEndTime(_ notification: NSNotification) { let newState = PlayerState.idle self.postStateChange(newState: newState, oldState: self.currentState) self.currentState = newState @@ -100,7 +101,6 @@ extension AVPlayerEngine { } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - PKLog.debug("observeValue:: onEvent/onState") guard context == &observerContext else { @@ -118,7 +118,18 @@ extension AVPlayerEngine { case #keyPath(currentItem.playbackLikelyToKeepUp): self.handleLikelyToKeepUp() case #keyPath(currentItem.playbackBufferEmpty): self.handleBufferEmptyChange() case #keyPath(rate): self.handleRate() - case #keyPath(currentItem.status): self.handleStatusChange() + case #keyPath(status): + guard let statusChange = change?[.newKey] as? NSNumber, let newPlayerStatus = AVPlayerStatus(rawValue: statusChange.intValue) else { + PKLog.error("unknown player status") + return + } + self.handle(status: newPlayerStatus) + case #keyPath(currentItem.status): + guard let statusChange = change?[.newKey] as? NSNumber, let newPlayerItemStatus = AVPlayerItemStatus(rawValue: statusChange.intValue) else { + PKLog.error("unknown player item status") + return + } + self.handle(playerItemStatus: newPlayerItemStatus) case #keyPath(currentItem): self.handleItemChange() case #keyPath(currentItem.timedMetadata): self.handleTimedMedia() default: super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) @@ -164,8 +175,21 @@ extension AVPlayerEngine { PKLog.debug("player rate was changed, now: \(self.rate)") } - private func handleStatusChange() { - if currentItem?.status == .readyToPlay { + private func handle(status: AVPlayerStatus) { + switch status { + case .readyToPlay: PKLog.debug("player is ready to play player items") + case .failed: + PKLog.error("player failed you must recreate the player instance") + if let error = (self.error as NSError?) { + self.post(event: PlayerEvent.Error(error: PlayerError.failed(rootError: error))) + } + case .unknown: break + } + } + + private func handle(playerItemStatus status: AVPlayerItemStatus) { + switch status { + case .readyToPlay: let newState = PlayerState.ready if self.startPosition > 0 { @@ -190,13 +214,14 @@ extension AVPlayerEngine { self.post(event: PlayerEvent.LoadedMetadata()) self.post(event: PlayerEvent.CanPlay()) } - } else if currentItem?.status == .failed { + case .failed: let newState = PlayerState.error self.postStateChange(newState: newState, oldState: self.currentState) self.currentState = newState if let error = currentItem?.error as NSError? { self.post(event: PlayerEvent.Error(error: PlayerError.playerItemFailed(rootError: error))) } + case .unknown: break } } diff --git a/Classes/Player/AVPlayerEngine/AVPlayerEngine.swift b/Classes/Player/AVPlayerEngine/AVPlayerEngine.swift index e11f0d65..9d979152 100644 --- a/Classes/Player/AVPlayerEngine/AVPlayerEngine.swift +++ b/Classes/Player/AVPlayerEngine/AVPlayerEngine.swift @@ -26,7 +26,6 @@ class AVPlayerEngine: AVPlayer { fileprivate var playerLayer: AVPlayerLayer! private var _view: PlayerView! - private var isDestroyed: Bool = false /// Keeps reference on the last timebase rate in order to post events accuratly. var lastTimebaseRate: Float64 = 0 @@ -39,21 +38,21 @@ class AVPlayerEngine: AVPlayer { var tracksManager = TracksManager() var observerContext = 0 - public var onEventBlock: ((PKEvent) -> Void)? + var onEventBlock: ((PKEvent) -> Void)? - public var view: PlayerView! { + var view: PlayerView! { PKLog.trace("get player view: \(_view)") return _view } - public var asset: AVURLAsset? { + var asset: AVURLAsset? { didSet { guard let newAsset = asset else { return } self.asynchronouslyLoadURLAsset(newAsset) } } - public var currentPosition: Double { + var currentPosition: Double { get { PKLog.trace("get currentPosition: \(self.currentTime())") return CMTimeGetSeconds(self.currentTime() - rangeStart) @@ -73,13 +72,13 @@ class AVPlayerEngine: AVPlayer { } } - public var startPosition: Double { + var startPosition: Double { didSet { PKLog.debug("set startPosition: \(startPosition)") } } - public var duration: Double { + var duration: Double { guard let currentItem = self.currentItem else { return 0.0 } var result = CMTimeGetSeconds(currentItem.duration) @@ -96,7 +95,7 @@ class AVPlayerEngine: AVPlayer { return result } - public var isPlaying: Bool { + var isPlaying: Bool { guard let currentItem = self.currentItem else { PKLog.error("current item is empty") return false @@ -114,14 +113,14 @@ class AVPlayerEngine: AVPlayer { return false } - public var currentAudioTrack: String? { + var currentAudioTrack: String? { if let currentItem = self.currentItem { return self.tracksManager.currentAudioTrack(item: currentItem) } return nil } - public var currentTextTrack: String? { + var currentTextTrack: String? { if let currentItem = self.currentItem { return self.tracksManager.currentTextTrack(item: currentItem) } @@ -143,7 +142,7 @@ class AVPlayerEngine: AVPlayer { // MARK: Player Methods - public override init() { + override init() { PKLog.info("init AVPlayer") self.startPosition = 0 @@ -164,10 +163,8 @@ class AVPlayerEngine: AVPlayer { deinit { PKLog.debug("\(String(describing: type(of: self))), was deinitialized") - // Avoid dealloc while key value observers were still registered - if (!self.isDestroyed) { - self.removeObservers() - } + // removes the observers only on deinit to prevent chances of being removed twice. + self.removeObservers() } func stop() { @@ -205,20 +202,16 @@ class AVPlayerEngine: AVPlayer { // this make sure everything will be cleared without any race conditions DispatchQueue.main.async { PKLog.info("destroy player") - self.removeObservers() self.playerLayer = nil self._view = nil self.onEventBlock = nil // removes app state observer AppStateSubject.shared.remove(observer: self) self.replaceCurrentItem(with: nil) - self.isDestroyed = true } } - - - public func selectTrack(trackId: String) { + func selectTrack(trackId: String) { if trackId.isEmpty == false { self.tracksManager.selectTrack(item: self.currentItem!, trackId: trackId) } else { From 83d3782b4ac93ea5d68e5d76659e37f484a0fb73 Mon Sep 17 00:00:00 2001 From: ElizaSapir Date: Thu, 22 Jun 2017 16:39:45 +0300 Subject: [PATCH 2/7] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ba08c1b8..146a69da 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/3c95a3b285d9447cb525be42647af4f8)](https://www.codacy.com/app/PlayKit/playkit-ios?utm_source=github.com&utm_medium=referral&utm_content=kaltura/playkit-ios&utm_campaign=badger) [![CI Status](https://api.travis-ci.org/kaltura/playkit-ios.svg?branch=develop)](https://travis-ci.org/kaltura/playkit-ios) [![Version](https://img.shields.io/cocoapods/v/PlayKit.svg?style=flat)](https://cocoapods.org/pods/PlayKit) [![License](https://img.shields.io/cocoapods/l/PlayKit.svg?style=flat)](https://cocoapods.org/pods/PlayKit) From d2350a6a22342da85f1affa66174ab442634db37 Mon Sep 17 00:00:00 2001 From: Gal Orlanczyk Date: Sun, 25 Jun 2017 09:20:56 +0300 Subject: [PATCH 3/7] #FEM-1483 (#189) * Added additional data attributes to kaltura stats. * Added new class for kaltura stats config. --- .../AnalyticsPluginProtocol.swift | 1 - Plugins/KalturaStats/KalturaStatsConfig.swift | 31 ++++ Plugins/KalturaStats/KalturaStatsPlugin.swift | 161 ++++++++---------- Plugins/KalturaStats/OVPStatsService.swift | 35 +++- 4 files changed, 133 insertions(+), 95 deletions(-) create mode 100644 Plugins/KalturaStats/KalturaStatsConfig.swift diff --git a/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift b/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift index d0ab4d3d..bc379ccf 100644 --- a/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift +++ b/Plugins/AnalyticsCommon/AnalyticsPluginProtocol.swift @@ -10,7 +10,6 @@ import Foundation protocol AnalyticsPluginProtocol: PKPlugin { - var config: AnalyticsConfig? { get set } var isFirstPlay: Bool { get set } var playerEventsToRegister: [PlayerEvent.Type] { get } diff --git a/Plugins/KalturaStats/KalturaStatsConfig.swift b/Plugins/KalturaStats/KalturaStatsConfig.swift new file mode 100644 index 00000000..e8aeb8e8 --- /dev/null +++ b/Plugins/KalturaStats/KalturaStatsConfig.swift @@ -0,0 +1,31 @@ +// +// KalturaStatsConfig.swift +// Pods +// +// Created by Gal Orlanczyk on 20/06/2017. +// +// + +import Foundation + +@objc public class KalturaStatsConfig: NSObject { + + private let defaultBaseUrl = "https://stats.kaltura.com/api_v3/index.php" + + let applicationId = Bundle.main.bundleIdentifier + + @objc public var uiconfId: Int + @objc public var partnerId: Int + @objc public var entryId: String + + @objc public var baseUrl: String + @objc public var userId: String? + @objc public var contextId: Int = -1 // need to be greater then 0 to be valid + + @objc public init(uiconfId: Int, partnerId: Int, entryId: String) { + self.baseUrl = defaultBaseUrl + self.uiconfId = uiconfId + self.partnerId = partnerId + self.entryId = entryId + } +} diff --git a/Plugins/KalturaStats/KalturaStatsPlugin.swift b/Plugins/KalturaStats/KalturaStatsPlugin.swift index 80b2bb5c..cd262079 100644 --- a/Plugins/KalturaStats/KalturaStatsPlugin.swift +++ b/Plugins/KalturaStats/KalturaStatsPlugin.swift @@ -29,7 +29,7 @@ extension PKEvent { } } -public class KalturaStatsPlugin: BaseAnalyticsPlugin { +public class KalturaStatsPlugin: BasePlugin, AnalyticsPluginProtocol { // stats event types enum KalturaStatsEventType : Int { @@ -76,10 +76,6 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { case error = 99 } - public override class var pluginName: String { - return "KalturaStatsPlugin" - } - private var isWidgetLoaded = false private var isMediaLoaded = false private var isBuffering = false @@ -94,13 +90,61 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { private var hasSeeked = false private var timer: Timer? - private var interval: TimeInterval = 30 + private var interval: TimeInterval = 10 + + var config: KalturaStatsConfig! + /// indicates whether we played for the first time or not. + var isFirstPlay: Bool = true + + /************************************************************/ + // MARK: - PKPlugin + /************************************************************/ + + public override class var pluginName: String { + return "KalturaStatsPlugin" + } + + public required init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws { + try super.init(player: player, pluginConfig: pluginConfig, messageBus: messageBus) + guard let config = pluginConfig as? KalturaStatsConfig else { + PKLog.error("missing plugin config or wrong plugin class type") + throw PKPluginError.missingPluginConfig(pluginName: KalturaStatsPlugin.pluginName) + } + self.config = config + self.registerEvents() + } + + public override func onUpdateMedia(mediaConfig: MediaConfig) { + super.onUpdateMedia(mediaConfig: mediaConfig) + self.resetPlayerFlags() + self.timer?.invalidate() + } + + public override func onUpdateConfig(pluginConfig: Any) { + super.onUpdateConfig(pluginConfig: pluginConfig) + + guard let config = pluginConfig as? KalturaStatsConfig else { + PKLog.error("plugin configis wrong") + return + } + + PKLog.debug("new config::\(String(describing: config))") + self.config = config + } + + public override func destroy() { + self.messageBus?.removeObserver(self, events: playerEventsToRegister) + if let t = self.timer { + t.invalidate() + } + super.destroy() + } /************************************************************/ // MARK: - AnalyticsPluginProtocol /************************************************************/ - override var playerEventsToRegister: [PlayerEvent.Type] { + var playerEventsToRegister: [PlayerEvent.Type] { return [ PlayerEvent.error, PlayerEvent.canPlay, @@ -110,7 +154,7 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { ] } - override func registerEvents() { + func registerEvents() { PKLog.debug("register player events") self.playerEventsToRegister.forEach { event in @@ -184,35 +228,6 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { } } - /************************************************************/ - // MARK: - PKPlugin - /************************************************************/ - - public override func onUpdateMedia(mediaConfig: MediaConfig) { - super.onUpdateMedia(mediaConfig: mediaConfig) - self.isWidgetLoaded = false - self.isMediaLoaded = false - self.isBuffering = false - - self.seekPercent = 0.0 - - self.playReached25 = false - self.playReached50 = false - self.playReached75 = false - self.playReached100 = false - self.intervalOn = false - self.hasSeeked = false - - self.timer?.invalidate() - } - - public override func destroy() { - super.destroy() - if let t = self.timer { - t.invalidate() - } - } - /************************************************************/ // MARK: - Private Implementation /************************************************************/ @@ -240,23 +255,19 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { } private func resetPlayerFlags() { - isFirstPlay = true - hasSeeked = false - isBuffering = false - isMediaLoaded = false - isWidgetLoaded = false - playReached25 = false - playReached50 = false - playReached75 = false - playReached100 = false + self.isWidgetLoaded = false + self.isMediaLoaded = false + self.isBuffering = false + self.playReached25 = false + self.playReached50 = false + self.playReached75 = false + self.playReached100 = false + self.intervalOn = false + self.hasSeeked = false + self.isFirstPlay = true } private func createTimer() { - - if let intr = self.config?.params["timerInterval"] as? TimeInterval { - self.interval = intr - } - if let t = self.timer { t.invalidate() } @@ -291,48 +302,15 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { private func sendAnalyticsEvent(action: KalturaStatsEventType) { guard let player = self.player else { return } - guard let mediaEntry = player.mediaEntry else { - PKLog.error("send analytics failed due to nil mediaEntry") - return - } - PKLog.debug("Action: \(action)") - let entryId: String - let sessionId = player.sessionId - var baseUrl = "https://stats.kaltura.com/api_v3/index.php" - var confId = 0 - var parterId = "" - - if let cId = self.config?.params["uiconfId"] as? Int { - confId = cId - } - - if let url = self.config?.params["baseUrl"] as? String { - baseUrl = url - } - - if let pId = self.config?.params["partnerId"] as? Int { - parterId = String(pId) - } - - if let eId = self.config?.params["entryId"] as? String { - entryId = eId - } else { - entryId = mediaEntry.id - } - - guard let builder: KalturaRequestBuilder = OVPStatsService.get(baseURL: baseUrl, - partnerId: parterId, - eventType: action.rawValue, - clientVer: PlayKitManager.clientTag, - duration: Float(player.duration), - sessionId: sessionId, - position: player.currentTime.toInt32(), - uiConfId: confId, - entryId: entryId, - widgetId: "_\(parterId)", - isSeek: hasSeeked) else { return } + guard let builder: KalturaRequestBuilder = OVPStatsService.get(config: self.config, + eventType: action.rawValue, + clientVer: PlayKitManager.clientTag, + duration: Float(player.duration), + sessionId: player.sessionId, + position: player.currentTime.toInt32(), + widgetId: "_\(self.config.partnerId)", isSeek: hasSeeked) else { return } builder.set { (response: Response) in PKLog.debug("Response: \(response)") @@ -340,5 +318,4 @@ public class KalturaStatsPlugin: BaseAnalyticsPlugin { USRExecutor.shared.send(request: builder.build()) } - } diff --git a/Plugins/KalturaStats/OVPStatsService.swift b/Plugins/KalturaStats/OVPStatsService.swift index 69e84893..c191e862 100644 --- a/Plugins/KalturaStats/OVPStatsService.swift +++ b/Plugins/KalturaStats/OVPStatsService.swift @@ -12,7 +12,27 @@ import KalturaNetKit internal class OVPStatsService { - internal static func get(baseURL: String, partnerId: String, eventType: Int, clientVer: String, duration: Float,sessionId: String, position: Int32, uiConfId: Int, entryId: String, widgetId: String, isSeek: Bool, referrer: String = "") -> KalturaRequestBuilder? { + static func get(config: KalturaStatsConfig, eventType: Int, clientVer: String, duration: Float, sessionId: String, position: Int32, widgetId: String, isSeek: Bool, referrer: String = "") -> KalturaRequestBuilder? { + + return get( + baseURL: config.baseUrl, + partnerId: "\(config.partnerId)", + eventType: eventType, + clientVer: PlayKitManager.clientTag, + duration: duration, + sessionId: sessionId, + position: position, + uiConfId: config.uiconfId, + entryId: config.entryId, + widgetId: widgetId, + isSeek: isSeek, + contextId: config.contextId, + appId: config.applicationId, + userId: config.userId + ) + } + + static func get(baseURL: String, partnerId: String, eventType: Int, clientVer: String, duration: Float, sessionId: String, position: Int32, uiConfId: Int, entryId: String, widgetId: String, isSeek: Bool, referrer: String = "", contextId: Int, appId: String?, userId: String?) -> KalturaRequestBuilder? { if let request: KalturaRequestBuilder = KalturaRequestBuilder(url: baseURL, service: nil, action: nil) { request @@ -27,7 +47,7 @@ internal class OVPStatsService { .setParam(key: "event:clientVer", value: "\(clientVer)") .setParam(key: "event:currentPoint", value: "\(position)") .setParam(key: "event:duration", value: "\(duration)") - .setParam(key: "event:eventTimeStamp", value: "\(Date().timeIntervalSince1970)") // + .setParam(key: "event:eventTimeStamp", value: "\(Date().timeIntervalSince1970)") .setParam(key: "event:isFirstInSession", value: "false") .setParam(key: "event:objectType", value: "KalturaStatsEvent") .setParam(key: "event:partnerId", value: partnerId) @@ -38,6 +58,17 @@ internal class OVPStatsService { .setParam(key: "event:widgetId", value: widgetId) .setParam(key: "event:referrer", value: referrer) .set(method: .get) + + if contextId > 0 { + request.setParam(key: "event:contextId", value: "\(contextId)") + } + if let applicationId = appId, applicationId != "" { + request.setParam(key: "event:applicationId", value: applicationId) + } + if let userId = userId, userId != "" { + request.setParam(key: "event:userId", value: userId) + } + return request } else { return nil From 407afa6326776df5d2ca913bf08f6d38987894e6 Mon Sep 17 00:00:00 2001 From: ElizaSapir Date: Mon, 26 Jun 2017 12:02:44 +0300 Subject: [PATCH 4/7] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 146a69da..b537438e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + +![Swift 3.0+](https://img.shields.io/badge/Swift-3.0+-orange.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/3c95a3b285d9447cb525be42647af4f8)](https://www.codacy.com/app/PlayKit/playkit-ios?utm_source=github.com&utm_medium=referral&utm_content=kaltura/playkit-ios&utm_campaign=badger) [![CI Status](https://api.travis-ci.org/kaltura/playkit-ios.svg?branch=develop)](https://travis-ci.org/kaltura/playkit-ios) [![Version](https://img.shields.io/cocoapods/v/PlayKit.svg?style=flat)](https://cocoapods.org/pods/PlayKit) From 683d2c67ba1352ae6ab88dfa8f7a0cea13194400 Mon Sep 17 00:00:00 2001 From: ElizaSapir Date: Mon, 26 Jun 2017 13:38:57 +0300 Subject: [PATCH 5/7] FEM-1503 #comment bump SwiftyXMLParser to fix xcode9 issue (#191) --- PlayKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayKit.podspec b/PlayKit.podspec index 8f2a43aa..0d9cdcd4 100644 --- a/PlayKit.podspec +++ b/PlayKit.podspec @@ -15,7 +15,7 @@ s.subspec 'Core' do |sp| sp.source_files = 'Classes/**/*' sp.dependency 'SwiftyJSON', '3.1.4' sp.dependency 'Log', '1.0' - sp.dependency 'SwiftyXMLParser', '3.0.0' + sp.dependency 'SwiftyXMLParser', '3.0.3' sp.dependency 'KalturaNetKit', '~> 0.0' end From 33174966731cb873ccebb577cdcc93926ecd02c9 Mon Sep 17 00:00:00 2001 From: Gal Orlanczyk Date: Wed, 28 Jun 2017 17:54:52 +0300 Subject: [PATCH 6/7] #FEM-1493 (#192) * #FEM-1493 * Changed `PlayerEvent.SourceSelected` to send `MediaSource` object instead of just content url. * Changed file id param to be received from the media source and not from config. * Added phoenix and tvpapi analytics plugin config. * Renamed OTTAnalyticsConfig to OTTAnalyticsPluginConfig * Added @objc attribute to config initializers --- Classes/Events/PlayerEvent.swift | 14 ++-- Classes/PKError.swift | 17 +---- Classes/Player/PKEvent.swift | 6 +- Classes/Player/PlayerController.swift | 2 +- .../AnalyticsCommon/BaseAnalyticsPlugin.swift | 40 ----------- Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift | 50 +++++++++----- Plugins/Phoenix/MediaMarkService.swift | 18 +---- .../Phoenix/OTTAnalyticsPluginConfig.swift | 42 ++++++++++++ Plugins/Phoenix/PhoenixAnalyticsPlugin.swift | 68 +++++++++++-------- Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift | 52 ++++++++------ Plugins/Youbora/YouboraManager.swift | 2 +- 11 files changed, 160 insertions(+), 151 deletions(-) create mode 100644 Plugins/Phoenix/OTTAnalyticsPluginConfig.swift diff --git a/Classes/Events/PlayerEvent.swift b/Classes/Events/PlayerEvent.swift index a4391636..94ab8976 100644 --- a/Classes/Events/PlayerEvent.swift +++ b/Classes/Events/PlayerEvent.swift @@ -51,11 +51,11 @@ import AVFoundation /// Sent when source was selected. @objc public static let sourceSelected: PlayerEvent.Type = SourceSelected.self - /// Sent when an error occurs. + /// Sent when an error occurs in the player that the playback can recover from. @objc public static let error: PlayerEvent.Type = Error.self - /// Sent when an plugin error occurs. + /// Sent when a plugin error occurs. @objc public static let pluginError: PlayerEvent.Type = PluginError.self - /// Sent when an error log event received from player. + /// Sent when an error log event received from player (non fatal errors). @objc public static let errorLog: PlayerEvent.Type = ErrorLog.self // MARK: - Player Basic Events @@ -77,12 +77,8 @@ import AVFoundation class Seeked: PlayerEvent {} class SourceSelected: PlayerEvent { - convenience init(contentURL: URL?) { - guard let url = contentURL else { - self.init() - return - } - self.init([EventDataKeys.contentURL: url]) + convenience init(mediaSource: MediaSource) { + self.init([EventDataKeys.mediaSource: mediaSource]) } } diff --git a/Classes/PKError.swift b/Classes/PKError.swift index 25dfca54..8f0fe4ca 100644 --- a/Classes/PKError.swift +++ b/Classes/PKError.swift @@ -100,7 +100,7 @@ public enum PKPluginError: PKError { public var errorDescription: String { switch self { case .failedToCreatePlugin(let pluginName): return "failed to create plugin (\(pluginName)), doesn't exist in registry" - case .missingPluginConfig(let pluginName): return "Missing plugin config for plugin: \(pluginName)" + case .missingPluginConfig(let pluginName): return "Missing plugin config for plugin: \(pluginName) (wrong type or doesn't exist)" } } @@ -197,21 +197,6 @@ public extension PKError where Self: RawRepresentable, Self.RawValue == String { } } -/************************************************************/ -// MARK: - Error -/************************************************************/ -// extension for easier access to domain and code properties. -extension Error { - - public var domain: String { - return self._domain - } - - public var code: Int { - return self._code - } -} - /************************************************************/ // MARK: - PKError UserInfo Keys /************************************************************/ diff --git a/Classes/Player/PKEvent.swift b/Classes/Player/PKEvent.swift index 7a9718ea..79c89b7c 100644 --- a/Classes/Player/PKEvent.swift +++ b/Classes/Player/PKEvent.swift @@ -44,7 +44,7 @@ public extension PKEvent { static let newState = "newState" static let error = "error" static let metadata = "metadata" - static let contentURL = "contentURL" + static let mediaSource = "mediaSource" } // MARK: Player Data Accessors @@ -93,7 +93,7 @@ public extension PKEvent { } /// Content url, PKEvent Data Accessor - @objc public var contentURL: URL? { - return self.data?[EventDataKeys.contentURL] as? URL + @objc public var mediaSource: MediaSource? { + return self.data?[EventDataKeys.mediaSource] as? MediaSource } } diff --git a/Classes/Player/PlayerController.swift b/Classes/Player/PlayerController.swift index efe4aa79..eb711b0e 100644 --- a/Classes/Player/PlayerController.swift +++ b/Classes/Player/PlayerController.swift @@ -95,7 +95,7 @@ class PlayerController: NSObject, Player, PlayerSettings { // get the preferred media source and post source selected event guard let (preferredMediaSource, handlerType) = AssetBuilder.getPreferredMediaSource(from: mediaConfig.mediaEntry) else { return } - self.onEventBlock?(PlayerEvent.SourceSelected(contentURL: preferredMediaSource.playbackUrl)) + self.onEventBlock?(PlayerEvent.SourceSelected(mediaSource: preferredMediaSource)) self.preferredMediaSource = preferredMediaSource // update the media source request adapter with new media uuid if using kaltura request adapter diff --git a/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift b/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift index abb1e53b..088e4c08 100644 --- a/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift +++ b/Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift @@ -8,46 +8,6 @@ import Foundation -/************************************************************/ -// MARK: - AnalyticsPluginError -/************************************************************/ - -/// `AnalyticsError` represents analytics plugins (kaltura stats, kaltura live stats, phoenix and tvpapi) common errors. -enum AnalyticsPluginError: PKError { - - case missingMediaEntry - case missingInitObject - - static let domain = "com.kaltura.playkit.error.analyticsPlugin" - - var code: Int { - switch self { - case .missingMediaEntry: return PKErrorCode.missingMediaEntry - case .missingInitObject: return PKErrorCode.missingInitObject - } - } - - var errorDescription: String { - switch self { - case .missingMediaEntry: return "failed to send analytics event, mediaEntry is nil" - case .missingInitObject: return "failed to send analytics event, missing initObj" - } - } - - var userInfo: [String: Any] { - return [:] - } -} - -extension PKErrorDomain { - @objc(AnalyticsPlugin) public static let analyticsPlugin = AnalyticsPluginError.domain -} - -extension PKErrorCode { - @objc(MissingMediaEntry) public static let missingMediaEntry = 2100 - @objc(MissingInitObject) public static let missingInitObject = 2101 -} - /************************************************************/ // MARK: - BaseAnalyticsPlugin /************************************************************/ diff --git a/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift b/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift index ed69b48b..8e6cbf40 100644 --- a/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift +++ b/Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift @@ -10,12 +10,15 @@ import Foundation import KalturaNetKit /// class `BaseOTTAnalyticsPlugin` is a base plugin object used for OTT analytics plugin subclasses -public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProtocol, AppStateObservable { +public class BaseOTTAnalyticsPlugin: BasePlugin, OTTAnalyticsPluginProtocol, AppStateObservable { + /// indicates whether we played for the first time or not. + var isFirstPlay: Bool = true var intervalOn: Bool = false var timer: Timer? var interval: TimeInterval = 30 var isContentEnded: Bool = false + var fileId: String? /************************************************************/ // MARK: - PKPlugin @@ -24,23 +27,26 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt public required init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws { try super.init(player: player, pluginConfig: pluginConfig, messageBus: messageBus) AppStateSubject.shared.add(observer: self) + self.registerEvents() } public override func onUpdateMedia(mediaConfig: MediaConfig) { super.onUpdateMedia(mediaConfig: mediaConfig) self.intervalOn = false self.isContentEnded = false + self.isFirstPlay = true self.timer?.invalidate() } public override func destroy() { - super.destroy() + self.messageBus?.removeObserver(self, events: playerEventsToRegister) // only send stop event if content started playing already & content is not ended if !self.isFirstPlay && !self.isContentEnded { self.sendAnalyticsEvent(ofType: .stop) } self.timer?.invalidate() AppStateSubject.shared.remove(observer: self) + super.destroy() } /************************************************************/ @@ -61,18 +67,20 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt /************************************************************/ /// default events to register - override var playerEventsToRegister: [PlayerEvent.Type] { + var playerEventsToRegister: [PlayerEvent.Type] { return [ PlayerEvent.ended, PlayerEvent.error, PlayerEvent.pause, + PlayerEvent.stopped, PlayerEvent.loadedMetadata, PlayerEvent.playing, - PlayerEvent.seeked + PlayerEvent.seeked, + PlayerEvent.sourceSelected ] } - override func registerEvents() { + func registerEvents() { PKLog.debug("plugin \(type(of:self)) register to all player events") self.playerEventsToRegister.forEach { event in @@ -83,7 +91,6 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt case let e where e.self == PlayerEvent.ended: self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in guard let strongSelf = self else { return } - PKLog.debug("ended event: \(event)") strongSelf.timer?.invalidate() strongSelf.sendAnalyticsEvent(ofType: .finish) strongSelf.isContentEnded = true @@ -91,13 +98,11 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt case let e where e.self == PlayerEvent.error: self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in guard let strongSelf = self else { return } - PKLog.debug("error event: \(event)") strongSelf.sendAnalyticsEvent(ofType: .error) } case let e where e.self == PlayerEvent.pause: 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 !strongSelf.isFirstPlay { @@ -106,16 +111,19 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt } strongSelf.sendAnalyticsEvent(ofType: .pause) } + case let e where e.self == PlayerEvent.stopped: + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + strongSelf.cancelTimer() + } case let e where e.self == PlayerEvent.loadedMetadata: self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in guard let strongSelf = self else { return } - PKLog.debug("loadedMetadata event: \(event)") strongSelf.sendAnalyticsEvent(ofType: .load) } case let e where e.self == PlayerEvent.playing: self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in guard let strongSelf = self else { return } - PKLog.debug("play event: \(event)") if !strongSelf.intervalOn { strongSelf.createTimer() @@ -129,6 +137,12 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt strongSelf.sendAnalyticsEvent(ofType: .play); } } + case let e where e.self == PlayerEvent.sourceSelected: + self.messageBus?.addObserver(self, events: [e.self]) { [weak self] event in + guard let strongSelf = self else { return } + guard let mediaSource = event.mediaSource else { return } + strongSelf.fileId = mediaSource.id + } default: assertionFailure("plugin \(type(of:self)) all events must be handled") } } @@ -173,10 +187,6 @@ public class BaseOTTAnalyticsPlugin: BaseAnalyticsPlugin, OTTAnalyticsPluginProt extension BaseOTTAnalyticsPlugin { fileprivate func createTimer() { - if let conf = self.config, let intr = conf.params["timerInterval"] as? TimeInterval { - self.interval = intr - } - if let t = self.timer { t.invalidate() } @@ -184,9 +194,17 @@ extension BaseOTTAnalyticsPlugin { // media hit should fire on every time we start the timer. self.sendProgressEvent() - self.timer = Timer.every(self.interval) { [unowned self] in + self.timer = Timer.every(self.interval) { [weak self] in PKLog.debug("timerHit") - self.sendProgressEvent() + self?.sendProgressEvent() + } + } + + fileprivate func cancelTimer() { + if let t = self.timer { + t.invalidate() + self.timer = nil + self.intervalOn = false } } diff --git a/Plugins/Phoenix/MediaMarkService.swift b/Plugins/Phoenix/MediaMarkService.swift index 06f3073b..dbd4fd36 100644 --- a/Plugins/Phoenix/MediaMarkService.swift +++ b/Plugins/Phoenix/MediaMarkService.swift @@ -10,9 +10,9 @@ import UIKit import SwiftyJSON import KalturaNetKit -internal class MediaMarkService { +class MediaMarkService { - internal static func sendTVPAPIEVent(baseURL: String, + static func sendTVPAPIEVent(baseURL: String, initObj: [String: Any], eventType: String, currentTime: Int32, @@ -31,20 +31,8 @@ internal class MediaMarkService { request.setBody(key: "Action", value: JSON(eventType)) } return request - }else{ + } else { return nil } - - } - - private static func createBookmark(eventType: String, position: Int32, assetId: String, fileId: String) -> JSON { - var json: JSON = JSON.init(["objectType": "KalturaBookmark"]) - json["type"] = JSON("media") - json["id"] = JSON(assetId) - json["position"] = JSON(position) - json["playerData"] = JSON.init(["action": JSON(eventType), "objectType": JSON("KalturaBookmarkPlayerData"), "fileId": JSON(fileId)]) - - - return json } } diff --git a/Plugins/Phoenix/OTTAnalyticsPluginConfig.swift b/Plugins/Phoenix/OTTAnalyticsPluginConfig.swift new file mode 100644 index 00000000..3251ac45 --- /dev/null +++ b/Plugins/Phoenix/OTTAnalyticsPluginConfig.swift @@ -0,0 +1,42 @@ +// +// OTTAnalyticsConfig.swift +// Pods +// +// Created by Gal Orlanczyk on 25/06/2017. +// +// + +import Foundation + +@objc public class OTTAnalyticsPluginConfig: NSObject { + + let baseUrl: String + let timerInterval: TimeInterval + + init(baseUrl: String, timerInterval: TimeInterval) { + self.baseUrl = baseUrl + self.timerInterval = timerInterval + } +} + +@objc public class PhoenixAnalyticsPluginConfig: OTTAnalyticsPluginConfig { + + let ks: String + let partnerId: Int + + @objc public init(baseUrl: String, timerInterval: TimeInterval, ks: String, partnerId: Int) { + self.ks = ks + self.partnerId = partnerId + super.init(baseUrl: baseUrl, timerInterval: timerInterval) + } +} + +@objc public class TVPAPIAnalyticsPluginConfig: OTTAnalyticsPluginConfig { + + let initObject: [String: Any] + + @objc public init(baseUrl: String, timerInterval: TimeInterval, initObject: [String: Any]) { + self.initObject = initObject + super.init(baseUrl: baseUrl, timerInterval: timerInterval) + } +} diff --git a/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift b/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift index f80e6480..8604a5c0 100644 --- a/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift +++ b/Plugins/Phoenix/PhoenixAnalyticsPlugin.swift @@ -13,32 +13,41 @@ public class PhoenixAnalyticsPlugin: BaseOTTAnalyticsPlugin { public override class var pluginName: String { return "PhoenixAnalytics" } - /************************************************************/ - // MARK: - KalturaOTTAnalyticsPluginProtocol - /************************************************************/ - - override func buildRequest(ofType type: OTTAnalyticsEventType) -> Request? { - var fileId = "" - var baseUrl = "" - var ks = "" - var parterId = 0 - - if let url = self.config?.params["baseUrl"] as? String { - baseUrl = url + var config: PhoenixAnalyticsPluginConfig! { + didSet { + self.interval = config.timerInterval } - - if let fId = self.config?.params["fileId"] as? String { - fileId = fId - } - - if let theKs = self.config?.params["ks"] as? String { - ks = theKs + } + + public required init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws { + try super.init(player: player, pluginConfig: pluginConfig, messageBus: messageBus) + guard let config = pluginConfig as? PhoenixAnalyticsPluginConfig else { + PKLog.error("missing/wrong plugin config") + throw PKPluginError.missingPluginConfig(pluginName: PhoenixAnalyticsPlugin.pluginName) } + self.config = config + self.interval = config.timerInterval + self.registerEvents() + } + + public override func onUpdateConfig(pluginConfig: Any) { + super.onUpdateConfig(pluginConfig: pluginConfig) - if let pId = self.config?.params["partnerId"] as? Int { - parterId = pId + guard let config = pluginConfig as? PhoenixAnalyticsPluginConfig else { + PKLog.error("plugin config is wrong") + return } + PKLog.debug("new config::\(String(describing: config))") + self.config = config + } + + /************************************************************/ + // MARK: - KalturaOTTAnalyticsPluginProtocol + /************************************************************/ + + override func buildRequest(ofType type: OTTAnalyticsEventType) -> Request? { + guard let player = self.player else { PKLog.error("send analytics failed due to nil associated player") return nil @@ -46,18 +55,17 @@ public class PhoenixAnalyticsPlugin: BaseOTTAnalyticsPlugin { guard let mediaEntry = player.mediaEntry else { PKLog.error("send analytics failed due to nil mediaEntry") - self.messageBus?.post(PlayerEvent.PluginError(error: AnalyticsPluginError.missingMediaEntry)) return nil } - guard let requestBuilder: KalturaRequestBuilder = BookmarkService.actionAdd(baseURL: baseUrl, - partnerId: parterId, - ks: ks, - eventType: type.rawValue.uppercased(), - currentTime: player.currentTime.toInt32(), - assetId: mediaEntry.id, - fileId: fileId) else { - return nil + guard let requestBuilder: KalturaRequestBuilder = BookmarkService.actionAdd(baseURL: config.baseUrl, + partnerId: config.partnerId, + ks: config.ks, + eventType: type.rawValue.uppercased(), + currentTime: player.currentTime.toInt32(), + assetId: mediaEntry.id, + fileId: fileId ?? "") else { + return nil } requestBuilder.set { (response: Response) in diff --git a/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift b/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift index 629a6baf..6f207499 100644 --- a/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift +++ b/Plugins/Phoenix/TVPAPIAnalyticsPlugin.swift @@ -14,6 +14,35 @@ public class TVPAPIAnalyticsPlugin: BaseOTTAnalyticsPlugin { public override class var pluginName: String { return "TVPAPIAnalytics" } + var config: TVPAPIAnalyticsPluginConfig! { + didSet { + self.interval = config.timerInterval + } + } + + public required init(player: Player, pluginConfig: Any?, messageBus: MessageBus) throws { + try super.init(player: player, pluginConfig: pluginConfig, messageBus: messageBus) + guard let config = pluginConfig as? TVPAPIAnalyticsPluginConfig else { + PKLog.error("missing/wrong plugin config") + throw PKPluginError.missingPluginConfig(pluginName: TVPAPIAnalyticsPlugin.pluginName) + } + self.config = config + self.interval = config.timerInterval + self.registerEvents() + } + + public override func onUpdateConfig(pluginConfig: Any) { + super.onUpdateConfig(pluginConfig: pluginConfig) + + guard let config = pluginConfig as? TVPAPIAnalyticsPluginConfig else { + PKLog.error("plugin config is wrong") + return + } + + PKLog.debug("new config::\(String(describing: config))") + self.config = config + } + /************************************************************/ // MARK: - KalturaOTTAnalyticsPluginProtocol /************************************************************/ @@ -21,38 +50,21 @@ 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)) - 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)) return nil } let method = type == .hit ? "MediaHit" : "MediaMark" - - if let url = self.config?.params["baseUrl"] as? String { - baseUrl = url - } - if let fId = self.config?.params["fileId"] as? String { - fileId = fId - } - baseUrl = "\(baseUrl)m=\(method)" + let baseUrl = "\(self.config.baseUrl)m=\(method)" guard let requestBuilder: RequestBuilder = MediaMarkService.sendTVPAPIEVent(baseURL: baseUrl, - initObj: initObj, + initObj: self.config.initObject, eventType: type.rawValue, currentTime: player.currentTime.toInt32(), assetId: mediaEntry.id, - fileId: fileId) else { + fileId: self.fileId ?? "") else { return nil } requestBuilder.set(responseSerializer: StringSerializer()) diff --git a/Plugins/Youbora/YouboraManager.swift b/Plugins/Youbora/YouboraManager.swift index e7f9fc52..f0cbd1d5 100644 --- a/Plugins/Youbora/YouboraManager.swift +++ b/Plugins/Youbora/YouboraManager.swift @@ -227,7 +227,7 @@ extension YouboraManager { case let e where e.self == PlayerEvent.sourceSelected: messageBus.addObserver(self, events: [e.self]) { [weak self] event in guard let strongSelf = self else { return } - self?.lastReportedResource = event.contentURL?.absoluteString + self?.lastReportedResource = event.mediaSource?.playbackUrl?.absoluteString strongSelf.postEventLog(withMessage: "\(event.namespace))") } case let e where e.self == PlayerEvent.error: From 1a6a4a3b0f5b301e9931e8f9b260dea73a0b7b02 Mon Sep 17 00:00:00 2001 From: Eliza Sapir Date: Wed, 28 Jun 2017 17:58:45 +0300 Subject: [PATCH 7/7] bump dev version to 0.4.x-dev --- PlayKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayKit.podspec b/PlayKit.podspec index 0d9cdcd4..c35dd6e7 100644 --- a/PlayKit.podspec +++ b/PlayKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'PlayKit' -s.version = '0.2.x-dev' +s.version = '0.4.x-dev' s.summary = 'PlayKit: Kaltura Mobile Player SDK - iOS' s.homepage = 'https://github.com/kaltura/playkit-ios' s.license = { :type => 'AGPLv3', :text => 'AGPLv3' }