Files
shaka-player/support.js
T
Joey Parrish 4cc4e96dbd Overhaul license comments and file annotations
* 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
2015-09-08 12:02:34 -07:00

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);
}