SSAI Session Expiry
In SSAI playbacks, ads are added dynamically from the server, so the server needs to keep a session for each playback to handle the ads properly. It's important for the server to end sessions when playback finishes or if the user takes action. Also, the server usually cleans up idle sessions after a while.
But in iOS apps, playback can pause for a long time, causing sessions to be removed from the server. When users come back to the app, errors can occur because the player tries to resume the previous playback.
To fix this, ad-serving companies suggest apps close the playback session before the app becomes idle. However, just relying on the app's background and foreground states isn't enough because some app features like Picture-in-Picture mode and Airplay support need special consideration. To prevent session problems, the app needs to handle these cases properly.
Picture-in-Picture
Scenario
- Open the iOS app
- Play any SSAI-enabled stream
- Put the app in the background; the stream should resume playing in PiP window
- Close the pip window
- Open the app after the SSAI session elapses, causing the player to throw a session expiry error.
Solution
Deinitialize the player instance when the PiP window is closed. This prevents the player from encountering errors when it becomes active after a session expiry.
func playerDidStopPictureInPicture() {
#if os(iOS)
// This will be called after restoring the pip as well.
// So check if the app is in a background state to identify pip close.
if UIApplication.shared.applicationState == .background, player.isLive {
// TODO: detach listeners self.player.stop();
}
#endif
}
Background / Foreground
Scenario 1: Pause on app background
- Open the iOS app
- Play any SSAI stream
- Put the app in the background: the playback should resumed on PiP window.
- Pause the playback on the PiP window.
- Open the app after the SSAI session keep-alive time has elapsed, causing the player to throw a session expiry error.
Solution
Deinitialize the player instance when the app is backgrounded and the playback is paused in the background. This prevents the player from encountering errors when it becomes active after a session expiry.
player.playbackState.add(self) { _, newState in
if player.isLive,
newState == .paused,
!player.isAirplayRouteActive, UIApplication.shared.applicationState == .background {
// TODO: detach listeners and stop the player
// TODO: Deinitialize the player
}
}
Scenario 2: Pause and app become background
- Open the iOS app.
- Play any SSAI stream.
- Pause the playback.
- Put the app on a background.
- Open the app after the SSAI session keep-alive time has elapsed, causing the player to throw a session expiry error.
Solution
Deinitialize the player instance when the app is backgrounded with a paused state. This prevents the player from encountering errors when it becomes active after a session expiry.
let appBGObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: UIApplication.shared, queue: OperationQueue.main) { [weak self] _ in
guard let self = self, let player = self.player else { return } if player.isLive,
!player.isAirplayRouteActive,
!player.isPictureInPictureActive {
// TODO: detach listeners and stop the player
// TODO: Deinitialize the player
}
}
Airplay
Scenario
- Open the iOS App.
- Play any Server-Side Ad Insertion (SSAI) stream.
- Airplay the playback on an external device.
- Put the iOS app in the background and close the playback on the external device using a remote.
- Open the app after the SSAI session keep-alive time has elapsed, causing the player to throw a session expiry error.
Solution
Deinitialize the player instance when the app is backgrounded and the Airplay device quits the playback. This prevents the player from encountering errors when it becomes active after a session expiry.
func playerAudioRouteDidChange(from: AVAudioSession.Port?, to: AVAudioSession.Port?) {
guard let from = from,
let to = to,
player.isLive,
from == .airPlay,
to != .airPlay,
UIApplication.shared.applicationState == .background {
// TODO: detach listeners and stop the player
// TODO: Deinitialize the player
}
}
Restart Playback Automatically
The goal is to retry the last playback when the user opens the app again. This involves acquiring a new content URL, creating a new player instance, and making it visible to the user.
let appFGObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: UIApplication.shared, queue: OperationQueue.main) { _ in
if self.player == nil {
//TODO: Perform Retry by creating a new player after acquiring the new playback URL
}
}
HTTP Failures
When encountering specific HTTP error codes, playback failures can be identified and reattempted when deemed suitable.
func playerDidFail(with error: FLError) {
// Read HTTP status code
if let statusCode = error.httpStatusCode {
switch statusCode {
case 403: // Example status code 403 (Forbidden)
// Stop & Dismiss the current Player
// Perform Retry Playback with Authorization
default:
// Handle other network status codes if needed
}
}
}
Refer to Handling Player Error section in Error Codes page for more details.
SSAI session expiry varies for different SSAI vendors and environments. Reach out to SSAI vendor to understand the configured expiry.