Source: lib/cast/cast_utils.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.cast.CastUtils');
  18. goog.require('shaka.util.FakeEvent');
  19. /**
  20. * @namespace shaka.cast.CastUtils
  21. * @summary A set of cast utility functions and variables shared between sender
  22. * and receiver.
  23. */
  24. /**
  25. * HTMLMediaElement events that are proxied while casting.
  26. * @const {!Array.<string>}
  27. */
  28. shaka.cast.CastUtils.VideoEvents = [
  29. 'ended',
  30. 'play',
  31. 'playing',
  32. 'pause',
  33. 'pausing',
  34. 'ratechange',
  35. 'seeked',
  36. 'seeking',
  37. 'timeupdate',
  38. 'volumechange'
  39. ];
  40. /**
  41. * HTMLMediaElement attributes that are proxied while casting.
  42. * @const {!Array.<string>}
  43. */
  44. shaka.cast.CastUtils.VideoAttributes = [
  45. 'buffered',
  46. 'currentTime',
  47. 'duration',
  48. 'ended',
  49. 'loop',
  50. 'muted',
  51. 'paused',
  52. 'playbackRate',
  53. 'seeking',
  54. 'videoHeight',
  55. 'videoWidth',
  56. 'volume'
  57. ];
  58. /**
  59. * HTMLMediaElement attributes that are transferred when casting begins.
  60. * @const {!Array.<string>}
  61. */
  62. shaka.cast.CastUtils.VideoInitStateAttributes = [
  63. 'loop',
  64. 'playbackRate'
  65. ];
  66. /**
  67. * HTMLMediaElement methods with no return value that are proxied while casting.
  68. * @const {!Array.<string>}
  69. */
  70. shaka.cast.CastUtils.VideoVoidMethods = [
  71. 'pause',
  72. 'play'
  73. ];
  74. /**
  75. * Player events that are proxied while casting.
  76. * @const {!Array.<string>}
  77. */
  78. shaka.cast.CastUtils.PlayerEvents = [
  79. 'adaptation',
  80. 'buffering',
  81. 'emsg',
  82. 'error',
  83. 'loading',
  84. 'streaming',
  85. 'texttrackvisibility',
  86. 'timelineregionadded',
  87. 'timelineregionenter',
  88. 'timelineregionexit',
  89. 'trackschanged',
  90. 'unloading'
  91. ];
  92. /**
  93. * Player getter methods that are proxied while casting.
  94. * The key is the method, the value is the frequency of updates.
  95. * Frequency 1 translates to every update; frequency 2 to every 2 updates, etc.
  96. * @const {!Object.<string, number>}
  97. */
  98. shaka.cast.CastUtils.PlayerGetterMethods = {
  99. // NOTE: The 'drmInfo' property is not proxied, as it is very large.
  100. 'getAudioLanguages': 2,
  101. 'getAudioLanguagesAndRoles': 2,
  102. 'getBufferedInfo': 2,
  103. 'getConfiguration': 2,
  104. 'getExpiration': 2,
  105. // Note that the 'getManifest' property is not proxied, as it is very large.
  106. 'getManifestUri': 2,
  107. 'getPlaybackRate': 2,
  108. 'getTextLanguages': 2,
  109. 'getTextLanguagesAndRoles': 2,
  110. 'getTextTracks': 2,
  111. 'getStats': 5,
  112. 'usingEmbeddedTextTrack': 2,
  113. 'getVariantTracks': 2,
  114. 'isAudioOnly': 10,
  115. 'isBuffering': 1,
  116. 'isInProgress': 1,
  117. 'isLive': 10,
  118. 'isTextTrackVisible': 1,
  119. 'keySystem': 10,
  120. 'seekRange': 1
  121. };
  122. /**
  123. * Player getter methods that are proxied while casting, but only when casting
  124. * a livestream.
  125. * The key is the method, the value is the frequency of updates.
  126. * Frequency 1 translates to every update; frequency 2 to every 2 updates, etc.
  127. * @const {!Object.<string, number>}
  128. */
  129. shaka.cast.CastUtils.PlayerGetterMethodsThatRequireLive = {
  130. 'getPlayheadTimeAsDate': 1,
  131. 'getPresentationStartTimeAsDate': 20
  132. };
  133. /**
  134. * Player getter and setter methods that are used to transfer state when casting
  135. * begins.
  136. * @const {!Array.<!Array.<string>>}
  137. */
  138. shaka.cast.CastUtils.PlayerInitState = [
  139. ['getConfiguration', 'configure']
  140. ];
  141. /**
  142. * Player getter and setter methods that are used to transfer state after
  143. * load() is resolved.
  144. * @const {!Array.<!Array.<string>>}
  145. */
  146. shaka.cast.CastUtils.PlayerInitAfterLoadState = [
  147. ['isTextTrackVisible', 'setTextTrackVisibility']
  148. ];
  149. /**
  150. * Player methods with no return value that are proxied while casting.
  151. * @const {!Array.<string>}
  152. */
  153. shaka.cast.CastUtils.PlayerVoidMethods = [
  154. 'addTextTrack',
  155. 'cancelTrickPlay',
  156. 'configure',
  157. 'resetConfiguration',
  158. 'retryStreaming',
  159. 'selectAudioLanguage',
  160. 'selectEmbeddedTextTrack',
  161. 'selectTextLanguage',
  162. 'selectTextTrack',
  163. 'selectVariantTrack',
  164. 'setTextTrackVisibility',
  165. 'trickPlay'
  166. ];
  167. /**
  168. * Player methods returning a Promise that are proxied while casting.
  169. * @const {!Array.<string>}
  170. */
  171. shaka.cast.CastUtils.PlayerPromiseMethods = [
  172. 'attach',
  173. 'detach',
  174. // The opt_manifestFactory parameter of load is not supported.
  175. 'load',
  176. 'unload'
  177. ];
  178. /**
  179. * @typedef {{
  180. * video: Object,
  181. * player: Object,
  182. * manifest: ?string,
  183. * startTime: ?number
  184. * }}
  185. * @property {Object} video
  186. * Dictionary of video properties to be set.
  187. * @property {Object} player
  188. * Dictionary of player setters to be called.
  189. * @property {?string} manifest
  190. * The currently-selected manifest, if present.
  191. * @property {?number} startTime
  192. * The playback start time, if currently playing.
  193. */
  194. shaka.cast.CastUtils.InitStateType;
  195. /**
  196. * The namespace for Shaka messages on the cast bus.
  197. * @const {string}
  198. */
  199. shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE = 'urn:x-cast:com.google.shaka.v2';
  200. /**
  201. * The namespace for generic messages on the cast bus.
  202. * @const {string}
  203. */
  204. shaka.cast.CastUtils.GENERIC_MESSAGE_NAMESPACE =
  205. 'urn:x-cast:com.google.cast.media';
  206. /**
  207. * Serialize as JSON, but specially encode things JSON will not otherwise
  208. * represent.
  209. * @param {?} thing
  210. * @return {string}
  211. */
  212. shaka.cast.CastUtils.serialize = function(thing) {
  213. return JSON.stringify(thing, function(key, value) {
  214. if (typeof value == 'function') {
  215. // Functions can't be (safely) serialized.
  216. return undefined;
  217. }
  218. if (value instanceof Event || value instanceof shaka.util.FakeEvent) {
  219. // Events don't serialize to JSON well because of the DOM objects
  220. // and other complex objects they contain, so we strip these out.
  221. // Note that using Object.keys or JSON.stringify directly on the event
  222. // will not capture its properties. We must use a for loop.
  223. let simpleEvent = {};
  224. for (let eventKey in value) {
  225. let eventValue = value[eventKey];
  226. if (eventValue && typeof eventValue == 'object') {
  227. if (eventKey == 'detail') {
  228. // Keep the detail value, because it contains important information
  229. // for diagnosing errors.
  230. simpleEvent[eventKey] = eventValue;
  231. }
  232. // Strip out non-null object types because they are complex and we
  233. // don't need them.
  234. } else if (eventKey in Event) {
  235. // Strip out keys that are found on Event itself because they are
  236. // class-level constants we don't need, like Event.MOUSEMOVE == 16.
  237. } else {
  238. simpleEvent[eventKey] = eventValue;
  239. }
  240. }
  241. return simpleEvent;
  242. }
  243. if (value instanceof TimeRanges) {
  244. // TimeRanges must be unpacked into plain data for serialization.
  245. return shaka.cast.CastUtils.unpackTimeRanges_(value);
  246. }
  247. if (value instanceof Uint8Array) {
  248. // Some of our code cares about Uint8Arrays actually being Uint8Arrays,
  249. // so this gives them special treatment.
  250. return shaka.cast.CastUtils.unpackUint8Array_(value);
  251. }
  252. if (typeof value == 'number') {
  253. // NaN and infinity cannot be represented directly in JSON.
  254. if (isNaN(value)) return 'NaN';
  255. if (isFinite(value)) return value;
  256. if (value < 0) return '-Infinity';
  257. return 'Infinity';
  258. }
  259. return value;
  260. });
  261. };
  262. /**
  263. * Deserialize JSON using our special encodings.
  264. * @param {string} str
  265. * @return {?}
  266. */
  267. shaka.cast.CastUtils.deserialize = function(str) {
  268. return JSON.parse(str, function(key, value) {
  269. if (value == 'NaN') {
  270. return NaN;
  271. } else if (value == '-Infinity') {
  272. return -Infinity;
  273. } else if (value == 'Infinity') {
  274. return Infinity;
  275. } else if (value && typeof value == 'object' &&
  276. value['__type__'] == 'TimeRanges') {
  277. // TimeRanges objects have been unpacked and sent as plain data.
  278. // Simulate the original TimeRanges object.
  279. return shaka.cast.CastUtils.simulateTimeRanges_(value);
  280. } else if (value && typeof value == 'object' &&
  281. value['__type__'] == 'Uint8Array') {
  282. return shaka.cast.CastUtils.makeUint8Array_(value);
  283. }
  284. return value;
  285. });
  286. };
  287. /**
  288. * @param {!TimeRanges} ranges
  289. * @return {Object}
  290. * @private
  291. */
  292. shaka.cast.CastUtils.unpackTimeRanges_ = function(ranges) {
  293. let obj = {
  294. '__type__': 'TimeRanges', // a signal to deserialize
  295. 'length': ranges.length,
  296. 'start': [],
  297. 'end': []
  298. };
  299. for (let i = 0; i < ranges.length; ++i) {
  300. obj['start'].push(ranges.start(i));
  301. obj['end'].push(ranges.end(i));
  302. }
  303. return obj;
  304. };
  305. /**
  306. * Creates a simulated TimeRanges object from data sent by the cast receiver.
  307. * @param {?} obj
  308. * @return {{
  309. * length: number,
  310. * start: function(number): number,
  311. * end: function(number): number
  312. * }}
  313. * @private
  314. */
  315. shaka.cast.CastUtils.simulateTimeRanges_ = function(obj) {
  316. return {
  317. length: obj.length,
  318. // NOTE: a more complete simulation would throw when |i| was out of range,
  319. // but for simplicity we will assume a well-behaved application that uses
  320. // length instead of catch to stop iterating.
  321. start: function(i) { return obj.start[i]; },
  322. end: function(i) { return obj.end[i]; }
  323. };
  324. };
  325. /**
  326. * @param {!Uint8Array} array
  327. * @return {Object}
  328. * @private
  329. */
  330. shaka.cast.CastUtils.unpackUint8Array_ = function(array) {
  331. return {
  332. '__type__': 'Uint8Array', // a signal to deserialize
  333. 'entries': Array.from(array),
  334. };
  335. };
  336. /**
  337. * Creates a Uint8Array object from data sent by the cast receiver.
  338. * @param {?} obj
  339. * @return {Uint8Array}
  340. * @private
  341. */
  342. shaka.cast.CastUtils.makeUint8Array_ = function(obj) {
  343. return new Uint8Array(obj['entries']);
  344. };