Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEM-2421 Add "alwaysStartWithPreroll" boolean. #353

Merged
merged 3 commits into from
Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 12 additions & 1 deletion Classes/Player/Ads/AdsDAIPlayerEngineWrapper.swift
Expand Up @@ -267,6 +267,17 @@ public class AdsDAIPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate,
let endTime = adsPlugin.streamTime(forContentTime: time)
guard !adsPlugin.isAdPlaying else { return }

if snapbackMode {
return
}

if isFirstPlay && startPosition != 0 && adsPlugin.startWithPreroll && pkAdDAICuePoints.hasPreRoll {
startPosition = 0
snapbackMode = true
snapbackTime = endTime
return
}

if !isFirstPlay {
let startTime = super.currentPosition
if startTime < endTime {
Expand Down Expand Up @@ -371,7 +382,7 @@ public class AdsDAIPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate,

public func adsPlugin(_ adsPlugin: AdsPlugin, loaderFailedWith error: String) {
// The loader can fail also when going to the background, therefore adding the retry here as well.
if adRequestTimedOutRetries < maxAdRequestTimedOutRetries {
if adRequestTimedOutRetries < maxAdRequestTimedOutRetries, prepareMediaConfig != nil {
adRequestTimedOutRetries += 1
prepare(prepareMediaConfig)
} else {
Expand Down
53 changes: 41 additions & 12 deletions Classes/Player/Ads/AdsPlayerEngineWrapper.swift
Expand Up @@ -4,11 +4,11 @@ import Foundation

/// `AdsPlayerEngineWrapperState` represents `AdsPlayerEngineWrapper` state machine states.
enum AdsPlayerEngineWrapperState: Int, StateProtocol {
/// initial state.
/// Initial state.
case start = 0
/// when prepare was requested for the first time and it is stalled until ad started (preroll) / faliure or content resume
/// When prepare was requested for the first time and it is stalled until ad started (preroll) / faliure or content resume
case waitingForPrepare
/// a moment before we called prepare until prepare() was finished (the sychornos code only not async tasks)
/// A moment before we called prepare until prepare() was finished (the sychornos code only not async tasks)
case preparing
/// Indicates when prepare() was finished (the sychornos code only not async tasks)
case prepared
Expand All @@ -23,16 +23,24 @@ public class AdsPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate, Ads
/// Uses @NSCopying in order to make a copy whenever set with new value.
@NSCopying private var prepareMediaConfig: MediaConfig!

/// indicates if play was used, if `play()` or `resume()` was called we set this to true.
/// Indicates if play was used, if `play()` or `resume()` was called we set this to true.
private var isPlayEnabled = false
/// Indicates if it's the first time we are starting the stream.
private var isFirstPlay = true

/// a semaphore to make sure prepare calling will not be reached from 2 threads by mistake.
/// A semaphore to make sure prepare calling will not be reached from 2 threads by mistake.
private let prepareSemaphore = DispatchSemaphore(value: 1)

/// when playing post roll google sends content resume when finished.
/// When playing post roll google sends content resume when finished.
/// In our case we need to prevent sending play/resume to the player because the content already ended.
var shouldPreventContentResume = false

var pkAdCuePoints: PKAdCuePoints = PKAdCuePoints(cuePoints: [])

/// Maintains seeking status for snapback to the start position if set to always start with the pre-roll.
private var snapbackTime: TimeInterval = 0
private var snapbackMode: Bool = false

private var adsPlugin: AdsPlugin

public init(adsPlugin: AdsPlugin) {
Expand All @@ -48,9 +56,9 @@ public class AdsPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate, Ads
// MARK: - Private
/************************************************************/

/// prepare the player only if wasn't prepared yet.
/// Prepare the player only if wasn't prepared yet.
private func preparePlayerIfNeeded() {
self.prepareSemaphore.wait() // use semaphore to make sure will not be called from more than one thread by mistake.
self.prepareSemaphore.wait() // Use semaphore to make sure will not be called from more than one thread by mistake.

if self.stateMachine.getState() == .waitingForPrepare {
self.stateMachine.set(state: .preparing)
Expand All @@ -76,6 +84,8 @@ public class AdsPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate, Ads
self.adsPlugin.destroyManager()
self.isPlayEnabled = false
self.shouldPreventContentResume = false
isFirstPlay = true
snapbackMode = false

self.stateMachine.set(state: .waitingForPrepare)
self.prepareMediaConfig = config
Expand All @@ -88,6 +98,16 @@ public class AdsPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate, Ads

override public func play() {
self.isPlayEnabled = true

if isFirstPlay {
isFirstPlay = false
if let startTime = mediaConfig?.startTime, startTime > 0 && adsPlugin.startWithPreroll && pkAdCuePoints.hasPreRoll {
startPosition = 0
snapbackMode = true
snapbackTime = startTime
}
}

self.adsPlugin.didRequestPlay(ofType: .play)
}

Expand Down Expand Up @@ -153,10 +173,19 @@ public class AdsPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate, Ads

public func adsPlugin(_ adsPlugin: AdsPlugin, didReceive event: PKEvent) {
switch event {
case is AdEvent.AdCuePointsUpdate:
if let adCuePoints = event.adCuePoints {
pkAdCuePoints = adCuePoints
}
case is AdEvent.AdDidRequestContentPause:
super.pause()
case is AdEvent.AdDidRequestContentResume:
if !self.shouldPreventContentResume {
if snapbackMode {
snapbackMode = false
playerEngine?.seek(to: snapbackTime)
}

self.preparePlayerIfNeeded()
super.resume()
}
Expand All @@ -165,12 +194,12 @@ public class AdsPlayerEngineWrapper: PlayerEngineWrapper, AdsPluginDelegate, Ads
case is AdEvent.AdResumed:
self.isPlayEnabled = true
case is AdEvent.AdStarted:
// when starting to play pre roll start preparing the player.
// When starting to play pre roll start preparing the player.
if event.adInfo?.positionType == .preRoll {
self.preparePlayerIfNeeded()
}
case is AdEvent.AdBreakReady, is AdEvent.AdLoaded:
if self.shouldPreventContentResume == true { return } // no need to handle twice if already true
if self.shouldPreventContentResume == true { return } // No need to handle twice if already true
if event.adInfo?.positionType == .postRoll {
self.shouldPreventContentResume = true
}
Expand Down Expand Up @@ -205,9 +234,9 @@ extension AdsPlayerEngineWrapper: AppStateObservable {
return [
NotificationObservation(name: UIApplication.didEnterBackgroundNotification) { [weak self] in
guard let self = self else { return }
// when we enter background make sure to pause if we were playing.
// When we enter background make sure to pause if we are playing.
self.pause()
// notify the ads plugin we are entering to the background.
// Notify the ads plugin we are entering the background.
self.adsPlugin.didEnterBackground()
},
NotificationObservation(name: UIApplication.willEnterForegroundNotification) { [weak self] in
Expand Down
2 changes: 2 additions & 0 deletions Classes/Player/Ads/AdsPlugin.swift
Expand Up @@ -47,6 +47,8 @@ public protocol AdsPlugin: PKPlugin {
var delegate: AdsPluginDelegate? { get set }
/// Is ad currently playing.
var isAdPlaying: Bool { get }
/// Whether or not the pre-roll should be played upon start position different than 0.
var startWithPreroll: Bool { get }
/// Request ads from the server.
func requestAds() throws
/// Resume ad.
Expand Down