@kkcompany/player 2.25.0-canary.24 → 2.25.0-canary.25

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.
@@ -0,0 +1,717 @@
1
+ import { A as once$1, i as isLiveDuration } from "./mediaBindings-CoY60lQw.mjs";
2
+ import { t as loadScript_default } from "./loadScript-Ct19kU9g.mjs";
3
+ import { n as pipeEvents } from "./events-B3vI3Srm.mjs";
4
+ import mitt from "mitt";
5
+
6
+ //#region src/playerCore/fetchManifests.js
7
+ /** @param {string} m3u8Manifest */
8
+ const getManifestUrl = ({ url, data }) => {
9
+ const lines = data.split("\n");
10
+ const i = lines.findIndex((line) => line.startsWith("#EXT-X-STREAM-INF"));
11
+ return i >= 0 ? new URL(lines[i + 1], url) : "";
12
+ };
13
+ /** @param {string} url */
14
+ const fetchManifests = async (url) => {
15
+ if (!url.toString().split("?")[0].endsWith(".m3u8")) return fetch(url);
16
+ const innerUrl = getManifestUrl({
17
+ url,
18
+ data: await fetch(url).then((result) => result.text())
19
+ });
20
+ return innerUrl && fetchManifests(innerUrl);
21
+ };
22
+ var fetchManifests_default = fetchManifests;
23
+
24
+ //#endregion
25
+ //#region src/ad/impression.js
26
+ const _IsNumber = (value) => typeof value === "number" && value >= 0;
27
+ const AD_TIME_EVENT_TYPE = [
28
+ "impression",
29
+ "start",
30
+ "firstQuartile",
31
+ "midpoint",
32
+ "thirdQuartile",
33
+ "complete"
34
+ ];
35
+ const doNothing = () => {};
36
+ const getSkipTimeOffset = (ad) => {
37
+ if (!ad.skipOffset) return;
38
+ const percentageOffset = (ad.skipOffset?.match(/\d+/)?.[0] || 0) / 100;
39
+ return (ad.skipOffset?.match(/(\d+):(\d+):(\d+)/) || []).slice(1, 4).reduce((last, time) => last * 60 + +time, 0) + ad.durationInSeconds * percentageOffset;
40
+ };
41
+ const inRange = ({ startTimeInSeconds, durationInSeconds }, time) => startTimeInSeconds <= time && time <= startTimeInSeconds + durationInSeconds;
42
+ const getCurrentAd = (adBreak, streamTime) => (adBreak?.ads || []).find((ad) => inRange(ad, streamTime)) || {};
43
+ const adEventData = (instance, ad) => {
44
+ const streamTime = instance._common.currentPosition;
45
+ const currentAd = getCurrentAd(ad, streamTime);
46
+ return {
47
+ getAd: () => ({ getSkipTimeOffset: () => getSkipTimeOffset(currentAd) }),
48
+ getStreamData: () => {
49
+ const adItems = [].concat(...instance.waitingForPlayAds.map((avail) => avail.ads));
50
+ return { adProgressData: {
51
+ adPosition: 1 + adItems.findIndex((item) => inRange(item, streamTime)),
52
+ totalAds: adItems.length,
53
+ currentTime: streamTime - currentAd.startTimeInSeconds,
54
+ duration: currentAd.durationInSeconds,
55
+ clickThroughUrl: currentAd.trackingEvents?.find((event) => event.eventType === "clickThrough")?.beaconUrls[0]
56
+ } };
57
+ }
58
+ };
59
+ };
60
+ var Impression = class {
61
+ constructor({ seek, onAdBreakStarted = doNothing, onAdProgress = doNothing, onAdBreakEnded = doNothing, onSkippableStateChanged = doNothing } = {}) {
62
+ this._common = {
63
+ adBreaks: [],
64
+ currentPosition: -1,
65
+ seek,
66
+ onAdBreakStarted,
67
+ onAdProgress,
68
+ onSkippableStateChanged,
69
+ onAdBreakEnded
70
+ };
71
+ this.currentAd = null;
72
+ this.waitingForPlayAds = [];
73
+ this.waitingForPlayAdIndex = null;
74
+ this.resumeUserSeekTime = null;
75
+ this.resumeAdStartTime = null;
76
+ this.isResumed = null;
77
+ this.checkAdEventProcess = null;
78
+ }
79
+ /**
80
+ * @description
81
+ * @param {object[]} value
82
+ */
83
+ set adBreaks(value) {
84
+ this._common.adBreaks = value;
85
+ if (_IsNumber(value.length)) this.checkAdCueTone();
86
+ }
87
+ /**
88
+ * @description when position is updated, check if ad is started or ended
89
+ * @param {number} value current position in seconds
90
+ */
91
+ set currentPosition(value) {
92
+ this._common.currentPosition = value;
93
+ if (_IsNumber(this._common.adBreaks.length)) this.checkAdCueTone();
94
+ }
95
+ getAdIndex(target) {
96
+ if (!target) return null;
97
+ return this._common.adBreaks.findIndex((ad) => ad.availId === target.availId);
98
+ }
99
+ getActiveAdIndex(time) {
100
+ const { adBreaks = [] } = this._common;
101
+ const index = adBreaks.findIndex((ad) => {
102
+ const { startTime, endTime } = this.getAdTimingInfo(ad);
103
+ return time >= startTime && time <= endTime;
104
+ });
105
+ return {
106
+ index,
107
+ position: (adBreaks[index]?.ads || []).findIndex((ad) => inRange(ad, time))
108
+ };
109
+ }
110
+ isWithinTimeRange(target, startTime, endTime) {
111
+ return target >= startTime && target <= endTime;
112
+ }
113
+ getAdTimingInfo(adInfo = {}) {
114
+ const adStartTime = adInfo.startTimeInSeconds || 0;
115
+ return {
116
+ startTime: adStartTime,
117
+ endTime: adStartTime + (adInfo.durationInSeconds || 0)
118
+ };
119
+ }
120
+ getSkippedAds(time) {
121
+ return this._common.adBreaks.filter((ad) => this._common.currentPosition <= ad.startTimeInSeconds && ad.startTimeInSeconds <= time && !ad.isFired);
122
+ }
123
+ setAdIsFiredByIndex() {}
124
+ checkAdCueTone() {
125
+ const { adBreaks, currentPosition, seek } = this._common;
126
+ const { index: activeAdIndex, position } = this.getActiveAdIndex(currentPosition);
127
+ if (this.currentAd) {
128
+ if (!this.checkAdEventProcess) this.checkAdEventProcess = this.checkAdEvent();
129
+ this.checkAdEventProcess({
130
+ index: activeAdIndex,
131
+ position
132
+ });
133
+ }
134
+ if (this.waitingForPlayAds.length > 0) {
135
+ const { startTime: currentAdStartTime, endTime: currentAdEndTime } = this.getAdTimingInfo(this.currentAd);
136
+ if (!this.isWithinTimeRange(currentPosition, currentAdStartTime, currentAdEndTime)) {
137
+ const nextAd = this.waitingForPlayAds[`${this.waitingForPlayAdIndex + 1}`];
138
+ if (currentPosition < this.currentAd.startTimeInSeconds + this.currentAd.durationInSeconds) return;
139
+ if (nextAd) {
140
+ const adIndex = this.getAdIndex(this.currentAd);
141
+ const { startTime: nextAdStartTime } = this.getAdTimingInfo(nextAd);
142
+ this.setAdIsFiredByIndex(adIndex);
143
+ this.updateWaitingForPlayIndex(this.waitingForPlayAdIndex + 1);
144
+ this.updateCurrentAd(nextAd);
145
+ seek?.(nextAdStartTime);
146
+ } else {
147
+ const adIndex = this.getAdIndex(this.currentAd);
148
+ this.setAdIsFiredByIndex(adIndex);
149
+ this.updateWaitingForPlayAds([]);
150
+ this.updateWaitingForPlayIndex(null);
151
+ this.updateCurrentAd(null);
152
+ _IsNumber(this.resumeUserSeekTime) && seek?.(this.resumeUserSeekTime);
153
+ this.resumeUserSeekTime = null;
154
+ }
155
+ }
156
+ } else if (activeAdIndex !== -1) if (!adBreaks[activeAdIndex].isFired) {
157
+ this.updateWaitingForPlayIndex(0);
158
+ this.updateWaitingForPlayAds(adBreaks.slice(activeAdIndex, activeAdIndex + 1));
159
+ this.updateCurrentAd(adBreaks[activeAdIndex]);
160
+ } else this.updateCurrentAd(adBreaks[`${activeAdIndex}`] || {});
161
+ else this.updateCurrentAd(null);
162
+ }
163
+ checkAdEvent() {
164
+ const state = {
165
+ lastPosition: void 0,
166
+ isSkippableEventFired: false
167
+ };
168
+ return ({ index, position }) => {
169
+ const streamTime = this._common.currentPosition;
170
+ const currentBreak = this._common.adBreaks[index];
171
+ const currentAd = currentBreak?.ads[position];
172
+ if (!currentAd) return;
173
+ if (position !== state.lastPosition) {
174
+ this._common.onAdProgress?.(adEventData(this, currentBreak));
175
+ Object.assign(state, {
176
+ lastPosition: position,
177
+ isSkippableEventFired: false,
178
+ trackingTypes: AD_TIME_EVENT_TYPE.slice()
179
+ });
180
+ }
181
+ if (!state.isSkippableEventFired && streamTime >= currentAd.startTimeInSeconds + getSkipTimeOffset(currentAd)) {
182
+ state.isSkippableEventFired = true;
183
+ this._common.onSkippableStateChanged({ getAd: () => ({ isSkippable: () => true }) });
184
+ }
185
+ if (!_IsNumber(streamTime) || currentAd.trackingEvents?.length <= 0) return;
186
+ currentAd.trackingEvents?.forEach((e) => {
187
+ const { eventType = "", beaconUrls = [], startTimeInSeconds = 0, isFired } = e;
188
+ const adEventIndex = state.trackingTypes.findIndex((type) => type === eventType);
189
+ if (!isFired && beaconUrls.length > 0 && streamTime >= startTimeInSeconds && adEventIndex !== -1) {
190
+ beaconUrls.forEach((url) => {
191
+ fetch(url);
192
+ });
193
+ state.trackingTypes.splice(adEventIndex, 1);
194
+ }
195
+ });
196
+ };
197
+ }
198
+ /**
199
+ * @description To snapback if seeking over some ads
200
+ * @param {number} to
201
+ */
202
+ onSeek(to) {
203
+ const { adBreaks } = this._common;
204
+ const skippedAds = this.getSkippedAds(to);
205
+ const seekTargetAdIndex = this.getActiveAdIndex(to);
206
+ if (!adBreaks || adBreaks.length <= 0) return;
207
+ if (this.currentAd) {} else if (skippedAds.length > 0) {
208
+ this.updateWaitingForPlayAds(skippedAds);
209
+ this.updateWaitingForPlayIndex(0);
210
+ this.resumeUserSeekTime = seekTargetAdIndex === -1 ? to : null;
211
+ } else if (seekTargetAdIndex !== -1 && !_IsNumber(this.resumeAdStartTime) && !this.isResumed) {
212
+ const { startTime } = this.getAdTimingInfo(adBreaks[seekTargetAdIndex]);
213
+ this.resumeAdStartTime = startTime;
214
+ }
215
+ }
216
+ /** @description to seek to next ad after snapback */
217
+ onSeeked() {
218
+ const { adBreaks, seek } = this._common;
219
+ if (!adBreaks || adBreaks.length <= 0) return;
220
+ if (this.waitingForPlayAds.length > 0 && !this.currentAd) {
221
+ const nextAd = this.waitingForPlayAds[`${this.waitingForPlayAdIndex}`] || {};
222
+ const { startTime: nextAdStartTime } = this.getAdTimingInfo(nextAd);
223
+ seek?.(nextAdStartTime);
224
+ this.updateCurrentAd(nextAd);
225
+ } else if (this.currentAd) {} else if (_IsNumber(this.resumeAdStartTime)) {
226
+ _IsNumber(this.resumeAdStartTime) && seek?.(this.resumeAdStartTime);
227
+ this.resumeAdStartTime = null;
228
+ this.isResumed = true;
229
+ } else this.isResumed = false;
230
+ }
231
+ updateCurrentAd(ad) {
232
+ if (!this.currentAd && ad) this._common.onAdBreakStarted?.(adEventData(this, ad));
233
+ else if (this.currentAd && !ad) this._common.onAdBreakEnded?.({ getStreamData: () => this.getStreamData() });
234
+ this.currentAd = ad;
235
+ if (!ad) this.checkAdEventProcess = null;
236
+ }
237
+ updateWaitingForPlayAds(ads) {
238
+ this.waitingForPlayAds = ads.slice();
239
+ }
240
+ updateWaitingForPlayIndex(index) {
241
+ this.waitingForPlayAdIndex = index;
242
+ }
243
+ /** @description mark all ads as played */
244
+ setAllAdsFired() {
245
+ this._common.adBreaks = this._common.adBreaks.map((ad) => ({
246
+ ...ad,
247
+ isFired: true
248
+ }));
249
+ this.updateWaitingForPlayAds([]);
250
+ this.updateWaitingForPlayIndex(null);
251
+ this.updateCurrentAd(null);
252
+ }
253
+ /** @description clear data */
254
+ reset() {
255
+ this._common = {
256
+ adBreaks: [],
257
+ currentPosition: -1,
258
+ seek: null
259
+ };
260
+ this.currentAd = null;
261
+ this.waitingForPlayAds = [];
262
+ this.waitingForPlayAdIndex = null;
263
+ this.resumeUserSeekTime = null;
264
+ this.resumeAdStartTime = null;
265
+ this.isResumed = null;
266
+ this.checkAdEventProcess = null;
267
+ }
268
+ /** @description clear ad status */
269
+ resetSession() {
270
+ this._common = {
271
+ ...this._common,
272
+ currentPosition: 0
273
+ };
274
+ this.resumeUserSeekTime = null;
275
+ this.resumeAdStartTime = null;
276
+ this.isResumed = null;
277
+ this.updateWaitingForPlayAds([]);
278
+ this.updateWaitingForPlayIndex(null);
279
+ this.updateCurrentAd(null);
280
+ }
281
+ };
282
+
283
+ //#endregion
284
+ //#region src/ad/vast.js
285
+ const getLastAd = (avails, streamTime) => avails.reduce((current, item) => current.startTimeInSeconds <= item.startTimeInSeconds && item.startTimeInSeconds <= streamTime ? item : current, {
286
+ startTimeInSeconds: 0,
287
+ durationInSeconds: 0
288
+ });
289
+ const getStreamTime = (avails, contentTime) => avails.reduce((time, item) => time + (time > item.startTimeInSeconds ? item.durationInSeconds : 0), contentTime);
290
+ const getContentTime = (avails, streamTime) => streamTime - avails.filter((item) => item.startTimeInSeconds <= streamTime).map((item) => Math.min(streamTime - item.startTimeInSeconds, item.durationInSeconds)).reduce((a, b) => a + b, 0);
291
+
292
+ //#endregion
293
+ //#region src/ad/seekingHandler.js
294
+ const seekingHandler = (handleSeeking) => {
295
+ const ref = {};
296
+ return (video) => {
297
+ if (!(Math.abs(video.currentTime - ref.originTime) > .5) || video.webkitPresentationMode !== "fullscreen") {
298
+ ref.originTime = video.currentTime;
299
+ return;
300
+ }
301
+ handleSeeking({
302
+ originTime: ref.originTime,
303
+ seekTime: video.currentTime
304
+ });
305
+ ref.originTime = video.currentTime;
306
+ };
307
+ };
308
+ var seekingHandler_default = seekingHandler;
309
+
310
+ //#endregion
311
+ //#region src/util/snapback.js
312
+ const snapback = ({ streamManager, originTime, seekTime, seek }) => {
313
+ const cuePoint = streamManager?.previousCuePointForStreamTime(seekTime);
314
+ if (Math.floor(cuePoint?.start) >= Math.floor(originTime)) {
315
+ once$1(streamManager, "adBreakEnded", async () => {
316
+ await new Promise((resolve) => {
317
+ setTimeout(resolve, 20);
318
+ });
319
+ seek(seekTime);
320
+ });
321
+ seek(cuePoint.start);
322
+ } else seek(seekTime);
323
+ };
324
+ var snapback_default = snapback;
325
+
326
+ //#endregion
327
+ //#region src/plugins/MediaTailor.js
328
+ const axios = () => {};
329
+ const addFetchPolyfill = () => {
330
+ window.fetch = async (url, { method } = {}) => {
331
+ const result = await axios(url, { method });
332
+ return Promise.resolve({ json: () => result.data });
333
+ };
334
+ };
335
+ const fetchStreamInfo = async (url, adsParams) => fetch(url, {
336
+ method: "POST",
337
+ body: JSON.stringify({ adsParams })
338
+ }).then((result) => result.json());
339
+ const on = (eventTarget, eventName, handler) => {
340
+ eventTarget.addEventListener(eventName, handler);
341
+ return () => eventTarget.removeEventListener(eventName, handler);
342
+ };
343
+ const once = (eventTarget, eventName, handler) => {
344
+ const listener = (...args) => {
345
+ eventTarget.removeEventListener(eventName, listener);
346
+ return handler(...args);
347
+ };
348
+ eventTarget.addEventListener(eventName, listener);
349
+ };
350
+ const seekVideo = (videoElement, streamTime) => {
351
+ videoElement.currentTime = streamTime;
352
+ };
353
+ const initialState = {
354
+ currentTime: 0,
355
+ adBreaks: [],
356
+ mpdStartTime: 0,
357
+ isUserSkipAd: false,
358
+ skipAdEndTime: 0
359
+ };
360
+ const getAdEndTime = (streamData, videoElement) => {
361
+ const { currentTime, duration } = streamData.adProgressData;
362
+ return videoElement.currentTime + duration - currentTime;
363
+ };
364
+ const pipeEvent = (emitter, type) => (event) => emitter.emit(type, {
365
+ type,
366
+ getAd: () => event.getAd(),
367
+ getStreamData: () => event.getStreamData()
368
+ });
369
+ const getMpdStartTime = (manifest) => {
370
+ const availabilityStartTime = new DOMParser().parseFromString(manifest, "text/xml").firstChild.getAttribute("availabilityStartTime");
371
+ return new Date(availabilityStartTime).getTime() / 1e3;
372
+ };
373
+ const createStreamManager = (videoElement, { player, emitter }) => {
374
+ let state = initialState;
375
+ const streamData = {};
376
+ const impression = new Impression({
377
+ seek: (streamTime) => seekVideo(videoElement, streamTime),
378
+ onAdBreakStarted: pipeEvent(emitter, "adBreakStarted"),
379
+ onAdProgress: (event) => {
380
+ state.adEndTime = getAdEndTime(event.getStreamData(), videoElement);
381
+ pipeEvent(emitter, "adProgress")(event);
382
+ },
383
+ onSkippableStateChanged: pipeEvent(emitter, "skippableStateChanged"),
384
+ onAdBreakEnded: pipeEvent(emitter, "adBreakEnded")
385
+ });
386
+ const previousCuePointForStreamTime = (streamTime) => {
387
+ const ad = getLastAd(state.adBreaks, streamTime - .1 - state.mpdStartTime);
388
+ if (ad.durationInSeconds > 0) {
389
+ const start = ad.startTimeInSeconds;
390
+ return {
391
+ start,
392
+ end: start + ad.durationInSeconds,
393
+ played: state.played[ad.availId]
394
+ };
395
+ }
396
+ };
397
+ emitter.on("adBreakEnded", () => {
398
+ state.adEndTime = -1;
399
+ const ad = getLastAd(state.adBreaks, videoElement.currentTime);
400
+ state.played[ad.availId] = true;
401
+ });
402
+ const refreshTrackingData = async () => {
403
+ if (!streamData.trackingUrl) return;
404
+ const trackingData = await fetch(streamData.trackingUrl).then((result) => result.json()) || { avails: [] };
405
+ state.adBreaks = trackingData.avails || [];
406
+ if (trackingData.avails.length > 0) {
407
+ impression.adBreaks = state.adBreaks;
408
+ emitter.emit("cuepointsChanged", { cuepoints: state.adBreaks.map((item) => ({ start: getContentTime(state.adBreaks, item.startTimeInSeconds) })) });
409
+ }
410
+ };
411
+ const handleTimeUpdate = (streamTime) => {
412
+ if (!Number.isFinite(streamTime)) return;
413
+ if (player.isLive() && streamTime > state.currentTime + 5) {
414
+ state.currentTime = streamTime;
415
+ refreshTrackingData();
416
+ }
417
+ if (state.isUserSkipAd) {
418
+ if (state.skipAdEndTime + .1 >= streamTime) return;
419
+ state.isUserSkipAd = false;
420
+ state.skipAdEndTime = -1;
421
+ }
422
+ impression.currentPosition = streamTime - state.mpdStartTime;
423
+ };
424
+ const streamManager = {
425
+ requestStream: async (options = {}) => {
426
+ const reportingUrl = options.client_side_reporting_url;
427
+ const info = await fetchStreamInfo(reportingUrl, options.adParams).catch((error) => ({ error }));
428
+ if (!info || info.error) return;
429
+ streamData.trackingUrl = new URL(info.trackingUrl, reportingUrl).toString();
430
+ streamData.url = new URL(info.manifestUrl, reportingUrl).toString();
431
+ await fetchManifests_default(streamData.url);
432
+ await refreshTrackingData();
433
+ emitter.emit("loaded", { getStreamData: () => streamData });
434
+ state.played = {};
435
+ },
436
+ addEventListener: (eventName, handler) => emitter.on(eventName, handler),
437
+ removeEventListener: (eventName, handler) => emitter.off(eventName, handler),
438
+ streamTimeForContentTime: (contentTime) => getStreamTime(state.adBreaks, contentTime),
439
+ contentTimeForStreamTime: (streamTime) => getContentTime(state.adBreaks, streamTime),
440
+ previousCuePointForStreamTime,
441
+ skipAd: () => {
442
+ if (state.adEndTime > 0) {
443
+ const seekTime = state.adEndTime;
444
+ handleTimeUpdate(state.adEndTime);
445
+ state.isUserSkipAd = true;
446
+ state.skipAdEndTime = seekTime;
447
+ player?.seek(seekTime, "internal");
448
+ emitter.emit("skip");
449
+ }
450
+ },
451
+ setMpdStartTime: (time) => {
452
+ state.mpdStartTime = time;
453
+ },
454
+ getVastAvails: () => state.adBreaks,
455
+ reset: () => {
456
+ state.registered.forEach((removeListener) => removeListener());
457
+ impression.reset();
458
+ state = initialState;
459
+ streamData.trackingUrl = "";
460
+ }
461
+ };
462
+ const handleSeeking = seekingHandler_default(({ originTime, seekTime }) => {
463
+ if (state.adEndTime > 0) {
464
+ seekVideo(videoElement, originTime);
465
+ return;
466
+ }
467
+ const diff = seekTime - originTime;
468
+ if (Math.abs(diff + 15) <= .25) {
469
+ seekVideo(videoElement, getStreamTime(state.adBreaks, getContentTime(state.adBreaks, originTime) + diff));
470
+ return;
471
+ }
472
+ snapback_default({
473
+ streamManager,
474
+ originTime,
475
+ seekTime,
476
+ seek: (streamTime) => {
477
+ if (Math.abs(videoElement.currentTime - streamTime) > .5) seekVideo(videoElement, streamTime);
478
+ }
479
+ });
480
+ });
481
+ state.registered = [on(videoElement, "timeupdate", () => {
482
+ handleSeeking(videoElement);
483
+ if (!videoElement.paused) handleTimeUpdate(videoElement.currentTime);
484
+ }), on(videoElement, "ended", () => handleTimeUpdate(Infinity))];
485
+ return streamManager;
486
+ };
487
+ const init = (options, { skipWatched }) => {
488
+ const { player, video, streamManager } = options;
489
+ const ref = {
490
+ player,
491
+ video,
492
+ streamManager
493
+ };
494
+ streamManager.addEventListener("adProgress", (event) => {
495
+ ref.adEndTime = getAdEndTime(event.getStreamData(), ref.video);
496
+ });
497
+ streamManager.addEventListener("adBreakEnded", () => {
498
+ ref.adEndTime = -1;
499
+ });
500
+ player?.on?.("sourceloaded", () => {
501
+ ref.isLive = player.isLive();
502
+ if (player.manifest.dash && player.isLive()) streamManager.setMpdStartTime(getMpdStartTime(player.getManifest()));
503
+ });
504
+ if (skipWatched) video.addEventListener("timeupdate", () => {
505
+ const streamTime = video.currentTime;
506
+ const cuePoint = streamManager.previousCuePointForStreamTime(streamTime + .5);
507
+ if (cuePoint?.end > streamTime && cuePoint.played) player?.seek(cuePoint.end, "internal");
508
+ });
509
+ return ref;
510
+ };
511
+ const MediaTailorPlugin = ({ adParams, skipWatched } = {}) => {
512
+ const emitter = mitt();
513
+ let ref = {};
514
+ let options = { adParams };
515
+ return {
516
+ isActive: () => !!ref.streamManager,
517
+ load: async (manifestItem, { player, video, source = {} } = {}) => {
518
+ if (typeof fetch !== "function") addFetchPolyfill();
519
+ ref.streamManager?.reset();
520
+ const mediaTailorOptions = manifestItem.ssai?.media_tailor;
521
+ if (!mediaTailorOptions) return;
522
+ mediaTailorOptions.adParams = options.adParams;
523
+ const streamManager = createStreamManager(video, {
524
+ player,
525
+ emitter
526
+ });
527
+ ref = init({
528
+ player,
529
+ video,
530
+ streamManager
531
+ }, { skipWatched });
532
+ streamManager.requestStream(mediaTailorOptions);
533
+ const { url } = await new Promise((resolve) => {
534
+ once(streamManager, "loaded", (event) => resolve(event.getStreamData()));
535
+ });
536
+ if (!url) {
537
+ console.warn("Ad stream is not available, use fallback stream instead");
538
+ return manifestItem;
539
+ }
540
+ return {
541
+ ...manifestItem,
542
+ ssaiProvider: "AWS",
543
+ url,
544
+ vastAvails: streamManager.getVastAvails(),
545
+ startTime: streamManager.streamTimeForContentTime(source.options?.startTime)
546
+ };
547
+ },
548
+ handleSeek: (contentTime, seek) => {
549
+ snapback_default({
550
+ streamManager: ref.streamManager,
551
+ originTime: ref.video.currentTime,
552
+ seekTime: ref.streamManager.streamTimeForContentTime(contentTime),
553
+ seek
554
+ });
555
+ },
556
+ skipAd: () => ref.streamManager.skipAd(),
557
+ getPlaybackStatus: () => ref.streamManager && {
558
+ ...!ref.isLive && {
559
+ currentTime: ref.streamManager.contentTimeForStreamTime(ref.video.currentTime),
560
+ duration: ref.streamManager.contentTimeForStreamTime(ref.video.duration)
561
+ },
562
+ ...ref.adEndTime > 0 && { adRemainingTime: ref.adEndTime - ref.video.currentTime }
563
+ },
564
+ on: (name, listener) => emitter.on(name, listener),
565
+ reset: () => {
566
+ ref.streamManager?.reset();
567
+ ref.streamManager = void 0;
568
+ },
569
+ setOptions: (updatedOptions) => {
570
+ options = updatedOptions;
571
+ }
572
+ };
573
+ };
574
+ var MediaTailor_default = MediaTailorPlugin;
575
+
576
+ //#endregion
577
+ //#region src/plugins/ImaDai.js
578
+ const GOOGLE_DAI_SDK_URL = "https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js";
579
+ const ensureDaiApi = async () => {
580
+ if (!window.google?.ima?.dai?.api?.StreamManager) await loadScript_default(GOOGLE_DAI_SDK_URL);
581
+ return window.google.ima.dai.api;
582
+ };
583
+ const ImaDaiPlugin = ({ requestOptionOverrides = {} } = {}) => {
584
+ const emitter = mitt();
585
+ const ref = {};
586
+ const reset = () => {
587
+ ref.progress = void 0;
588
+ ref.streamManager?.reset();
589
+ ref.streamManager = void 0;
590
+ ref.video = void 0;
591
+ };
592
+ const getStartTime = (startTime) => {
593
+ const startTimeInStreamTime = ref.streamManager.streamTimeForContentTime(startTime);
594
+ let earliestCuepoint = { start: startTimeInStreamTime };
595
+ while (earliestCuepoint !== null && earliestCuepoint.start !== 0) earliestCuepoint = ref.streamManager.previousCuePointForStreamTime(earliestCuepoint.start);
596
+ return earliestCuepoint?.start ?? startTimeInStreamTime;
597
+ };
598
+ return {
599
+ isActive: () => !!ref.streamManager,
600
+ load: async (manifestItem, { player, video, streamFormat, startTime }) => {
601
+ reset();
602
+ if (!manifestItem.ssai?.google_dai) return;
603
+ ref.video = video;
604
+ const { live: liveOptions, vod: vodOptions } = manifestItem.ssai.google_dai;
605
+ const daiApi = await ensureDaiApi();
606
+ if (!ref.adContainer) console.warn(`The 'adContainer' is '${ref.adContainer}'. Please provide it using 'ImaDai.setAdContainer', or the DAI SDK won't request skippable ADs. See: https://developers.google.com/interactive-media-ads/docs/sdks/html5/dai/reference/js/StreamManager#StreamManager`);
607
+ ref.streamManager = new daiApi.StreamManager(video, ref.adContainer);
608
+ pipeEvents(ref.streamManager, emitter, [daiApi.StreamEvent.Type.SKIPPABLE_STATE_CHANGED, daiApi.StreamEvent.Type.SKIPPED]);
609
+ /**
610
+ * To align with the MideaTailor, convert the cuepoint.start from the stream time to content time.
611
+ */
612
+ ref.streamManager.addEventListener(daiApi.StreamEvent.Type.CUEPOINTS_CHANGED, (event) => {
613
+ const cuepointsInContentTime = event.getStreamData().cuepoints.map((cuepoint) => ({
614
+ start: ref.streamManager.contentTimeForStreamTime(cuepoint.start),
615
+ duration: cuepoint.end - cuepoint.start
616
+ }));
617
+ emitter.emit("cuepointsChanged", { cuepoints: cuepointsInContentTime });
618
+ });
619
+ /**
620
+ * We can't get the adProgressData from the event adBreakStarted, so we map the first call of adProgress to adBreakStarted.
621
+ */
622
+ let isFirstAdProgressCalled = true;
623
+ ref.streamManager.addEventListener("adProgress", (event) => {
624
+ if (isFirstAdProgressCalled) {
625
+ emitter.emit("adBreakStarted", event);
626
+ isFirstAdProgressCalled = false;
627
+ } else emitter.emit("adProgress", event);
628
+ });
629
+ ref.streamManager.addEventListener("adBreakEnded", (event) => {
630
+ emitter.emit("adBreakEnded", event);
631
+ isFirstAdProgressCalled = true;
632
+ });
633
+ const requestOptions = {
634
+ apiKey: liveOptions?.api_key || vodOptions?.api_key,
635
+ contentSourceId: vodOptions?.content_source_id,
636
+ videoId: vodOptions?.video_id,
637
+ assetKey: liveOptions?.asset_key,
638
+ format: streamFormat,
639
+ ...requestOptionOverrides
640
+ };
641
+ const streamRequest = Object.assign(liveOptions ? new daiApi.LiveStreamRequest() : new daiApi.VODStreamRequest(), requestOptions);
642
+ const url = await new Promise((resolve) => {
643
+ ref.streamManager.addEventListener(daiApi.StreamEvent.Type.LOADED, (e) => resolve(e.getStreamData().url));
644
+ ref.streamManager.requestStream(streamRequest);
645
+ });
646
+ player.on("metadata", ({ metadata }) => {
647
+ const TXXX = metadata.TXXX || metadata.properties.messageData;
648
+ ref.streamManager.onTimedMetadata({ TXXX });
649
+ });
650
+ ref.streamManager.addEventListener(daiApi.StreamEvent.Type.AD_PROGRESS, (e) => {
651
+ ref.progress = e.getStreamData().adProgressData;
652
+ });
653
+ return {
654
+ ssaiProvider: "DAI",
655
+ url,
656
+ startTime: getStartTime(startTime)
657
+ };
658
+ },
659
+ getPlaybackStatus: () => ref.streamManager && ref.video && {
660
+ currentTime: ref.streamManager.contentTimeForStreamTime(ref.video.currentTime),
661
+ duration: ref.streamManager.contentTimeForStreamTime(ref.video.duration),
662
+ ...ref.progress && { adRemainingTime: ref.progress.duration - ref.progress.currentTime }
663
+ },
664
+ on: (name, listener) => emitter.on(name, listener),
665
+ reset,
666
+ handleSeek: (contentTime, seek) => {
667
+ snapback_default({
668
+ streamManager: ref.streamManager,
669
+ originTime: ref.video.currentTime,
670
+ seekTime: ref.streamManager.streamTimeForContentTime(contentTime),
671
+ seek
672
+ });
673
+ },
674
+ setAdContainer: (adContainer) => {
675
+ ref.adContainer = adContainer;
676
+ }
677
+ };
678
+ };
679
+ var ImaDai_default = ImaDaiPlugin;
680
+
681
+ //#endregion
682
+ //#region src/plugins/StallReload.js
683
+ const wallTimeSeconds = () => Date.now() / 1e3;
684
+ const StallReloadPlugin = ({ stallThresholdSeconds = 10 } = {}) => {
685
+ const state = {
686
+ lastVideoTime: 0,
687
+ lastTimer: 0,
688
+ lastUpdateSeconds: wallTimeSeconds()
689
+ };
690
+ const load = (_, { player, video, reload }) => {
691
+ clearInterval(state.lastTimer);
692
+ const checkStall = async () => {
693
+ if (!isLiveDuration(video.duration)) return;
694
+ if (video.paused || video.playbackRate === 0 || state.lastVideoTime !== video.currentTime || !(video.currentTime > 0)) {
695
+ state.lastUpdateSeconds = wallTimeSeconds();
696
+ state.lastVideoTime = video.currentTime;
697
+ return;
698
+ }
699
+ if (wallTimeSeconds() - state.lastUpdateSeconds > stallThresholdSeconds) {
700
+ console.warn("Stall detected, reload to resume");
701
+ await reload();
702
+ if (player.play) player.play();
703
+ else video.play();
704
+ }
705
+ };
706
+ state.lastVideoTime = video.currentTime;
707
+ state.lastTimer = setInterval(checkStall, stallThresholdSeconds / 8 * 1e3);
708
+ video.addEventListener("play", () => {
709
+ state.lastUpdateSeconds = wallTimeSeconds();
710
+ });
711
+ };
712
+ return { load };
713
+ };
714
+ var StallReload_default = StallReloadPlugin;
715
+
716
+ //#endregion
717
+ export { ImaDai_default as n, MediaTailor_default as r, StallReload_default as t };