Documentation Index Fetch the complete documentation index at: https://docs.permutive.com/llms.txt
Use this file to discover all available pages before exploring further.
The MediaTracker API allows you to track video content events with Permutive. Use this for tracking video playback, engagement, and completion metrics.
Connected TV Integration Video event tracking described below is available once the connected TV integration has been enabled. Please contact your Customer Success Manager (CSM) to enable this feature.
Overview
MediaTracker provides comprehensive video tracking capabilities:
Automatic lifecycle tracking - Tracks Videoview and VideoCompletion events
Engagement metrics - Measures time spent watching and completion percentage
Custom events - Track custom video-related events
Rich metadata - Support for extensive video properties
Video Events
MediaTracker automatically tracks two event types:
1. Videoview Event
Tracked when MediaTracker is created:
Event: Videoview
Properties:
- [your custom properties]
- [standard video properties]
2. VideoCompletion Event
Tracked when video is stopped:
Event: VideoCompletion
Properties:
- aggregations.VideoEngagement.engaged_time: Total time spent watching (seconds)
- aggregations.VideoEngagement.completion: Percentage of video viewed (0.0 - 1.0)
- [your custom properties]
- [standard video properties]
📘 Note
The aggregations.VideoEngagement.completion property requires a known duration to be set. Pass duration (in seconds) when calling createVideoTracker(...).
Basic Usage
Creating a Video Tracker
Create a video tracker when playback begins. The iOS SDK does not auto-nest publisher properties under video like Android does — hand-build the nested EventProperties dictionary so the emitted event matches the canonical schema (video.title, video.content_type, …). See CTV Video Tracking for the full schema.
import Permutive_iOS
import AVKit
class VideoPlayerViewController : UIViewController {
private var mediaTracker: (NSObject & MediaTrackerProtocol)?
private var player: AVPlayer!
override func viewDidLoad () {
super . viewDidLoad ()
let videoProps = try ? EventProperties ([
"video" : [
"title" : "Sample Video" ,
"genre" : [ "Documentary" ],
"content_type" : [ "Education" ],
"runtime" : 120
]
])
// `duration` is a TimeInterval (seconds), not milliseconds.
mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : 120 , // 2 minutes
properties : videoProps
)
}
override func viewWillDisappear ( _ animated : Bool ) {
super . viewWillDisappear (animated)
mediaTracker?. stop () // Important: Always stop to send VideoCompletion
}
}
Expected Usage Flow
Create MediaTracker instance with properties
Call play() when playback begins
Track buffering with pause() / play()
Track scrubbing with play(position)
Call stop() when playback completes
⚠️ Single Instance Limitation
Only a single instance of PageTracker or MediaTracker is available at any time. Creating a new tracker automatically stops any existing one.
Playback Control Methods
play()
Call when video starts playing:
play(position:)
Call when playback position changes (e.g., seeking). Position is a TimeInterval in seconds.
mediaTracker?. play ( position : 30 ) // Seek to 30 seconds
pause()
Call when video is paused or buffering:
stop()
Call when video playback completes or user exits:
mediaTracker?. stop () // Sends VideoCompletion event
set(duration:)
Call to update the video duration if it wasn’t known when the tracker was created:
mediaTracker?. set ( duration : 240 ) // 4 minutes
⚠️ Important: Always Call stop()
Failing to call stop() will prevent VideoCompletion events from being tracked.
Video Properties
The iOS SDK accepts an untyped EventProperties dictionary. Build the nested shape that matches the canonical schema — properties under a top-level video parent, in snake_case. There is no auto-mapping or auto-nesting like on Android.
Standard Video Properties
let videoProps = try ? EventProperties ([
"video" : [
"title" : "Video Title" ,
"genre" : [ "Action" , "Thriller" ],
"content_type" : [ "Movie" , "Feature" ],
"age_rating" : "PG-13" ,
"runtime" : 7200 , // Runtime in seconds
"country" : "US" ,
"original_language" : "en" ,
"audio_language" : "en" ,
"subtitles" : [
"enabled" : true ,
"language" : "en"
],
"season_number" : 1 ,
"episode_number" : 5 ,
"consecutive_episodes" : 3 ,
"iab_categories" : [ "IAB1" , "IAB2" ]
]
])
Schema Reference
Property Type Description video.titleString Video title video.genre[String] Video genres (e.g., “Drama”, “Comedy”) video.content_type[String] Content types (e.g., “Movie”, “Series”) video.age_ratingString Age rating (e.g., “PG-13”, “TV-MA”) video.runtimeInt Runtime in seconds video.countryString Origin country video.original_languageString Original language code video.audio_languageString Audio language code video.subtitles.enabledBool Whether subtitles are enabled video.subtitles.languageString Subtitle language code video.season_numberInt Season number (for series) video.episode_numberInt Episode number (for series) video.consecutive_episodesInt Consecutive episodes watched video.iab_categories[String] IAB content taxonomy categories
💡 Best Practice
Track as many properties as possible to enable richer cohort creation and insights.
Page Context (Optional)
If the video is displayed within a page context (e.g., embedded in an article), pass a Context to associate the tracker with that page:
let videoProps = try ? EventProperties ([
"video" : [ "title" : "Tutorial Video" ]
])
let context = Context (
title : "How to Use iOS SDK" ,
url : URL ( string : "https://example.com/tutorial" ),
referrer : URL ( string : "https://example.com/docs" )
)
mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : 120 ,
properties : videoProps,
context : context
)
Context Reference
Property Type Description title String? Page title url URL? Page URL referrer URL? Referring page URL
Providing a url in Context enables contextual cohort generation for the video content.
Custom Properties
The iOS SDK does not take a separate “custom properties” parameter — add custom keys directly to the same EventProperties dictionary, alongside the video schema fields. Custom keys are emitted at the top level of the event:
let props = try ? EventProperties ([
"video" : [
"title" : "Episode 5" ,
"season_number" : 1 ,
"episode_number" : 5
],
"video_id" : "vid_12345" ,
"playlist_id" : "playlist_abc" ,
"is_premium_content" : true ,
"content_partner" : "PartnerXYZ"
])
mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : 120 ,
properties : props
)
Complete Example
import Permutive_iOS
import AVKit
class VideoPlayerViewController : UIViewController {
private var mediaTracker: (NSObject & MediaTrackerProtocol)?
private var player: AVPlayer!
private var playerObserver: Any ?
override func viewDidLoad () {
super . viewDidLoad ()
// Set up AVPlayer
setupPlayer ()
}
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
// Get duration once available, then set up tracker
player. currentItem ?. asset . loadValuesAsynchronously ( forKeys : [ "duration" ]) { [ weak self ] in
guard let self = self ,
let duration = self .player.currentItem?.duration,
!duration.isIndefinite else { return }
let durationSeconds = CMTimeGetSeconds (duration)
DispatchQueue. main . async {
let props = try ? EventProperties ([
"video" : [
"title" : "Introduction to iOS" ,
"genre" : [ "Educational" , "Technology" ],
"content_type" : [ "Tutorial" ],
"age_rating" : "G" ,
"runtime" : 180 ,
"country" : "US" ,
"original_language" : "en" ,
"audio_language" : "en" ,
"subtitles" : [ "enabled" : false ],
"iab_categories" : [ "IAB19" ]
],
"video_id" : "vid_tutorial_001" ,
"is_premium" : false
])
let context = Context (
title : "iOS Tutorial Page" ,
url : URL ( string : "https://example.com/tutorials/ios" ),
referrer : URL ( string : "https://example.com/home" )
)
self . mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : durationSeconds,
properties : props,
context : context
)
self . observePlayer ()
}
}
}
private func observePlayer () {
playerObserver = player. observe (\. timeControlStatus ) { [ weak self ] player, _ in
switch player.timeControlStatus {
case . playing :
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 () // Send VideoCompletion event
}
deinit {
if let observer = playerObserver {
player. removeTimeObserver (observer)
}
}
}
Tracking Custom Events
Track custom video-related events using the track(event:properties:) method. Both parameters are required (pass nil for properties if there are none).
// User shares video
try ? mediaTracker?. track (
event : "VideoShared" ,
properties : try ? EventProperties ([
"share_method" : "twitter" ,
"video_position_seconds" : 45
])
)
// User adds to favorites
try ? mediaTracker?. track ( event : "VideoAddedToFavorites" , properties : nil )
// Quality changed
try ? mediaTracker?. track (
event : "VideoQualityChanged" ,
properties : try ? EventProperties ([
"from_quality" : "720p" ,
"to_quality" : "1080p"
])
)
Video Ad Tracking
iOS does not have a native AdTracker. Emit standard VideoAdView, VideoAdCompletion, and VideoAdClicked events using Permutive.shared.track(event:properties:) with properties nested under an ad parent per the canonical schema .
// When the ad starts
try ? Permutive. shared . track (
event : "VideoAdView" ,
properties : try ? EventProperties ([
"ad" : [
"title" : "Product Ad" ,
"duration" : 15 ,
"muted" : false ,
"campaign_id" : "campaign_123" ,
"creative_id" : "creative_456"
]
])
)
// When the ad completes
try ? Permutive. shared . track (
event : "VideoAdCompletion" ,
properties : try ? EventProperties ([
"ad" : [
"title" : "Product Ad" ,
"campaign_id" : "campaign_123" ,
"creative_id" : "creative_456"
]
])
)
See Video Ad Tracking for the full guide.
Use Cases
let props = try ? EventProperties ([
"video" : [
"title" : movie. title ,
"genre" : movie. genres ,
"content_type" : [ "Movie" , "Feature" ],
"age_rating" : movie. rating ,
"runtime" : movie. runtimeSeconds ,
"country" : movie. country ,
"original_language" : movie. language ,
"iab_categories" : movie. iabCategories
]
])
mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : movieDurationSeconds,
properties : props
)
let props = try ? EventProperties ([
"video" : [
"title" : tutorial. title ,
"genre" : [ "Educational" ],
"content_type" : [ "Tutorial" ],
"runtime" : tutorial. runtimeSeconds
],
"course_id" : tutorial. courseId ,
"module_id" : tutorial. moduleId ,
"difficulty_level" : tutorial. difficulty
])
mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : tutorialDurationSeconds,
properties : props
)
let props = try ? EventProperties ([
"video" : [
"title" : episode. title ,
"genre" : series. genres ,
"content_type" : [ "Series" , "Episode" ],
"season_number" : episode. season ,
"episode_number" : episode. number ,
"consecutive_episodes" : userWatchData. consecutiveCount ,
"runtime" : episode. runtimeSeconds
]
])
mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : episodeDurationSeconds,
properties : props
)
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 ) {
self . setupMediaTracker ()
playerVC. player ?. play ()
}
}
}
Troubleshooting
VideoCompletion Not Tracked
Problem: VideoCompletion events not appearing.Cause: stop() not called.Solution: Always call stop() in viewWillDisappear:override func viewWillDisappear ( _ animated : Bool ) {
super . viewWillDisappear (animated)
mediaTracker?. stop () // REQUIRED
}
Engagement Time Incorrect
Problem: Engaged time is inaccurate.Causes:
Not calling play() when video plays
Not calling pause() when video pauses/buffers
Solution: Track all play/pause events:playerObserver = player. observe (\. timeControlStatus ) { [ weak self ] player, _ in
switch player.timeControlStatus {
case . playing :
try ? self ?. mediaTracker ?. play ()
case . paused :
self ?. mediaTracker ?. pause ()
default :
break
}
}
Completion Percentage Missing
Problem: aggregations.VideoEngagement.completion is missing or nil.Cause: Duration not provided when creating the video tracker.Solution: Always provide duration (in seconds):mediaTracker = try ? Permutive. shared . createVideoTracker (
duration : videoDurationSeconds // REQUIRED for completion
)
Problem: Error about multiple trackers.Cause: Creating a new PageTracker or video tracker without closing the existing one.Solution: Close the existing tracker first:// Close old tracker
mediaTracker?. stop ()
// Now create new tracker
mediaTracker = try ? Permutive. shared . createVideoTracker (...)
See Common Errors for more troubleshooting.
Best Practices
Always call stop() when video finishes
Provide duration for completion tracking
Track play/pause events accurately
Include as many video properties as possible
Close tracker before creating a new one
Track buffering with pause/play
Forget to call stop()
Create multiple MediaTrackers simultaneously
Skip play/pause lifecycle events
Omit video duration
Video Ad Tracking Track video advertisements
Page Tracking Track non-video content
Event Properties Custom properties
Issues Solve common issues
API Reference
play() — Start/resume video playback
play(position: TimeInterval) — Start at a specific position (seconds)
pause() — Pause video playback
stop() — Complete video tracking (sends VideoCompletion)
set(duration: TimeInterval) — Update video duration (seconds), if it wasn’t known at creation
track(event: String, properties: EventProperties?) throws — Track a custom event
Creating a Video Tracker
Permutive.shared.createVideoTracker(duration: TimeInterval, properties: EventProperties?, context: Context?) throws -> NSObject & MediaTrackerProtocol