shaka-player 4.9.34 → 4.9.35

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.
@@ -60,7 +60,7 @@ shaka.cea.Cea708Service = class {
60
60
  // Control codes are in 1 of 4 logical groups:
61
61
  // CL (C0, C2), CR (C1, C3), GL (G0, G2), GR (G1, G2).
62
62
  if (controlCode >= 0x00 && controlCode <= 0x1f) {
63
- return this.handleC0_(controlCode, pts);
63
+ return this.handleC0_(dtvccPacket, controlCode, pts);
64
64
  } else if (controlCode >= 0x80 && controlCode <= 0x9f) {
65
65
  return this.handleC1_(dtvccPacket, controlCode, pts);
66
66
  } else if (controlCode >= 0x1000 && controlCode <= 0x101f) {
@@ -153,17 +153,42 @@ shaka.cea.Cea708Service = class {
153
153
 
154
154
  /**
155
155
  * Handles C0 group data.
156
+ * @param {!shaka.cea.DtvccPacket} dtvccPacket
156
157
  * @param {number} controlCode
157
158
  * @param {number} pts
158
159
  * @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
159
160
  * @private
160
161
  */
161
- handleC0_(controlCode, pts) {
162
+ handleC0_(dtvccPacket, controlCode, pts) {
162
163
  // All these commands pertain to the current window, so ensure it exists.
163
164
  if (!this.currentWindow_) {
164
165
  return null;
165
166
  }
166
167
 
168
+ if (controlCode == 0x18) {
169
+ const firstByte = dtvccPacket.readByte().value;
170
+ const secondByte = dtvccPacket.readByte().value;
171
+
172
+ const isTextBlock = (b) => {
173
+ return (b >= 0x20 && b <= 0x7f) || (b >= 0xa0 && b <= 0xff);
174
+ };
175
+
176
+ if (isTextBlock(firstByte) && isTextBlock(secondByte)) {
177
+ const toHexString = (byteArray) => {
178
+ return byteArray.map((byte) => {
179
+ return ('0' + (byte & 0xFF).toString(16)).slice(-2);
180
+ }).join('');
181
+ };
182
+ const unicode = toHexString([firstByte, secondByte]);
183
+ // Takes a unicode hex string and creates a single character.
184
+ const char = String.fromCharCode(parseInt(unicode, 16));
185
+ this.currentWindow_.setCharacter(char);
186
+ return null;
187
+ } else {
188
+ dtvccPacket.rewind(2);
189
+ }
190
+ }
191
+
167
192
  const window = this.currentWindow_;
168
193
  let parsedClosedCaption = null;
169
194
 
@@ -161,6 +161,21 @@ shaka.cea.DtvccPacket = class {
161
161
  }
162
162
  this.pos_ += numBlocks;
163
163
  }
164
+
165
+ /**
166
+ * Rewinds the provided number of blocks in the buffer.
167
+ * @param {number} numBlocks
168
+ * @throws {!shaka.util.Error}
169
+ */
170
+ rewind(numBlocks) {
171
+ if (this.pos_ - numBlocks < 0) {
172
+ throw new shaka.util.Error(
173
+ shaka.util.Error.Severity.CRITICAL,
174
+ shaka.util.Error.Category.TEXT,
175
+ shaka.util.Error.Code.BUFFER_READ_OUT_OF_BOUNDS);
176
+ }
177
+ this.pos_ -= numBlocks;
178
+ }
164
179
  };
165
180
 
166
181
  /**
@@ -645,10 +645,11 @@ shaka.dash.DashParser = class {
645
645
  // that the complex logic of multi-Period eviction and period-flattening
646
646
  // is correct. See also:
647
647
  // https://github.com/shaka-project/shaka-player/issues/3169#issuecomment-823580634
648
+ const availabilityStart =
649
+ presentationTimeline.getSegmentAvailabilityStart();
648
650
  for (const stream of Object.values(this.streamMap_)) {
649
651
  if (stream.segmentIndex) {
650
- stream.segmentIndex.evict(
651
- presentationTimeline.getSegmentAvailabilityStart());
652
+ stream.segmentIndex.evict(availabilityStart);
652
653
  }
653
654
  }
654
655
  } else {
@@ -2122,8 +2123,12 @@ shaka.dash.DashParser = class {
2122
2123
  return element.attributes['schemeIdUri'] == expectedUri &&
2123
2124
  element.attributes['value'] == expectedValue;
2124
2125
  });
2126
+ const codecs = context.representation.codecs;
2127
+ // Detect the presence of Dolby Atmos audio content based on the codec and
2128
+ // bandwidth. Bandwidth above 384 kbps is a good indicator of Atmos content.
2129
+ const isAtmos = codecs.includes('ec-3') && context.bandwidth >= 384000;
2125
2130
  let spatialAudio = false;
2126
- if (hasJoc) {
2131
+ if (hasJoc || isAtmos) {
2127
2132
  spatialAudio = true;
2128
2133
  }
2129
2134
 
@@ -2157,7 +2162,6 @@ shaka.dash.DashParser = class {
2157
2162
 
2158
2163
  let hdr;
2159
2164
  const profiles = context.profiles;
2160
- const codecs = context.representation.codecs;
2161
2165
 
2162
2166
  const hevcHDR = 'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
2163
2167
  if (profiles.includes(hevcHDR) && (codecs.includes('hvc1.2.4.L153.B0') ||
@@ -279,10 +279,9 @@ shaka.dash.MpdUtils = class {
279
279
 
280
280
  const durationStr =
281
281
  MpdUtils.inheritAttribute(context, callback, 'duration');
282
- let segmentDuration = TXml.parsePositiveInt(durationStr || '');
283
- if (segmentDuration) {
284
- segmentDuration /= timescale;
285
- }
282
+ const unscaledSegmentDuration = TXml.parsePositiveInt(durationStr || '');
283
+ const segmentDuration = unscaledSegmentDuration ?
284
+ unscaledSegmentDuration / timescale : null;
286
285
 
287
286
  const startNumberStr =
288
287
  MpdUtils.inheritAttribute(context, callback, 'startNumber');
@@ -309,6 +308,7 @@ shaka.dash.MpdUtils = class {
309
308
  (unscaledPresentationTimeOffset / timescale) || 0;
310
309
  return {
311
310
  timescale: timescale,
311
+ unscaledSegmentDuration: unscaledSegmentDuration,
312
312
  segmentDuration: segmentDuration,
313
313
  startNumber: startNumber,
314
314
  scaledPresentationTimeOffset: scaledPresentationTimeOffset,
@@ -564,6 +564,7 @@ shaka.dash.MpdUtils = class {
564
564
  /**
565
565
  * @typedef {{
566
566
  * timescale: number,
567
+ * unscaledSegmentDuration: ?number,
567
568
  * segmentDuration: ?number,
568
569
  * startNumber: number,
569
570
  * scaledPresentationTimeOffset: number,
@@ -576,6 +577,8 @@ shaka.dash.MpdUtils = class {
576
577
  *
577
578
  * @property {number} timescale
578
579
  * The time-scale of the representation.
580
+ * @property {?number} unscaledSegmentDuration
581
+ * The duration of the segments in timescale units, if given.
579
582
  * @property {?number} segmentDuration
580
583
  * The duration of the segments in seconds, if given.
581
584
  * @property {number} startNumber
@@ -258,6 +258,7 @@ shaka.dash.SegmentTemplate = class {
258
258
  context, SegmentTemplate.fromInheritance_, 'index');
259
259
 
260
260
  return {
261
+ unscaledSegmentDuration: segmentInfo.unscaledSegmentDuration,
261
262
  segmentDuration: segmentInfo.segmentDuration,
262
263
  timescale: segmentInfo.timescale,
263
264
  startNumber: segmentInfo.startNumber,
@@ -304,10 +305,12 @@ shaka.dash.SegmentTemplate = class {
304
305
  if (info.indexTemplate) {
305
306
  shaka.log.info('Using the index URL template by default.');
306
307
  info.timeline = null;
308
+ info.unscaledSegmentDuration = null;
307
309
  info.segmentDuration = null;
308
310
  } else {
309
311
  goog.asserts.assert(info.timeline, 'There should be a timeline');
310
312
  shaka.log.info('Using the SegmentTimeline by default.');
313
+ info.unscaledSegmentDuration = null;
311
314
  info.segmentDuration = null;
312
315
  }
313
316
  }
@@ -399,7 +402,6 @@ shaka.dash.SegmentTemplate = class {
399
402
  segmentDuration != null, 'Segment duration must not be null!');
400
403
 
401
404
  const startNumber = info.startNumber;
402
- const timescale = info.timescale;
403
405
 
404
406
  const template = info.mediaTemplate;
405
407
  const bandwidth = context.bandwidth || null;
@@ -482,20 +484,18 @@ shaka.dash.SegmentTemplate = class {
482
484
  const positionWithinPeriod = position - startNumber;
483
485
  const segmentPeriodTime = positionWithinPeriod * segmentDuration;
484
486
 
485
- // What will appear in the actual segment files. The media timestamp is
486
- // what is expected in the $Time$ template.
487
- const segmentMediaTime = segmentPeriodTime +
488
- info.scaledPresentationTimeOffset;
487
+ const unscaledSegmentDuration = info.unscaledSegmentDuration;
488
+ goog.asserts.assert(unscaledSegmentDuration != null,
489
+ 'Segment duration must not be null!');
489
490
 
490
- const getUris = () => {
491
- let time = segmentMediaTime * timescale;
492
- if ('BigInt' in window && time > Number.MAX_SAFE_INTEGER) {
493
- time = BigInt(segmentMediaTime) * BigInt(timescale);
494
- }
495
- const mediaUri = MpdUtils.fillUriTemplate(
496
- template, id, position, /* subNumber= */ null, bandwidth, time);
497
- return ManifestParserUtils.resolveUris(getBaseUris(), [mediaUri]);
498
- };
491
+ // The original media timestamp from the timeline is what is expected in
492
+ // the $Time$ template. (Or based on duration, in this case.) It should
493
+ // not be adjusted with presentationTimeOffset or the Period start.
494
+ let timeReplacement = positionWithinPeriod * unscaledSegmentDuration;
495
+ if ('BigInt' in window && timeReplacement > Number.MAX_SAFE_INTEGER) {
496
+ timeReplacement =
497
+ BigInt(positionWithinPeriod) * BigInt(unscaledSegmentDuration);
498
+ }
499
499
 
500
500
  // Relative to the presentation.
501
501
  const segmentStart = segmentPeriodTime + periodStart;
@@ -509,6 +509,13 @@ shaka.dash.SegmentTemplate = class {
509
509
  goog.asserts.assert(segmentStart < segmentEnd,
510
510
  'Generated a segment outside of the period!');
511
511
 
512
+ const getUris = () => {
513
+ const mediaUri = MpdUtils.fillUriTemplate(
514
+ template, id, position, /* subNumber= */ null, bandwidth,
515
+ timeReplacement);
516
+ return ManifestParserUtils.resolveUris(getBaseUris(), [mediaUri]);
517
+ };
518
+
512
519
  const ref = new shaka.media.SegmentReference(
513
520
  segmentStart,
514
521
  segmentEnd,
@@ -925,8 +932,15 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
925
932
  if (!ref) {
926
933
  const range = this.templateInfo_.timeline[correctedPosition];
927
934
  const segmentReplacement = range.segmentPosition;
928
- const timeReplacement = this.templateInfo_
929
- .unscaledPresentationTimeOffset + range.unscaledStart;
935
+
936
+ // The original media timestamp from the timeline is what is expected in
937
+ // the $Time$ template. It should not be adjusted with
938
+ // presentationTimeOffset or the Period start, but
939
+ // unscaledPresentationTimeOffset was already subtracted from the times
940
+ // in timeline.
941
+ const timeReplacement = range.unscaledStart +
942
+ this.templateInfo_.unscaledPresentationTimeOffset;
943
+
930
944
  const timestampOffset = this.periodStart_ -
931
945
  this.templateInfo_.scaledPresentationTimeOffset;
932
946
  const trueSegmentEnd = this.periodStart_ + range.end;
@@ -1068,6 +1082,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
1068
1082
  /**
1069
1083
  * @typedef {{
1070
1084
  * timescale: number,
1085
+ * unscaledSegmentDuration: ?number,
1071
1086
  * segmentDuration: ?number,
1072
1087
  * startNumber: number,
1073
1088
  * scaledPresentationTimeOffset: number,
@@ -1084,6 +1099,8 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
1084
1099
  *
1085
1100
  * @property {number} timescale
1086
1101
  * The time-scale of the representation.
1102
+ * @property {?number} unscaledSegmentDuration
1103
+ * The duration of the segments in seconds, in timescale units.
1087
1104
  * @property {?number} segmentDuration
1088
1105
  * The duration of the segments in seconds, if given.
1089
1106
  * @property {number} startNumber
@@ -1013,7 +1013,8 @@ shaka.hls.HlsParser = class {
1013
1013
 
1014
1014
  if (extension == 'ts' || contentMimeType == 'video/mp2t') {
1015
1015
  const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromTs(
1016
- response.data, this.config_.disableAudio, this.config_.disableVideo);
1016
+ response.data, this.config_.disableAudio, this.config_.disableVideo,
1017
+ this.config_.disableText);
1017
1018
  if (basicInfo) {
1018
1019
  return basicInfo;
1019
1020
  }
@@ -1023,7 +1024,7 @@ shaka.hls.HlsParser = class {
1023
1024
  contentMimeType == 'audio/mp4' ||
1024
1025
  contentMimeType == 'video/iso.segment') {
1025
1026
  const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromMp4(
1026
- initData, response.data);
1027
+ initData, response.data, this.config_.disableText);
1027
1028
  if (basicInfo) {
1028
1029
  return basicInfo;
1029
1030
  }
@@ -313,4 +313,5 @@ shaka.hls.ManifestTextParser.SEGMENT_TAGS = [
313
313
  'EXT-X-DATERANGE',
314
314
  'EXT-X-MAP',
315
315
  'EXT-X-GAP',
316
+ 'EXT-X-TILES',
316
317
  ];
@@ -47,11 +47,12 @@ shaka.media.SegmentUtils = class {
47
47
 
48
48
  /**
49
49
  * @param {!BufferSource} data
50
- * @param {boolean=} disableAudio
51
- * @param {boolean=} disableVideo
50
+ * @param {boolean} disableAudio
51
+ * @param {boolean} disableVideo
52
+ * @param {boolean} disableText
52
53
  * @return {?shaka.media.SegmentUtils.BasicInfo}
53
54
  */
54
- static getBasicInfoFromTs(data, disableAudio = false, disableVideo = false) {
55
+ static getBasicInfoFromTs(data, disableAudio, disableVideo, disableText) {
55
56
  const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
56
57
  const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
57
58
  const tsCodecs = tsParser.getCodecs();
@@ -63,24 +64,34 @@ shaka.media.SegmentUtils = class {
63
64
  switch (tsCodecs.audio) {
64
65
  case 'aac':
65
66
  case 'aac-loas':
66
- codecs.push('mp4a.40.2');
67
- hasAudio = true;
67
+ if (tsParser.getAudioData().length) {
68
+ codecs.push('mp4a.40.2');
69
+ hasAudio = true;
70
+ }
68
71
  break;
69
72
  case 'mp3':
70
- codecs.push('mp4a.40.34');
71
- hasAudio = true;
73
+ if (tsParser.getAudioData().length) {
74
+ codecs.push('mp4a.40.34');
75
+ hasAudio = true;
76
+ }
72
77
  break;
73
78
  case 'ac3':
74
- codecs.push('ac-3');
75
- hasAudio = true;
79
+ if (tsParser.getAudioData().length) {
80
+ codecs.push('ac-3');
81
+ hasAudio = true;
82
+ }
76
83
  break;
77
84
  case 'ec3':
78
- codecs.push('ec-3');
79
- hasAudio = true;
85
+ if (tsParser.getAudioData().length) {
86
+ codecs.push('ec-3');
87
+ hasAudio = true;
88
+ }
80
89
  break;
81
90
  case 'opus':
82
- codecs.push('opus');
83
- hasAudio = true;
91
+ if (tsParser.getAudioData().length) {
92
+ codecs.push('opus');
93
+ hasAudio = true;
94
+ }
84
95
  break;
85
96
  }
86
97
  }
@@ -113,7 +124,7 @@ shaka.media.SegmentUtils = class {
113
124
  }
114
125
  const onlyAudio = hasAudio && !hasVideo;
115
126
  const closedCaptions = new Map();
116
- if (hasVideo) {
127
+ if (hasVideo && !disableText) {
117
128
  const captionParser = new shaka.media.ClosedCaptionParser('video/mp2t');
118
129
  captionParser.parseFrom(data);
119
130
  for (const stream of captionParser.getStreams()) {
@@ -139,9 +150,10 @@ shaka.media.SegmentUtils = class {
139
150
  /**
140
151
  * @param {?BufferSource} initData
141
152
  * @param {!BufferSource} data
153
+ * @param {boolean} disableText
142
154
  * @return {?shaka.media.SegmentUtils.BasicInfo}
143
155
  */
144
- static getBasicInfoFromMp4(initData, data) {
156
+ static getBasicInfoFromMp4(initData, data, disableText) {
145
157
  const Mp4Parser = shaka.util.Mp4Parser;
146
158
  const SegmentUtils = shaka.media.SegmentUtils;
147
159
 
@@ -382,7 +394,7 @@ shaka.media.SegmentUtils = class {
382
394
  }
383
395
  const onlyAudio = hasAudio && !hasVideo;
384
396
  const closedCaptions = new Map();
385
- if (hasVideo) {
397
+ if (hasVideo && !disableText) {
386
398
  const captionParser = new shaka.media.ClosedCaptionParser('video/mp4');
387
399
  if (initData) {
388
400
  captionParser.init(initData);
@@ -2485,7 +2485,7 @@ shaka.media.StreamingEngine = class {
2485
2485
  const segmentIndex = mediaState.stream.segmentIndex;
2486
2486
  if (segmentIndex instanceof shaka.media.MetaSegmentIndex) {
2487
2487
  segmentIndex.evict(
2488
- this.manifest_.presentationTimeline.getSeekRangeStart());
2488
+ this.manifest_.presentationTimeline.getSegmentAvailabilityStart());
2489
2489
  }
2490
2490
 
2491
2491
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
@@ -104,7 +104,7 @@ shaka.net.HttpFetchPlugin = class {
104
104
  const fetch = shaka.net.HttpFetchPlugin.fetch_;
105
105
  const ReadableStream = shaka.net.HttpFetchPlugin.ReadableStream_;
106
106
  let response;
107
- let arrayBuffer;
107
+ let arrayBuffer = new ArrayBuffer(0);
108
108
  let loaded = 0;
109
109
  let lastLoaded = 0;
110
110
 
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.net.HttpPluginUtils');
8
8
 
9
+ goog.require('goog.asserts');
9
10
  goog.require('shaka.log');
10
11
  goog.require('shaka.util.Error');
11
12
  goog.require('shaka.util.StringUtils');
@@ -27,6 +28,8 @@ shaka.net.HttpPluginUtils = class {
27
28
  * @return {!shaka.extern.Response}
28
29
  */
29
30
  static makeResponse(headers, data, status, uri, responseURL, requestType) {
31
+ goog.asserts.assert(data, 'Data should be non-null!');
32
+
30
33
  if (status >= 200 && status <= 299 && status != 202) {
31
34
  // Most 2xx HTTP codes are success cases.
32
35
  /** @type {shaka.extern.Response} */
package/lib/player.js CHANGED
@@ -7542,7 +7542,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7542
7542
  if (!this.video_ || this.video_.ended) {
7543
7543
  return true;
7544
7544
  }
7545
- return this.video_.currentTime >= this.seekRange().end;
7545
+ return !this.isLive() && this.video_.currentTime >= this.seekRange().end;
7546
7546
  }
7547
7547
  };
7548
7548
 
@@ -7585,7 +7585,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7585
7585
  * @export
7586
7586
  */
7587
7587
  // eslint-disable-next-line no-useless-concat, max-len
7588
- shaka.Player.version = 'v4.9.34' + '-uncompiled'; // x-release-please-version
7588
+ shaka.Player.version = 'v4.9.35' + '-uncompiled'; // x-release-please-version
7589
7589
 
7590
7590
  // Initialize the deprecation system using the version string we just set
7591
7591
  // on the player.
@@ -517,7 +517,13 @@ shaka.util.PlayerConfiguration = class {
517
517
  if (hdrLevel == 'AUTO') {
518
518
  // Auto detect the ideal HDR level.
519
519
  if (window.matchMedia('(color-gamut: p3)').matches) {
520
- hdrLevel = 'PQ';
520
+ const someHLG = tracks.some((track) => {
521
+ if (track.hdr && track.hdr == 'HLG') {
522
+ return true;
523
+ }
524
+ return false;
525
+ });
526
+ hdrLevel = someHLG ? 'HLG' : 'PQ';
521
527
  } else {
522
528
  hdrLevel = 'SDR';
523
529
  }
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.34",
4
+ "version": "4.9.35",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [