
January 1, 2022
Track HTML5 Video with Google Analytics 4 and Google Tag Manager
If your website is using some no-name video player, there is a very high chance that this is a generic HTML5 video player. If you want to track its engagement with Google Tag Manager, you will need to do some additional configuration.
In this blog post, I will show how to track HTML5 video player with Google Analytics 4 and Google Tag Manager.
Table of Contents
– Hide table of contents –
- GTM recipe
- Custom JavaScript variable + Auto-event listener
- Test the listener
- Data Layer Variables, Custom Event Trigger
- GA4 event tag
- Test the setup
- Register custom dimensions
- Wait for up to 24 hours
- Where can I see data in GA4 reports?
- Final words
GTM recipe
If you are in a hurry, I have prepared a GTM container template that you can import, configure and then it will start tracking embedded HTML5 video players + send the data to GA4.
Custom JavaScript variable + Auto-event listener
The most important part of this setup is a custom code that is designed to keep looking for HTML5 video interactions. Originally, this solution was created by David Vallejo but I have added some modifications to make the tracking more convenient with Google Analytics 4. But before we create this tag, we need to create a Custom JavaScript variable.
The listener that we are going to use consists of 100+ lines of code and if you are working with GTM, you should always try to optimize the setup so that it affects the page loading/performance as little as possible.
Instead of firing the listener on every page, we will be firing it only on those pages where the HTML5 player is actually present. That can be done with the Custom JavaScript variable.
In Google Tag Manager, go to Variables > New > Custom JavaScript and paste the following code:
function () { var video = document.getElementsByTagName('video').length; if (video > 0) { return true; } else { return false; } }
Name this variable: cjs – is HTML5 Video on a page.
Then go to Triggers > New > Window Loaded and enter the following settings:
This trigger will be activated when the page completely loads and the HTML5 video player is present on a page. How will we know if the player is on a page? That Custom JavaScript variable will return true if the player is actually available on a page.
Now, it’s time to create a Custom HTML tag that will contain the listener’s code. Just to remind you, this tag will be looking for HTML5 video interactions and if it spots one, we will be able to see it in the GTM preview mode, thus fire the GA4 event tag.
In GTM, go to Tags > New > Custom HTML and paste the following code:
<script> // Let's wrap everything inside a function so variables are not defined as globals (function() { // This is gonna our percent buckets ( 25%-75% ) var divisor = 25; // We're going to save our players status on this object. var videos_status = {}; // This is the funcion that is gonna handle the event sent by the player listeners function eventHandler(e) { switch (e.type) { // This event type is sent everytime the player updated it's current time, // We're using for the % of the video played. case 'timeupdate': // Let's set the save the current player's video time in our status object videos_status[e.target.id].current = Math.round(e.target.currentTime); // We just want to send the percent events once var pct = Math.floor(100 * videos_status[e.target.id].current / e.target.duration); for (var j in videos_status[e.target.id]._progress_markers) { if (pct >= j && j > videos_status[e.target.id].greatest_marker) { videos_status[e.target.id].greatest_marker = j; } } // current bucket hasn't been already sent to GA?, let's push it to GTM if (videos_status[e.target.id].greatest_marker && !videos_status[e.target.id]._progress_markers[videos_status[e.target.id].greatest_marker]) { videos_status[e.target.id]._progress_markers[videos_status[e.target.id].greatest_marker] = true; dataLayer.push({ 'event': 'video', 'video_status': 'progress', 'video_provider' : 'generic html5 video player', 'video_percent': videos_status[e.target.id].greatest_marker, // We are sanitizing the current video src string, and getting just the video name part 'video_title': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1]) }); } break; // This event is fired when user's click on the play button case 'play': dataLayer.push({ 'event': 'video', 'video_status' : 'play', 'video_provider' : 'generic html5 video player', 'video_percent': videos_status[e.target.id].greatest_marker, 'video_title': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1]) }); break; // This event is fied when user's click on the pause button case 'pause': if (videos_status[e.target.id].greatest_marker < '75') { dataLayer.push({ 'event': 'video', 'video_status' : 'pause', 'video_provider' : 'generic html5 video player', 'video_percent': videos_status[e.target.id].greatest_marker, 'video_title': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1]) }); } break; // If the user ends playing the video, an Finish video will be pushed ( This equals to % played = 100 ) case 'ended': dataLayer.push({ 'event': 'video', 'video_status' : 'complete', 'video_provider' : 'generic html5 video player', 'video_percent': '100', 'video_title': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1]) }); break; default: break; } } // We need to configure the listeners // Let's grab all the the "video" objects on the current page var videos = document.getElementsByTagName('video'); for (var i = 0; i < videos.length; i++) { // In order to have some id to reference in our status object, we are adding an id to the video objects // that don't have an id attribute. var videoTagId; if (!videos[i].getAttribute('id')) { // Generate a random alphanumeric string to use is as the id videoTagId = 'html5_video_' + Math.random().toString(36).slice(2); videos[i].setAttribute('id', videoTagId); }// Current video has alredy a id attribute, then let's use it <img draggable="false" class="emoji" alt="?" src="https://s.w.org/images/core/emoji/2/svg/1f642.svg"> else { videoTagId = videos[i].getAttribute('id'); } // Video Status Object declaration videos_status[videoTagId] = {}; // We'll save the highest percent mark played by the user in the current video. videos_status[videoTagId].greatest_marker = 0; // Let's set the progress markers, so we can know afterwards which ones have been already sent. videos_status[videoTagId]._progress_markers = {}; for (j = 0; j < 100; j++) { videos_status[videoTagId].progress_point = divisor * Math.floor(j / divisor); videos_status[videoTagId]._progress_markers[videos_status[videoTagId].progress_point] = false; } // On page DOM, all players currentTime is 0 videos_status[videoTagId].current = 0; // Now we're setting the event listeners. videos[i].addEventListener("play", eventHandler, false); videos[i].addEventListener("pause", eventHandler, false); videos[i].addEventListener("ended", eventHandler, false); videos[i].addEventListener("timeupdate", eventHandler, false); videos[i].addEventListener("ended", eventHandler, false); } })(); </script>
Add the previously created Window Loaded trigger to this tag.
Test the listener
Save all changes in your GTM container and then click the Preview button in the top-right corner of the GTM interface and enable the preview mode.
First of all, in the preview mode, you should see that your Custom HTML tag has fired once the page loaded.
Then go to your website and interact with the HTML5 video player. Play it, watch for a bit (at least 25% of the video length), pause. Go back to the Preview mode of GTM. You should see a bunch of video events there.
Click one of them and expand the dataLayer.push. You will see some information about the interaction.
If you are seeing all of this as I am, then we can continue.
Data Layer Variables, Custom Event Trigger
Even though we have that video data in the Data Layer, we cannot use it until we create variables for each data point that we plan to use. In our case, we are going to use:
- video_status
- video_provider
- video_percent
- video_title
For each one of them, we need to create a separate Data Layer Variable. Let’s go to Variables > New > Data Layer Variable and enter the following settings:
Do the same thing for the rest of the data points you want to use (video_provider, video_percent, video_title).
Then we need to create a trigger. Every time a “video” event is pushed to the data layer (and we see it in the preview mode), we want to fire a GA4 tag (that we will create later). That tag will send the video data to Google Analytics. The only way how can we fire a tag is by using a trigger.
Since we are getting a custom named “video” event in the preview mode, we have to use a Custom Event trigger in GTM. Go to Triggers > New > Custom Event and enter the word “video” (without quotation marks, all lowercase).
It’s important that you enter the word “video” exactly the same way as you see it here:
GA4 event tag
Now, it’s time to send the HTML5 video data to Google Analytics 4 as an event. In GTM, go to Tags > New > GA4 event and enter the following settings:
- You need to select your existing GA4 config tag (if you don’t know what it is, read this guide)
- The event’s name is video_ and then I inserted the Data Layer variable that returns the video_status . As a result, the final output of the event name will be video_play, video_pause, video_progress, or video_complete. I’m just trying to follow Google’s naming convention.
- Then I inserted other data layer variables that will be sent as event parameters video_provider, video_percent, and video_title (again, I’m just following Google’s naming convention here)
- Finally, I linked the Custom Event trigger to this GA4 tag.
Test the setup
Click the Preview button in the GTM interface to refresh it. Then go to your website and interact with the HTML5 video player (play, watch for a bit, etc.). Then go back to the GTM preview mode and you should see this:
Click on any of those events and check if your GA4 tag has fired. Now click another event and do the same thing. Tag fired? That’s good.
Now go to Google Analytics > DebugView and find your device:
Once you do that, you should see the event stream and several video events (e.g. video_play). Click them and check the values of their parameters.
If everything is working as expected, it’s time to publish the container. Hit SUBMIT in the top-right corner and follow all the necessary steps. After that, your changes will go live to all visitors of your website.
Register custom dimensions
I don’t know whether this will change in the future, but right now (if you want to use/see parameters like video_title or video_percent in your regular GA4 reports and Analysis Hub), you have to register those parameters as custom dimensions.
In GA4, go to Custom Definitions and click Create Custom Dimension.
Then enter the following settings if you want to use/see the video_title parameter. Want to use others as well? You will need to register all of them one by one here. Dimension scope should be “event” for all the dimensions/parameters in this case.
Wait for up to 24 hours
Now, you have to be a bit patient and wait. The next day, you should start seeing your video events and their data in your GA4 reports.
Where can I see video event data in Google Analytics 4?
This is answered in my guide on event tracking with Google Analytics 4. I have a dedicated section that explains in which reports can you find video data. So if you want to learn more, go here.
Track HTML5 Video with Google Analytics 4: Final words
That’s it for this time. In this blog post, I explained the process of how to track embedded videos with GTM (that are not Youtube videos). In fact, if you try to implement something similar for other players, the principle is quite the same:
- You find a custom auto-event listener built specifically for that video player (if you can’t find it, then most likely, you won’t be able to track the video player)
- You create a Custom JS variable that returns true if the player is on a page
- You fire a listener (Custom HTML tag) only if the player is present on a page
- Then you create Data Layer Variables, Custom Event trigger
- Finally, you create a GA4 event tag that fires on the custom event trigger and sends the values of data layer variables
Also, don’t forget to try the GTM recipe and implement this kind of tracking faster.
13 COMMENTS
Hi Julius,
Is there a way to filter out the autoplay HTML5 videos so that the only videos being tracked are the ones being clicked?
You can achieve that by adjusting trigering options for the tag.
Hello. Thank you for your tutorials. I got as far as the video listener test in GTM preview but I can't get it to fire. In addition to, "cjs – is HTML5 Video on a page" there is another filter appended: _event equals gtm.load that causes it to fail. Do you know where this parameter is coming from to correct/remove?
My fault... the tag is fired on 'Window Loaded' not from the video click itself.
Brilliant intro to Tag Manager, GA4 and HTML5 Video tracking :-)
Will this track videos that auto-play when the page loads?
Is there a way to send data from the application? Push new arguments to the dataLayer for example, like an ID. If so, how can that be accomplished?
Hi Juliius. Great post thanks. Along with all your other posts and videos I have now a basic understanding of GTM and a fairly good setup. I have followed this post and am now tracking video interactions with one issue. I am using JW player as our video player, and I want to pass the video title properly. I assume I need to change this part of the custom HTML tag:
'video_title': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1])
..to pass in the correct video title coming from JW player, but I have no idea what it should be. Are you able to help with this?
Just google "JW player google tag manager" and you will find a code that is created specifically for JW player
Do you have a solution for vidalytics video percentage watched tracking?
There's a lot of userbase switching from wistia to vidalytics, can't find anything specific for vidalytics.
Thanks for this article.
Can we track HTML video which is getting played on model popup? It is working fine for video which is not on the model popup, but video on model popup is not getting traced in GTM.
Can you share your thought.
Video is also not getting tracked in GTM if we play video on click event.
Is there a reason you chose event names that differ from Google's enhanced measurement events for video engagement (video_start, video_progress, video_complete)?
Just wondering if you didn't think of doing so, or if you considered it but decided to use different names for a particular reason.
Thanks!