Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* #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
  • Loading branch information
gal-orlanczyk authored Jun 28, 2017
1 parent 683d2c6 commit 3317496
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 151 deletions.
14 changes: 5 additions & 9 deletions Classes/Events/PlayerEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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])
}
}

Expand Down
17 changes: 1 addition & 16 deletions Classes/PKError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
}
}

Expand Down Expand Up @@ -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
/************************************************************/
Expand Down
6 changes: 3 additions & 3 deletions Classes/Player/PKEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion Classes/Player/PlayerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 0 additions & 40 deletions Plugins/AnalyticsCommon/BaseAnalyticsPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
/************************************************************/
Expand Down
50 changes: 34 additions & 16 deletions Plugins/Phoenix/BaseOTTAnalyticsPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}

/************************************************************/
Expand All @@ -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
Expand All @@ -83,21 +91,18 @@ 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
}
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 {
Expand All @@ -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()
Expand All @@ -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")
}
}
Expand Down Expand Up @@ -173,20 +187,24 @@ 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()
}

// 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
}
}

Expand Down
18 changes: 3 additions & 15 deletions Plugins/Phoenix/MediaMarkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
}
}
42 changes: 42 additions & 0 deletions Plugins/Phoenix/OTTAnalyticsPluginConfig.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 3317496

Please sign in to comment.