shaka-player 4.1.0 → 4.1.3

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 (162) hide show
  1. package/.github/workflows/build-and-test.yaml +67 -15
  2. package/.github/workflows/compute-incremental-coverage.py +251 -0
  3. package/.github/workflows/report-incremental-coverage.yaml +39 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +44 -1
  5. package/AUTHORS +2 -0
  6. package/CHANGELOG.md +42 -0
  7. package/CONTRIBUTORS +4 -0
  8. package/README.md +20 -6
  9. package/demo/common/assets.js +1 -1
  10. package/demo/index.html +1 -3
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +1 -1
  13. package/dist/demo.compiled.debug.js +3 -3
  14. package/dist/demo.compiled.debug.map +2 -2
  15. package/dist/demo.compiled.js +3 -3
  16. package/dist/demo.compiled.map +2 -2
  17. package/dist/deps.js +3 -3
  18. package/dist/locales.js +29 -29
  19. package/dist/receiver.compiled.debug.js +1 -1
  20. package/dist/receiver.compiled.debug.map +1 -1
  21. package/dist/receiver.compiled.js +1 -1
  22. package/dist/receiver.compiled.map +1 -1
  23. package/dist/shaka-player.compiled.d.ts +12 -1
  24. package/dist/shaka-player.compiled.debug.d.ts +12 -1
  25. package/dist/shaka-player.compiled.debug.externs.js +52 -40
  26. package/dist/shaka-player.compiled.debug.js +270 -257
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +52 -40
  29. package/dist/shaka-player.compiled.js +754 -746
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +12 -1
  32. package/dist/shaka-player.ui.debug.d.ts +12 -1
  33. package/dist/shaka-player.ui.debug.externs.js +52 -40
  34. package/dist/shaka-player.ui.debug.js +296 -283
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +52 -40
  37. package/dist/shaka-player.ui.js +874 -867
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/ui.md +6 -6
  40. package/docs/tutorials/upgrade.md +1 -0
  41. package/docs/tutorials/welcome.md +6 -3
  42. package/externs/shaka/player.js +4 -2
  43. package/karma.conf.js +3 -11
  44. package/lib/ads/client_side_ad_manager.js +1 -1
  45. package/lib/dash/dash_parser.js +5 -0
  46. package/lib/hls/hls_parser.js +183 -70
  47. package/lib/media/drm_engine.js +1 -1
  48. package/lib/media/media_source_engine.js +57 -24
  49. package/lib/media/presentation_timeline.js +24 -1
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +4 -4
  53. package/lib/media/time_ranges_utils.js +46 -0
  54. package/lib/player.js +1 -6
  55. package/lib/polyfill/media_capabilities.js +9 -0
  56. package/lib/polyfill/videoplaybackquality.js +3 -1
  57. package/lib/text/cue.js +0 -5
  58. package/lib/text/simple_text_displayer.js +21 -9
  59. package/lib/text/ui_text_displayer.js +83 -19
  60. package/lib/util/buffer_utils.js +3 -0
  61. package/lib/util/object_utils.js +1 -0
  62. package/lib/util/platform.js +19 -1
  63. package/lib/util/stream_utils.js +24 -3
  64. package/lib/util/string_utils.js +102 -13
  65. package/maintained-branches.md +15 -0
  66. package/package.json +3 -5
  67. package/test/hls/hls_parser_unit.js +102 -8
  68. package/test/media/drm_engine_integration.js +24 -8
  69. package/test/media/media_source_engine_integration.js +24 -10
  70. package/test/media/media_source_engine_unit.js +53 -38
  71. package/test/media/streaming_engine_unit.js +28 -27
  72. package/test/test/util/fake_media_source_engine.js +10 -9
  73. package/test/text/simple_text_displayer_unit.js +27 -0
  74. package/test/text/ui_text_displayer_unit.js +65 -0
  75. package/test/util/string_utils_unit.js +46 -8
  76. package/ui/text_selection.js +2 -2
  77. package/app-engine/README.md +0 -9
  78. package/app-engine/demo-version-index/README.md +0 -14
  79. package/app-engine/demo-version-index/app.yaml +0 -28
  80. package/app-engine/demo-version-index/generate.py +0 -164
  81. package/app-engine/demo-version-index/main.py +0 -11
  82. package/app-engine/demo-version-index/requirements.txt +0 -5
  83. package/app-engine/demo-version-index/templates/index.html +0 -114
  84. package/app-engine/shaka-player-demo/README.md +0 -18
  85. package/app-engine/shaka-player-demo/app.yaml +0 -61
  86. package/app-engine/shaka-player-demo/main.py +0 -95
  87. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  88. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  89. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  90. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  91. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  92. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  93. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  94. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  95. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  96. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  97. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  98. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  99. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  100. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  101. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  102. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  103. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  104. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  105. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  106. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  107. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  108. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  109. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  110. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  111. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  112. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  113. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  114. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  115. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  116. package/app-engine/shaka-player-demo/posters.py +0 -110
  117. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  118. package/app-engine/shaka-player-demo/time.txt +0 -1
  119. package/build/README.md +0 -127
  120. package/build/__init__.py +0 -0
  121. package/build/all.py +0 -150
  122. package/build/apps.py +0 -186
  123. package/build/build.py +0 -392
  124. package/build/check.py +0 -354
  125. package/build/checkversion.py +0 -84
  126. package/build/compiler.py +0 -513
  127. package/build/conformance.textproto +0 -335
  128. package/build/docker/Dockerfile +0 -20
  129. package/build/docker/docker-compose.yml +0 -10
  130. package/build/docs.py +0 -54
  131. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  132. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  133. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  134. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  135. package/build/gendeps.py +0 -71
  136. package/build/generateExterns.js +0 -867
  137. package/build/generateLocalizations.py +0 -262
  138. package/build/generateTsDefs.py +0 -137
  139. package/build/install-linux-prereqs.sh +0 -136
  140. package/build/license-header +0 -5
  141. package/build/misspellings.txt +0 -39
  142. package/build/pixelsChanged.js +0 -35
  143. package/build/shaka-lab.yaml +0 -161
  144. package/build/shakaBuildHelpers.py +0 -356
  145. package/build/stats.py +0 -816
  146. package/build/subprocessWindowsPatch.py +0 -103
  147. package/build/test.py +0 -541
  148. package/build/types/ads +0 -8
  149. package/build/types/cast +0 -6
  150. package/build/types/cea +0 -13
  151. package/build/types/complete +0 -9
  152. package/build/types/core +0 -108
  153. package/build/types/dash +0 -8
  154. package/build/types/hls +0 -9
  155. package/build/types/manifests +0 -5
  156. package/build/types/networking +0 -6
  157. package/build/types/offline +0 -22
  158. package/build/types/polyfill +0 -18
  159. package/build/types/text +0 -11
  160. package/build/types/ui +0 -39
  161. package/build/updateScreenshots.py +0 -85
  162. package/build/wrapper.template.js +0 -52
