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 durationMilliseconds when creating the MediaTracker.
Basic Usage
Create a MediaTracker when video playback begins:
import com.permutive.android.MediaTracker
import android.net.Uri
class VideoActivity : AppCompatActivity () {
private val permutive by lazy {
(application as MyApplication).permutive
}
private lateinit var videoTracker: MediaTracker
override fun onCreate (savedInstanceState: Bundle ?) {
super . onCreate (savedInstanceState)
videoTracker = permutive. trackVideoView (
durationMilliseconds = 120000 , // 2 minutes
videoProperties = MediaTracker. VideoProperties (
title = "Sample Video" ,
genre = listOf ( "Documentary" ),
contentType = listOf ( "Education" ),
runtime = 120
)
)
}
override fun onDestroy () {
super . onDestroy ()
videoTracker. 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. Close existing trackers before creating new ones.
Playback Control Methods
play()
Call when video starts playing:
play(positionMs)
Call when playback position changes (e.g., seeking):
videoTracker. play (positionMs = 30000 ) // Seek to 30 seconds
pause()
Call when video is paused or buffering:
stop()
Call when video playback completes or user exits:
videoTracker. stop () // Sends VideoCompletion event
⚠️ Important: Always Call stop()
Failing to call stop() will prevent VideoCompletion events from being tracked.
Video Properties
Standard Video Properties
The SDK provides standard video properties for consistent tracking:
MediaTracker. VideoProperties (
title = "Video Title" ,
genre = listOf ( "Action" , "Thriller" ),
contentType = listOf ( "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 = listOf ( "IAB1" , "IAB2" )
)
Property Reference
These are the Kotlin SDK parameter names. The SDK maps them to the snake_case event schema and nests them under the video parent automatically.
Property Type Schema Name Description title String video.titleVideo title genre List<String> video.genreVideo genres (e.g., “Drama”, “Comedy”) contentType List<String> video.content_typeContent types (e.g., “Movie”, “Series”) ageRating String video.age_ratingAge rating (e.g., “PG-13”, “TV-MA”) runtime Int video.runtimeRuntime in seconds country String video.countryOrigin country originalLanguage String video.original_languageOriginal language code audioLanguage String video.audio_languageAudio language code areSubtitlesEnabled Boolean video.subtitles.enabledWhether subtitles are enabled subtitlesLanguage String video.subtitles.languageSubtitle language code seasonNumber Int video.season_numberSeason number (for series) episodeNumber Int video.episode_numberEpisode number (for series) consecutiveEpisodes Int video.consecutive_episodesConsecutive episodes watched iabCategories List<String> video.iab_categoriesIAB 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:
videoTracker = permutive. trackVideoView (
durationMilliseconds = 120000 ,
videoProperties = MediaTracker. VideoProperties (
title = "Tutorial Video"
),
pageProperties = MediaTracker. PageProperties (
title = "How to Use Android SDK" ,
url = Uri. parse ( "https://example.com/tutorial" ),
referrer = Uri. parse ( "https://example.com/docs" )
)
)
PageProperties Reference
Property Type Description title String Page title url Uri Page URL referrer Uri Referring page URL
Custom Properties
Add custom properties specific to your event schema:
import com.permutive.android.EventProperties
videoTracker = permutive. trackVideoView (
durationMilliseconds = 120000 ,
videoProperties = videoProps,
pageProperties = null ,
customEventProperties = EventProperties. from (
"video_id" to "vid_12345" ,
"playlist_id" to "playlist_abc" ,
"is_premium_content" to true ,
"content_partner" to "PartnerXYZ"
)
)
Complete Example
import com.permutive.android.MediaTracker
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
class VideoPlayerFragment : Fragment () {
private val permutive by lazy {
( requireActivity ().application as MyApplication).permutive
}
private lateinit var videoTracker: MediaTracker
private lateinit var exoPlayer: ExoPlayer
override fun onCreateView (
inflater: LayoutInflater ,
container: ViewGroup ?,
savedInstanceState: Bundle ?
): View ? {
// Initialize video tracker
videoTracker = permutive. trackVideoView (
durationMilliseconds = 180000 , // 3 minutes
videoProperties = MediaTracker. VideoProperties (
title = "Introduction to Android" ,
genre = listOf ( "Educational" , "Technology" ),
contentType = listOf ( "Tutorial" ),
ageRating = "G" ,
runtime = 180 ,
country = "US" ,
originalLanguage = "en" ,
audioLanguage = "en" ,
areSubtitlesEnabled = false ,
iabCategories = listOf ( "IAB19" )
),
pageProperties = MediaTracker. PageProperties (
title = "Android Tutorial Page" ,
url = Uri. parse ( "https://example.com/tutorials/android" ),
referrer = Uri. parse ( "https://example.com/home" )
),
customEventProperties = EventProperties. from (
"video_id" to "vid_tutorial_001" ,
"is_premium" to false
)
)
// Set up ExoPlayer with tracking
setupVideoPlayer ()
return inflater. inflate (R.layout.fragment_video, container, false )
}
private fun setupVideoPlayer () {
exoPlayer = ExoPlayer. Builder ( requireContext ()). build ()
exoPlayer. addListener ( object : Player . Listener {
override fun onPlaybackStateChanged (state: Int ) {
when (state) {
Player.STATE_READY -> {
// Video ready to play
if (exoPlayer.isPlaying) {
videoTracker. play ()
}
}
Player.STATE_BUFFERING -> {
videoTracker. pause ()
}
}
}
override fun onIsPlayingChanged (isPlaying: Boolean ) {
if (isPlaying) {
videoTracker. play ()
} else {
videoTracker. pause ()
}
}
})
// Track position updates
exoPlayer. addAnalyticsListener ( object : AnalyticsListener {
override fun onPositionDiscontinuity (
eventTime: AnalyticsListener .EventTime,
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
videoTracker. play (exoPlayer.currentPosition)
}
}
})
}
override fun onPause () {
super . onPause ()
videoTracker. pause ()
exoPlayer. pause ()
}
override fun onResume () {
super . onResume ()
if (exoPlayer.isPlaying) {
videoTracker. play ()
}
}
override fun onDestroy () {
super . onDestroy ()
videoTracker. stop () // Send VideoCompletion event
exoPlayer. release ()
}
}
Tracking Custom Events
Track custom video-related events using the track() method:
// User shares video
videoTracker. track (
"VideoShared" ,
EventProperties. from (
"share_method" to "twitter" ,
"video_position_seconds" to 45
)
)
// User adds to favorites
videoTracker. track ( "VideoAddedToFavorites" )
// Quality changed
videoTracker. track (
"VideoQualityChanged" ,
EventProperties. from (
"from_quality" to "720p" ,
"to_quality" to "1080p"
)
)
Video Ad Tracking
If you need to track video ads within video content, use the AdTracker API:
// Create ad tracker from video tracker
val adTracker = videoTracker. 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
adTracker. play ()
// ... ad plays ...
adTracker. completion ()
See Video Ad Tracking for complete documentation.
Use Cases
videoTracker = permutive. trackVideoView (
durationMilliseconds = movieDurationMs,
videoProperties = MediaTracker. VideoProperties (
title = movie.title,
genre = movie.genres,
contentType = listOf ( "Movie" , "Feature" ),
ageRating = movie.rating,
runtime = movie.runtimeSeconds,
country = movie.country,
originalLanguage = movie.language,
iabCategories = movie.iabCategories
)
)
videoTracker = permutive. trackVideoView (
durationMilliseconds = tutorialDurationMs,
videoProperties = MediaTracker. VideoProperties (
title = tutorial.title,
genre = listOf ( "Educational" ),
contentType = listOf ( "Tutorial" ),
runtime = tutorial.runtimeSeconds
),
customEventProperties = EventProperties. from (
"course_id" to tutorial.courseId,
"module_id" to tutorial.moduleId,
"difficulty_level" to tutorial.difficulty
)
)
videoTracker = permutive. trackVideoView (
durationMilliseconds = episodeDurationMs,
videoProperties = MediaTracker. VideoProperties (
title = episode.title,
genre = series.genres,
contentType = listOf ( "Series" , "Episode" ),
seasonNumber = episode.season,
episodeNumber = episode.number,
consecutiveEpisodes = userWatchData.consecutiveCount,
runtime = episode.runtimeSeconds
)
)
Troubleshooting
VideoCompletion Not Tracked
Problem: VideoCompletion events not appearing.Cause: stop() not called.Solution: Always call stop() in onDestroy():override fun onDestroy () {
super . onDestroy ()
videoTracker. 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:exoPlayer. addListener ( object : Player . Listener {
override fun onIsPlayingChanged (isPlaying: Boolean ) {
if (isPlaying) {
videoTracker. play ()
} else {
videoTracker. pause ()
}
}
})
Completion Percentage Missing
Problem: completion property is missing or null.Cause: Duration not provided when creating MediaTracker.Solution: Always provide durationMilliseconds:videoTracker = permutive. trackVideoView (
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
videoTracker. stop ()
// Now create new tracker
videoTracker = permutive. trackVideoView (...)
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
For complete API documentation, see the Javadocs .
play() - Start/resume video playback
play(positionMs: Long) - Start at specific position
pause() - Pause video playback
stop() - Complete video tracking (sends VideoCompletion)
setDuration(durationMs: Long) - 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
Permutive.trackVideoView(durationMilliseconds, videoProperties, pageProperties, customEventProperties) - Create MediaTracker