@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.
- package/CHANGELOG.md +2 -0
- package/dist/StallReload-BFlQRphx.mjs +717 -0
- package/dist/Video-CMbK-cxg.mjs +120 -0
- package/dist/adaptation-BcTsh-wx.mjs +74 -0
- package/dist/api-2BOrEA5d.mjs +1057 -0
- package/dist/debugUtil-IF7p5TSI.mjs +23 -0
- package/dist/events-B3vI3Srm.mjs +16 -0
- package/dist/fixDashManifest-CJ63KKaA.mjs +56 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +3 -10153
- package/dist/loadPlayer-CQdGA3Te.mjs +1560 -0
- package/dist/loadScript-Ct19kU9g.mjs +13 -0
- package/dist/mediaBindings-CoY60lQw.mjs +542 -0
- package/dist/modules.d.mts +51 -0
- package/dist/modules.mjs +631 -2201
- package/dist/playerCore/index.d.mts +3 -0
- package/dist/playerCore/index.mjs +4 -0
- package/dist/plugins/index.d.mts +2 -0
- package/dist/plugins/index.mjs +3 -0
- package/dist/reactEntry.d.mts +20 -0
- package/dist/reactEntry.mjs +6339 -0
- package/dist/util-B2YBSBjR.mjs +29 -0
- package/package.json +24 -19
- package/dist/core.mjs +0 -3075
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -20943
- package/dist/modules.d.ts +0 -89
- package/dist/plugins.d.ts +0 -5
- package/dist/plugins.mjs +0 -1105
- package/dist/react.d.ts +0 -178
- package/dist/react.mjs +0 -13066
|
@@ -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 };
|