@@ -22,6 +22,7 @@ shaka.util.ObjectUtils = class {
22
22
  const seenObjects = new Set();
23
23
  // This recursively clones the value |val|, using the captured variable
24
24
  // |seenObjects| to track the objects we have already cloned.
25
+ /** @suppress {strictMissingProperties} */
25
26
  const clone = (val) => {
26
27
  switch (typeof val) {
27
28
  case 'undefined':
@@ -197,7 +197,9 @@ shaka.util.Platform = class {
197
197
  */
198
198
  static isApple() {
199
199
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
200
- !shaka.util.Platform.isTizen();
200
+ !shaka.util.Platform.isTizen() &&
201
+ !shaka.util.Platform.isEOS() &&
202
+ !shaka.util.Platform.isPS4();
201
203
  }
202
204
 
203
205
  /**
@@ -213,6 +215,13 @@ shaka.util.Platform = class {
213
215
  return shaka.util.Platform.userAgentContains_('PlayStation 5');
214
216
  }
215
217
 
218
+ /**
219
+ * Check if the current platform is Playstation 4.
220
+ */
221
+ static isPS4() {
222
+ return shaka.util.Platform.userAgentContains_('PlayStation 4');
223
+ }
224
+
216
225
  /**
217
226
  * Returns a major version number for Safari, or Safari-based iOS browsers.
218
227
  *
@@ -260,6 +269,15 @@ shaka.util.Platform = class {
260
269
  return !!shaka.util.Platform.safariVersion();
261
270
  }
262
271
 
272
+ /**
273
+ * Check if the current platform is an EOS set-top box.
274
+ *
275
+ * @return {boolean}
276
+ */
277
+ static isEOS() {
278
+ return shaka.util.Platform.userAgentContains_('PC=EOS');
279
+ }
280
+
263
281
  /**
264
282
  * Guesses if the platform is a mobile one (iOS or Android).
265
283
  *
@@ -585,8 +585,16 @@ shaka.util.StreamUtils = class {
585
585
  // VideoConfiguration
586
586
  mediaDecodingConfig.video = {
587
587
  contentType: fullType,
588
- width: video.width || 1,
589
- height: video.height || 1,
588
+
589
+ // NOTE: Some decoders strictly check the width and height fields and
590
+ // won't decode smaller than 64x64. So if we don't have this info (as
591
+ // is the case in some of our simpler tests), assume a 64x64 resolution
592
+ // to fill in this required field for MediaCapabilities.
593
+ //
594
+ // This became an issue specifically on Firefox on M1 Macs.
595
+ width: video.width || 64,
596
+ height: video.height || 64,
597
+
590
598
  bitrate: video.bandwidth || variant.bandwidth || 1,
591
599
  // framerate must be greater than 0, otherwise the config is invalid.
592
600
  framerate: video.frameRate || 1,
@@ -749,7 +757,19 @@ shaka.util.StreamUtils = class {
749
757
  */
750
758
  static patchVp9(codec) {
751
759
  if (codec == 'vp9') {
752
- return 'vp09.00.10.08';
760
+ // This means profile 0, level 4.1, 8-bit color. This supports 1080p @
761
+ // 60Hz. See https://en.wikipedia.org/wiki/VP9#Levels
762
+ //
763
+ // If we don't have more detailed codec info, assume this profile and
764
+ // level because it's high enough to likely accommodate the parameters we
765
+ // do have, such as width and height. If an implementation is checking
766
+ // the profile and level very strictly, we want older VP9 content to
767
+ // still work to some degree. But we don't want to set a level so high
768
+ // that it is rejected by a hardware decoder that can't handle the
769
+ // maximum requirements of the level.
770
+ //
771
+ // This became an issue specifically on Firefox on M1 Macs.
772
+ return 'vp09.00.41.08';
753
773
  }
754
774
  return codec;
755
775
  }
@@ -1015,6 +1035,7 @@ shaka.util.StreamUtils = class {
1015
1035
  track.channelsCount = audio.channelsCount;
1016
1036
  track.audioSamplingRate = audio.audioSamplingRate;
1017
1037
  track.audioBandwidth = audio.bandwidth || null;
1038
+ track.spatialAudio = audio.spatialAudio;
1018
1039
  track.label = audio.label;
1019
1040
  track.audioRoles = audio.roles;
1020
1041
  }
@@ -11,6 +11,7 @@ goog.require('shaka.log');
11
11
  goog.require('shaka.util.BufferUtils');
12
12
  goog.require('shaka.util.Error');
13
13
  goog.require('shaka.util.Lazy');
14
+ goog.require('shaka.util.Platform');
14
15
 
15
16
 
16
17
  /**
@@ -37,18 +38,84 @@ shaka.util.StringUtils = class {
37
38
  uint8 = uint8.subarray(3);
38
39
  }
39
40
 
40
- // Use the TextDecoder interface to decode the text. This has the advantage
41
- // compared to the previously-standard decodeUriComponent that it will
42
- // continue parsing even if it finds an invalid UTF8 character, rather than
43
- // stop and throw an error.
44
- const utf8decoder = new TextDecoder();
45
- const decoded = utf8decoder.decode(uint8);
46
- if (decoded.includes('\uFFFD')) {
47
- shaka.log.alwaysError('Decoded string contains an "unknown character" ' +
48
- 'codepoint. That probably means the UTF8 ' +
49
- 'encoding was incorrect!');
41
+ if (window.TextDecoder && !shaka.util.Platform.isPS4()) {
42
+ // Use the TextDecoder interface to decode the text. This has the
43
+ // advantage compared to the previously-standard decodeUriComponent that
44
+ // it will continue parsing even if it finds an invalid UTF8 character,
45
+ // rather than stop and throw an error.
46
+ const utf8decoder = new TextDecoder();
47
+ const decoded = utf8decoder.decode(uint8);
48
+ if (decoded.includes('\uFFFD')) {
49
+ shaka.log.alwaysError('Decoded string contains an "unknown character' +
50
+ '" codepoint. That probably means the UTF8 ' +
51
+ 'encoding was incorrect!');
52
+ }
53
+ return decoded;
54
+ } else {
55
+ // Homebrewed UTF-8 decoder based on
56
+ // https://en.wikipedia.org/wiki/UTF-8#Encoding
57
+ // Unlike decodeURIComponent, won't throw on bad encoding.
58
+ // In this way, it is similar to TextDecoder.
59
+
60
+ let decoded = '';
61
+ for (let i = 0; i < uint8.length; ++i) {
62
+ // By default, the "replacement character" codepoint.
63
+ let codePoint = 0xFFFD;
64
+
65
+ // Top bit is 0, 1-byte encoding.
66
+ if ((uint8[i] & 0x80) == 0) {
67
+ codePoint = uint8[i];
68
+
69
+ // Top 3 bits of byte 0 are 110, top 2 bits of byte 1 are 10,
70
+ // 2-byte encoding.
71
+ } else if (uint8.length >= i + 2 &&
72
+ (uint8[i] & 0xe0) == 0xc0 &&
73
+ (uint8[i + 1] & 0xc0) == 0x80) {
74
+ codePoint = ((uint8[i] & 0x1f) << 6) |
75
+ ((uint8[i + 1] & 0x3f));
76
+ i += 1; // Consume one extra byte.
77
+
78
+ // Top 4 bits of byte 0 are 1110, top 2 bits of byte 1 and 2 are 10,
79
+ // 3-byte encoding.
80
+ } else if (uint8.length >= i + 3 &&
81
+ (uint8[i] & 0xf0) == 0xe0 &&
82
+ (uint8[i + 1] & 0xc0) == 0x80 &&
83
+ (uint8[i + 2] & 0xc0) == 0x80) {
84
+ codePoint = ((uint8[i] & 0x0f) << 12) |
85
+ ((uint8[i + 1] & 0x3f) << 6) |
86
+ ((uint8[i + 2] & 0x3f));
87
+ i += 2; // Consume two extra bytes.
88
+
89
+ // Top 5 bits of byte 0 are 11110, top 2 bits of byte 1, 2 and 3 are 10,
90
+ // 4-byte encoding.
91
+ } else if (uint8.length >= i + 4 &&
92
+ (uint8[i] & 0xf1) == 0xf0 &&
93
+ (uint8[i + 1] & 0xc0) == 0x80 &&
94
+ (uint8[i + 2] & 0xc0) == 0x80 &&
95
+ (uint8[i + 3] & 0xc0) == 0x80) {
96
+ codePoint = ((uint8[i] & 0x07) << 18) |
97
+ ((uint8[i + 1] & 0x3f) << 12) |
98
+ ((uint8[i + 2] & 0x3f) << 6) |
99
+ ((uint8[i + 3] & 0x3f));
100
+ i += 3; // Consume three extra bytes.
101
+ }
102
+
103
+ // JavaScript strings are a series of UTF-16 characters.
104
+ if (codePoint <= 0xffff) {
105
+ decoded += String.fromCharCode(codePoint);
106
+ } else {
107
+ // UTF-16 surrogate-pair encoding, based on
108
+ // https://en.wikipedia.org/wiki/UTF-16#Description
109
+ const baseCodePoint = codePoint - 0x10000;
110
+ const highPart = baseCodePoint >> 10;
111
+ const lowPart = baseCodePoint & 0x3ff;
112
+ decoded += String.fromCharCode(0xd800 + highPart);
113
+ decoded += String.fromCharCode(0xdc00 + lowPart);
114
+ }
115
+ }
116
+
117
+ return decoded;
50
118
  }
51
- return decoded;
52
119
  }
53
120
 
54
121
 
@@ -141,8 +208,30 @@ shaka.util.StringUtils = class {
141
208
  * @export
142
209
  */
143
210
  static toUTF8(str) {
144
- const utf8Encoder = new TextEncoder();
145
- return shaka.util.BufferUtils.toArrayBuffer(utf8Encoder.encode(str));
211
+ if (window.TextEncoder && !shaka.util.Platform.isPS4()) {
212
+ const utf8Encoder = new TextEncoder();
213
+ return shaka.util.BufferUtils.toArrayBuffer(utf8Encoder.encode(str));
214
+ } else {
215
+ // http://stackoverflow.com/a/13691499
216
+ // Converts the given string to a URI encoded string. If a character
217
+ // falls in the ASCII range, it is not converted; otherwise it will be
218
+ // converted to a series of URI escape sequences according to UTF-8.
219
+ // Example: 'g#€' -> 'g#%E3%82%AC'
220
+ const encoded = encodeURIComponent(str);
221
+ // Convert each escape sequence individually into a character. Each
222
+ // escape sequence is interpreted as a code-point, so if an escape
223
+ // sequence happens to be part of a multi-byte sequence, each byte will
224
+ // be converted to a single character.
225
+ // Example: 'g#%E3%82%AC' -> '\x67\x35\xe3\x82\xac'
226
+ const utf8 = unescape(encoded);
227
+
228
+ const result = new Uint8Array(utf8.length);
229
+ for (let i = 0; i < utf8.length; i++) {
230
+ const item = utf8[i];
231
+ result[i] = item.charCodeAt(0);
232
+ }
233
+ return shaka.util.BufferUtils.toArrayBuffer(result);
234
+ }
146
235
  }
147
236
 
148
237
 
@@ -0,0 +1,15 @@
1
+ # Maintained Shaka Player Branches
2
+
3
+ We are currently maintaining the following branches of Shaka Player:
4
+
5
+ - v4.1 (latest)
6
+ - v4.0 (previous)
7
+ - v3.3 (LTS until April 30, 2023)
8
+ - v3.2 (in use by the Cast Application Framework)
9
+
10
+ Other branches are no longer receiving bug fixes, and we recommend you upgrade
11
+ to a maintained branch. For details on upgrading, please see our
12
+ [upgrade guide](https://shaka-player-demo.appspot.com/docs/api/tutorial-upgrade.html).
13
+
14
+ For more information on how these branches are chosen, please refer to our
15
+ [Branch Maintenance Policy](https://shaka-project.github.io/maintenance/shaka-player.html#branch-maintenance-policy).
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.1.0",
4
+ "version": "4.1.3",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -27,7 +27,6 @@
27
27
  "eslint-config-google": "^0.14.0",
28
28
  "eslint-plugin-shaka-rules": "file:./build/eslint-plugin-shaka-rules",
29
29
  "esprima": "^4.0.1",
30
- "fastestsmallesttextencoderdecoder": "^1.0.22",
31
30
  "fontfaceonload": "^1.0.2",
32
31
  "google-closure-compiler-java": "^20220301.0.0",
33
32
  "google-closure-deps": "https://gitpkg.now.sh/google/closure-library/closure-deps?d7736da6",
@@ -50,7 +49,7 @@
50
49
  "less": "https://gitpkg.now.sh/joeyparrish/less.js/packages/less?28c63a43",
51
50
  "less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
52
51
  "material-design-lite": "^1.3.0",
53
- "mux.js": "^6.1.0",
52
+ "mux.js": "^6.2.0",
54
53
  "open-sans-fonts": "^1.6.2",
55
54
  "postcss-less": "^6.0.0",
56
55
  "pwacompat": "^2.0.17",
@@ -71,7 +70,6 @@
71
70
  "dialog-polyfill/dist/dialog-polyfill.js",
72
71
  "eme-encryption-scheme-polyfill/index.js",
73
72
  "es6-promise-polyfill/promise.min.js",
74
- "fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js",
75
73
  "google-closure-library/closure/goog/base.js",
76
74
  "less/dist/less.js",
77
75
  "material-design-lite/dist/material.indigo-blue.min.css",
@@ -96,7 +94,7 @@
96
94
  "prepublishOnly": "python build/checkversion.py && python build/all.py --force"
97
95
  },
98
96
  "dependencies": {
99
- "eme-encryption-scheme-polyfill": "^2.0.3"
97
+ "eme-encryption-scheme-polyfill": "^2.1.1"
100
98
  },
101
99
  "engines": {
102
100
  "node": ">=14"
@@ -2021,6 +2021,10 @@ describe('HlsParser', () => {
2021
2021
  });
2022
2022
 
2023
2023
  describe('produces syncTime', () => {
2024
+ // Corresponds to "2000-01-01T00:00:00.00Z".
2025
+ // All the PROGRAM-DATE-TIME values in the tests below are at or after this.
2026
+ const syncTimeBase = 946684800;
2027
+
2024
2028
  /**
2025
2029
  * @param {number} startTime
2026
2030
  * @param {number} endTime
@@ -2039,9 +2043,10 @@ describe('HlsParser', () => {
2039
2043
  /**
2040
2044
  * @param {string} media
2041
2045
  * @param {!Array.<number>} startTimes
2046
+ * @param {number} syncTimeOffset
2042
2047
  * @param {(function(!shaka.media.SegmentReference))=} modifyFn
2043
2048
  */
2044
- async function test(media, startTimes, modifyFn) {
2049
+ async function test(media, startTimes, syncTimeOffset, modifyFn) {
2045
2050
  const master = [
2046
2051
  '#EXTM3U\n',
2047
2052
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
@@ -2053,7 +2058,8 @@ describe('HlsParser', () => {
2053
2058
  for (let i = 0; i < startTimes.length - 1; i++) {
2054
2059
  const startTime = startTimes[i];
2055
2060
  const endTime = startTimes[i + 1];
2056
- const reference = makeReference(startTime, endTime, startTime);
2061
+ const reference = makeReference(
2062
+ startTime, endTime, syncTimeOffset + startTime);
2057
2063
  if (modifyFn) {
2058
2064
  modifyFn(reference);
2059
2065
  }
@@ -2100,7 +2106,7 @@ describe('HlsParser', () => {
2100
2106
  '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2101
2107
  '#EXTINF:5,\n',
2102
2108
  'main.mp4',
2103
- ].join(''), [0, 5, 10, 15, 20, 25]);
2109
+ ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5);
2104
2110
  });
2105
2111
 
2106
2112
  it('when some EXT-X-PROGRAM-DATE-TIME values are missing', async () => {
@@ -2122,7 +2128,7 @@ describe('HlsParser', () => {
2122
2128
  'main.mp4\n',
2123
2129
  '#EXTINF:4,\n',
2124
2130
  'main.mp4',
2125
- ].join(''), [0, 2, 7, 12, 17, 19, 23]);
2131
+ ].join(''), [0, 2, 7, 12, 17, 19, 23], syncTimeBase + 8);
2126
2132
  });
2127
2133
 
2128
2134
  it('except when ignoreManifestProgramDateTime is set', async () => {
@@ -2148,7 +2154,7 @@ describe('HlsParser', () => {
2148
2154
  '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2149
2155
  '#EXTINF:5,\n',
2150
2156
  'main.mp4',
2151
- ].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
2157
+ ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
2152
2158
  reference.syncTime = null;
2153
2159
  });
2154
2160
  });
@@ -2176,11 +2182,11 @@ describe('HlsParser', () => {
2176
2182
  '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2177
2183
  '#EXTINF:5,\n',
2178
2184
  'main.mp4',
2179
- ].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
2185
+ ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
2180
2186
  if (reference.startTime == 10) {
2181
2187
  reference.partialReferences = [
2182
- makeReference(10, 12.5, 10),
2183
- makeReference(12.5, 15, 12.5),
2188
+ makeReference(10, 12.5, syncTimeBase + 15),
2189
+ makeReference(12.5, 15, syncTimeBase + 17.5),
2184
2190
  ];
2185
2191
  }
2186
2192
  });
@@ -3555,4 +3561,92 @@ describe('HlsParser', () => {
3555
3561
 
3556
3562
  await testHlsParser(media, '', manifest);
3557
3563
  });
3564
+
3565
+ it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
3566
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
3567
+ config.hls.ignoreManifestProgramDateTime = true;
3568
+ parser.configure(config);
3569
+
3570
+ const master = [
3571
+ '#EXTM3U\n',
3572
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
3573
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
3574
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
3575
+ 'video\n',
3576
+ ].join('');
3577
+
3578
+ const video = [
3579
+ '#EXTM3U\n',
3580
+ '#EXT-X-TARGETDURATION:5\n',
3581
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3582
+ '#EXT-X-MEDIA-SEQUENCE:1\n',
3583
+ '#EXTINF:5,\n',
3584
+ 'video1.mp4\n',
3585
+ '#EXTINF:5,\n',
3586
+ 'video2.mp4\n',
3587
+ '#EXTINF:5,\n',
3588
+ 'video3.mp4\n',
3589
+ ].join('');
3590
+
3591
+ const audio = [
3592
+ '#EXTM3U\n',
3593
+ '#EXT-X-TARGETDURATION:5\n',
3594
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3595
+ '#EXT-X-MEDIA-SEQUENCE:3\n',
3596
+ '#EXTINF:5,\n',
3597
+ 'audio3.mp4\n',
3598
+ '#EXTINF:5,\n',
3599
+ 'audio4.mp4\n',
3600
+ ].join('');
3601
+
3602
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3603
+ manifest.sequenceMode = true;
3604
+ manifest.anyTimeline();
3605
+ manifest.addPartialVariant((variant) => {
3606
+ variant.language = 'en';
3607
+ variant.bandwidth = 200;
3608
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3609
+ stream.mime('video/mp4', 'avc1');
3610
+ stream.size(960, 540);
3611
+ stream.frameRate = 60;
3612
+ });
3613
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
3614
+ stream.language = 'en';
3615
+ stream.mime('audio/mp4', 'mp4a');
3616
+ });
3617
+ });
3618
+ });
3619
+
3620
+ fakeNetEngine
3621
+ .setResponseText('test:/master', master)
3622
+ .setResponseText('test:/audio', audio)
3623
+ .setResponseText('test:/video', video)
3624
+ .setResponseValue('test:/init.mp4', initSegmentData);
3625
+
3626
+ const actualManifest = await parser.start('test:/master', playerInterface);
3627
+ expect(actualManifest).toEqual(manifest);
3628
+
3629
+ const actualVideo = actualManifest.variants[0].video;
3630
+ await actualVideo.createSegmentIndex();
3631
+ goog.asserts.assert(actualVideo.segmentIndex != null, 'Null segmentIndex!');
3632
+
3633
+ const actualAudio = actualManifest.variants[0].audio;
3634
+ await actualAudio.createSegmentIndex();
3635
+ goog.asserts.assert(actualAudio.segmentIndex != null, 'Null segmentIndex!');
3636
+
3637
+ // Verify that the references are aligned on sequence number.
3638
+ const videoSegments = Array.from(actualVideo.segmentIndex);
3639
+ const audioSegments = Array.from(actualAudio.segmentIndex);
3640
+
3641
+ // The first two were dropped to align with the audio.
3642
+ expect(videoSegments.map((ref) => ref.getUris()[0])).toEqual([
3643
+ 'test:/video3.mp4',
3644
+ ]);
3645
+ // Audio has a 4th segment that video doesn't, but that doesn't get clipped
3646
+ // or used as the base. Alignment is truly based on media sequence number.
3647
+ expect(audioSegments.map((ref) => ref.getUris()[0])).toEqual([
3648
+ 'test:/audio3.mp4',
3649
+ 'test:/audio4.mp4',
3650
+ ]);
3651
+ });
3558
3652
  });
@@ -209,10 +209,10 @@ describe('DrmEngine', () => {
209
209
  await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
210
210
  await drmEngine.attach(video);
211
211
  await mediaSourceEngine.appendBuffer(
212
- ContentType.VIDEO, videoInitSegment, null, null,
212
+ ContentType.VIDEO, videoInitSegment, null,
213
213
  /* hasClosedCaptions= */ false);
214
214
  await mediaSourceEngine.appendBuffer(
215
- ContentType.AUDIO, audioInitSegment, null, null,
215
+ ContentType.AUDIO, audioInitSegment, null,
216
216
  /* hasClosedCaptions= */ false);
217
217
  await encryptedEventSeen;
218
218
  // With PlayReady, a persistent license policy can cause a different
@@ -245,11 +245,13 @@ describe('DrmEngine', () => {
245
245
  }
246
246
  }
247
247
 
248
+ const reference = dummyReference(0, 10);
249
+
248
250
  await mediaSourceEngine.appendBuffer(
249
- ContentType.VIDEO, videoSegment, 0, 10,
251
+ ContentType.VIDEO, videoSegment, reference,
250
252
  /* hasClosedCaptions= */ false);
251
253
  await mediaSourceEngine.appendBuffer(
252
- ContentType.AUDIO, audioSegment, 0, 10,
254
+ ContentType.AUDIO, audioSegment, reference,
253
255
  /* hasClosedCaptions= */ false);
254
256
 
255
257
  expect(video.buffered.end(0)).toBeGreaterThan(0);
@@ -305,10 +307,10 @@ describe('DrmEngine', () => {
305
307
  await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
306
308
  await drmEngine.attach(video);
307
309
  await mediaSourceEngine.appendBuffer(
308
- ContentType.VIDEO, videoInitSegment, null, null,
310
+ ContentType.VIDEO, videoInitSegment, null,
309
311
  /* hasClosedCaptions= */ false);
310
312
  await mediaSourceEngine.appendBuffer(
311
- ContentType.AUDIO, audioInitSegment, null, null,
313
+ ContentType.AUDIO, audioInitSegment, null,
312
314
  /* hasClosedCaptions= */ false);
313
315
  await encryptedEventSeen;
314
316
 
@@ -326,11 +328,13 @@ describe('DrmEngine', () => {
326
328
  }
327
329
  }
328
330
 
331
+ const reference = dummyReference(0, 10);
332
+
329
333
  await mediaSourceEngine.appendBuffer(
330
- ContentType.VIDEO, videoSegment, 0, 10,
334
+ ContentType.VIDEO, videoSegment, reference,
331
335
  /* hasClosedCaptions= */ false);
332
336
  await mediaSourceEngine.appendBuffer(
333
- ContentType.AUDIO, audioSegment, 0, 10,
337
+ ContentType.AUDIO, audioSegment, reference,
334
338
  /* hasClosedCaptions= */ false);
335
339
 
336
340
  expect(video.buffered.end(0)).toBeGreaterThan(0);
@@ -345,4 +349,16 @@ describe('DrmEngine', () => {
345
349
  expect(video.currentTime).toBeGreaterThan(0);
346
350
  });
347
351
  }); // describe('ClearKey')
352
+
353
+ function dummyReference(startTime, endTime) {
354
+ return new shaka.media.SegmentReference(
355
+ startTime, endTime,
356
+ /* uris= */ () => ['foo://bar'],
357
+ /* startByte= */ 0,
358
+ /* endByte= */ null,
359
+ /* initSegmentReference= */ null,
360
+ /* timestampOffset= */ 0,
361
+ /* appendWindowStart= */ 0,
362
+ /* appendWindowEnd= */ Infinity);
363
+ }
348
364
  });
