shaka-player 3.2.1 → 3.3.1

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 (213) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +9 -5
  5. package/CHANGELOG.md +358 -0
  6. package/CONTRIBUTORS +7 -2
  7. package/build/conformance.textproto +183 -169
  8. package/build/generateExterns.js +1 -1
  9. package/build/types/core +3 -0
  10. package/build/types/offline +1 -0
  11. package/build/types/polyfill +1 -0
  12. package/build/types/ui +2 -0
  13. package/demo/common/assets.js +19 -1
  14. package/demo/common/message_ids.js +13 -1
  15. package/demo/config.js +31 -1
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +12 -0
  18. package/demo/locales/source.json +48 -0
  19. package/dist/controls.css +26 -2
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +20 -17
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +20 -17
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +19 -12
  26. package/dist/locales.js +39 -37
  27. package/dist/receiver.compiled.debug.js +12 -10
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +12 -10
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +231 -18
  32. package/dist/shaka-player.compiled.debug.d.ts +231 -18
  33. package/dist/shaka-player.compiled.debug.externs.js +279 -8
  34. package/dist/shaka-player.compiled.debug.js +512 -449
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +279 -8
  37. package/dist/shaka-player.compiled.js +854 -821
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +247 -19
  40. package/dist/shaka-player.ui.debug.d.ts +247 -19
  41. package/dist/shaka-player.ui.debug.externs.js +307 -9
  42. package/dist/shaka-player.ui.debug.js +662 -580
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +307 -9
  45. package/dist/shaka-player.ui.js +1047 -1000
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/bg-fetch-after.gv +29 -0
  48. package/docs/design/bg-fetch-after.gv.png +0 -0
  49. package/docs/design/bg-fetch-before.gv +18 -0
  50. package/docs/design/bg-fetch-before.gv.png +0 -0
  51. package/docs/design/bg-fetch.md +134 -0
  52. package/docs/tutorials/blob-url.md +15 -0
  53. package/docs/tutorials/fairplay.md +1 -1
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/cmcd.js +186 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +5 -0
  60. package/externs/shaka/manifest.js +12 -1
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +31 -2
  64. package/externs/shaka/player.js +138 -5
  65. package/externs/shaka/text.js +24 -0
  66. package/externs/webcrypto.js +18 -0
  67. package/karma.conf.js +1 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/client_side_ad.js +8 -0
  72. package/lib/ads/client_side_ad_manager.js +54 -90
  73. package/lib/ads/server_side_ad.js +7 -0
  74. package/lib/ads/server_side_ad_manager.js +7 -10
  75. package/lib/cast/cast_proxy.js +3 -2
  76. package/lib/cast/cast_receiver.js +2 -1
  77. package/lib/cast/cast_sender.js +2 -2
  78. package/lib/cast/cast_utils.js +1 -0
  79. package/lib/dash/dash_parser.js +141 -16
  80. package/lib/dash/segment_base.js +25 -1
  81. package/lib/dash/segment_list.js +19 -10
  82. package/lib/dash/segment_template.js +24 -11
  83. package/lib/hls/hls_parser.js +146 -40
  84. package/lib/media/drm_engine.js +7 -0
  85. package/lib/media/gap_jumping_controller.js +4 -2
  86. package/lib/media/media_source_engine.js +1 -2
  87. package/lib/media/quality_observer.js +291 -0
  88. package/lib/media/segment_index.js +8 -1
  89. package/lib/media/segment_reference.js +37 -4
  90. package/lib/media/streaming_engine.js +49 -7
  91. package/lib/net/http_fetch_plugin.js +31 -11
  92. package/lib/net/http_plugin_utils.js +1 -0
  93. package/lib/net/http_xhr_plugin.js +40 -14
  94. package/lib/net/networking_engine.js +83 -19
  95. package/lib/offline/download_info.js +68 -0
  96. package/lib/offline/download_manager.js +13 -5
  97. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  98. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  99. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  100. package/lib/offline/storage.js +393 -284
  101. package/lib/offline/stored_content_utils.js +2 -0
  102. package/lib/player.js +249 -43
  103. package/lib/polyfill/media_capabilities.js +36 -15
  104. package/lib/polyfill/mediasource.js +6 -0
  105. package/lib/polyfill/orientation.js +1 -1
  106. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  107. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  108. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  109. package/lib/polyfill/random_uuid.js +52 -0
  110. package/lib/text/cue.js +18 -0
  111. package/lib/text/mp4_ttml_parser.js +3 -0
  112. package/lib/text/text_engine.js +14 -2
  113. package/lib/text/ttml_text_parser.js +114 -60
  114. package/lib/text/ui_text_displayer.js +95 -41
  115. package/lib/util/cmcd_manager.js +672 -0
  116. package/lib/util/error.js +6 -0
  117. package/lib/util/fake_event.js +53 -7
  118. package/lib/util/language_utils.js +12 -0
  119. package/lib/util/mutex.js +52 -0
  120. package/lib/util/periods.js +102 -65
  121. package/lib/util/platform.js +13 -1
  122. package/lib/util/player_configuration.js +18 -0
  123. package/lib/util/stream_utils.js +99 -10
  124. package/lib/util/xml_utils.js +19 -0
  125. package/package.json +2 -2
  126. package/shaka-player.uncompiled.js +3 -0
  127. package/test/cast/cast_proxy_unit.js +12 -6
  128. package/test/cast/cast_utils_unit.js +1 -1
  129. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  130. package/test/dash/dash_parser_live_unit.js +101 -1
  131. package/test/dash/dash_parser_manifest_unit.js +222 -2
  132. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  133. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  134. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  135. package/test/hls/hls_live_unit.js +2 -0
  136. package/test/hls/hls_parser_unit.js +7 -1
  137. package/test/media/closed_caption_parser_unit.js +6 -1
  138. package/test/media/drm_engine_unit.js +47 -3
  139. package/test/media/quality_observer_unit.js +139 -0
  140. package/test/media/segment_reference_unit.js +16 -1
  141. package/test/media/streaming_engine_integration.js +4 -2
  142. package/test/media/streaming_engine_unit.js +147 -0
  143. package/test/net/http_plugin_unit.js +20 -15
  144. package/test/offline/storage_integration.js +108 -4
  145. package/test/player_integration.js +178 -8
  146. package/test/player_src_equals_integration.js +41 -8
  147. package/test/player_unit.js +19 -9
  148. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  149. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  150. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  151. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  152. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  153. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  154. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  155. package/test/test/assets/3675/dash_0.mpd +30 -0
  156. package/test/test/assets/3675/dash_1.mpd +30 -0
  157. package/test/test/assets/3675/dash_2.mpd +30 -0
  158. package/test/test/assets/3675/dash_3.mpd +30 -0
  159. package/test/test/assets/3675/dash_4.mpd +30 -0
  160. package/test/test/assets/3675/dash_5.mpd +26 -0
  161. package/test/test/assets/3675/init-stream0.m4s +0 -0
  162. package/test/test/assets/chapters.vtt +2 -2
  163. package/test/test/assets/chapters2.vtt +10 -0
  164. package/test/test/util/dash_parser_util.js +4 -0
  165. package/test/test/util/fake_ad.js +7 -0
  166. package/test/test/util/fake_ad_manager.js +1 -1
  167. package/test/test/util/fake_drm_engine.js +9 -2
  168. package/test/test/util/manifest_generator.js +3 -2
  169. package/test/test/util/test_scheme.js +14 -1
  170. package/test/test/util/ttml_utils.js +81 -0
  171. package/test/text/mp4_ttml_parser_unit.js +11 -18
  172. package/test/text/ttml_text_parser_unit.js +552 -227
  173. package/test/text/ui_text_displayer_unit.js +29 -17
  174. package/test/ui/localization_unit.js +15 -14
  175. package/test/ui/text_displayer_layout_unit.js +13 -1
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +291 -0
  178. package/test/util/stream_utils_unit.js +45 -4
  179. package/third_party/closure-uri/uri.js +21 -0
  180. package/ui/airplay_button.js +1 -0
  181. package/ui/audio_language_selection.js +7 -0
  182. package/ui/cast_button.js +3 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +45 -13
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +14 -5
  189. package/ui/fullscreen_button.js +1 -0
  190. package/ui/less/buttons.less +0 -4
  191. package/ui/less/containers.less +77 -7
  192. package/ui/less/overflow_menu.less +0 -10
  193. package/ui/less/tooltip.less +97 -0
  194. package/ui/locales/en.json +1 -0
  195. package/ui/locales/ru.json +6 -6
  196. package/ui/locales/source.json +4 -0
  197. package/ui/localization.js +28 -51
  198. package/ui/loop_button.js +5 -0
  199. package/ui/mute_button.js +2 -1
  200. package/ui/overflow_menu.js +5 -25
  201. package/ui/pip_button.js +7 -3
  202. package/ui/play_button.js +2 -2
  203. package/ui/playback_rate_selection.js +8 -9
  204. package/ui/resolution_selection.js +7 -0
  205. package/ui/rewind_button.js +15 -6
  206. package/ui/seek_bar.js +4 -2
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/statistics_button.js +253 -0
  210. package/ui/text_selection.js +12 -4
  211. package/ui/ui.js +30 -0
  212. package/ui/volume_bar.js +1 -1
  213. package/externs/media_capabilities.js +0 -16
