Skip to main content

Client Side Ad Insertion

The FLAdvertisingGoogleIMA library integrates with GoogleIMA to offer client-side ad insertion capability for both VOD and Live.

GoogleIMA​

This section describes the supported SDK versions and how to set up GoogleIMA.

Supported GoogleIMA SDK versions:

  • iOS 3.22.1
  • tvOS 4.12.0

Create ad request​

Create a GoogleIMAAdRequest with an ad tag URL. The following snippet shows ad request creation with a sample ad URL endpoint:

let adTagUrl =
"https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&" +
"iu=/124319096/external/ad_rule_samples&ciu_szs=300x250" +
"&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vast&" +
"cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&" +
"vid=short_onecue&correlator=&unviewed_position_start=1";
let adRequest = FLAdvertisingGoogleIMAFactory.adRequest(adTagUrl: adTagUrl);

Create ad player​

Create an ad player using the main content player, GoogleIMAAdRequest, and UIViewController. Note that you can't use the created ad player standalone because it manages only GoogleIMA ad playback. The main content playback isn't managed by this ad player. The view controller should be the parent of the playback view.

// player is the main content player
let adPlayer = FLAdvertisingGoogleIMAFactory.adPlayer(
contentPlayer: player,
request: adRequest,
playerViewController: viewController,
shouldSupportPIP: true
);

Note: Prefetch license is required for picture-in-picture support. Otherwise, you'll see DRM failures.

When pre-warming pre-roll ads via QPAdsLoader.shared, pass enablePrefetchAds: true. For detailed integration steps, see Pre-roll ad prefetch.

Create ad composed player​

AdComposablePlayer manages both the main content player and ad player, providing seamless main content playback with GoogleIMA ads served per the GoogleIMA ad response.

let adComposedPlayer = FLAdvertisingGoogleIMAFactory.adComposablePlayer(
contentPlayer: player,
adPlayer: adPlayer,
);

You can use this player like a conventional player from the FLPlayer library for playback.

Note: The GoogleIMA SDK provides the skip ad button if the ad is skippable. :::

Listening to Ad Events​

The client can set delegate on ad player and listen to ad events to track the state of ad playback and implement custom UIs. The AdPlayer invokes the following ad delegate methods:

/// Called on ad playback failure
func adPlayer(ad: Ad?, didFailWith error: Error)

/// Called when initial and regular cue points are available (deprecated)
func adPlayerDidProvide(cuepoints: [Int])

/// Called when the unwatched cue points change
func adPlayerDidProvide(original cuepoints: [Int], unwatched cues: [Int])

/// Called with ad-incoming countdown updates
func adIncoming(with label: AdIncomingType)

/// Called on ad break start
func adPlayerDidStart(adBreak: AdBreak)

/// Called on ad start
func adPlayerDidStart(ad: Ad)

/// Called on ad playback progress change
func adPlayer(ad: Ad?, didChange progress: TimeInterval)

/// Called on ad playback buffer state change
func adPlayer(ad: Ad?, didChangeBuffer isBuffering: Bool)

/// Called on occurence of ad track event
func adPlayer(ad: Ad?, didTrack event: AdTrackingEvent)

/// Called on ad end
func adPlayerDidEnd(ad: Ad)

/// Called on ad break end
func adPlayerDidEnd(adBreak: AdBreak)

Ad-incoming notifications and cue points​

For the ad-incoming countdown and the unwatched cue-point callbacks, see Incoming ad notification.

Ad Metadata​

AdBreak​

Represents an ad break object.

PropertyTypeDescription
positionAdPositionThe position of the ad break, can be on of preroll, midroll or postroll
startTimeTimeIntervalThe start position in seconds
durationTimeIntervalThe duration of the ad break, in seconds
totalAdsIntTotal number of adverts in the ad break
note

The totalAds read from AdPlayer may not be accurate when it comes from adPlayerDidStart event. This may change once ad playback starts. GoogleIMA advises to read totalAds on or after FIRST_QUARTILE ad tracking event, to get a reliable data.

Represents an advert object.

PropertyTypeDescription
adIdStringThe identifier of the advert
sequenceIntThe sequence of the advert
durationTimeIntervalThe duration of the advert in seconds
isSkippableBoolIndicates if the ad is skippable
adBreakAdBreakThe ad break which the advert belongs to
titleStringThe title of the ad
vastPropertiesVASTPropertiesVAST properties of advert
VASTProperties​

Represents a VAST properties object.

