Track media events with the native mobile trackers
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.
See the tracked media events and entities live as you watch a video in our React example app. View the source code.
Quick start
A media tracking session follows this lifecycle:
- Start tracking when the player loads (before playback begins)
- Update player state every second during playback
- Track events as they occur (play, pause, seek, etc.)
- End tracking when the user leaves
- iOS
- Android (Kotlin)
- Android (Java)
// 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)
// 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)
// 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. The label property is recommended as it helps identify content during analysis.
- iOS
- Android (Kotlin)
- Android (Java)
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)
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)
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
- Android (Kotlin)
- Android (Java)
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)
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)
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
- Android (Kotlin)
- Android (Java)
let configuration = MediaTrackingConfiguration(id: id)
.boundaries([10, 25, 50, 75, 90]) // Fire events at these percentages
val configuration = MediaTrackingConfiguration(
id = id,
boundaries = listOf(10, 25, 50, 75, 90) // Fire events at these percentages
)
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 is attached to all media events by default. It's optional for the Media Player data model, so you could choose not to include it.
We recommend tracking this entity.
- iOS
- Android (Kotlin)
- Android (Java)
let configuration = MediaTrackingConfiguration(id: id).session(true)
val configuration = MediaTrackingConfiguration(id = id, session = true)
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
- Android (Kotlin)
- Android (Java)
let configuration = MediaTrackingConfiguration(id: id)
.entities([
SelfDescribingJson(
schema: "iglu:com.example/video_metadata/jsonschema/1-0-0",
andData: ["contentId": "abc123", "category": "tutorial"]
)
])
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")
)
)
)
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
- Android (Kotlin)
- Android (Java)
Only track specific event types by providing an allowlist:
let configuration = MediaTrackingConfiguration(id: id)
.captureEvents([MediaPlayEvent.self, MediaPauseEvent.self, MediaEndEvent.self])
Only track specific event types by providing an allowlist:
val configuration = MediaTrackingConfiguration(
id = id,
captureEvents = listOf(MediaPlayEvent::class, MediaPauseEvent::class, MediaEndEvent::class)
)
Only track specific event types by providing an allowlist:
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.
Playback events
- iOS
- Android (Kotlin)
- Android (Java)
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
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
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
- Android (Kotlin)
- Android (Java)
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"
))
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"
))
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 and media ad break entity schemas for property details.
- iOS
- Android (Kotlin)
- Android (Java)
// 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())
// 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())
// 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
- Android (Kotlin)
- Android (Java)
mediaTracking.track(SelfDescribing(
schema: "iglu:com.example/video_interaction/jsonschema/1-0-0",
payload: ["action": "share", "platform": "twitter"]
))
mediaTracking?.track(SelfDescribing(
"iglu:com.example/video_interaction/jsonschema/1-0-0",
mapOf("action" to "share", "platform" to "twitter")
))
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
- Android (Kotlin)
- 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.
mediaTracking.update(player: MediaPlayerEntity().currentTime(currentTime))
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.
mediaTracking?.update(player = MediaPlayerEntity(currentTime = currentTime))
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.
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. Pass the player instance to startMediaTracking:
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