blob: 7cef7cd0c74ce2794cc298b611dd176a6b397d07 [file] [log] [blame]
[email protected]4d5f13bb2013-08-02 00:45:441// Copyright 2013 The Chromium Authors. All rights reserved.
[email protected]8295ab52013-07-15 22:19:152// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file contains common utilities to find video/audio elements on a page
6// and collect metrics for each.
7
8(function() {
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);
13 this.metrics = {};
14 this.id = '';
15 this.element = element;
[email protected]8295ab52013-07-15 22:19:1516 }
17
18 MediaMetricBase.prototype.getMetrics = function() {
19 return this.metrics;
20 };
21
22 MediaMetricBase.prototype.getSummary = function() {
23 return {
24 'id': this.id,
25 'metrics': this.getMetrics()
26 };
27 };
28
[email protected]8295ab52013-07-15 22:19:1529 function HTMLMediaMetric(element) {
30 MediaMetricBase.prototype.constructor.call(this, element);
31 // Set the basic event handlers for HTML5 media element.
32 var metric = this;
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();
37 }
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) {
43 metric.onPlaying(e);
44 });
45 this.element.addEventListener('ended', function(e) {
46 metric.onEnded(e);
47 });
48 this.setID();
[email protected]8e0a3952013-07-23 23:43:2149
50 // Listen to when a Telemetry actions gets called.
51 this.element.addEventListener('willPlay', function (e) {
52 metric.onWillPlay(e);
53 }, false);
54 this.element.addEventListener('willSeek', function (e) {
55 metric.onWillSeek(e);
56 }, false);
[email protected]8295ab52013-07-15 22:19:1557 }
58
59 HTMLMediaMetric.prototype = new MediaMetricBase();
60 HTMLMediaMetric.prototype.constructor = HTMLMediaMetric;
[email protected]8e0a3952013-07-23 23:43:2161
[email protected]8295ab52013-07-15 22:19:1562 HTMLMediaMetric.prototype.setID = function() {
63 if (this.element.src)
64 this.id = this.element.src.substring(this.element.src.lastIndexOf("/")+1);
65 else if (this.element.id)
66 this.id = this.element.id;
67 else
68 this.id = 'media_' + window.__globalCounter++;
69 };
70
[email protected]8e0a3952013-07-23 23:43:2171 HTMLMediaMetric.prototype.onWillPlay = function(e) {
72 this.playbackTimer = new Timer();
73 };
74
75 HTMLMediaMetric.prototype.onWillSeek = function(e) {
76 var seekLabel = '';
77 if (e.seekLabel)
78 seekLabel = '_' + e.seekLabel;
79 var metric = this;
80 var onSeeked = function(e) {
81 metric.appendMetric('seek' + seekLabel, metric.seekTimer.stop())
82 e.target.removeEventListener('seeked', onSeeked);
83 };
84 this.seekTimer = new Timer();
85 this.element.addEventListener('seeked', onSeeked);
86 };
87
88 HTMLMediaMetric.prototype.appendMetric = function(metric, value) {
89 if (!this.metrics[metric])
90 this.metrics[metric] = [];
91 this.metrics[metric].push(value);
92 }
93
94 HTMLMediaMetric.prototype.onPlaying = function(event) {
95 // Playing event can fire more than once if seeking.
96 if (!this.metrics['time_to_play'])
97 this.metrics['time_to_play'] = this.playbackTimer.stop();
98 };
99
100 HTMLMediaMetric.prototype.onEnded = function(event) {
101 this.metrics['playback_time'] = this.playbackTimer.stop();
102 };
103
[email protected]8295ab52013-07-15 22:19:15104 HTMLMediaMetric.prototype.getMetrics = function() {
105 this.metrics['decoded_frame_count'] = this.element.webkitDecodedFrameCount;
106 this.metrics['dropped_frame_count'] = this.element.webkitDroppedFrameCount;
107 this.metrics['decoded_video_bytes'] =
108 this.element.webkitVideoDecodedByteCount;
109 this.metrics['decoded_audio_bytes'] =
110 this.element.webkitAudioDecodedByteCount;
111 return this.metrics;
112 };
113
[email protected]8295ab52013-07-15 22:19:15114 function MediaMetric(element) {
115 if (element instanceof HTMLMediaElement)
116 return new HTMLMediaMetric(element);
117 throw new Error('Unrecognized media element type.');
118 }
119
120 function Timer() {
121 this.start_ = 0;
122 this.start();
123 }
124
125 Timer.prototype = {
126 start: function() {
127 this.start_ = getCurrentTime();
128 },
129
130 stop: function() {
131 // Return delta time since start in secs.
132 return ((getCurrentTime() - this.start_) / 1000).toFixed(3);
133 }
134 };
135
136 function checkElementIsNotBound(element) {
137 if (!element)
138 return;
139 for (var i = 0; i < window.__mediaMetrics.length; i++) {
140 if (window.__mediaMetrics[i].element == element)
141 throw new Error('Can not create MediaMetric for same element twice.');
142 }
143 }
144
145 function createMediaMetricsForDocument() {
146 // Searches for all video and audio elements on the page and creates a
147 // corresponding media metric instance for each.
148 var mediaElements = document.querySelectorAll('video, audio');
149 for (var i = 0; i < mediaElements.length; i++)
150 window.__mediaMetrics.push(new MediaMetric(mediaElements[i]));
151 }
152
153 function getCurrentTime() {
154 if (window.performance)
155 return (performance.now ||
156 performance.mozNow ||
157 performance.msNow ||
158 performance.oNow ||
159 performance.webkitNow).call(window.performance);
160 else
161 return Date.now();
162 }
163
164 function getAllMetrics() {
165 // Returns a summary (info + metrics) for all media metrics.
166 var metrics = [];
167 for (var i = 0; i < window.__mediaMetrics.length; i++)
168 metrics.push(window.__mediaMetrics[i].getSummary());
169 return metrics;
170 }
171
172 window.__globalCounter = 0;
173 window.__mediaMetrics = [];
174 window.__getAllMetrics = getAllMetrics;
175 window.__createMediaMetricsForDocument = createMediaMetricsForDocument;
176})();