[email protected] | 4d5f13bb | 2013-08-02 00:45:44 | [diff] [blame^] | 1 | // Copyright 2013 The Chromium Authors. All rights reserved. |
[email protected] | 8295ab5 | 2013-07-15 22:19:15 | [diff] [blame] | 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; |
[email protected] | 8295ab5 | 2013-07-15 22:19:15 | [diff] [blame] | 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 | |
[email protected] | 8295ab5 | 2013-07-15 22:19:15 | [diff] [blame] | 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(); |
[email protected] | 8e0a395 | 2013-07-23 23:43:21 | [diff] [blame] | 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); |
[email protected] | 8295ab5 | 2013-07-15 22:19:15 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | HTMLMediaMetric.prototype = new MediaMetricBase(); |
| 60 | HTMLMediaMetric.prototype.constructor = HTMLMediaMetric; |
[email protected] | 8e0a395 | 2013-07-23 23:43:21 | [diff] [blame] | 61 | |
[email protected] | 8295ab5 | 2013-07-15 22:19:15 | [diff] [blame] | 62 | 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] | 8e0a395 | 2013-07-23 23:43:21 | [diff] [blame] | 71 | 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] | 8295ab5 | 2013-07-15 22:19:15 | [diff] [blame] | 104 | 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] | 8295ab5 | 2013-07-15 22:19:15 | [diff] [blame] | 114 | 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 | })(); |