shaka-player 4.10.12 → 4.10.13

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.
@@ -30,6 +30,7 @@ goog.require('shaka.util.LanguageUtils');
30
30
  goog.require('shaka.util.ManifestParserUtils');
31
31
  goog.require('shaka.util.MimeUtils');
32
32
  goog.require('shaka.util.Networking');
33
+ goog.require('shaka.util.ObjectUtils');
33
34
  goog.require('shaka.util.OperationManager');
34
35
  goog.require('shaka.util.PeriodCombiner');
35
36
  goog.require('shaka.util.PlayerConfiguration');
@@ -93,12 +94,19 @@ shaka.dash.DashParser = class {
93
94
 
94
95
  /**
95
96
  * A map of IDs to Stream objects.
96
- * ID: Period@id,AdaptationSet@id,@Representation@id
97
- * e.g.: '1,5,23'
97
+ * ID: Period@id,Representation@id
98
+ * e.g.: '1,23'
98
99
  * @private {!Object.<string, !shaka.extern.Stream>}
99
100
  */
100
101
  this.streamMap_ = {};
101
102
 
103
+ /**
104
+ * A map of Period IDs to Stream Map IDs.
105
+ * Use to have direct access to streamMap key.
106
+ * @private {!Object.<string, !Array<string>>}
107
+ */
108
+ this.indexStreamMap_ = {};
109
+
102
110
  /**
103
111
  * A map of period ids to their durations
104
112
  * @private {!Object.<string, number>}
@@ -256,6 +264,7 @@ shaka.dash.DashParser = class {
256
264
  this.manifestUris_ = [];
257
265
  this.manifest_ = null;
258
266
  this.streamMap_ = {};
267
+ this.indexStreamMap_ = {};
259
268
  this.contextCache_.clear();
260
269
  this.manifestPatchContext_ = {
261
270
  mpdId: '',
@@ -653,7 +662,7 @@ shaka.dash.DashParser = class {
653
662
  }
654
663
 
655
664
  this.manifestPatchContext_.type = mpdType;
656
-
665
+ this.cleanStreamMap_();
657
666
  /** @type {!shaka.media.PresentationTimeline} */
658
667
  let presentationTimeline;
659
668
  if (this.manifest_) {
@@ -734,7 +743,8 @@ shaka.dash.DashParser = class {
734
743
  };
735
744
 
736
745
  this.gapCount_ = 0;
737
- const periodsAndDuration = this.parsePeriods_(context, getBaseUris, mpd);
746
+ const periodsAndDuration = this.parsePeriods_(
747
+ context, getBaseUris, mpd, /* newPeriod= */ false);
738
748
  const duration = periodsAndDuration.duration;
739
749
  const periods = periodsAndDuration.periods;
740
750
 
@@ -1013,7 +1023,7 @@ shaka.dash.DashParser = class {
1013
1023
  };
1014
1024
 
1015
1025
  const periodsAndDuration = this.parsePeriods_(context,
1016
- this.manifestPatchContext_.getBaseUris, periods);
1026
+ this.manifestPatchContext_.getBaseUris, periods, /* newPeriod= */ true);
1017
1027
 
1018
1028
  return periodsAndDuration.periods;
1019
1029
  }
@@ -1033,6 +1043,10 @@ shaka.dash.DashParser = class {
1033
1043
  this.contextCache_.delete(contextId);
1034
1044
  }
1035
1045
  }
1046
+ const newPeriods = this.lastManifestUpdatePeriodIds_.filter((pID) => {
1047
+ return pID !== periodId;
1048
+ });
1049
+ this.lastManifestUpdatePeriodIds_ = newPeriods;
1036
1050
  }
1037
1051
 
