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.
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" )
)
)
}
}
State Diagram
┌──────────────────────────────────────────────────────────────┐
│ │
│ Created ──► play() ──► Playing ──► pause() ──► Paused │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ stop() ◄──────────────────┘ │
│ │ │
│ ▼ │
│ Stopped │
│ │
└──────────────────────────────────────────────────────────────┘
Lifecycle Methods
Method Effect Events Generated play()Starts/resumes playback tracking VideoPlay (first call)pause()Pauses tracking VideoPausestop()Ends tracking permanently VideoComplete
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:
Property Type Description 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:
Property Type Description 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)
Don’t create multiple MediaTrackers simultaneously
Don’t forget to call stop() when done
Don’t call play() without user intent to play
Don’t track very short progress intervals (< 500ms)
Don’t include PII in video properties
Troubleshooting
VideoPlay event not firing
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
Duration is zero or undefined
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