@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
package/dist/plugins.mjs
DELETED
|
@@ -1,1105 +0,0 @@
|
|
|
1
|
-
import mitt from 'mitt';
|
|
2
|
-
import UAParser from 'ua-parser-js';
|
|
3
|
-
|
|
4
|
-
/** @param {string} m3u8Manifest */
|
|
5
|
-
const getManifestUrl = ({
|
|
6
|
-
url,
|
|
7
|
-
data
|
|
8
|
-
}) => {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
const fetchManifests = async url => {
|
|
17
|
-
if (!url.toString().split('?')[0].endsWith('.m3u8')) {
|
|
18
|
-
return fetch(url);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const data = await fetch(url).then(result => result.text());
|
|
22
|
-
const innerUrl = getManifestUrl({
|
|
23
|
-
url,
|
|
24
|
-
data
|
|
25
|
-
});
|
|
26
|
-
return innerUrl && fetchManifests(innerUrl);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/* eslint-disable class-methods-use-this */
|
|
30
|
-
|
|
31
|
-
/* eslint-disable no-underscore-dangle */
|
|
32
|
-
const _IsNumber = value => typeof value === 'number' && value >= 0;
|
|
33
|
-
|
|
34
|
-
const AD_TIME_EVENT_TYPE = ['impression', 'start', 'firstQuartile', 'midpoint', 'thirdQuartile', 'complete'];
|
|
35
|
-
|
|
36
|
-
const doNothing = () => {};
|
|
37
|
-
|
|
38
|
-
const getSkipTimeOffset = ad => {
|
|
39
|
-
var _ad$skipOffset, _ad$skipOffset$match, _ad$skipOffset2;
|
|
40
|
-
|
|
41
|
-
if (!ad.skipOffset) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const percentageOffset = (((_ad$skipOffset = ad.skipOffset) === null || _ad$skipOffset === void 0 ? void 0 : (_ad$skipOffset$match = _ad$skipOffset.match(/\d+/)) === null || _ad$skipOffset$match === void 0 ? void 0 : _ad$skipOffset$match[0]) || 0) / 100; // 00:01:07 -> 67
|
|
46
|
-
|
|
47
|
-
const timeOffset = (((_ad$skipOffset2 = ad.skipOffset) === null || _ad$skipOffset2 === void 0 ? void 0 : _ad$skipOffset2.match(/(\d+):(\d+):(\d+)/)) || []).slice(1, 4).reduce((last, time) => last * 60 + +time, 0);
|
|
48
|
-
return timeOffset + ad.durationInSeconds * percentageOffset;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const inRange = ({
|
|
52
|
-
startTimeInSeconds,
|
|
53
|
-
durationInSeconds
|
|
54
|
-
}, time) => startTimeInSeconds <= time && time <= startTimeInSeconds + durationInSeconds;
|
|
55
|
-
|
|
56
|
-
const getCurrentAd = (adBreak, streamTime) => ((adBreak === null || adBreak === void 0 ? void 0 : adBreak.ads) || []).find(ad => inRange(ad, streamTime)) || {};
|
|
57
|
-
|
|
58
|
-
const adEventData = (instance, ad) => {
|
|
59
|
-
const streamTime = instance._common.currentPosition;
|
|
60
|
-
const currentAd = getCurrentAd(ad, streamTime);
|
|
61
|
-
return {
|
|
62
|
-
getAd: () => ({
|
|
63
|
-
getSkipTimeOffset: () => getSkipTimeOffset(currentAd)
|
|
64
|
-
}),
|
|
65
|
-
getStreamData: () => {
|
|
66
|
-
var _currentAd$trackingEv, _currentAd$trackingEv2;
|
|
67
|
-
|
|
68
|
-
const adItems = [].concat(...instance.waitingForPlayAds.map(avail => avail.ads));
|
|
69
|
-
const adPosition = 1 + adItems.findIndex(item => inRange(item, streamTime));
|
|
70
|
-
const adProgressData = {
|
|
71
|
-
adPosition,
|
|
72
|
-
totalAds: adItems.length,
|
|
73
|
-
currentTime: streamTime - currentAd.startTimeInSeconds,
|
|
74
|
-
duration: currentAd.durationInSeconds,
|
|
75
|
-
clickThroughUrl: (_currentAd$trackingEv = currentAd.trackingEvents) === null || _currentAd$trackingEv === void 0 ? void 0 : (_currentAd$trackingEv2 = _currentAd$trackingEv.find(event => event.eventType === 'clickThrough')) === null || _currentAd$trackingEv2 === void 0 ? void 0 : _currentAd$trackingEv2.beaconUrls[0]
|
|
76
|
-
};
|
|
77
|
-
return {
|
|
78
|
-
adProgressData
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
class Impression {
|
|
85
|
-
constructor({
|
|
86
|
-
seek,
|
|
87
|
-
onAdBreakStarted = doNothing,
|
|
88
|
-
onAdProgress = doNothing,
|
|
89
|
-
onAdBreakEnded = doNothing,
|
|
90
|
-
onSkippableStateChanged = doNothing
|
|
91
|
-
} = {}) {
|
|
92
|
-
this._common = {
|
|
93
|
-
adBreaks: [],
|
|
94
|
-
currentPosition: -1,
|
|
95
|
-
seek,
|
|
96
|
-
onAdBreakStarted,
|
|
97
|
-
onAdProgress,
|
|
98
|
-
onSkippableStateChanged,
|
|
99
|
-
onAdBreakEnded
|
|
100
|
-
};
|
|
101
|
-
this.currentAd = null;
|
|
102
|
-
this.waitingForPlayAds = [];
|
|
103
|
-
this.waitingForPlayAdIndex = null;
|
|
104
|
-
this.resumeUserSeekTime = null;
|
|
105
|
-
this.resumeAdStartTime = null;
|
|
106
|
-
this.isResumed = null;
|
|
107
|
-
this.checkAdEventProcess = null;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* @description
|
|
111
|
-
* @param {object[]} value
|
|
112
|
-
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
set adBreaks(value) {
|
|
116
|
-
this._common.adBreaks = value;
|
|
117
|
-
if (_IsNumber(value.length)) this.checkAdCueTone();
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* @description when position is updated, check if ad is started or ended
|
|
121
|
-
* @param {number} value current position in seconds
|
|
122
|
-
*/
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
set currentPosition(value) {
|
|
126
|
-
this._common.currentPosition = value;
|
|
127
|
-
if (_IsNumber(this._common.adBreaks.length)) this.checkAdCueTone();
|
|
128
|
-
} // TODO: send ad status (current ad index, count, total duration)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
getAdIndex(target) {
|
|
132
|
-
if (!target) return null;
|
|
133
|
-
return this._common.adBreaks.findIndex(ad => ad.availId === target.availId);
|
|
134
|
-
} // 取得播放時間是在哪個廣告區間
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
getActiveAdIndex(time) {
|
|
138
|
-
var _adBreaks$index;
|
|
139
|
-
|
|
140
|
-
const {
|
|
141
|
-
adBreaks = []
|
|
142
|
-
} = this._common;
|
|
143
|
-
const index = adBreaks.findIndex(ad => {
|
|
144
|
-
const {
|
|
145
|
-
startTime,
|
|
146
|
-
endTime
|
|
147
|
-
} = this.getAdTimingInfo(ad);
|
|
148
|
-
return time >= startTime && time <= endTime;
|
|
149
|
-
});
|
|
150
|
-
const position = (((_adBreaks$index = adBreaks[index]) === null || _adBreaks$index === void 0 ? void 0 : _adBreaks$index.ads) || []).findIndex(ad => inRange(ad, time));
|
|
151
|
-
return {
|
|
152
|
-
index,
|
|
153
|
-
position
|
|
154
|
-
};
|
|
155
|
-
} // 檢查播放時間是否在所提供的區間
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
isWithinTimeRange(target, startTime, endTime) {
|
|
159
|
-
return target >= startTime && target <= endTime;
|
|
160
|
-
} // 取得該廣告的起始/結束時間
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
getAdTimingInfo(adInfo = {}) {
|
|
164
|
-
const adStartTime = adInfo.startTimeInSeconds || 0;
|
|
165
|
-
const adDuration = adInfo.durationInSeconds || 0;
|
|
166
|
-
return {
|
|
167
|
-
startTime: adStartTime,
|
|
168
|
-
endTime: adStartTime + adDuration
|
|
169
|
-
};
|
|
170
|
-
} // 取得跳過的廣告
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
getSkippedAds(time) {
|
|
174
|
-
return this._common.adBreaks.filter(ad => this._common.currentPosition <= ad.startTimeInSeconds && ad.startTimeInSeconds <= time && !ad.isFired);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
setAdIsFiredByIndex() {}
|
|
178
|
-
|
|
179
|
-
checkAdCueTone() {
|
|
180
|
-
const {
|
|
181
|
-
adBreaks,
|
|
182
|
-
currentPosition,
|
|
183
|
-
seek
|
|
184
|
-
} = this._common;
|
|
185
|
-
const {
|
|
186
|
-
index: activeAdIndex,
|
|
187
|
-
position
|
|
188
|
-
} = this.getActiveAdIndex(currentPosition);
|
|
189
|
-
|
|
190
|
-
if (this.currentAd) {
|
|
191
|
-
if (!this.checkAdEventProcess) {
|
|
192
|
-
this.checkAdEventProcess = this.checkAdEvent();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
this.checkAdEventProcess({
|
|
196
|
-
index: activeAdIndex,
|
|
197
|
-
position
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (this.waitingForPlayAds.length > 0) {
|
|
202
|
-
const {
|
|
203
|
-
startTime: currentAdStartTime,
|
|
204
|
-
endTime: currentAdEndTime
|
|
205
|
-
} = this.getAdTimingInfo(this.currentAd);
|
|
206
|
-
const isAdStillPlaying = this.isWithinTimeRange(currentPosition, currentAdStartTime, currentAdEndTime);
|
|
207
|
-
|
|
208
|
-
if (!isAdStillPlaying) {
|
|
209
|
-
// Ad finished
|
|
210
|
-
const nextAd = this.waitingForPlayAds[`${this.waitingForPlayAdIndex + 1}`];
|
|
211
|
-
if (currentPosition < this.currentAd.startTimeInSeconds + this.currentAd.durationInSeconds) return;
|
|
212
|
-
|
|
213
|
-
if (nextAd) {
|
|
214
|
-
// have non-played & skipped Ad
|
|
215
|
-
const adIndex = this.getAdIndex(this.currentAd);
|
|
216
|
-
const {
|
|
217
|
-
startTime: nextAdStartTime
|
|
218
|
-
} = this.getAdTimingInfo(nextAd);
|
|
219
|
-
this.setAdIsFiredByIndex(adIndex);
|
|
220
|
-
this.updateWaitingForPlayIndex(this.waitingForPlayAdIndex + 1);
|
|
221
|
-
this.updateCurrentAd(nextAd);
|
|
222
|
-
seek === null || seek === void 0 ? void 0 : seek(nextAdStartTime);
|
|
223
|
-
} else {
|
|
224
|
-
// don't have non-played Ad
|
|
225
|
-
const adIndex = this.getAdIndex(this.currentAd);
|
|
226
|
-
this.setAdIsFiredByIndex(adIndex);
|
|
227
|
-
this.updateWaitingForPlayAds([]);
|
|
228
|
-
this.updateWaitingForPlayIndex(null);
|
|
229
|
-
this.updateCurrentAd(null);
|
|
230
|
-
_IsNumber(this.resumeUserSeekTime) && (seek === null || seek === void 0 ? void 0 : seek(this.resumeUserSeekTime));
|
|
231
|
-
this.resumeUserSeekTime = null;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
} else if (activeAdIndex !== -1) {
|
|
235
|
-
if (!adBreaks[activeAdIndex].isFired) {
|
|
236
|
-
this.updateWaitingForPlayIndex(0);
|
|
237
|
-
this.updateWaitingForPlayAds(adBreaks.slice(activeAdIndex, activeAdIndex + 1));
|
|
238
|
-
this.updateCurrentAd(adBreaks[activeAdIndex]);
|
|
239
|
-
} else {
|
|
240
|
-
// in Ad duration but Ad was played
|
|
241
|
-
this.updateCurrentAd(adBreaks[`${activeAdIndex}`] || {});
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
// not in Ad duration
|
|
245
|
-
this.updateCurrentAd(null);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
checkAdEvent() {
|
|
250
|
-
const state = {
|
|
251
|
-
lastPosition: undefined,
|
|
252
|
-
isSkippableEventFired: false
|
|
253
|
-
};
|
|
254
|
-
return ({
|
|
255
|
-
index,
|
|
256
|
-
position
|
|
257
|
-
}) => {
|
|
258
|
-
var _currentAd$trackingEv3, _currentAd$trackingEv4;
|
|
259
|
-
|
|
260
|
-
const streamTime = this._common.currentPosition;
|
|
261
|
-
const currentBreak = this._common.adBreaks[index];
|
|
262
|
-
const currentAd = currentBreak === null || currentBreak === void 0 ? void 0 : currentBreak.ads[position];
|
|
263
|
-
|
|
264
|
-
if (!currentAd) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (position !== state.lastPosition) {
|
|
269
|
-
var _this$_common$onAdPro, _this$_common;
|
|
270
|
-
|
|
271
|
-
(_this$_common$onAdPro = (_this$_common = this._common).onAdProgress) === null || _this$_common$onAdPro === void 0 ? void 0 : _this$_common$onAdPro.call(_this$_common, adEventData(this, currentBreak));
|
|
272
|
-
Object.assign(state, {
|
|
273
|
-
lastPosition: position,
|
|
274
|
-
isSkippableEventFired: false,
|
|
275
|
-
trackingTypes: AD_TIME_EVENT_TYPE.slice()
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (!state.isSkippableEventFired && streamTime >= currentAd.startTimeInSeconds + getSkipTimeOffset(currentAd)) {
|
|
280
|
-
state.isSkippableEventFired = true;
|
|
281
|
-
|
|
282
|
-
this._common.onSkippableStateChanged({
|
|
283
|
-
getAd: () => ({
|
|
284
|
-
isSkippable: () => true
|
|
285
|
-
})
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (!_IsNumber(streamTime) || ((_currentAd$trackingEv3 = currentAd.trackingEvents) === null || _currentAd$trackingEv3 === void 0 ? void 0 : _currentAd$trackingEv3.length) <= 0) return;
|
|
290
|
-
(_currentAd$trackingEv4 = currentAd.trackingEvents) === null || _currentAd$trackingEv4 === void 0 ? void 0 : _currentAd$trackingEv4.forEach(e => {
|
|
291
|
-
const {
|
|
292
|
-
eventType = '',
|
|
293
|
-
beaconUrls = [],
|
|
294
|
-
startTimeInSeconds = 0,
|
|
295
|
-
isFired
|
|
296
|
-
} = e;
|
|
297
|
-
const adEventIndex = state.trackingTypes.findIndex(type => type === eventType);
|
|
298
|
-
|
|
299
|
-
if (!isFired && beaconUrls.length > 0 && streamTime >= startTimeInSeconds && adEventIndex !== -1) {
|
|
300
|
-
beaconUrls.forEach(url => {
|
|
301
|
-
fetch(url);
|
|
302
|
-
});
|
|
303
|
-
state.trackingTypes.splice(adEventIndex, 1);
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* @description To snapback if seeking over some ads
|
|
310
|
-
* @param {number} to
|
|
311
|
-
*/
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
onSeek(to) {
|
|
315
|
-
const {
|
|
316
|
-
adBreaks
|
|
317
|
-
} = this._common;
|
|
318
|
-
const skippedAds = this.getSkippedAds(to);
|
|
319
|
-
const seekTargetAdIndex = this.getActiveAdIndex(to);
|
|
320
|
-
if (!adBreaks || adBreaks.length <= 0) return;
|
|
321
|
-
|
|
322
|
-
if (this.currentAd) ; else if (skippedAds.length > 0) {
|
|
323
|
-
this.updateWaitingForPlayAds(skippedAds);
|
|
324
|
-
this.updateWaitingForPlayIndex(0);
|
|
325
|
-
this.resumeUserSeekTime = seekTargetAdIndex === -1 ? to : null;
|
|
326
|
-
} else if (seekTargetAdIndex !== -1 && !_IsNumber(this.resumeAdStartTime) && !this.isResumed) {
|
|
327
|
-
const {
|
|
328
|
-
startTime
|
|
329
|
-
} = this.getAdTimingInfo(adBreaks[seekTargetAdIndex]);
|
|
330
|
-
this.resumeAdStartTime = startTime;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
/** @description to seek to next ad after snapback */
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
onSeeked() {
|
|
337
|
-
const {
|
|
338
|
-
adBreaks,
|
|
339
|
-
seek
|
|
340
|
-
} = this._common;
|
|
341
|
-
if (!adBreaks || adBreaks.length <= 0) return;
|
|
342
|
-
|
|
343
|
-
if (this.waitingForPlayAds.length > 0 && !this.currentAd) {
|
|
344
|
-
const nextAd = this.waitingForPlayAds[`${this.waitingForPlayAdIndex}`] || {};
|
|
345
|
-
const {
|
|
346
|
-
startTime: nextAdStartTime
|
|
347
|
-
} = this.getAdTimingInfo(nextAd);
|
|
348
|
-
seek === null || seek === void 0 ? void 0 : seek(nextAdStartTime);
|
|
349
|
-
this.updateCurrentAd(nextAd);
|
|
350
|
-
} else if (this.currentAd) ; else if (_IsNumber(this.resumeAdStartTime)) {
|
|
351
|
-
_IsNumber(this.resumeAdStartTime) && (seek === null || seek === void 0 ? void 0 : seek(this.resumeAdStartTime));
|
|
352
|
-
this.resumeAdStartTime = null;
|
|
353
|
-
this.isResumed = true;
|
|
354
|
-
} else {
|
|
355
|
-
this.isResumed = false;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
updateCurrentAd(ad) {
|
|
360
|
-
if (!this.currentAd && ad) {
|
|
361
|
-
var _this$_common$onAdBre, _this$_common2;
|
|
362
|
-
|
|
363
|
-
(_this$_common$onAdBre = (_this$_common2 = this._common).onAdBreakStarted) === null || _this$_common$onAdBre === void 0 ? void 0 : _this$_common$onAdBre.call(_this$_common2, adEventData(this, ad));
|
|
364
|
-
} else if (this.currentAd && !ad) {
|
|
365
|
-
var _this$_common$onAdBre2, _this$_common3;
|
|
366
|
-
|
|
367
|
-
(_this$_common$onAdBre2 = (_this$_common3 = this._common).onAdBreakEnded) === null || _this$_common$onAdBre2 === void 0 ? void 0 : _this$_common$onAdBre2.call(_this$_common3, {
|
|
368
|
-
getStreamData: () => this.getStreamData()
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
this.currentAd = ad;
|
|
373
|
-
|
|
374
|
-
if (!ad) {
|
|
375
|
-
this.checkAdEventProcess = null;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
updateWaitingForPlayAds(ads) {
|
|
380
|
-
this.waitingForPlayAds = ads.slice();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
updateWaitingForPlayIndex(index) {
|
|
384
|
-
this.waitingForPlayAdIndex = index;
|
|
385
|
-
}
|
|
386
|
-
/** @description mark all ads as played */
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
setAllAdsFired() {
|
|
390
|
-
this._common.adBreaks = this._common.adBreaks.map(ad => ({ ...ad,
|
|
391
|
-
isFired: true
|
|
392
|
-
}));
|
|
393
|
-
this.updateWaitingForPlayAds([]);
|
|
394
|
-
this.updateWaitingForPlayIndex(null);
|
|
395
|
-
this.updateCurrentAd(null);
|
|
396
|
-
}
|
|
397
|
-
/** @description clear data */
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
reset() {
|
|
401
|
-
this._common = {
|
|
402
|
-
adBreaks: [],
|
|
403
|
-
currentPosition: -1,
|
|
404
|
-
seek: null
|
|
405
|
-
};
|
|
406
|
-
this.currentAd = null;
|
|
407
|
-
this.waitingForPlayAds = [];
|
|
408
|
-
this.waitingForPlayAdIndex = null;
|
|
409
|
-
this.resumeUserSeekTime = null;
|
|
410
|
-
this.resumeAdStartTime = null;
|
|
411
|
-
this.isResumed = null;
|
|
412
|
-
this.checkAdEventProcess = null;
|
|
413
|
-
}
|
|
414
|
-
/** @description clear ad status */
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
resetSession() {
|
|
418
|
-
this._common = { ...this._common,
|
|
419
|
-
currentPosition: 0
|
|
420
|
-
};
|
|
421
|
-
this.resumeUserSeekTime = null;
|
|
422
|
-
this.resumeAdStartTime = null;
|
|
423
|
-
this.isResumed = null;
|
|
424
|
-
this.updateWaitingForPlayAds([]);
|
|
425
|
-
this.updateWaitingForPlayIndex(null);
|
|
426
|
-
this.updateCurrentAd(null);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const getLastAd = (avails, streamTime) => avails.reduce((current, item) => current.startTimeInSeconds <= item.startTimeInSeconds && item.startTimeInSeconds <= streamTime ? item : current, {
|
|
432
|
-
startTimeInSeconds: 0,
|
|
433
|
-
durationInSeconds: 0
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
const getStreamTime = (avails, contentTime) => avails.reduce((time, item) => time + (time > item.startTimeInSeconds ? item.durationInSeconds : 0), contentTime);
|
|
437
|
-
|
|
438
|
-
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);
|
|
439
|
-
|
|
440
|
-
const seekingHandler = handleSeeking => {
|
|
441
|
-
const ref = {};
|
|
442
|
-
return video => {
|
|
443
|
-
if (!(Math.abs(video.currentTime - ref.originTime) > 0.5) || video.webkitPresentationMode !== 'fullscreen') {
|
|
444
|
-
ref.originTime = video.currentTime;
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
handleSeeking({
|
|
449
|
-
originTime: ref.originTime,
|
|
450
|
-
seekTime: video.currentTime
|
|
451
|
-
});
|
|
452
|
-
ref.originTime = video.currentTime;
|
|
453
|
-
};
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
const once$1 = (target, name, handler) => {
|
|
457
|
-
const oneTime = (...args) => {
|
|
458
|
-
handler(...args);
|
|
459
|
-
target.removeEventListener(name, oneTime);
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
target.addEventListener(name, oneTime);
|
|
463
|
-
return () => target.removeEventListener(name, oneTime);
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
const snapback = ({
|
|
467
|
-
streamManager,
|
|
468
|
-
originTime,
|
|
469
|
-
seekTime,
|
|
470
|
-
seek
|
|
471
|
-
}) => {
|
|
472
|
-
const cuePoint = streamManager === null || streamManager === void 0 ? void 0 : streamManager.previousCuePointForStreamTime(seekTime); // Only check the integer part because the decimal part may come from floating precision error.
|
|
473
|
-
// TODO: Try to extract this with the workaround OTP_2813 in MediaTailor > createStreamManager > handleTimeUpdate
|
|
474
|
-
|
|
475
|
-
if (Math.floor(cuePoint === null || cuePoint === void 0 ? void 0 : cuePoint.start) >= Math.floor(originTime)) {
|
|
476
|
-
once$1(streamManager, 'adBreakEnded', async () => {
|
|
477
|
-
// wait for ad playing flag to clear before resuming, TODO seek earlier
|
|
478
|
-
await new Promise(resolve => {
|
|
479
|
-
setTimeout(resolve, 20);
|
|
480
|
-
});
|
|
481
|
-
seek(seekTime);
|
|
482
|
-
});
|
|
483
|
-
seek(cuePoint.start);
|
|
484
|
-
} else {
|
|
485
|
-
seek(seekTime);
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
const axios = () => {};
|
|
490
|
-
|
|
491
|
-
const addFetchPolyfill = () => {
|
|
492
|
-
window.fetch = async (url, {
|
|
493
|
-
method
|
|
494
|
-
} = {}) => {
|
|
495
|
-
const result = await axios();
|
|
496
|
-
return Promise.resolve({
|
|
497
|
-
json: () => result.data
|
|
498
|
-
});
|
|
499
|
-
};
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
const fetchStreamInfo = async (url, adsParams) => fetch(url, {
|
|
503
|
-
method: 'POST',
|
|
504
|
-
body: JSON.stringify({
|
|
505
|
-
adsParams
|
|
506
|
-
})
|
|
507
|
-
}).then(result => result.json());
|
|
508
|
-
|
|
509
|
-
const on = (eventTarget, eventName, handler) => {
|
|
510
|
-
eventTarget.addEventListener(eventName, handler);
|
|
511
|
-
return () => eventTarget.removeEventListener(eventName, handler);
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
const once = (eventTarget, eventName, handler) => {
|
|
515
|
-
const listener = (...args) => {
|
|
516
|
-
eventTarget.removeEventListener(eventName, listener);
|
|
517
|
-
return handler(...args);
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
eventTarget.addEventListener(eventName, listener);
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
const seekVideo = (videoElement, streamTime) => {
|
|
524
|
-
// eslint-disable-next-line no-param-reassign
|
|
525
|
-
videoElement.currentTime = streamTime;
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
const initialState = {
|
|
529
|
-
currentTime: 0,
|
|
530
|
-
adBreaks: [],
|
|
531
|
-
mpdStartTime: 0,
|
|
532
|
-
isUserSkipAd: false,
|
|
533
|
-
skipAdEndTime: 0
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
const getAdEndTime = (streamData, videoElement) => {
|
|
537
|
-
const {
|
|
538
|
-
currentTime,
|
|
539
|
-
duration
|
|
540
|
-
} = streamData.adProgressData;
|
|
541
|
-
return videoElement.currentTime + duration - currentTime;
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
const pipeEvent = (emitter, type) => event => emitter.emit(type, {
|
|
545
|
-
type,
|
|
546
|
-
getAd: () => event.getAd(),
|
|
547
|
-
getStreamData: () => event.getStreamData()
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
const getMpdStartTime = manifest => {
|
|
551
|
-
const mpdDocument = new DOMParser().parseFromString(manifest, 'text/xml');
|
|
552
|
-
const availabilityStartTime = mpdDocument.firstChild.getAttribute('availabilityStartTime');
|
|
553
|
-
return new Date(availabilityStartTime).getTime() / 1000;
|
|
554
|
-
}; // Align to Google DAI StreamManager
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const createStreamManager = (videoElement, {
|
|
558
|
-
player,
|
|
559
|
-
emitter
|
|
560
|
-
}) => {
|
|
561
|
-
let state = initialState;
|
|
562
|
-
const streamData = {};
|
|
563
|
-
const impression = new Impression({
|
|
564
|
-
seek: streamTime => seekVideo(videoElement, streamTime),
|
|
565
|
-
onAdBreakStarted: pipeEvent(emitter, 'adBreakStarted'),
|
|
566
|
-
onAdProgress: event => {
|
|
567
|
-
state.adEndTime = getAdEndTime(event.getStreamData(), videoElement);
|
|
568
|
-
pipeEvent(emitter, 'adProgress')(event);
|
|
569
|
-
},
|
|
570
|
-
onSkippableStateChanged: pipeEvent(emitter, 'skippableStateChanged'),
|
|
571
|
-
onAdBreakEnded: pipeEvent(emitter, 'adBreakEnded')
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
const previousCuePointForStreamTime = streamTime => {
|
|
575
|
-
const ad = getLastAd(state.adBreaks, streamTime - 0.1 - state.mpdStartTime);
|
|
576
|
-
|
|
577
|
-
if (ad.durationInSeconds > 0) {
|
|
578
|
-
const start = ad.startTimeInSeconds;
|
|
579
|
-
const end = start + ad.durationInSeconds;
|
|
580
|
-
return {
|
|
581
|
-
start,
|
|
582
|
-
end,
|
|
583
|
-
played: state.played[ad.availId]
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
return undefined;
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
emitter.on('adBreakEnded', () => {
|
|
591
|
-
state.adEndTime = -1;
|
|
592
|
-
const ad = getLastAd(state.adBreaks, videoElement.currentTime);
|
|
593
|
-
state.played[ad.availId] = true;
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
const refreshTrackingData = async () => {
|
|
597
|
-
if (!streamData.trackingUrl) {
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const trackingData = (await fetch(streamData.trackingUrl).then(result => result.json())) || {
|
|
602
|
-
avails: []
|
|
603
|
-
};
|
|
604
|
-
state.adBreaks = trackingData.avails || [];
|
|
605
|
-
|
|
606
|
-
if (trackingData.avails.length > 0) {
|
|
607
|
-
impression.adBreaks = state.adBreaks;
|
|
608
|
-
emitter.emit('cuepointsChanged', {
|
|
609
|
-
cuepoints: state.adBreaks.map(item => ({
|
|
610
|
-
start: getContentTime(state.adBreaks, item.startTimeInSeconds)
|
|
611
|
-
}))
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const handleTimeUpdate = streamTime => {
|
|
617
|
-
// TODO get tracking events with actual buffer length
|
|
618
|
-
if (!Number.isFinite(streamTime)) {
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (player.isLive() && streamTime > state.currentTime + 5) {
|
|
623
|
-
state.currentTime = streamTime;
|
|
624
|
-
refreshTrackingData();
|
|
625
|
-
} // workaround_OTP_2813
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (state.isUserSkipAd) {
|
|
629
|
-
// TODO: Try to migrate this with the workaround in the snapback
|
|
630
|
-
// 0.1 is magic number for float-point
|
|
631
|
-
if (state.skipAdEndTime + 0.1 >= streamTime) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
state.isUserSkipAd = false;
|
|
636
|
-
state.skipAdEndTime = -1;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
impression.currentPosition = streamTime - state.mpdStartTime;
|
|
640
|
-
};
|
|
641
|
-
|
|
642
|
-
const streamManager = {
|
|
643
|
-
requestStream: async (options = {}) => {
|
|
644
|
-
const reportingUrl = options.client_side_reporting_url;
|
|
645
|
-
const info = await fetchStreamInfo(reportingUrl, options.adParams).catch(error => ({
|
|
646
|
-
error
|
|
647
|
-
}));
|
|
648
|
-
|
|
649
|
-
if (!info || info.error) {
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
streamData.trackingUrl = new URL(info.trackingUrl, reportingUrl).toString();
|
|
654
|
-
streamData.url = new URL(info.manifestUrl, reportingUrl).toString(); // tracking events are available only after manifests are requested
|
|
655
|
-
|
|
656
|
-
await fetchManifests(streamData.url);
|
|
657
|
-
await refreshTrackingData();
|
|
658
|
-
emitter.emit('loaded', {
|
|
659
|
-
getStreamData: () => streamData
|
|
660
|
-
});
|
|
661
|
-
state.played = {};
|
|
662
|
-
},
|
|
663
|
-
addEventListener: (eventName, handler) => emitter.on(eventName, handler),
|
|
664
|
-
removeEventListener: (eventName, handler) => emitter.off(eventName, handler),
|
|
665
|
-
streamTimeForContentTime: contentTime => getStreamTime(state.adBreaks, contentTime),
|
|
666
|
-
contentTimeForStreamTime: streamTime => getContentTime(state.adBreaks, streamTime),
|
|
667
|
-
previousCuePointForStreamTime,
|
|
668
|
-
skipAd: () => {
|
|
669
|
-
if (state.adEndTime > 0) {
|
|
670
|
-
// workaround_OTP_2813
|
|
671
|
-
const seekTime = state.adEndTime;
|
|
672
|
-
handleTimeUpdate(state.adEndTime);
|
|
673
|
-
state.isUserSkipAd = true;
|
|
674
|
-
state.skipAdEndTime = seekTime;
|
|
675
|
-
player === null || player === void 0 ? void 0 : player.seek(seekTime, 'internal'); // TODO: Should provide methods getAd and getStreamData to align with Google Dai
|
|
676
|
-
|
|
677
|
-
emitter.emit('skip');
|
|
678
|
-
}
|
|
679
|
-
},
|
|
680
|
-
setMpdStartTime: time => {
|
|
681
|
-
state.mpdStartTime = time;
|
|
682
|
-
},
|
|
683
|
-
getVastAvails: () => state.adBreaks,
|
|
684
|
-
reset: () => {
|
|
685
|
-
state.registered.forEach(removeListener => removeListener());
|
|
686
|
-
impression.reset();
|
|
687
|
-
state = initialState;
|
|
688
|
-
streamData.trackingUrl = '';
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
const handleSeeking = seekingHandler(({
|
|
692
|
-
originTime,
|
|
693
|
-
seekTime
|
|
694
|
-
}) => {
|
|
695
|
-
if (state.adEndTime > 0) {
|
|
696
|
-
seekVideo(videoElement, originTime);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const diff = seekTime - originTime;
|
|
701
|
-
|
|
702
|
-
if (Math.abs(diff + 15) <= 0.25) {
|
|
703
|
-
seekVideo(videoElement, getStreamTime(state.adBreaks, getContentTime(state.adBreaks, originTime) + diff));
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
snapback({
|
|
708
|
-
streamManager,
|
|
709
|
-
originTime,
|
|
710
|
-
seekTime,
|
|
711
|
-
seek: streamTime => {
|
|
712
|
-
if (Math.abs(videoElement.currentTime - streamTime) > 0.5) {
|
|
713
|
-
seekVideo(videoElement, streamTime);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
});
|
|
717
|
-
});
|
|
718
|
-
state.registered = [on(videoElement, 'timeupdate', () => {
|
|
719
|
-
handleSeeking(videoElement);
|
|
720
|
-
|
|
721
|
-
if (!videoElement.paused) {
|
|
722
|
-
handleTimeUpdate(videoElement.currentTime);
|
|
723
|
-
}
|
|
724
|
-
}), on(videoElement, 'ended', () => handleTimeUpdate(Infinity))];
|
|
725
|
-
return streamManager;
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
const init = (options, {
|
|
729
|
-
skipWatched
|
|
730
|
-
}) => {
|
|
731
|
-
var _player$on;
|
|
732
|
-
|
|
733
|
-
const {
|
|
734
|
-
player,
|
|
735
|
-
video,
|
|
736
|
-
streamManager
|
|
737
|
-
} = options;
|
|
738
|
-
const ref = {
|
|
739
|
-
player,
|
|
740
|
-
video,
|
|
741
|
-
streamManager
|
|
742
|
-
};
|
|
743
|
-
streamManager.addEventListener('adProgress', event => {
|
|
744
|
-
ref.adEndTime = getAdEndTime(event.getStreamData(), ref.video);
|
|
745
|
-
});
|
|
746
|
-
streamManager.addEventListener('adBreakEnded', () => {
|
|
747
|
-
ref.adEndTime = -1;
|
|
748
|
-
});
|
|
749
|
-
player === null || player === void 0 ? void 0 : (_player$on = player.on) === null || _player$on === void 0 ? void 0 : _player$on.call(player, 'sourceloaded', () => {
|
|
750
|
-
ref.isLive = player.isLive();
|
|
751
|
-
|
|
752
|
-
if (player.manifest.dash && player.isLive()) {
|
|
753
|
-
// ad start / end time is based on availabilityStartTime in MPD manifest
|
|
754
|
-
streamManager.setMpdStartTime(getMpdStartTime(player.getManifest()));
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
if (skipWatched) {
|
|
759
|
-
video.addEventListener('timeupdate', () => {
|
|
760
|
-
const streamTime = video.currentTime;
|
|
761
|
-
const cuePoint = streamManager.previousCuePointForStreamTime(streamTime + 0.5);
|
|
762
|
-
|
|
763
|
-
if ((cuePoint === null || cuePoint === void 0 ? void 0 : cuePoint.end) > streamTime && cuePoint.played) {
|
|
764
|
-
player === null || player === void 0 ? void 0 : player.seek(cuePoint.end, 'internal');
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
return ref;
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
const MediaTailorPlugin = ({
|
|
773
|
-
adParams,
|
|
774
|
-
skipWatched
|
|
775
|
-
} = {}) => {
|
|
776
|
-
const emitter = mitt();
|
|
777
|
-
let ref = {};
|
|
778
|
-
let options = {
|
|
779
|
-
adParams
|
|
780
|
-
};
|
|
781
|
-
return {
|
|
782
|
-
isActive: () => !!ref.streamManager,
|
|
783
|
-
load: async (manifestItem, {
|
|
784
|
-
player,
|
|
785
|
-
video,
|
|
786
|
-
source = {}
|
|
787
|
-
} = {}) => {
|
|
788
|
-
var _ref$streamManager, _manifestItem$ssai, _source$options;
|
|
789
|
-
|
|
790
|
-
if (typeof fetch !== 'function') {
|
|
791
|
-
addFetchPolyfill();
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
(_ref$streamManager = ref.streamManager) === null || _ref$streamManager === void 0 ? void 0 : _ref$streamManager.reset();
|
|
795
|
-
const mediaTailorOptions = (_manifestItem$ssai = manifestItem.ssai) === null || _manifestItem$ssai === void 0 ? void 0 : _manifestItem$ssai.media_tailor;
|
|
796
|
-
|
|
797
|
-
if (!mediaTailorOptions) {
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
mediaTailorOptions.adParams = options.adParams;
|
|
802
|
-
const streamManager = createStreamManager(video, {
|
|
803
|
-
player,
|
|
804
|
-
emitter
|
|
805
|
-
});
|
|
806
|
-
ref = init({
|
|
807
|
-
player,
|
|
808
|
-
video,
|
|
809
|
-
streamManager
|
|
810
|
-
}, {
|
|
811
|
-
skipWatched
|
|
812
|
-
});
|
|
813
|
-
streamManager.requestStream(mediaTailorOptions);
|
|
814
|
-
const {
|
|
815
|
-
url
|
|
816
|
-
} = await new Promise(resolve => {
|
|
817
|
-
once(streamManager, 'loaded', event => resolve(event.getStreamData()));
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
if (!url) {
|
|
821
|
-
console.warn('Ad stream is not available, use fallback stream instead');
|
|
822
|
-
return manifestItem;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
return { ...manifestItem,
|
|
826
|
-
ssaiProvider: 'AWS',
|
|
827
|
-
url,
|
|
828
|
-
vastAvails: streamManager.getVastAvails(),
|
|
829
|
-
startTime: streamManager.streamTimeForContentTime((_source$options = source.options) === null || _source$options === void 0 ? void 0 : _source$options.startTime)
|
|
830
|
-
};
|
|
831
|
-
},
|
|
832
|
-
handleSeek: (contentTime, seek) => {
|
|
833
|
-
snapback({
|
|
834
|
-
streamManager: ref.streamManager,
|
|
835
|
-
originTime: ref.video.currentTime,
|
|
836
|
-
seekTime: ref.streamManager.streamTimeForContentTime(contentTime),
|
|
837
|
-
seek
|
|
838
|
-
});
|
|
839
|
-
},
|
|
840
|
-
skipAd: () => ref.streamManager.skipAd(),
|
|
841
|
-
getPlaybackStatus: () => ref.streamManager && { ...(!ref.isLive && {
|
|
842
|
-
currentTime: ref.streamManager.contentTimeForStreamTime(ref.video.currentTime),
|
|
843
|
-
duration: ref.streamManager.contentTimeForStreamTime(ref.video.duration)
|
|
844
|
-
}),
|
|
845
|
-
...(ref.adEndTime > 0 && {
|
|
846
|
-
adRemainingTime: ref.adEndTime - ref.video.currentTime
|
|
847
|
-
})
|
|
848
|
-
},
|
|
849
|
-
on: (name, listener) => emitter.on(name, listener),
|
|
850
|
-
reset: () => {
|
|
851
|
-
var _ref$streamManager2;
|
|
852
|
-
|
|
853
|
-
(_ref$streamManager2 = ref.streamManager) === null || _ref$streamManager2 === void 0 ? void 0 : _ref$streamManager2.reset();
|
|
854
|
-
ref.streamManager = undefined;
|
|
855
|
-
},
|
|
856
|
-
setOptions: updatedOptions => {
|
|
857
|
-
options = updatedOptions;
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
var MediaTailorPlugin$1 = MediaTailorPlugin;
|
|
863
|
-
|
|
864
|
-
const pipeEvents = (source, target, types) => {
|
|
865
|
-
const registered = types.map(name => {
|
|
866
|
-
const pipe = event => target.emit(name, event);
|
|
867
|
-
|
|
868
|
-
source.addEventListener(name, pipe);
|
|
869
|
-
return () => source.removeEventListener(name, pipe);
|
|
870
|
-
});
|
|
871
|
-
return () => [].concat(...registered).forEach(removeListener => removeListener());
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
const loadScript = url => new Promise(resolve => {
|
|
875
|
-
const script = Object.assign(document.createElement('script'), {
|
|
876
|
-
async: true,
|
|
877
|
-
src: url
|
|
878
|
-
});
|
|
879
|
-
script.addEventListener('load', resolve);
|
|
880
|
-
document.body.appendChild(script);
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
const GOOGLE_DAI_SDK_URL = 'https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js';
|
|
884
|
-
|
|
885
|
-
const ensureDaiApi = async () => {
|
|
886
|
-
var _window$google, _window$google$ima, _window$google$ima$da, _window$google$ima$da2;
|
|
887
|
-
|
|
888
|
-
if (!((_window$google = window.google) !== null && _window$google !== void 0 && (_window$google$ima = _window$google.ima) !== null && _window$google$ima !== void 0 && (_window$google$ima$da = _window$google$ima.dai) !== null && _window$google$ima$da !== void 0 && (_window$google$ima$da2 = _window$google$ima$da.api) !== null && _window$google$ima$da2 !== void 0 && _window$google$ima$da2.StreamManager)) {
|
|
889
|
-
await loadScript(GOOGLE_DAI_SDK_URL);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
return window.google.ima.dai.api;
|
|
893
|
-
};
|
|
894
|
-
|
|
895
|
-
const ImaDaiPlugin = ({
|
|
896
|
-
requestOptionOverrides = {}
|
|
897
|
-
} = {}) => {
|
|
898
|
-
const emitter = mitt();
|
|
899
|
-
const ref = {};
|
|
900
|
-
|
|
901
|
-
const reset = () => {
|
|
902
|
-
var _ref$streamManager;
|
|
903
|
-
|
|
904
|
-
ref.progress = undefined;
|
|
905
|
-
(_ref$streamManager = ref.streamManager) === null || _ref$streamManager === void 0 ? void 0 : _ref$streamManager.reset();
|
|
906
|
-
ref.streamManager = undefined;
|
|
907
|
-
ref.video = undefined;
|
|
908
|
-
};
|
|
909
|
-
|
|
910
|
-
const getStartTime = startTime => {
|
|
911
|
-
var _earliestCuepoint$sta, _earliestCuepoint;
|
|
912
|
-
|
|
913
|
-
const startTimeInStreamTime = ref.streamManager.streamTimeForContentTime(startTime);
|
|
914
|
-
let earliestCuepoint = {
|
|
915
|
-
start: startTimeInStreamTime
|
|
916
|
-
}; // Find if there is a pre-roll ad. Note that we need to handle multiple mid-roll ads in phase 2.
|
|
917
|
-
// The possible solution is storing the cuepoints in a stack and pop it while finishing an ad.
|
|
918
|
-
|
|
919
|
-
while (earliestCuepoint !== null && earliestCuepoint.start !== 0) {
|
|
920
|
-
earliestCuepoint = ref.streamManager.previousCuePointForStreamTime(earliestCuepoint.start);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
return (_earliestCuepoint$sta = (_earliestCuepoint = earliestCuepoint) === null || _earliestCuepoint === void 0 ? void 0 : _earliestCuepoint.start) !== null && _earliestCuepoint$sta !== void 0 ? _earliestCuepoint$sta : startTimeInStreamTime;
|
|
924
|
-
};
|
|
925
|
-
|
|
926
|
-
return {
|
|
927
|
-
isActive: () => !!ref.streamManager,
|
|
928
|
-
load: async (manifestItem, {
|
|
929
|
-
player,
|
|
930
|
-
video,
|
|
931
|
-
streamFormat,
|
|
932
|
-
startTime
|
|
933
|
-
}) => {
|
|
934
|
-
var _manifestItem$ssai;
|
|
935
|
-
|
|
936
|
-
reset();
|
|
937
|
-
|
|
938
|
-
if (!((_manifestItem$ssai = manifestItem.ssai) !== null && _manifestItem$ssai !== void 0 && _manifestItem$ssai.google_dai)) {
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
ref.video = video;
|
|
943
|
-
const {
|
|
944
|
-
live: liveOptions,
|
|
945
|
-
vod: vodOptions
|
|
946
|
-
} = manifestItem.ssai.google_dai;
|
|
947
|
-
const daiApi = await ensureDaiApi();
|
|
948
|
-
|
|
949
|
-
if (!ref.adContainer) {
|
|
950
|
-
// eslint-disable-next-line no-console
|
|
951
|
-
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`);
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
ref.streamManager = new daiApi.StreamManager(video, ref.adContainer);
|
|
955
|
-
pipeEvents(ref.streamManager, emitter, [daiApi.StreamEvent.Type.SKIPPABLE_STATE_CHANGED, daiApi.StreamEvent.Type.SKIPPED]);
|
|
956
|
-
/**
|
|
957
|
-
* To align with the MideaTailor, convert the cuepoint.start from the stream time to content time.
|
|
958
|
-
*/
|
|
959
|
-
|
|
960
|
-
ref.streamManager.addEventListener(daiApi.StreamEvent.Type.CUEPOINTS_CHANGED, event => {
|
|
961
|
-
const cuepointsInContentTime = event.getStreamData().cuepoints.map(cuepoint => ({
|
|
962
|
-
start: ref.streamManager.contentTimeForStreamTime(cuepoint.start),
|
|
963
|
-
duration: cuepoint.end - cuepoint.start
|
|
964
|
-
}));
|
|
965
|
-
emitter.emit('cuepointsChanged', {
|
|
966
|
-
cuepoints: cuepointsInContentTime
|
|
967
|
-
});
|
|
968
|
-
});
|
|
969
|
-
/**
|
|
970
|
-
* We can't get the adProgressData from the event adBreakStarted, so we map the first call of adProgress to adBreakStarted.
|
|
971
|
-
*/
|
|
972
|
-
|
|
973
|
-
let isFirstAdProgressCalled = true;
|
|
974
|
-
ref.streamManager.addEventListener('adProgress', event => {
|
|
975
|
-
if (isFirstAdProgressCalled) {
|
|
976
|
-
emitter.emit('adBreakStarted', event);
|
|
977
|
-
isFirstAdProgressCalled = false;
|
|
978
|
-
} else {
|
|
979
|
-
emitter.emit('adProgress', event);
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
ref.streamManager.addEventListener('adBreakEnded', event => {
|
|
983
|
-
emitter.emit('adBreakEnded', event);
|
|
984
|
-
isFirstAdProgressCalled = true;
|
|
985
|
-
});
|
|
986
|
-
const requestOptions = {
|
|
987
|
-
apiKey: (liveOptions === null || liveOptions === void 0 ? void 0 : liveOptions.api_key) || (vodOptions === null || vodOptions === void 0 ? void 0 : vodOptions.api_key),
|
|
988
|
-
contentSourceId: vodOptions === null || vodOptions === void 0 ? void 0 : vodOptions.content_source_id,
|
|
989
|
-
videoId: vodOptions === null || vodOptions === void 0 ? void 0 : vodOptions.video_id,
|
|
990
|
-
assetKey: liveOptions === null || liveOptions === void 0 ? void 0 : liveOptions.asset_key,
|
|
991
|
-
format: streamFormat,
|
|
992
|
-
...requestOptionOverrides
|
|
993
|
-
};
|
|
994
|
-
const streamRequest = Object.assign(liveOptions ? new daiApi.LiveStreamRequest() : new daiApi.VODStreamRequest(), requestOptions);
|
|
995
|
-
const url = await new Promise(resolve => {
|
|
996
|
-
ref.streamManager.addEventListener(daiApi.StreamEvent.Type.LOADED, e => resolve(e.getStreamData().url));
|
|
997
|
-
ref.streamManager.requestStream(streamRequest);
|
|
998
|
-
});
|
|
999
|
-
player.on('metadata', ({
|
|
1000
|
-
metadata
|
|
1001
|
-
}) => {
|
|
1002
|
-
const TXXX = metadata.TXXX || metadata.properties.messageData;
|
|
1003
|
-
ref.streamManager.onTimedMetadata({
|
|
1004
|
-
TXXX
|
|
1005
|
-
});
|
|
1006
|
-
});
|
|
1007
|
-
ref.streamManager.addEventListener(daiApi.StreamEvent.Type.AD_PROGRESS, e => {
|
|
1008
|
-
ref.progress = e.getStreamData().adProgressData;
|
|
1009
|
-
});
|
|
1010
|
-
return {
|
|
1011
|
-
ssaiProvider: 'DAI',
|
|
1012
|
-
url,
|
|
1013
|
-
startTime: getStartTime(startTime)
|
|
1014
|
-
};
|
|
1015
|
-
},
|
|
1016
|
-
getPlaybackStatus: () => ref.streamManager && ref.video && {
|
|
1017
|
-
currentTime: ref.streamManager.contentTimeForStreamTime(ref.video.currentTime),
|
|
1018
|
-
duration: ref.streamManager.contentTimeForStreamTime(ref.video.duration),
|
|
1019
|
-
...(ref.progress && {
|
|
1020
|
-
adRemainingTime: ref.progress.duration - ref.progress.currentTime
|
|
1021
|
-
})
|
|
1022
|
-
},
|
|
1023
|
-
on: (name, listener) => emitter.on(name, listener),
|
|
1024
|
-
reset,
|
|
1025
|
-
handleSeek: (contentTime, seek) => {
|
|
1026
|
-
snapback({
|
|
1027
|
-
streamManager: ref.streamManager,
|
|
1028
|
-
originTime: ref.video.currentTime,
|
|
1029
|
-
seekTime: ref.streamManager.streamTimeForContentTime(contentTime),
|
|
1030
|
-
seek
|
|
1031
|
-
});
|
|
1032
|
-
},
|
|
1033
|
-
setAdContainer: adContainer => {
|
|
1034
|
-
ref.adContainer = adContainer;
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
};
|
|
1038
|
-
|
|
1039
|
-
var ImaDaiPlugin$1 = ImaDaiPlugin;
|
|
1040
|
-
|
|
1041
|
-
/* eslint-disable no-plusplus */
|
|
1042
|
-
new UAParser();
|
|
1043
|
-
|
|
1044
|
-
/* eslint-disable no-param-reassign */
|
|
1045
|
-
|
|
1046
|
-
const SHAKA_LIVE_DURATION = 4294967296;
|
|
1047
|
-
|
|
1048
|
-
const isLiveDuration = duration => duration >= SHAKA_LIVE_DURATION;
|
|
1049
|
-
|
|
1050
|
-
const wallTimeSeconds = () => Date.now() / 1000;
|
|
1051
|
-
|
|
1052
|
-
const StallReloadPlugin = ({
|
|
1053
|
-
stallThresholdSeconds = 10
|
|
1054
|
-
} = {}) => {
|
|
1055
|
-
const state = {
|
|
1056
|
-
lastVideoTime: 0,
|
|
1057
|
-
lastTimer: 0,
|
|
1058
|
-
lastUpdateSeconds: wallTimeSeconds()
|
|
1059
|
-
};
|
|
1060
|
-
|
|
1061
|
-
const load = (_, {
|
|
1062
|
-
player,
|
|
1063
|
-
video,
|
|
1064
|
-
reload
|
|
1065
|
-
}) => {
|
|
1066
|
-
clearInterval(state.lastTimer);
|
|
1067
|
-
|
|
1068
|
-
const checkStall = async () => {
|
|
1069
|
-
if (!isLiveDuration(video.duration)) {
|
|
1070
|
-
return;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
if (video.paused || video.playbackRate === 0 || state.lastVideoTime !== video.currentTime || !(video.currentTime > 0)) {
|
|
1074
|
-
state.lastUpdateSeconds = wallTimeSeconds();
|
|
1075
|
-
state.lastVideoTime = video.currentTime;
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
if (wallTimeSeconds() - state.lastUpdateSeconds > stallThresholdSeconds) {
|
|
1080
|
-
console.warn('Stall detected, reload to resume');
|
|
1081
|
-
await reload();
|
|
1082
|
-
|
|
1083
|
-
if (player.play) {
|
|
1084
|
-
player.play();
|
|
1085
|
-
} else {
|
|
1086
|
-
video.play();
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
state.lastVideoTime = video.currentTime;
|
|
1092
|
-
state.lastTimer = setInterval(checkStall, stallThresholdSeconds / 8 * 1000);
|
|
1093
|
-
video.addEventListener('play', () => {
|
|
1094
|
-
state.lastUpdateSeconds = wallTimeSeconds();
|
|
1095
|
-
});
|
|
1096
|
-
};
|
|
1097
|
-
|
|
1098
|
-
return {
|
|
1099
|
-
load
|
|
1100
|
-
};
|
|
1101
|
-
};
|
|
1102
|
-
|
|
1103
|
-
var StallReloadPlugin$1 = StallReloadPlugin;
|
|
1104
|
-
|
|
1105
|
-
export { ImaDaiPlugin$1 as ImaDaiPlugin, MediaTailorPlugin$1 as MediaTailorPlugin, StallReloadPlugin$1 as StallReloadPlugin };
|