Page Summary
-
Snapback is a feature that prevents viewers from seeking past mid-roll ads by returning them to the start of an ad break if they attempt to do so.
-
After the ad break completes, snapback then returns the viewer to their original desired seek location.
-
Implementing snapback involves checking if a seek skipped an unwatched ad break and, if so, seeking back to the ad break start, followed by returning the user to their original seek point after the ad break finishes.
As a video publisher, you may want to prevent your viewers from seeking past your mid-roll ads. When a user seeks past an ad break, you can take them back to the start of that ad break, and then return them to their seek location after that ad break has completed. This feature is called "snapback."
As an example, see the diagram below. Your viewer is watching a video, and decides to seek from the 5-minute mark to the 15-minute mark. There is, however, an ad break at the 10-minute mark that you want them to watch before they can watch the content after it:

In order to show this ad break, take the following steps:
- Check if the user ran a seek that jumped past an unwatched ad break, and if so, take them back to the ad break.
- After the ad break completes, return them to their original seek.
In diagram form, that looks like this:

Here's how to implement this workflow in the IMA DAI SDK, as done in the AdvancedExample.
Prevent skipping unwatched ads
If a user attempts to skip over an ad break, the player must detect the jump and force playback to the start of that specific ad break. To prevent skipping unwatched ads, do the following:
- When the user begins interacting with the seek bar, record their current playback time.
- After the user finishes seeking to a different time in the stream, identify the most recent ad break located before this time.
- If the ad break starts after the recorded start time, indicating a skip, and has not yet been played, seek the player to the beginning of the ad break.
- Enable a
snapbackModeflag to track that this ad break was forced.
Objective-C
- (IBAction)videoControlsTouchStarted:(id)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(hideFullscreenControls)
object:self];
self.currentlySeeking = YES;
self.seekStartTime = self.contentPlayer.currentTime;
}
- (IBAction)videoControlsTouchEnded:(id)sender {
if (self.fullscreen) {
[self startHideControlsTimer];
}
self.currentlySeeking = NO;
if (!self.adPlaying) {
self.seekEndTime = CMTimeMake(self.progressBar.value, 1);
IMACuepoint *lastCuepoint =
[self.streamManager previousCuepointForStreamTime:CMTimeGetSeconds(self.seekEndTime)];
if (!lastCuepoint.played && (lastCuepoint.startTime > CMTimeGetSeconds(self.seekStartTime))) {
self.snapbackMode = YES;
// Add 1 to the seek time to get the keyframe at the start of the ad to be our landing
// place.
[self.contentPlayer
seekToTime:CMTimeMakeWithSeconds(lastCuepoint.startTime + 1, NSEC_PER_SEC)];
}
}
}
Swift
@IBAction func progressBarTouchStarted(_ sender: UISlider) {
guard !isAdPlaying else { return }
currentlySeeking = true
seekStartTime = contentPlayer.currentTime().seconds
}
// MARK: Snapback Logic
@IBAction func progressBarTouchEnded(_ sender: UISlider) {
guard !isAdPlaying else { return }
if isFullScreen {
startHideControlsTimer()
}
currentlySeeking = false
seekEndTime = Float64(sender.value)
guard let streamManager else { return }
if let lastCuepoint = streamManager.previousCuepoint(forStreamTime: seekEndTime) {
if !lastCuepoint.isPlayed, lastCuepoint.startTime > seekStartTime {
logMessage(
"Snapback to \(String(format: "%.2f", lastCuepoint.startTime)) from \(String(format: "%.2f", seekEndTime))"
)
snapbackMode = true
contentPlayer.seek(
to: CMTime(seconds: Double(sender.value), preferredTimescale: 1000))
}
}
}
Resume the original seek
After the forced ad break has finished playing, the player brings the user to the intended content point.
To resume the user's original seek, do the following:
Listen for the
AD_BREAK_ENDEDevent in your stream manager.Check if the
snapbackModeflag is active to make sure this jump happens after a forced ad view.If active, seek the player to the saved destination time to return the user to the intended timestamp.
The following example listens for a finished ad break and returns a user to the original seek:
Objective-C
case kIMAAdEvent_AD_BREAK_ENDED: {
[self logMessage:@"Ad break ended"];
self.adPlaying = NO;
if (self.snapbackMode) {
self.snapbackMode = NO;
if (CMTimeCompare(self.seekEndTime, self.contentPlayer.currentTime)) {
[self.contentPlayer seekToTime:self.seekEndTime];
}
}
break;
}
Swift
case .AD_BREAK_ENDED:
logMessage("Ad break ended")
isAdPlaying = false
progressBar.isUserInteractionEnabled = true
if snapbackMode {
snapbackMode = false
if contentPlayer.currentTime().seconds < seekEndTime {
contentPlayer.seek(to: CMTime(seconds: Double(seekEndTime), preferredTimescale: 1000))
}
} else if pendingBookmarkSeek, let time = bookmarkStreamTime {
logMessage(String(format: "AD_BREAK_ENDED: Seeking to bookmark streamTime: %.2f", time))
imaVideoDisplay.seekStream(toTime: time)
pendingBookmarkSeek = false
bookmarkStreamTime = nil
}
updatePlayHeadState(isPlaying: self.isContentPlaying)