@@ -0,0 +1,291 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.require('shaka.util.CmcdManager');
8
+ goog.require('shaka.util.ObjectUtils');
9
+
10
+ describe('CmcdManager', () => {
11
+ const CmcdManager = shaka.util.CmcdManager;
12
+ const uuidRegex =
13
+ '[A-F\\d]{8}-[A-F\\d]{4}-4[A-F\\d]{3}-[89AB][A-F\\d]{3}-[A-F\\d]{12}';
14
+ const data = {
15
+ 'sid': 'c936730c-031e-4a73-976f-92bc34039c60',
16
+ 'cid': 'xyz',
17
+ 'su': false,
18
+ 'nor': '../testing/3.m4v',
19
+ 'nrr': '0-99',
20
+ 'd': 6066.66,
21
+ 'mtp': 10049,
22
+ 'bs': true,
23
+ 'br': 52317,
24
+ 'v': 1,
25
+ 'pr': 1,
26
+ 'com.test-hello': 'world',
27
+ 'com.test-testing': 1234,
28
+ 'com.test-exists': true,
29
+ 'com.test-notExists': false,
30
+ 'com.test-token': Symbol('s'),
31
+ };
32
+
33
+ describe('Query serialization', () => {
34
+ it('produces correctly serialized data', () => {
35
+ const query = CmcdManager.toQuery(data);
36
+ const result = 'br=52317,bs,cid="xyz",com.test-exists,' +
37
+ 'com.test-hello="world",com.test-testing=1234,' +
38
+ 'com.test-token=s,d=6067,mtp=10000,' +
39
+ 'nor="..%2Ftesting%2F3.m4v",nrr="0-99",' +
40
+ 'sid="c936730c-031e-4a73-976f-92bc34039c60"';
41
+ expect(query).toBe(result);
42
+ });
43
+
44
+ it('escapes reserve character in string values', () => {
45
+ const query = CmcdManager.toQuery({
46
+ 'com.test-escape': 'Double "Quotes"',
47
+ });
48
+ const result = 'com.test-escape="Double \\"Quotes\\""';
49
+ expect(query).toBe(result);
50
+ });
51
+ });
52
+
53
+ describe('Header serialization', () => {
54
+ it('produces all header shards', () => {
55
+ const header = CmcdManager.toHeaders(data);
56
+ expect(header).toEqual({
57
+ 'CMCD-Object': 'br=52317,d=6067',
58
+ 'CMCD-Request': 'com.test-exists,com.test-hello="world",' +
59
+ 'com.test-testing=1234,com.test-token=s,mtp=10000,' +
60
+ 'nor="..%2Ftesting%2F3.m4v",nrr="0-99"',
61
+ 'CMCD-Session': 'cid="xyz",sid="c936730c-031e-4a73-976f-92bc34039c60"',
62
+ 'CMCD-Status': 'bs',
63
+ });
64
+ });
65
+
66
+ it('ignores empty shards', () => {
67
+ expect(CmcdManager.toHeaders({br: 200})).toEqual({
68
+ 'CMCD-Object': 'br=200',
69
+ });
70
+ });
71
+ });
72
+
73
+ describe('CmcdManager instance', () => {
74
+ const ObjectUtils = shaka.util.ObjectUtils;
75
+
76
+ const playerInterface = {
77
+ isLive: () => false,
78
+ getBandwidthEstimate: () => 10000000,
79
+ getBufferedInfo: () => ({
80
+ video: [
81
+ {start: 0, end: 5},
82
+ {start: 6, end: 31.234},
83
+ {start: 35, end: 40},
84
+ ],
85
+ }),
86
+ getVariantTracks: () => /** @type {Array.<shaka.extern.Track>} */([
87
+ {
88
+ type: 'variant',
89
+ bandwidth: 50000,
90
+ videoBandwidth: 40000,
91
+ audioBandWidth: 10000,
92
+ },
93
+ {
94
+ type: 'variant',
95
+ bandwidth: 5000000,
96
+ videoBandwidth: 4000000,
97
+ audioBandWidth: 1000000,
98
+ },
99
+ ]),
100
+ getPlaybackRate: () => 1,
101
+ getCurrentTime: () => 10,
102
+ };
103
+
104
+ const sid = '2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3';
105
+ const config = {
106
+ enabled: false,
107
+ sessionId: '',
108
+ contentId: 'testing',
109
+ useHeaders: false,
110
+ };
111
+
112
+ /** @type shaka.util.CmcdManager */
113
+ let cmcdManager = new CmcdManager(playerInterface, config);
114
+
115
+ const request = {
116
+ uris: ['https://test.com/test.mpd'],
117
+ method: 'GET',
118
+ body: null,
119
+ headers: {
120
+ testing: '1234',
121
+ },
122
+ allowCrossSiteCredentials: false,
123
+ retryParameters: /** @type {shaka.extern.RetryParameters} */({}),
124
+ licenseRequestType: null,
125
+ sessionId: null,
126
+ streamDataCallback: null,
127
+ };
128
+
129
+ const manifestInfo = {
130
+ format: shaka.util.CmcdManager.StreamingFormat.DASH,
131
+ };
132
+
133
+ const segmentInfo = {
134
+ type: 'video',
135
+ init: false,
136
+ duration: 3.33,
137
+ mimeType: 'application/mp4',
138
+ codecs: 'avc1.42001e',
139
+ bandwidth: 5234167,
140
+ };
141
+
142
+ describe('configuration', () => {
143
+ it('does not modify requests when disabled', () => {
144
+ const r = ObjectUtils.cloneObject(request);
145
+
146
+ cmcdManager.applyManifestData(r, manifestInfo);
147
+ expect(r.uris[0]).toBe(request.uris[0]);
148
+
149
+ cmcdManager.applySegmentData(r, segmentInfo);
150
+ expect(r.uris[0]).toBe(request.uris[0]);
151
+ });
152
+
153
+ it('generates a session id if not provided', () => {
154
+ config.enabled = true;
155
+ cmcdManager = new CmcdManager(playerInterface, config);
156
+
157
+ const r = ObjectUtils.cloneObject(request);
158
+
159
+ cmcdManager.applyManifestData(r, manifestInfo);
160
+ const regex = new RegExp(`sid%3D%22${uuidRegex}%22`, 'i');
161
+ expect(regex.test(r.uris[0])).toBe(true);
162
+ });
163
+ });
164
+
165
+ describe('query mode', () => {
166
+ beforeAll(() => {
167
+ config.sessionId = sid;
168
+ cmcdManager = new CmcdManager(playerInterface, config);
169
+ });
170
+
171
+ it('modifies manifest request uris', () => {
172
+ const r = ObjectUtils.cloneObject(request);
173
+ cmcdManager.applyManifestData(r, manifestInfo);
174
+ const uri = 'https://test.com/test.mpd?CMCD=cid%3D%22testing%22%2C' +
175
+ 'mtp%3D10000%2Cot%3Dm%2Csf%3Dd%2C' +
176
+ 'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
177
+ expect(r.uris[0]).toBe(uri);
178
+ });
179
+
180
+ it('modifies segment request uris', () => {
181
+ const r = ObjectUtils.cloneObject(request);
182
+ cmcdManager.applySegmentData(r, segmentInfo);
183
+ const uri = 'https://test.com/test.mpd?CMCD=bl%3D21200%2Cbr%3D5234%2Ccid%3D%22' +
184
+ 'testing%22%2Cd%3D3330%2Cmtp%3D10000%2Cot%3Dv%2Csf%3Dd%2C' +
185
+ 'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Cst%3Dv%2Csu%2C' +
186
+ 'tb%3D4000';
187
+ expect(r.uris[0]).toBe(uri);
188
+ });
189
+
190
+ it('modifies text request uris', () => {
191
+ const r = ObjectUtils.cloneObject(request);
192
+ cmcdManager.applyTextData(r);
193
+ const uri = 'https://test.com/test.mpd?CMCD=cid%3D%22' +
194
+ 'testing%22%2Cmtp%3D10000%2Cot%3Dc%2Csf%3Dd%2C' +
195
+ 'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
196
+ expect(r.uris[0]).toBe(uri);
197
+ });
198
+ });
199
+
200
+ describe('header mode', () => {
201
+ beforeAll(() => {
202
+ config.useHeaders = true;
203
+ cmcdManager = new CmcdManager(playerInterface, config);
204
+ });
205
+
206
+ it('modifies manifest request headers', () => {
207
+ const r = ObjectUtils.cloneObject(request);
208
+ cmcdManager.applyManifestData(r, manifestInfo);
209
+ expect(r.headers).toEqual({
210
+ 'testing': '1234',
211
+ 'CMCD-Object': 'ot=m',
212
+ 'CMCD-Request': 'mtp=10000,su',
213
+ 'CMCD-Session': 'cid="testing",sf=d,' +
214
+ 'sid="2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3"',
215
+ });
216
+ });
217
+
218
+ it('modifies segment request headers', () => {
219
+ const r = ObjectUtils.cloneObject(request);
220
+ cmcdManager.applySegmentData(r, segmentInfo);
221
+ expect(r.headers).toEqual({
222
+ 'testing': '1234',
223
+ 'CMCD-Object': 'br=5234,d=3330,ot=v,tb=4000',
224
+ 'CMCD-Request': 'bl=21200,mtp=10000,su',
225
+ 'CMCD-Session': 'cid="testing",sf=d,' +
226
+ 'sid="2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3",st=v',
227
+ });
228
+ });
229
+ });
230
+
231
+ describe('src= mode', () => {
232
+ it('modifies media stream uris', () => {
233
+ const r = cmcdManager
234
+ .appendSrcData('https://test.com/test.mp4', 'video/mp4');
235
+ const uri = 'https://test.com/test.mp4?CMCD=cid%3D%22testing%22%2C' +
236
+ 'mtp%3D10000%2Cot%3Dav%2Csf%3Dd%2C' +
237
+ 'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
238
+ expect(r).toBe(uri);
239
+ });
240
+
241
+ it('modifies manifest stream uris', () => {
242
+ const r = cmcdManager
243
+ .appendSrcData('https://test.com/test.m3u8', 'application/x-mpegurl');
244
+ const uri = 'https://test.com/test.m3u8?CMCD=cid%3D%22testing%22%2C' +
245
+ 'mtp%3D10000%2Cot%3Dm%2Csf%3Dd%2C' +
246
+ 'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
247
+ expect(r).toBe(uri);
248
+ });
249
+
250
+ it('modifies text track uris', () => {
251
+ const r = cmcdManager.appendTextTrackData('https://test.com/test.vtt');
252
+ const uri = 'https://test.com/test.vtt?CMCD=cid%3D%22testing%22%2C' +
253
+ 'mtp%3D10000%2Cot%3Dc%2Csf%3Dd%2C' +
254
+ 'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
255
+ expect(r).toBe(uri);
256
+ });
257
+ });
258
+
259
+ describe('adheres to the spec', () => {
260
+ beforeAll(() => {
261
+ cmcdManager.setBuffering(false);
262
+ cmcdManager.setBuffering(true);
263
+ });
264
+
265
+ it('sends bs only once', () => {
266
+ let r = ObjectUtils.cloneObject(request);
267
+ cmcdManager.applySegmentData(r, segmentInfo);
268
+ expect(r.headers['CMCD-Status']).toContain('bs');
269
+
270
+ r = ObjectUtils.cloneObject(request);
271
+ cmcdManager.applySegmentData(r, segmentInfo);
272
+ expect(r.headers['CMCD-Status']).not.toContain('bs');
273
+ });
274
+
275
+ it('sends su until buffering is complete', () => {
276
+ let r = ObjectUtils.cloneObject(request);
277
+ cmcdManager.applySegmentData(r, segmentInfo);
278
+ expect(r.headers['CMCD-Request']).toContain(',su');
279
+
280
+ r = ObjectUtils.cloneObject(request);
281
+ cmcdManager.applySegmentData(r, segmentInfo);
282
+ expect(r.headers['CMCD-Request']).toContain(',su');
283
+
284
+ cmcdManager.setBuffering(false);
285
+ r = ObjectUtils.cloneObject(request);
286
+ cmcdManager.applySegmentData(r, segmentInfo);
287
+ expect(r.headers['CMCD-Request']).not.toContain(',su');
288
+ });
289
+ });
290
+ });
291
+ });
@@ -16,14 +16,14 @@ describe('StreamUtils', () => {
16
16
  /** @type {!jasmine.Spy} */
17
17
  let decodingInfoSpy;
18
18
 
19
- const originalDecodingInfo = window.shakaMediaCapabilities.decodingInfo;
19
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
20
20
 
21
21
  beforeEach(() => {
22
22
  decodingInfoSpy = jasmine.createSpy('decodingInfo');
23
23
  });
24
24
 
25
25
  afterEach(() => {
26
- window.shakaMediaCapabilities.decodingInfo = originalDecodingInfo;
26
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
27
27
  });
28
28
 
29
29
  describe('filterStreamsByLanguageAndRole', () => {
@@ -511,7 +511,7 @@ describe('StreamUtils', () => {
511
511
  });
512
512
 
513
513
  it('handles decodingInfo exception', async () => {
514
- window.shakaMediaCapabilities.decodingInfo =
514
+ navigator.mediaCapabilities.decodingInfo =
515
515
  shaka.test.Util.spyFunc(decodingInfoSpy);
516
516
  // If decodingInfo() fails, setDecodingInfo should finish without throwing
517
517
  // an exception, and the variant should have no decodingInfo result.
@@ -537,6 +537,47 @@ describe('StreamUtils', () => {
537
537
  expect(manifest.variants.length).toBe(1);
538
538
  expect(manifest.variants[0].decodingInfos.length).toBe(0);
539
539
  });
540
+
541
+ it('includes transferFunction in config when hdr', async () => {
542
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
543
+
544
+ try {
545
+ navigator.mediaCapabilities.decodingInfo =
546
+ shaka.test.Util.spyFunc(decodingInfoSpy);
547
+
548
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
549
+ manifest.addVariant(0, (variant) => {
550
+ variant.addVideo(0, (stream) => {
551
+ stream.mime('video/mp4', 'avc1.640028');
552
+ stream.hdr = 'SDR';
553
+ });
554
+ });
555
+ manifest.addVariant(1, (variant) => {
556
+ variant.addVideo(1, (stream) => {
557
+ stream.mime('video/mp4', 'hvc1.2.4.L150.90');
558
+ stream.hdr = 'PQ';
559
+ });
560
+ });
561
+ manifest.addVariant(2, (variant) => {
562
+ variant.addVideo(2, (stream) => {
563
+ stream.mime('video/mp4', 'hvc1.2.4.L153.B0');
564
+ stream.hdr = 'HLG';
565
+ });
566
+ });
567
+ });
568
+
569
+ await StreamUtils.getDecodingInfosForVariants(manifest.variants,
570
+ /* usePersistentLicenses= */ false, /* srcEquals= */ false);
571
+ expect(decodingInfoSpy.calls.argsFor(0)[0].video.transferFunction)
572
+ .toBe('srgb');
573
+ expect(decodingInfoSpy.calls.argsFor(1)[0].video.transferFunction)
574
+ .toBe('pq');
575
+ expect(decodingInfoSpy.calls.argsFor(2)[0].video.transferFunction)
576
+ .toBe('hlg');
577
+ } finally {
578
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
579
+ }
580
+ });
540
581
  });
541
582
 
542
583
  describe('filterManifest', () => {
@@ -826,7 +867,7 @@ describe('StreamUtils', () => {
826
867
  });
827
868
  });
828
869
  });
