Return to a skipped ad break

  • 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.

Select platform: HTML5 Android iOS tvOS Roku

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:

  1. Check if the user ran a seek that jumped past an unwatched ad break, and if so, take them back to the ad break.
  2. 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:

  1. When the user begins interacting with the seek bar, record their current playback time.
  2. After the user finishes seeking to a different time in the stream, identify the most recent ad break located before this time.
  3. 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.
  4. Enable a snapbackMode flag 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:

  1. Listen for the AD_BREAK_ENDED event in your stream manager.

  2. Check if the snapbackMode flag is active to make sure this jump happens after a forced ad view.

  3. 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 
 ) 
  
 
Create a Mobile Website
View Site in Mobile | Classic
Share by: