1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This file contains common utilities to find video/audio elements on a page
6 // and collect metrics for each.
9 // MediaMetric class responsible for collecting metrics on a media element.
10 // It attaches required event listeners in order to collect different metrics.
11 function MediaMetricBase(element) {
12 checkElementIsNotBound(element);
15 this.element = element;
18 MediaMetricBase.prototype.getMetrics = function() {
22 MediaMetricBase.prototype.getSummary = function() {
25 'metrics': this.getMetrics()
29 function HTMLMediaMetric(element) {
30 MediaMetricBase.prototype.constructor.call(this, element);
31 // Set the basic event handlers for HTML5 media element.
33 function onVideoLoad(event) {
34 // If a 'Play' action is performed, then playback_timer != undefined.
35 if (metric.playbackTimer == undefined)
36 metric.playbackTimer = new Timer();
38 // For the cases where autoplay=true, and without a 'play' action, we want
39 // to start playbackTimer at 'play' or 'loadedmetadata' events.
40 this.element.addEventListener('play', onVideoLoad);
41 this.element.addEventListener('loadedmetadata', onVideoLoad);
42 this.element.addEventListener('playing', function(e) {
45 this.element.addEventListener('ended', function(e) {
50 // Listen to when a Telemetry actions gets called.
51 this.element.addEventListener('willPlay', function (e) {
54 this.element.addEventListener('willSeek', function (e) {
57 this.element.addEventListener('willLoop', function (e) {
62 HTMLMediaMetric.prototype = new MediaMetricBase();
63 HTMLMediaMetric.prototype.constructor = HTMLMediaMetric;
65 HTMLMediaMetric.prototype.setID = function() {
67 this.id = this.element.id;
68 else if (this.element.src)
69 this.id = this.element.src.substring(this.element.src.lastIndexOf("/")+1);
71 this.id = 'media_' + window.__globalCounter++;
74 HTMLMediaMetric.prototype.onWillPlay = function(e) {
75 this.playbackTimer = new Timer();
78 HTMLMediaMetric.prototype.onWillSeek = function(e) {
81 seekLabel = '_' + e.seekLabel;
83 var onSeeked = function(e) {
84 metric.appendMetric('seek' + seekLabel, metric.seekTimer.stop())
85 e.target.removeEventListener('seeked', onSeeked);
87 this.seekTimer = new Timer();
88 this.element.addEventListener('seeked', onSeeked);
91 HTMLMediaMetric.prototype.onWillLoop = function(e) {
92 var loopTimer = new Timer();
94 var loopCount = e.loopCount;
95 var onEndLoop = function(e) {
96 var actualDuration = loopTimer.stop();
97 var idealDuration = metric.element.duration * loopCount;
98 var avg_loop_time = (actualDuration - idealDuration) / loopCount;
99 metric.metrics['avg_loop_time'] = avg_loop_time.toFixed(3);
100 e.target.removeEventListener('endLoop', onEndLoop);
102 this.element.addEventListener('endLoop', onEndLoop);
105 HTMLMediaMetric.prototype.appendMetric = function(metric, value) {
106 if (!this.metrics[metric])
107 this.metrics[metric] = [];
108 this.metrics[metric].push(value);
111 HTMLMediaMetric.prototype.onPlaying = function(event) {
112 // Playing event can fire more than once if seeking.
113 if (!this.metrics['time_to_play'])
114 this.metrics['time_to_play'] = this.playbackTimer.stop();
117 HTMLMediaMetric.prototype.onEnded = function(event) {
118 var time_to_end = this.playbackTimer.stop() - this.metrics['time_to_play'];
119 // TODO(shadi): Measure buffering time more accurately using events such as
120 // stalled, waiting, progress, etc. This works only when continuous playback
122 this.metrics['buffering_time'] = time_to_end - this.element.duration * 1000;
125 HTMLMediaMetric.prototype.getMetrics = function() {
126 this.metrics['decoded_frame_count'] = this.element.webkitDecodedFrameCount;
127 this.metrics['dropped_frame_count'] = this.element.webkitDroppedFrameCount;
128 this.metrics['decoded_video_bytes'] =
129 this.element.webkitVideoDecodedByteCount;
130 this.metrics['decoded_audio_bytes'] =
131 this.element.webkitAudioDecodedByteCount;
135 function MediaMetric(element) {
136 if (element instanceof HTMLMediaElement)
137 return new HTMLMediaMetric(element);
138 throw new Error('Unrecognized media element type.');
148 this.start_ = getCurrentTime();
152 // Return delta time since start in millisecs.
153 return ((getCurrentTime() - this.start_)).toFixed(3);
157 function checkElementIsNotBound(element) {
160 for (var i = 0; i < window.__mediaMetrics.length; i++) {
161 if (window.__mediaMetrics[i].element == element)
162 throw new Error('Can not create MediaMetric for same element twice.');
166 function createMediaMetricsForDocument() {
167 // Searches for all video and audio elements on the page and creates a
168 // corresponding media metric instance for each.
169 var mediaElements = document.querySelectorAll('video, audio');
170 for (var i = 0; i < mediaElements.length; i++)
171 window.__mediaMetrics.push(new MediaMetric(mediaElements[i]));
174 function getCurrentTime() {
175 if (window.performance)
176 return (performance.now ||
177 performance.mozNow ||
180 performance.webkitNow).call(window.performance);
185 function getAllMetrics() {
186 // Returns a summary (info + metrics) for all media metrics.
188 for (var i = 0; i < window.__mediaMetrics.length; i++)
189 metrics.push(window.__mediaMetrics[i].getSummary());
193 window.__globalCounter = 0;
194 window.__mediaMetrics = [];
195 window.__getAllMetrics = getAllMetrics;
196 window.__createMediaMetricsForDocument = createMediaMetricsForDocument;