@@ -57,32 +57,32 @@ describe('MediaSourceEngine', () => {
57
57
 
58
58
  function appendInit(type) {
59
59
  const segment = generators[type].getInitSegment(Date.now() / 1000);
60
+ const reference = null;
60
61
  return mediaSourceEngine.appendBuffer(
61
- type, segment, null, null, /* hasClosedCaptions= */ false);
62
+ type, segment, reference, /* hasClosedCaptions= */ false);
62
63
  }
63
64
 
64
65
  function append(type, segmentNumber) {
65
66
  const segment = generators[type]
66
67
  .getSegment(segmentNumber, Date.now() / 1000);
68
+ const reference = dummyReference(type, segmentNumber);
67
69
  return mediaSourceEngine.appendBuffer(
68
- type, segment, null, null, /* hasClosedCaptions= */ false);
70
+ type, segment, reference, /* hasClosedCaptions= */ false);
69
71
  }
70
72
 
71
- // The start time and end time should be null for init segment with closed
72
- // captions.
73
73
  function appendInitWithClosedCaptions(type) {
74
74
  const segment = generators[type].getInitSegment(Date.now() / 1000);
75
- return mediaSourceEngine.appendBuffer(type, segment, /* startTime= */ null,
76
- /* endTime= */ null, /* hasClosedCaptions= */ true);
75
+ const reference = null;
76
+ return mediaSourceEngine.appendBuffer(
77
+ type, segment, reference, /* hasClosedCaptions= */ true);
77
78
  }