PropertyTypeDescription
skipOffsetTimeIntervalThe number of seconds before the skip button is presented
isAutoPlayBoolWhether the player will auto-play content
adPositionAdPositionThe position of the ad break, can be on of preroll, midroll or postroll
adSystemstringThe source ad server of the advert
AdTitlestringThe common name of the advert
adServingIdstringAn identifier used to compare impression-level data across systems
creativeIdstringCreativeId of the advert
categorystringCategory of the advert content
descriptionstringThe longer description of the advert
advertiserstringAdvertiser name
pricingstringPricing element of the advert
surveystringAn URI to any resource file having to do with collecting survey data
expiresstringNumber of seconds in which the ad is valid for execution

Events tracked as part of AdTrackingEvent​

EventDescription
adBreakStartFired when an ad break in a stream starts.
adStartFired when an ad starts playing.
adFirstQuartileFired when the ad playhead crosses first quartile.
adMidpointFired when the ad playhead crosses midpoint.
adThirdQuartileFired when the ad playhead crosses third quartile.
adPauseFired when an ad is paused.
adResumeFired when an ad is resumed.
adMuteFired when an ad is muted.
adUnmuteFired when an ad is unmuted.
adEndFired when an ad completes playing.
adBreakEndFired when an ad break in a stream ends.
adClickedFired when an ad is clicked.
adPlaybackViewTappedFired when a non-clickthrough portion of a video ad is clicked.
adIconTappedFired when ad icon is tapped.
adIconFallbackImageClosedFired on closing the icon fallback image dialog. This event only fires for Connected TV devices.
adSkippedFired when an ad was skipped.
adCompletedAllFired when all the valid ads in the ads response finished playing, or when the response doesn't return any valid ads.
adCuePointChangedFired when VOD stream cuepoints have changed.

Picture in Picture​

​In order to support Picture in Picture, GoogleIMA SDK uses the main content player to play ad assets. In the process of playing ads, the main content asset and ad assets are switched with the player. Whenever a fairplay protected asset is loaded with player on ad break end, a on demand request for license key is made by the player. So it is suggested to prefetch the license key to avoid player requesting on-demand license whever a ad playback ends and main content is resumed playback.

CCPA compliance​

The CCPA consent string must be sent as us_privacy query param with ad request url. Please check Google IMA SDK Documentation² to read further on for CCPA compliance. ​ The below code shows an ad request url with the IAB parameter "1YNN".

let adTagUrl =
'https://pubads.g.doubleclick.net/gampad/ads' +
'iu=/124319096/external/single_ad_samples&output=vast&us_privacy=1YNN';

Best Practices for Using AVPlayerViewController in tvOS​

Overview​

When integrating AVPlayerViewController in a tvOS application, it is recommended to use it as a child view controller within your playback view controller. This ensures that ads, ad UI, and focus management function correctly.

Implementation Example​

Below is an example of how to properly integrate AVPlayerViewController within a custom playback view controller.

import UIKit
import AVKit
import FLPlayer
import FLPlayerInterface

class QPAVPlayerViewController: UIViewController {
// AVPlayerViewController instance for handling playback
private var playerViewController: AVPlayerViewController = AVPlayerViewController()

// Quickplay Player instance (set after the ViewController is created)
var qpPlayer: Player? {
didSet {
if let qpPlayer = qpPlayer, let avPlayer = qpPlayer.rawPlayer as? AVPlayer {
embedAVPlayerViewController()
playerViewController.player = avPlayer
addPlaybackView()
}
}
}

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
// Ensure a smooth playback transition
}

// Embeds AVPlayerViewController within the current ViewController
private func embedAVPlayerViewController() {
addChild(playerViewController)
playerViewController.view.frame = view.bounds
view.insertSubview(playerViewController.view, at: 0)
playerViewController.didMove(toParent: self)
}

// Attaches the player’s playback view to AVPlayerViewController
private func addPlaybackView() {
guard let playbackView = qpPlayer?.playbackView else { return }
playbackView.frame = playerViewController.view.bounds
playerViewController.view.insertSubview(playbackView, at: 0)
}
}

Key Considerations​

  • Encapsulation: AVPlayerViewController should always be contained within your playback view controller to properly handle media playback and interactions.
  • Focus & UI Handling: Using AVPlayerViewController ensures seamless integration of ads, player controls, and focus management on tvOS.
  • Full-Screen Playback: The example assumes that playback occurs in full screen, which is the default behavior on tvOS.
  • Efficient View Management: The playerViewController instance is properly added and removed using addChild(_:) and didMove(toParent:) to ensure memory management best practices.

By following these best practices, you can ensure a robust and scalable media playback experience on tvOS.


Ad Frequency Control (AFC)​

To prevent ad fatigue by suppressing repeated ad breaks based on content consumption and exposure thresholds, refer to the Ad Frequency Control documentation.


Pre-roll ad prefetch​

