Paused Ads on tvOS

Let StreamLayer deliver a paused ad automatically on each ad break on tvOS — sourced from Studio or Prebid — by signalling pause/resume and selecting the delivery source.

Paused ads are StreamLayer-delivered ads shown automatically when the viewer pauses. Your app only signals the pause and resume; StreamLayer decides which ad to show and renders it. The ad is sourced either from Studio (server-driven inventory and rotation, the default) or from Prebid (an SDK-managed programmatic auction). This flow is tvOS-only.

Paused ads vs. exposed ads. With paused ads, your app signals when (an ad break) and StreamLayer chooses what (Studio or Prebid). With exposed ads, your app decides both — it calls showAd(_:) / showVASTAd(_:vastURL:) on demand and supplies the creative. Use paused ads when you want StreamLayer to manage delivery; use exposed ads when you want to drive the content yourself.

Prerequisites

  1. Complete the tvOS Integration Guide.
  2. Create an event session and the StreamLayer Element:
StreamLayer.createSession(for: "your-event-id")

let slrViewController = StreamLayer.createOverlay(
    containerViewController: self,
    contentView: containerView,
    delegate: self
)

Signal the ad break

Paused ads are triggered by an ad break. Call StreamLayer.startADBreak() when the viewer pauses and StreamLayer.stopADBreak() when playback resumes. Add a delay so brief or accidental pauses don't start a break — a 3–5 second delay is common. Use a DispatchWorkItem you can cancel if playback resumes first.

private var adBreakWorkItem: DispatchWorkItem?

func handlePlayingState() {
    StreamLayer.stopADBreak()
    adBreakWorkItem?.cancel()
    adBreakWorkItem = nil
}

func handlePausedState() {
    let workItem = DispatchWorkItem {
        StreamLayer.startADBreak()
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: workItem)
    adBreakWorkItem = workItem
}

When the ad break starts, StreamLayer presents a paused ad from the active delivery source (below) automatically — no additional call or delegate is required. See Pause Advertising for more on wiring startADBreak / stopADBreak to your player.

Choose the delivery source

setPausedAdDelivery(_:) selects where paused ads come from. The SDK owns the delivery lifecycle (auction, caching, expiry, presentation); you only choose the source.

public static func setPausedAdDelivery(_ delivery: SLRPausedAdDelivery)
public enum SLRPausedAdDelivery {
    case standard
    case prebid(SLRPrebidPlacement)
}

Studio (standard) — the default

.standard delivers server-driven paused ads configured in StreamLayer Studio, with inventory and rotation managed for you. It is the default — paused ads work on ad breaks without calling setPausedAdDelivery at all. Call it explicitly to switch back from Prebid:

StreamLayer.setPausedAdDelivery(.standard)

Prebid

.prebid(_:) enables an SDK-managed Prebid programmatic source. StreamLayer preloads a winning bid so an ad is ready the instant an ad break starts, then presents it through the VAST sidebar / L-bar pipeline. Opt in once with an SLRPrebidPlacement:

func enablePrebidPausedAds() {
    ATTrackingManager.requestTrackingAuthorization { _ in
        DispatchQueue.main.async {
            StreamLayer.setPausedAdDelivery(
                .prebid(SLRPrebidPlacement(
                    storedRequestId: "sl-pause-tvos",
                    demandSignals: {
                        SLRPrebidDemandSignals(
                            advertisingIdentifier: ASIdentifierManager.shared().advertisingIdentifier.uuidString,
                            isLimitAdTrackingEnabled: false
                        )
                    }
                ))
            )
        }
    }
}

SLRPrebidPlacement fields:

  • storedRequestId — the stored request configured for your placement in the auction backend.
  • demandSignals — a closure returning per-request signals (advertising identifier, limit-ad-tracking flag), re-read fresh on every auction. Return SLRPrebidDemandSignals.limited when the user has not granted tracking authorization.
  • cannedResponse — an optional SLRPrebidCannedResponse for testing against a stored auction response instead of a live auction.
// Testing against a stored auction response
StreamLayer.setPausedAdDelivery(
    .prebid(SLRPrebidPlacement(
        storedRequestId: "sl-pause-tvos",
        cannedResponse: SLRPrebidCannedResponse(
            storedAuctionResponseId: "mock_video_1",
            bidderPlacements: ["appnexus": 13144370]
        )
    ))
)

Request tracking authorization (ATTrackingManager.requestTrackingAuthorization) before building live demand signals so the advertising identifier is available. The same SLRPrebidPlacement type also drives on-demand showAd(.prebid(_:)) in Exposed Ads (tvOS); the difference is that setPausedAdDelivery(.prebid) preloads a bid for automatic ad-break delivery, whereas showAd(.prebid) runs a fresh auction on demand.

Resume playback

When the viewer dismisses the paused ad, the SDK calls streamLayerDelegateAdDidFinish(_:) on your StreamLayerTVOSDelegate, passing an SLRAdInfo. For paused ads info.isPausedAd is true — resume playback and clear the ad break there. The optional streamLayerDelegateAdDidStart(_:) fires when the ad begins presenting.

extension YourViewController: StreamLayerTVOSDelegate {
    func streamLayerDelegateAdDidStart(_ info: SLRAdInfo) {}

    func streamLayerDelegateAdDidFinish(_ info: SLRAdInfo) {
        guard info.isPausedAd else { return }
        player.play()
        StreamLayer.stopADBreak()
        adBreakWorkItem?.cancel()
        adBreakWorkItem = nil
    }

    func streamLayerDelegateUpdateDuckingState(_ enabled: Bool) {
        player.volume = enabled ? 0.2 : 1.0
    }
}

Related