shaka-player 4.9.24 → 4.9.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,6 +29,7 @@ goog.require('shaka.util.LanguageUtils');
29
29
  goog.require('shaka.util.ManifestParserUtils');
30
30
  goog.require('shaka.util.MimeUtils');
31
31
  goog.require('shaka.util.Networking');
32
+ goog.require('shaka.util.ObjectUtils');
32
33
  goog.require('shaka.util.OperationManager');
33
34
  goog.require('shaka.util.PeriodCombiner');
34
35
  goog.require('shaka.util.PlayerConfiguration');
@@ -92,12 +93,19 @@ shaka.dash.DashParser = class {
92
93
 
93
94
  /**
94
95
  * A map of IDs to Stream objects.
95
- * ID: Period@id,AdaptationSet@id,@Representation@id
96
- * e.g.: '1,5,23'
96
+ * ID: Period@id,Representation@id
97
+ * e.g.: '1,23'
97
98
  * @private {!Object.<string, !shaka.extern.Stream>}
98
99
  */
99
100
  this.streamMap_ = {};
100
101
 
102
+ /**
103
+ * A map of Period IDs to Stream Map IDs.
104
+ * Use to have direct access to streamMap key.
105
+ * @private {!Object.<string, !Array<string>>}
106
+ */
107
+ this.indexStreamMap_ = {};
108
+
101
109
  /**
102
110
  * A map of period ids to their durations
103
111
  * @private {!Object.<string, number>}
@@ -235,6 +243,7 @@ shaka.dash.DashParser = class {
235
243
  this.manifestUris_ = [];
236
244
  this.manifest_ = null;
237
245
  this.streamMap_ = {};
246
+ this.indexStreamMap_ = {};
238
247
  this.contextCache_.clear();
239
248
  this.manifestPatchContext_ = {
240
249
  mpdId: '',
@@ -618,7 +627,7 @@ shaka.dash.DashParser = class {
618
627
  }
619
628
 
620
629
  this.manifestPatchContext_.type = mpdType;
621
-
630
+ this.cleanStreamMap_();
622
631
  /** @type {!shaka.media.PresentationTimeline} */
623
632
  let presentationTimeline;
624
633
  if (this.manifest_) {
@@ -697,7 +706,8 @@ shaka.dash.DashParser = class {
697
706
  profiles: profiles.split(','),
698
707
  };
699
708
 
700
- const periodsAndDuration = this.parsePeriods_(context, getBaseUris, mpd);
709
+ const periodsAndDuration = this.parsePeriods_(
710
+ context, getBaseUris, mpd, /* newPeriod= */ false);
701
711
  const duration = periodsAndDuration.duration;
702
712
  const periods = periodsAndDuration.periods;
703
713
 
@@ -977,7 +987,7 @@ shaka.dash.DashParser = class {
977
987
  };
978
988
 
979
989
  const periodsAndDuration = this.parsePeriods_(context,
980
- this.manifestPatchContext_.getBaseUris, periods);
990
+ this.manifestPatchContext_.getBaseUris, periods, /* newPeriod= */ true);
981
991
 
982
992
  return periodsAndDuration.periods;
983
993
  }
@@ -996,6 +1006,10 @@ shaka.dash.DashParser = class {
996
1006
  this.contextCache_.delete(contextId);
997
1007
  }
998
1008
  }
1009
+ const newPeriods = this.lastManifestUpdatePeriodIds_.filter((pID) => {
1010
+ return pID !== periodId;
1011
+ });
1012
+ this.lastManifestUpdatePeriodIds_ = newPeriods;
999
1013
  }
1000
1014
 
1001
1015
  /**
@@ -1237,6 +1251,7 @@ shaka.dash.DashParser = class {
1237
1251
  * @param {shaka.dash.DashParser.Context} context
1238
1252
  * @param {function():!Array.<string>} getBaseUris
1239
1253
  * @param {!shaka.extern.xml.Node} mpd
1254
+ * @param {!boolean} newPeriod
1240
1255
  * @return {{
1241
1256
  * periods: !Array.<shaka.extern.Period>,
1242
1257
  * duration: ?number,
@@ -1244,7 +1259,7 @@ shaka.dash.DashParser = class {
1244
1259
  * }}
1245
1260
  * @private
1246
1261
  */
