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
@@ -0,0 +1,828 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * @fileoverview
9
+ * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
10
+ */
11
+
12
+ goog.provide('shaka.ads.MediaTailorAdManager');
13
+
14
+ goog.require('goog.asserts');
15
+ goog.require('shaka.ads.MediaTailorAd');
16
+ goog.require('shaka.log');
17
+ goog.require('shaka.net.NetworkingEngine');
18
+ goog.require('shaka.util.IReleasable');
19
+ goog.require('shaka.util.StringUtils');
20
+
21
+
22
+ /**
23
+ * A class responsible for MediaTailor ad interactions.
24
+ *
25
+ * @implements {shaka.util.IReleasable}
26
+ */
27
+ shaka.ads.MediaTailorAdManager = class {
28
+ /**
29
+ * @param {HTMLElement} adContainer
30
+ * @param {shaka.net.NetworkingEngine} networkingEngine
31
+ * @param {HTMLMediaElement} video
32
+ * @param {function(!shaka.util.FakeEvent)} onEvent
33
+ */
34
+ constructor(adContainer, networkingEngine, video, onEvent) {
35
+ /** @private {HTMLElement} */
36
+ this.adContainer_ = adContainer;
37
+
38
+ /** @private {shaka.net.NetworkingEngine} */
39
+ this.networkingEngine_ = networkingEngine;
40
+
41
+ /** @private {HTMLMediaElement} */
42
+ this.video_ = video;
43
+
44
+ /** @private {?shaka.util.PublicPromise.<string>} */
45
+ this.streamPromise_ = null;
46
+
47
+ /** @private {number} */
48
+ this.streamRequestStartTime_ = NaN;
49
+
50
+ /** @private {function(!shaka.util.FakeEvent)} */
51
+ this.onEvent_ = onEvent;
52
+
53
+ /** @private {boolean} */
54
+ this.isLive_ = false;
55
+
56
+ /**
57
+ * Time to seek to after an ad if that ad was played as the result of
58
+ * snapback.
59
+ * @private {?number}
60
+ */
61
+ this.snapForwardTime_ = null;
62
+
63
+ /** @private {!Array.<!mediaTailor.AdBreak>} */
64
+ this.adBreaks_ = [];
65
+
66
+ /** @private {!Array.<string>} */
67
+ this.playedAds_ = [];
68
+
69
+ /** @private {?shaka.ads.MediaTailorAd} */
70
+ this.ad_ = null;
71
+
72
+ /** @private {?mediaTailor.Ad} */
73
+ this.mediaTailorAd_ = null;
74
+
75
+ /** @private {?mediaTailor.AdBreak} */
76
+ this.mediaTailorAdBreak_ = null;
77
+
78
+ /** @private {!Map.<string,!Array.<mediaTailorExternalResource.App>>} */
79
+ this.staticResources_ = new Map();
80
+
81
+ /** @private {!Array.<{target: EventTarget, type: string,
82
+ * listener: shaka.util.EventManager.ListenerType}>}
83
+ */
84
+ this.adListeners_ = [];
85
+
86
+ /** @private {!Array.<string>} */
87
+ this.eventsSent = [];
88
+
89
+ /** @private {string} */
90
+ this.trackingUrl_ = '';
91
+
92
+ /** @private {boolean} */
93
+ this.firstTrackingRequest_ = true;
94
+
95
+ /** @private {string} */
96
+ this.backupUrl_ = '';
97
+
98
+ /** @private {!Array.<!shaka.extern.AdCuePoint>} */
99
+ this.currentCuePoints_ = [];
100
+
101
+ /** @private {shaka.util.EventManager} */
102
+ this.eventManager_ = new shaka.util.EventManager();
103
+ }
104
+
105
+ /**
106
+ * @param {string} url
107
+ * @param {Object} adsParams
108
+ * @param {string} backupUrl
109
+ * @return {!Promise.<string>}
110
+ */
111
+ streamRequest(url, adsParams, backupUrl) {
112
+ if (this.streamPromise_) {
113
+ return Promise.reject(new shaka.util.Error(
114
+ shaka.util.Error.Severity.RECOVERABLE,
115
+ shaka.util.Error.Category.ADS,
116
+ shaka.util.Error.Code.CURRENT_DAI_REQUEST_NOT_FINISHED));
117
+ }
118
+
119
+ this.streamPromise_ = new shaka.util.PublicPromise();
120
+ this.requestSessionInfo_(url, adsParams);
121
+ this.backupUrl_ = backupUrl || '';
122
+
123
+ this.streamRequestStartTime_ = Date.now() / 1000;
124
+
125
+ return this.streamPromise_;
126
+ }
127
+
128
+ /**
129
+ * Resets the MediaTailor manager and removes any continuous polling.
130
+ */
131
+ stop() {
132
+ for (const listener of this.adListeners_) {
133
+ this.eventManager_.unlisten(
134
+ listener.target, listener.type, listener.listener);
135
+ }
136
+ this.onEnded_();
137
+ this.adListeners_ = [];
138
+ this.eventsSent = [];
139
+ this.trackingUrl_ = '';
140
+ this.firstTrackingRequest_ = true;
141
+ this.backupUrl_ = '';
142
+ this.snapForwardTime_ = null;
143
+ this.adBreaks_ = [];
144
+ this.playedAds_ = [];
145
+ this.staticResources_.clear();
146
+ }
147
+
148
+ /** @override */
149
+ release() {
150
+ this.stop();
151
+ if (this.eventManager_) {
152
+ this.eventManager_.release();
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Fired when the manifest is updated
158
+ *
159
+ * @param {boolean} isLive
160
+ */
161
+ onManifestUpdated(isLive) {
162
+ this.isLive_ = isLive;
163
+ if (this.trackingUrl_ != '') {
164
+ this.requestTrackingInfo_(
165
+ this.trackingUrl_, this.firstTrackingRequest_);
166
+ this.firstTrackingRequest_ = false;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * @return {!Array.<!shaka.extern.AdCuePoint>}
172
+ */
173
+ getCuePoints() {
174
+ const cuePoints = [];
175
+ for (const adbreak of this.adBreaks_) {
176
+ for (const ad of adbreak.ads) {
177
+ /** @type {!shaka.extern.AdCuePoint} */
178
+ const cuePoint = {
179
+ start: ad.startTimeInSeconds,
180
+ end: ad.startTimeInSeconds + ad.durationInSeconds,
181
+ };
182
+ cuePoints.push(cuePoint);
183
+ }
184
+ }
185
+ return cuePoints;
186
+ }
187
+
188
+ /**
189
+ * @param {string} url
190
+ * @param {Object} adsParams
191
+ * @private
192
+ */
193
+ async requestSessionInfo_(url, adsParams) {
194
+ const type = shaka.net.NetworkingEngine.RequestType.ADS;
195
+ const request = shaka.net.NetworkingEngine.makeRequest(
196
+ [url],
197
+ shaka.net.NetworkingEngine.defaultRetryParameters());
198
+ request.method = 'POST';
199
+ if (adsParams) {
200
+ const body = JSON.stringify(adsParams);
201
+ request.body = shaka.util.StringUtils.toUTF8(body);
202
+ }
203
+
204
+ const op = this.networkingEngine_.request(type, request);
205
+ try {
206
+ const response = await op.promise;
207
+ const data = shaka.util.StringUtils.fromUTF8(response.data);
208
+ const dataAsJson =
209
+ /** @type {!mediaTailor.SessionResponse} */ (JSON.parse(data));
210
+ if (dataAsJson.manifestUrl && dataAsJson.trackingUrl) {
211
+ const baseUri = new goog.Uri(url);
212
+ const relativeTrackingUri = new goog.Uri(dataAsJson.trackingUrl);
213
+ this.trackingUrl_ = baseUri.resolve(relativeTrackingUri).toString();
214
+ const now = Date.now() / 1000;
215
+ const loadTime = now - this.streamRequestStartTime_;
216
+ this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED,
217
+ (new Map()).set('loadTime', loadTime)));
218
+ const relativeManifestUri = new goog.Uri(dataAsJson.manifestUrl);
219
+ this.streamPromise_.resolve(
220
+ baseUri.resolve(relativeManifestUri).toString());
221
+ this.streamPromise_ = null;
222
+ } else {
223
+ throw new Error('Insufficient data from MediaTailor.');
224
+ }
225
+ } catch (e) {
226
+ if (!this.backupUrl_.length) {
227
+ this.streamPromise_.reject('MediaTailor request returned an error ' +
228
+ 'and there was no backup asset uri provided.');
229
+ this.streamPromise_ = null;
230
+ return;
231
+ }
232
+
233
+ shaka.log.warning('MediaTailor request returned an error. ' +
234
+ 'Falling back to the backup asset uri.');
235
+ this.streamPromise_.resolve(this.backupUrl_);
236
+ this.streamPromise_ = null;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * @param {string} trackingUrl
242
+ * @param {boolean} firstRequest
243
+ * @private
244
+ */
245
+ async requestTrackingInfo_(trackingUrl, firstRequest) {
246
+ const type = shaka.net.NetworkingEngine.RequestType.ADS;
247
+ const request = shaka.net.NetworkingEngine.makeRequest(
248
+ [trackingUrl],
249
+ shaka.net.NetworkingEngine.defaultRetryParameters());
250
+
251
+ const op = this.networkingEngine_.request(type, request);
252
+ try {
253
+ const response = await op.promise;
254
+ let cuepoints = [];
255
+ const data = shaka.util.StringUtils.fromUTF8(response.data);
256
+ const dataAsJson =
257
+ /** @type {!mediaTailor.TrackingResponse} */ (JSON.parse(data));
258
+ if (dataAsJson.avails.length > 0) {
259
+ if (JSON.stringify(this.adBreaks_) !=
260
+ JSON.stringify(dataAsJson.avails)) {
261
+ this.adBreaks_ = dataAsJson.avails;
262
+ for (const adbreak of this.adBreaks_) {
263
+ for (const nonLinearAd of adbreak.nonLinearAdsList) {
264
+ for (const nonLinearAdResource of nonLinearAd.nonLinearAdList) {
265
+ this.requestStaticResource_(nonLinearAdResource);
266
+ }
267
+ }
268
+ }
269
+ cuepoints = this.getCuePoints();
270
+ this.onEvent_(new shaka.util.FakeEvent(
271
+ shaka.ads.AdManager.CUEPOINTS_CHANGED,
272
+ (new Map()).set('cuepoints', cuepoints)));
273
+ }
274
+ } else {
275
+ if (this.adBreaks_.length) {
276
+ this.onEvent_(new shaka.util.FakeEvent(
277
+ shaka.ads.AdManager.CUEPOINTS_CHANGED,
278
+ (new Map()).set('cuepoints', cuepoints)));
279
+ }
280
+ this.onEnded_();
281
+ this.adBreaks_ = [];
282
+ }
283
+ if (firstRequest && (this.isLive_ || cuepoints.length > 0)) {
284
+ this.setupAdBreakListeners_();
285
+ }
286
+ } catch (e) {}
287
+ }
288
+
289
+ /**
290
+ * @param {mediaTailor.NonLinearAd} nonLinearAd
291
+ * @private
292
+ */
293
+ async requestStaticResource_(nonLinearAd) {
294
+ if (!nonLinearAd.staticResource) {
295
+ return;
296
+ }
297
+ const cacheKey = this.getCacheKeyForNonLinear_(nonLinearAd);
298
+ const staticResource = this.staticResources_.get(cacheKey);
299
+ if (staticResource) {
300
+ return;
301
+ }
302
+
303
+ const type = shaka.net.NetworkingEngine.RequestType.ADS;
304
+ const request = shaka.net.NetworkingEngine.makeRequest(
305
+ [nonLinearAd.staticResource],
306
+ shaka.net.NetworkingEngine.defaultRetryParameters());
307
+
308
+ const op = this.networkingEngine_.request(type, request);
309
+ try {
310
+ this.staticResources_.set(cacheKey, []);
311
+ const response = await op.promise;
312
+ const data = shaka.util.StringUtils.fromUTF8(response.data);
313
+ const dataAsJson =
314
+ /** @type {!mediaTailorExternalResource.Response} */ (JSON.parse(data));
315
+ const apps = dataAsJson.apps;
316
+ this.staticResources_.set(cacheKey, apps);
317
+ } catch (e) {
318
+ this.staticResources_.delete(cacheKey);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * @param {mediaTailor.NonLinearAd} nonLinearAd
324
+ * @return {string}
325
+ * @private
326
+ */
327
+ getCacheKeyForNonLinear_(nonLinearAd) {
328
+ return [
329
+ nonLinearAd.adId,
330
+ nonLinearAd.adParameters,
331
+ nonLinearAd.adSystem,
332
+ nonLinearAd.adTitle,
333
+ nonLinearAd.creativeAdId,
334
+ nonLinearAd.creativeId,
335
+ nonLinearAd.creativeSequence,
336
+ nonLinearAd.height,
337
+ nonLinearAd.width,
338
+ nonLinearAd.staticResource,
339
+ ].join('');
340
+ }
341
+
342
+ /**
343
+ * Setup Ad Break listeners
344
+ *
345
+ * @private
346
+ */
347
+ setupAdBreakListeners_() {
348
+ this.onTimeupdate_();
349
+ if (!this.isLive_) {
350
+ this.checkForSnapback_();
351
+ this.eventManager_.listen(this.video_, 'seeked', () => {
352
+ this.checkForSnapback_();
353
+ });
354
+ this.eventManager_.listen(this.video_, 'ended', () => {
355
+ this.onEnded_();
356
+ });
357
+ }
358
+ this.eventManager_.listen(this.video_, 'timeupdate', () => {
359
+ this.onTimeupdate_();
360
+ });
361
+ }
362
+
363
+ /**
364
+ * If a seek jumped over the ad break, return to the start of the
365
+ * ad break, then complete the seek after the ad played through.
366
+ *
367
+ * @private
368
+ */
369
+ checkForSnapback_() {
370
+ const currentTime = this.video_.currentTime;
371
+ if (currentTime == 0 || this.snapForwardTime_ != null) {
372
+ return;
373
+ }
374
+
375
+ let previousAdBreak;
376
+ let previousAd;
377
+ for (const adbreak of this.adBreaks_) {
378
+ for (const ad of adbreak.ads) {
379
+ if (!previousAd) {
380
+ if (ad.startTimeInSeconds < currentTime) {
381
+ previousAd = ad;
382
+ previousAdBreak = adbreak;
383
+ }
384
+ } else if (ad.startTimeInSeconds < currentTime &&
385
+ ad.startTimeInSeconds >
386
+ (previousAd.startTimeInSeconds + previousAd.durationInSeconds)) {
387
+ previousAd = ad;
388
+ previousAdBreak = adbreak;
389
+ break;
390
+ }
391
+ }
392
+ }
393
+
394
+ // The cue point gets marked as 'played' as soon as the playhead hits it
395
+ // (at the start of an ad), so when we come back to this method as a result
396
+ // of seeking back to the user-selected time, the 'played' flag will be set.
397
+ if (previousAdBreak && previousAd &&
398
+ !this.playedAds_.includes(previousAd.adId)) {
399
+ shaka.log.info('Seeking back to the start of the ad break at ' +
400
+ previousAdBreak.startTimeInSeconds +
401
+ ' and will return to ' + currentTime);
402
+ this.snapForwardTime_ = currentTime;
403
+ this.video_.currentTime = previousAdBreak.startTimeInSeconds;
404
+ }
405
+ }
406
+
407
+ /**
408
+ * @private
409
+ */
410
+ onAdBreakEnded_() {
411
+ const currentTime = this.video_.currentTime;
412
+ // If the ad break was a result of snapping back (a user seeked over
413
+ // an ad break and was returned to it), seek forward to the point,
414
+ // originally chosen by the user.
415
+ if (this.snapForwardTime_ && this.snapForwardTime_ > currentTime) {
416
+ this.video_.currentTime = this.snapForwardTime_;
417
+ }
418
+ this.snapForwardTime_ = null;
419
+ }
420
+
421
+ /**
422
+ * @private
423
+ */
424
+ onTimeupdate_() {
425
+ if (!this.video_.duration) {
426
+ // Can't play yet. Ignore.
427
+ return;
428
+ }
429
+ if (!this.ad_ && !this.adBreaks_.length) {
430
+ // No ads
431
+ return;
432
+ }
433
+ const currentTime = this.video_.currentTime;
434
+ let previousAd = false;
435
+ if (this.ad_) {
436
+ previousAd = true;
437
+ goog.asserts.assert(this.mediaTailorAd_, 'Ad should be defined');
438
+ this.sendInProgressEvents_(currentTime, this.mediaTailorAd_);
439
+ const remainingTime = this.ad_.getRemainingTime();
440
+ const duration = this.ad_.getDuration();
441
+ if (duration > 0 && (remainingTime <= 0 || remainingTime > duration)) {
442
+ this.onEnded_();
443
+ }
444
+ }
445
+ if (!this.ad_ || !this.ad_.isLinear()) {
446
+ this.checkLinearAds_(currentTime);
447
+ if (!this.ad_) {
448
+ this.checkNonLinearAds_(currentTime);
449
+ }
450
+ if (previousAd && !this.ad_) {
451
+ this.onAdBreakEnded_();
452
+ }
453
+ }
454
+ }
455
+
456
+ /**
457
+ * @param {number} currentTime
458
+ * @param {mediaTailor.Ad} ad
459
+ * @private
460
+ */
461
+ sendInProgressEvents_(currentTime, ad) {
462
+ const MediaTailorAdManager = shaka.ads.MediaTailorAdManager;
463
+ const firstQuartileTime = ad.startTimeInSeconds +
464
+ 0.25 * ad.durationInSeconds;
465
+ const midpointTime = ad.startTimeInSeconds +
466
+ 0.5 * ad.durationInSeconds;
467
+ const thirdQuartileTime = ad.startTimeInSeconds +
468
+ 0.75 * ad.durationInSeconds;
469
+ if (currentTime >= firstQuartileTime &&
470
+ !this.eventsSent.includes(MediaTailorAdManager.FIRSTQUARTILE_)) {
471
+ this.eventsSent.push(MediaTailorAdManager.FIRSTQUARTILE_);
472
+ this.sendTrackingEvent_(MediaTailorAdManager.FIRSTQUARTILE_);
473
+ } else if (currentTime >= midpointTime &&
474
+ !this.eventsSent.includes(MediaTailorAdManager.MIDPOINT_)) {
475
+ this.eventsSent.push(MediaTailorAdManager.MIDPOINT_);
476
+ this.sendTrackingEvent_(MediaTailorAdManager.MIDPOINT_);
477
+ } else if (currentTime >= thirdQuartileTime &&
478
+ !this.eventsSent.includes(MediaTailorAdManager.THIRDQUARTILE_)) {
479
+ this.eventsSent.push(MediaTailorAdManager.THIRDQUARTILE_);
480
+ this.sendTrackingEvent_(MediaTailorAdManager.THIRDQUARTILE_);
481
+ }
482
+ }
483
+
484
+ /**
485
+ * @param {number} currentTime
486
+ * @private
487
+ */
488
+ checkLinearAds_(currentTime) {
489
+ const MediaTailorAdManager = shaka.ads.MediaTailorAdManager;
490
+ for (const adbreak of this.adBreaks_) {
491
+ if (this.ad_ && this.ad_.isLinear()) {
492
+ break;
493
+ }
494
+ for (let i = 0; i < adbreak.ads.length; i++) {
495
+ const ad = adbreak.ads[i];
496
+ const startTime = ad.startTimeInSeconds;
497
+ const endTime = ad.startTimeInSeconds + ad.durationInSeconds;
498
+ if (startTime <= currentTime && endTime > currentTime) {
499
+ if (this.playedAds_.includes(ad.adId)) {
500
+ if (this.video_.ended) {
501
+ continue;
502
+ }
503
+ this.video_.currentTime = endTime;
504
+ return;
505
+ }
506
+ this.onEnded_();
507
+ this.mediaTailorAdBreak_ = adbreak;
508
+ this.ad_ = new shaka.ads.MediaTailorAd(
509
+ ad,
510
+ /* adPosition= */ i + 1,
511
+ /* totalAds= */ adbreak.ads.length,
512
+ /* isLinear= */ true,
513
+ this.video_);
514
+ this.mediaTailorAd_ = ad;
515
+ if (i === 0) {
516
+ this.sendTrackingEvent_(MediaTailorAdManager.BREAKSTART_);
517
+ }
518
+ this.setupCurrentAdListeners_();
519
+ break;
520
+ }
521
+ }
522
+ }
523
+ }
524
+
525
+ /**
526
+ * @param {number} currentTime
527
+ * @private
528
+ */
529
+ checkNonLinearAds_(currentTime) {
530
+ const MediaTailorAdManager = shaka.ads.MediaTailorAdManager;
531
+ for (const adbreak of this.adBreaks_) {
532
+ if (this.ad_) {
533
+ break;
534
+ }
535
+ for (let i = 0; i < adbreak.nonLinearAdsList.length; i++) {
536
+ const ad = adbreak.nonLinearAdsList[i];
537
+ if (!ad.nonLinearAdList.length) {
538
+ continue;
539
+ }
540
+ const startTime = adbreak.startTimeInSeconds;
541
+ const cacheKey = this.getCacheKeyForNonLinear_(ad.nonLinearAdList[0]);
542
+ const staticResource = this.staticResources_.get(cacheKey);
543
+ if (startTime <= currentTime &&
544
+ staticResource && staticResource.length) {
545
+ this.onEnded_();
546
+ this.displayNonLinearAd_(staticResource);
547
+ this.mediaTailorAdBreak_ = adbreak;
548
+ this.ad_ = new shaka.ads.MediaTailorAd(
549
+ ad,
550
+ /* adPosition= */ i + 1,
551
+ /* totalAds= */ adbreak.ads.length,
552
+ /* isLinear= */ false,
553
+ this.video_);
554
+ this.mediaTailorAd_ = ad;
555
+ if (i === 0) {
556
+ this.sendTrackingEvent_(MediaTailorAdManager.BREAKSTART_);
557
+ }
558
+ this.setupCurrentAdListeners_();
559
+ break;
560
+ }
561
+ }
562
+ }
563
+ }
564
+
565
+ /**
566
+ * @param {!Array.<mediaTailorExternalResource.App>} apps
567
+ * @private
568
+ */
569
+ displayNonLinearAd_(apps) {
570
+ for (const app of apps) {
571
+ if (!app.data.source.length) {
572
+ continue;
573
+ }
574
+ const imageElement = document.createElement('img');
575
+ imageElement.setAttribute('src', app.data.source[0].url);
576
+ imageElement.style.top = (app.placeholder.top || 0) + '%';
577
+ imageElement.style.height = (100 - (app.placeholder.top || 0)) + '%';
578
+ imageElement.style.left = (app.placeholder.left || 0) + '%';
579
+ imageElement.style.maxWidth = (100 - (app.placeholder.left || 0)) + '%';
580
+ imageElement.style.objectFit = 'contain';
581
+ imageElement.style.position = 'absolute';
582
+ this.adContainer_.appendChild(imageElement);
583
+ }
584
+ }
585
+
586
+ /**
587
+ * @private
588
+ */
589
+ onEnded_() {
590
+ if (this.ad_) {
591
+ // Remove all child nodes
592
+ while (this.adContainer_.lastChild) {
593
+ this.adContainer_.removeChild(this.adContainer_.firstChild);
594
+ }
595
+ if (!this.isLive_) {
596
+ this.playedAds_.push(this.mediaTailorAd_.adId);
597
+ }
598
+ this.removeCurrentAdListeners_();
599
+ const position = this.ad_.getPositionInSequence();
600
+ const totalAdsInBreak = this.ad_.getSequenceLength();
601
+ if (position === totalAdsInBreak) {
602
+ this.sendTrackingEvent_(shaka.ads.MediaTailorAdManager.BREAKEND_);
603
+ }
604
+ this.ad_ = null;
605
+ this.mediaTailorAd_ = null;
606
+ this.mediaTailorAdBreak_ = null;
607
+ }
608
+ }
609
+
610
+ /**
611
+ * @private
612
+ */
613
+ setupCurrentAdListeners_() {
614
+ const MediaTailorAdManager = shaka.ads.MediaTailorAdManager;
615
+ this.sendTrackingEvent_(MediaTailorAdManager.IMPRESSION_);
616
+ this.sendTrackingEvent_(MediaTailorAdManager.START_);
617
+ this.adListeners_.push({
618
+ target: this.video_,
619
+ type: 'volumechange',
620
+ listener: () => {
621
+ if (this.video_.muted) {
622
+ this.sendTrackingEvent_(MediaTailorAdManager.MUTE_);
623
+ }
624
+ },
625
+ });
626
+ this.adListeners_.push({
627
+ target: this.video_,
628
+ type: 'volumechange',
629
+ listener: () => {
630
+ if (!this.video_.muted) {
631
+ this.sendTrackingEvent_(MediaTailorAdManager.UNMUTE_);
632
+ }
633
+ },
634
+ });
635
+ this.adListeners_.push({
636
+ target: this.video_,
637
+ type: 'play',
638
+ listener: () => {
639
+ this.sendTrackingEvent_(MediaTailorAdManager.RESUME_);
640
+ },
641
+ });
642
+ this.adListeners_.push({
643
+ target: this.video_,
644
+ type: 'pause',
645
+ listener: () => {
646
+ this.sendTrackingEvent_(MediaTailorAdManager.PAUSE_);
647
+ },
648
+ });
649
+ for (const listener of this.adListeners_) {
650
+ this.eventManager_.listen(
651
+ listener.target, listener.type, listener.listener);
652
+ }
653
+ }
654
+
655
+ /**
656
+ * @private
657
+ */
658
+ removeCurrentAdListeners_() {
659
+ this.sendTrackingEvent_(shaka.ads.MediaTailorAdManager.COMPLETE_);
660
+ for (const listener of this.adListeners_) {
661
+ this.eventManager_.unlisten(
662
+ listener.target, listener.type, listener.listener);
663
+ }
664
+ this.adListeners_ = [];
665
+ this.eventsSent = [];
666
+ }
667
+
668
+ /**
669
+ * @param {string} eventType
670
+ * @private
671
+ */
672
+ sendTrackingEvent_(eventType) {
673
+ let trackingEvent = this.mediaTailorAd_.trackingEvents.find(
674
+ (event) => event.eventType == eventType);
675
+ if (!trackingEvent) {
676
+ trackingEvent = this.mediaTailorAdBreak_.adBreakTrackingEvents.find(
677
+ (event) => event.eventType == eventType);
678
+ }
679
+ if (trackingEvent) {
680
+ const type = shaka.net.NetworkingEngine.RequestType.ADS;
681
+ for (const beaconUrl of trackingEvent.beaconUrls) {
682
+ if (!beaconUrl || beaconUrl == '') {
683
+ continue;
684
+ }
685
+ const request = shaka.net.NetworkingEngine.makeRequest(
686
+ [beaconUrl],
687
+ shaka.net.NetworkingEngine.defaultRetryParameters());
688
+ request.method = 'POST';
689
+ this.networkingEngine_.request(type, request);
690
+ }
691
+ }
692
+ switch (eventType) {
693
+ case shaka.ads.MediaTailorAdManager.IMPRESSION_:
694
+ this.onEvent_(
695
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_IMPRESSION));
696
+ break;
697
+ case shaka.ads.MediaTailorAdManager.START_:
698
+ this.onEvent_(
699
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STARTED,
700
+ (new Map()).set('ad', this.ad_)));
701
+ break;
702
+ case shaka.ads.MediaTailorAdManager.MUTE_:
703
+ this.onEvent_(
704
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_MUTED));
705
+ break;
706
+ case shaka.ads.MediaTailorAdManager.UNMUTE_:
707
+ this.onEvent_(
708
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_VOLUME_CHANGED));
709
+ break;
710
+ case shaka.ads.MediaTailorAdManager.RESUME_:
711
+ this.onEvent_(
712
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_RESUMED));
713
+ break;
714
+ case shaka.ads.MediaTailorAdManager.PAUSE_:
715
+ this.onEvent_(
716
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_PAUSED));
717
+ break;
718
+ case shaka.ads.MediaTailorAdManager.FIRSTQUARTILE_:
719
+ this.onEvent_(
720
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_FIRST_QUARTILE));
721
+ break;
722
+ case shaka.ads.MediaTailorAdManager.MIDPOINT_:
723
+ this.onEvent_(
724
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_MIDPOINT));
725
+ break;
726
+ case shaka.ads.MediaTailorAdManager.THIRDQUARTILE_:
727
+ this.onEvent_(
728
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_THIRD_QUARTILE));
729
+ break;
730
+ case shaka.ads.MediaTailorAdManager.COMPLETE_:
731
+ this.onEvent_(
732
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_COMPLETE));
733
+ this.onEvent_(
734
+ new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED));
735
+ break;
736
+ case shaka.ads.MediaTailorAdManager.BREAKSTART_:
737
+ this.adContainer_.setAttribute('ad-active', 'true');
738
+ break;
739
+ case shaka.ads.MediaTailorAdManager.BREAKEND_:
740
+ this.adContainer_.removeAttribute('ad-active');
741
+ break;
742
+ }
743
+ }
744
+ };
745
+
746
+
747
+ /**
748
+ * @const {string}
749
+ * @private
750
+ */
751
+ shaka.ads.MediaTailorAdManager.IMPRESSION_ = 'impression';
752
+
753
+
754
+ /**
755
+ * @const {string}
756
+ * @private
757
+ */
758
+ shaka.ads.MediaTailorAdManager.START_ = 'start';
759
+
760
+
761
+ /**
762
+ * @const {string}
763
+ * @private
764
+ */
765
+ shaka.ads.MediaTailorAdManager.MUTE_ = 'mute';
766
+
767
+
768
+ /**
769
+ * @const {string}
770
+ * @private
771
+ */
772
+ shaka.ads.MediaTailorAdManager.UNMUTE_ = 'unmute';
773
+
774
+
775
+ /**
776
+ * @const {string}
777
+ * @private
778
+ */
779
+ shaka.ads.MediaTailorAdManager.RESUME_ = 'resume';
780
+
781
+
782
+ /**
783
+ * @const {string}
784
+ * @private
785
+ */
786
+ shaka.ads.MediaTailorAdManager.PAUSE_ = 'pause';
787
+
788
+
789
+ /**
790
+ * @const {string}
791
+ * @private
792
+ */
793
+ shaka.ads.MediaTailorAdManager.FIRSTQUARTILE_ = 'firstQuartile';
794
+
795
+
796
+ /**
797
+ * @const {string}
798
+ * @private
799
+ */
800
+ shaka.ads.MediaTailorAdManager.MIDPOINT_ = 'midpoint';
801
+
802
+
803
+ /**
804
+ * @const {string}
805
+ * @private
806
+ */
807
+ shaka.ads.MediaTailorAdManager.THIRDQUARTILE_ = 'thirdQuartile';
808
+
809
+
810
+ /**
811
+ * @const {string}
812
+ * @private
813
+ */
814
+ shaka.ads.MediaTailorAdManager.COMPLETE_ = 'complete';
815
+
816
+
817
+ /**
818
+ * @const {string}
819
+ * @private
820
+ */
821
+ shaka.ads.MediaTailorAdManager.BREAKSTART_ = 'breakStart';
822
+
823
+
824
+ /**
825
+ * @const {string}
826
+ * @private
827
+ */
828
+ shaka.ads.MediaTailorAdManager.BREAKEND_ = 'breakEnd';