Pre-roll ad prefetch lets you request and warm up a Google IMA pre-roll ad break before the user starts playback. When the user selects a video, the player immediately uses the prefetched pre-roll ad, which eliminates initial ad buffering and network latency.

Prefetching operates via QPAdsLoader.shared in FLAdvertisingGoogleIMA, warming up the IMAAdsManager in advance so the ad break is ready to play instantly.

Integration & usage guide​

1. Initializing ad loader & prefetching​

Call initAdsLoader(adRequest:) early in your application or stream setup sequence (such as during app launch or catalog display) to kick off prefetching:

import FLAdvertisingGoogleIMA

let adRequest = FLAdvertisingGoogleIMAFactory.adRequest(adTagUrl: "https://pubads.g.doubleclick.net/gampad/ads?...")
QPAdsLoader.shared.initAdsLoader(adRequest: adRequest)
Ad request options for prefetching​

QPAdsLoader.shared.initAdsLoader(adRequest:) takes a GoogleIMAAdRequest configured with the pre-roll ad tag:

ParameterTypeRequiredDescription
adTagUrlStringYesThe pre-roll VAST or VMAP ad tag endpoint URL.
loadMediaTimeoutTimeInterval?NoTimeout for loading the video ad media file (defaults to 8 seconds).
adOverlayUIScope[AdOverlayUIScope]?NoViews to be treated as friendly obstructions by Google IMA.
imaUiElementOptionIMAUiElementOptionNoControls visibility of ad UI elements (countdown timer, attribution).
playAdsAfterTimeTimeInterval?NoOffset in seconds to resume and only play ads scheduled after that time.

2. Creating composable player via factory​

Use FLAdvertisingGoogleIMAFactory to construct GoogleIMAAdPlayer with enablePrefetchAds (set to true to consume prefetched ads, or false to prevent consuming them) and wrap it with AdComposablePlayer:

info

Providing the enablePrefetchAds parameter (whether set to true or false) routes playback through the new AdvanceAdComposablePlayer (advanceCsaiPlayer) flow instead of the legacy player flow.

import FLPlayer
import FLAdvertisingGoogleIMA

// Create CSAI Ad Player enabling prefetched ads
let adPlayer = FLAdvertisingGoogleIMAFactory.adPlayer(
contentPlayer: mainContentPlayer,
request: adRequest,
shouldSupportPIP: true,
enablePrefetchAds: true,
playerViewControllerCallback: { presentingVC }
)

// Construct AdvanceAdComposablePlayer
let composablePlayer = FLAdvertisingGoogleIMAFactory.adComposablePlayer(
contentPlayer: mainContentPlayer,
adPlayer: adPlayer
)

// Begin playback
composablePlayer.play()
Prefetched pre-roll ad consumption
  • Providing enablePrefetchAds (true or false) engages the new AdvanceAdComposablePlayer flow.
  • Prefetching is handled at the app level. It can happen regardless of the enablePrefetchAds setting.
  • Consumption is controlled by enablePrefetchAds. It decides whether playback uses an already-prefetched pre-roll ad.
enablePrefetchAdsPrefetched ad availableBehavior
trueYesPlayback consumes the prefetched pre-roll ad.
trueNoPlayback follows the normal ad request flow.
falseYesThe prefetched ad is not consumed, even though it was already prefetched.
falseNoPlayback follows the normal ad request flow.

Setting enablePrefetchAds to false does not stop the app from prefetching ads. It only prevents playback from using them.

3. Playback lifecycle & event handling​

AdvanceAdComposablePlayer handles switching between ads and main content automatically:

// Observe player state changes
composablePlayer.addStateChangeBlock { player in
print("Current Player State: \(player.playbackState.value)")
}

// Pause or resume composable player
composablePlayer.pause()
composablePlayer.play()

When content finishes playing, inform the ads loader:

@objc func contentDidFinishPlaying(notification: NSNotification) {
QPAdsLoader.shared.adsLoader?.contentComplete()
}

4. Deinitializing ads loader​

Call QPAdsLoader.shared.deinitAdsLoader() to reset the shared ads loader during user profile changes or login / logout:

// Stop composable player
composablePlayer.stop()

// Deinitialize shared loader and clear prefetch manager
QPAdsLoader.shared.deinitAdsLoader()
Prefetched ad lifecycle

QPAdsLoader.shared.deinitAdsLoader() is used in the following scenarios:

  • Login / Logout flow: Call deinitAdsLoader() when the user logs in or logs out.
  • User profile change: When switching profiles, call deinitAdsLoader() before the profile change, and then call initAdsLoader(adRequest:) with the new profile's ad tag URL so the player does not reuse the previous profile's prefetched ad break.