shaka-player 4.4.3 → 4.5.0

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 (198) hide show
  1. package/.github/workflows/release-please.yaml +2 -27
  2. package/CHANGELOG.md +64 -45
  3. package/README.md +16 -10
  4. package/demo/asset_card.js +18 -21
  5. package/demo/cast_receiver/index.html +0 -1
  6. package/demo/close_button.js +0 -3
  7. package/demo/common/asset.js +22 -5
  8. package/demo/common/assets.js +132 -49
  9. package/demo/config.js +192 -253
  10. package/demo/custom.js +39 -98
  11. package/demo/front.js +7 -10
  12. package/demo/index.html +19 -22
  13. package/demo/input.js +6 -11
  14. package/demo/input_container.js +8 -9
  15. package/demo/main.js +51 -123
  16. package/demo/search.js +22 -34
  17. package/demo/service_worker.js +0 -3
  18. package/demo/tooltip.js +3 -5
  19. package/demo/visualizer.js +3 -5
  20. package/demo/visualizer_button.js +1 -20
  21. package/dist/demo.compiled.debug.js +152 -178
  22. package/dist/demo.compiled.debug.map +4 -4
  23. package/dist/demo.compiled.js +152 -178
  24. package/dist/demo.compiled.map +4 -4
  25. package/dist/deps.js +27 -24
  26. package/dist/locales.js +31 -31
  27. package/dist/receiver.compiled.debug.js +22 -43
  28. package/dist/receiver.compiled.debug.map +4 -4
  29. package/dist/receiver.compiled.js +22 -43
  30. package/dist/receiver.compiled.map +4 -4
  31. package/dist/shaka-player.compiled.d.ts +507 -365
  32. package/dist/shaka-player.compiled.debug.d.ts +507 -365
  33. package/dist/shaka-player.compiled.debug.externs.js +205 -51
  34. package/dist/shaka-player.compiled.debug.js +541 -450
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +205 -51
  37. package/dist/shaka-player.compiled.js +1120 -1066
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +562 -410
  40. package/dist/shaka-player.ui.debug.d.ts +562 -410
  41. package/dist/shaka-player.ui.debug.externs.js +226 -51
  42. package/dist/shaka-player.ui.debug.js +606 -506
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +226 -51
  45. package/dist/shaka-player.ui.js +1266 -1207
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/lcevc-integration.md +14 -14
  48. package/docs/tutorials/faq.md +27 -55
  49. package/docs/tutorials/index.json +2 -1
  50. package/docs/tutorials/lcevc.md +118 -0
  51. package/docs/tutorials/ui-customization.md +4 -0
  52. package/externs/css_style_declaration.js +19 -0
  53. package/externs/lcevc.js +37 -13
  54. package/externs/media_remote.js +68 -0
  55. package/externs/mediatailor.js +193 -0
  56. package/externs/pictureinpicture.js +11 -0
  57. package/externs/shaka/ads.js +27 -0
  58. package/externs/shaka/manifest.js +10 -5
  59. package/externs/shaka/manifest_parser.js +7 -1
  60. package/externs/shaka/offline.js +4 -0
  61. package/externs/shaka/player.js +12 -1
  62. package/karma.conf.js +7 -3
  63. package/lib/abr/simple_abr_manager.js +43 -3
  64. package/lib/ads/ad_manager.js +104 -50
  65. package/lib/ads/media_tailor_ad.js +297 -0
  66. package/lib/ads/media_tailor_ad_manager.js +828 -0
  67. package/lib/cea/ts_cea_parser.js +15 -2
  68. package/lib/config/codec_switching_strategy.js +24 -0
  69. package/lib/dash/content_protection.js +120 -2
  70. package/lib/dash/dash_parser.js +75 -12
  71. package/lib/dash/mpd_utils.js +10 -6
  72. package/lib/dash/segment_base.js +13 -5
  73. package/lib/dash/segment_list.js +13 -5
  74. package/lib/dash/segment_template.js +43 -85
  75. package/lib/dependencies/all.js +8 -7
  76. package/lib/hls/hls_parser.js +44 -16
  77. package/lib/lcevc/lcevc_dec.js +220 -0
  78. package/lib/media/adaptation_set.js +22 -11
  79. package/lib/media/adaptation_set_criteria.js +25 -7
  80. package/lib/media/media_source_engine.js +215 -27
  81. package/lib/media/segment_index.js +1 -1
  82. package/lib/media/segment_reference.js +7 -7
  83. package/lib/media/streaming_engine.js +33 -18
  84. package/lib/mss/mss_parser.js +1 -0
  85. package/lib/net/networking_engine.js +1 -0
  86. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  87. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  88. package/lib/offline/manifest_converter.js +1 -0
  89. package/lib/offline/storage.js +3 -0
  90. package/lib/player.js +142 -72
  91. package/lib/routing/payload.js +5 -1
  92. package/lib/text/cue.js +15 -0
  93. package/lib/text/ttml_text_parser.js +17 -0
  94. package/lib/text/ui_text_displayer.js +4 -0
  95. package/lib/text/vtt_text_parser.js +33 -20
  96. package/lib/transmuxer/h265.js +862 -0
  97. package/lib/transmuxer/mpeg_ts_transmuxer.js +3 -2
  98. package/lib/transmuxer/ts_transmuxer.js +160 -15
  99. package/lib/util/error.js +28 -9
  100. package/lib/util/exp_golomb.js +30 -2
  101. package/lib/util/fake_event.js +3 -0
  102. package/lib/util/mp4_generator.js +4 -4
  103. package/lib/util/periods.js +2 -0
  104. package/lib/util/platform.js +40 -0
  105. package/lib/util/player_configuration.js +8 -0
  106. package/lib/util/state_history.js +6 -2
  107. package/lib/util/stream_utils.js +133 -217
  108. package/lib/util/ts_parser.js +226 -40
  109. package/maintained-branches.md +3 -2
  110. package/package.json +4 -6
  111. package/roadmap.md +15 -5
  112. package/shaka-player.uncompiled.js +2 -1
  113. package/support.html +0 -1
  114. package/test/cast/cast_receiver_integration.js +47 -0
  115. package/test/codec_switching/codec_switching_integration.js +181 -0
  116. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  117. package/test/dash/dash_parser_integration.js +73 -0
  118. package/test/dash/dash_parser_live_unit.js +2 -0
  119. package/test/dash/dash_parser_manifest_unit.js +42 -0
  120. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  121. package/test/dash/dash_parser_segment_list_unit.js +2 -0
  122. package/test/dash/dash_parser_segment_template_unit.js +2 -0
  123. package/test/demo/demo_unit.js +0 -16
  124. package/test/hls/hls_live_unit.js +2 -0
  125. package/test/hls/hls_parser_unit.js +8 -6
  126. package/test/media/adaptation_set_criteria_unit.js +118 -0
  127. package/test/media/adaptation_set_unit.js +1 -0
  128. package/test/media/media_source_engine_unit.js +165 -1
  129. package/test/media/segment_index_unit.js +4 -4
  130. package/test/media/segment_prefetch_unit.js +1 -1
  131. package/test/media/streaming_engine_unit.js +17 -17
  132. package/test/mss/mss_parser_unit.js +2 -0
  133. package/test/offline/manifest_convert_unit.js +5 -0
  134. package/test/offline/storage_integration.js +1 -4
  135. package/test/player_unit.js +102 -131
  136. package/test/routing/walker_unit.js +1 -0
  137. package/test/test/assets/dash-aes-128/Fragments(video=0,format=mpd-time-csf) +0 -0
  138. package/test/test/assets/dash-aes-128/Fragments(video=20000000,format=mpd-time-csf) +0 -0
  139. package/test/test/assets/dash-aes-128/Fragments(video=40000000,format=mpd-time-csf) +0 -0
  140. package/test/test/assets/dash-aes-128/Fragments(video=60000000,format=mpd-time-csf) +0 -0
  141. package/test/test/assets/dash-aes-128/Fragments(video=i,format=mpd-time-csf) +0 -0
  142. package/test/test/assets/dash-aes-128/dash.mpd +18 -0
  143. package/test/test/assets/dash-aes-128/license +1 -0
  144. package/test/test/assets/dash-multi-codec/audio_en_2c_128k_aac.mp4 +0 -0
  145. package/test/test/assets/dash-multi-codec/audio_es_2c_64k_opus.webm +0 -0
  146. package/test/test/assets/dash-multi-codec/dash.mpd +32 -0
  147. package/test/test/assets/dash-multi-codec/video_144p_108k_h264.mp4 +0 -0
  148. package/test/test/assets/hls-ts-h265/0.ts +0 -0
  149. package/test/test/assets/hls-ts-h265/hevc.m3u8 +6 -0
  150. package/test/test/assets/hls-ts-h265/index.m3u8 +7 -0
  151. package/test/test/assets/hls-ts-muxed-aac-h265/0.ts +0 -0
  152. package/test/test/assets/hls-ts-muxed-aac-h265/1.ts +0 -0
  153. package/test/test/assets/hls-ts-muxed-aac-h265/media.m3u8 +9 -0
  154. package/test/test/assets/hls-ts-muxed-ac3-h264/media.m3u8 +24 -0
  155. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00000.ts +0 -0
  156. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00001.ts +0 -0
  157. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00002.ts +0 -0
  158. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00003.ts +0 -0
  159. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00004.ts +0 -0
  160. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00005.ts +0 -0
  161. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0000.ts +0 -0
  162. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0001.ts +0 -0
  163. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0002.ts +0 -0
  164. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0003.ts +0 -0
  165. package/test/test/assets/hls-ts-muxed-ec3-h264/prog_index.m3u8 +14 -0
  166. package/test/test/assets/hls-ts-muxed-mp3-h264/index.m3u8 +23 -0
  167. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-1-v1-a1.ts +0 -0
  168. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-2-v1-a1.ts +0 -0
  169. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-3-v1-a1.ts +0 -0
  170. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-4-v1-a1.ts +0 -0
  171. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-5-v1-a1.ts +0 -0
  172. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-6-v1-a1.ts +0 -0
  173. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-7-v1-a1.ts +0 -0
  174. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-8-v1-a1.ts +0 -0
  175. package/test/test/util/dash_parser_util.js +4 -0
  176. package/test/test/util/fake_ad_manager.js +20 -0
  177. package/test/test/util/fake_media_source_engine.js +8 -2
  178. package/test/test/util/manifest_generator.js +7 -3
  179. package/test/test/util/mss_parser_util.js +4 -0
  180. package/test/test/util/offline_utils.js +1 -0
  181. package/test/test/util/streaming_engine_util.js +7 -4
  182. package/test/text/ttml_text_parser_unit.js +52 -0
  183. package/test/text/vtt_text_parser_unit.js +103 -31
  184. package/test/transmuxer/transmuxer_integration.js +117 -0
  185. package/test/ui/ui_unit.js +3 -10
  186. package/test/util/stream_utils_unit.js +208 -74
  187. package/ui/controls.js +125 -0
  188. package/ui/externs/ui.js +8 -1
  189. package/ui/pip_button.js +22 -120
  190. package/ui/remote_button.js +201 -0
  191. package/ui/ui.js +5 -2
  192. package/demo/common/message_ids.js +0 -309
  193. package/demo/locales/en.json +0 -288
  194. package/demo/locales/source.json +0 -1146
  195. package/externs/mux.js +0 -69
  196. package/lib/lcevc/lcevc_dil.js +0 -152
  197. package/lib/transmuxer/muxjs_transmuxer.js +0 -242
  198. package/test/transmuxer/muxjs_transmuxer_integration.js +0 -177
