shaka-player 4.2.4 → 4.2.5

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.
Files changed (62) hide show
  1. package/AUTHORS +1 -0
  2. package/CHANGELOG.md +24 -0
  3. package/CONTRIBUTORS +1 -0
  4. package/demo/index.html +1 -1
  5. package/dist/demo.compiled.debug.js +64 -64
  6. package/dist/demo.compiled.debug.map +2 -2
  7. package/dist/demo.compiled.js +64 -64
  8. package/dist/demo.compiled.map +2 -2
  9. package/dist/deps.js +3 -2
  10. package/dist/locales.js +32 -32
  11. package/dist/receiver.compiled.debug.js +12 -12
  12. package/dist/receiver.compiled.debug.map +2 -2
  13. package/dist/receiver.compiled.js +12 -12
  14. package/dist/receiver.compiled.map +2 -2
  15. package/dist/shaka-player.compiled.d.ts +10 -0
  16. package/dist/shaka-player.compiled.debug.d.ts +10 -0
  17. package/dist/shaka-player.compiled.debug.externs.js +93 -79
  18. package/dist/shaka-player.compiled.debug.js +557 -550
  19. package/dist/shaka-player.compiled.debug.map +4 -4
  20. package/dist/shaka-player.compiled.externs.js +93 -79
  21. package/dist/shaka-player.compiled.js +806 -806
  22. package/dist/shaka-player.compiled.map +3 -3
  23. package/dist/shaka-player.ui.d.ts +10 -0
  24. package/dist/shaka-player.ui.debug.d.ts +10 -0
  25. package/dist/shaka-player.ui.debug.externs.js +93 -79
  26. package/dist/shaka-player.ui.debug.js +583 -576
  27. package/dist/shaka-player.ui.debug.map +4 -4
  28. package/dist/shaka-player.ui.externs.js +93 -79
  29. package/dist/shaka-player.ui.js +953 -953
  30. package/dist/shaka-player.ui.map +3 -3
  31. package/docs/tutorials/ui.md +7 -7
  32. package/docs/tutorials/welcome.md +2 -2
  33. package/externs/shaka/player.js +2 -1
  34. package/lib/cea/cea708_window.js +2 -2
  35. package/lib/dash/segment_template.js +1 -1
  36. package/lib/hls/hls_parser.js +12 -2
  37. package/lib/media/content_workarounds.js +12 -0
  38. package/lib/player.js +1 -1
  39. package/lib/polyfill/abort_controller.js +143 -0
  40. package/lib/polyfill/media_capabilities.js +59 -9
  41. package/lib/text/ttml_text_parser.js +32 -7
  42. package/lib/text/ui_text_displayer.js +22 -5
  43. package/lib/util/platform.js +9 -1
  44. package/lib/util/player_configuration.js +10 -3
  45. package/lib/util/stream_utils.js +30 -10
  46. package/package.json +1 -1
  47. package/shaka-player.uncompiled.js +1 -0
  48. package/test/dash/dash_parser_manifest_unit.js +1 -0
  49. package/test/hls/hls_parser_unit.js +1 -0
  50. package/test/media/drm_engine_unit.js +11 -1
  51. package/test/media/media_source_engine_integration.js +78 -2
  52. package/test/polyfill/media_capabilities_unit.js +261 -0
  53. package/test/test/util/stream_generator.js +212 -8
  54. package/test/test/util/test_scheme.js +3 -2
  55. package/test/test/util/util.js +3 -2
  56. package/test/text/mp4_ttml_parser_unit.js +6 -4
  57. package/test/text/ttml_text_parser_unit.js +119 -86
  58. package/test/text/ui_text_displayer_unit.js +76 -0
  59. package/test/ui/text_displayer_layout_unit.js +14 -0
  60. package/test/util/stream_utils_unit.js +18 -0
  61. package/test/util/xml_utils_unit.js +6 -1
  62. package/ui/controls.js +45 -36
