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'] =
100 Math
.round(avg_loop_time
* 1000) / 1000;
101 e
.target
.removeEventListener('endLoop', onEndLoop
);
103 this.element
.addEventListener('endLoop', onEndLoop
);
106 HTMLMediaMetric
.prototype.appendMetric = function(metric
, value
) {
107 if (!this.metrics
[metric
])
108 this.metrics
[metric
] = [];
109 this.metrics
[metric
].push(value
);
112 HTMLMediaMetric
.prototype.onPlaying = function(event
) {
113 // Playing event can fire more than once if seeking.
114 if (!this.metrics
['time_to_play'] && this.playbackTimer
)
115 this.metrics
['time_to_play'] = this.playbackTimer
.stop();
118 HTMLMediaMetric
.prototype.onEnded = function(event
) {
119 var time_to_end
= this.playbackTimer
.stop() - this.metrics
['time_to_play'];
120 // TODO(shadi): Measure buffering time more accurately using events such as
121 // stalled, waiting, progress, etc. This works only when continuous playback
123 this.metrics
['buffering_time'] = time_to_end
- this.element
.duration
* 1000;
126 HTMLMediaMetric
.prototype.getMetrics = function() {
127 var decodedFrames
= this.element
.webkitDecodedFrameCount
;
128 var droppedFrames
= this.element
.webkitDroppedFrameCount
;
129 // Audio media does not report decoded/dropped frame count
130 if (decodedFrames
!= undefined)
131 this.metrics
['decoded_frame_count'] = decodedFrames
;
132 if (droppedFrames
!= undefined)
133 this.metrics
['dropped_frame_count'] = droppedFrames
;
134 this.metrics
['decoded_video_bytes'] =
135 this.element
.webkitVideoDecodedByteCount
|| 0;
136 this.metrics
['decoded_audio_bytes'] =
137 this.element
.webkitAudioDecodedByteCount
|| 0;
141 function MediaMetric(element
) {
142 if (element
instanceof HTMLMediaElement
)
143 return new HTMLMediaMetric(element
);
144 throw new Error('Unrecognized media element type.');
154 this.start_
= getCurrentTime();
158 // Return delta time since start in millisecs.
159 return Math
.round((getCurrentTime() - this.start_
) * 1000) / 1000;
163 function checkElementIsNotBound(element
) {
166 if (getMediaMetric(element
))
167 throw new Error('Can not create MediaMetric for same element twice.');
170 function getMediaMetric(element
) {
171 for (var i
= 0; i
< window
.__mediaMetrics
.length
; i
++) {
172 if (window
.__mediaMetrics
[i
].element
== element
)
173 return window
.__mediaMetrics
[i
];
178 function createMediaMetricsForDocument() {
179 // Searches for all video and audio elements on the page and creates a
180 // corresponding media metric instance for each.
181 var mediaElements
= document
.querySelectorAll('video, audio');
182 for (var i
= 0; i
< mediaElements
.length
; i
++)
183 window
.__mediaMetrics
.push(new MediaMetric(mediaElements
[i
]));
186 function getCurrentTime() {
187 if (window
.performance
)
188 return (performance
.now
||
189 performance
.mozNow
||
192 performance
.webkitNow
).call(window
.performance
);
197 function getAllMetrics() {
198 // Returns a summary (info + metrics) for all media metrics.
200 for (var i
= 0; i
< window
.__mediaMetrics
.length
; i
++)
201 metrics
.push(window
.__mediaMetrics
[i
].getSummary());
205 window
.__globalCounter
= 0;
206 window
.__mediaMetrics
= [];
207 window
.__getMediaMetric
= getMediaMetric
;
208 window
.__getAllMetrics
= getAllMetrics
;
209 window
.__createMediaMetricsForDocument
= createMediaMetricsForDocument
;