Commit 7949031f authored by shadi@chromium.org's avatar shadi@chromium.org

Add Telemetry media loop action.

The loop action plays HTML5 media in loops and measures the avg time it 
takes to loop, i.e. time from end to start playback again.

BUG=249435

Review URL: https://chromiumcodereview.appspot.com/21134002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@220682 0039d316-1c4b-4281-b951-d872f2087c98
parent e12d8d91
...@@ -54,6 +54,9 @@ ...@@ -54,6 +54,9 @@
this.element.addEventListener('willSeek', function (e) { this.element.addEventListener('willSeek', function (e) {
metric.onWillSeek(e); metric.onWillSeek(e);
}, false); }, false);
this.element.addEventListener('willLoop', function (e) {
metric.onWillLoop(e);
}, false);
} }
HTMLMediaMetric.prototype = new MediaMetricBase(); HTMLMediaMetric.prototype = new MediaMetricBase();
...@@ -85,6 +88,20 @@ ...@@ -85,6 +88,20 @@
this.element.addEventListener('seeked', onSeeked); this.element.addEventListener('seeked', onSeeked);
}; };
HTMLMediaMetric.prototype.onWillLoop = function(e) {
var loopTimer = new Timer();
var metric = this;
var loopCount = e.loopCount;
var onEndLoop = function(e) {
var actualDuration = loopTimer.stop();
var idealDuration = metric.element.duration * loopCount;
var avg_loop_time = (actualDuration - idealDuration) / loopCount;
metric.metrics['avg_loop_time'] = avg_loop_time.toFixed(3);
e.target.removeEventListener('endLoop', onEndLoop);
};
this.element.addEventListener('endLoop', onEndLoop);
};
HTMLMediaMetric.prototype.appendMetric = function(metric, value) { HTMLMediaMetric.prototype.appendMetric = function(metric, value) {
if (!this.metrics[metric]) if (!this.metrics[metric])
this.metrics[metric] = []; this.metrics[metric] = [];
......
...@@ -58,6 +58,7 @@ class MediaMetric(Metric): ...@@ -58,6 +58,7 @@ class MediaMetric(Metric):
if not trace: if not trace:
logging.error('Metrics ID is missing in results.') logging.error('Metrics ID is missing in results.')
return return
AddOneResult('avg_loop_time', 'sec')
AddOneResult('decoded_audio_bytes', 'bytes') AddOneResult('decoded_audio_bytes', 'bytes')
AddOneResult('decoded_video_bytes', 'bytes') AddOneResult('decoded_video_bytes', 'bytes')
AddOneResult('decoded_frame_count', 'frames') AddOneResult('decoded_frame_count', 'frames')
......
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file performs actions on media elements.
(function() {
function loopMedia(selector, loopCount) {
// Loops media playback `loopCount` times.
var mediaElements = window.__findMediaElements(selector);
for (var i = 0; i < mediaElements.length; i++) {
loop(mediaElements[i], loopCount);
}
}
function loop(element, loopCount) {
if (element instanceof HTMLMediaElement)
loopHTML5Element(element, loopCount);
else
throw new Error('Can not play non HTML5 media elements.');
}
function loopHTML5Element(element, loopCount) {
element['loop_completed'] = false;
var currentLoop = 0;
var onLoop = function(e) {
++currentLoop;
if (currentLoop == loopCount) {
element.pause();
element.removeEventListener('seeked', onLoop);
element['loop_completed'] = true;
// Dispatch endLoopEvent to mark end of looping.
var endLoopEvent = document.createEvent('Event');
endLoopEvent.initEvent('endLoop', false, false);
element.dispatchEvent(endLoopEvent);
}
};
function onError(e) {
throw new Error('Error playing media :' + e.type);
}
element.addEventListener('error', onError);
element.addEventListener('abort', onError);
element.addEventListener('seeked', onLoop);
element.loop = true;
// Dispatch willLoopEvent to measure loop time.
var willLoopEvent = document.createEvent('Event');
willLoopEvent.initEvent('willLoop', false, false);
willLoopEvent.loopCount = loopCount;
element.dispatchEvent(willLoopEvent);
// Reset HTML5 player to start playback from beginning.
element.load();
element.play();
}
window.__loopMedia = loopMedia;
})();
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A Telemetry page_action that loops media playback.
Action attributes are:
- loop_count: The number of times to loop media.
- selector: If no selector is defined then the action attempts to loop the first
media element on the page. If 'all' then loop all media elements.
- wait_timeout: Timeout to wait for media to loop. Default is
60 sec x loop_count.
- wait_for_loop: If true, forces the action to wait for last loop to end,
otherwise it starts the loops and exit. Default true.
"""
from telemetry.core import exceptions
from telemetry.page.actions import page_action
import telemetry.page.actions.media_action as media_action
class LoopAction(media_action.MediaAction):
def WillRunAction(self, page, tab):
"""Load the media metrics JS code prior to running the action."""
super(LoopAction, self).WillRunAction(page, tab)
self.LoadJS(tab, 'loop.js')
def RunAction(self, page, tab, previous_action):
try:
assert hasattr(self, 'loop_count') and self.loop_count > 0
selector = self.selector if hasattr(self, 'selector') else ''
tab.ExecuteJavaScript('window.__loopMedia("%s", %i);' %
(selector, self.loop_count))
timeout = (self.wait_timeout if hasattr(self, 'wait_timeout')
else 60 * self.loop_count)
# Check if there is no need to wait for all loops to end
if hasattr(self, 'wait_for_loop') and not self.wait_for_loop:
return
self.WaitForEvent(tab, selector, 'loop', timeout)
except exceptions.EvaluateException:
raise page_action.PageActionFailed('Cannot loop media element(s) with '
'selector = %s.' % selector)
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from telemetry.core import util
from telemetry.page.actions import loop
from telemetry.unittest import tab_test_case
AUDIO_1_LOOP_CHECK = 'window.__hasEventCompleted("#audio_1", "loop");'
VIDEO_1_LOOP_CHECK = 'window.__hasEventCompleted("#video_1", "loop");'
class LoopActionTest(tab_test_case.TabTestCase):
def setUp(self):
tab_test_case.TabTestCase.setUp(self)
self._browser.SetHTTPServerDirectories(util.GetUnittestDataDir())
self._tab.Navigate(self._browser.http_server.UrlOf('video_test.html'))
self._tab.WaitForDocumentReadyStateToBeComplete()
def testLoopWithNoSelector(self):
"""Tests that with no selector Loop action loops first media element."""
data = {'selector': '#video_1', 'loop_count': 2}
action = loop.LoopAction(data)
action.WillRunAction(None, self._tab)
action.RunAction(None, self._tab, None)
# Assert only first video has played.
self.assertTrue(self._tab.EvaluateJavaScript(VIDEO_1_LOOP_CHECK))
self.assertFalse(self._tab.EvaluateJavaScript(AUDIO_1_LOOP_CHECK))
def testLoopWithAllSelector(self):
"""Tests that Loop action loops all video elements with selector='all'."""
data = {'selector': 'all', 'loop_count': 2}
action = loop.LoopAction(data)
action.WillRunAction(None, self._tab)
# Both videos not playing before running action.
self.assertFalse(self._tab.EvaluateJavaScript(VIDEO_1_LOOP_CHECK))
self.assertFalse(self._tab.EvaluateJavaScript(AUDIO_1_LOOP_CHECK))
action.RunAction(None, self._tab, None)
# Assert all media elements played.
self.assertTrue(self._tab.EvaluateJavaScript(VIDEO_1_LOOP_CHECK))
self.assertTrue(self._tab.EvaluateJavaScript(AUDIO_1_LOOP_CHECK))
def testLoopWaitForLoopTimeout(self):
"""Tests that wait_for_loop timeouts if video does not loop."""
data = {'selector': '#video_1',
'wait_timeout': 1,
'loop_count': 2}
action = loop.LoopAction(data)
action.WillRunAction(None, self._tab)
self.assertFalse(self._tab.EvaluateJavaScript(VIDEO_1_LOOP_CHECK))
self.assertRaises(util.TimeoutException, action.RunAction, None, self._tab,
None)
def testLoopWithoutLoopCount(self):
"""Tests that loop action fails with no loop count."""
data = {}
action = loop.LoopAction(data)
action.WillRunAction(None, self._tab)
self.assertRaises(AssertionError, action.RunAction, None, self._tab, None)
...@@ -11,9 +11,6 @@ from telemetry.page.actions import page_action ...@@ -11,9 +11,6 @@ from telemetry.page.actions import page_action
class MediaAction(page_action.PageAction): class MediaAction(page_action.PageAction):
def __init__(self, attributes=None):
super(MediaAction, self).__init__(attributes)
def WillRunAction(self, page, tab): def WillRunAction(self, page, tab):
"""Loads the common media action JS code prior to running the action.""" """Loads the common media action JS code prior to running the action."""
self.LoadJS(tab, 'media_action.js') self.LoadJS(tab, 'media_action.js')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment