> ## 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.

# Video Tracking

> Track video playback, engagement, and completion metrics

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

<CardGroup cols={4}>
  <Card title="Basic Usage" href="#basic-usage" icon="play" />

  <Card title="Properties" href="#video-properties" icon="list" />

  <Card title="Use Cases" href="#use-cases" icon="layer-group" />

  <Card title="Ad Tracking" href="#video-ad-tracking" icon="rectangle-ad" />
</CardGroup>

<Info>
  **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.
</Info>

## 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:

<CodeGroup>
  ```swift Swift theme={"dark"}
  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
      }
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  #import "VideoPlayerViewController.h"
  @import Permutive_iOS;
  @import AVKit;

  @interface VideoPlayerViewController ()
  @property (nonatomic, strong) NSObject<PermutiveMediaTrackerProtocol> *mediaTracker;
  @property (nonatomic, strong) AVPlayer *player;
  @end

  @implementation VideoPlayerViewController

  - (void)viewDidLoad {
      [super viewDidLoad];

      PermutiveMediaTrackerVideoProperties *videoProps =
          [[PermutiveMediaTrackerVideoProperties alloc]
              initWithTitle:@"Sample Video"
              genre:@[@"Documentary"]
              contentType:@[@"Education"]
              runtime:@120];

      NSError *error = nil;
      self.mediaTracker = [Permutive.shared
          createMediaTrackerWithDurationMilliseconds:120000
          videoProperties:videoProps
          pageProperties:nil
          error:&error];
  }

  - (void)viewWillDisappear:(BOOL)animated {
      [super viewWillDisappear:animated];
      [self.mediaTracker stop];  // Important: Always stop to send VideoComplete
  }

  @end
  ```
</CodeGroup>

***

## 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:

```swift theme={"dark"}
try? mediaTracker?.play()
```

#### play(positionMs)

Call when playback position changes (e.g., seeking):

```swift theme={"dark"}
try? mediaTracker?.play(positionMs: 30000)  // Seek to 30 seconds
```

#### pause()

Call when video is paused or buffering:

```swift theme={"dark"}
mediaTracker?.pause()
```

#### stop()

Call when video playback completes or user exits:

```swift theme={"dark"}
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:

```swift theme={"dark"}
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

| Property                | Type      | Description                             |
| ----------------------- | --------- | --------------------------------------- |
| **title**               | String    | Video title                             |
| **genre**               | \[String] | Video genres (e.g., "Drama", "Comedy")  |
| **contentType**         | \[String] | Content types (e.g., "Movie", "Series") |
| **ageRating**           | String    | Age rating (e.g., "PG-13", "TV-MA")     |
| **runtime**             | Int       | Runtime in **seconds**                  |
| **country**             | String    | Origin country                          |
| **originalLanguage**    | String    | Original language code                  |
| **audioLanguage**       | String    | Audio language code                     |
| **areSubtitlesEnabled** | Bool      | Whether subtitles are enabled           |
| **subtitlesLanguage**   | String    | Subtitle language code                  |
| **seasonNumber**        | Int       | Season number (for series)              |
| **episodeNumber**       | Int       | Episode number (for series)             |
| **consecutiveEpisodes** | Int       | Consecutive 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:

```swift theme={"dark"}
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

| Property     | Type   | Description        |
| ------------ | ------ | ------------------ |
| **title**    | String | Page title         |
| **url**      | URL    | Page URL           |
| **referrer** | URL    | Referring page URL |

<Tip>
  Providing a `url` in PageProperties enables contextual cohort generation for the video content.
</Tip>

***

## Custom Properties

Add custom properties specific to your event schema:

```swift theme={"dark"}
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

<CodeGroup>
  ```swift Swift theme={"dark"}
  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)
          }
      }
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  @implementation VideoPlayerViewController

  - (void)viewDidLoad {
      [super viewDidLoad];
      [self setupPlayer];
  }

  - (void)setupPlayer {
      NSURL *url = [NSURL URLWithString:@"https://example.com/video.mp4"];
      self.player = [AVPlayer playerWithURL:url];

      AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init];
      playerVC.player = self.player;
      [self addChildViewController:playerVC];
      [self.view addSubview:playerVC.view];
      playerVC.view.frame = self.view.bounds;

      // Get duration once available
      [self.player.currentItem.asset loadValuesAsynchronouslyForKeys:@[@"duration"]
                                                   completionHandler:^{
          CMTime duration = self.player.currentItem.duration;
          if (CMTIME_IS_INDEFINITE(duration)) return;

          int64_t durationMs = (int64_t)(CMTimeGetSeconds(duration) * 1000);

          dispatch_async(dispatch_get_main_queue(), ^{
              PermutiveMediaTrackerVideoProperties *videoProps =
                  [[PermutiveMediaTrackerVideoProperties alloc]
                      initWithTitle:@"Introduction to iOS"
                      genre:@[@"Educational", @"Technology"]
                      contentType:@[@"Tutorial"]
                      ageRating:@"G"
                      runtime:@180
                      country:@"US"
                      originalLanguage:@"en"
                      audioLanguage:@"en"
                      areSubtitlesEnabled:NO
                      iabCategories:@[@"IAB19"]];

              PermutiveMediaTrackerPageProperties *pageProps =
                  [[PermutiveMediaTrackerPageProperties alloc]
                      initWithTitle:@"iOS Tutorial Page"
                      url:[NSURL URLWithString:@"https://example.com/tutorials/ios"]
                      referrer:[NSURL URLWithString:@"https://example.com/home"]];

              NSError *error = nil;
              self.mediaTracker = [Permutive.shared
                  createMediaTrackerWithDurationMilliseconds:durationMs
                  videoProperties:videoProps
                  pageProperties:pageProps
                  error:&error];

              [self observePlayer];
          });
      }];
  }

  - (void)observePlayer {
      [self.player addObserver:self
                    forKeyPath:@"timeControlStatus"
                       options:NSKeyValueObservingOptionNew
                       context:nil];
  }

  - (void)observeValueForKeyPath:(NSString *)keyPath
                        ofObject:(id)object
                          change:(NSDictionary *)change
                         context:(void *)context {

      if ([keyPath isEqualToString:@"timeControlStatus"]) {
          if (self.player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
              [self.mediaTracker playAndReturnError:nil];
          } else if (self.player.timeControlStatus == AVPlayerTimeControlStatusPaused) {
              [self.mediaTracker pause];
          }
      }
  }

  - (void)viewWillDisappear:(BOOL)animated {
      [super viewWillDisappear:animated];
      [self.mediaTracker stop];  // Send VideoComplete event
  }

  - (void)dealloc {
      [self.player removeObserver:self forKeyPath:@"timeControlStatus"];
  }

  @end
  ```