78
79
 
79
- // The start time and end time should be valid for the segments with closed
80
- // captions.
81
80
  function appendWithClosedCaptions(type, segmentNumber) {
82
81
  const segment = generators[type]
83
82
  .getSegment(segmentNumber, Date.now() / 1000);
84
- return mediaSourceEngine.appendBuffer(type, segment, /* startTime= */ 0,
85
- /* endTime= */ 2, /* hasClosedCaptions= */ true);
83
+ const reference = dummyReference(type, segmentNumber);
84
+ return mediaSourceEngine.appendBuffer(
85
+ type, segment, reference, /* hasClosedCaptions= */ true);
86
86
  }
87
87
 
88
88
  function buffered(type, time) {
@@ -93,6 +93,20 @@ describe('MediaSourceEngine', () => {
93
93
  return mediaSourceEngine.bufferStart(type);
94
94
  }
95
95
 
96
+ function dummyReference(type, segmentNumber) {
97
+ const start = segmentNumber * metadata[type].segmentDuration;
98
+ const end = (segmentNumber + 1) * metadata[type].segmentDuration;
99
+ return new shaka.media.SegmentReference(
100
+ start, end,
101
+ /* uris= */ () => ['foo://bar'],
102
+ /* startByte= */ 0,
103
+ /* endByte= */ null,
104
+ /* initSegmentReference= */ null,
105
+ /* timestampOffset= */ 0,
106
+ /* appendWindowStart= */ 0,
107
+ /* appendWindowEnd= */ Infinity);
108
+ }
109
+
96
110
  function remove(type, segmentNumber) {
97
111
  const start = segmentNumber * metadata[type].segmentDuration;
98
112
  const end = (segmentNumber + 1) * metadata[type].segmentDuration;