blob: bec9b91048b565455c94cd39b78c9aaf47e763f7 [file] [log] [blame]
// 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 = {
'encrypted': 'onEncrypted',
};
for (eventName in eventListenerMap) {
var eventListenerFunction = player[eventListenerMap[eventName]];
if (eventListenerFunction) {
player.video.addEventListener(eventName, function(e) {
player[eventListenerMap[e.type]](e);
});
}
}
player.video.addEventListener('error', function(error) {
// This most likely happens on pipeline failures (e.g. when the CDM
// crashes). Don't report a failure if the test is checking that sessions
// are closed on a crash.
Utils.timeLog('onHTMLElementError', error.target.error.message);
if (player.testConfig.keySystem == CRASH_TEST_KEYSYSTEM) {
// On failure the session should have been closed, so verify.
player.session.closed.then(
function(result) {
Utils.setResultInTitle(EME_SESSION_CLOSED_AND_ERROR);
},
function(error) { Utils.failTest(error); });
} else {
Utils.failTest(error);
}
});
};
// Register the necessary event handlers needed when playing encrypted content.
// Returns a promise that resolves to the player.
PlayerUtils.registerEMEEventListeners = function(player) {
player.video.addEventListener('encrypted', function(message) {
function addMediaKeySessionListeners(mediaKeySession) {
mediaKeySession.addEventListener('message', function(message) {
Utils.timeLog('MediaKeyMessageEvent: ' + message.messageType);
player.video.receivedKeyMessage = true;
if (message.messageType == 'license-request' ||
message.messageType == 'license-renewal' ||
message.messageType == 'individualization-request') {
player.video.receivedMessageTypes.add(message.messageType);
} else {
Utils.failTest('Unexpected message type:' + message.messageType,
EME_MESSAGE_UNEXPECTED_TYPE);
}
player.onMessage(message);
});
}
// Calls getStatusForPolicy() and returns a resolved promise if the result
// matches the |expectedResult|, whose value can be:
// - a valid key status, e.g. "usable", in which case getStatusForPolicy()
// must be resolved with |expectedResult|.
// - "rejected", in which case getStatusForPolicy() must be rejected.
// - "resolved", in which case getStatusForPolicy() can be resolved by any
// value.
async function getStatusForHdcpPolicy(
mediaKeys, hdcpVersion, expectedResult) {
try {
var keyStatus =
await mediaKeys.getStatusForPolicy({minHdcpVersion: hdcpVersion});
if (expectedResult == 'resolved' ||
(expectedResult != 'rejected' && keyStatus == expectedResult)) {
return true;
}
return Promise.reject(
'For HDCP version "' + hdcpVersion + '", keyStatus "' + keyStatus +
'" does not match "' + expectedResult + '"');
} catch (e) {
if (expectedResult == 'rejected') {
return true;
}
return Promise.reject('Promise rejected unexpectedly: ' + e);
}
}
// Tests HDCP policy check. Returns a resolved promise if all tests pass.
function testGetStatusForHdcpPolicy(mediaKeys) {
const keySystem = this.testConfig.keySystem;
Utils.timeLog('Key system: ' + keySystem);
if (keySystem == EXTERNAL_CLEARKEY) {
// ClearKeyCdm pretends the device is HDCP 2.0 compliant. See
// ClearKeyCdm::GetStatusForPolicy() for details.
return Promise.all([
getStatusForHdcpPolicy(mediaKeys, '', 'usable'),
getStatusForHdcpPolicy(mediaKeys, '1.0', 'usable'),
getStatusForHdcpPolicy(mediaKeys, '2.3', 'output-restricted'),
]);
}
if (keySystem == CLEARKEY) {
// AesDecryptor does not support getStatusForPolicy() so the promise
// is always rejected.
return Promise.all([
getStatusForHdcpPolicy(mediaKeys, '', 'rejected'),
getStatusForHdcpPolicy(mediaKeys, '1.0', 'rejected'),
]);
}
if (keySystem == WIDEVINE_KEYSYSTEM) {
// Widevine CDM supports getStatusForPolicy() so the promise is always
// resolved. However the key status depends on the device's HDCP level
// so we cannot enforce it.
return Promise.all([
getStatusForHdcpPolicy(mediaKeys, '', 'usable'),
getStatusForHdcpPolicy(mediaKeys, '1.0', 'resolved'),
]);
}
return Promise.reject('Unsupported key system');
}
try {
if (player.testConfig.sessionToLoad) {
// Create a session to load using a new MediaKeys.
// TODO(jrummell): Add a test that covers remove().
player.access.createMediaKeys()
.then(function(mediaKeys) {
// As the tests run with a different origin every time, there is
// no way currently to create a session in one test and then load
// it in a subsequent test (https://crbug.com/715349).
// So if |sessionToLoad| is 'LoadableSession', create a session
// that can be loaded and use that session to load. Otherwise
// use the name provided (which allows for testing load() on a
// session which doesn't exist).
if (player.testConfig.sessionToLoad == 'LoadableSession') {
return Utils.createSessionToLoad(
mediaKeys, player.testConfig.sessionToLoad);
} else {
return player.testConfig.sessionToLoad;
}
})
.then(function(sessionId) {
Utils.timeLog('Loading session: ' + sessionId);
player.session =
message.target.mediaKeys.createSession('persistent-license');
addMediaKeySessionListeners(player.session);
return player.session.load(sessionId);
})
.then(
function(result) {
if (!result)
Utils.failTest('Session not found.', EME_SESSION_NOT_FOUND);
},
function(error) {
Utils.failTest(error, EME_LOAD_FAILED);
});
} else if (player.testConfig.policyCheck) {
// TODO(xhwang): We should be able to move all policy check code to a
// new test js file once we figure out an easy way to separate the
// requrestMediaKeySystemAccess() logic from the rest of this file.
Utils.timeLog('Policy check test.');
player.access.createMediaKeys().then(function (mediaKeys) {
// Call getStatusForPolicy() before creating any MediaKeySessions.
return testGetStatusForHdcpPolicy(mediaKeys);
}).then(function (result) {
Utils.timeLog('Policy check test passed.');
Utils.setResultInTitle(UNIT_TEST_SUCCESS);
}).catch(function (error) {
Utils.timeLog('Policy check test failed.');
Utils.failTest(error, UNIT_TEST_FAILURE);
});
} else {
Utils.timeLog('Creating new media key session for initDataType: ' +
message.initDataType + ', initData: ' +
Utils.getHexString(new Uint8Array(message.initData)));
player.session = message.target.mediaKeys.createSession();
addMediaKeySessionListeners(player.session);
player.session.generateRequest(message.initDataType, message.initData)
.catch(function(error) {
// Ignore the error if a crash is expected. This ensures that
// the decoder actually detects and reports the error.
if (this.testConfig.keySystem != CRASH_TEST_KEYSYSTEM) {
Utils.failTest(error, EME_GENERATEREQUEST_FAILED);
}
});
}
} catch (e) {
Utils.failTest(e);
}
});
this.registerDefaultEventListeners(player);
player.video.receivedKeyMessage = false;
player.video.receivedMessageTypes = new Set();
Utils.timeLog('Setting video media keys: ' + player.testConfig.keySystem);
var config = {
audioCapabilities: [],
videoCapabilities: [],
persistentState: 'optional',
sessionTypes: ['temporary'],
};
// requestMediaKeySystemAccess() requires at least one of 'audioCapabilities'
// or 'videoCapabilities' to be specified. It also requires only codecs
// specific to the capability, so unlike MSE cannot have both audio and
// video codecs in the contentType.
if (player.testConfig.mediaType) {
if (player.testConfig.mediaType.substring(0, 5) == 'video') {
config.videoCapabilities = [{contentType: player.testConfig.mediaType}];
} else if (player.testConfig.mediaType.substring(0, 5) == 'audio') {
config.audioCapabilities = [{contentType: player.testConfig.mediaType}];
}
// Handle special cases where both audio and video are needed.
if (player.testConfig.mediaType == 'video/webm; codecs="vorbis, vp8"') {
config.audioCapabilities = [{contentType: 'audio/webm; codecs="vorbis"'}];
config.videoCapabilities = [{contentType: 'video/webm; codecs="vp8"'}];
} else if (
player.testConfig.mediaType == 'video/webm; codecs="opus, vp9"') {
config.audioCapabilities = [{contentType: 'audio/webm; codecs="opus"'}];
config.videoCapabilities = [{contentType: 'video/webm; codecs="vp9"'}];
}
} else {
// Some tests (e.g. mse_different_containers.html) specify audio and
// video codecs seperately.
if (player.testConfig.videoFormat) {
config.videoCapabilities = [{contentType: player.testConfig.videoFormat}];
}
if (player.testConfig.audioFormat) {
config.audioCapabilities = [{contentType: player.testConfig.audioFormat}];
}
}
// The File IO test requires persistent state support.
if (player.testConfig.keySystem == FILE_IO_TEST_KEYSYSTEM) {
config.persistentState = 'required';
} else if (
player.testConfig.sessionToLoad ||
player.testConfig.keySystem == STORAGE_ID_TEST_KEYSYSTEM) {
config.persistentState = 'required';
config.sessionTypes = ['temporary', 'persistent-license'];
}
return navigator
.requestMediaKeySystemAccess(player.testConfig.keySystem, [config])
.then(function(access) {
player.access = access;
return access.createMediaKeys();
})
.then(function(mediaKeys) {
return player.video.setMediaKeys(mediaKeys);
})
.then(function(result) {
return player;
})
.catch(function(error) {
Utils.failTest(error, NOTSUPPORTEDERROR);
});
};
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;
}
Utils.timeLog('video.src has been set to ' + player.video.src);
};
// Initialize the player to play encrypted content. Returns a promise that
// resolves to the player.
PlayerUtils.initEMEPlayer = function(player) {
return player.registerEventListeners().then(function(result) {
PlayerUtils.setVideoSource(player);
Utils.timeLog('initEMEPlayer() done');
return player;
});
};
// Return the appropriate player based on test configuration.
PlayerUtils.createPlayer = function(video, testConfig) {
function getPlayerType(keySystem) {
switch (keySystem) {
case WIDEVINE_KEYSYSTEM:
return WidevinePlayer;
case CLEARKEY:
case EXTERNAL_CLEARKEY:
case EXTERNAL_CLEARKEY_CDM_PROXY:
case MESSAGE_TYPE_TEST_KEYSYSTEM:
case CRASH_TEST_KEYSYSTEM:
return ClearKeyPlayer;
case FILE_IO_TEST_KEYSYSTEM:
case OUTPUT_PROTECTION_TEST_KEYSYSTEM:
case PLATFORM_VERIFICATION_TEST_KEYSYSTEM:
case VERIFY_HOST_FILES_TEST_KEYSYSTEM:
case STORAGE_ID_TEST_KEYSYSTEM:
return UnitTestPlayer;
default:
Utils.timeLog(keySystem + ' is not a known key system');
return ClearKeyPlayer;
}
}
var Player = getPlayerType(testConfig.keySystem);
return new Player(video, testConfig);
};