829
- window.shakaMediaCapabilities.decodingInfo =
870
+ navigator.mediaCapabilities.decodingInfo =
830
871
  shaka.test.Util.spyFunc(decodingInfoSpy);
831
872
  decodingInfoSpy.and.callFake((config) => {
832
873
  const res = config.video.contentType.includes('notsmooth') ?
@@ -808,6 +808,27 @@ goog.Uri.QueryData.prototype.add = function(key, value) {
808
808
  return this;
809
809
  };
810
810
 
811
+ /**
812
+ * Sets a key value pair and removes all other keys with the same value.
813
+ *
814
+ * @param {string} key Name.
815
+ * @param {string} value Value.
816
+ * @return {!goog.Uri.QueryData} Instance of this object.
817
+ */
818
+ goog.Uri.QueryData.prototype.set = function(key, value) {
819
+ this.ensureKeyMapInitialized_();
820
+ // Invalidate the cache.
821
+ this.encodedQuery_ = null;
822
+
823
+ if (!this.keyMap_.hasOwnProperty(key)) {
824
+ this.add(key, value);
825
+ } else {
826
+ this.keyMap_[key] = [value];
827
+ }
828
+
829
+ return this;
830
+ };
831
+
811
832
 
812
833
  /**
813
834
  * @return {string} Encoded query string.
@@ -36,6 +36,7 @@ shaka.ui.AirPlayButton = class extends shaka.ui.Element {
36
36
  /** @private {!HTMLButtonElement} */
37
37
  this.airplayButton_ = shaka.util.Dom.createButton();
38
38
  this.airplayButton_.classList.add('shaka-airplay-button');
39
+ this.airplayButton_.classList.add('shaka-tooltip');
39
40
  this.airplayButton_.ariaPressed = 'false';
40
41
 
41
42
  /** @private {!HTMLElement} */
@@ -7,6 +7,7 @@
7
7
 
8
8
  goog.provide('shaka.ui.AudioLanguageSelection');
9
9
 
10
+ goog.require('shaka.ui.Controls');
10
11
  goog.require('shaka.ui.Enums');
11
12
  goog.require('shaka.ui.LanguageUtils');
12
13
  goog.require('shaka.ui.Locales');
@@ -31,6 +32,7 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
31
32
  super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.LANGUAGE);
32
33
 
33
34
  this.button.classList.add('shaka-language-button');
35
+ this.button.classList.add('shaka-tooltip-status');
34
36
  this.menu.classList.add('shaka-audio-languages');
35
37
 
36
38
  this.eventManager.listen(
@@ -71,6 +73,8 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
71
73
 
72
74
  this.controls.dispatchEvent(
73
75
  new shaka.util.FakeEvent('languageselectionupdated'));
76
+
77
+ this.button.setAttribute('shaka-status', this.currentSelection.innerText);
74
78
  }
75
79
 
76
80
  /** @private */
@@ -118,3 +122,6 @@ shaka.ui.AudioLanguageSelection.Factory = class {
118
122
 
119
123
  shaka.ui.OverflowMenu.registerElement(
120
124
  'language', new shaka.ui.AudioLanguageSelection.Factory());
125
+
126
+ shaka.ui.Controls.registerElement(
127
+ 'language', new shaka.ui.AudioLanguageSelection.Factory());
package/ui/cast_button.js CHANGED
@@ -41,6 +41,7 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
41
41
  /** @private {!HTMLButtonElement} */
42
42
  this.castButton_ = shaka.util.Dom.createButton();
43
43
  this.castButton_.classList.add('shaka-cast-button');
44
+ this.castButton_.classList.add('shaka-tooltip');
44
45
  this.castButton_.ariaPressed = 'false';
45
46
 
46
47
  /** @private {!HTMLElement} */
@@ -101,9 +102,8 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
101
102
  } catch (error) {
102
103
  this.castButton_.disabled = false;
103
104
  if (error.code != shaka.util.Error.Code.CAST_CANCELED_BY_USER) {
104
- this.controls.dispatchEvent(new shaka.util.FakeEvent('error', {
105
- detail: error,
106
- }));
105
+ this.controls.dispatchEvent(new shaka.util.FakeEvent(
106
+ 'error', (new Map()).set('detail', error)));
107
107
  }
108
108
  }
109
109
  }
