summaryrefslogtreecommitdiffstats
path: root/media/test
diff options
context:
space:
mode:
authorshadi@chromium.org <shadi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-21 19:57:02 +0000
committershadi@chromium.org <shadi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-21 19:57:02 +0000
commit1bede83dac9be1583d699b15e9f28942b25275ee (patch)
treebea88e627199b4342a959d0b8d6d18e35c089c4a /media/test
parent5c5e60b1d5f9db17f026bbe7269b83b472e2a7b6 (diff)
downloadchromium_src-1bede83dac9be1583d699b15e9f28942b25275ee.zip
chromium_src-1bede83dac9be1583d699b15e9f28942b25275ee.tar.gz
chromium_src-1bede83dac9be1583d699b15e9f28942b25275ee.tar.bz2
Have media content and chrome browser tests load data from media/test/data
Update remaining encrypted media browser tests to use new player app. (This is identical to r282795 which got reverted, i.e. revert of revert). The fix was submitted separately in r283757 BUG=379314 Review URL: https://codereview.chromium.org/398823004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284489 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/test')
-rw-r--r--media/test/data/blackwhite.html231
-rw-r--r--media/test/data/eme_player.html108
-rw-r--r--media/test/data/eme_player_js/app_loader.js24
-rw-r--r--media/test/data/eme_player_js/clearkey_player.js33
-rw-r--r--media/test/data/eme_player_js/eme_app.js81
-rw-r--r--media/test/data/eme_player_js/file_io_test_player.js34
-rw-r--r--media/test/data/eme_player_js/fps_observer.js67
-rw-r--r--media/test/data/eme_player_js/globals.js80
-rw-r--r--media/test/data/eme_player_js/media_source_utils.js70
-rw-r--r--media/test/data/eme_player_js/player_utils.js186
-rw-r--r--media/test/data/eme_player_js/prefixed_clearkey_player.js27
-rw-r--r--media/test/data/eme_player_js/prefixed_widevine_player.js32
-rw-r--r--media/test/data/eme_player_js/test_config.js65
-rw-r--r--media/test/data/eme_player_js/utils.js245
-rw-r--r--media/test/data/eme_player_js/widevine_player.js38
-rw-r--r--media/test/data/encrypted_frame_size_change.html50
-rw-r--r--media/test/data/media_source_player.html38
-rw-r--r--media/test/data/mse_config_change.html134
-rw-r--r--media/test/data/player.html77
-rw-r--r--media/test/data/test_key_system_instantiation.html21
20 files changed, 1641 insertions, 0 deletions
diff --git a/media/test/data/blackwhite.html b/media/test/data/blackwhite.html
new file mode 100644
index 0000000..6b9d049
--- /dev/null
+++ b/media/test/data/blackwhite.html
@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+ body {
+ color: white;
+ background-color: black;
+ }
+ </style>
+ </head>
+ <body onload="main()">
+ <div id="buttons"></div>
+ <table>
+ <tr>
+ <td>Image</td>
+ <td id="video_header"></td>
+ <td>Absolute Diff</td>
+ <td>Different Pixels</td>
+ </tr>
+ <tr>
+ <td><img src="blackwhite.png"></div>
+ <td><video autoplay></video></div>
+ <td><canvas id="diff"></canvas></td>
+ <td><canvas id="mask"></canvas></td>
+ </tr>
+ </div>
+
+ <p id="result"></p>
+
+ <script>
+ function log(str) {
+ document.getElementById('result').textContent = str;
+ console.log(str);
+ }
+
+ function loadVideo(name) {
+ var videoElem = document.querySelector('video');
+ videoElem.src = 'blackwhite_' + name;
+
+ document.getElementById('video_header').textContent = name;
+ videoElem.addEventListener('ended', onVideoEnded);
+ }
+
+ function onVideoEnded(e) {
+ document.title = verifyVideo() ? 'ENDED' : 'FAILED';
+ }
+
+ function onVideoError(e) {
+ document.title = 'ERROR';
+ document.getElementById('diff').style.visibility = 'hidden';
+ document.getElementById('mask').style.visibility = 'hidden';
+ log('Error playing video: ' + e.target.error.code + '.');
+ }
+
+ function main() {
+ // Programatically create buttons for each clip for manual testing.
+ var buttonsElem = document.getElementById('buttons');
+
+ function createButton(name) {
+ var buttonElem = document.createElement('button');
+ buttonElem.textContent = name;
+ buttonElem.addEventListener('click', function() {
+ loadVideo(name);
+ });
+ buttonsElem.appendChild(buttonElem);
+ }
+
+ var VIDEOS = [
+ 'yuv420p.ogv',
+ 'yuv422p.ogv',
+ 'yuv444p.ogv',
+ 'yuv420p.webm',
+ 'yuv444p.webm',
+ 'yuv420p.mp4',
+ 'yuvj420p.mp4',
+ 'yuv422p.mp4',
+ 'yuv444p.mp4',
+ 'yuv420p.avi'
+ ];
+
+ for (var i = 0; i < VIDEOS.length; ++i) {
+ createButton(VIDEOS[i]);
+ }
+
+ // Video event handlers.
+ var videoElem = document.querySelector('video');
+ videoElem.addEventListener('error', onVideoError);
+
+ // Check if a query parameter was provided for automated tests.
+ if (window.location.search.length > 1) {
+ loadVideo(window.location.search.substr(1));
+ } else {
+ // If we're not an automated test, compute some pretty diffs.
+ document.querySelector('video').addEventListener('ended',
+ computeDiffs);
+ }
+ }
+
+ function getCanvasPixels(canvas) {
+ try {
+ return canvas.getContext('2d')
+ .getImageData(0, 0, canvas.width, canvas.height)
+ .data;
+ } catch(e) {
+ var message = 'ERROR: ' + e;
+ if (e.name == 'SecurityError') {
+ message += ' Couldn\'t get image pixels, try running with ' +
+ '--allow-file-access-from-files.';
+ }
+ log(message);
+ }
+ }
+
+ function verifyVideo() {
+ var videoElem = document.querySelector('video');
+ var offscreen = document.createElement('canvas');
+ offscreen.width = videoElem.videoWidth;
+ offscreen.height = videoElem.videoHeight;
+ offscreen.getContext('2d')
+ .drawImage(videoElem, 0, 0, offscreen.width, offscreen.height);
+
+ videoData = getCanvasPixels(offscreen);
+ if (!videoData)
+ return false;
+
+ // Check the color of a givel pixel |x,y| in |imgData| against an
+ // expected value, |expected|, with up to |allowedError| difference.
+ function checkColor(imgData, x, y, stride, expected, allowedError) {
+ for (var i = 0; i < 3; ++i) {
+ if (Math.abs(imgData[(x + y * stride) * 4 + i] - expected) >
+ allowedError) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Check one pixel in each quadrant (in the upper left, away from
+ // boundaries and the text, to avoid compression artifacts).
+ // Also allow a small error, for the same reason.
+
+ // TODO(mtomasz): Once code.google.com/p/libyuv/issues/detail?id=324 is
+ // fixed, the allowedError should be decreased to 1.
+ var allowedError = 2;
+
+ return checkColor(videoData, 30, 30, videoElem.videoWidth, 0xff,
+ allowedError) &&
+ checkColor(videoData, 150, 30, videoElem.videoWidth, 0x00,
+ allowedError) &&
+ checkColor(videoData, 30, 150, videoElem.videoWidth, 0x10,
+ allowedError) &&
+ checkColor(videoData, 150, 150, videoElem.videoWidth, 0xef,
+ allowedError);
+ }
+
+ // Compute a standard diff image, plus a high-contrast mask that shows
+ // each differing pixel more visibly.
+ function computeDiffs() {
+ var diffElem = document.getElementById('diff');
+ var maskElem = document.getElementById('mask');
+ var videoElem = document.querySelector('video');
+ var imgElem = document.querySelector('img');
+
+ var width = imgElem.width;
+ var height = imgElem.height;
+
+ if (videoElem.videoWidth != width || videoElem.videoHeight != height) {
+ log('ERROR: video dimensions don\'t match reference image ' +
+ 'dimensions');
+ return;
+ }
+
+ // Make an offscreen canvas to dump reference image pixels into.
+ var offscreen = document.createElement('canvas');
+ offscreen.width = width;
+ offscreen.height = height;
+
+ offscreen.getContext('2d').drawImage(imgElem, 0, 0, width, height);
+ imgData = getCanvasPixels(offscreen);
+ if (!imgData)
+ return;
+
+ // Scale and clear diff canvases.
+ diffElem.width = maskElem.width = width;
+ diffElem.height = maskElem.height = height;
+ var diffCtx = diffElem.getContext('2d');
+ var maskCtx = maskElem.getContext('2d');
+ maskCtx.clearRect(0, 0, width, height);
+ diffCtx.clearRect(0, 0, width, height);
+
+ // Copy video pixels into diff.
+ diffCtx.drawImage(videoElem, 0, 0, width, height);
+
+ var diffIData = diffCtx.getImageData(0, 0, width, height);
+ var diffData = diffIData.data;
+ var maskIData = maskCtx.getImageData(0, 0, width, height);
+ var maskData = maskIData.data;
+
+ // Make diffs and collect stats.
+ var meanSquaredError = 0;
+ for (var i = 0; i < imgData.length; i += 4) {
+ var difference = 0;
+ for (var j = 0; j < 3; ++j) {
+ diffData[i + j] = Math.abs(diffData[i + j] - imgData[i + j]);
+ meanSquaredError += diffData[i + j] * diffData[i + j];
+ if (diffData[i + j] != 0) {
+ difference += diffData[i + j];
+ }
+ }
+ if (difference > 0) {
+ if (difference <= 3) {
+ // If we're only off by a bit per channel or so, use darker red.
+ maskData[i] = 128;
+ } else {
+ // Bright red to indicate a different pixel.
+ maskData[i] = 255;
+ }
+ maskData[i+3] = 255;
+ }
+ }
+
+ meanSquaredError /= width * height;
+ log('Mean squared error: ' + meanSquaredError);
+ diffCtx.putImageData(diffIData, 0, 0);
+ maskCtx.putImageData(maskIData, 0, 0);
+ document.getElementById('diff').style.visibility = 'visible';
+ document.getElementById('mask').style.visibility = 'visible';
+ }
+ </script>
+ </body>
+</html>
diff --git a/media/test/data/eme_player.html b/media/test/data/eme_player.html
new file mode 100644
index 0000000..65c120f
--- /dev/null
+++ b/media/test/data/eme_player.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html lang='en-US'>
+ <head>
+ <title>EME playback test application</title>
+ </head>
+ <body style='font-family:"Lucida Console", Monaco, monospace; font-size:14px'>
+ <i>Clearkey works only with content encrypted using bear key.</i><br><br>
+ <table>
+ <tr title='URL param mediaFile=...'>
+ <td><label for='mediaFile'>Encrypted video URL:</label></td>
+ <td><input id='mediaFile' type='text' size='64'></td>
+ </tr>
+ <tr title='URL param licenseServerURL=...'>
+ <td><label for='licenseServer'>License sever URL:</label></td>
+ <td><input id='licenseServer' type='text' size='64'></td>
+ </tr>
+ <tr title='URL param keySystem=...'>
+ <td><label for='keySystemList'>Key system:</label></td>
+ <td><select id='keySystemList'></select></td>
+ </tr>
+ <tr title='URL param mediaType=...'>
+ <td><label for='mediaTypeList'>Media type:</label></td>
+ <td><select id='mediaTypeList'></select></td>
+ </tr>
+ <tr title='URL param usePrefixedEME=1|0'>
+ <td><label for='usePrefixedEME'>EME API version:</label></td>
+ <td><select id='usePrefixedEME'></select></td>
+ </tr>
+ <tr title='URL param useMSE=1|0'>
+ <td><label for='useMSE'>Load media by:</label></td>
+ <td>
+ <select id='useMSE'>
+ <option value='true' selected='selected'>MSE</option>
+ <option value='false'>src</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ <br>
+ <button onclick='Play();'>Play</button>
+ <br><br>
+ Decoded fps: <span id='decodedFPS'></span>
+ <br>
+ Dropped fps: <span id='droppedFPS'></span>
+ <br>
+ Total dropped frames: <span id='droppedFrames'></span>
+ <br><br>
+ <table>
+ <tr>
+ <td valign='top'><span id='video'></span></td>
+ <td valign='top'>
+ <label for='logs' onclick="toggleDisplay('logs');"><i>Click to toggle logs visibility (newest at top).</i><br></label>
+ <div id='logs' style='overflow: auto; height: 480px; width: 480px; white-space: nowrap; display: none'></div>
+ </td>
+ </tr>
+ </table>
+ <div></div>
+ </body>
+ <script src='eme_player_js/app_loader.js' type='text/javascript'></script>
+ <script type='text/javascript'>
+ var testConfig = new TestConfig();
+ testConfig.loadQueryParams();
+ // Update document with test configuration values.
+ var emeApp = new EMEApp(testConfig);
+
+ function onTimeUpdate(e) {
+ var video = e.target;
+ if (video.currentTime < 1)
+ return;
+ // For loadSession() tests, addKey() will not be called after
+ // loadSession() (the key is loaded internally). Do not check keyadded
+ // and heartbeat for these tests.
+ if (!testConfig.sessionToLoad) {
+ // keyadded may be fired around the start of playback; check for it
+ // after a delay to avoid timing issues.
+ if (testConfig.usePrefixedEME && !video.receivedKeyAdded)
+ Utils.failTest('Key added event not received.');
+ if (testConfig.keySystem == EXTERNAL_CLEARKEY &&
+ !video.receivedHeartbeat)
+ Utils.failTest('Heartbeat keymessage event not received.');
+ }
+ video.removeEventListener('ended', Utils.failTest);
+ Utils.installTitleEventHandler(video, 'ended');
+ video.removeEventListener('timeupdate', onTimeUpdate);
+ }
+
+ function Play() {
+ // Update test configuration with UI elements values.
+ var video = emeApp.createPlayer().video;
+ Utils.resetTitleChange();
+ // Ended should not fire before onTimeUpdate.
+ video.addEventListener('ended', Utils.failTest);
+ video.addEventListener('timeupdate', onTimeUpdate);
+ video.play();
+ }
+
+ function toggleDisplay(id) {
+ var element = document.getElementById(id);
+ if (!element)
+ return;
+ if (element.style['display'] != 'none')
+ element.style['display'] = 'none';
+ else
+ element.style['display'] = '';
+ }
+ Play();
+ </script>
+</html>
diff --git a/media/test/data/eme_player_js/app_loader.js b/media/test/data/eme_player_js/app_loader.js
new file mode 100644
index 0000000..f4a3e74
--- /dev/null
+++ b/media/test/data/eme_player_js/app_loader.js
@@ -0,0 +1,24 @@
+// Copyright 2014 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.
+
+// Adds a Javascript source tag to the document.
+function addScriptTag(src) {
+ document.write(
+ '<script type="text/javascript" src="eme_player_js/' + src +
+ '"></script>');
+}
+
+// Load all the dependencies for the app.
+addScriptTag('globals.js');
+addScriptTag('utils.js');
+addScriptTag('test_config.js');
+addScriptTag('fps_observer.js');
+addScriptTag('media_source_utils.js');
+addScriptTag('player_utils.js');
+addScriptTag('prefixed_clearkey_player.js');
+addScriptTag('clearkey_player.js');
+addScriptTag('widevine_player.js');
+addScriptTag('prefixed_widevine_player.js');
+addScriptTag('file_io_test_player.js');
+addScriptTag('eme_app.js');
diff --git a/media/test/data/eme_player_js/clearkey_player.js b/media/test/data/eme_player_js/clearkey_player.js
new file mode 100644
index 0000000..6de8408
--- /dev/null
+++ b/media/test/data/eme_player_js/clearkey_player.js
@@ -0,0 +1,33 @@
+// Copyright 2014 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.
+
+// ClearKeyPlayer responsible for playing media using Clear Key key system and
+// the unprefixed version of EME.
+function ClearKeyPlayer(video, testConfig) {
+ this.video = video;
+ this.testConfig = testConfig;
+}
+
+ClearKeyPlayer.prototype.init = function() {
+ PlayerUtils.initEMEPlayer(this);
+};
+
+ClearKeyPlayer.prototype.registerEventListeners = function() {
+ PlayerUtils.registerEMEEventListeners(this);
+};
+
+ClearKeyPlayer.prototype.onMessage = function(message) {
+ Utils.timeLog('MediaKeySession onMessage', message);
+ var initData =
+ Utils.getInitDataFromMessage(message, this.testConfig.mediaType);
+ var key = Utils.getDefaultKey(this.testConfig.forceInvalidResponse);
+ var jwkSet = Utils.createJWKData(initData, key);
+ if (PROMISES_SUPPORTED) {
+ message.target.update(jwkSet).catch(function(error) {
+ Utils.failTest(error, KEY_ERROR);
+ });
+ } else {
+ message.target.update(jwkSet);
+ }
+};
diff --git a/media/test/data/eme_player_js/eme_app.js b/media/test/data/eme_player_js/eme_app.js
new file mode 100644
index 0000000..4c2ec0d
--- /dev/null
+++ b/media/test/data/eme_player_js/eme_app.js
@@ -0,0 +1,81 @@
+// Copyright 2014 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.
+
+// EMEApp is responsible for starting playback on the eme_player.html page.
+// It selects the suitable player based on key system and other test options.
+function EMEApp(testConfig) {
+ this.video_ = null;
+ this.testConfig_ = testConfig;
+ this.updateDocument(testConfig);
+}
+
+EMEApp.prototype.createPlayer = function() {
+ // Load document test configuration.
+ this.updateTestConfig();
+ if (this.video_) {
+ Utils.timeLog('Delete old video tag.');
+ this.video_.pause();
+ this.video_.remove();
+ delete(this.video_);
+ }
+
+ this.video_ = document.createElement('video');
+ this.video_.controls = true;
+ this.video_.preload = true;
+ this.video_.width = 848;
+ this.video_.height = 480;
+ var videoSpan = document.getElementById(VIDEO_ELEMENT_ID);
+ if (videoSpan)
+ videoSpan.appendChild(this.video_);
+ else
+ document.body.appendChild(this.video_);
+
+ var videoPlayer = PlayerUtils.createPlayer(this.video_, this.testConfig_);
+ if (!videoPlayer) {
+ Utils.timeLog('Cannot create a media player.');
+ return;
+ }
+ Utils.timeLog('Using ' + videoPlayer.constructor.name);
+ if (this.testConfig_.runFPS)
+ FPSObserver.observe(this.video_);
+
+ videoPlayer.init();
+ return videoPlayer;
+};
+
+EMEApp.prototype.updateDocument = function(testConfig) {
+ // Update document lists with test configuration values.
+ Utils.addOptions(KEYSYSTEM_ELEMENT_ID, KEY_SYSTEMS);
+ Utils.addOptions(MEDIA_TYPE_ELEMENT_ID, MEDIA_TYPES);
+ Utils.addOptions(USE_PREFIXED_EME_ID, EME_VERSIONS_OPTIONS,
+ EME_DISABLED_OPTIONS);
+ document.getElementById(MEDIA_FILE_ELEMENT_ID).value =
+ testConfig.mediaFile || DEFAULT_MEDIA_FILE;
+ document.getElementById(LICENSE_SERVER_ELEMENT_ID).value =
+ testConfig.licenseServerURL || DEFAULT_LICENSE_SERVER;
+ if (testConfig.keySystem)
+ Utils.ensureOptionInList(KEYSYSTEM_ELEMENT_ID, testConfig.keySystem);
+ if (testConfig.mediaType)
+ Utils.ensureOptionInList(MEDIA_TYPE_ELEMENT_ID, testConfig.mediaType);
+ document.getElementById(USE_MSE_ELEMENT_ID).value = testConfig.useMSE;
+ if (testConfig.usePrefixedEME)
+ document.getElementById(USE_PREFIXED_EME_ID).value = EME_PREFIXED_VERSION;
+};
+
+EMEApp.prototype.updateTestConfig = function() {
+ // Reload test configuration from document.
+ this.testConfig_.mediaFile =
+ document.getElementById(MEDIA_FILE_ELEMENT_ID).value;
+ this.testConfig_.keySystem =
+ document.getElementById(KEYSYSTEM_ELEMENT_ID).value;
+ this.testConfig_.mediaType =
+ document.getElementById(MEDIA_TYPE_ELEMENT_ID).value;
+ this.testConfig_.useMSE =
+ document.getElementById(USE_MSE_ELEMENT_ID).value == 'true';
+ this.testConfig_.usePrefixedEME = (
+ document.getElementById(USE_PREFIXED_EME_ID).value ==
+ EME_PREFIXED_VERSION);
+ this.testConfig_.licenseServerURL =
+ document.getElementById(LICENSE_SERVER_ELEMENT_ID).value;
+};
diff --git a/media/test/data/eme_player_js/file_io_test_player.js b/media/test/data/eme_player_js/file_io_test_player.js
new file mode 100644
index 0000000..d259801
--- /dev/null
+++ b/media/test/data/eme_player_js/file_io_test_player.js
@@ -0,0 +1,34 @@
+// Copyright 2014 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.
+
+// File IO test player is used to test File IO CDM functionality.
+function FileIOTestPlayer(video, testConfig) {
+ this.video = video;
+ this.testConfig = testConfig;
+}
+
+FileIOTestPlayer.prototype.init = function() {
+ PlayerUtils.initPrefixedEMEPlayer(this);
+};
+
+FileIOTestPlayer.prototype.registerEventListeners = function() {
+ PlayerUtils.registerPrefixedEMEEventListeners(this);
+};
+
+FileIOTestPlayer.prototype.onWebkitKeyMessage = function(message) {
+ // The test result is either '0' or '1' appended to the header.
+ if (Utils.hasPrefix(message.message, FILE_IO_TEST_RESULT_HEADER)) {
+ if (message.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) {
+ Utils.failTest('Unexpected FileIOTest CDM message' + message.message);
+ return;
+ }
+ var result_index = FILE_IO_TEST_RESULT_HEADER.length;
+ var success = String.fromCharCode(message.message[result_index]) == 1;
+ Utils.timeLog('CDM file IO test: ' + (success ? 'Success' : 'Fail'));
+ if (success)
+ Utils.setResultInTitle(FILE_IO_TEST_SUCCESS);
+ else
+ Utils.failTest('File IO CDM message fail status.');
+ }
+};
diff --git a/media/test/data/eme_player_js/fps_observer.js b/media/test/data/eme_player_js/fps_observer.js
new file mode 100644
index 0000000..8226df8
--- /dev/null
+++ b/media/test/data/eme_player_js/fps_observer.js
@@ -0,0 +1,67 @@
+// Copyright 2014 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.
+
+// FPSObserver observes a <video> and reports decoded FPS, dropped FPS, and
+// total dropped frames during the video playback.
+var FPSObserver = new function() {
+ this.video_ = null;
+ this.decodedFrames_ = 0;
+ this.droppedFrames_ = 0;
+ this.startTime_ = 0;
+ this.intID_ = null;
+}
+
+FPSObserver.observe = function(video) {
+ this.video_ = video;
+ var observer = this;
+ this.video_.addEventListener('playing', function() {
+ observer.onVideoPlaying();
+ });
+
+ this.video_.addEventListener('error', function() {
+ observer.endTest();
+ });
+
+ this.video_.addEventListener('ended', function() {
+ observer.endTest();
+ });
+};
+
+FPSObserver.onVideoPlaying = function() {
+ this.decodedFrames_ = 0;
+ this.droppedFrames_ = 0;
+ this.startTime_ = window.performance.now();
+ this.endTest(true);
+ var observer = this;
+ this.intID_ = window.setInterval(function() {
+ observer.calculateStats();}, 1000);
+};
+
+FPSObserver.calculateStats = function() {
+ if (this.video_.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA ||
+ this.video_.paused || this.video_.ended)
+ return;
+ var currentTime = window.performance.now();
+ var deltaTime = (currentTime - this.startTime_) / 1000;
+ this.startTime_ = currentTime;
+
+ // Calculate decoded frames per sec.
+ var fps = (this.video_.webkitDecodedFrameCount - this.decodedFrames_) /
+ deltaTime;
+ this.decodedFrames_ = this.video_.webkitDecodedFrameCount;
+ fps = fps.toFixed(2);
+ decodedFPSElement.innerHTML = fps;
+
+ // Calculate dropped frames per sec.
+ fps = (this.video_.webkitDroppedFrameCount - this.droppedFrames_) / deltaTime;
+ this.droppedFrames_ = this.video_.webkitDroppedFrameCount;
+ fps = fps.toFixed(2);
+ droppedFPSElement.innerHTML = fps;
+
+ droppedFramesElement.innerHTML = this.droppedFrames_;
+};
+
+FPSObserver.endTest = function() {
+ window.clearInterval(this.intID_);
+};
diff --git a/media/test/data/eme_player_js/globals.js b/media/test/data/eme_player_js/globals.js
new file mode 100644
index 0000000..c608dfe
--- /dev/null
+++ b/media/test/data/eme_player_js/globals.js
@@ -0,0 +1,80 @@
+// Copyright 2014 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 contains global constant variables used by the application.
+
+// Heart beat message header.
+var HEART_BEAT_HEADER = 'HEARTBEAT';
+
+// Default key used to encrypt many media files used in browser tests.
+var KEY = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
+ 0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
+
+var DEFAULT_LICENSE_SERVER = document.location.origin + '/license_server';
+
+var DEFAULT_MEDIA_FILE = 'http://shadi.kir/alcatraz/Chrome_44-enc_av.webm';
+
+// Key ID used for init data.
+var KEY_ID = '0123456789012345';
+
+// Unique strings to identify test result expectations.
+var KEY_ERROR = 'KEY_ERROR';
+var FILE_IO_TEST_RESULT_HEADER = 'FILEIOTESTRESULT';
+var FILE_IO_TEST_SUCCESS = 'FILE_IO_TEST_SUCCESS';
+var PREFIXED_API_LOAD_SESSION_HEADER = 'LOAD_SESSION|';
+var NOTSUPPORTEDERROR = 'NOTSUPPORTEDERROR';
+
+// Available EME key systems to use.
+var PREFIXED_CLEARKEY = 'webkit-org.w3.clearkey';
+var CLEARKEY = 'org.w3.clearkey';
+var EXTERNAL_CLEARKEY = 'org.chromium.externalclearkey';
+var WIDEVINE_KEYSYSTEM = 'com.widevine.alpha';
+var FILE_IO_TEST_KEYSYSTEM = 'org.chromium.externalclearkey.fileiotest';
+var EME_PREFIXED_VERSION = 'Prefixed EME (v 0.1b)';
+var EME_UNPREFIXED_VERSION = 'Unprefixed EME (Working draft)';
+
+// Key system name:value map to show on the document page.
+var KEY_SYSTEMS = {
+ 'Widevine': WIDEVINE_KEYSYSTEM,
+ 'Clearkey': CLEARKEY,
+ 'External Clearkey': EXTERNAL_CLEARKEY
+};
+
+// General WebM and MP4 name:content_type map to show on the document page.
+var MEDIA_TYPES = {
+ 'WebM - Audio Video': 'video/webm; codecs="vorbis, vp8"',
+ 'WebM - Video Only': 'video/webm; codecs="vp8"',
+ 'WebM - Audio Only': 'video/webm; codecs="vorbis"',
+ 'MP4 - Video Only': 'video/mp4; codecs="avc1.4D4041"',
+ 'MP4 - Audio Only': 'audio/mp4; codecs="mp4a.40.2"'
+};
+
+// Update the EME versions list by checking runtime support by the browser.
+var EME_VERSIONS_OPTIONS = {};
+EME_VERSIONS_OPTIONS[EME_UNPREFIXED_VERSION] = EME_UNPREFIXED_VERSION;
+EME_VERSIONS_OPTIONS[EME_PREFIXED_VERSION] = EME_PREFIXED_VERSION;
+
+var EME_DISABLED_OPTIONS = [];
+var PROMISES_SUPPORTED = false;
+if (!document.createElement('video').webkitAddKey)
+ EME_DISABLED_OPTIONS.push(EME_PREFIXED_VERSION);
+if (!document.createElement('video').setMediaKeys)
+ EME_DISABLED_OPTIONS.push(EME_UNPREFIXED_VERSION);
+else
+ PROMISES_SUPPORTED = MediaKeys.create != undefined;
+
+// Global document elements ID's.
+var VIDEO_ELEMENT_ID = 'video';
+var MEDIA_FILE_ELEMENT_ID = 'mediaFile';
+var LICENSE_SERVER_ELEMENT_ID = 'licenseServer';
+var KEYSYSTEM_ELEMENT_ID = 'keySystemList';
+var MEDIA_TYPE_ELEMENT_ID = 'mediaTypeList';
+var USE_MSE_ELEMENT_ID = 'useMSE';
+var USE_PREFIXED_EME_ID = 'usePrefixedEME';
+
+// These variables get updated every second, so better to have global pointers.
+var decodedFPSElement = document.getElementById('decodedFPS');
+var droppedFPSElement = document.getElementById('droppedFPS');
+var droppedFramesElement = document.getElementById('droppedFrames');
+var docLogs = document.getElementById('logs');
diff --git a/media/test/data/eme_player_js/media_source_utils.js b/media/test/data/eme_player_js/media_source_utils.js
new file mode 100644
index 0000000..8888582
--- /dev/null
+++ b/media/test/data/eme_player_js/media_source_utils.js
@@ -0,0 +1,70 @@
+// Copyright 2014 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.
+
+// MediaSourceUtils provides basic functionality to load content using MSE API.
+var MediaSourceUtils = new function() {
+}
+
+MediaSourceUtils.loadMediaSourceFromTestConfig = function(testConfig,
+ appendCallbackFn) {
+ return this.loadMediaSource(testConfig.mediaFile,
+ testConfig.mediaType,
+ appendCallbackFn);
+};
+
+MediaSourceUtils.loadMediaSource = function(mediaFiles,
+ mediaTypes,
+ appendCallbackFn) {
+ if (!mediaFiles || !mediaTypes)
+ Utils.failTest('Missing parameters in loadMediaSource().');
+
+ var mediaFiles = Utils.convertToArray(mediaFiles);
+ var mediaTypes = Utils.convertToArray(mediaTypes);
+ var totalAppended = 0;
+ function onSourceOpen(e) {
+ Utils.timeLog('onSourceOpen', e);
+ // We can load multiple media files using the same media type. However, if
+ // more than one media type is used, we expect to have a media type entry
+ // for each corresponding media file.
+ var srcBuffer = null;
+ for (var i = 0; i < mediaFiles.length; i++) {
+ if (i == 0 || mediaFiles.length == mediaTypes.length) {
+ Utils.timeLog('Creating a source buffer for type ' + mediaTypes[i]);
+ try {
+ srcBuffer = mediaSource.addSourceBuffer(mediaTypes[i]);
+ } catch (e) {
+ Utils.failTest('Exception adding source buffer: ' + e.message);
+ return;
+ }
+ }
+ doAppend(mediaFiles[i], srcBuffer);
+ }
+ }
+
+ function doAppend(mediaFile, srcBuffer) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', mediaFile);
+ xhr.responseType = 'arraybuffer';
+ xhr.addEventListener('load', function(e) {
+ var onUpdateEnd = function(e) {
+ Utils.timeLog('End of appending buffer from ' + mediaFile);
+ srcBuffer.removeEventListener('updateend', onUpdateEnd);
+ totalAppended++;
+ if (totalAppended == mediaFiles.length) {
+ if (appendCallbackFn)
+ appendCallbackFn(mediaSource);
+ else
+ mediaSource.endOfStream();
+ }
+ };
+ srcBuffer.addEventListener('updateend', onUpdateEnd);
+ srcBuffer.appendBuffer(new Uint8Array(e.target.response));
+ });
+ xhr.send();
+ }
+
+ var mediaSource = new MediaSource();
+ mediaSource.addEventListener('sourceopen', onSourceOpen);
+ return mediaSource;
+};
diff --git a/media/test/data/eme_player_js/player_utils.js b/media/test/data/eme_player_js/player_utils.js
new file mode 100644
index 0000000..ac7b6dd
--- /dev/null
+++ b/media/test/data/eme_player_js/player_utils.js
@@ -0,0 +1,186 @@
+// Copyright 2014 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.
+
+// The PlayerUtils provides utility functions to binding common media events
+// to specific player functions. It also provides functions to load media source
+// base on test configurations.
+var PlayerUtils = new function() {
+}
+
+// Prepares a video element for playback by setting default event handlers
+// and source attribute.
+PlayerUtils.registerDefaultEventListeners = function(player) {
+ Utils.timeLog('Registering video event handlers.');
+ // Map from event name to event listener function name. It is common for
+ // event listeners to be named onEventName.
+ var eventListenerMap = {
+ 'needkey': 'onNeedKey',
+ 'webkitneedkey': 'onWebkitNeedKey',
+ 'webkitkeymessage': 'onWebkitKeyMessage',
+ 'webkitkeyadded': 'onWebkitKeyAdded',
+ 'webkitkeyerror': 'onWebkitKeyError'
+ };
+ for (eventName in eventListenerMap) {
+ var eventListenerFunction = player[eventListenerMap[eventName]];
+ if (eventListenerFunction) {
+ player.video.addEventListener(eventName, function(e) {
+ player[eventListenerMap[e.type]](e);
+ });
+ }
+ }
+ // List of events that fail tests.
+ var failingEvents = ['error', 'abort'];
+ for (var i = 0; i < failingEvents.length; i++) {
+ player.video.addEventListener(failingEvents[i], Utils.failTest);
+ }
+};
+
+PlayerUtils.registerEMEEventListeners = function(player) {
+ player.video.addEventListener('needkey', function(message) {
+
+ function addMediaKeySessionListeners(mediaKeySession) {
+ mediaKeySession.addEventListener('message', function(message) {
+ player.video.receivedKeyMessage = true;
+ if (Utils.isHeartBeatMessage(message.message)) {
+ Utils.timeLog('MediaKeySession onMessage - heart beat', message);
+ player.video.receivedHeartbeat = true;
+ }
+ player.onMessage(message);
+ });
+ mediaKeySession.addEventListener('error', function(error) {
+ Utils.failTest(error, KEY_ERROR);
+ });
+ }
+
+ Utils.timeLog('Creating new media key session for contentType: ' +
+ message.contentType + ', initData: ' +
+ Utils.getHexString(message.initData));
+ try {
+ var session = message.target.mediaKeys.createSession(
+ message.contentType, message.initData);
+ if (PROMISES_SUPPORTED) {
+ session.then(addMediaKeySessionListeners)
+ .catch (function(error) {
+ Utils.failTest(error, KEY_ERROR);
+ });
+ } else {
+ addMediaKeySessionListeners(session);
+ }
+ } catch (e) {
+ Utils.failTest(e);
+ }
+ });
+ this.registerDefaultEventListeners(player);
+ try {
+ Utils.timeLog('Setting video media keys: ' + player.testConfig.keySystem);
+ if (PROMISES_SUPPORTED) {
+ MediaKeys.create(player.testConfig.keySystem).then(function(mediaKeys) {
+ player.video.setMediaKeys(mediaKeys);
+ }).catch(function(error) {
+ Utils.failTest(error, NOTSUPPORTEDERROR);
+ });
+ } else {
+ player.video.setMediaKeys(new MediaKeys(player.testConfig.keySystem));
+ }
+ } catch (e) {
+ Utils.failTest(e);
+ }
+};
+
+PlayerUtils.registerPrefixedEMEEventListeners = function(player) {
+ player.video.addEventListener('webkitneedkey', function(message) {
+ var initData = message.initData;
+ if (player.testConfig.sessionToLoad) {
+ Utils.timeLog('Loading session: ' + player.testConfig.sessionToLoad);
+ initData = Utils.convertToUint8Array(
+ PREFIXED_API_LOAD_SESSION_HEADER + player.testConfig.sessionToLoad);
+ }
+ Utils.timeLog(player.testConfig.keySystem +
+ ' Generate key request, initData: ' +
+ Utils.getHexString(initData));
+ try {
+ message.target.webkitGenerateKeyRequest(player.testConfig.keySystem,
+ initData);
+ } catch (e) {
+ Utils.failTest(e);
+ }
+ });
+
+ player.video.addEventListener('webkitkeyadded', function(message) {
+ Utils.timeLog('onWebkitKeyAdded', message);
+ message.target.receivedKeyAdded = true;
+ });
+
+ player.video.addEventListener('webkitkeyerror', function(error) {
+ Utils.timeLog('onWebkitKeyError', error);
+ Utils.failTest(error, KEY_ERROR);
+ });
+
+ player.video.addEventListener('webkitkeymessage', function(message) {
+ Utils.timeLog('onWebkitKeyMessage', message);
+ message.target.receivedKeyMessage = true;
+ if (Utils.isHeartBeatMessage(message.message)) {
+ Utils.timeLog('onWebkitKeyMessage - heart beat', message);
+ message.target.receivedHeartbeat = true;
+ }
+ });
+ this.registerDefaultEventListeners(player);
+};
+
+PlayerUtils.setVideoSource = function(player) {
+ if (player.testConfig.useMSE) {
+ Utils.timeLog('Loading media using MSE.');
+ var mediaSource =
+ MediaSourceUtils.loadMediaSourceFromTestConfig(player.testConfig);
+ player.video.src = window.URL.createObjectURL(mediaSource);
+ } else {
+ Utils.timeLog('Loading media using src.');
+ player.video.src = player.testConfig.mediaFile;
+ }
+};
+
+PlayerUtils.initEMEPlayer = function(player) {
+ this.registerEMEEventListeners(player);
+ this.setVideoSource(player);
+};
+
+PlayerUtils.initPrefixedEMEPlayer = function(player) {
+ this.registerPrefixedEMEEventListeners(player);
+ this.setVideoSource(player);
+};
+
+// Return the appropriate player based on test configuration.
+PlayerUtils.createPlayer = function(video, testConfig) {
+ // Update keySystem if using prefixed Clear Key since it is not available as a
+ // separate key system to choose from; however it can be set in URL query.
+ var usePrefixedEME = testConfig.usePrefixedEME;
+ if (testConfig.keySystem == CLEARKEY && usePrefixedEME)
+ testConfig.keySystem = PREFIXED_CLEARKEY;
+
+ function getPlayerType(keySystem) {
+ switch (keySystem) {
+ case WIDEVINE_KEYSYSTEM:
+ if (usePrefixedEME)
+ return PrefixedWidevinePlayer;
+ return WidevinePlayer;
+ case PREFIXED_CLEARKEY:
+ return PrefixedClearKeyPlayer;
+ case EXTERNAL_CLEARKEY:
+ case CLEARKEY:
+ if (usePrefixedEME)
+ return PrefixedClearKeyPlayer;
+ return ClearKeyPlayer;
+ case FILE_IO_TEST_KEYSYSTEM:
+ if (usePrefixedEME)
+ return FileIOTestPlayer;
+ default:
+ Utils.timeLog(keySystem + ' is not a known key system');
+ if (usePrefixedEME)
+ return PrefixedClearKeyPlayer;
+ return ClearKeyPlayer;
+ }
+ }
+ var Player = getPlayerType(testConfig.keySystem);
+ return new Player(video, testConfig);
+};
diff --git a/media/test/data/eme_player_js/prefixed_clearkey_player.js b/media/test/data/eme_player_js/prefixed_clearkey_player.js
new file mode 100644
index 0000000..cbca7d3
--- /dev/null
+++ b/media/test/data/eme_player_js/prefixed_clearkey_player.js
@@ -0,0 +1,27 @@
+// Copyright 2014 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.
+
+// ClearKey player responsible for playing media using Clear Key key system and
+// prefixed EME API.
+function PrefixedClearKeyPlayer(video, testConfig) {
+ this.video = video;
+ this.testConfig = testConfig;
+}
+
+PrefixedClearKeyPlayer.prototype.init = function() {
+ PlayerUtils.initPrefixedEMEPlayer(this);
+};
+
+PrefixedClearKeyPlayer.prototype.registerEventListeners = function() {
+ PlayerUtils.registerPrefixedEMEEventListeners(this);
+};
+
+PrefixedClearKeyPlayer.prototype.onWebkitKeyMessage = function(message) {
+ var initData =
+ Utils.getInitDataFromMessage(message, this.testConfig.mediaType);
+ var key = Utils.getDefaultKey(this.testConfig.forceInvalidResponse);
+ Utils.timeLog('Adding key to sessionID: ' + message.sessionId);
+ message.target.webkitAddKey(this.testConfig.keySystem, key, initData,
+ message.sessionId);
+};
diff --git a/media/test/data/eme_player_js/prefixed_widevine_player.js b/media/test/data/eme_player_js/prefixed_widevine_player.js
new file mode 100644
index 0000000..adfd570
--- /dev/null
+++ b/media/test/data/eme_player_js/prefixed_widevine_player.js
@@ -0,0 +1,32 @@
+// Copyright 2014 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.
+
+// Widevine player responsible for playing media using Widevine key system
+// and prefixed EME API.
+function PrefixedWidevinePlayer(video, testConfig) {
+ this.video = video;
+ this.testConfig = testConfig;
+}
+
+PrefixedWidevinePlayer.prototype.init = function() {
+ PlayerUtils.initPrefixedEMEPlayer(this);
+};
+
+PrefixedWidevinePlayer.prototype.registerEventListeners = function() {
+ PlayerUtils.registerPrefixedEMEEventListeners(this);
+};
+
+PrefixedWidevinePlayer.prototype.onWebkitKeyMessage = function(message) {
+ function onSuccess(response) {
+ var key = new Uint8Array(response);
+ Utils.timeLog('Adding key to sessionID: ' + message.sessionId, key);
+ message.target.webkitAddKey(this.testConfig.keySystem,
+ key,
+ new Uint8Array(1),
+ message.sessionId);
+ }
+ Utils.sendRequest('POST', 'arraybuffer', message.message,
+ this.testConfig.licenseServerURL, onSuccess,
+ this.testConfig.forceInvalidResponse);
+};
diff --git a/media/test/data/eme_player_js/test_config.js b/media/test/data/eme_player_js/test_config.js
new file mode 100644
index 0000000..8be8a9a
--- /dev/null
+++ b/media/test/data/eme_player_js/test_config.js
@@ -0,0 +1,65 @@
+// Copyright 2014 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.
+
+// Test configuration used by test page to configure the player app and other
+// test specific configurations.
+function TestConfig() {
+ this.mediaFile = null;
+ this.keySystem = null;
+ this.mediaType = null;
+ this.licenseServerURL = null;
+ this.useMSE = false;
+ this.usePrefixedEME = false;
+ this.runFPS = false;
+}
+
+TestConfig.prototype.loadQueryParams = function() {
+ // Load query parameters and set default values.
+ var r = /([^&=]+)=?([^&]*)/g;
+ // Lambda function for decoding extracted match values. Replaces '+' with
+ // space so decodeURIComponent functions properly.
+ var decodeURI = function decodeURI(s) {
+ return decodeURIComponent(s.replace(/\+/g, ' '));
+ };
+ var match;
+ while (match = r.exec(window.location.search.substring(1)))
+ this[decodeURI(match[1])] = decodeURI(match[2]);
+ this.useMSE = this.useMSE == '1' || this.useMSE == 'true';
+ this.usePrefixedEME =
+ this.usePrefixedEME == '1' || this.usePrefixedEME == 'true';
+};
+
+TestConfig.updateDocument = function() {
+ this.loadQueryParams();
+ Utils.addOptions(KEYSYSTEM_ELEMENT_ID, KEY_SYSTEMS);
+ Utils.addOptions(MEDIA_TYPE_ELEMENT_ID, MEDIA_TYPES);
+ Utils.addOptions(USE_PREFIXED_EME_ID, EME_VERSIONS_OPTIONS,
+ EME_DISABLED_OPTIONS);
+
+ document.getElementById(MEDIA_FILE_ELEMENT_ID).value =
+ this.mediaFile || DEFAULT_MEDIA_FILE;
+
+ document.getElementById(LICENSE_SERVER_ELEMENT_ID).value =
+ this.licenseServerURL || DEFAULT_LICENSE_SERVER;
+
+ if (this.keySystem)
+ Utils.ensureOptionInList(KEYSYSTEM_ELEMENT_ID, this.keySystem);
+ if (this.mediaType)
+ Utils.ensureOptionInList(MEDIA_TYPE_ELEMENT_ID, this.mediaType);
+ document.getElementById(USE_MSE_ELEMENT_ID).value = this.useMSE;
+ if (this.usePrefixedEME)
+ document.getElementById(USE_PREFIXED_EME_ID).value = EME_PREFIXED_VERSION;
+};
+
+TestConfig.init = function() {
+ // Reload test configuration from document.
+ this.mediaFile = document.getElementById(MEDIA_FILE_ELEMENT_ID).value;
+ this.keySystem = document.getElementById(KEYSYSTEM_ELEMENT_ID).value;
+ this.mediaType = document.getElementById(MEDIA_TYPE_ELEMENT_ID).value;
+ this.useMSE = document.getElementById(USE_MSE_ELEMENT_ID).value == 'true';
+ this.usePrefixedEME = document.getElementById(USE_PREFIXED_EME_ID).value ==
+ EME_PREFIXED_VERSION;
+ this.licenseServerURL =
+ document.getElementById(LICENSE_SERVER_ELEMENT_ID).value;
+};
diff --git a/media/test/data/eme_player_js/utils.js b/media/test/data/eme_player_js/utils.js
new file mode 100644
index 0000000..e664593
--- /dev/null
+++ b/media/test/data/eme_player_js/utils.js
@@ -0,0 +1,245 @@
+// Copyright 2014 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.
+
+// Utils provide logging functions and other JS functions commonly used by the
+// app and media players.
+var Utils = new function() {
+ this.titleChanged = false;
+};
+
+// Adds options to document element.
+Utils.addOptions = function(elementID, keyValueOptions, disabledOptions) {
+ disabledOptions = disabledOptions || [];
+ var selectElement = document.getElementById(elementID);
+ var keys = Object.keys(keyValueOptions);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var option = new Option(key, keyValueOptions[key]);
+ option.title = keyValueOptions[key];
+ if (disabledOptions.indexOf(key) >= 0)
+ option.disabled = true;
+ selectElement.options.add(option);
+ }
+};
+
+Utils.convertToArray = function(input) {
+ if (Array.isArray(input))
+ return input;
+ return [input];
+};
+
+Utils.convertToUint8Array = function(msg) {
+ var ans = new Uint8Array(msg.length);
+ for (var i = 0; i < msg.length; i++) {
+ ans[i] = msg.charCodeAt(i);
+ }
+ return ans;
+};
+
+Utils.createJWKData = function(keyId, key) {
+ // JWK routines copied from third_party/WebKit/LayoutTests/media/
+ // encrypted-media/encrypted-media-utils.js
+ //
+ // Encodes data (Uint8Array) into base64 string without trailing '='.
+ // TODO(jrummell): Update once the EME spec is updated to say base64url
+ // encoding.
+ function base64Encode(data) {
+ var result = btoa(String.fromCharCode.apply(null, data));
+ return result.replace(/=+$/g, '');
+ }
+
+ // Creates a JWK from raw key ID and key.
+ function createJWK(keyId, key) {
+ var jwk = '{"kty":"oct","kid":"';
+ jwk += base64Encode(keyId);
+ jwk += '","k":"';
+ jwk += base64Encode(key);
+ jwk += '"}';
+ return jwk;
+ }
+
+ // Creates a JWK Set from an array of JWK(s).
+ function createJWKSet() {
+ var jwkSet = '{"keys":[';
+ for (var i = 0; i < arguments.length; i++) {
+ if (i != 0)
+ jwkSet += ',';
+ jwkSet += arguments[i];
+ }
+ jwkSet += ']}';
+ return jwkSet;
+ }
+
+ return Utils.convertToUint8Array(createJWKSet(createJWK(keyId, key)));
+};
+
+Utils.documentLog = function(log, success, time) {
+ if (!docLogs)
+ return;
+ time = time || Utils.getCurrentTimeString();
+ var timeLog = '<span style="color: green">' + time + '</span>';
+ var logColor = !success ? 'red' : 'black'; // default is true.
+ log = '<span style="color: "' + logColor + '>' + log + '</span>';
+ docLogs.innerHTML = timeLog + ' - ' + log + '<br>' + docLogs.innerHTML;
+};
+
+Utils.ensureOptionInList = function(listID, option) {
+ var selectElement = document.getElementById(listID);
+ for (var i = 0; i < selectElement.length; i++) {
+ if (selectElement.options[i].value == option) {
+ selectElement.value = option;
+ return;
+ }
+ }
+ // The list does not have the option, let's add it and select it.
+ var optionElement = new Option(option, option);
+ optionElement.title = option;
+ selectElement.options.add(optionElement);
+ selectElement.value = option;
+};
+
+Utils.failTest = function(msg, newTitle) {
+ var failMessage = 'FAIL: ';
+ var title = 'FAILED';
+ // Handle exception messages;
+ if (msg.message) {
+ title = msg.name || 'Error';
+ failMessage += title + ' ' + msg.message;
+ } else if (msg instanceof Event) {
+ // Handle failing events.
+ failMessage = msg.target + '.' + msg.type;
+ title = msg.type;
+ } else {
+ failMessage += msg;
+ }
+ // Force newTitle if passed.
+ title = newTitle || title;
+ // Log failure.
+ Utils.documentLog(failMessage, false);
+ console.log(failMessage, msg);
+ Utils.setResultInTitle(title);
+};
+
+Utils.getCurrentTimeString = function() {
+ var date = new Date();
+ var hours = ('0' + date.getHours()).slice(-2);
+ var minutes = ('0' + date.getMinutes()).slice(-2);
+ var secs = ('0' + date.getSeconds()).slice(-2);
+ var milliSecs = ('00' + date.getMilliseconds()).slice(-3);
+ return hours + ':' + minutes + ':' + secs + '.' + milliSecs;
+};
+
+Utils.getDefaultKey = function(forceInvalidResponse) {
+ if (forceInvalidResponse) {
+ Utils.timeLog('Forcing invalid key data.');
+ return new Uint8Array([0xAA]);
+ }
+ return KEY;
+};
+
+Utils.getHexString = function(uintArray) {
+ var hex_str = '';
+ for (var i = 0; i < uintArray.length; i++) {
+ var hex = uintArray[i].toString(16);
+ if (hex.length == 1)
+ hex = '0' + hex;
+ hex_str += hex;
+ }
+ return hex_str;
+};
+
+Utils.getInitDataFromMessage = function(message, mediaType) {
+ var initData = message.message;
+ if (mediaType.indexOf('mp4') != -1) {
+ // Temporary hack for Clear Key in v0.1.
+ // If content uses mp4, then message.message is PSSH data. Instead of
+ // parsing that data we hard code the initData.
+ initData = Utils.convertToUint8Array(KEY_ID);
+ }
+ return initData;
+};
+
+Utils.hasPrefix = function(msg, prefix) {
+ var message = String.fromCharCode.apply(null, msg);
+ return message.substring(0, prefix.length) == prefix;
+};
+
+Utils.installTitleEventHandler = function(element, event) {
+ element.addEventListener(event, function(e) {
+ Utils.setResultInTitle(e.type);
+ }, false);
+};
+
+Utils.isHeartBeatMessage = function(msg) {
+ return Utils.hasPrefix(msg, HEART_BEAT_HEADER);
+};
+
+Utils.resetTitleChange = function() {
+ this.titleChanged = false;
+ document.title = '';
+};
+
+Utils.sendRequest = function(requestType, responseType, message, serverURL,
+ onSuccessCallbackFn, forceInvalidResponse) {
+ var requestAttemptCount = 0;
+ var MAXIMUM_REQUEST_ATTEMPTS = 3;
+ var REQUEST_RETRY_DELAY_MS = 3000;
+
+ function sendRequestAttempt() {
+ requestAttemptCount++;
+ if (requestAttemptCount == MAXIMUM_REQUEST_ATTEMPTS) {
+ Utils.failTest('FAILED: Exceeded maximum license request attempts.');
+ return;
+ }
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.responseType = responseType;
+ xmlhttp.open(requestType, serverURL, true);
+
+ xmlhttp.onload = function(e) {
+ if (this.status == 200) {
+ if (onSuccessCallbackFn)
+ onSuccessCallbackFn(this.response);
+ } else {
+ Utils.timeLog('Bad response status: ' + this.status);
+ Utils.timeLog('Bad response: ' + this.response);
+ Utils.timeLog('Retrying request if possible in ' +
+ REQUEST_RETRY_DELAY_MS + 'ms');
+ setTimeout(sendRequestAttempt, REQUEST_RETRY_DELAY_MS);
+ }
+ };
+ Utils.timeLog('Attempt (' + requestAttemptCount +
+ '): sending request to server: ' + serverURL);
+ xmlhttp.send(message);
+ }
+
+ if (forceInvalidResponse) {
+ Utils.timeLog('Not sending request - forcing an invalid response.');
+ return onSuccessCallbackFn([0xAA]);
+ }
+ sendRequestAttempt();
+};
+
+Utils.setResultInTitle = function(title) {
+ // If document title is 'ENDED', then update it with new title to possibly
+ // mark a test as failure. Otherwise, keep the first title change in place.
+ if (!this.titleChanged || document.title.toUpperCase() == 'ENDED')
+ document.title = title.toUpperCase();
+ Utils.timeLog('Set document title to: ' + title + ', updated title: ' +
+ document.title);
+ this.titleChanged = true;
+};
+
+Utils.timeLog = function(/**/) {
+ if (arguments.length == 0)
+ return;
+ var time = Utils.getCurrentTimeString();
+ // Log to document.
+ Utils.documentLog(arguments[0], time);
+ // Log to JS console.
+ var logString = time + ' - ';
+ for (var i = 0; i < arguments.length; i++) {
+ logString += ' ' + arguments[i];
+ }
+ console.log(logString);
+};
diff --git a/media/test/data/eme_player_js/widevine_player.js b/media/test/data/eme_player_js/widevine_player.js
new file mode 100644
index 0000000..11e8ec86
--- /dev/null
+++ b/media/test/data/eme_player_js/widevine_player.js
@@ -0,0 +1,38 @@
+// Copyright 2014 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.
+
+// Widevine player responsible for playing media using Widevine key system
+// and EME working draft API.
+function WidevinePlayer(video, testConfig) {
+ this.video = video;
+ this.testConfig = testConfig;
+}
+
+WidevinePlayer.prototype.init = function() {
+ PlayerUtils.initEMEPlayer(this);
+};
+
+WidevinePlayer.prototype.registerEventListeners = function() {
+ PlayerUtils.registerEMEEventListeners(this);
+};
+
+WidevinePlayer.prototype.onMessage = function(message) {
+ Utils.timeLog('MediaKeySession onMessage', message);
+ var mediaKeySession = message.target;
+ function onSuccess(response) {
+ var key = new Uint8Array(response);
+ Utils.timeLog('Update media key session with license response.', key);
+ if (PROMISES_SUPPORTED) {
+ mediaKeySession.update(key).catch(function(error) {
+ Utils.failTest(error, KEY_ERROR);
+ });
+ } else {
+ mediaKeySession.update(key);
+ }
+
+ }
+ Utils.sendRequest('POST', 'arraybuffer', message.message,
+ this.testConfig.licenseServerURL, onSuccess,
+ this.testConfig.forceInvalidResponse);
+};
diff --git a/media/test/data/encrypted_frame_size_change.html b/media/test/data/encrypted_frame_size_change.html
new file mode 100644
index 0000000..5a90990
--- /dev/null
+++ b/media/test/data/encrypted_frame_size_change.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+ <body onload="load()">
+ <p>Tests decoding and rendering encrypted video element that has a changing
+ resolution.</p>
+ <video width=320 controls></video>
+ <video controls></video>
+ <script src='eme_player_js/app_loader.js' type='text/javascript'></script>
+ <script>
+ var firstVideoSeek = false;
+ var video_fixed_size = document.getElementsByTagName("video")[0];
+ var video = document.getElementsByTagName("video")[1];
+ var testConfig = new TestConfig();
+ testConfig.loadQueryParams();
+
+ function load() {
+ loadVideo(video_fixed_size);
+ loadVideo(video);
+ }
+
+ function loadVideo(video) {
+ var videoPlayer = PlayerUtils.createPlayer(video, testConfig);
+ videoPlayer.init();
+ video.addEventListener('playing', function() {
+ // Make sure the video plays for a bit.
+ video.addEventListener('timeupdate', function() {
+ if (video.currentTime > 1.0) {
+ video.pause();
+ }
+ });
+ });
+
+ video.addEventListener('pause', function() {
+ video.addEventListener('seeked', function() {
+ if (!firstVideoSeek) {
+ Utils.timeLog('One video seeked.');
+ firstVideoSeek = true;
+ return;
+ }
+ Utils.setResultInTitle('ENDED');
+ });
+ video.currentTime = 0.5;
+ });
+
+ video.addEventListener('canplay', oncanplay);
+ video.play();
+ }
+ </script>
+ </body>
+</html>
diff --git a/media/test/data/media_source_player.html b/media/test/data/media_source_player.html
new file mode 100644
index 0000000..7a2ecbe
--- /dev/null
+++ b/media/test/data/media_source_player.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Media Source Player</title>
+ </head>
+ <body onload="runTest();">
+ <video controls></video>
+ <script src='eme_player_js/app_loader.js' type='text/javascript'></script>
+ <script type="text/javascript">
+ var video = document.querySelector('video');
+
+ function onTimeUpdate() {
+ video.removeEventListener('timeupdate', onTimeUpdate);
+ video.currentTime = 0.9 * video.duration;
+ }
+
+ function onSeeked() {
+ video.removeEventListener('ended', Utils.failTest);
+ Utils.installTitleEventHandler(video, 'ended');
+ }
+
+ // The test completes after media starts playing, seeks to 0.9 of
+ // duration and fires the ended event.
+ // The test stops when an error or ended event fire unexpectedly.
+ function runTest() {
+ var testConfig = new TestConfig();
+ testConfig.loadQueryParams();
+ Utils.installTitleEventHandler(video, 'error');
+ video.addEventListener('ended', Utils.failTest);
+ video.addEventListener('seeked', onSeeked);
+ video.addEventListener('timeupdate', onTimeUpdate);
+ var source = MediaSourceUtils.loadMediaSourceFromTestConfig(testConfig);
+ video.src = window.URL.createObjectURL(source);
+ video.play();
+ }
+ </script>
+ </body>
+</html>
diff --git a/media/test/data/mse_config_change.html b/media/test/data/mse_config_change.html
new file mode 100644
index 0000000..f536ece
--- /dev/null
+++ b/media/test/data/mse_config_change.html
@@ -0,0 +1,134 @@
+<html>
+ <head>
+ <title>Test media source config changes.</title>
+ </head>
+ <body onload="runTest();">
+ <video controls></video>
+ <script src='eme_player_js/app_loader.js' type='text/javascript'></script>
+ <script type="text/javascript">
+ var testConfig = new TestConfig();
+ testConfig.loadQueryParams();
+ var runEncrypted = testConfig.runEncrypted == 1;
+
+ var video = document.querySelector('video');
+ var mediaType = 'video/webm; codecs="vorbis, vp8"';
+
+ var MEDIA_1 = 'bear-320x240.webm';
+ var MEDIA_2 = 'bear-640x360.webm';
+ if (runEncrypted) {
+ MEDIA_1 = 'bear-320x240-av_enc-av.webm';
+ MEDIA_2 = 'bear-640x360-av_enc-av.webm';
+ }
+
+ var MEDIA_1_WIDTH = 320;
+ var MEDIA_1_HEIGHT = 240;
+
+ var MEDIA_2_WIDTH = 640;
+ var MEDIA_2_HEIGHT = 360;
+ var MEDIA_2_LENGTH = 2.75;
+
+ // The time in secs to append the second media source.
+ var APPEND_TIME = 1;
+ // DELTA is the time after APPEND_TIME where the second video dimensions
+ // are guaranteed to take effect.
+ var DELTA = 0.1;
+ // Append MEDIA_2 source at APPEND_TIME, so expected total duration is:
+ var TOTAL_DURATION = APPEND_TIME + MEDIA_2_LENGTH;
+
+ function appendNextSource(mediaSource) {
+ console.log('Appending next media source at ' + APPEND_TIME + 'sec.');
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", MEDIA_2);
+ xhr.responseType = 'arraybuffer';
+ xhr.addEventListener('load', function(e) {
+ var onUpdateEnd = function(e) {
+ console.log('Second buffer append ended.');
+ srcBuffer.removeEventListener('updateend', onUpdateEnd);
+ mediaSource.endOfStream();
+ if (!mediaSource.duration ||
+ Math.abs(mediaSource.duration - TOTAL_DURATION) > DELTA) {
+ Utils.failTest('Unexpected mediaSource.duration = ' +
+ mediaSource.duration + ', expected duration = ' +
+ TOTAL_DURATION);
+ return;
+ }
+ video.play();
+ };
+ console.log('Appending next media source at ' + APPEND_TIME + 'sec.');
+ var srcBuffer = mediaSource.sourceBuffers[0];
+ srcBuffer.addEventListener('updateend', onUpdateEnd);
+ srcBuffer.timestampOffset = APPEND_TIME;
+ srcBuffer.appendBuffer(new Uint8Array(e.target.response));
+ });
+ xhr.send();
+ }
+
+ function onTimeUpdate() {
+ // crbug.com/246308
+ //checkVideoProperties();
+
+ // Seek to APPEND_TIME because after a seek a timeUpdate event is fired
+ // before video width and height properties get updated.
+ if (video.currentTime < APPEND_TIME - DELTA) {
+ // Seek to save test execution time (about 1 secs) and to test seek
+ // on the first buffer.
+ video.currentTime = APPEND_TIME - DELTA;
+ } else if (video.currentTime > APPEND_TIME + DELTA) {
+ // Check video duration here to guarantee that second segment has been
+ // appended and video total duration is updated.
+ // Video duration is a float value so we check it within a range.
+ if (!video.duration ||
+ Math.abs(video.duration - TOTAL_DURATION) > DELTA) {
+ Utils.failTest('Unexpected video.duration = ' + video.duration +
+ ', expected duration = ' + TOTAL_DURATION);
+ return;
+ }
+
+ video.removeEventListener('timeupdate', onTimeUpdate);
+ video.removeEventListener('ended', Utils.failTest);
+ Utils.installTitleEventHandler(video, 'ended');
+ // Seek to save test execution time and to test seek on second buffer.
+ video.currentTime = APPEND_TIME + MEDIA_2_LENGTH * 0.9;
+ }
+ }
+
+ function checkVideoProperties() {
+ if (video.currentTime <= APPEND_TIME) {
+ if (video.videoWidth != MEDIA_1_WIDTH ||
+ video.videoHeight != MEDIA_1_HEIGHT) {
+ logVideoDimensions();
+ Utils.failTest('Unexpected dimensions for first video segment.');
+ return;
+ }
+ } else if (video.currentTime >= APPEND_TIME + DELTA) {
+ if (video.videoWidth != MEDIA_2_WIDTH ||
+ video.videoHeight != MEDIA_2_HEIGHT) {
+ logVideoDimensions();
+ Utils.failTest('Unexpected dimensions for second video segment.');
+ return;
+ }
+ }
+ }
+
+ function logVideoDimensions() {
+ console.log('video.currentTime = ' + video.currentTime +
+ ', video dimensions = ' + video.videoWidth + 'x' +
+ video.videoHeight + '.');
+ }
+
+ function runTest() {
+ testConfig.mediaFile = MEDIA_1;
+ testConfig.mediaType = mediaType;
+ video.addEventListener('timeupdate', onTimeUpdate);
+ video.addEventListener('ended', Utils.failTest);
+ if (runEncrypted) {
+ var emePlayer = PlayerUtils.createPlayer(video, testConfig);
+ emePlayer.registerEventListeners();
+ }
+ var mediaSource = MediaSourceUtils.loadMediaSource(
+ MEDIA_1, mediaType, appendNextSource);
+ video.src = window.URL.createObjectURL(mediaSource);
+ }
+ </script>
+ </body>
+</html>
diff --git a/media/test/data/player.html b/media/test/data/player.html
new file mode 100644
index 0000000..e954cf8
--- /dev/null
+++ b/media/test/data/player.html
@@ -0,0 +1,77 @@
+<html>
+<body onload="RunTest();">
+<div id="player_container"></div>
+</body>
+
+<script type="text/javascript">
+// <audio> or <video> player element.
+var player;
+
+// Listen for |event| from |element|, set document.title = |event| upon event.
+function InstallTitleEventHandler(element, event) {
+ element.addEventListener(event, function(e) {
+ document.title = event.toUpperCase();
+ }, false);
+}
+
+function Failed() {
+ document.title = 'FAILED';
+ return false;
+}
+
+function SeekTestStep(e) {
+ player.removeEventListener('ended', SeekTestStep, false);
+
+ // Test completes on the next ended event.
+ InstallTitleEventHandler(player, 'ended');
+
+ player.currentTime = 0.9 * player.duration;
+ player.play();
+}
+
+function SeekTestTimeoutSetup() {
+ if (player.currentTime < 2)
+ return;
+
+ player.removeEventListener('timeupdate', SeekTestTimeoutSetup, false);
+ SeekTestStep();
+}
+
+// Uses URL query parameters to create an audio or video element using a given
+// source. URL must be of the form "player.html?[tag]=[media_url]". Plays the
+// media and waits for X seconds of playback or the ended event, at which point
+// the test seeks near the end of the file and resumes playback. Test completes
+// when the second ended event occurs or an error event occurs at any time.
+function RunTest() {
+ var url_parts = window.location.href.split('?');
+ if (url_parts.length != 2)
+ return Failed();
+
+ var query_parts = url_parts[1].split('=');
+ if (query_parts.length != 2)
+ return Failed();
+
+ var tag = query_parts[0];
+ var media_url = query_parts[1];
+ if (tag != 'audio' && tag != 'video')
+ return Failed();
+
+ // Create player and insert into DOM.
+ player = document.createElement(tag);
+ player.controls = true;
+ document.getElementById('player_container').appendChild(player);
+
+ // Transition to the seek test after X seconds of playback or when the ended
+ // event occurs, whichever happens first.
+ player.addEventListener('ended', SeekTestStep, false);
+ player.addEventListener('timeupdate', SeekTestTimeoutSetup, false);
+
+ // Ensure we percolate up any error events.
+ InstallTitleEventHandler(player, 'error');
+
+ // Starts the player.
+ player.src = media_url;
+ player.play();
+}
+</script>
+</html>
diff --git a/media/test/data/test_key_system_instantiation.html b/media/test/data/test_key_system_instantiation.html
new file mode 100644
index 0000000..0199920
--- /dev/null
+++ b/media/test/data/test_key_system_instantiation.html
@@ -0,0 +1,21 @@
+<html>
+ <body>
+ <video controls="" name="video">
+ <!-- This test doesn't play the video, so any file will do
+ as long as it can be loaded. -->
+ <source src="bear-320x240-av_enc-a.webm" type="video/webm">
+ </video>
+ <script type="text/javascript">
+ function testKeySystemInstantiation(keySystem) {
+ var video = document.getElementsByTagName('video')[0];
+ var initData = new Uint8Array([0x41, 0x42, 0x43]);
+ try {
+ video.webkitGenerateKeyRequest(keySystem, initData);
+ return 'success';
+ } catch (err) {
+ return err.name;
+ }
+ }
+ </script>
+ </body>
+</html>