Skip to main content
The Permutive SDK provides MediaTracker for tracking video content viewing. It handles video play/pause/complete events and engagement time automatically.

Overview

MediaTracker provides:
  • Automatic video events (play, pause, complete)
  • Progress tracking at configurable intervals
  • Engagement time measurement
  • Contextual cohort generation when URLs are provided
Only one MediaTracker (or PageTracker) can be active at a time. Creating a new tracker automatically stops any existing one.

Creating a MediaTracker

import Permutive_iOS
import AVKit

class VideoPlayerViewController: UIViewController {
    private var mediaTracker: MediaTrackerProtocol?
    private var player: AVPlayer!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Video duration in milliseconds
        let videoDurationMs = Int64(video.duration * 1000)

        // Create the MediaTracker
        mediaTracker = try? Permutive.shared.createMediaTracker(
            durationMilliseconds: videoDurationMs,
            videoProperties: MediaTracker.VideoProperties(
                title: "Introduction to Swift",
                description: "Learn the basics of Swift programming",
                genre: ["Technology", "Education"],
                contentType: ["Video", "Tutorial"],
                source: URL(string: "https://example.com/videos/swift-intro.mp4")
            ),
            pageProperties: MediaTracker.PageProperties(
                title: "Swift Tutorial",
                url: URL(string: "https://example.com/tutorials/swift-intro"),
                referrer: URL(string: "https://example.com/tutorials")
            )
        )
    }
}

MediaTracker Lifecycle

State Diagram

┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Created ──► play() ──► Playing ──► pause() ──► Paused      │
│                            │                      │          │
│                            │                      │          │
│                            ▼                      ▼          │
│                         stop() ◄──────────────────┘          │
│                            │                                 │
│                            ▼                                 │
│                        Stopped                               │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Lifecycle Methods

MethodEffectEvents Generated
play()Starts/resumes playback trackingVideoPlay (first call)
pause()Pauses trackingVideoPause
stop()Ends tracking permanentlyVideoComplete

Integrating with AVPlayer

class VideoPlayerViewController: UIViewController {
    private var mediaTracker: MediaTrackerProtocol?
    private var player: AVPlayer!
    private var playerObserver: Any?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupPlayer()
        setupMediaTracker()
        observePlayer()
    }

    private func setupPlayer() {
        let url = URL(string: "https://example.com/video.mp4")!
        player = AVPlayer(url: url)

        let playerViewController = AVPlayerViewController()
        playerViewController.player = player
        addChild(playerViewController)
        view.addSubview(playerViewController.view)
        playerViewController.view.frame = view.bounds
    }

    private func setupMediaTracker() {
        // Get duration once available
        player.currentItem?.asset.loadValuesAsynchronously(forKeys: ["duration"]) { [weak self] in
            guard let self = self,
                  let duration = self.player.currentItem?.duration,
                  !duration.isIndefinite else { return }

            let durationMs = Int64(CMTimeGetSeconds(duration) * 1000)

            DispatchQueue.main.async {
                self.mediaTracker = try? Permutive.shared.createMediaTracker(
                    durationMilliseconds: durationMs,
                    videoProperties: MediaTracker.VideoProperties(
                        title: "Video Title"
                    ),
                    pageProperties: nil
                )
            }
        }
    }

    private func observePlayer() {
        // Observe playback state
        playerObserver = player.observe(\.timeControlStatus) { [weak self] player, _ in
            switch player.timeControlStatus {
            case .playing:
                try? self?.mediaTracker?.play()
            case .paused:
                self?.mediaTracker?.pause()
            case .waitingToPlayAtSpecifiedRate:
                break // Buffering
            @unknown default:
                break
            }
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        mediaTracker?.stop()
    }

    deinit {
        if let observer = playerObserver {
            player.removeObserver(self, forKeyPath: "timeControlStatus")
        }
    }
}

Progress Tracking

Track video playback progress:
class VideoPlayerViewController: UIViewController {
    private var mediaTracker: MediaTrackerProtocol?
    private var player: AVPlayer!
    private var progressObserver: Any?

    private func observeProgress() {
        // Update progress every second
        let interval = CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))

        progressObserver = player.addPeriodicTimeObserver(
            forInterval: interval,
            queue: .main
        ) { [weak self] time in
            guard let self = self,
                  let duration = self.player.currentItem?.duration,
                  !duration.isIndefinite else { return }

            let currentSeconds = CMTimeGetSeconds(time)
            let totalSeconds = CMTimeGetSeconds(duration)
            let progress = Float(currentSeconds / totalSeconds)

            self.mediaTracker?.updateProgress(percentage: progress)
        }
    }

    deinit {
        if let observer = progressObserver {
            player.removeTimeObserver(observer)
        }
    }
}

Video Properties

The VideoProperties class describes the video content:
PropertyTypeDescription
titleString?Video title
descriptionString?Video description
genre[String]?Genre categories
contentType[String]?Content type tags
sourceURL?Video source URL
MediaTracker.VideoProperties(
    title: "Product Demo",
    description: "See our new product in action",
    genre: ["Technology", "Product"],
    contentType: ["Demo", "Promotional"],
    source: URL(string: "https://example.com/demo.mp4")
)

Page Properties

Optional page context for contextual targeting:
PropertyTypeDescription
titleString?Page title
urlURL?Page URL (enables contextual cohorts)
referrerURL?Referring page URL
MediaTracker.PageProperties(
    title: "Watch: Product Demo",
    url: URL(string: "https://example.com/videos/demo"),
    referrer: URL(string: "https://example.com/products")
)
Providing a url in PageProperties enables contextual cohort generation for the video content.

tvOS Considerations

tvOS Note: MediaTracker works identically on tvOS. Use the same APIs with your TVUIKit video player or AVPlayerViewController.
// tvOS with AVPlayerViewController
class TVVideoPlayerViewController: UIViewController {
    private var mediaTracker: MediaTrackerProtocol?

    override func viewDidLoad() {
        super.viewDidLoad()

        let playerVC = AVPlayerViewController()
        playerVC.player = AVPlayer(url: videoURL)

        present(playerVC, animated: true) {
            // Setup tracking after presentation
            self.setupMediaTracker()
            playerVC.player?.play()
        }
    }
}

Best Practices

  • Create MediaTracker when video is ready to play
  • Sync play/pause calls with actual player state
  • Call stop() when video ends or view disappears
  • Provide accurate duration in milliseconds
  • Include URLs for contextual targeting
  • Track progress at reasonable intervals (1-5 seconds)

Troubleshooting

Problem: No VideoPlay event in logs.Solutions:
  • Ensure play() is called when video actually starts
  • Check that SDK is initialized
  • Verify no errors when creating MediaTracker
  • Enable debug logging
Problem: Video duration not available.Solutions:
  • Wait for asset to load before creating MediaTracker
  • Use loadValuesAsynchronously for duration
  • Check that video URL is valid
Problem: Progress percentage not being tracked.Solutions:
  • Verify periodic time observer is set up correctly
  • Check that duration is valid (not indefinite)
  • Ensure updateProgress is called with 0-1 range