summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/DEPS1
-rw-r--r--media/base/test_data_util.cc32
-rw-r--r--media/base/test_data_util.h15
-rw-r--r--media/media.gyp1
-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
24 files changed, 1686 insertions, 4 deletions
diff --git a/media/DEPS b/media/DEPS
index 8f61ef2..1275117 100644
--- a/media/DEPS
+++ b/media/DEPS
@@ -1,6 +1,7 @@
include_rules = [
"+gpu",
"+jni",
+ "+net/test",
"+third_party/ffmpeg",
"+third_party/libvpx",
"+third_party/libyuv",
diff --git a/media/base/test_data_util.cc b/media/base/test_data_util.cc
index a83fa84..9cd886f 100644
--- a/media/base/test_data_util.cc
+++ b/media/base/test_data_util.cc
@@ -12,14 +12,38 @@
namespace media {
+const base::FilePath::CharType kTestDataPath[] =
+ FILE_PATH_LITERAL("media/test/data");
+
base::FilePath GetTestDataFilePath(const std::string& name) {
base::FilePath file_path;
CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
+ return file_path.Append(GetTestDataPath()).AppendASCII(name);
+}
+
+base::FilePath GetTestDataPath() {
+ return base::FilePath(kTestDataPath);
+}
+
+std::string GetURLQueryString(const QueryParams& query_params) {
+ std::string query = "";
+ QueryParams::const_iterator itr = query_params.begin();
+ for (; itr != query_params.end(); ++itr) {
+ if (itr != query_params.begin())
+ query.append("&");
+ query.append(itr->first + "=" + itr->second);
+ }
+ return query;
+}
- return file_path.AppendASCII("media")
- .AppendASCII("test")
- .AppendASCII("data")
- .AppendASCII(name);
+scoped_ptr<net::SpawnedTestServer> StartMediaHttpTestServer() {
+ scoped_ptr<net::SpawnedTestServer> http_test_server;
+ http_test_server.reset(new net::SpawnedTestServer(
+ net::SpawnedTestServer::TYPE_HTTP,
+ net::SpawnedTestServer::kLocalhost,
+ GetTestDataPath()));
+ CHECK(http_test_server->Start());
+ return http_test_server.Pass();
}
scoped_refptr<DecoderBuffer> ReadTestDataFile(const std::string& name) {
diff --git a/media/base/test_data_util.h b/media/base/test_data_util.h
index 8d51e96..955d615 100644
--- a/media/base/test_data_util.h
+++ b/media/base/test_data_util.h
@@ -6,19 +6,34 @@
#define MEDIA_BASE_TEST_DATA_UTIL_H_
#include <string>
+#include <utility>
+#include <vector>
#include "base/basictypes.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
namespace media {
class DecoderBuffer;
+typedef std::vector<std::pair<std::string, std::string> > QueryParams;
+
// Returns a file path for a file in the media/test/data directory.
base::FilePath GetTestDataFilePath(const std::string& name);
+// Returns relative path for test data folder: media/test/data.
+base::FilePath GetTestDataPath();
+
+// Starts an HTTP server serving files from media data path.
+scoped_ptr<net::SpawnedTestServer> StartMediaHttpTestServer();
+
+// Returns a string containing key value query params in the form of:
+// "key_1=value_1&key_2=value2"
+std::string GetURLQueryString(const QueryParams& query_params);
+
// Reads a test file from media/test/data directory and stores it in
// a DecoderBuffer. Use DecoderBuffer vs DataBuffer to ensure no matter
// what a test does, it's safe to use FFmpeg methods.
diff --git a/media/media.gyp b/media/media.gyp
index fdccc42..00ad811 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -1349,6 +1349,7 @@
'media',
'shared_memory_support',
'../base/base.gyp:base',
+ '../net/net.gyp:net_test_support',
'../skia/skia.gyp:skia',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
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>