@@ -51,19 +51,36 @@ shaka.test.IStreamGenerator = class {
51
51
  * @implements {shaka.test.IStreamGenerator}
52
52
  */
53
53
  shaka.test.TSVodStreamGenerator = class {
54
- /** @param {string} segmentUri The URI of the segment. */
55
- constructor(segmentUri) {
54
+ /**
55
+ * @param {string} segmentUri The URI of the segment.
56
+ * @param {number} segmentDuration The duration of a single segment, in
57
+ * seconds.
58
+ */
59
+ constructor(segmentUri, segmentDuration) {
56
60
  /** @private {string} */
57
61
  this.segmentUri_ = segmentUri;
58
62
 
63
+ /**
64
+ * Internally, everything is in timescale units.
65
+ * @private {number}
66
+ */
67
+ this.segmentDuration_ = segmentDuration * 90000;
68
+
69
+ /** @private {!Array.<{offset: number, dts: ?number, pts: ?number}>} */
70
+ this.timestamps_ = [];
71
+
72
+ /** @private {number} */
73
+ this.timestampOffset_ = Infinity;
74
+
59
75
  /** @private {ArrayBuffer} */
60
- this.segment_ = null;
76
+ this.segmentTemplate_ = null;
61
77
  }
62
78
 
63
79
  /** @override */
64
80
  async init() {
65
81
  const segment = await shaka.test.Util.fetch(this.segmentUri_);
66
- this.segment_ = segment;
82
+ this.segmentTemplate_ = segment;
83
+ this.parseSegment_();
67
84
  }
68
85
 
69
86
  /** @override */
@@ -75,11 +92,198 @@ shaka.test.TSVodStreamGenerator = class {
75
92
  /** @override */
76
93
  getSegment(position, wallClockTime) {
77
94
  goog.asserts.assert(
78
- this.segment_,
95
+ this.segmentTemplate_,
79
96
  'init() must be called before getSegment().');
80
- // TODO: complete implementation; this should change the timestamps based on
81
- // the given wallClockTime, so as to simulate a long stream.
82
- return this.segment_;
97
+
98
+ // This will create a copy of the given buffer.
99
+ const buffer = shaka.util.Uint8ArrayUtils.concat(this.segmentTemplate_);
100
+
101
+ for (const timestampMetadata of this.timestamps_) {
102
+ this.setTimestamp_(buffer, timestampMetadata, position);
103
+ }
104
+
105
+ return shaka.util.BufferUtils.toArrayBuffer(buffer);
106
+ }
107
+
108
+ /** @private */
109
+ parseSegment_() {
110
+ goog.asserts.assert(
111
+ this.segmentTemplate_,
112
+ 'init() must be called before parseSegment_().');
113
+
114
+ // A TS segment can contain a timestamp in each 188-byte PES packet.
115
+ // Find all the timestamps and their offsets, and cache them.
116
+ const dataView = shaka.util.BufferUtils.toDataView(this.segmentTemplate_);
117
+ const reader = new shaka.util.DataViewReader(
118
+ dataView, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
119
+
120
+ // Read each TS packet (188 bytes).
121
+ for (let i = 0; i < reader.getLength(); i += 188) {
122
+ reader.seek(i);
123
+ const syncByte = reader.readUint8();
124
+ goog.asserts.assert(syncByte == 0x47, 'Sync byte not found!');
125
+
126
+ const flagsAndPacketId = reader.readUint16();
127
+ const packetId = flagsAndPacketId & 0x1fff;
128
+ if (packetId == 0x1fff) {
129
+ // A "null" TS packet. Skip it.
130
+ continue;
131
+ }
132
+
133
+ const hasPesPacket = flagsAndPacketId & 0x4000;
134
+ if (!hasPesPacket) {
135
+ // Not a PES packet. Skip it.
136
+ continue;
137
+ }
138
+
139
+ const flags = reader.readUint8();
140
+ const adaptationFieldControl = (flags & 0x30) >> 4;
141
+ if (adaptationFieldControl == 0 /* reserved */ ||
142
+ adaptationFieldControl == 2 /* adaptation field, no payload */) {
143
+ throw new Error(
144
+ `Unexpected adaptation field control: ${adaptationFieldControl}`);
145
+ }
146
+
147
+ if (adaptationFieldControl == 3) {
148
+ // Skip over adaptation field.
149
+ const length = reader.readUint8();
150
+ reader.skip(length);
151
+ }
152
+
153
+ // Now we come to the PES header (hopefully).
154
+ // Format reference: https://bit.ly/TsPES
155
+ const startCode = reader.readUint32();
156
+ const startCodePrefix = startCode >> 8;
157
+ if (startCodePrefix != 1) {
158
+ // Not a PES packet. Skip it.
159
+ continue;
160
+ }
161
+
162
+ // Skip the 16-bit PES length and the first 8 bits of the optional header.
163
+ reader.skip(3);
164
+ // The next 8 bits contain flags about DTS & PTS.
165
+ const ptsDtsIndicator = reader.readUint8() >> 6;
166
+ if (ptsDtsIndicator == 0 /* no timestamp */ ||
167
+ ptsDtsIndicator == 1 /* forbidden */) {
168
+ throw new Error(`Unexpected PTS/DTS flag: ${ptsDtsIndicator}`);
169
+ }
170
+
171
+ const pesHeaderLengthRemaining = reader.readUint8();
172
+ if (pesHeaderLengthRemaining == 0) {
173
+ throw new Error(`Malformed TS, no room for PTS/DTS!`);
174
+ }
175
+
176
+ const offset = reader.getPosition();
177
+ let pts = null;
178
+ let dts = null;
179
+
180
+ // Parse timestamps and keep track of the minimum timestamp seen, to use
181
+ // as a timestamp offset when we calculate new timestamps for a segment.
182
+ if (ptsDtsIndicator == 2 /* PTS only */) {
183
+ goog.asserts.assert(pesHeaderLengthRemaining == 5, 'Bad PES header?');
184
+ pts = this.parseTimestamp_(reader);
185
+ this.timestampOffset_ = Math.min(this.timestampOffset_, pts);
186
+ } else if (ptsDtsIndicator == 3 /* PTS and DTS */) {
187
+ goog.asserts.assert(pesHeaderLengthRemaining == 10, 'Bad PES header?');
188
+ pts = this.parseTimestamp_(reader);
189
+ dts = this.parseTimestamp_(reader);
190
+ this.timestampOffset_ = Math.min(this.timestampOffset_, pts, dts);
191
+ }
192
+
193
+ this.timestamps_.push({
194
+ offset,
195
+ pts,
196
+ dts,
197
+ });
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @param {!shaka.util.DataViewReader} reader
203
+ * @return {number}
204
+ * @private
205
+ */
206
+ parseTimestamp_(reader) {
207
+ const pts0 = reader.readUint8();
208
+ const pts1 = reader.readUint16();
209
+ const pts2 = reader.readUint16();
210
+ // Reconstruct 33-bit PTS from the 5-byte, padded structure.
211
+ const ptsHigh3 = (pts0 & 0x0e) >> 1;
212
+ const ptsLow30 = ((pts1 & 0xfffe) << 14) | ((pts2 & 0xfffe) >> 1);
213
+ // Reconstruct the PTS as a float. Avoid bitwise operations to combine
214
+ // because bitwise ops treat the values as 32-bit ints.
215
+ return ptsHigh3 * (1 << 30) + ptsLow30;
216
+ }
217
+
218
+ /**
219
+ * @param {!Uint8Array} buffer
220
+ * @param {{offset: number, pts: ?number, dts: ?number}} timestampMetadata
221
+ * @param {number} position
222
+ * @private
223
+ */
224
+ setTimestamp_(buffer, timestampMetadata, position) {
225
+ // Wikipedia: "If only PTS is present, this is done by catenating 0010 ...
226
+ // If both PTS and DTS are present, first 4 bits are 0011 and first 4 bits
227
+ // for DTS are 0001."
228
+ const ptsHeader = timestampMetadata.dts == null ? 0b0010 : 0b0011;
229
+ const dtsHeader = 0b0001;
230
+
231
+ const segmentTime = this.segmentDuration_ * position;
232
+
233
+ if (timestampMetadata.pts != null) {
234
+ const pts = timestampMetadata.pts - this.timestampOffset_ + segmentTime;
235
+ this.writeTimestamp_(
236
+ buffer, timestampMetadata.offset, ptsHeader, this.overflow_(pts));
237
+ }
238
+ if (timestampMetadata.dts != null) {
239
+ const dts = timestampMetadata.dts - this.timestampOffset_ + segmentTime;
240
+ this.writeTimestamp_(
241
+ buffer, timestampMetadata.offset + 5, dtsHeader, this.overflow_(dts));
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Write a timestamp (PTS or DTS) to a specific place in the buffer, with a
247
+ * specific header, in the PES timestamp layout.
248
+ *
249
+ * @param {!Uint8Array} buffer
250
+ * @param {number} offset Where to begin writing
251
+ * @param {number} header 4-bit header for the timestamp
252
+ * @param {number} timestamp the actual timestamp
253
+ * @private
254
+ */
255
+ writeTimestamp_(buffer, offset, header, timestamp) {
256
+ // The 33 bit timestamp is split into parts, then packed into 40 bits
257
+ // (5 bytes). Wikipedia phrases the layout as: "0010b, most significant 3
258
+ // bits from PTS, 1, following next 15 bits, 1, rest 15 bits and 1."
259
+ // https://en.wikipedia.org/wiki/Packetized_elementary_stream
260
+ const top3Bits = timestamp >> 30;
261
+ const next15Bits = (timestamp >> 15) & 0x7fff;
262
+ const last15Bits = timestamp & 0x7fff;
263
+
264
+ buffer[offset + 0] = (header << 4) | (top3Bits << 1) | 1;
265
+ buffer[offset + 1] = next15Bits >> 7;
266
+ buffer[offset + 2] = ((next15Bits & 0x7f) << 1) | 1;
267
+ buffer[offset + 3] = last15Bits >> 7;
268
+ buffer[offset + 4] = ((last15Bits & 0x7f) << 1) | 1;
269
+ }
270
+
271
+ /**
272
+ * Handle PES timestamp overflow (33 bits).
273
+ *
274
+ * @param {number} timestamp
275
+ * @return {number} The same timestamp, with TS overflow applied.
276
+ * @private
277
+ */
278
+ overflow_(timestamp) {
279
+ // NOTE: You can't get 2^33 with a bit-shift, because JavaScript will treat
280
+ // the number as a 32-bit int. Use Math.pow() instead. The result is
281
+ // still accurate as an integer.
282
+ const limit = Math.pow(2, 33);
283
+ while (timestamp >= limit) {
284
+ timestamp -= limit;
285
+ }
286
+ return timestamp;
83
287
  }
84
288
  };
85
289
 
@@ -172,7 +172,8 @@ shaka.test.TestScheme = class {
172
172
  */
173
173
  function createStreamGenerator(metadata) {
174
174
  if (metadata.segmentUri.includes('.ts')) {
175
- return new shaka.test.TSVodStreamGenerator(metadata.segmentUri);
175
+ return new shaka.test.TSVodStreamGenerator(
176
+ metadata.segmentUri, metadata.segmentDuration);
176
177
  }
177
178
  return new shaka.test.Mp4VodStreamGenerator(
178
179
  metadata.initSegmentUri, metadata.mdhdOffset, metadata.segmentUri,
@@ -623,7 +624,7 @@ shaka.test.TestScheme.DATA = {
623
624
  segmentUri: '/base/test/test/assets/captions-test.ts',
624
625
  mimeType: 'video/mp2t',
625
626
  codecs: 'avc1.64001e',
626
- segmentDuration: 2,
627
+ segmentDuration: 20, // yes, this is accurate
627
628
  },
628
629
  text: {
629
630
  mimeType: 'application/cea-608',
@@ -89,10 +89,11 @@ shaka.test.Util = class {
89
89
  * given value.
90
90
  *
91
91
  * @param {number} val
92
+ * @param {number=} maxDelta
92
93
  * @return {number}
93
94
  */
94
- static closeTo(val) {
95
- const E = 0.000001;
95
+ static closeTo(val, maxDelta = 0.000001) {
96
+ const E = /** @type {number} */(maxDelta);
96
97
  return /** @type {number} */(/** @type {?} */({
97
98
  asymmetricMatch: (other) => other >= val - E && other <= val + E,
98
99
  jasmineToString: () => '<closeTo: ' + val + '>',
@@ -41,7 +41,8 @@ describe('Mp4TtmlParser', () => {
41
41
  it('handles media segments with multiple mdats', () => {
42
42
  const parser = new shaka.text.Mp4TtmlParser();
43
43
  parser.parseInit(ttmlInitSegment);
44
- const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
44
+ const time =
45
+ {periodStart: 0, segmentStart: 0, segmentEnd: 60, vttOffset: 0};
45
46
  const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time);
46
47
  // Bodies.
47
48
  expect(ret.length).toBe(2);
@@ -55,9 +56,9 @@ describe('Mp4TtmlParser', () => {
55
56
 
56
57
  it('accounts for offset', () => {
57
58
  const time1 =
58
- {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
59
+ {periodStart: 0, segmentStart: 0, segmentEnd: 70, vttOffset: 0};
59
60
  const time2 =
60
- {periodStart: 7, segmentStart: 0, segmentEnd: 0, vttOffset: 7};
61
+ {periodStart: 7, segmentStart: 0, segmentEnd: 70, vttOffset: 7};
61
62
 
62
63
  const parser = new shaka.text.Mp4TtmlParser();
63
64
  parser.parseInit(ttmlInitSegment);
@@ -161,7 +162,8 @@ describe('Mp4TtmlParser', () => {
161
162
  ];
162
163
  const parser = new shaka.text.Mp4TtmlParser();
163
164
  parser.parseInit(ttmlInitSegment);
164
- const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
165
+ const time =
166
+ {periodStart: 0, segmentStart: 0, segmentEnd: 60, vttOffset: 0};
165
167
  const result = parser.parseMedia(ttmlSegment, time);
166
168
  shaka.test.TtmlUtils.verifyHelper(
167
169
  cues, result, {startTime: 23, endTime: 53.5});