1247
- parsePeriods_(context, getBaseUris, mpd) {
1262
+ parsePeriods_(context, getBaseUris, mpd, newPeriod) {
1248
1263
  const TXml = shaka.util.TXml;
1249
1264
  let presentationDuration = context.mediaPresentationDuration;
1250
1265
 
@@ -1370,8 +1385,18 @@ shaka.dash.DashParser = class {
1370
1385
  prevEnd = start + periodDuration;
1371
1386
  } // end of period parsing loop
1372
1387
 
1373
- // Replace previous seen periods with the current one.
1374
- this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
1388
+ if (newPeriod) {
1389
+ // append new period from the patch manifest
1390
+ for (const el of periods) {
1391
+ const periodID = el.id;
1392
+ if (!this.lastManifestUpdatePeriodIds_.includes(periodID)) {
1393
+ this.lastManifestUpdatePeriodIds_.push(periodID);
1394
+ }
1395
+ }
1396
+ } else {
1397
+ // Replace previous seen periods with the current one.
1398
+ this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
1399
+ }
1375
1400
 
1376
1401
  if (presentationDuration != null) {
1377
1402
  if (prevEnd != null) {
@@ -1399,6 +1424,26 @@ shaka.dash.DashParser = class {
1399
1424
  }
1400
1425
  }
1401
1426
 
1427
+ /**
1428
+ * Clean StreamMap Object to remove reference of deleted Stream Object
1429
+ * @private
1430
+ */
1431
+ cleanStreamMap_() {
1432
+ const oldPeriodIds = Object.keys(this.indexStreamMap_);
1433
+ if (oldPeriodIds.length === this.lastManifestUpdatePeriodIds_.length) {
1434
+ return;
1435
+ }
1436
+ const diffPeriodsIDs = oldPeriodIds.filter((pId) => {
1437
+ return !this.lastManifestUpdatePeriodIds_.includes(pId);
1438
+ });
1439
+ for (const pId of diffPeriodsIDs) {
1440
+ for (const contextId of this.indexStreamMap_[pId]) {
1441
+ delete this.streamMap_[contextId];
1442
+ }
1443
+ delete this.indexStreamMap_[pId];
1444
+ }
1445
+ }
1446
+
1402
1447
  /**
1403
1448
  * Parses a Period XML element. Unlike the other parse methods, this is not
1404
1449
  * given the Node; it is given a PeriodInfo structure. Also, partial parsing
@@ -1415,6 +1460,7 @@ shaka.dash.DashParser = class {
1415
1460
  const TXml = shaka.util.TXml;
1416
1461
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1417
1462
 
1463
+ goog.asserts.assert(periodInfo.node, 'periodInfo.node should exist');
1418
1464
  context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
1419
1465
  context.periodInfo = periodInfo;
1420
1466
  context.period.availabilityTimeOffset = context.availabilityTimeOffset;
@@ -2184,7 +2230,12 @@ shaka.dash.DashParser = class {
2184
2230
  };
2185
2231
 
2186
2232
  if (contextId && context.dynamic && !this.streamMap_[contextId]) {
2233
+ const periodId = context.period.id || '';
2234
+ if (!this.indexStreamMap_[periodId]) {
2235
+ this.indexStreamMap_[periodId] = [];
2236
+ }
2187
2237
  this.streamMap_[contextId] = stream;
2238
+ this.indexStreamMap_[periodId].push(contextId);
2188
2239
  }
2189
2240
 
2190
2241
  return stream;
@@ -2198,45 +2249,29 @@ shaka.dash.DashParser = class {
2198
2249
  * @private
2199
2250
  */
2200
2251
  cloneContext_(context) {
2201
- const contextClone = /** @type {!shaka.dash.DashParser.Context} */({});
2202
-
2203
- for (const k of Object.keys(context)) {
2204
- if (['period', 'adaptationSet', 'representation'].includes(k)) {
2205
- /** @type {shaka.dash.DashParser.InheritanceFrame} */
2206
- const frameRef = context[k];
2207
- contextClone[k] = {
2208
- segmentBase: null,
2209
- segmentList: null,
2210
- segmentTemplate: frameRef.segmentTemplate,
2211
- getBaseUris: frameRef.getBaseUris,
2212
- width: frameRef.width,
2213
- height: frameRef.height,
2214
- contentType: frameRef.contentType,
2215
- mimeType: frameRef.mimeType,
2216
- language: frameRef.language,
2217
- codecs: frameRef.codecs,
2218
- frameRate: frameRef.frameRate,
2219
- pixelAspectRatio: frameRef.pixelAspectRatio,
2220
- emsgSchemeIdUris: frameRef.emsgSchemeIdUris,
2221
- id: frameRef.id,
2222
- position: frameRef.position,
2223
- numChannels: frameRef.numChannels,
2224
- audioSamplingRate: frameRef.audioSamplingRate,
2225
- availabilityTimeOffset: frameRef.availabilityTimeOffset,
2226
- initialization: frameRef.initialization,
2227
- };
2228
- } else if (k == 'periodInfo') {
2229
- /** @type {shaka.dash.DashParser.PeriodInfo} */
2230
- const frameRef = context[k];
2231
- contextClone[k] = {
2232
- start: frameRef.start,
2233
- duration: frameRef.duration,
2234
- node: null,
2235
- isLastPeriod: frameRef.isLastPeriod,
2236
- };
2237
- } else {
2238
- contextClone[k] = context[k];
2252
+ /**
2253
+ * @param {?shaka.dash.DashParser.InheritanceFrame} frame
2254
+ * @return {?shaka.dash.DashParser.InheritanceFrame}
2255
+ */
2256
+ const cloneFrame = (frame) => {
2257
+ if (!frame) {
2258
+ return null;
2239
2259
  }
2260
+ const clone = shaka.util.ObjectUtils.shallowCloneObject(frame);
2261
+ clone.segmentBase = null;
2262
+ clone.segmentList = null;
2263
+ clone.segmentTemplate = shaka.util.TXml.cloneNode(clone.segmentTemplate);
2264
+ return clone;
2265
+ };
2266
+ const contextClone = shaka.util.ObjectUtils.shallowCloneObject(context);
2267
+ contextClone.period = cloneFrame(contextClone.period);
2268
+ contextClone.adaptationSet = cloneFrame(contextClone.adaptationSet);
2269
+ contextClone.representation = cloneFrame(contextClone.representation);
2270
+
2271
+ if (contextClone.periodInfo) {
2272
+ contextClone.periodInfo =
2273
+ shaka.util.ObjectUtils.shallowCloneObject(contextClone.periodInfo);
2274
+ contextClone.periodInfo.node = null;
2240
2275
  }
2241
2276
 
2242
2277
  return contextClone;
@@ -3082,7 +3117,7 @@ shaka.dash.DashParser.Context;
3082
3117
  * @typedef {{
3083
3118
  * start: number,
3084
3119
  * duration: ?number,
3085
- * node: !shaka.extern.xml.Node,
3120
+ * node: ?shaka.extern.xml.Node,
3086
3121
  * isLastPeriod: boolean
3087
3122
  * }}
3088
3123
  *
@@ -3094,7 +3129,7 @@ shaka.dash.DashParser.Context;
3094
3129
  * @property {?number} duration
3095
3130
  * The duration of the period; or null if the duration is not given. This
3096
3131
  * will be non-null for all periods except the last.
3097
- * @property {!shaka.extern.xml.Node} node
3132
+ * @property {?shaka.extern.xml.Node} node
3098
3133
  * The XML Node for the Period.
3099
3134
  * @property {boolean} isLastPeriod
3100
3135
  * Whether this Period is the last one in the manifest.
@@ -73,6 +73,13 @@ shaka.media.StreamingEngine = class {
73
73
  /** @private {?shaka.extern.StreamingConfiguration} */
74
74
  this.config_ = null;
75
75
 
76
+ /**
77
+ * Retains a reference to the function used to close SegmentIndex objects
78
+ * for streams which were switched away from during an ongoing update_().
79
+ * @private {!Map.<string, !function()>}
80
+ */
81
+ this.deferredCloseSegmentIndex_ = new Map();
82
+
76
83
  /** @private {number} */
77
84
  this.bufferingGoalScale_ = 1;
78
85
 
@@ -504,6 +511,24 @@ shaka.media.StreamingEngine = class {
504
511
  }
505
512
 
506
513
 
514
+ /**
515
+ * Handles deferred releases of old SegmentIndexes for the mediaState's
516
+ * content type from a previous update.
517
+ * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
518
+ * @private
519
+ */
520
+ handleDeferredCloseSegmentIndexes_(mediaState) {
521
+ for (const [key, value] of this.deferredCloseSegmentIndex_.entries()) {
522
+ const streamId = /** @type {string} */ (key);
523
+ const closeSegmentIndex = /** @type {!function()} */ (value);
524
+ if (streamId.includes(mediaState.type)) {
525
+ closeSegmentIndex();
526
+ this.deferredCloseSegmentIndex_.delete(streamId);
527
+ }
528
+ }
529
+ }
530
+
531
+
507
532
  /**
508
533
  * Switches to the given Stream. |stream| may be from any Variant.
509
534
  *
@@ -576,7 +601,23 @@ shaka.media.StreamingEngine = class {
576
601
  // Do not close segment indexes we are prefetching.
577
602
  if (!this.audioPrefetchMap_.has(mediaState.stream)) {
578
603
  if (mediaState.stream.closeSegmentIndex) {
579
- mediaState.stream.closeSegmentIndex();
604
+ if (mediaState.performingUpdate) {
605
+ const oldStreamTag =
606
+ shaka.media.StreamingEngine.logPrefix_(mediaState);
607
+ if (!this.deferredCloseSegmentIndex_.has(oldStreamTag)) {
608
+ // The ongoing update is still using the old stream's segment
609
+ // reference information.
610
+ // If we close the old stream now, the update will not complete
611
+ // correctly.
612
+ // The next onUpdate_() for this content type will resume the
613
+ // closeSegmentIndex() operation for the old stream once the ongoing
614
+ // update has finished, then immediately create a new segment index.
615
+ this.deferredCloseSegmentIndex_.set(
616
+ oldStreamTag, mediaState.stream.closeSegmentIndex);
617
+ }
618
+ } else {
619
+ mediaState.stream.closeSegmentIndex();
620
+ }
580
621
  }
581
622
  }
582
623
 
@@ -1169,6 +1210,13 @@ shaka.media.StreamingEngine = class {
1169
1210
  return;
1170
1211
  }
1171
1212
 
1213
+ // If stream switches happened during the previous update_() for this
1214
+ // content type, close out the old streams that were switched away from.
1215
+ // Even if we had switched away from the active stream 'A' during the
1216
+ // update_(), e.g. (A -> B -> A), closing 'A' is permissible here since we
1217
+ // will immediately re-create it in the logic below.
1218
+ this.handleDeferredCloseSegmentIndexes_(mediaState);
1219
+
1172
1220
  // Make sure the segment index exists. If not, create the segment index.
1173
1221
  if (!mediaState.stream.segmentIndex) {
1174
1222
  const thisStream = mediaState.stream;
@@ -1450,14 +1498,11 @@ shaka.media.StreamingEngine = class {
1450
1498
  mediaState.stream.segmentIndex,
1451
1499
  'segment index should have been generated already');
1452
1500
 
1453
- const currentSegment = mediaState.segmentIterator ?
1454
- mediaState.segmentIterator.current() : null;
1455
-
1456
- if (currentSegment) {
1501
+ if (mediaState.segmentIterator) {
1457
1502
  // Something is buffered from the same Stream. Use the current position
1458
1503
  // in the segment index. This is updated via next() after each segment is
1459
1504
  // appended.
1460
- return currentSegment;
1505
+ return mediaState.segmentIterator.current();
1461
1506
  } else if (mediaState.lastSegmentReference || bufferEnd) {
1462
1507
  // Something is buffered from another Stream.
1463
1508
  const time = mediaState.lastSegmentReference ?
@@ -1955,8 +2000,8 @@ shaka.media.StreamingEngine = class {
1955
2000
  reference.startTime <= appendWindowEnd,
1956
2001
  logPrefix + ' segment should start before append window end');
1957
2002
 
1958
- const codecs = MimeUtils.getCodecBase(
1959
- reference.codecs || mediaState.stream.codecs);
2003
+ const fullCodecs = (reference.codecs || mediaState.stream.codecs);
2004
+ const codecs = MimeUtils.getCodecBase(fullCodecs);
1960
2005
  const mimeType = MimeUtils.getBasicType(
1961
2006
  reference.mimeType || mediaState.stream.mimeType);
1962
2007
  const timestampOffset = reference.timestampOffset;
@@ -1974,7 +2019,7 @@ shaka.media.StreamingEngine = class {
1974
2019
  const isResetMediaSourceNecessary =
1975
2020
  mediaState.lastCodecs && mediaState.lastMimeType &&
1976
2021
  this.playerInterface_.mediaSourceEngine.isResetMediaSourceNecessary(
1977
- mediaState.type, mediaState.stream, mimeType, codecs);
2022
+ mediaState.type, mediaState.stream, mimeType, fullCodecs);
1978
2023
  if (isResetMediaSourceNecessary) {
1979
2024
  let otherState = null;
1980
2025
  if (mediaState.type === ContentType.VIDEO) {
@@ -2717,6 +2762,10 @@ shaka.media.StreamingEngine = class {
2717
2762
  this.destroyer_.ensureNotDestroyed();
2718
2763
  // Try to recover from network errors
2719
2764
  if (error.category === shaka.util.Error.Category.NETWORK) {
2765
+ if (mediaState.restoreStreamAfterTrickPlay) {
2766
+ this.setTrickPlay(/* on= */ false);
2767
+ return;
2768
+ }
2720
2769
  const maxDisabledTime = this.getDisabledTime_(error);
2721
2770
  error.handled = this.playerInterface_.disableStream(
2722
2771
  mediaState.stream, maxDisabledTime);
package/lib/player.js CHANGED
@@ -5297,6 +5297,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5297
5297
  }
5298
5298
 
5299
5299
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
5300
+ this.stats_.addBytesDownloaded(NaN);
5300
5301
  this.stats_.setResolution(
5301
5302
  /* width= */ element.videoWidth || NaN,
5302
5303
  /* height= */ element.videoHeight || NaN);
@@ -6942,19 +6943,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6942
6943
  if (hasAltStream) {
6943
6944
  let didDisableStream = false;
6944
6945
 
6946
+ let isTrickModeVideo = false;
6947
+
6945
6948
  for (const variant of this.manifest_.variants) {
6946
6949
  const candidate = variant[stream.type];
6947
6950
 
6948
- if (candidate && candidate.id === stream.id) {
6951
+ if (!candidate) {
6952
+ continue;
6953
+ }
6954
+
6955
+ if (candidate.id === stream.id) {
6949
6956
  variant.disabledUntilTime = (Date.now() / 1000) + disableTime;
6950
6957
  didDisableStream = true;
6951
6958
 
6952
6959
  shaka.log.v2(
6953
6960
  'Disabled stream ' + stream.type + ':' + stream.id +
6954
6961
  ' for ' + disableTime + ' seconds...');
6962
+ } else if (candidate.trickModeVideo &&
6963
+ candidate.trickModeVideo.id == stream.id) {
6964
+ isTrickModeVideo = true;
6955
6965
  }
6956
6966
  }
6957
6967
 
6968
+ if (!didDisableStream && isTrickModeVideo) {
6969
+ return false;
6970
+ }
6971
+
6958
6972
  goog.asserts.assert(didDisableStream, 'Must have disabled stream');
6959
6973
 
6960
6974
  this.checkVariantsTimer_.tickEvery(1);
@@ -7504,7 +7518,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7504
7518
  * @export
7505
7519
  */
7506
7520
  // eslint-disable-next-line no-useless-concat
7507
- shaka.Player.version = 'v4.9.24' + '-uncompiled'; // x-release-please-version
7521
+ shaka.Player.version = 'v4.9.26' + '-uncompiled'; // x-release-please-version
7508
7522
 
7509
7523
  // Initialize the deprecation system using the version string we just set
7510
7524
  // on the player.
@@ -710,10 +710,6 @@ shaka.text.UITextDisplayer = class {
710
710
  // Only do this for the text itself.
711
711
  elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
712
712
  }
713
- const fontFamily = inherit((c) => c.fontFamily);
714
- if (fontFamily) {
715
- elem.style.fontFamily = fontFamily;
716
- }
717
713
  }
718
714
  if (text) {
719
715
  elem.textContent = text;
@@ -890,26 +890,33 @@ shaka.util.PeriodCombiner = class {
890
890
  // Create a fresh output stream for trick-mode playback.
891
891
  output.trickModeVideo = shaka.util.PeriodCombiner.cloneStream_(
892
892
  input.trickModeVideo);
893
- // TODO: fix the createSegmentIndex function for trickModeVideo.
894
- // The trick-mode tracks in multi-period content should have trick-mode
895
- // segment indexes whenever available, rather than only regular-mode
896
- // segment indexes.
893
+ output.trickModeVideo.matchedStreams = [];
897
894
  output.trickModeVideo.createSegmentIndex = () => {
898
- // Satisfy the compiler about the type.
899
- goog.asserts.assert(
900
- output.segmentIndex instanceof shaka.media.MetaSegmentIndex,
901
- 'The stream should have a MetaSegmentIndex.');
902
- output.trickModeVideo.segmentIndex = output.segmentIndex.clone();
895
+ if (output.trickModeVideo.segmentIndex) {
896
+ return Promise.resolve();
897
+ }
898
+ const segmentIndex = new shaka.media.MetaSegmentIndex();
899
+ goog.asserts.assert(output.trickModeVideo.matchedStreams,
900
+ 'trickmode matched streams should exist');
901
+ for (const stream of output.trickModeVideo.matchedStreams) {
902
+ goog.asserts.assert(stream.segmentIndex,
903
+ 'trickmode segment index should exist');
904
+ segmentIndex.appendSegmentIndex(stream.segmentIndex);
905
+ }
906
+ output.trickModeVideo.segmentIndex = segmentIndex;
907
+
903
908
  return Promise.resolve();
904
909
  };
905
910
  }
906
911
 
907
912
  // Concatenate the trick mode input onto the trick mode output.
913
+ output.trickModeVideo.matchedStreams.push(input.trickModeVideo);
908
914
  shaka.util.PeriodCombiner.concatenateStreams_(
909
915
  output.trickModeVideo, input.trickModeVideo);
910
916
  } else if (output.trickModeVideo) {
911
917
  // We have a trick mode output, but no input from this Period. Fill it in
912
918
  // from the standard input Stream.
919
+ output.trickModeVideo.matchedStreams.push(input);
913
920
  shaka.util.PeriodCombiner.concatenateStreams_(
914
921
  output.trickModeVideo, input);
915
922
  }
package/lib/util/tXml.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.util.TXml');
8
8
 
9
+ goog.require('shaka.util.ObjectUtils');
9
10
  goog.require('shaka.util.StringUtils');
10
11
  goog.require('shaka.log');
11
12
 
@@ -71,24 +72,37 @@ shaka.util.TXml = class {
71
72
  }
72
73
 
73
74
  /**
74
- * Parse some data
75
+ * Get namespace based on schema
75
76
  * @param {string} schema
76
77
  * @return {string}
77
78
  */
78
79
  static getKnownNameSpace(schema) {
79
- if (shaka.util.TXml.knownNameSpaces_.has(schema)) {
80
- return shaka.util.TXml.knownNameSpaces_.get(schema);
80
+ if (shaka.util.TXml.uriToNameSpace_.has(schema)) {
81
+ return shaka.util.TXml.uriToNameSpace_.get(schema);
81
82
  }
82
83
  return '';
83
84
  }
84
85
 
85
86
  /**
86
- * Parse some data
87
+ * Get schema based on namespace
88
+ * @param {string} NS
89
+ * @return {string}
90
+ */
91
+ static getKnownSchema(NS) {
92
+ if (shaka.util.TXml.nameSpaceToUri_.has(NS)) {
93
+ return shaka.util.TXml.nameSpaceToUri_.get(NS);
94
+ }
95
+ return '';
96
+ }
97
+
98
+ /**
99
+ * Sets NS <-> schema bidirectional mapping
87
100
  * @param {string} schema
88
101
  * @param {string} NS
89
102
  */
90
103
  static setKnownNameSpace(schema, NS) {
91
- shaka.util.TXml.knownNameSpaces_.set(schema, NS);
104
+ shaka.util.TXml.uriToNameSpace_.set(schema, NS);
105
+ shaka.util.TXml.nameSpaceToUri_.set(NS, schema);
92
106
  }
93
107
 
94
108
  /**
@@ -873,7 +887,12 @@ shaka.util.TXml = class {
873
887
  */
874
888
  static txmlNodeToDomElement(node) {
875
889
  const TXml = shaka.util.TXml;
876
- const element = document.createElementNS('', node.tagName);
890
+ let namespace = '';
891
+ const parts = node.tagName.split(':');
892
+ if (parts.length > 0) {
893
+ namespace = TXml.getKnownSchema(parts[0]);
894
+ }
895
+ const element = document.createElementNS(namespace, node.tagName);
877
896
 
878
897
  for (const k in node.attributes) {
879
898
  const v = node.attributes[k];
@@ -892,9 +911,41 @@ shaka.util.TXml = class {
892
911
 
893
912
  return element;
894
913
  }
914
+
915
+ /**
916
+ * Clones node and its children recursively. Skips parent.
917
+ * @param {?shaka.extern.xml.Node} node
918
+ * @return {?shaka.extern.xml.Node}
919
+ */
920
+ static cloneNode(node) {
921
+ if (!node) {
922
+ return null;
923
+ }
924
+ /** @type {!shaka.extern.xml.Node} */
925
+ const clone = {
926
+ tagName: node.tagName,
927
+ attributes: shaka.util.ObjectUtils.shallowCloneObject(node.attributes),
928
+ children: [],
929
+ parent: null,
930
+ };
931
+ for (const child of node.children) {
932
+ if (typeof child === 'string') {
933
+ clone.children.push(child);
934
+ } else {
935
+ const clonedChild = shaka.util.TXml.cloneNode(child);
936
+ clonedChild.parent = clone;
937
+ clone.children.push(clonedChild);
938
+ }
939
+ }
940
+ return clone;
941
+ }
895
942
  };
896
943
 
897
- shaka.util.TXml.knownNameSpaces_ = new Map([]);
944
+ /** @private {!Map<string, string>} */
945
+ shaka.util.TXml.uriToNameSpace_ = new Map();
946
+
947
+ /** @private {!Map<string, string>} */
948
+ shaka.util.TXml.nameSpaceToUri_ = new Map();
898
949
 
899
950
 
900
951
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.9.24",
4
+ "version": "4.9.26",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -22,10 +22,9 @@
22
22
  }
23
23
 
24
24
  /* Set the fonts for all other content. */
25
- * {
26
- font-family: Roboto-Regular, Roboto, sans-serif, TengwarTelcontar;
27
- -webkit-font-smoothing: antialiased;
28
- }
25
+ font-family: Roboto, sans-serif, TengwarTelcontar;
26
+ font-weight: normal;
27
+ -webkit-font-smoothing: antialiased;
29
28
  }
30
29
 
31
30
  /* Each browser has a different prefixed pseudo-class for fullscreened elements.
@@ -47,7 +47,7 @@
47
47
  content: attr(aria-label);
48
48
 
49
49
  /* Override .material-icons-round text styling */
50
- font-family: Roboto-Regular, Roboto, sans-serif;
50
+ font-family: Roboto, sans-serif;
51
51
  line-height: calc(@material-icons-width / 2);
52
52
  white-space: nowrap;
53
53
  font-size: 13px;