Skip to main content
The MediaTracker API allows you to track video content events with Permutive. Use this for tracking video playback, engagement, and completion metrics.

Basic Usage

Properties

Use Cases

Ad Tracking

Connected TV IntegrationVideo 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 VideoComplete 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. VideoComplete Event

Tracked when video is stopped:
Event: VideoComplete
Properties:
  - engaged_time: Total time spent watching (seconds)
  - completion: Percentage of video viewed (0.0 - 1.0)
  - [your custom properties]
  - [standard video properties]
📘 Note The completion property requires a known duration to be set. Pass durationMilliseconds when creating the MediaTracker.

Basic Usage

Creating a MediaTracker

Create a MediaTracker when video playback begins:
import Permutive_iOS
import AVKit

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

    override func viewDidLoad() {
        super.viewDidLoad()

        mediaTracker = try? Permutive.shared.createMediaTracker(
            durationMilliseconds: 120000, // 2 minutes
            videoProperties: MediaTracker.VideoProperties(
                title: "Sample Video",
                genre: ["Documentary"],
                contentType: ["Education"],
                runtime: 120
            )
        )
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        mediaTracker?.stop()  // Important: Always stop to send VideoComplete
    }
}

MediaTracker Lifecycle

Expected Usage Flow

  1. Create MediaTracker instance with properties
  2. Call play() when playback begins
  3. Track buffering with pause() / play()
  4. Track scrubbing with play(position)
  5. 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:
try? mediaTracker?.play()

play(positionMs)

Call when playback position changes (e.g., seeking):
try? mediaTracker?.play(positionMs: 30000)  // Seek to 30 seconds

pause()

Call when video is paused or buffering:
mediaTracker?.pause()

stop()

Call when video playback completes or user exits:
mediaTracker?.stop()  // Sends VideoComplete event
⚠️ Important: Always Call stop() Failing to call stop() will prevent VideoComplete events from being tracked.

Video Properties

Standard Video Properties

The SDK provides standard video properties for consistent tracking:
MediaTracker.VideoProperties(
    title: "Video Title",
    genre: ["Action", "Thriller"],
    contentType: ["Movie", "Feature"],
    ageRating: "PG-13",
    runtime: 7200,  // Runtime in seconds
    country: "US",
    originalLanguage: "en",
    audioLanguage: "en",
    areSubtitlesEnabled: true,
    subtitlesLanguage: "en",
    seasonNumber: 1,
    episodeNumber: 5,
    consecutiveEpisodes: 3,
    iabCategories: ["IAB1", "IAB2"]
)

Property Reference

PropertyTypeDescription
titleStringVideo title
genre[String]Video genres (e.g., “Drama”, “Comedy”)
contentType[String]Content types (e.g., “Movie”, “Series”)
ageRatingStringAge rating (e.g., “PG-13”, “TV-MA”)
runtimeIntRuntime in seconds
countryStringOrigin country
originalLanguageStringOriginal language code
audioLanguageStringAudio language code
areSubtitlesEnabledBoolWhether subtitles are enabled
subtitlesLanguageStringSubtitle language code
seasonNumberIntSeason number (for series)
episodeNumberIntEpisode number (for series)
consecutiveEpisodesIntConsecutive episodes watched
iabCategories[String]IAB content taxonomy categories
💡 Best Practice Track as many properties as possible to enable richer cohort creation and insights.

Page Properties (Optional)

If the video is displayed within a page context (e.g., embedded in article), include page properties:
mediaTracker = try? Permutive.shared.createMediaTracker(
    durationMilliseconds: 120000,
    videoProperties: MediaTracker.VideoProperties(
        title: "Tutorial Video"
    ),
    pageProperties: MediaTracker.PageProperties(
        title: "How to Use iOS SDK",
        url: URL(string: "https://example.com/tutorial"),
        referrer: URL(string: "https://example.com/docs")
    )
)

PageProperties Reference

PropertyTypeDescription
titleStringPage title
urlURLPage URL
referrerURLReferring page URL
Providing a url in PageProperties enables contextual cohort generation for the video content.

Custom Properties

Add custom properties specific to your event schema:
import Permutive_iOS

mediaTracker = try? Permutive.shared.createMediaTracker(
    durationMilliseconds: 120000,
    videoProperties: videoProps,
    pageProperties: nil,
    customEventProperties: try EventProperties([
        "video_id": "vid_12345",
        "playlist_id": "playlist_abc",
        "is_premium_content": true,
        "content_partner": "PartnerXYZ"
    ])
)

Complete Example

import Permutive_iOS
import AVKit

class VideoPlayerViewController: UIViewController {

    private var mediaTracker: 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 durationMs = Int64(CMTimeGetSeconds(duration) * 1000)

