Skip to main content
Track video advertisement viewing to understand ad engagement and enable ad-based segmentation.

Overview

Video ad tracking enables:
  • Ad impression tracking when ads start
  • Ad completion tracking when ads finish
  • Ad engagement metrics for targeting
  • Ad-based cohort qualification

Tracking Video Ads

Track video ad events using the standard event tracking API:
import Permutive_iOS

class VideoAdTracker {

    func trackAdImpression(ad: Ad, position: AdPosition) {
        try? Permutive.shared.track(
            event: "VideoAdImpression",
            properties: try? EventProperties([
                "ad_id": ad.id,
                "ad_title": ad.title,
                "ad_duration": ad.duration,
                "ad_position": position.rawValue,  // "pre-roll", "mid-roll", "post-roll"
                "advertiser": ad.advertiser,
                "campaign_id": ad.campaignId
            ])
        )
    }

    func trackAdComplete(ad: Ad, watchedPercentage: Float) {
        try? Permutive.shared.track(
            event: "VideoAdComplete",
            properties: try? EventProperties([
                "ad_id": ad.id,
                "ad_title": ad.title,
                "watched_percentage": watchedPercentage,
                "completed": watchedPercentage >= 0.95
            ])
        )
    }

    func trackAdSkipped(ad: Ad, skippedAtSeconds: Double) {
        try? Permutive.shared.track(
            event: "VideoAdSkipped",
            properties: try? EventProperties([
                "ad_id": ad.id,
                "skipped_at_seconds": skippedAtSeconds,
                "total_duration": ad.duration
            ])
        )
    }

    func trackAdClicked(ad: Ad) {
        try? Permutive.shared.track(
            event: "VideoAdClicked",
            properties: try? EventProperties([
                "ad_id": ad.id,
                "click_url": ad.clickThroughUrl
            ])
        )
    }
}

enum AdPosition: String {
    case preRoll = "pre-roll"
    case midRoll = "mid-roll"
    case postRoll = "post-roll"
}

Google IMA Integration

Integrate with Google Interactive Media Ads (IMA) SDK:
import GoogleInteractiveMediaAds
import Permutive_iOS

class VideoPlayerViewController: UIViewController, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
    private var adsLoader: IMAAdsLoader!
    private var adsManager: IMAAdsManager?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupAdsLoader()
    }

    private func setupAdsLoader() {
        adsLoader = IMAAdsLoader(settings: nil)
        adsLoader.delegate = self
    }

    func requestAds() {
        // Include Permutive targeting in ad request
        let adRequest = GAMRequest()
        adRequest.customTargeting = Permutive.shared.googleCustomTargeting(
            adTargetable: pageTracker  // Include if using PageTracker
        )

        let adDisplayContainer = IMAAdDisplayContainer(
            adContainer: adContainerView,
            viewController: self
        )

        let request = IMAAdsRequest(
            adTagUrl: adTagUrl,
            adDisplayContainer: adDisplayContainer,
            contentPlayhead: contentPlayhead,
            userContext: nil
        )

        adsLoader.requestAds(with: request)
    }

    // MARK: - IMAAdsManagerDelegate

    func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) {
        switch event.type {
        case .STARTED:
            trackAdStarted(event.ad)

        case .COMPLETE:
            trackAdCompleted(event.ad)

        case .SKIPPED:
            trackAdSkipped(event.ad)

        case .CLICKED:
            trackAdClicked(event.ad)

        case .FIRST_QUARTILE:
            trackAdProgress(event.ad, quartile: 1)

        case .MIDPOINT:
            trackAdProgress(event.ad, quartile: 2)

        case .THIRD_QUARTILE:
            trackAdProgress(event.ad, quartile: 3)

        default:
            break
        }
    }

    private func trackAdStarted(_ ad: IMAAd?) {
        guard let ad = ad else { return }

        try? Permutive.shared.track(
            event: "VideoAdStarted",
            properties: try? EventProperties([
                "ad_id": ad.adId,
                "ad_title": ad.adTitle,
                "duration": ad.duration,
                "is_skippable": ad.isSkippable,
                "ad_system": ad.adSystem,
                "advertiser": ad.advertiserName
            ])
        )
    }

    private func trackAdCompleted(_ ad: IMAAd?) {
        guard let ad = ad else { return }

        try? Permutive.shared.track(
            event: "VideoAdComplete",
            properties: try? EventProperties([
                "ad_id": ad.adId,
                "ad_title": ad.adTitle,
                "duration": ad.duration
            ])
        )
    }

    private func trackAdSkipped(_ ad: IMAAd?) {
        guard let ad = ad else { return }

        try? Permutive.shared.track(
            event: "VideoAdSkipped",
            properties: try? EventProperties([
                "ad_id": ad.adId,
                "ad_title": ad.adTitle
            ])
        )
    }

    private func trackAdClicked(_ ad: IMAAd?) {
        guard let ad = ad else { return }

        try? Permutive.shared.track(
            event: "VideoAdClicked",
            properties: try? EventProperties([
                "ad_id": ad.adId
            ])
        )
    }

    private func trackAdProgress(_ ad: IMAAd?, quartile: Int) {
        guard let ad = ad else { return }

        try? Permutive.shared.track(
            event: "VideoAdProgress",
            properties: try? EventProperties([
                "ad_id": ad.adId,
                "quartile": quartile,
                "percentage": quartile * 25
            ])
        )
    }
}