@@ -0,0 +1,109 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+
8
+ goog.provide('shaka.ui.ContextMenu');
9
+
10
+ goog.require('goog.asserts');
11
+ goog.require('shaka.log');
12
+ goog.require('shaka.ui.Element');
13
+ goog.require('shaka.ui.Utils');
14
+ goog.require('shaka.util.Dom');
15
+ goog.requireType('shaka.ui.Controls');
16
+
17
+
18
+ /**
19
+ * @extends {shaka.ui.Element}
20
+ * @final
21
+ * @export
22
+ */
23
+ shaka.ui.ContextMenu = class extends shaka.ui.Element {
24
+ /**
25
+ * @param {!HTMLElement} parent
26
+ * @param {!shaka.ui.Controls} controls
27
+ */
28
+ constructor(parent, controls) {
29
+ super(parent, controls);
30
+
31
+ /** @private {!shaka.extern.UIConfiguration} */
32
+ this.config_ = this.controls.getConfig();
33
+
34
+ /** @private {HTMLElement} */
35
+ this.controlsContainer_ = this.controls.getControlsContainer();
36
+
37
+ /** @private {!Array.<shaka.extern.IUIElement>} */
38
+ this.children_ = [];
39
+
40
+ /** @private {!HTMLElement} */
41
+ this.contextMenu_ = shaka.util.Dom.createHTMLElement('div');
42
+ this.contextMenu_.classList.add('shaka-no-propagation');
43
+ this.contextMenu_.classList.add('shaka-context-menu');
44
+ this.contextMenu_.classList.add('shaka-hidden');
45
+
46
+ this.controlsContainer_.appendChild(this.contextMenu_);
47
+
48
+ this.eventManager.listen(this.controlsContainer_, 'contextmenu', (e) => {
49
+ if (this.contextMenu_.classList.contains('shaka-hidden')) {
50
+ e.preventDefault();
51
+
52
+ const controlsLocation =
53
+ this.controlsContainer_.getBoundingClientRect();
54
+ this.contextMenu_.style.left = `${e.clientX - controlsLocation.left}px`;
55
+ this.contextMenu_.style.top = `${e.clientY - controlsLocation.top}px`;
56
+
57
+ shaka.ui.Utils.setDisplay(this.contextMenu_, true);
58
+ } else {
59
+ shaka.ui.Utils.setDisplay(this.contextMenu_, false);
60
+ }
61
+ });
62
+
63
+ this.eventManager.listen(window, 'click', () => {
64
+ shaka.ui.Utils.setDisplay(this.contextMenu_, false);
65
+ });
66
+
67
+ this.createChildren_();
68
+ }
69
+
70
+ /** @override */
71
+ release() {
72
+ this.controlsContainer_ = null;
73
+
74
+ for (const element of this.children_) {
75
+ element.release();
76
+ }
77
+
78
+ this.children_ = [];
79
+ super.release();
80
+ }
81
+
82
+ /**
83
+ * @param {string} name
84
+ * @param {!shaka.extern.IUIElement.Factory} factory
85
+ * @export
86
+ */
87
+ static registerElement(name, factory) {
88
+ shaka.ui.ContextMenu.elementNamesToFactories_.set(name, factory);
89
+ }
90
+
91
+ /**
92
+ * @private
93
+ */
94
+ createChildren_() {
95
+ for (const name of this.config_.contextMenuElements) {
96
+ const factory =
97
+ shaka.ui.ContextMenu.elementNamesToFactories_.get(name);
98
+ if (factory) {
99
+ goog.asserts.assert(this.controls, 'Controls should not be null!');
100
+ this.children_.push(factory.create(this.contextMenu_, this.controls));
101
+ } else {
102
+ shaka.log.alwaysWarn('Unrecognized context menu element:', name);
103
+ }
104
+ }
105
+ }
106
+ };
107
+
108
+ /** @private {!Map.<string, !shaka.extern.IUIElement.Factory>} */
109
+ shaka.ui.ContextMenu.elementNamesToFactories_ = new Map();