# Track media events with the native mobile trackers
> Track media playback events including play, pause, buffering, and completion using the mobile media tracking plugin.
> Source: https://docs.snowplow.io/docs/sources/mobile-trackers/tracking-events/media-tracking/
> Documentation index: https://docs.snowplow.io/llms.txt

The Snowplow media tracking APIs enable you to track events from media playback on the web and in mobile apps. Track changes in playback state (play, pause, seek), playback position (ping and percentage progress events), and ad playback (ad breaks, ad progress, ad clicks).

For details on the events and entities tracked, see the [media tracking overview](/docs/events/ootb-data/media-events/).

> **Info: Example app**
>
> See the tracked media events and entities live as you watch a video in our [React example app](https://snowplow-industry-solutions.github.io/snowplow-javascript-tracker-examples/media). [View the source code](https://github.com/snowplow-industry-solutions/snowplow-javascript-tracker-examples/tree/master/react).

## Quick start

A media tracking session follows this lifecycle:

1. **Start tracking** when the player loads (before playback begins)
2. **Update player state** every second during playback
3. **Track events** as they occur (play, pause, seek, etc.)
4. **End tracking** when the user leaves

**iOS:**

```swift
// 1. Start tracking when player loads
let id = UUID().uuidString
let player = MediaPlayerEntity()
  .duration(300)
  .label("My Video Title")
  .mediaType(.video)

let mediaTracking = tracker.media.startMediaTracking(id: id, player: player)

// 2. Update player state every second (or use AVPlayer auto-tracking)
mediaTracking.update(player: MediaPlayerEntity().currentTime(currentTime))

// 3. Track events as they occur
mediaTracking.track(MediaPlayEvent())
mediaTracking.track(MediaPauseEvent())
mediaTracking.track(MediaSeekStartEvent())
mediaTracking.track(MediaSeekEndEvent())
mediaTracking.track(MediaEndEvent())

// 4. End tracking when done
tracker.media.endMediaTracking(id: id)
```

**Android (Kotlin):**

```kotlin
// 1. Start tracking when player loads
val id = UUID.randomUUID().toString()
val player = MediaPlayerEntity(
  duration = 300.0,
  label = "My Video Title",
  mediaType = MediaType.Video
)
val mediaTracking = tracker?.media?.startMediaTracking(id, player)

// 2. Update player state every second
mediaTracking?.update(player = MediaPlayerEntity(currentTime = currentTime))

// 3. Track events as they occur
mediaTracking?.track(MediaPlayEvent())
mediaTracking?.track(MediaPauseEvent())
mediaTracking?.track(MediaSeekStartEvent())
mediaTracking?.track(MediaSeekEndEvent())
mediaTracking?.track(MediaEndEvent())

// 4. End tracking when done
tracker?.media?.endMediaTracking(id)
```

**Android (Java):**

```java
// 1. Start tracking when player loads
String id = UUID.randomUUID().toString();
MediaPlayerEntity player = new MediaPlayerEntity();
player.setDuration(300.0);
player.setLabel("My Video Title");
player.setMediaType(MediaType.Video);

TrackerController tracker = Snowplow.getDefaultTracker();
MediaTracking mediaTracking = tracker.getMedia().startMediaTracking(id, player);

// 2. Update player state every second
MediaPlayerEntity update = new MediaPlayerEntity();
update.setCurrentTime(currentTime);
mediaTracking.update(update, null, null);

// 3. Track events as they occur
mediaTracking.track(new MediaPlayEvent(), null, null, null);
mediaTracking.track(new MediaPauseEvent(), null, null, null);
mediaTracking.track(new MediaSeekStartEvent(), null, null, null);
mediaTracking.track(new MediaSeekEndEvent(), null, null, null);
mediaTracking.track(new MediaEndEvent(), null, null, null);

// 4. End tracking when done
tracker.getMedia().endMediaTracking(id);
```

***

## Configuration

Configure media tracking when you call `startMediaTracking`. All configuration is optional.

### Player properties

Set initial player properties to populate the [media player entity](/docs/events/ootb-data/media-events/#media-player). The `label` property is recommended as it helps identify content during analysis.

**iOS:**

```swift
let player = MediaPlayerEntity()
  .currentTime(0)          // Current playback position in seconds
  .duration(300)           // Total duration in seconds
  .ended(false)            // Whether playback has ended
  .livestream(false)       // Whether this is a live stream
  .label("My Video")       // Human-readable title (recommended)
  .loop(false)             // Whether to restart after ending
  .mediaType(.video)       // .video or .audio
  .muted(false)            // Whether audio is muted
  .paused(true)            // Whether playback is paused
  .pictureInPicture(false) // Whether in picture-in-picture mode
  .playbackRate(1.0)       // Playback speed (1 = normal)
  .quality("1080p")        // Quality level
  .volume(100)             // Volume percentage (0-100)

let mediaTracking = tracker.media.startMediaTracking(id: id, player: player)
```

**Android (Kotlin):**

```kotlin
val player = MediaPlayerEntity(
  currentTime = 0.0,        // Current playback position in seconds
  duration = 300.0,         // Total duration in seconds
  ended = false,            // Whether playback has ended
  livestream = false,       // Whether this is a live stream
  label = "My Video",       // Human-readable title (recommended)
  loop = false,             // Whether to restart after ending
  mediaType = MediaType.Video, // MediaType.Video or MediaType.Audio
  muted = false,            // Whether audio is muted
  paused = true,            // Whether playback is paused
  pictureInPicture = false, // Whether in picture-in-picture mode
  playbackRate = 1.0,       // Playback speed (1 = normal)
  quality = "1080p",        // Quality level
  volume = 100              // Volume percentage (0-100)
)

val mediaTracking = tracker?.media?.startMediaTracking(id, player)
```

**Android (Java):**

```java
MediaPlayerEntity player = new MediaPlayerEntity();
player.setCurrentTime(0.0);          // Current playback position in seconds
player.setDuration(300.0);           // Total duration in seconds
player.setEnded(false);              // Whether playback has ended
player.setLivestream(false);         // Whether this is a live stream
player.setLabel("My Video");         // Human-readable title (recommended)
player.setLoop(false);               // Whether to restart after ending
player.setMediaType(MediaType.Video); // MediaType.Video or MediaType.Audio
player.setMuted(false);              // Whether audio is muted
player.setPaused(true);              // Whether playback is paused
player.setPictureInPicture(false);   // Whether in picture-in-picture mode
player.setPlaybackRate(1.0);         // Playback speed (1 = normal)
player.setQuality("1080p");          // Quality level
player.setVolume(100);               // Volume percentage (0-100)

MediaTracking mediaTracking = tracker.getMedia().startMediaTracking(id, player);
```

***

### Ping events

Media ping events are sent at regular intervals (default: 30 seconds) to report playback position. You can configure ping behavior in the starting configuration.

**iOS:**

```swift
let configuration = MediaTrackingConfiguration(id: id)
  .pingInterval(30)   // Seconds between pings (default: 30)
  .maxPausedPings(1)  // Max pings while paused (default: 1)

// Or turn off pings entirely
let configuration = MediaTrackingConfiguration(id: id)
  .pings(false)
```

**Android (Kotlin):**

```kotlin
val configuration = MediaTrackingConfiguration(
  id = id,
  pingInterval = 30,   // Seconds between pings (default: 30)
  maxPausedPings = 1   // Max pings while paused (default: 1)
)

// Or turn off pings entirely
val configuration = MediaTrackingConfiguration(id = id, pings = false)
```

**Android (Java):**

```java
MediaTrackingConfiguration configuration = new MediaTrackingConfiguration(id, null);
configuration.setPingInterval(30);   // Seconds between pings (default: 30)
configuration.setMaxPausedPings(1);  // Max pings while paused (default: 1)

// Or turn off pings entirely
configuration.setPings(false);
```

***

### Percentage progress

Track events when playback reaches specified percentage boundaries.

**iOS:**

```swift
let configuration = MediaTrackingConfiguration(id: id)
  .boundaries([10, 25, 50, 75, 90])  // Fire events at these percentages
```

**Android (Kotlin):**

```kotlin
val configuration = MediaTrackingConfiguration(
  id = id,
  boundaries = listOf(10, 25, 50, 75, 90)  // Fire events at these percentages
)
```

**Android (Java):**

```java
MediaTrackingConfiguration configuration = new MediaTrackingConfiguration(id, null);
configuration.setBoundaries(Arrays.asList(10, 25, 50, 75, 90));  // Fire events at these percentages
```

***

### Session tracking

The [media session entity](/docs/events/ootb-data/media-events/#media-session) is attached to all media events by default. It's optional for the [Media Player](/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-models/dbt-media-player-data-model/) data model, so you could choose not to include it.

We recommend tracking this entity.

**iOS:**

```swift
let configuration = MediaTrackingConfiguration(id: id).session(true)
```

**Android (Kotlin):**

```kotlin
val configuration = MediaTrackingConfiguration(id = id, session = true)
```

**Android (Java):**

```java
MediaTrackingConfiguration configuration = new MediaTrackingConfiguration(id, null);
configuration.setSession(true);
```

***

### Custom entities

You can attach custom entities to all events for this media tracking instance.

**iOS:**

```swift
let configuration = MediaTrackingConfiguration(id: id)
  .entities([
      SelfDescribingJson(
          schema: "iglu:com.example/video_metadata/jsonschema/1-0-0",
          andData: ["contentId": "abc123", "category": "tutorial"]
      )
  ])
```

**Android (Kotlin):**

```kotlin
val configuration = MediaTrackingConfiguration(
  id = id,
  entities = listOf(
      SelfDescribingJson(
          schema = "iglu:com.example/video_metadata/jsonschema/1-0-0",
          data = mapOf("contentId" to "abc123", "category" to "tutorial")
      )
  )
)
```

**Android (Java):**

```java
MediaTrackingConfiguration configuration = new MediaTrackingConfiguration(id, null);
configuration.setEntities(Collections.singletonList(
  new SelfDescribingJson(
      "iglu:com.example/video_metadata/jsonschema/1-0-0",
      new HashMap<String, Object>() {{
          put("contentId", "abc123");
          put("category", "tutorial");
      }}
  )
));
```

***

### Filter events

**iOS:**

Only track specific event types by providing an allowlist:

```swift
let configuration = MediaTrackingConfiguration(id: id)
  .captureEvents([MediaPlayEvent.self, MediaPauseEvent.self, MediaEndEvent.self])
```

**Android (Kotlin):**

Only track specific event types by providing an allowlist:

```kotlin
val configuration = MediaTrackingConfiguration(
  id = id,
  captureEvents = listOf(MediaPlayEvent::class, MediaPauseEvent::class, MediaEndEvent::class)
)
```

**Android (Java):**

Only track specific event types by providing an allowlist:

```java
MediaTrackingConfiguration configuration = new MediaTrackingConfiguration(id, null);
configuration.setCaptureEvents(Arrays.asList(
  MediaPlayEvent.class, MediaPauseEvent.class, MediaEndEvent.class
));
```

***

## Tracking events

Track events by calling the appropriate function when player events occur. For a complete list of available events and their properties, see the [media events reference](/docs/events/ootb-data/media-events/#playback-lifecycle-events).

### Playback events

**iOS:**

```swift
mediaTracking.track(MediaReadyEvent())        // Player is ready
mediaTracking.track(MediaPlayEvent())         // Playback started
mediaTracking.track(MediaPauseEvent())        // Playback paused
mediaTracking.track(MediaEndEvent())          // Playback ended
mediaTracking.track(MediaSeekStartEvent())    // Seek operation started
mediaTracking.track(MediaSeekEndEvent())      // Seek operation ended
mediaTracking.track(MediaBufferStartEvent())  // Buffering started
mediaTracking.track(MediaBufferEndEvent())    // Buffering ended
```

**Android (Kotlin):**

```kotlin
mediaTracking?.track(MediaReadyEvent())        // Player is ready
mediaTracking?.track(MediaPlayEvent())         // Playback started
mediaTracking?.track(MediaPauseEvent())        // Playback paused
mediaTracking?.track(MediaEndEvent())          // Playback ended
mediaTracking?.track(MediaSeekStartEvent())    // Seek operation started
mediaTracking?.track(MediaSeekEndEvent())      // Seek operation ended
mediaTracking?.track(MediaBufferStartEvent())  // Buffering started
mediaTracking?.track(MediaBufferEndEvent())    // Buffering ended
```

**Android (Java):**

```java
mediaTracking.track(new MediaReadyEvent(), null, null, null);        // Player is ready
mediaTracking.track(new MediaPlayEvent(), null, null, null);         // Playback started
mediaTracking.track(new MediaPauseEvent(), null, null, null);        // Playback paused
mediaTracking.track(new MediaEndEvent(), null, null, null);          // Playback ended
mediaTracking.track(new MediaSeekStartEvent(), null, null, null);    // Seek operation started
mediaTracking.track(new MediaSeekEndEvent(), null, null, null);      // Seek operation ended
mediaTracking.track(new MediaBufferStartEvent(), null, null, null);  // Buffering started
mediaTracking.track(new MediaBufferEndEvent(), null, null, null);    // Buffering ended
```

***

### Player state events

**iOS:**

```swift
mediaTracking.track(MediaPlaybackRateChangeEvent(newRate: 1.5))
mediaTracking.track(MediaVolumeChangeEvent(newVolume: 80))
mediaTracking.track(MediaFullscreenChangeEvent(fullscreen: true))
mediaTracking.track(MediaPictureInPictureChangeEvent(pictureInPicture: true))
mediaTracking.track(MediaQualityChangeEvent(
  newQuality: "1080p",
  bitrate: 5000,
  framesPerSecond: 30,
  automatic: true
))
mediaTracking.track(MediaErrorEvent(
  errorCode: "500",
  errorName: "NetworkError",
  errorDescription: "Failed to load media"
))
```

**Android (Kotlin):**

```kotlin
mediaTracking?.track(MediaPlaybackRateChangeEvent(newRate = 1.5))
mediaTracking?.track(MediaVolumeChangeEvent(newVolume = 80))
mediaTracking?.track(MediaFullscreenChangeEvent(fullscreen = true))
mediaTracking?.track(MediaPictureInPictureChangeEvent(pictureInPicture = true))
mediaTracking?.track(MediaQualityChangeEvent(
  newQuality = "1080p",
  bitrate = 5000,
  framesPerSecond = 30,
  automatic = true
))
mediaTracking?.track(MediaErrorEvent(
  errorCode = "500",
  errorName = "NetworkError",
  errorDescription = "Failed to load media"
))
```

**Android (Java):**

```java
mediaTracking.track(new MediaPlaybackRateChangeEvent(null, 1.5), null, null, null);
mediaTracking.track(new MediaVolumeChangeEvent(null, 80), null, null, null);
mediaTracking.track(new MediaFullscreenChangeEvent(true), null, null, null);
mediaTracking.track(new MediaPictureInPictureChangeEvent(true), null, null, null);

MediaQualityChangeEvent qualityEvent = new MediaQualityChangeEvent();
qualityEvent.setNewQuality("1080p");
qualityEvent.setBitrate(5000);
qualityEvent.setFramesPerSecond(30);
qualityEvent.setAutomatic(true);
mediaTracking.track(qualityEvent, null, null, null);

MediaErrorEvent errorEvent = new MediaErrorEvent();
errorEvent.setErrorCode("500");
errorEvent.setErrorName("NetworkError");
errorEvent.setErrorDescription("Failed to load media");
mediaTracking.track(errorEvent, null, null, null);
```

***

### Ad events

Track advertising events with ad and ad break information. See the [media ad](/docs/events/ootb-data/media-events/#media-ad) and [media ad break](/docs/events/ootb-data/media-events/#media-ad-break) entity schemas for property details.

**iOS:**

```swift
// Ad break events
let adBreak = MediaAdBreakEntity(breakId: "break-1")
  .name("pre-roll")
  .breakType(.linear)
  .podSize(3)
mediaTracking.track(MediaAdBreakStartEvent(), adBreak: adBreak)
mediaTracking.track(MediaAdBreakEndEvent())

// Ad events
let ad = MediaAdEntity(adId: "ad-1")
  .name("Product Ad")
  .creativeId("creative-1")
  .duration(30)
  .skippable(true)
mediaTracking.track(MediaAdStartEvent(), ad: ad)
mediaTracking.track(MediaAdFirstQuartileEvent())
mediaTracking.track(MediaAdMidpointEvent())
mediaTracking.track(MediaAdThirdQuartileEvent())
mediaTracking.track(MediaAdCompleteEvent())
mediaTracking.track(MediaAdSkipEvent())
mediaTracking.track(MediaAdClickEvent())
mediaTracking.track(MediaAdPauseEvent())
mediaTracking.track(MediaAdResumeEvent())
```

**Android (Kotlin):**

```kotlin
// Ad break events
val adBreak = MediaAdBreakEntity(
  breakId = "break-1",
  name = "pre-roll",
  breakType = MediaAdBreakType.Linear,
  podSize = 3
)
mediaTracking?.track(MediaAdBreakStartEvent(), adBreak = adBreak)
mediaTracking?.track(MediaAdBreakEndEvent())

// Ad events
val ad = MediaAdEntity(
  adId = "ad-1",
  name = "Product Ad",
  creativeId = "creative-1",
  duration = 30.0,
  skippable = true
)
mediaTracking?.track(MediaAdStartEvent(), ad = ad)
mediaTracking?.track(MediaAdFirstQuartileEvent())
mediaTracking?.track(MediaAdMidpointEvent())
mediaTracking?.track(MediaAdThirdQuartileEvent())
mediaTracking?.track(MediaAdCompleteEvent())
mediaTracking?.track(MediaAdSkipEvent())
mediaTracking?.track(MediaAdClickEvent())
mediaTracking?.track(MediaAdPauseEvent())
mediaTracking?.track(MediaAdResumeEvent())
```

**Android (Java):**

```java
// Ad break events
MediaAdBreakEntity adBreak = new MediaAdBreakEntity("break-1");
adBreak.setName("pre-roll");
adBreak.setBreakType(MediaAdBreakType.Linear);
adBreak.setPodSize(3);
mediaTracking.track(new MediaAdBreakStartEvent(), null, null, adBreak);
mediaTracking.track(new MediaAdBreakEndEvent(), null, null, null);

// Ad events
MediaAdEntity ad = new MediaAdEntity("ad-1");
ad.setName("Product Ad");
ad.setCreativeId("creative-1");
ad.setDuration(30.0);
ad.setSkippable(true);
mediaTracking.track(new MediaAdStartEvent(), null, ad, null);
mediaTracking.track(new MediaAdFirstQuartileEvent(), null, null, null);
mediaTracking.track(new MediaAdMidpointEvent(), null, null, null);
mediaTracking.track(new MediaAdThirdQuartileEvent(), null, null, null);
mediaTracking.track(new MediaAdCompleteEvent(), null, null, null);
mediaTracking.track(new MediaAdSkipEvent(), null, null, null);
mediaTracking.track(new MediaAdClickEvent(), null, null, null);
mediaTracking.track(new MediaAdPauseEvent(), null, null, null);
mediaTracking.track(new MediaAdResumeEvent(), null, null, null);
```

***

### Custom events

Track custom self-describing events within the media session. The tracker automatically attaches media entities.

**iOS:**

```swift
mediaTracking.track(SelfDescribing(
  schema: "iglu:com.example/video_interaction/jsonschema/1-0-0",
  payload: ["action": "share", "platform": "twitter"]
))
```

**Android (Kotlin):**

```kotlin
mediaTracking?.track(SelfDescribing(
  "iglu:com.example/video_interaction/jsonschema/1-0-0",
  mapOf("action" to "share", "platform" to "twitter")
))
```

**Android (Java):**

```java
mediaTracking.track(new SelfDescribing(
  "iglu:com.example/video_interaction/jsonschema/1-0-0",
  new HashMap<String, Object>() {{
      put("action", "share");
      put("platform", "twitter");
  }}
), null, null, null);
```

***

## Update player state

**iOS:**

Update the player state every second during playback. This ensures accurate metrics in ping events and the session entity. This function does not send any events.

```swift
mediaTracking.update(player: MediaPlayerEntity().currentTime(currentTime))
```

**Android (Kotlin):**

Update the player state every second during playback. This ensures accurate metrics in ping events and the session entity. This function does not send any events.

```kotlin
mediaTracking?.update(player = MediaPlayerEntity(currentTime = currentTime))
```

**Android (Java):**

Update the player state every second during playback. This ensures accurate metrics in ping events and the session entity. This function does not send any events.

```java
MediaPlayerEntity player = new MediaPlayerEntity();
player.setCurrentTime(currentTime);
mediaTracking.update(player, null, null);
```

***

## AVPlayer auto-tracking (iOS)

The iOS tracker can automatically track events from [AVPlayer](https://developer.apple.com/documentation/avfoundation/avplayer). Pass the player instance to `startMediaTracking`:

```swift
let mediaTracking = tracker.media.startMediaTracking(
    player: avPlayer,
    configuration: MediaTrackingConfiguration(id: "my-video")
)
```

The following events are auto-tracked:

- Play events
- Pause events
- Seek end events
- Ping events
- Percent progress events (if configured)
- Buffer start events
- End events
- Error events
