UNPKG

shaka-player

Version:
470 lines (411 loc) 12.2 kB
/** * @license * Copyright 2016 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. */ goog.provide('shaka.cast.CastUtils'); goog.require('shaka.util.FakeEvent'); /** * @namespace shaka.cast.CastUtils * @summary A set of cast utility functions and variables shared between sender * and receiver. */ /** * HTMLMediaElement events that are proxied while casting. * @const {!Array.<string>} */ shaka.cast.CastUtils.VideoEvents = [ 'ended', 'play', 'playing', 'pause', 'pausing', 'ratechange', 'seeked', 'seeking', 'timeupdate', 'volumechange', ]; /** * HTMLMediaElement attributes that are proxied while casting. * @const {!Array.<string>} */ shaka.cast.CastUtils.VideoAttributes = [ 'buffered', 'currentTime', 'duration', 'ended', 'loop', 'muted', 'paused', 'playbackRate', 'seeking', 'videoHeight', 'videoWidth', 'volume', ]; /** * HTMLMediaElement attributes that are transferred when casting begins. * @const {!Array.<string>} */ shaka.cast.CastUtils.VideoInitStateAttributes = [ 'loop', 'playbackRate', ]; /** * HTMLMediaElement methods with no return value that are proxied while casting. * @const {!Array.<string>} */ shaka.cast.CastUtils.VideoVoidMethods = [ 'pause', 'play', ]; /** * Player events that are proxied while casting. * @const {!Array.<string>} */ shaka.cast.CastUtils.PlayerEvents = [ 'abrstatuschanged', 'adaptation', 'buffering', 'drmsessionupdate', 'emsg', 'error', 'expirationupdated', 'largegap', 'loading', 'manifestparsed', 'onstatechange', 'onstateidle', 'streaming', 'textchanged', 'texttrackvisibility', 'timelineregionadded', 'timelineregionenter', 'timelineregionexit', 'trackschanged', 'unloading', 'variantchanged', ]; /** * Player getter methods that are proxied while casting. * The key is the method, the value is the frequency of updates. * Frequency 1 translates to every update; frequency 2 to every 2 updates, etc. * @const {!Object.<string, number>} */ shaka.cast.CastUtils.PlayerGetterMethods = { // NOTE: The 'drmInfo' property is not proxied, as it is very large. 'getAssetUri': 2, 'getAudioLanguages': 2, 'getAudioLanguagesAndRoles': 2, 'getBufferedInfo': 2, // NOTE: The 'getSharedConfiguration' property is not proxied as it would // not be possible to share a reference. 'getConfiguration': 2, 'getExpiration': 2, // NOTE: The 'getManifest' property is not proxied, as it is very large. // TODO(vaage): Remove |getManifestUri| references in v3.0. // NOTE: The 'getManifestUri' property is not proxied, as CastProxy has a // handler for it. // NOTE: The 'getManifestParserFactory' property is not proxied, as it would // not serialize. 'getPlaybackRate': 2, 'getTextLanguages': 2, 'getTextLanguagesAndRoles': 2, 'getTextTracks': 2, 'getStats': 5, 'getVariantTracks': 2, 'isAudioOnly': 10, 'isBuffering': 1, 'isInProgress': 1, 'isLive': 10, 'isTextTrackVisible': 1, 'keySystem': 10, 'seekRange': 1, 'usingEmbeddedTextTrack': 2, 'getLoadMode': 10, }; /** * Player getter methods that are proxied while casting, but only when casting * a livestream. * The key is the method, the value is the frequency of updates. * Frequency 1 translates to every update; frequency 2 to every 2 updates, etc. * @const {!Object.<string, number>} */ shaka.cast.CastUtils.PlayerGetterMethodsThatRequireLive = { 'getPlayheadTimeAsDate': 1, 'getPresentationStartTimeAsDate': 20, }; /** * Player getter and setter methods that are used to transfer state when casting * begins. * @const {!Array.<!Array.<string>>} */ shaka.cast.CastUtils.PlayerInitState = [ ['getConfiguration', 'configure'], ]; /** * Player getter and setter methods that are used to transfer state after * load() is resolved. * @const {!Array.<!Array.<string>>} */ shaka.cast.CastUtils.PlayerInitAfterLoadState = [ ['isTextTrackVisible', 'setTextTrackVisibility'], ]; /** * Player methods with no return value that are proxied while casting. * @const {!Array.<string>} */ shaka.cast.CastUtils.PlayerVoidMethods = [ 'addTextTrack', 'cancelTrickPlay', 'configure', 'resetConfiguration', 'retryStreaming', 'selectAudioLanguage', 'selectEmbeddedTextTrack', 'selectTextLanguage', 'selectTextTrack', 'selectVariantTrack', 'selectVariantsByLabel', 'setTextTrackVisibility', 'trickPlay', ]; /** * Player methods returning a Promise that are proxied while casting. * @const {!Array.<string>} */ shaka.cast.CastUtils.PlayerPromiseMethods = [ 'attach', 'detach', // The manifestFactory parameter of load is not supported. 'load', 'unload', ]; /** * @typedef {{ * video: Object, * player: Object, * manifest: ?string, * startTime: ?number * }} * @property {Object} video * Dictionary of video properties to be set. * @property {Object} player * Dictionary of player setters to be called. * @property {?string} manifest * The currently-selected manifest, if present. * @property {?number} startTime * The playback start time, if currently playing. */ shaka.cast.CastUtils.InitStateType; /** * The namespace for Shaka messages on the cast bus. * @const {string} */ shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE = 'urn:x-cast:com.google.shaka.v2'; /** * The namespace for generic messages on the cast bus. * @const {string} */ shaka.cast.CastUtils.GENERIC_MESSAGE_NAMESPACE = 'urn:x-cast:com.google.cast.media'; /** * Serialize as JSON, but specially encode things JSON will not otherwise * represent. * @param {?} thing * @return {string} */ shaka.cast.CastUtils.serialize = function(thing) { return JSON.stringify(thing, function(key, value) { if (typeof value == 'function') { // Functions can't be (safely) serialized. return undefined; } if (value instanceof Event || value instanceof shaka.util.FakeEvent) { // Events don't serialize to JSON well because of the DOM objects // and other complex objects they contain, so we strip these out. // Note that using Object.keys or JSON.stringify directly on the event // will not capture its properties. We must use a for loop. let simpleEvent = {}; for (let eventKey in value) { let eventValue = value[eventKey]; if (eventValue && typeof eventValue == 'object') { if (eventKey == 'detail') { // Keep the detail value, because it contains important information // for diagnosing errors. simpleEvent[eventKey] = eventValue; } // Strip out non-null object types because they are complex and we // don't need them. } else if (eventKey in Event) { // Strip out keys that are found on Event itself because they are // class-level constants we don't need, like Event.MOUSEMOVE == 16. } else { simpleEvent[eventKey] = eventValue; } } return simpleEvent; } if (value instanceof Error) { // Errors don't serialize to JSON well, either. TypeError, for example, // turns in "{}", leading to messages like "Error UNKNOWN.UNKNOWN" when // deserialized on the sender and displayed in the demo app. return shaka.cast.CastUtils.unpackError_(value); } if (value instanceof TimeRanges) { // TimeRanges must be unpacked into plain data for serialization. return shaka.cast.CastUtils.unpackTimeRanges_(value); } if (value instanceof Uint8Array) { // Some of our code cares about Uint8Arrays actually being Uint8Arrays, // so this gives them special treatment. return shaka.cast.CastUtils.unpackUint8Array_(value); } if (typeof value == 'number') { // NaN and infinity cannot be represented directly in JSON. if (isNaN(value)) return 'NaN'; if (isFinite(value)) return value; if (value < 0) return '-Infinity'; return 'Infinity'; } return value; }); }; /** * Deserialize JSON using our special encodings. * @param {string} str * @return {?} */ shaka.cast.CastUtils.deserialize = function(str) { return JSON.parse(str, function(key, value) { if (value == 'NaN') { return NaN; } else if (value == '-Infinity') { return -Infinity; } else if (value == 'Infinity') { return Infinity; } else if (value && typeof value == 'object' && value['__type__'] == 'TimeRanges') { // TimeRanges objects have been unpacked and sent as plain data. // Simulate the original TimeRanges object. return shaka.cast.CastUtils.simulateTimeRanges_(value); } else if (value && typeof value == 'object' && value['__type__'] == 'Uint8Array') { return shaka.cast.CastUtils.makeUint8Array_(value); } else if (value && typeof value == 'object' && value['__type__'] == 'Error') { return shaka.cast.CastUtils.makeError_(value); } return value; }); }; /** * @param {!TimeRanges} ranges * @return {!Object} * @private */ shaka.cast.CastUtils.unpackTimeRanges_ = function(ranges) { let obj = { '__type__': 'TimeRanges', // a signal to deserialize 'length': ranges.length, 'start': [], 'end': [], }; for (let i = 0; i < ranges.length; ++i) { obj['start'].push(ranges.start(i)); obj['end'].push(ranges.end(i)); } return obj; }; /** * Creates a simulated TimeRanges object from data sent by the cast receiver. * @param {?} obj * @return {{ * length: number, * start: function(number): number, * end: function(number): number * }} * @private */ shaka.cast.CastUtils.simulateTimeRanges_ = function(obj) { return { length: obj.length, // NOTE: a more complete simulation would throw when |i| was out of range, // but for simplicity we will assume a well-behaved application that uses // length instead of catch to stop iterating. start: function(i) { return obj.start[i]; }, end: function(i) { return obj.end[i]; }, }; }; /** * @param {!Uint8Array} array * @return {!Object} * @private */ shaka.cast.CastUtils.unpackUint8Array_ = function(array) { return { '__type__': 'Uint8Array', // a signal to deserialize 'entries': Array.from(array), }; }; /** * Creates a Uint8Array object from data sent by the cast receiver. * @param {?} obj * @return {!Uint8Array} * @private */ shaka.cast.CastUtils.makeUint8Array_ = function(obj) { return new Uint8Array(obj['entries']); }; /** * @param {!Error} error * @return {!Object} * @private */ shaka.cast.CastUtils.unpackError_ = function(error) { // None of the properties in TypeError are enumerable, but there are some // common Error properties we expect. We also enumerate any enumerable // properties and "own" properties of the type, in case there is an Error // subtype with additional properties we don't know about in advance. const properties = new Set(['name', 'message', 'stack']); for (const key in error) { properties.add(key); } for (const key of Object.getOwnPropertyNames(error)) { properties.add(key); } const contents = {}; for (const key of properties) { contents[key] = error[key]; } return { '__type__': 'Error', // a signal to deserialize 'contents': contents, }; }; /** * Creates an Error object from data sent by the cast receiver. * @param {?} obj * @return {!Error} * @private */ shaka.cast.CastUtils.makeError_ = function(obj) { const contents = obj['contents']; const error = new Error(contents['message']); for (const key in contents) { error[key] = contents[key]; } return error; };