这一篇介绍如何使用Google Tag Manager(GTM)对网站中的 H5(HTML5)视频进行行为跟踪,包括播放、暂停、播放完成以及播放进度(25%、50%、75%)。
如果你的网站使用的是 YouTube 视频,可以参考另一篇延伸阅读:Google Analytics 4 中对YouTube视频做跟踪两种方法。
什么是 H5 视频(HTML5 Video)
HTML5 定义了一种通过 <video> 标签直接在网页中嵌入视频的标准方式,目前已经是网站最主流的视频播放方案。
可以通过源码判断,如果你在页面源码中看到如下结构<video></video>,基本可以确定这是一个 H5 视频:
<video controls="controls" width="320" height="240">
<source src="movie.mp4" type="video/mp4" />
</video>
还可以通过浏览器控制台判断,在浏览器开发者工具的控制台里使用document.getElementsByTagName(‘video’).length查看,返回大于 0 的数值,就表示页面有H5视频。
跟踪原理说明(核心逻辑)
H5视频是可被JavaScript直接控制的DOM元素,这正是我们可以做跟踪的关键。
整体思路如下:
- 页面加载时判断是否存在
<video> 元素
- 如果存在,注入监听代码
- 监听视频的关键行为(play / pause / ended / timeupdate)
- 触发事件后,通过
dataLayer.push() 发送数据
- GTM 使用「自定义事件」接收数据
- 将数据发送到 GA4 作为事件与参数
<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>
当前监听代码支持以下行为:
- 开始播放(play)
- 暂停(pause)
- 播放结束(ended)
- 播放进度:25%、50%、75%
本方案不依赖第三方播放器,适用于原生 <video> 视频。
配置步骤详解
配置主要分两步,第一步是配置H5视频的监听代码,第二步是配置视频事件
Step 1 :配置H5视频的监听代码
变量:判断页面是否存在H5视频
在GTM中点击「变量」——「新建」——「选择一个变量类型以开始设置…」——「自定义 JavaScript」,命名为“cjs – is HTML5 Video on a page”,然后做如下设定:
这个变量的作用是判断页面是否有视频。
触发器:页面加载完成
在GTM中点击「触发器」——「新建」——「选择一个触发器类型以开始设置…」——「窗口已加载」,命名为“Pageview – HTML5 Video Player is Present”,然后做如下设定:

选择DOM 已准备就绪还是窗口已加载,如果页面,视频加载比较快,选择DOM 已准备就绪,如果比较慢,选择窗口已经加载,这个设置要确保cjs – is HTML5 Video on a page是true,也就是页面有视频,才触发。
代码:H5视频监听代码
在GTM中点击「代码」——「新建」——「选择一个代码类型以开始设置…」——「自定义 HTML」,命名为“cHTML – HTML5 Video Listener”,做如下设置:

这个代码实现的作用是:页面加载的时候,判断页面是否有视频,有就加载H5视频的监听代码。
Step 2:配置视频事件(GTM → GA4)
接下来就是配置视频事件。
数据层变量(Data Layer Variable)
dataLayer发送的数据层信息如:
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])
要用数据层变量将video_status、video_provider、video_percent和video_title获取,以为video_status是例子,在GTM中点击「变量」——「新建」——「选择一个变量类型以开始设置…」——「数据层变量」,命名为“video_status”,然后做如下设定:
其余三个同样设置,设置后有4个数据层变量:

触发器:视频事件
在GTM中点击「触发器」——「新建」——「选择一个触发器类型以开始设置…」——「自定义事件」,命名为”video”,然后做如下设定:
GA4事件配置
在GTM中点击「代码」——「新建」——「选择一个代码类型以开始设置…」——「Google Analytics:GA4 事件」,命名为“GA4-Event-Video Tracking”,做如下设置:

Step 3:预览调试(非常关键)
然后就是测试,GTM中点击「预览」,然后模拟行为,返回到Tag Assistant看:
事件正常触发,发送的数据准确,跟踪没问题。
参考:Google Tag Manager中做预览调试(用Tag Assistant)
Step 4:在 GA4 中注册事件参数
在视频事件里,设置了三个事件参数,video_provider、video_title和video_percent和,需要在GA4里注册后才可以使用。
以video_provider为例,在GA4中点击「管理」——「自定义设置」——「创建自定义维度」,然后做如下设置:

Step 5:查看数据报告
完成上线并产生数据后,可以在GA4中查看::

适用场景与注意事项
适用原生 <video> 视频,不依赖播放器SDK,可扩展性强
注意
- 无法区分用户是否真的“看完”,只能判断播放行为
- 视频文件名作为标题,若需更友好命名需额外处理
- SPA 站点需额外处理路由变化
参考资料