Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

How to Properly Integrate Google IMA SDK for Pre-roll, Mid-roll, and Post-roll Ads in a tvOS App using TVJS and Swift?

I'm working on a tvOS application that plays video content using TVMLKit/TVJS. I'm trying to integrate Google IMA SDK to show pre-roll, mid-roll, and post-roll ads in my app.

Here’s what I’ve done so far:

  • Video playback is handled through JavaScript in application.js.
  • Ads are managed in Swift using Google IMA SDK.
  • I use the evaluateJavaScript bridge to control video playback from Swift.
  • I pause and resume the TVJS player (Player object) using a function like startPlayingVideo(value) from Swift based on the ad lifecycle.

Current Flow:

  • When the video is about to start, I call loadAds() from JS.
  • This presents a Swift ViewController that handles IMA ad requests.
  • On adsManagerDidRequestContentPause, I pause the video using JS via the bridge.
  • On adsManagerDidRequestContentResume, I resume the video.

The Issue:

  • This setup doesn't behave consistently:
  • Sometimes the ad plays in the background and video started playing as well but can not see the ad.
  • Not able to see the post-roll ads

Relevant Code Snippets:

application.js

function startPlayingVideo(value) {
if (playerReference != undefined) {
if (value == true) {
playerReference.play();
else {
playerReference.pause();
   }
  }
}

function playVideo(videoURL) {
setup playerReference, push mediaItem, etc.
loadAds();
player.present();
}

AppDelegate.swift

let loadAds: @convention(block) () -\> Void = {
DispatchQueue.main.async {
let adManagerVC = ViewController()
AppDelegate.tvController?.navigationController.present(adManagerVC, animated: true)
}
}

let updateVideoPlayTime: @convention(block) (Double) -\> Void = { time in
CustomContentPlayhead.shared.currentTime = TimeInterval(time)
}

ViewController.swift

func adsManagerDidRequestContentPause(\_ adsManager: IMAAdsManager) {
showAdUI()
playerViewController.player?.pause()
}

func adsManagerDidRequestContentResume(\_ adsManager: IMAAdsManager) {
hideAdUI()
// Expecting JS video to resume via bridge
}

And yeah my IMSDK Implementation is working fine if I am using it with swift AVPlayer.

What I Need Help With:

  • Best practice for coordinating video playback between JS (Player) and Swift (IMAAdsManager).
  • How can I sync the playhead reliably between JS and Swift?
  • Is there a better way to pause/resume TVJS player from Swift during ad lifecycle?
  • How to reliably insert mid-roll ads when the playback is primarily controlled in JS?

Any insights, code examples, or recommended architecture improvements would be greatly appreciated!

How to Properly Integrate Google IMA SDK for Pre-roll, Mid-roll, and Post-roll Ads in a tvOS App using TVJS and Swift?
 
 
Q