Select platform: [HTML5](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/snapback "View this page for the HTML5 platform docs.") [Android](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/android/snapback "View this page for the Android platform docs.") [iOS](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/ios/snapback "View this page for the iOS platform docs.") [tvOS](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/tvos/snapback "View this page for the tvOS platform docs.") [Roku](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/roku/snapback "View this page for the Roku platform docs.")

<br />

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:

![](https://developers.google.com/static/interactive-media-ads/images/dai_snapback1.png)

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:

![](https://developers.google.com/static/interactive-media-ads/images/dai_snapback2.png)

Here's how to implement this workflow in the IMA DAI SDK, as done in the
[AdvancedExample](https://github.com/googleads/googleads-ima-ios-dai).

## 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)