mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
4cc4e96dbd
* Updates all Copyright years to 2015. * Adds licenses annotations to all JS. * Makes all licenses identical to avoid repeated appearance in the compiled output. * Drops fileoverview annotations, which do not affect docs output. * The linter still requires fileoverview on externs. This patch required a newer closure compiler, since the previous version we used had a bug regarding license annotations that caused the license comment block to appear in the output once per file regardless of uniqueness. Change-Id: I2e9272db680cba7ecc4613d97f1d3a94ac2244cc
400 lines
12 KiB
JavaScript
400 lines
12 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2015 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// Status values for the report entries.
|
|
var kGood = 0;
|
|
var kInfo = 1;
|
|
var kBad = 2;
|
|
|
|
var vp8Type = 'video/webm; codecs="vp8"';
|
|
var vp9Type = 'video/webm; codecs="vp9"';
|
|
var mp4Type = 'video/mp4; codecs="avc1.42E01E"';
|
|
var tsType = 'video/mp2t; codecs="avc1.42E01E"';
|
|
|
|
var clearKeyId = 'org.w3.clearkey';
|
|
var widevineId = 'com.widevine.alpha';
|
|
var playReadyId = 'com.microsoft.playready';
|
|
var adobeAccessId = 'com.adobe.access';
|
|
var fairPlayId = 'com.apple.fairplay';
|
|
|
|
var classPrefixes = [
|
|
'WebKit',
|
|
'MS',
|
|
'Moz'
|
|
];
|
|
|
|
var propertyPrefixes = [
|
|
'webkit',
|
|
'ms',
|
|
'moz'
|
|
];
|
|
|
|
var keySystemPrefixes = [
|
|
'webkit-'
|
|
];
|
|
|
|
var video = document.createElement('video');
|
|
|
|
// The entries in the report. Each item is an array of name, status, and value.
|
|
var report = [];
|
|
|
|
// A map of unprefixed names to the found things themselves. Properties are
|
|
// represented as true if found.
|
|
var found = {};
|
|
|
|
// An array of Promises representing asynchronous operations.
|
|
var async = [];
|
|
|
|
// Find an entry in the report by name, then change its status to "bad".
|
|
function markAsBad(name, opt_newValue) {
|
|
for (var i = 0; i < report.length; ++i) {
|
|
if (report[i][0] == name) {
|
|
report[i][1] = kBad;
|
|
if (opt_newValue !== undefined) {
|
|
report[i][2] = opt_newValue;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// A helper used by the other testFor* methods. Look for a property named
|
|
// |name| in |parent|. |required| is a true if it is a required property.
|
|
// |boolValue| is true if the actual property value should be replaced with
|
|
// true when found. |prefixes| is the list of prefixes to test if an unprefixed
|
|
// version is not found. |prefixFn| is a function that combines a prefix and
|
|
// name in the correct way for whatever is being tested.
|
|
function testFor(parent, name, required, boolValue, prefixes, prefixFn) {
|
|
if (parent && (name in parent)) {
|
|
report.push([name, kGood, '(unprefixed)']);
|
|
found[name] = boolValue ? true : parent[name];
|
|
} else {
|
|
for (var i = 0; i < prefixes.length; ++i) {
|
|
var name2 = prefixFn(prefixes[i], name);
|
|
if (parent && (name2 in parent)) {
|
|
report.push([name, kInfo, prefixes[i]]);
|
|
found[name] = boolValue ? true : parent[name2];
|
|
break;
|
|
}
|
|
}
|
|
if (i == prefixes.length) {
|
|
report.push([name, required ? kBad : kInfo, '(not found)']);
|
|
}
|
|
}
|
|
}
|
|
|
|
function testForClass(parent, name, required) {
|
|
testFor(parent, name, required, false, classPrefixes,
|
|
function(prefix, name) {
|
|
return prefix + name;
|
|
});
|
|
}
|
|
|
|
function testForClassUsable(parent, name, required, args) {
|
|
testForClass(parent, name, required);
|
|
var ctor = found[name];
|
|
if (ctor) {
|
|
try {
|
|
// Some native DOM object constructors cannot be called without new.
|
|
// So in order to apply args, they must be bound.
|
|
var bindArgs = [ null ].concat(args);
|
|
var boundCtor = ctor.bind.apply(ctor, bindArgs);
|
|
new boundCtor();
|
|
} catch (exception) {
|
|
found[name] = false;
|
|
markAsBad(name, '(not usable)');
|
|
}
|
|
}
|
|
}
|
|
|
|
function testForMethod(parent, name, required) {
|
|
testFor(parent, name, required, false, propertyPrefixes,
|
|
function(prefix, name) {
|
|
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
|
|
});
|
|
}
|
|
|
|
function testForProperty(parent, name, required) {
|
|
testFor(parent, name, required, true, propertyPrefixes,
|
|
function(prefix, name) {
|
|
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
|
|
});
|
|
}
|
|
|
|
function testForMimeType(type) {
|
|
var mse = found['MediaSource'];
|
|
if (mse && mse.isTypeSupported && mse.isTypeSupported(type)) {
|
|
report.push([type, kGood, '(supported)']);
|
|
found[type] = true;
|
|
} else {
|
|
report.push([type, kInfo, '(not found)']);
|
|
}
|
|
}
|
|
|
|
function canPlayType2(ks) {
|
|
// Check if the video can play any of the well-known types with this key
|
|
// system, using the 2-argument version of canPlayType.
|
|
return video.canPlayType(vp8Type, ks) ||
|
|
video.canPlayType(vp9Type, ks) ||
|
|
video.canPlayType(mp4Type, ks) ||
|
|
video.canPlayType(tsType, ks);
|
|
}
|
|
|
|
function isKeySystemSupported(ks) {
|
|
console.assert(!navigator.requestMediaKeySystemAccess,
|
|
'isKeySystemSupported() should only be used when async testing is not ' +
|
|
'available!');
|
|
|
|
var mk = found['MediaKeys'];
|
|
if (mk && mk.isTypeSupported) {
|
|
return mk.isTypeSupported(ks);
|
|
} else {
|
|
if (canPlayType2('com.bogus.keysystem')) {
|
|
// The browser doesn't understand the 2-argument canPlayType.
|
|
// Don't give it any legitimate queries using this method.
|
|
return false;
|
|
}
|
|
return canPlayType2(ks);
|
|
}
|
|
}
|
|
|
|
function testForKeySystemAsync(ks) {
|
|
// Check unprefixed first.
|
|
var p = navigator.requestMediaKeySystemAccess(ks, [{}]).then(function() {
|
|
return '(unprefixed)';
|
|
});
|
|
for (var i = 0; i < keySystemPrefixes.length; ++i) {
|
|
// Chain a check for a prefixed version if the previous one failed.
|
|
var prefix = keySystemPrefixes[i];
|
|
p = p.catch(function(prefix) {
|
|
var ks2 = prefix + ks;
|
|
return navigator.requestMediaKeySystemAccess(ks2, [{}]).then(function() {
|
|
return prefix;
|
|
});
|
|
}.bind(prefix));
|
|
}
|
|
return p;
|
|
}
|
|
|
|
function testForKeySystemSync(ks, required) {
|
|
if (isKeySystemSupported(ks)) {
|
|
report.push([ks, kGood, '(supported)']);
|
|
found[ks] = true;
|
|
} else {
|
|
for (var i = 0; i < keySystemPrefixes.length; ++i) {
|
|
var prefix = keySystemPrefixes[i];
|
|
var ks2 = prefix + ks;
|
|
if (isKeySystemSupported(ks2)) {
|
|
report.push([ks, kGood, prefix]);
|
|
found[ks] = true;
|
|
break;
|
|
}
|
|
}
|
|
if (i == keySystemPrefixes.length) {
|
|
report.push([ks, required ? kBad : kInfo, '(not found)']);
|
|
}
|
|
}
|
|
}
|
|
|
|
function testForKeySystem(ks, required) {
|
|
var Promise = found['Promise'];
|
|
var mk = found['MediaKeys'];
|
|
|
|
if (Promise && mk && navigator.requestMediaKeySystemAccess) {
|
|
var p = testForKeySystemAsync(ks).then(function(prefix) {
|
|
report.push([ks, kGood, prefix]);
|
|
found[ks] = true;
|
|
}).catch(function() {
|
|
report.push([ks, required ? kBad : kInfo, '(not found)']);
|
|
});
|
|
async.push(p);
|
|
} else {
|
|
testForKeySystemSync(ks, required);
|
|
}
|
|
}
|
|
|
|
// Required, no polyfill provided:
|
|
testForClass(window, 'HTMLMediaElement', true);
|
|
testForClass(window, 'MediaSource', true);
|
|
testForClass(window, 'Promise', true);
|
|
testForClass(window, 'Uint8Array', true);
|
|
|
|
// Optional:
|
|
testForClass(window, 'VTTCue', false);
|
|
testForProperty(document, 'fullscreenElement', false);
|
|
testForProperty(document, 'fullScreenElement', false);
|
|
testForClassUsable(window, 'CustomEvent', true, ['']);
|
|
testForProperty(window, 'indexedDB', false);
|
|
|
|
// Codecs:
|
|
testForMimeType(vp8Type);
|
|
testForMimeType(vp9Type);
|
|
testForMimeType(mp4Type);
|
|
testForMimeType(tsType);
|
|
|
|
// At least one of these should be supported:
|
|
if (!found[vp8Type] && !found[vp9Type] && !found[mp4Type] && !found[tsType]) {
|
|
markAsBad(vp8Type);
|
|
markAsBad(vp9Type);
|
|
markAsBad(mp4Type);
|
|
markAsBad(tsType);
|
|
}
|
|
|
|
// QoE stats:
|
|
testForMethod(video, 'getVideoPlaybackQuality', false);
|
|
testForProperty(video, 'droppedFrameCount', false);
|
|
testForProperty(video, 'decodedFrameCount', false);
|
|
|
|
// MediaKeys:
|
|
testForMethod(video, 'generateKeyRequest', false);
|
|
testForClass(window, 'MediaKeys', false);
|
|
testForMethod(found['MediaKeys'], 'create', false);
|
|
testForMethod(found['MediaKeys'], 'isTypeSupported', false);
|
|
testForMethod(navigator, 'requestMediaKeySystemAccess', false);
|
|
testForMethod(window, 'MediaKeySystemAccess', false);
|
|
testForMethod(found['MediaKeySystemAccess'] ?
|
|
found['MediaKeySystemAccess'].prototype : null,
|
|
'getConfiguration', false);
|
|
testForClass(window, 'MediaKeySession');
|
|
|
|
// Specific CDMs:
|
|
testForKeySystem(clearKeyId, found['MediaKeys'] || found['generateKeyRequest']);
|
|
testForKeySystem(widevineId, false);
|
|
testForKeySystem(playReadyId, false);
|
|
testForKeySystem(adobeAccessId, false);
|
|
testForKeySystem(fairPlayId, false);
|
|
|
|
// If EME is available, at least one key system other than ClearKey should be
|
|
// available.
|
|
if ((found['MediaKeys'] || found['generateKeyRequest']) &&
|
|
(!found[clearKeyId] && !found[widevineId] && !found[playReadyId] &&
|
|
!found[adobeAccessId] && !found[fairPlayId])) {
|
|
markAsBad(clearKeyId);
|
|
markAsBad(widevineId);
|
|
markAsBad(playReadyId);
|
|
markAsBad(adobeAccessId);
|
|
markAsBad(fairPlayId);
|
|
}
|
|
|
|
if (async.length) {
|
|
// The browser supports Promises and we have async tests going.
|
|
// Create a Promise for DOMContentLoaded and add it to the list.
|
|
var loaded = new Promise(function(resolve, reject) {
|
|
onLoaded(resolve);
|
|
});
|
|
async.push(loaded);
|
|
Promise.all(async).then(onAsyncComplete);
|
|
} else {
|
|
// The browser does not support Promises or there are no async tests.
|
|
// Listen for DOMContentLoaded.
|
|
onLoaded(onAsyncComplete);
|
|
}
|
|
|
|
function onLoaded(fn) {
|
|
// IE 9 fires DOMContentLoaded, and enters the "interactive"
|
|
// readyState, before document.body has been initialized, so wait
|
|
// for window.load
|
|
if (document.readyState == 'loading' ||
|
|
document.readyState == 'interactive') {
|
|
window.addEventListener('load', fn);
|
|
} else {
|
|
fn();
|
|
}
|
|
}
|
|
|
|
function onAsyncComplete() {
|
|
// Synthesize a summary at the top from other properties.
|
|
// Must be done after all async tasks are complete.
|
|
var requiredFeatures = found['HTMLMediaElement'] && found['MediaSource'] &&
|
|
found['Promise'];
|
|
var qoe = found['getVideoPlaybackQuality'] || found['droppedFrameCount'];
|
|
var subtitles = found['VTTCue'];
|
|
var emeApi = found['MediaKeys'] || found['generateKeyRequest'];
|
|
var emeV01b = found['generateKeyRequest'];
|
|
var latestEme = found['requestMediaKeySystemAccess'] &&
|
|
found['getConfiguration'];
|
|
var anyKeySystems = found[clearKeyId] || found[widevineId] ||
|
|
found[playReadyId] || found[adobeAccessId] ||
|
|
found[fairPlayId];
|
|
var offline = found['indexedDB'];
|
|
var fullscreenApi = found['fullscreenElement'] || found['fullScreenElement'];
|
|
var requiresPolyfills = !latestEme || !found['getVideoPlaybackQuality'] ||
|
|
!document.fullscreenElement || !found['CustomEvent'];
|
|
|
|
var emeStatus, emeValue;
|
|
if (emeApi && anyKeySystems) {
|
|
emeStatus = kGood;
|
|
if (latestEme) {
|
|
emeValue = 'latest EME';
|
|
} else if (emeV01b) {
|
|
emeValue = 'EME v0.1b';
|
|
} else {
|
|
emeValue = 'unknown';
|
|
}
|
|
} else if (emeApi) {
|
|
emeStatus = kBad;
|
|
emeValue = 'no known key systems!';
|
|
} else {
|
|
emeStatus = kInfo;
|
|
emeValue = 'not supported';
|
|
}
|
|
|
|
var summary = [];
|
|
summary.push(["userAgent", kInfo, navigator.userAgent]);
|
|
summary.push(reportEntry('Required Features', requiredFeatures, true));
|
|
summary.push(reportEntry('QoE Stats', qoe, false));
|
|
summary.push(reportEntry('Subtitles', subtitles, false));
|
|
summary.push(['Encrypted Content', emeStatus, emeValue]);
|
|
summary.push(reportEntry('Offline Content', offline, false));
|
|
summary.push(['Requires Polyfills', requiresPolyfills ? kInfo : kGood,
|
|
'yes', 'natively supported!']);
|
|
summary.push(reportDivider());
|
|
|
|
// Prepend the summary.
|
|
report.unshift.apply(report, summary);
|
|
|
|
// Render the final report.
|
|
renderReport();
|
|
}
|
|
|
|
function reportDivider() {
|
|
return ['=====', kInfo, '====='];
|
|
}
|
|
|
|
function reportEntry(name, ok, important) {
|
|
var status = ok ? kGood : (important ? kBad : kInfo);
|
|
var text = ok ? 'OK' : (important ? 'FAIL' : 'missing');
|
|
return [name, status, text];
|
|
}
|
|
|
|
function renderReport() {
|
|
var table = document.createElement('table');
|
|
for (var i = 0; i < report.length; ++i) {
|
|
var tr = document.createElement('tr');
|
|
var td0 = document.createElement('td');
|
|
var td1 = document.createElement('td');
|
|
td0.textContent = report[i][0];
|
|
if (report[i][1] == kGood) td1.style.color = '#070';
|
|
else if (report[i][1] == kBad) td1.style.color = '#700';
|
|
td1.textContent = report[i][2];
|
|
tr.appendChild(td0);
|
|
tr.appendChild(td1);
|
|
table.appendChild(tr);
|
|
}
|
|
document.body.appendChild(table);
|
|
}
|