</CodeGroup>

***

## Tracking Custom Events

Track custom video-related events using the `track()` method:

```swift theme={"dark"}
// 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:

```swift theme={"dark"}
// 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](/sdks/mobile/ios/features/video-ad-tracking) for complete documentation.

***

## Use Cases

<AccordionGroup>
  <Accordion title="Movie/TV App" icon="film">
    ```swift theme={"dark"}
    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
        )
    )
    ```
  </Accordion>

  <Accordion title="Educational/Tutorial App" icon="graduation-cap">
    ```swift theme={"dark"}
    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
        ])
    )
    ```
  </Accordion>

  <Accordion title="Series/Episode Tracking" icon="tv">
    ```swift theme={"dark"}
    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
        )
    )
    ```
  </Accordion>
</AccordionGroup>

***

## tvOS Considerations

<Info>
  **tvOS Note:** MediaTracker works identically on tvOS. Use the same APIs with your TVUIKit video player or AVPlayerViewController.
</Info>

```swift theme={"dark"}
// 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

<AccordionGroup>
  <Accordion title="VideoComplete Not Tracked">
    **Problem:** VideoComplete events not appearing.

    **Cause:** `stop()` not called.

    **Solution:** Always call `stop()` in `viewWillDisappear`:

    ```swift theme={"dark"}
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        mediaTracker?.stop()  // REQUIRED
    }
    ```
  </Accordion>

  <Accordion title="Engagement Time Incorrect">
    **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:

    ```swift theme={"dark"}
    playerObserver = player.observe(\.timeControlStatus) { [weak self] player, _ in
        switch player.timeControlStatus {
        case .playing:
            try? self?.mediaTracker?.play()
        case .paused:
            self?.mediaTracker?.pause()
        default:
            break
        }
    }
    ```
  </Accordion>

  <Accordion title="Completion Percentage Missing">
    **Problem:** `completion` property is missing or nil.

    **Cause:** Duration not provided when creating MediaTracker.

    **Solution:** Always provide `durationMilliseconds`:

    ```swift theme={"dark"}
    mediaTracker = try? Permutive.shared.createMediaTracker(
        durationMilliseconds: videoDurationMs  // REQUIRED for completion
    )
    ```
  </Accordion>

  <Accordion title="Multiple Tracker Error">
    **Problem:** Error about multiple trackers.

    **Cause:** Creating new PageTracker or MediaTracker without closing existing one.

    **Solution:** Close existing tracker first:

    ```swift theme={"dark"}
    // Close old tracker
    mediaTracker?.stop()

    // Now create new tracker
    mediaTracker = try? Permutive.shared.createMediaTracker(...)
    ```

    See [Common Errors](/sdks/mobile/ios/troubleshooting/common-errors) for more troubleshooting.
  </Accordion>
</AccordionGroup>

***

## Best Practices

<Tabs>
  <Tab title="Do">
    * 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
  </Tab>

  <Tab title="Don't">
    * Forget to call `stop()`
    * Create multiple MediaTrackers simultaneously
    * Skip play/pause lifecycle events
    * Omit video duration
  </Tab>
</Tabs>

***

## Related Documentation

<CardGroup cols={2}>
  <Card title="Video Ad Tracking" icon="rectangle-ad" href="/sdks/mobile/ios/features/video-ad-tracking">
    Track video advertisements
  </Card>

  <Card title="Page Tracking" icon="file" href="/sdks/mobile/ios/features/page-tracking">
    Track non-video content
  </Card>

  <Card title="Event Properties" icon="list" href="/sdks/mobile/ios/core-concepts/event-properties">
    Custom properties
  </Card>

  <Card title="Issues" icon="wrench" href="/sdks/mobile/ios/troubleshooting/common-errors">
    Solve common issues
  </Card>
</CardGroup>

***

## 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