            DispatchQueue.main.async {
                self.mediaTracker = try? Permutive.shared.createMediaTracker(
                    durationMilliseconds: durationMs,
                    videoProperties: MediaTracker.VideoProperties(
                        title: "Introduction to iOS",
                        genre: ["Educational", "Technology"],
                        contentType: ["Tutorial"],
                        ageRating: "G",
                        runtime: 180,
                        country: "US",
                        originalLanguage: "en",
                        audioLanguage: "en",
                        areSubtitlesEnabled: false,
                        iabCategories: ["IAB19"]
                    ),
                    pageProperties: MediaTracker.PageProperties(
                        title: "iOS Tutorial Page",
                        url: URL(string: "https://example.com/tutorials/ios"),
                        referrer: URL(string: "https://example.com/home")
                    ),
                    customEventProperties: try? EventProperties([
                        "video_id": "vid_tutorial_001",
                        "is_premium": false
                    ])
                )
                self.observePlayer()
            }
        }
    }

    private func observePlayer() {
        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()  // Send VideoComplete event
    }

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

Tracking Custom Events

Track custom video-related events using the track() method:
// User shares video
try? mediaTracker?.track(
    "VideoShared",
    properties: EventProperties([
        "share_method": "twitter",
        "video_position_seconds": 45
    ])
)

// User adds to favorites
try? mediaTracker?.track("VideoAddedToFavorites")

// Quality changed
try? mediaTracker?.track(
    "VideoQualityChanged",
    properties: EventProperties([
        "from_quality": "720p",
        "to_quality": "1080p"
    ])
)

Video Ad Tracking

If you need to track video ads within video content, use the AdTracker API:
// Create ad tracker from video tracker
let adTracker = try? mediaTracker?.trackAdView(
    durationMs: 15000,  // 15 second ad
    adProperties: AdTracker.AdProperties(
        title: "Product Ad",
        durationInSeconds: 15,
        isMuted: false,
        campaignId: "campaign_123",
        creativeId: "creative_456"
    )
)

// Track ad playback
try? adTracker?.play()
// ... ad plays ...
adTracker?.completion()
See Video Ad Tracking for complete documentation.

Use Cases

mediaTracker = try? Permutive.shared.createMediaTracker(
    durationMilliseconds: movieDurationMs,
    videoProperties: MediaTracker.VideoProperties(
        title: movie.title,
        genre: movie.genres,
        contentType: ["Movie", "Feature"],
        ageRating: movie.rating,
        runtime: movie.runtimeSeconds,
        country: movie.country,
        originalLanguage: movie.language,
        iabCategories: movie.iabCategories
    )
)
mediaTracker = try? Permutive.shared.createMediaTracker(
    durationMilliseconds: tutorialDurationMs,
    videoProperties: MediaTracker.VideoProperties(
        title: tutorial.title,
        genre: ["Educational"],
        contentType: ["Tutorial"],
        runtime: tutorial.runtimeSeconds
    ),
    customEventProperties: try? EventProperties([
        "course_id": tutorial.courseId,
        "module_id": tutorial.moduleId,
        "difficulty_level": tutorial.difficulty
    ])
)
mediaTracker = try? Permutive.shared.createMediaTracker(
    durationMilliseconds: episodeDurationMs,
    videoProperties: MediaTracker.VideoProperties(
        title: episode.title,
        genre: series.genres,
        contentType: ["Series", "Episode"],
        seasonNumber: episode.season,
        episodeNumber: episode.number,
        consecutiveEpisodes: userWatchData.consecutiveCount,
        runtime: episode.runtimeSeconds
    )
)

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

Problem: VideoComplete events not appearing.Cause: stop() not called.Solution: Always call stop() in viewWillDisappear:
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    mediaTracker?.stop()  // REQUIRED
}
Problem: Engaged time is inaccurate.Causes:
  1. Not calling play() when video plays
  2. 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
    }
}
Problem: completion property is missing or nil.Cause: Duration not provided when creating MediaTracker.Solution: Always provide durationMilliseconds:
mediaTracker = try? Permutive.shared.createMediaTracker(
    durationMilliseconds: videoDurationMs  // REQUIRED for completion
)
Problem: Error about multiple trackers.Cause: Creating new PageTracker or MediaTracker without closing existing one.Solution: Close existing tracker first:
// Close old tracker
mediaTracker?.stop()

// Now create new tracker
mediaTracker = try? Permutive.shared.createMediaTracker(...)
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

Video Ad Tracking

Track video advertisements

Page Tracking

Track non-video content

Event Properties

Custom properties

Issues

Solve common issues

API Reference

MediaTracker Protocol

  • play() - Start/resume video playback
  • play(positionMs: Int64) - Start at specific position
  • pause() - Pause video playback
  • stop() - Complete video tracking (sends VideoComplete)
  • setDuration(durationMs: Int64) - Update video duration
  • track(_ eventName: String) - Track custom event
  • track(_ eventName: String, properties: EventProperties) - Track custom event with properties
  • trackAdView(...) - Create AdTracker for video ad

Creating MediaTracker

  • Permutive.shared.createMediaTracker(durationMilliseconds:videoProperties:pageProperties:customEventProperties:) - Create MediaTracker