@@ -43,10 +43,11 @@ shaka.media.AdaptationSet = class {
43
43
 
44
44
  /**
45
45
  * @param {shaka.extern.Variant} variant
46
+ * @param {boolean=} compareCodecs
46
47
  * @return {boolean}
47
48
  */
48
- add(variant) {
49
- if (this.canInclude(variant)) {
49
+ add(variant, compareCodecs = true) {
50
+ if (this.canInclude(variant, compareCodecs)) {
50
51
  this.variants_.add(variant);
51
52
  return true;
52
53
  }
@@ -62,18 +63,21 @@ shaka.media.AdaptationSet = class {
62
63
  * |false|, calling |add| will result in it being ignored.
63
64
  *
64
65
  * @param {shaka.extern.Variant} variant
66
+ * @param {boolean=} compareCodecs
65
67
  * @return {boolean}
66
68
  */
67
- canInclude(variant) {
68
- return shaka.media.AdaptationSet.areAdaptable(this.root_, variant);
69
+ canInclude(variant, compareCodecs = true) {
70
+ return shaka.media.AdaptationSet
71
+ .areAdaptable(this.root_, variant, compareCodecs);
69
72
  }
70
73
 
71
74
  /**
72
75
  * @param {shaka.extern.Variant} a
73
76
  * @param {shaka.extern.Variant} b
77
+ * @param {boolean=} compareCodecs
74
78
  * @return {boolean}
75
79
  */
76
- static areAdaptable(a, b) {
80
+ static areAdaptable(a, b, compareCodecs = true) {
77
81
  const AdaptationSet = shaka.media.AdaptationSet;
78
82
 
79
83
  // All variants should have audio or should all not have audio.
@@ -95,7 +99,7 @@ shaka.media.AdaptationSet = class {
95
99
  !!a.audio == !!b.audio,
96
100
  'Both should either have audio or not have audio.');
97
101
  if (a.audio && b.audio &&
98
- !AdaptationSet.areAudiosCompatible_(a.audio, b.audio)) {
102
+ !AdaptationSet.areAudiosCompatible_(a.audio, b.audio, compareCodecs)) {
99
103
  return false;
100
104
  }
101
105
 
@@ -103,7 +107,7 @@ shaka.media.AdaptationSet = class {
103
107
  !!a.video == !!b.video,
104
108
  'Both should either have video or not have video.');
105
109
  if (a.video && b.video &&
106
- !AdaptationSet.areVideosCompatible_(a.video, b.video)) {
110
+ !AdaptationSet.areVideosCompatible_(a.video, b.video, compareCodecs)) {
107
111
  return false;
108
112
  }
109
113
 
@@ -122,10 +126,11 @@ shaka.media.AdaptationSet = class {
122
126
  *
123
127
  * @param {shaka.extern.Stream} a
124
128
  * @param {shaka.extern.Stream} b
129
+ * @param {boolean} compareCodecs
125
130
  * @return {boolean}
126
131
  * @private
127
132
  */
128
- static areAudiosCompatible_(a, b) {
133
+ static areAudiosCompatible_(a, b, compareCodecs) {
129
134
  const AdaptationSet = shaka.media.AdaptationSet;
130
135
 
131
136
  // Don't adapt between channel counts, which could annoy the user
@@ -139,7 +144,7 @@ shaka.media.AdaptationSet = class {
139
144
  }
140
145
 
141
146
  // We can only adapt between base-codecs.
142
- if (!AdaptationSet.canTransitionBetween_(a, b)) {
147
+ if (compareCodecs && !AdaptationSet.canTransitionBetween_(a, b)) {
143
148
  return false;
144
149
  }
145
150
 
@@ -148,6 +153,11 @@ shaka.media.AdaptationSet = class {
148
153
  return false;
149
154
  }
150
155
 
156
+ // We can only adapt between the same groupId.
157
+ if (a.groupId !== b.groupId) {
158
+ return false;
159
+ }
160
+
151
161
  return true;
152
162
  }
153
163
 
@@ -156,14 +166,15 @@ shaka.media.AdaptationSet = class {
156
166
  *
157
167
  * @param {shaka.extern.Stream} a
158
168
  * @param {shaka.extern.Stream} b
169
+ * @param {boolean} compareCodecs
159
170
  * @return {boolean}
160
171
  * @private
161
172
  */
162
- static areVideosCompatible_(a, b) {
173
+ static areVideosCompatible_(a, b, compareCodecs) {
163
174
  const AdaptationSet = shaka.media.AdaptationSet;
164
175
 
165
176
  // We can only adapt between base-codecs.
166
- if (!AdaptationSet.canTransitionBetween_(a, b)) {
177
+ if (compareCodecs && !AdaptationSet.canTransitionBetween_(a, b)) {
167
178
  return false;
168
179
  }
169
180
 
@@ -8,8 +8,10 @@ goog.provide('shaka.media.AdaptationSetCriteria');
8
8
  goog.provide('shaka.media.ExampleBasedCriteria');
9
9
  goog.provide('shaka.media.PreferenceBasedCriteria');
10
10
 
11
+ goog.require('shaka.config.CodecSwitchingStrategy');
11
12
  goog.require('shaka.log');
12
13
  goog.require('shaka.media.AdaptationSet');
14
+ goog.require('shaka.media.Capabilities');
13
15
  goog.require('shaka.util.LanguageUtils');
14
16
  goog.require('shaka.util.StreamUtils');
15
17
 
@@ -40,10 +42,14 @@ shaka.media.AdaptationSetCriteria = class {
40
42
  shaka.media.ExampleBasedCriteria = class {
41
43
  /**
42
44
  * @param {shaka.extern.Variant} example
45
+ * @param {shaka.config.CodecSwitchingStrategy=} codecSwitchingStrategy
43
46
  */
44
- constructor(example) {
47
+ constructor(example,
48
+ codecSwitchingStrategy = shaka.config.CodecSwitchingStrategy.RELOAD) {
45
49
  /** @private {shaka.extern.Variant} */
46
50
  this.example_ = example;
51
+ /** @private {shaka.config.CodecSwitchingStrategy} */
52
+ this.codecSwitchingStrategy_ = codecSwitchingStrategy;
47
53
 
48
54
  // We can't know if role and label are really important, so we don't use
49
55
  // role and label for this.
@@ -56,15 +62,20 @@ shaka.media.ExampleBasedCriteria = class {
56
62
 
57
63
  /** @private {!shaka.media.AdaptationSetCriteria} */
58
64
  this.fallback_ = new shaka.media.PreferenceBasedCriteria(
59
- example.language, role, channelCount, hdrLevel, label);
65
+ example.language, role, channelCount, hdrLevel, label,
66
+ codecSwitchingStrategy);
60
67
  }
61
68
 
62
69
  /** @override */
63
70
  create(variants) {
71
+ const supportsSmoothCodecTransitions = this.codecSwitchingStrategy_ ==
72
+ shaka.config.CodecSwitchingStrategy.SMOOTH &&
73
+ shaka.media.Capabilities.isChangeTypeSupported();
64
74
  // We can't assume that the example is in |variants| because it could
65
75
  // actually be from another period.
66
76
  const shortList = variants.filter((variant) => {
67
- return shaka.media.AdaptationSet.areAdaptable(this.example_, variant);
77
+ return shaka.media.AdaptationSet.areAdaptable(this.example_, variant,
78
+ !supportsSmoothCodecTransitions);
68
79
  });
69
80
 
70
81
  if (shortList.length) {
@@ -90,8 +101,10 @@ shaka.media.PreferenceBasedCriteria = class {
90
101
  * @param {number} channelCount
91
102
  * @param {string} hdrLevel
92
103
  * @param {string=} label
104
+ * @param {shaka.config.CodecSwitchingStrategy=} codecSwitchingStrategy
93
105
  */
94
- constructor(language, role, channelCount, hdrLevel, label = '') {
106
+ constructor(language, role, channelCount, hdrLevel, label = '',
107
+ codecSwitchingStrategy = shaka.config.CodecSwitchingStrategy.RELOAD) {
95
108
  /** @private {string} */
96
109
  this.language_ = language;
97
110
  /** @private {string} */
@@ -102,6 +115,8 @@ shaka.media.PreferenceBasedCriteria = class {
102
115
  this.hdrLevel_ = hdrLevel;
103
116
  /** @private {string} */
104
117
  this.label_ = label;
118
+ /** @private {shaka.config.CodecSwitchingStrategy} */
119
+ this.codecSwitchingStrategy_ = codecSwitchingStrategy;
105
120
  }
106
121
 
107
122
  /** @override */
@@ -162,12 +177,15 @@ shaka.media.PreferenceBasedCriteria = class {
162
177
  }
163
178
  }
164
179
 
180
+ const supportsSmoothCodecTransitions = this.codecSwitchingStrategy_ ==
181
+ shaka.config.CodecSwitchingStrategy.SMOOTH &&
182
+ shaka.media.Capabilities.isChangeTypeSupported();
183
+
165
184
  // Make sure we only return a valid adaptation set.
166
185
  const set = new shaka.media.AdaptationSet(current[0]);
167
186
  for (const variant of current) {
168
- if (set.canInclude(variant)) {
169
- set.add(variant);
170
- }
187
+ // `add` checks combatability by calling `canInclude` internally.
188
+ set.add(variant, !supportsSmoothCodecTransitions);
171
189
  }
172
190
 
173
191
  return set;
@@ -8,6 +8,7 @@ goog.provide('shaka.media.MediaSourceEngine');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
+ goog.require('shaka.config.CodecSwitchingStrategy');
11
12
  goog.require('shaka.media.Capabilities');
12
13
  goog.require('shaka.media.ContentWorkarounds');
13
14
  goog.require('shaka.media.ClosedCaptionParser');
@@ -31,7 +32,7 @@ goog.require('shaka.util.Mp4Parser');
31
32
  goog.require('shaka.util.Platform');
32
33
  goog.require('shaka.util.PublicPromise');
33
34
  goog.require('shaka.util.TsParser');
34
- goog.require('shaka.lcevc.Dil');
35
+ goog.require('shaka.lcevc.Dec');
35
36
 
36
37
 
37
38
  /**
@@ -53,10 +54,10 @@ shaka.media.MediaSourceEngine = class {
53
54
  * MediaSourceEngine is destroyed, it will destroy the displayer.
54
55
  * @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)=}
55
56
  * onMetadata
56
- * @param {?shaka.lcevc.Dil} [lcevcDil] Optional - LCEVC Dil Object
57
+ * @param {?shaka.lcevc.Dec} [lcevcDec] Optional - LCEVC Decoder Object
57
58
  *
58
59
  */
59
- constructor(video, textDisplayer, onMetadata, lcevcDil) {
60
+ constructor(video, textDisplayer, onMetadata, lcevcDec) {
60
61
  /** @private {HTMLMediaElement} */
61
62
  this.video_ = video;
62
63
 
@@ -91,8 +92,8 @@ shaka.media.MediaSourceEngine = class {
91
92
  number, ?number)} */
92
93
  this.onMetadata_ = onMetadata || onMetadataNoOp;
93
94
 
94
- /** @private {?shaka.lcevc.Dil} */
95
- this.lcevcDil_ = lcevcDil || null;
95
+ /** @private {?shaka.lcevc.Dec} */
96
+ this.lcevcDec_ = lcevcDec || null;
96
97
 
97
98
  /**
98
99
  * @private {!Object.<string,
@@ -115,6 +116,9 @@ shaka.media.MediaSourceEngine = class {
115
116
  /** @private {string} */
116
117
  this.url_ = '';
117
118
 
119
+ /** @private {boolean} */
120
+ this.playbackHasBegun_ = false;
121
+
118
122
  /** @private {MediaSource} */
119
123
  this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
120
124
 
@@ -138,6 +142,9 @@ shaka.media.MediaSourceEngine = class {
138
142
 
139
143
  /** @private {!shaka.util.PublicPromise.<number>} */
140
144
  this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
145
+
146
+ /** @private {boolean} */
147
+ this.needSplitMuxedContent_ = false;
141
148
  }
142
149
 
143
150
  /**
@@ -156,6 +163,11 @@ shaka.media.MediaSourceEngine = class {
156
163
  this.eventManager_.listenOnce(
157
164
  mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
158
165
 
166
+ // Correctly set when playback has begun.
167
+ this.eventManager_.listenOnce(this.video_, 'playing', () => {
168
+ this.playbackHasBegun_ = true;
169
+ });
170
+
159
171
  // Store the object URL for releasing it later.
160
172
  this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource);
161
173
 
@@ -341,7 +353,7 @@ shaka.media.MediaSourceEngine = class {
341
353
  this.queues_ = {};
342
354
 
343
355
  // This object is owned by Player
344
- this.lcevcDil_ = null;
356
+ this.lcevcDec_ = null;
345
357
  }
346
358
 
347
359
  /**
@@ -379,6 +391,8 @@ shaka.media.MediaSourceEngine = class {
379
391
  async init(streamsByType, sequenceMode=false,
380
392
  manifestType=shaka.media.ManifestParser.UNKNOWN,
381
393
  ignoreManifestTimestampsInSegmentsMode=false) {
394
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
395
+
382
396
  await this.mediaSourceOpen_;
383
397
 
384
398
  this.sequenceMode_ = sequenceMode;
@@ -392,8 +406,13 @@ shaka.media.MediaSourceEngine = class {
392
406
 
393
407
  for (const contentType of streamsByType.keys()) {
394
408
  const stream = streamsByType.get(contentType);
395
- this.initSourceBuffer_(contentType, stream);
396
- this.queues_[contentType] = [];
409
+ this.initSourceBuffer_(contentType, stream, stream.codecs);
410
+ if (this.needSplitMuxedContent_) {
411
+ this.queues_[ContentType.AUDIO] = [];
412
+ this.queues_[ContentType.VIDEO] = [];
413
+ } else {
414
+ this.queues_[contentType] = [];
415
+ }
397
416
  }
398
417
  }
399
418
 
@@ -402,9 +421,10 @@ shaka.media.MediaSourceEngine = class {
402
421
  *
403
422
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
404
423
  * @param {shaka.extern.Stream} stream
424
+ * @param {string} codecs
405
425
  * @private
406
426
  */
407
- initSourceBuffer_(contentType, stream) {
427
+ initSourceBuffer_(contentType, stream, codecs) {
408
428
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
409
429
 
410
430
  goog.asserts.assert(
@@ -412,7 +432,7 @@ shaka.media.MediaSourceEngine = class {
412
432
  'Type negotiation should happen before MediaSourceEngine.init!');
413
433
 
414
434
  let mimeType = shaka.util.MimeUtils.getFullType(
415
- stream.mimeType, stream.codecs);
435
+ stream.mimeType, codecs);
416
436
  if (contentType == ContentType.TEXT) {
417
437
  this.reinitText(mimeType, this.sequenceMode_, stream.external);
418
438
  } else {
@@ -424,9 +444,19 @@ shaka.media.MediaSourceEngine = class {
424
444
  }
425
445
  const mimeTypeWithAllCodecs =
426
446
  shaka.util.MimeUtils.getFullTypeWithAllCodecs(
427
- stream.mimeType, stream.codecs);
447
+ stream.mimeType, codecs);
428
448
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
429
449
  if (needTransmux) {
450
+ const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
451
+ ContentType.AUDIO, (codecs || '').split(','));
452
+ const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
453
+ ContentType.VIDEO, (codecs || '').split(','));
454
+ if (audioCodec && videoCodec) {
455
+ this.needSplitMuxedContent_ = true;
456
+ this.initSourceBuffer_(ContentType.AUDIO, stream, audioCodec);
457
+ this.initSourceBuffer_(ContentType.VIDEO, stream, videoCodec);
458
+ return;
459
+ }
430
460
  const transmuxerPlugin =
431
461
  TransmuxerEngine.findTransmuxer(mimeTypeWithAllCodecs);
432
462
  if (transmuxerPlugin) {
@@ -513,7 +543,8 @@ shaka.media.MediaSourceEngine = class {
513
543
  * @return {?number} The timestamp in seconds, or null if nothing is buffered.
514
544
  */
515
545
  bufferStart(contentType) {
516
- if (this.reloadingMediaSource_) {
546
+ if (this.reloadingMediaSource_ ||
547
+ !Object.keys(this.sourceBuffers_).length) {
517
548
  return null;
518
549
  }
519
550
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
@@ -743,7 +774,7 @@ shaka.media.MediaSourceEngine = class {
743
774
  */
744
775
  async appendBuffer(
745
776
  contentType, data, reference, stream, hasClosedCaptions, seeked = false,
746
- adaptation = false, isChunkedData = false) {
777
+ adaptation = false, isChunkedData = false, fromSplit = false) {
747
778
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
748
779
 
749
780
  if (contentType == ContentType.TEXT) {
@@ -759,6 +790,21 @@ shaka.media.MediaSourceEngine = class {
759
790
  return;
760
791
  }
761
792
 
793
+ if (!fromSplit && this.needSplitMuxedContent_) {
794
+ await this.appendBuffer(ContentType.AUDIO, data, reference, stream,
795
+ hasClosedCaptions, seeked, adaptation, isChunkedData,
796
+ /* fromSplit= */ true);
797
+ await this.appendBuffer(ContentType.VIDEO, data, reference, stream,
798
+ hasClosedCaptions, seeked, adaptation, isChunkedData,
799
+ /* fromSplit= */ true);
800
+ return;
801
+ }
802
+
803
+ if (!this.sourceBuffers_[contentType]) {
804
+ shaka.log.warning('Attempted to restore a non-existent source buffer');
805
+ return;
806
+ }
807
+
762
808
  let timestampOffset = this.sourceBuffers_[contentType].timestampOffset;
763
809
 
764
810
  let mimeType = this.sourceBufferTypes_[contentType];
@@ -850,7 +896,7 @@ shaka.media.MediaSourceEngine = class {
850
896
  if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
851
897
  bufferedBefore = this.getBuffered_(contentType);
852
898
  }
853
- this.append_(contentType, data);
899
+ this.append_(contentType, data, timestampOffset);
854
900
  });
855
901
 
856
902
  if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
@@ -910,6 +956,11 @@ shaka.media.MediaSourceEngine = class {
910
956
  await this.enqueueOperation_(
911
957
  contentType,
912
958
  () => this.remove_(contentType, startTime, endTime));
959
+ if (this.needSplitMuxedContent_) {
960
+ await this.enqueueOperation_(
961
+ ContentType.AUDIO,
962
+ () => this.remove_(ContentType.AUDIO, startTime, endTime));
963
+ }
913
964
  }
914
965
  }
915
966
 
@@ -931,6 +982,12 @@ shaka.media.MediaSourceEngine = class {
931
982
  await this.enqueueOperation_(
932
983
  contentType,
933
984
  () => this.remove_(contentType, 0, this.mediaSource_.duration));
985
+ if (this.needSplitMuxedContent_) {
986
+ await this.enqueueOperation_(
987
+ ContentType.AUDIO,
988
+ () => this.remove_(
989
+ ContentType.AUDIO, 0, this.mediaSource_.duration));
990
+ }
934
991
  }
935
992
  }
936
993
 
@@ -961,6 +1018,11 @@ shaka.media.MediaSourceEngine = class {
961
1018
  await this.enqueueOperation_(
962
1019
  contentType,
963
1020
  () => this.flush_(contentType));
1021
+ if (this.needSplitMuxedContent_) {
1022
+ await this.enqueueOperation_(
1023
+ ContentType.AUDIO,
1024
+ () => this.flush_(ContentType.AUDIO));
1025
+ }
964
1026
  }
965
1027
 
966
1028
  /**
@@ -1014,16 +1076,33 @@ shaka.media.MediaSourceEngine = class {
1014
1076
  operations.push(this.enqueueOperation_(
1015
1077
  contentType,
1016
1078
  () => this.abort_(contentType)));
1079
+ if (this.needSplitMuxedContent_) {
1080
+ operations.push(this.enqueueOperation_(
1081
+ ContentType.AUDIO,
1082
+ () => this.abort_(ContentType.AUDIO)));
1083
+ }
1017
1084
  }
1018
1085
  if (!ignoreTimestampOffset) {
1019
1086
  operations.push(this.enqueueOperation_(
1020
1087
  contentType,
1021
1088
  () => this.setTimestampOffset_(contentType, timestampOffset)));
1089
+ if (this.needSplitMuxedContent_) {
1090
+ operations.push(this.enqueueOperation_(
1091
+ ContentType.AUDIO,
1092
+ () => this.setTimestampOffset_(
1093
+ ContentType.AUDIO, timestampOffset)));
1094
+ }
1022
1095
  }
1023
1096
  operations.push(this.enqueueOperation_(
1024
1097
  contentType,
1025
1098
  () => this.setAppendWindow_(
1026
1099
  contentType, appendWindowStart, appendWindowEnd)));
1100
+ if (this.needSplitMuxedContent_) {
1101
+ operations.push(this.enqueueOperation_(
1102
+ ContentType.AUDIO,
1103
+ () => this.setAppendWindow_(
1104
+ ContentType.AUDIO, appendWindowStart, appendWindowEnd)));
1105
+ }
1027
1106
 
1028
1107
  await Promise.all(operations);
1029
1108
  }
@@ -1054,9 +1133,19 @@ shaka.media.MediaSourceEngine = class {
1054
1133
  this.enqueueOperation_(
1055
1134
  contentType,
1056
1135
  () => this.abort_(contentType));
1136
+ if (this.needSplitMuxedContent_) {
1137
+ this.enqueueOperation_(
1138
+ ContentType.AUDIO,
1139
+ () => this.abort_(ContentType.AUDIO));
1140
+ }
1057
1141
  await this.enqueueOperation_(
1058
1142
  contentType,
1059
1143
  () => this.setTimestampOffset_(contentType, timestampOffset));
1144
+ if (this.needSplitMuxedContent_) {
1145
+ await this.enqueueOperation_(
1146
+ ContentType.AUDIO,
1147
+ () => this.setTimestampOffset_(ContentType.AUDIO, timestampOffset));
1148
+ }
1060
1149
  }
1061
1150
 
1062
1151
  /**
@@ -1120,16 +1209,17 @@ shaka.media.MediaSourceEngine = class {
1120
1209
  * Append data to the SourceBuffer.
1121
1210
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1122
1211
  * @param {BufferSource} data
1212
+ * @param {number} timestampOffset
1123
1213
  * @private
1124
1214
  */
1125
- append_(contentType, data) {
1215
+ append_(contentType, data, timestampOffset) {
1126
1216
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1127
1217
 
1128
- // Append only video data to the LCEVC Dil.
1129
- if (contentType == ContentType.VIDEO && this.lcevcDil_) {
1130
- // Append video buffers to the LCEVC Dil for parsing and storing
1218
+ // Append only video data to the LCEVC Dec.
1219
+ if (contentType == ContentType.VIDEO && this.lcevcDec_) {
1220
+ // Append video buffers to the LCEVC Dec for parsing and storing
1131
1221
  // of LCEVC data.
1132
- this.lcevcDil_.appendBuffer(data);
1222
+ this.lcevcDec_.appendBuffer(data, timestampOffset);
1133
1223
  }
1134
1224
 
1135
1225
  // This will trigger an 'updateend' event.
@@ -1292,7 +1382,6 @@ shaka.media.MediaSourceEngine = class {
1292
1382
  */
1293
1383
  enqueueOperation_(contentType, start) {
1294
1384
  this.destroyer_.ensureNotDestroyed();
1295
-
1296
1385
  const operation = {
1297
1386
  start: start,
1298
1387
  p: new shaka.util.PublicPromise(),
@@ -1563,8 +1652,24 @@ shaka.media.MediaSourceEngine = class {
1563
1652
  */
1564
1653
  async reset_(streamsByType) {
1565
1654
  const Functional = shaka.util.Functional;
1655
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
1566
1656
  this.reloadingMediaSource_ = true;
1657
+ this.needSplitMuxedContent_ = false;
1567
1658
  const currentTime = this.video_.currentTime;
1659
+
1660
+ // When codec switching if the user is currently paused we don't want
1661
+ // to trigger a play when switching codec.
1662
+ // Playing can also end up in a paused state after a codec switch
1663
+ // so we need to remember the current states.
1664
+ const previousAutoPlayState = this.video_.autoplay;
1665
+ const previousPausedState = this.video_.paused;
1666
+ if (this.playbackHasBegun_) {
1667
+ // Only set autoplay to false if the video playback has already begun.
1668
+ // When a codec switch happens before playback has begun this can cause
1669
+ // autoplay not to work as expected.
1670
+ this.video_.autoplay = false;
1671
+ }
1672
+
1568
1673
  try {
1569
1674
  this.eventManager_.removeAll();
1570
1675
 
@@ -1598,7 +1703,6 @@ shaka.media.MediaSourceEngine = class {
1598
1703
  }
1599
1704
  await Promise.all(cleanup);
1600
1705
  this.transmuxers_ = {};
1601
- this.queues_ = {};
1602
1706
  this.sourceBuffers_ = {};
1603
1707
 
1604
1708
  const previousDuration = this.mediaSource_.duration;
@@ -1622,7 +1726,13 @@ shaka.media.MediaSourceEngine = class {
1622
1726
 
1623
1727
  for (const contentType of streamsByType.keys()) {
1624
1728
  const stream = streamsByType.get(contentType);
1625
- this.initSourceBuffer_(contentType, stream);
1729
+ this.initSourceBuffer_(contentType, stream, stream.codecs);
1730
+ if (this.needSplitMuxedContent_) {
1731
+ this.queues_[ContentType.AUDIO] = [];
1732
+ this.queues_[ContentType.VIDEO] = [];
1733
+ } else {
1734
+ this.queues_[contentType] = [];
1735
+ }
1626
1736
  }
1627
1737
 
1628
1738
  // Fake a seek to catchup the playhead.
@@ -1631,6 +1741,17 @@ shaka.media.MediaSourceEngine = class {
1631
1741
  await sourceBufferAdded;
1632
1742
  } finally {
1633
1743
  this.reloadingMediaSource_ = false;
1744
+
1745
+ this.destroyer_.ensureNotDestroyed();
1746
+
1747
+ this.eventManager_.listenOnce(this.video_, 'canplay', () => {
1748
+ this.destroyer_.ensureNotDestroyed();
1749
+
1750
+ this.video_.autoplay = previousAutoPlayState;
1751
+ if (!previousPausedState) {
1752
+ this.video_.play();
1753
+ }
1754
+ });
1634
1755
  }
1635
1756
  }
1636
1757
 
@@ -1696,7 +1817,10 @@ shaka.media.MediaSourceEngine = class {
1696
1817
  return false;
1697
1818
  }
1698
1819
 
1699
- if (shaka.media.Capabilities.isChangeTypeSupported()) {
1820
+ if (this.config_.codecSwitchingStrategy ===
1821
+ shaka.config.CodecSwitchingStrategy.SMOOTH &&
1822
+ shaka.media.Capabilities.isChangeTypeSupported() &&
1823
+ !this.needSplitMuxedContent_) {
1700
1824
  await this.changeType(contentType, newMimeType, transmuxer);
1701
1825
  } else {
1702
1826
  if (transmuxer) {
@@ -1708,11 +1832,75 @@ shaka.media.MediaSourceEngine = class {
1708
1832
  }
1709
1833
 
1710
1834
  /**
1711
- * Update LCEVC DIL object when ready for LCEVC Decodes
1712
- * @param {?shaka.lcevc.Dil} lcevcDil
1835
+ * Returns true if it's necessary codec switch to load the new stream.
1836
+ *
1837
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1838
+ * @param {shaka.extern.Stream} stream
1839
+ * @return {boolean}
1840
+ * @private
1841
+ */
1842
+ isCodecSwitchNecessary_(contentType, stream) {
1843
+ const MimeUtils = shaka.util.MimeUtils;
1844
+ const currentCodec = MimeUtils.getCodecBase(
1845
+ MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
1846
+ const currentBasicType = MimeUtils.getBasicType(
1847
+ this.sourceBufferTypes_[contentType]);
1848
+
1849
+ let newMimeType = shaka.util.MimeUtils.getFullType(
1850
+ stream.mimeType, stream.codecs);
1851
+ let needTransmux = this.config_.forceTransmux;
1852
+ if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
1853
+ (!this.sequenceMode_ &&
1854
+ shaka.util.MimeUtils.RAW_FORMATS.includes(newMimeType))) {
1855
+ needTransmux = true;
1856
+ }
1857
+ const newMimeTypeWithAllCodecs =
1858
+ shaka.util.MimeUtils.getFullTypeWithAllCodecs(
1859
+ stream.mimeType, stream.codecs);
1860
+ const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
1861
+ if (needTransmux) {
1862
+ const transmuxerPlugin =
1863
+ TransmuxerEngine.findTransmuxer(newMimeTypeWithAllCodecs);
1864
+ if (transmuxerPlugin) {
1865
+ const transmuxer = transmuxerPlugin();
1866
+ newMimeType =
1867
+ transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
1868
+ transmuxer.destroy();
1869
+ }
1870
+ }
1871
+
1872
+ const newCodec = MimeUtils.getCodecBase(
1873
+ MimeUtils.getCodecs(newMimeType));
1874
+ const newBasicType = MimeUtils.getBasicType(newMimeType);
1875
+
1876
+ return currentCodec !== newCodec || currentBasicType !== newBasicType;
1877
+ }
1878
+
1879
+ /**
1880
+ * Returns true if it's necessary reset the media source to load the
1881
+ * new stream.
1882
+ *
1883
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1884
+ * @param {shaka.extern.Stream} stream
1885
+ * @return {boolean}
1886
+ */
1887
+ isResetMediaSourceNecessary(contentType, stream) {
1888
+ if (!this.isCodecSwitchNecessary_(contentType, stream)) {
1889
+ return false;
1890
+ }
1891
+
1892
+ return this.config_.codecSwitchingStrategy !==
1893
+ shaka.config.CodecSwitchingStrategy.SMOOTH ||
1894
+ !shaka.media.Capabilities.isChangeTypeSupported() ||
1895
+ this.needSplitMuxedContent_;
1896
+ }
1897
+
1898
+ /**
1899
+ * Update LCEVC Decoder object when ready for LCEVC Decode.
1900
+ * @param {?shaka.lcevc.Dec} lcevcDec
1713
1901
  */
1714
- updateLcevcDil(lcevcDil) {
1715
- this.lcevcDil_ = lcevcDil;
1902
+ updateLcevcDec(lcevcDec) {
1903
+ this.lcevcDec_ = lcevcDec;
1716
1904
  }
1717
1905
  };
1718
1906
 
@@ -379,7 +379,7 @@ shaka.media.SegmentIndex = class {
379
379
  lastReference.tileDuration,
380
380
  lastReference.syncTime,
381
381
  lastReference.status,
382
- lastReference.hlsAes128Key,
382
+ lastReference.aes128Key,
383
383
  );
384
384
  this.references[this.references.length - 1].discontinuitySequence =
385
385
  lastReference.discontinuitySequence;