- add sources.
[platform/framework/web/crosswalk.git] / src / tools / perf / metrics / media.js
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.
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;
16   }
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
29   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();
49
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);
57     this.element.addEventListener('willLoop', function (e) {
58         metric.onWillLoop(e);
59       }, false);
60   }
61
62   HTMLMediaMetric.prototype = new MediaMetricBase();
63   HTMLMediaMetric.prototype.constructor = HTMLMediaMetric;
64
65   HTMLMediaMetric.prototype.setID = function() {
66     if (this.element.id)
67       this.id = this.element.id;
68     else if (this.element.src)
69       this.id = this.element.src.substring(this.element.src.lastIndexOf("/")+1);
70     else
71       this.id = 'media_' + window.__globalCounter++;
72   };
73
74   HTMLMediaMetric.prototype.onWillPlay = function(e) {
75     this.playbackTimer = new Timer();
76   };
77
78   HTMLMediaMetric.prototype.onWillSeek = function(e) {
79     var seekLabel = '';
80     if (e.seekLabel)
81       seekLabel = '_' + e.seekLabel;
82     var metric = this;
83     var onSeeked = function(e) {
84         metric.appendMetric('seek' + seekLabel, metric.seekTimer.stop())
85         e.target.removeEventListener('seeked', onSeeked);
86       };
87     this.seekTimer = new Timer();
88     this.element.addEventListener('seeked', onSeeked);
89   };
90
91   HTMLMediaMetric.prototype.onWillLoop = function(e) {
92     var loopTimer = new Timer();
93     var metric = this;
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);
101       };
102     this.element.addEventListener('endLoop', onEndLoop);
103   };
104
105   HTMLMediaMetric.prototype.appendMetric = function(metric, value) {
106     if (!this.metrics[metric])
107       this.metrics[metric] = [];
108     this.metrics[metric].push(value);
109   }
110
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();
115   };
116
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
121     // is used.
122     this.metrics['buffering_time'] = time_to_end - this.element.duration * 1000;
123   };
124
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;
132     return this.metrics;
133   };
134
135   function MediaMetric(element) {
136     if (element instanceof HTMLMediaElement)
137       return new HTMLMediaMetric(element);
138     throw new Error('Unrecognized media element type.');
139   }
140
141   function Timer() {
142     this.start_ = 0;
143     this.start();
144   }
145
146   Timer.prototype = {
147     start: function() {
148       this.start_ = getCurrentTime();
149     },
150
151     stop: function() {
152       // Return delta time since start in millisecs.
153       return ((getCurrentTime() - this.start_)).toFixed(3);
154     }
155   };
156
157   function checkElementIsNotBound(element) {
158     if (!element)
159       return;
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.');
163     }
164   }
165
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]));
172   }
173
174   function getCurrentTime() {
175     if (window.performance)
176       return (performance.now ||
177               performance.mozNow ||
178               performance.msNow ||
179               performance.oNow ||
180               performance.webkitNow).call(window.performance);
181     else
182       return Date.now();
183   }
184
185   function getAllMetrics() {
186     // Returns a summary (info + metrics) for all media metrics.
187     var metrics = [];
188     for (var i = 0; i < window.__mediaMetrics.length; i++)
189       metrics.push(window.__mediaMetrics[i].getSummary());
190     return metrics;
191   }
192
193   window.__globalCounter = 0;
194   window.__mediaMetrics = [];
195   window.__getAllMetrics = getAllMetrics;
196   window.__createMediaMetricsForDocument = createMediaMetricsForDocument;
197 })();