Tracking Youtube video engagement with usermaven
Track how users interact with embedded YouTube videos on your site, including plays, progress milestones, and completions. This guide provides a JavaScript solution using the YouTube Iframe Player API.
Goal
To send events to Usermaven when a user:
- Starts playing a YouTube video.
- Reaches specific progress milestones (e.g., 25%, 50%, 75% viewed).
- Watches the entire video (100%).
Prerequisites
- Usermaven JavaScript SDK: Ensure the Usermaven SDK is installed and initialized on your website.
- YouTube Iframe API: You’ll need to include the YouTube Iframe API script on your page.
Implementation steps
1. HTML setup
Add a <div>
element to your HTML where the YouTube player will be embedded. Give it a unique ID (e.g., player
).
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<!-- 2. Include the YouTube Iframe API script -->
<!-- This should ideally be placed before your custom script or at the end of the body -->
<script src="https://www.youtube.com/iframe_api"></script>
2. Javascript tracking code
Add the following JavaScript code to your page. It’s best placed in a <script>
tag after the YouTube API script include, or in your main JavaScript file.
// --- Configuration ---
let player;
let videoInterval;
let videoEventName = "sample-video"; // Customize this for different videos
const videoId = "M7lc1UVf-VE"; // REPLACE with your YouTube Video ID
const breakpoints = [0.25, 0.5, 0.75]; // Progress milestones to track (25%, 50%, 75%)
// --- State Variables ---
let passedBreakpoint = 0; // Tracks the last breakpoint successfully reported
let hasTrackedInitialPlay = false; // Ensures "clicked" event fires only once per play session
// 1. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
function onYouTubeIframeAPIReady() {
player = new YT.Player("player", { // 'player' is the ID of your div
height: "360",
width: "640",
videoId: videoId,
playerVars: {
// Add any playerVars you need, e.g.:
// 'autoplay': 0, // 0 for no autoplay, 1 for autoplay
// 'controls': 1, // 0 for no controls, 1 for controls
// 'modestbranding': 1, // Hide YouTube logo
// 'rel': 0 // Do not show related videos at the end
},
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
}
// 2. The API will call this function when the video player is ready.
function onPlayerReady(event) {
// You can do initial setup here if needed, e.g., mute the player
// event.target.mute();
console.log("YouTube Player ready for video:", videoId);
}
// 3. The API calls this function when the player's state changes.
function onPlayerStateChange(event) {
if (event.data === YT.PlayerState.PLAYING) {
// Video is playing
if (!hasTrackedInitialPlay) {
// This is the first play event for this "session"
usermaven("track", `clicked-${videoEventName}`);
hasTrackedInitialPlay = true;
passedBreakpoint = 0; // Reset breakpoints for this new viewing session
}
// Start checking progress if not already doing so
if (!videoInterval) {
videoInterval = setInterval(checkVideoState, 1000); // Check every second
}
} else if (
event.data === YT.PlayerState.PAUSED ||
event.data === YT.PlayerState.BUFFERING
) {
// Video paused or buffering, stop checking progress
clearInterval(videoInterval);
videoInterval = null;
} else if (event.data === YT.PlayerState.ENDED) {
// Video ended
checkVideoState(); // Run one last time to catch any final breakpoint
usermaven("track", `watched-${videoEventName}-100%`);
clearInterval(videoInterval);
videoInterval = null;
hasTrackedInitialPlay = false; // Reset for a potential replay
// passedBreakpoint will be reset on the next PLAYING event
} else if (event.data === YT.PlayerState.UNSTARTED || event.data === YT.PlayerState.CUED) {
// Video is reset (e.g. new video loaded) or cued.
clearInterval(videoInterval);
videoInterval = null;
hasTrackedInitialPlay = false;
passedBreakpoint = 0;
}
}
// 4. Function to check video progress and track breakpoints
function checkVideoState() {
if (!player || typeof player.getDuration !== 'function' || typeof player.getCurrentTime !== 'function') {
return; // Player not ready or methods unavailable
}
const duration = player.getDuration();
if (duration === 0) {
return; // Duration not available yet
}
const currentTime = player.getCurrentTime();
const viewedPercentage = currentTime / duration;
breakpoints.forEach((breakpoint) => {
if (passedBreakpoint < breakpoint && viewedPercentage >= breakpoint) {
passedBreakpoint = breakpoint; // Mark this breakpoint as passed
usermaven("track", `watched-${videoEventName}-${breakpoint * 100}%`);
}
});
}
This is a placeholder. Your actual Usermaven SDK setup will provide the
usermaven()
function.// function usermaven(type, eventName, properties) { // console.log(`Usermaven Event: ${type} - ${eventName}`, properties || ""); // }
3. Customization
videoEventName:
Change sample-video
to a unique, descriptive name for your video (e.g., product-demo-video
, homepage-banner-video
). This helps segment your analytics in Usermaven.
videoId:
Replace "M7lc1UVf-VE"
with the actual YouTube Video ID you want to embed and track. The Video ID is the string of characters after v=
in a YouTube URL (e.g., for https://www.youtube.com/watch?v=dQw4w9WgXcQ
, the ID is dQw4w9WgXcQ
).
breakpoints:
Adjust the [0.25, 0.5, 0.75]
array if you want to track different progress milestones (e.g., [0.1, 0.5, 0.9]
for 10%, 50%, 90%).
Player dimensions & playerVars:
Modify height
, width
, and the playerVars
object within onYouTubeIframeAPIReady
to customize the player’s appearance and behavior. See YouTube Player Parameters.
How it works
onYouTubeIframeAPIReady():
This function is automatically called by the YouTube API once it’s loaded. It initializes the YouTube player (YT.Player
) targeting your <div id="player">
.
onPlayerStateChange(event):
This function listens to player state changes:
- PLAYING: When the video starts playing (or resumes), it tracks a
clicked-{videoEventName}
event (only once per “play session” thanks tohasTrackedInitialPlay
). It also starts an interval (videoInterval
) to periodically check progress.passedBreakpoint
is reset here to allow tracking for replays. - PAUSED / BUFFERING: Clears the progress-checking interval to save resources.
- ENDED: Tracks a
watched-{videoEventName}-100%
event and clears the interval. It also resetshasTrackedInitialPlay
so if the user replays, the “clicked” event fires again. - UNSTARTED / CUED: Resets tracking state if a new video is cued or the player is reset.
checkVideoState():
Called every second while the video is playing.
- It calculates the current
viewedPercentage
. - It iterates through your defined
breakpoints
. If theviewedPercentage
has passed a new breakpoint (and that breakpoint hasn’t been reported yet, managed bypassedBreakpoint
), it sends awatched-{videoEventName}-{percentage}%
event to Usermaven.
Example usermaven events
Assuming videoEventName
is "product-tour"
:
- User clicks play:
usermaven("track", "clicked-product-tour")
- User watches 25% of the video:
usermaven("track", "watched-product-tour-25%")
- User watches 50% of the video:
usermaven("track", "watched-product-tour-50%")
- User watches 75% of the video:
usermaven("track", "watched-product-tour-75%")
- User watches the entire video:
usermaven("track", "watched-product-tour-100%")
Important considerations
Single Page Applications (SPAs):
If your site is an SPA and you dynamically load/unload YouTube players without a full page refresh, ensure this script is re-initialized or that player instances are correctly managed (destroyed and recreated) to avoid issues with stale player objects or event listeners. The UNSTARTED / CUED
state handling helps here.
Multiple videos on one page:
If you have multiple YouTube players on a single page, you’ll need to adapt this script. Each player would need its own YT.Player
instance, and you’d likely need to manage state variables (like passedBreakpoint
, videoInterval
) distinctly for each player, perhaps by associating them with the player’s ID or an object.
Error handling:
For production, you might want to add more robust error handling (e.g., try...catch
blocks) around API calls.