1038
1052
  /**
@@ -1274,6 +1288,7 @@ shaka.dash.DashParser = class {
1274
1288
  * @param {shaka.dash.DashParser.Context} context
1275
1289
  * @param {function():!Array.<string>} getBaseUris
1276
1290
  * @param {!shaka.extern.xml.Node} mpd
1291
+ * @param {!boolean} newPeriod
1277
1292
  * @return {{
1278
1293
  * periods: !Array.<shaka.extern.Period>,
1279
1294
  * duration: ?number,
@@ -1281,7 +1296,7 @@ shaka.dash.DashParser = class {
1281
1296
  * }}
1282
1297
  * @private
1283
1298
  */
1284
- parsePeriods_(context, getBaseUris, mpd) {
1299
+ parsePeriods_(context, getBaseUris, mpd, newPeriod) {
1285
1300
  const TXml = shaka.util.TXml;
1286
1301
  let presentationDuration = context.mediaPresentationDuration;
1287
1302
 
@@ -1413,8 +1428,18 @@ shaka.dash.DashParser = class {
1413
1428
  prevEnd = start + periodDuration;
1414
1429
  } // end of period parsing loop
1415
1430
 
1416
- // Replace previous seen periods with the current one.
1417
- this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
1431
+ if (newPeriod) {
1432
+ // append new period from the patch manifest
1433
+ for (const el of periods) {
1434
+ const periodID = el.id;
1435
+ if (!this.lastManifestUpdatePeriodIds_.includes(periodID)) {
1436
+ this.lastManifestUpdatePeriodIds_.push(periodID);
1437
+ }
1438
+ }
1439
+ } else {
1440
+ // Replace previous seen periods with the current one.
1441
+ this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
1442
+ }
1418
1443
 
1419
1444
  if (presentationDuration != null) {
1420
1445
  if (prevEnd != null) {
@@ -1442,6 +1467,26 @@ shaka.dash.DashParser = class {
1442
1467
  }
1443
1468
  }
1444
1469
 
1470
+ /**
1471
+ * Clean StreamMap Object to remove reference of deleted Stream Object
1472
+ * @private
1473
+ */
1474
+ cleanStreamMap_() {
1475
+ const oldPeriodIds = Object.keys(this.indexStreamMap_);
1476
+ if (oldPeriodIds.length === this.lastManifestUpdatePeriodIds_.length) {
1477
+ return;
1478
+ }
1479
+ const diffPeriodsIDs = oldPeriodIds.filter((pId) => {
1480
+ return !this.lastManifestUpdatePeriodIds_.includes(pId);
1481
+ });
1482
+ for (const pId of diffPeriodsIDs) {
1483
+ for (const contextId of this.indexStreamMap_[pId]) {
1484
+ delete this.streamMap_[contextId];
1485
+ }
1486
+ delete this.indexStreamMap_[pId];
1487
+ }
1488
+ }
1489
+
1445
1490
  /**
1446
1491
  * Parses a Period XML element. Unlike the other parse methods, this is not
1447
1492
  * given the Node; it is given a PeriodInfo structure. Also, partial parsing
@@ -1458,6 +1503,7 @@ shaka.dash.DashParser = class {
1458
1503
  const TXml = shaka.util.TXml;
1459
1504
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1460
1505
 
1506
+ goog.asserts.assert(periodInfo.node, 'periodInfo.node should exist');
1461
1507
  context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
1462
1508
  context.periodInfo = periodInfo;
1463
1509
  context.period.availabilityTimeOffset = context.availabilityTimeOffset;
@@ -2221,7 +2267,12 @@ shaka.dash.DashParser = class {
2221
2267
  };
2222
2268
 
2223
2269
  if (contextId && context.dynamic && !this.streamMap_[contextId]) {
2270
+ const periodId = context.period.id || '';
2271
+ if (!this.indexStreamMap_[periodId]) {
2272
+ this.indexStreamMap_[periodId] = [];
2273
+ }
2224
2274
  this.streamMap_[contextId] = stream;
2275
+ this.indexStreamMap_[periodId].push(contextId);
2225
2276
  }
2226
2277
 
2227
2278
  return stream;
@@ -2235,45 +2286,29 @@ shaka.dash.DashParser = class {
2235
2286
  * @private
2236
2287
  */
2237
2288
  cloneContext_(context) {
2238
- const contextClone = /** @type {!shaka.dash.DashParser.Context} */({});
2239
-
2240
- for (const k of Object.keys(context)) {
2241
- if (['period', 'adaptationSet', 'representation'].includes(k)) {
2242
- /** @type {shaka.dash.DashParser.InheritanceFrame} */
2243
- const frameRef = context[k];
2244
- contextClone[k] = {
2245
- segmentBase: null,
2246
- segmentList: null,
2247
- segmentTemplate: frameRef.segmentTemplate,
2248
- getBaseUris: frameRef.getBaseUris,
2249
- width: frameRef.width,
2250
- height: frameRef.height,
2251
- contentType: frameRef.contentType,
2252
- mimeType: frameRef.mimeType,
2253
- language: frameRef.language,
2254
- codecs: frameRef.codecs,
2255
- frameRate: frameRef.frameRate,
2256
- pixelAspectRatio: frameRef.pixelAspectRatio,
2257
- emsgSchemeIdUris: frameRef.emsgSchemeIdUris,
2258
- id: frameRef.id,
2259
- position: frameRef.position,
2260
- numChannels: frameRef.numChannels,
2261
- audioSamplingRate: frameRef.audioSamplingRate,
2262
- availabilityTimeOffset: frameRef.availabilityTimeOffset,
2263
- initialization: frameRef.initialization,
2264
- };
2265
- } else if (k == 'periodInfo') {
2266
- /** @type {shaka.dash.DashParser.PeriodInfo} */
2267
- const frameRef = context[k];
2268
- contextClone[k] = {
2269
- start: frameRef.start,
2270
- duration: frameRef.duration,
2271
- node: null,
2272
- isLastPeriod: frameRef.isLastPeriod,
2273
- };
2274
- } else {
2275
- contextClone[k] = context[k];
2289
+ /**
2290
+ * @param {?shaka.dash.DashParser.InheritanceFrame} frame
2291
+ * @return {?shaka.dash.DashParser.InheritanceFrame}
2292
+ */
2293
+ const cloneFrame = (frame) => {
2294
+ if (!frame) {
2295
+ return null;
2276
2296
  }
2297
+ const clone = shaka.util.ObjectUtils.shallowCloneObject(frame);
2298
+ clone.segmentBase = null;
2299
+ clone.segmentList = null;
2300
+ clone.segmentTemplate = shaka.util.TXml.cloneNode(clone.segmentTemplate);
2301
+ return clone;
2302
+ };
2303
+ const contextClone = shaka.util.ObjectUtils.shallowCloneObject(context);
2304
+ contextClone.period = cloneFrame(contextClone.period);
2305
+ contextClone.adaptationSet = cloneFrame(contextClone.adaptationSet);
2306
+ contextClone.representation = cloneFrame(contextClone.representation);
2307
+
2308
+ if (contextClone.periodInfo) {
2309
+ contextClone.periodInfo =
2310
+ shaka.util.ObjectUtils.shallowCloneObject(contextClone.periodInfo);
2311
+ contextClone.periodInfo.node = null;
2277
2312
  }
2278
2313
 
2279
2314
  return contextClone;
@@ -3137,7 +3172,7 @@ shaka.dash.DashParser.Context;
3137
3172
  * @typedef {{
3138
3173
  * start: number,
3139
3174
  * duration: ?number,
3140
- * node: !shaka.extern.xml.Node,
3175
+ * node: ?shaka.extern.xml.Node,
3141
3176
  * isLastPeriod: boolean
3142
3177
  * }}
3143
3178
  *
@@ -3149,7 +3184,7 @@ shaka.dash.DashParser.Context;
3149
3184
  * @property {?number} duration
3150
3185
  * The duration of the period; or null if the duration is not given. This
3151
3186
  * will be non-null for all periods except the last.
3152
- * @property {!shaka.extern.xml.Node} node
3187
+ * @property {?shaka.extern.xml.Node} node
3153
3188
  * The XML Node for the Period.
3154
3189
  * @property {boolean} isLastPeriod
3155
3190
  * 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;
@@ -1455,14 +1503,11 @@ shaka.media.StreamingEngine = class {
1455
1503
  mediaState.stream.segmentIndex,
1456
1504
  'segment index should have been generated already');
1457
1505
 
1458
- const currentSegment = mediaState.segmentIterator ?
1459
- mediaState.segmentIterator.current() : null;
1460
-
1461
- if (currentSegment) {
1506
+ if (mediaState.segmentIterator) {
1462
1507
  // Something is buffered from the same Stream. Use the current position
1463
1508
  // in the segment index. This is updated via next() after each segment is
1464
1509
  // appended.
1465
- return currentSegment;
1510
+ return mediaState.segmentIterator.current();
1466
1511
  } else if (mediaState.lastSegmentReference || bufferEnd) {
1467
1512
  // Something is buffered from another Stream.
1468
1513
  const time = mediaState.lastSegmentReference ?
@@ -2755,6 +2800,10 @@ shaka.media.StreamingEngine = class {
2755
2800
  this.destroyer_.ensureNotDestroyed();
2756
2801
  // Try to recover from network errors
2757
2802
  if (error.category === shaka.util.Error.Category.NETWORK) {
2803
+ if (mediaState.restoreStreamAfterTrickPlay) {
2804
+ this.setTrickPlay(/* on= */ false);
2805
+ return;
2806
+ }
2758
2807
  const maxDisabledTime = this.getDisabledTime_(error);
2759
2808
  error.handled = this.playerInterface_.disableStream(
2760
2809
  mediaState.stream, maxDisabledTime);
package/lib/player.js CHANGED
@@ -4454,9 +4454,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4454
4454
  * beginning or end of the seekable range for the content.
4455
4455
  *
4456
4456
  * @param {number} rate
4457
+ * @param {boolean=} useTrickPlayTrack
4457
4458
  * @export
4458
4459
  */
4459
- trickPlay(rate) {
4460
+ trickPlay(rate, useTrickPlayTrack = true) {
4460
4461
  // A playbackRate of 0 is used internally when we are in a buffering state,
4461
4462
  // and doesn't make sense for trick play. If you set a rate of 0 for trick
4462
4463
  // play, we will reject it and issue a warning. If it happens during a
@@ -4478,7 +4479,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4478
4479
 
4479
4480
  if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
4480
4481
  this.abrManager_.playbackRateChanged(rate);
4481
- this.streamingEngine_.setTrickPlay(Math.abs(rate) > 1);
4482
+ this.streamingEngine_.setTrickPlay(
4483
+ useTrickPlayTrack && Math.abs(rate) > 1);
4482
4484
  }
4483
4485
  if (this.isLive()) {
4484
4486
  this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
@@ -5561,6 +5563,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5561
5563
  }
5562
5564
 
5563
5565
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
5566
+ this.stats_.addBytesDownloaded(NaN);
5564
5567
  this.stats_.setResolution(
5565
5568
  /* width= */ element.videoWidth || NaN,
5566
5569
  /* height= */ element.videoHeight || NaN);
@@ -6502,7 +6505,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6502
6505
  shaka.log.debug('Buffer fullness ratio (' + bufferFullness + ') ' +
6503
6506
  'is less than the vodDynamicPlaybackRateBufferRatio (' +
6504
6507
  bufferThreshold + '). Updating playbackRate to ' + minPlaybackRate);
6505
- this.trickPlay(minPlaybackRate);
6508
+ this.trickPlay(minPlaybackRate, /* useTrickPlayTrack= */ false);
6506
6509
  }
6507
6510
  } else if (bufferFullness == 1) {
6508
6511
  if (playbackRate !== this.playRateController_.getDefaultRate()) {
@@ -6629,7 +6632,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6629
6632
  timeSinceLastRebuffer + 's) ' +
6630
6633
  'is less than the live sync panicThreshold (' + panicThreshold +
6631
6634
  's). Updating playbackRate to ' + minPlaybackRate);
6632
- this.trickPlay(minPlaybackRate);
6635
+ this.trickPlay(minPlaybackRate, /* useTrickPlayTrack= */ false);
6633
6636
  }
6634
6637
  } else if (maxLatency && maxPlaybackRate &&
6635
6638
  (latency - offset) > maxLatency) {
@@ -6637,7 +6640,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6637
6640
  shaka.log.debug('Latency (' + latency + 's) is greater than ' +
6638
6641
  'live sync maxLatency (' + maxLatency + 's). ' +
6639
6642
  'Updating playbackRate to ' + maxPlaybackRate);
6640
- this.trickPlay(maxPlaybackRate);
6643
+ this.trickPlay(maxPlaybackRate, /* useTrickPlayTrack= */ false);
6641
6644
  }
6642
6645
  this.targetLatencyReached_ = null;
6643
6646
  } else if (minLatency && minPlaybackRate &&
@@ -6646,7 +6649,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6646
6649
  shaka.log.debug('Latency (' + latency + 's) is smaller than ' +
6647
6650
  'live sync minLatency (' + minLatency + 's). ' +
6648
6651
  'Updating playbackRate to ' + minPlaybackRate);
6649
- this.trickPlay(minPlaybackRate);
6652
+ this.trickPlay(minPlaybackRate, /* useTrickPlayTrack= */ false);
6650
6653
  }
6651
6654
  this.targetLatencyReached_ = null;
6652
6655
  } else if (playbackRate !== this.playRateController_.getDefaultRate()) {
@@ -7257,19 +7260,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7257
7260
  if (hasAltStream) {
7258
7261
  let didDisableStream = false;
7259
7262
 
7263
+ let isTrickModeVideo = false;
7264
+
7260
7265
  for (const variant of this.manifest_.variants) {
7261
7266
  const candidate = variant[stream.type];
7262
7267
 
7263
- if (candidate && candidate.id === stream.id) {
7268
+ if (!candidate) {
7269
+ continue;
7270
+ }
7271
+
7272
+ if (candidate.id === stream.id) {
7264
7273
  variant.disabledUntilTime = (Date.now() / 1000) + disableTime;
7265
7274
  didDisableStream = true;
7266
7275
 
7267
7276
  shaka.log.v2(
7268
7277
  'Disabled stream ' + stream.type + ':' + stream.id +
7269
7278
  ' for ' + disableTime + ' seconds...');
7279
+ } else if (candidate.trickModeVideo &&
7280
+ candidate.trickModeVideo.id == stream.id) {
7281
+ isTrickModeVideo = true;
7270
7282
  }
7271
7283
  }
7272
7284
 
7285
+ if (!didDisableStream && isTrickModeVideo) {
7286
+ return false;
7287
+ }
7288
+
7273
7289
  goog.asserts.assert(didDisableStream, 'Must have disabled stream');
7274
7290
 
7275
7291
  this.checkVariantsTimer_.tickEvery(1);
@@ -7832,7 +7848,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7832
7848
  * @export
7833
7849
  */
7834
7850
  // eslint-disable-next-line no-useless-concat
7835
- shaka.Player.version = 'v4.10.12' + '-uncompiled'; // x-release-please-version
7851
+ shaka.Player.version = 'v4.10.13' + '-uncompiled'; // x-release-please-version
7836
7852
 
7837
7853
  // Initialize the deprecation system using the version string we just set
7838
7854
  // on the player.
@@ -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.10.12",
4
+ "version": "4.10.13",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [