@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/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 };