Ad Pod Tracking

Track ad pods (multiple ads in sequence):
class AdPodTracker {

    func trackAdPodStarted(podIndex: Int, adCount: Int) {
        try? Permutive.shared.track(
            event: "AdPodStarted",
            properties: try? EventProperties([
                "pod_index": podIndex,
                "ad_count": adCount,
                "position": podIndex == 0 ? "pre-roll" : "mid-roll"
            ])
        )
    }

    func trackAdPodComplete(podIndex: Int, adsWatched: Int, totalAds: Int) {
        try? Permutive.shared.track(
            event: "AdPodComplete",
            properties: try? EventProperties([
                "pod_index": podIndex,
                "ads_watched": adsWatched,
                "total_ads": totalAds,
                "completion_rate": Float(adsWatched) / Float(totalAds)
            ])
        )
    }
}

Combining with Content Tracking

Track both content and ads in a video player:
class VideoPlayerController {
    private var mediaTracker: MediaTrackerProtocol?
    private var isPlayingAd = false

    // Content tracking
    func onContentPlay() {
        guard !isPlayingAd else { return }
        try? mediaTracker?.play()
    }

    func onContentPause() {
        guard !isPlayingAd else { return }
        mediaTracker?.pause()
    }

    // Ad tracking
    func onAdBreakStarted() {
        isPlayingAd = true
        mediaTracker?.pause()  // Pause content tracking during ads
    }

    func onAdBreakEnded() {
        isPlayingAd = false
        // Content will resume with onContentPlay()
    }

    func onAdStarted(_ ad: Ad) {
        trackAdImpression(ad: ad)
    }

    func onAdCompleted(_ ad: Ad) {
        trackAdComplete(ad: ad)
    }

    private func trackAdImpression(ad: Ad) {
        try? Permutive.shared.track(
            event: "VideoAdImpression",
            properties: try? EventProperties([
                "ad_id": ad.id,
                "ad_title": ad.title
            ])
        )
    }

    private func trackAdComplete(ad: Ad) {
        try? Permutive.shared.track(
            event: "VideoAdComplete",
            properties: try? EventProperties([
                "ad_id": ad.id
            ])
        )
    }
}

Event Schema

Common video ad event properties:
PropertyTypeDescription
ad_idStringUnique ad identifier
ad_titleStringAd title or description
durationNumberAd duration in seconds
ad_positionString”pre-roll”, “mid-roll”, “post-roll”
advertiserStringAdvertiser name
campaign_idStringCampaign identifier
is_skippableBooleanWhether ad can be skipped
watched_percentageNumberPercentage of ad watched (0-1)
Ensure your event properties match your Permutive dashboard schema. Mismatched properties result in event rejection.

tvOS Considerations

tvOS Note: Video ad tracking works the same on tvOS. Use the same event tracking APIs with your TVML or UIKit video player.

Best Practices

  • Track ad impressions when ads actually start playing
  • Include ad position (pre-roll, mid-roll, post-roll)
  • Track quartile progress for engagement analysis
  • Pause content tracking during ad breaks
  • Include meaningful ad metadata

Troubleshooting

Problem: Video ad events not in dashboard.Solutions:
  • Enable debug logging
  • Check event names match schema
  • Verify SDK is initialized
  • Look for schema validation errors
Problem: Permutive targeting not applied to ad requests.Solutions:
  • Ensure googleCustomTargeting is called before ad request
  • Verify PageTracker is active when requesting ads
  • Check targeting dictionary is applied to GAMRequest