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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1057 @@
1
+ import { t as DEBUG_OPTIONS } from "./debugUtil-IF7p5TSI.mjs";
2
+ import { A as once, D as SeekOrigin, i as isLiveDuration, k as on, m as getSource, o as seek$1 } from "./mediaBindings-CoY60lQw.mjs";
3
+ import { t as loadScript_default } from "./loadScript-Ct19kU9g.mjs";
4
+ import mitt from "mitt";
5
+
6
+ //#region rolldown:runtime
7
+ var __create = Object.create;
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __getProtoOf = Object.getPrototypeOf;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
14
+ var __exportAll = (all, symbols) => {
15
+ let target = {};
16
+ for (var name in all) {
17
+ __defProp(target, name, {
18
+ get: all[name],
19
+ enumerable: true
20
+ });
21
+ }
22
+ if (symbols) {
23
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
24
+ }
25
+ return target;
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") {
29
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
30
+ key = keys[i];
31
+ if (!__hasOwnProp.call(to, key) && key !== except) {
32
+ __defProp(to, key, {
33
+ get: ((k) => from[k]).bind(null, key),
34
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
35
+ });
36
+ }
37
+ }
38
+ }
39
+ return to;
40
+ };
41
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
42
+ value: mod,
43
+ enumerable: true
44
+ }) : target, mod));
45
+
46
+ //#endregion
47
+ //#region src/premium/timeline.js
48
+ const getChapterIndex = (chapters, time) => chapters.reduce((currentIndex, chapter, index) => time >= chapter.startTime && chapter.startTime > chapters[currentIndex].startTime ? index : currentIndex, 0);
49
+ const dispatchChapterEvents = ({ media, chapters = [], getTime }) => {
50
+ const state = {};
51
+ const updateChapter = (event) => {
52
+ if (media.webkitDisplayingFullscreen) return;
53
+ const next = getChapterIndex(chapters, getTime());
54
+ if (next !== state.currentChapterIndex) media.dispatchEvent(Object.assign(new CustomEvent("chapterChange"), {
55
+ action: /seek/.test(event?.type) ? "seek" : "",
56
+ chapterIndex: next,
57
+ chapter: chapters[next]
58
+ }));
59
+ state.currentChapterIndex = next;
60
+ };
61
+ const listeners = [
62
+ on(media, "seeking", updateChapter),
63
+ on(media, "timeupdate", updateChapter),
64
+ on(media, "seeked", updateChapter),
65
+ on(media, "webkitendfullscreen", updateChapter)
66
+ ];
67
+ const checkInterval = setInterval(() => {
68
+ const timeToNext = (chapters[state.currentChapterIndex + 1]?.startTime - getTime() + .05) / media.playbackRate;
69
+ if (timeToNext >= 0 && timeToNext <= 1) state.timerId = setTimeout(updateChapter, timeToNext * 1e3);
70
+ }, 1e3);
71
+ return () => {
72
+ clearInterval(checkInterval);
73
+ clearInterval(state.timerId);
74
+ listeners.forEach((removeListener) => removeListener());
75
+ };
76
+ };
77
+ const getNextChapter = (chapters, currentTime) => chapters.find((chapter) => chapter.startTime > currentTime);
78
+ const dispatchSkipEvent = (media, chapters, triggerMethod) => {
79
+ if (!media || chapters?.length < 1) return;
80
+ const skipFrom = media?.currentTime;
81
+ const skipTo = getNextChapter(chapters, media.currentTime)?.startTime;
82
+ media.dispatchEvent(Object.assign(new CustomEvent("skip"), {
83
+ skipFrom,
84
+ skipTo,
85
+ triggerMethod
86
+ }));
87
+ };
88
+ const goToNextChapter = (media, chapters) => {
89
+ const nextChapter = getNextChapter(chapters, media.currentTime);
90
+ if (nextChapter) seek$1(media, { player: {} }, nextChapter.startTime + .8);
91
+ };
92
+ const autoSkip = ({ media, chapters, autoSkipOpening = 3, autoplayNext = 10, onStateUpdate }) => {
93
+ const state = { maxPlayedTime: 0 };
94
+ let skipTimer;
95
+ const handleChapterChange = ({ action, chapter = {} }) => {
96
+ state.currentChapter = chapter.type;
97
+ if (chapter.type === "opening") {
98
+ const enableAutoSkip = action !== "seek" && media.currentTime > state.maxPlayedTime && autoSkipOpening;
99
+ state.maxPlayedTime = Infinity;
100
+ onStateUpdate({
101
+ display: "skip-opening",
102
+ autoSkip: enableAutoSkip
103
+ });
104
+ if (enableAutoSkip) skipTimer = setTimeout(() => {
105
+ dispatchSkipEvent(media, chapters, "auto_skip");
106
+ goToNextChapter(media, chapters);
107
+ }, autoSkipOpening * 1e3);
108
+ } else if (chapter.type === "ending") {
109
+ const autoSkipTime = action !== "seek" && !state.reachedEnding && autoplayNext;
110
+ onStateUpdate({
111
+ display: state.reachedEnding ? "none" : "skip-ending",
112
+ autoSkip: autoSkipTime,
113
+ chapter: "ending"
114
+ });
115
+ state.reachedEnding = true;
116
+ } else {
117
+ clearInterval(skipTimer);
118
+ onStateUpdate({ display: "none" });
119
+ }
120
+ state.maxPlayedTime = Math.max(media.currentTime, state.maxPlayedTime);
121
+ };
122
+ const listeners = [on(media, "chapterChange", handleChapterChange), on(media, "ended", () => {
123
+ state.chapter = "ended";
124
+ onStateUpdate({
125
+ display: "skip-ending",
126
+ autoSkip: autoplayNext,
127
+ chapter: "ended"
128
+ });
129
+ state.reachedEnding = true;
130
+ })];
131
+ return {
132
+ skip: () => {
133
+ onStateUpdate({ display: "none" });
134
+ goToNextChapter(media, chapters);
135
+ },
136
+ dismiss: () => {
137
+ clearInterval(skipTimer);
138
+ onStateUpdate({
139
+ chapter: state.chapter,
140
+ display: state.currentChapter === "opening" ? "skip-opening" : "none",
141
+ autoSkip: false
142
+ });
143
+ },
144
+ clear: () => {
145
+ onStateUpdate({ display: "none" });
146
+ state.reachedOpening = false;
147
+ clearInterval(skipTimer);
148
+ listeners.forEach((removeListener) => removeListener());
149
+ }
150
+ };
151
+ };
152
+ const getTimelineSegments = (chapters, { startTime, current, buffered, duration }) => (chapters.length === 0 ? [{ startTime }] : chapters).map((chapter, index) => {
153
+ const length = (chapters[index + 1]?.startTime || duration) - chapter.startTime;
154
+ return {
155
+ length: length / duration,
156
+ current: Math.min(Math.max(0, (current - chapter.startTime) / length), 1),
157
+ buffered: Math.min(Math.max(0, (buffered - chapter.startTime) / length), 1)
158
+ };
159
+ });
160
+
161
+ //#endregion
162
+ //#region src/cast/framework.js
163
+ const SENDER_URL = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";
164
+ const getContext = () => window.cast && cast.framework?.CastContext.getInstance();
165
+ const getCastReceiverState = () => {
166
+ const context = getContext();
167
+ const castState = context.getCastState();
168
+ const currentSession = context.getCurrentSession();
169
+ const mediaTitle = currentSession.getMediaSession()?.media.metadata.title;
170
+ return {
171
+ castState,
172
+ deviceName: castState === "CONNECTED" && currentSession.getCastDevice().friendlyName,
173
+ mediaTitle,
174
+ ...currentSession && {
175
+ volume: currentSession.getVolume(),
176
+ muted: currentSession.isMute()
177
+ }
178
+ };
179
+ };
180
+ const getMediaSession = () => {
181
+ return getContext()?.getCurrentSession()?.getMediaSession() || {};
182
+ };
183
+ const ensureSenderFramework = () => {
184
+ if (window.cast && cast.framework && window.chrome && chrome.cast) return Promise.resolve(getContext());
185
+ if (!window.loadSenderFramework) window.loadSenderFramework = new Promise((resolve, reject) => {
186
+ window.__onGCastApiAvailable = (isAvailable) => {
187
+ if (isAvailable) resolve(getContext());
188
+ else reject();
189
+ };
190
+ loadScript_default(SENDER_URL);
191
+ });
192
+ return window.loadSenderFramework;
193
+ };
194
+ const LIVE_EDGE_GAP = 10;
195
+ const getLiveTime = () => {
196
+ if (typeof window === "undefined" || !window.cast) return {};
197
+ const mediaSession = getMediaSession();
198
+ if (!mediaSession.getEstimatedTime) return {};
199
+ const liveSeekableRange = mediaSession.media.metadata.sectionStartTimeInMedia && (mediaSession.getEstimatedLiveSeekableRange() || {});
200
+ if (!liveSeekableRange) return {
201
+ startTime: 0,
202
+ currentTime: mediaSession.getEstimatedTime(),
203
+ duration: mediaSession.media.duration
204
+ };
205
+ const actualTime = mediaSession.getEstimatedTime() - liveSeekableRange.end;
206
+ const currentTime = Math.abs(actualTime) < LIVE_EDGE_GAP ? 0 : actualTime;
207
+ const duration = liveSeekableRange.end - liveSeekableRange.start;
208
+ return {
209
+ startTime: -duration,
210
+ currentTime,
211
+ duration
212
+ };
213
+ };
214
+ const setAppId = ({ context, appId }) => {
215
+ context.setOptions({
216
+ receiverApplicationId: appId,
217
+ resumeSavedSession: true,
218
+ autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
219
+ });
220
+ return context;
221
+ };
222
+ const initSender = async ({ appId, ...options }) => {
223
+ (await ensureSenderFramework()).setOptions({
224
+ receiverApplicationId: appId,
225
+ resumeSavedSession: true,
226
+ autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
227
+ ...options
228
+ });
229
+ };
230
+ const connect = () => {
231
+ const context = getContext();
232
+ const currentSession = context.getCurrentSession();
233
+ if (currentSession) return Promise.resolve(currentSession);
234
+ return context.requestSession().then(() => context.getCurrentSession()).catch((e) => {
235
+ console.log(e);
236
+ });
237
+ };
238
+ const seek = (options) => {
239
+ const seconds = options.seconds || options;
240
+ const origin = options.origin || SeekOrigin.START;
241
+ const media = getMediaSession();
242
+ const startOverOffset = media.getEstimatedLiveSeekableRange()?.end || 0;
243
+ const currentTime = seconds + (origin === SeekOrigin.CURRENT ? media.getEstimatedTime() : 0) + startOverOffset;
244
+ media.seek(Object.assign(new chrome.cast.media.SeekRequest(), { currentTime }));
245
+ };
246
+ const setVolume = (volume) => getContext().getCurrentSession().setVolume(volume);
247
+ const getCastState = () => {
248
+ const context = getContext();
249
+ const castState = context.getCastState();
250
+ const mediaSession = context.getCurrentSession()?.getMediaSession() || {};
251
+ return {
252
+ playbackState: castState === "CONNECTED" ? mediaSession.playerState : castState,
253
+ deviceName: context.getCurrentSession()?.getCastDevice().friendlyName,
254
+ title: mediaSession.media?.metadata?.title,
255
+ playingIndex: mediaSession.currentItemId,
256
+ queueSize: mediaSession.items?.length,
257
+ ...getLiveTime()
258
+ };
259
+ };
260
+ const subscribeSessionState = (onChange) => {
261
+ const request = ensureSenderFramework().then(() => {
262
+ const { CAST_STATE_CHANGED } = cast.framework.CastContextEventType;
263
+ const { PLAYER_STATE_CHANGED, TITLE_CHANGED, MEDIA_INFO_CHANGED, CURRENT_TIME_CHANGED, DURATION_CHANGED, IS_PLAYING_BREAK_CHANGED, BREAK_CLIP_ID_CHANGED, CURRENT_BREAK_TIME_CHANGED } = cast.framework.RemotePlayerEventType;
264
+ const notifyChange = () => onChange(getCastState());
265
+ const context = getContext();
266
+ const player = new cast.framework.RemotePlayer();
267
+ const controller = new cast.framework.RemotePlayerController(player);
268
+ context.addEventListener(CAST_STATE_CHANGED, notifyChange);
269
+ const listeners = [
270
+ PLAYER_STATE_CHANGED,
271
+ TITLE_CHANGED,
272
+ MEDIA_INFO_CHANGED,
273
+ CURRENT_TIME_CHANGED,
274
+ DURATION_CHANGED,
275
+ IS_PLAYING_BREAK_CHANGED,
276
+ BREAK_CLIP_ID_CHANGED,
277
+ CURRENT_BREAK_TIME_CHANGED
278
+ ].map((eventName) => {
279
+ controller.addEventListener(eventName, notifyChange);
280
+ return () => controller.removeEventListener(eventName, notifyChange);
281
+ });
282
+ notifyChange();
283
+ return listeners;
284
+ });
285
+ return () => request.then((listeners) => listeners.forEach((removeListener) => removeListener()));
286
+ };
287
+ const initializeSenderState = ({ context, setState, setCastState, getVolume: getVolume$1 }) => {
288
+ const handleCastStateChange = ({ castState }) => {
289
+ const currentSession = context.getCurrentSession();
290
+ setCastState({
291
+ castState,
292
+ deviceName: castState === "CONNECTED" && currentSession.getCastDevice().friendlyName
293
+ });
294
+ };
295
+ context.addEventListener(cast.framework.CastContextEventType.CAST_STATE_CHANGED, handleCastStateChange);
296
+ handleCastStateChange({ castState: context.getCastState() });
297
+ const player = new cast.framework.RemotePlayer();
298
+ const controller = new cast.framework.RemotePlayerController(player);
299
+ const remotePlayerEventType = cast.framework.RemotePlayerEventType;
300
+ controller.addEventListener(remotePlayerEventType.PLAYER_STATE_CHANGED, ({ value }) => {
301
+ setCastState({
302
+ playerState: value,
303
+ castingMedia: value !== "IDLE" ? getMediaSession().media : null
304
+ });
305
+ });
306
+ controller.addEventListener(remotePlayerEventType.TITLE_CHANGED, ({ value }) => setCastState({ mediaTitle: value }));
307
+ controller.addEventListener(remotePlayerEventType.MEDIA_INFO_CHANGED, ({ value }) => {
308
+ const { media } = getMediaSession() || { media: {} };
309
+ setCastState({
310
+ castingMedia: value,
311
+ ...value ? value.customData && { customData: value.customData } : { customData: {} },
312
+ metadata: media.metadata,
313
+ seekEnabled: getMediaSession()?.supportedMediaCommands.includes("seek")
314
+ });
315
+ });
316
+ controller.addEventListener(remotePlayerEventType.DURATION_CHANGED, ({ value }) => setCastState({ duration: value }));
317
+ controller.addEventListener(remotePlayerEventType.CURRENT_TIME_CHANGED, ({ value }) => setState((current) => ({
318
+ ...current,
319
+ progressTime: value
320
+ })));
321
+ controller.addEventListener(remotePlayerEventType.MEDIA_INFO_CHANGED, ({ value }) => setCastState({ streamType: value && value.streamType }));
322
+ controller.addEventListener(remotePlayerEventType.MEDIA_INFO_CHANGED, ({ value }) => {
323
+ const { activeTrackIds = [] } = getMediaSession() || { media: {} };
324
+ setCastState({
325
+ castingMedia: value,
326
+ activeTrackIds
327
+ });
328
+ });
329
+ controller.addEventListener(remotePlayerEventType.IS_PLAYING_BREAK_CHANGED, ({ value }) => setCastState({ isPlayingBreak: value }));
330
+ controller.addEventListener(remotePlayerEventType.BREAK_CLIP_ID_CHANGED, ({ value }) => setCastState({
331
+ breakClipId: value,
332
+ clickThroughUrl: (getMediaSession().media?.breakClips || []).find((item) => item.id === value)?.clickThroughUrl
333
+ }));
334
+ controller.addEventListener(remotePlayerEventType.CURRENT_BREAK_TIME_CHANGED, ({ value }) => {
335
+ setCastState({
336
+ currentBreakTime: value,
337
+ whenSkippable: player.whenSkippable
338
+ });
339
+ });
340
+ const subscribeVolumeChange$1 = (handleChange) => {
341
+ const listeners = [on(controller, remotePlayerEventType.VOLUME_LEVEL_CHANGED, () => handleChange(getVolume$1(context.getCurrentSession()))), on(controller, remotePlayerEventType.IS_MUTED_CHANGED, () => handleChange(getVolume$1(context.getCurrentSession())))];
342
+ return () => listeners.forEach((removeListener) => removeListener());
343
+ };
344
+ const getItem = (media, index) => {
345
+ const items = media.items || [];
346
+ return items[(items.length + index) % items.length] || {};
347
+ };
348
+ return {
349
+ connect: () => connect(),
350
+ stopCasting: () => context.endCurrentSession(true),
351
+ play: () => getMediaSession().play(new chrome.cast.media.PlayRequest()),
352
+ pause: () => getMediaSession().pause(new chrome.cast.media.PauseRequest()),
353
+ seek,
354
+ skipAd: () => controller.skipAd(),
355
+ hasPrevious: () => {
356
+ const media = getMediaSession() || {};
357
+ return media.currentItemId !== getItem(media, 0).itemId;
358
+ },
359
+ hasNext: () => {
360
+ const media = getMediaSession() || {};
361
+ return media.currentItemId !== getItem(media, -1).itemId;
362
+ },
363
+ changePreviousEpisode: () => new Promise((resolve, reject) => {
364
+ getMediaSession().queuePrev(resolve, reject);
365
+ }),
366
+ changeNextEpisode: () => new Promise((resolve, reject) => {
367
+ getMediaSession().queueNext(resolve, reject);
368
+ }),
369
+ subscribeVolumeChange: subscribeVolumeChange$1,
370
+ setVolume,
371
+ toggleMute: () => controller.muteOrUnmute()
372
+ };
373
+ };
374
+ const togglePlay = () => {
375
+ const player = new cast.framework.RemotePlayer();
376
+ return new cast.framework.RemotePlayerController(player).playOrPause();
377
+ };
378
+ const queuePrevious = () => getMediaSession()?.queuePrev();
379
+ const queueNext = () => getMediaSession()?.queueNext();
380
+ const getVolume = () => {
381
+ const session = getContext()?.getCurrentSession();
382
+ return session && {
383
+ volume: session.getVolume(),
384
+ muted: session.isMute()
385
+ };
386
+ };
387
+ const subscribeVolumeChange = (handleChange) => {
388
+ const player = new cast.framework.RemotePlayer();
389
+ const controller = new cast.framework.RemotePlayerController(player);
390
+ const { VOLUME_LEVEL_CHANGED, IS_MUTED_CHANGED } = cast.framework.RemotePlayerEventType;
391
+ const listeners = [on(controller, VOLUME_LEVEL_CHANGED, () => handleChange(getVolume())), on(controller, IS_MUTED_CHANGED, () => handleChange(getVolume()))];
392
+ return () => listeners.forEach((removeListener) => removeListener());
393
+ };
394
+ const toggleMute = () => {
395
+ const player = new cast.framework.RemotePlayer();
396
+ return new cast.framework.RemotePlayerController(player).muteOrUnmute();
397
+ };
398
+ const skipAd = () => {
399
+ const player = new cast.framework.RemotePlayer();
400
+ return new cast.framework.RemotePlayerController(player).skipAd();
401
+ };
402
+ const parseMediaTracks = ({ castingMediaTracks, activeTrackIds }) => {
403
+ const tracks = castingMediaTracks.map((track) => ({
404
+ type: track.type.toLowerCase(),
405
+ label: track.name || track.language,
406
+ value: track.trackId,
407
+ enabled: false,
408
+ trackId: track.trackId
409
+ }));
410
+ const audioTracks = tracks.filter((track) => track.type === "audio");
411
+ const subtitleTracks = tracks.filter((track) => track.type === "text");
412
+ const activeAudio = activeTrackIds.find((id) => audioTracks.some((track) => track.value === id)) || audioTracks[0]?.value;
413
+ const activeSubtitles = activeTrackIds.find((id) => subtitleTracks.some((track) => track.value === id));
414
+ return {
415
+ sections: [{
416
+ name: "audio",
417
+ title: "KKS.AUDIO",
418
+ items: audioTracks
419
+ }, {
420
+ name: "subtitles",
421
+ title: "KKS.SUBTITLES",
422
+ items: [{
423
+ label: "OFF",
424
+ value: "off"
425
+ }].concat(subtitleTracks)
426
+ }],
427
+ values: {
428
+ audio: activeAudio,
429
+ subtitles: activeSubtitles || "off"
430
+ }
431
+ };
432
+ };
433
+ const switchTracks = ({ audio, subtitles }) => window.cast && getMediaSession()?.editTracksInfo?.(new chrome.cast.media.EditTracksInfoRequest([audio, subtitles].filter((id) => id !== "off")));
434
+ const subscribeCastState = (handleStateChange) => {
435
+ const waitFramework = ensureSenderFramework().then(() => {
436
+ const name = cast.framework.CastContextEventType.CAST_STATE_CHANGED;
437
+ const context = getContext();
438
+ const onChange = ({ castState }) => handleStateChange(castState);
439
+ handleStateChange(context.getCastState());
440
+ context.addEventListener(name, onChange);
441
+ return () => context.removeEventListener(name, onChange);
442
+ });
443
+ return () => waitFramework.then((removeListeners) => removeListeners());
444
+ };
445
+ const getMediaInfo = ({ manifestOptions, source, values }) => {
446
+ const { src, drm } = getSource(source, manifestOptions);
447
+ const { title, locale, audio, subtitles, modulesConfig } = values;
448
+ const customData = {
449
+ drm: {
450
+ ...drm?.widevine,
451
+ licenseUrl: drm?.widevine?.licenseUri
452
+ },
453
+ locale,
454
+ audioTrack: {
455
+ language: audio,
456
+ name: audio
457
+ },
458
+ subtitleTrack: {
459
+ language: subtitles,
460
+ name: subtitles
461
+ },
462
+ modulesConfig
463
+ };
464
+ return Object.assign(new chrome.cast.media.MediaInfo(src), {
465
+ contentId: src,
466
+ metadata: { title },
467
+ customData
468
+ });
469
+ };
470
+ const startCastPlaylist = async (mediaInfos, onLoad, startIndex = 0) => {
471
+ const castSession = await connect();
472
+ const mediaItems = mediaInfos.map((media) => new chrome.cast.media.QueueItem(media));
473
+ const request = new chrome.cast.media.QueueLoadRequest(mediaItems);
474
+ request.startIndex = startIndex;
475
+ await onLoad?.(request);
476
+ return new Promise((resolve, reject) => {
477
+ castSession.getSessionObj().queueLoad(request, () => resolve(getCastReceiverState()), (failEvent) => {
478
+ console.log(failEvent);
479
+ return reject(failEvent);
480
+ });
481
+ });
482
+ };
483
+ const startCastVideo = async (source, onLoad, { title, locale, audio, subtitles, modulesConfig }) => {
484
+ const castSession = await connect();
485
+ const { src, drm } = source || {};
486
+ const customData = {
487
+ drm: {
488
+ ...drm?.widevine,
489
+ licenseUrl: drm?.widevine?.licenseUri
490
+ },
491
+ locale,
492
+ audioTrack: {
493
+ language: audio,
494
+ name: audio
495
+ },
496
+ subtitleTrack: {
497
+ language: subtitles,
498
+ name: subtitles
499
+ },
500
+ modulesConfig
501
+ };
502
+ const media = Object.assign(new chrome.cast.media.MediaInfo(src), {
503
+ contentId: src,
504
+ metadata: { title },
505
+ customData
506
+ });
507
+ const request = new chrome.cast.media.LoadRequest(media);
508
+ await onLoad?.(request);
509
+ return castSession.loadMedia(request).then(() => getCastReceiverState()).catch((e) => {
510
+ console.log(e);
511
+ return Promise.reject(e);
512
+ });
513
+ };
514
+ const disconnect = async () => {
515
+ await ensureSenderFramework();
516
+ try {
517
+ getContext().endCurrentSession(true);
518
+ } catch (e) {
519
+ console.log(e);
520
+ }
521
+ };
522
+ const loadMedia = ({ source, contentType, contentId: paramContentId, sourceType, apiConfig, customData }) => {
523
+ const session = getContext().getCurrentSession();
524
+ if (!session) return "receiver_unavailable";
525
+ const sourceInfo = source && getSource(source);
526
+ const contentId = paramContentId || sourceInfo?.src;
527
+ const drm = sourceInfo?.drm && {
528
+ ...sourceInfo.drm?.widevine,
529
+ licenseUrl: sourceInfo.drm?.widevine?.licenseUri
530
+ };
531
+ const request = new chrome.cast.media.LoadRequest(Object.assign(new chrome.cast.media.MediaInfo(contentId), {
532
+ contentId,
533
+ contentID: contentId,
534
+ customData: {
535
+ itemType: contentType,
536
+ ...apiConfig,
537
+ customHeaders: apiConfig?.headers,
538
+ customQuery: apiConfig?.params,
539
+ mediaSource: sourceType,
540
+ ...drm && { drm },
541
+ ...customData
542
+ },
543
+ metadata: new chrome.cast.media.GenericMediaMetadata()
544
+ }));
545
+ const currentMedia = session?.getMediaSession()?.media || {};
546
+ if (contentId === currentMedia.contentId && contentType === currentMedia.customData?.itemType) return Promise.resolve();
547
+ return session.loadMedia(request).catch((e) => console.log(e));
548
+ };
549
+ const linkCast = ({ onChange, ...options }) => subscribeCastState((state) => {
550
+ if (state === "CONNECTED") loadMedia(options);
551
+ onChange?.(state);
552
+ });
553
+
554
+ //#endregion
555
+ //#region src/util/uuidv4.js
556
+ const uuidv4 = () => {
557
+ const crypto = window.crypto || window.msCrypto;
558
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));
559
+ };
560
+ var uuidv4_default = uuidv4;
561
+
562
+ //#endregion
563
+ //#region src/playerCore/playlogv2.js
564
+ const modes = {
565
+ videos: "video",
566
+ lives: "live"
567
+ };
568
+ const logEventNames = {
569
+ playbackBegan: "video_playback_began",
570
+ playbackStarted: "video_playback_started",
571
+ playbackStopped: "video_playback_stopped",
572
+ playbackEnded: "video_playback_ended",
573
+ bufferingStarted: "video_buffering_started",
574
+ bufferingEnded: "video_buffering_ended",
575
+ playbackSpeedChange: "playback_speed_change",
576
+ seeked: "video_seeking_ended",
577
+ playbackError: "video_playback_error_occurred",
578
+ playing: "play",
579
+ paused: "pause",
580
+ rewind: "rewind",
581
+ forward: "forward",
582
+ speedSettingChange: "speed_setting_change",
583
+ previousEpisode: "previous_episode",
584
+ nextEpisode: "next_episode",
585
+ openSettings: "setting_page_entered",
586
+ closeSettings: "setting_page_exited",
587
+ adPlaybackStarted: "ad_playback_started",
588
+ adPlaybackStopped: "ad_playback_stopped",
589
+ skip: "op_skip"
590
+ };
591
+ const mapLogEvents = ({ video, session = video, version, playerName, getPlaybackStatus = () => video }) => {
592
+ const emitter = mitt();
593
+ const state = {
594
+ status: "init",
595
+ seeking: false,
596
+ playerStartTime: Date.now(),
597
+ moduleStartTime: Date.now(),
598
+ content: session.getContent?.() || {},
599
+ temporaryEvent: null
600
+ };
601
+ const commonProperties = () => ({
602
+ player_name: playerName,
603
+ playback_module_version: version,
604
+ playback_mode: modes[state.content.type],
605
+ playback_session_id: state.sessionId,
606
+ id: state.content.id,
607
+ name: state.content.title,
608
+ ...state.content.type === "videos" && {
609
+ current_position: state.currentTime,
610
+ video_total_duration: state.duration
611
+ },
612
+ ...state.content.type === "lives" && {
613
+ section_id: state.content.section?.id,
614
+ name_2: state.content.channelName,
615
+ live_offset: state.liveOffset || 0
616
+ },
617
+ SSAI: state.ssaiProvider || "None"
618
+ });
619
+ const dispatchStart = () => {
620
+ if (state.status === "started") return;
621
+ state.status = "started";
622
+ state.lastStartTime = video.currentTime;
623
+ state.temporaryEvent = state.isPlayingAd ? "adPlaybackStarted" : "playbackStarted";
624
+ };
625
+ const dispatchStop = () => {
626
+ if (state.status !== "started") return;
627
+ state.status = "stopped";
628
+ const played = video.currentTime - state.lastStartTime;
629
+ if (state.isPlayingAd) state.adPlayedDuration += played;
630
+ else state.playedDuration += played;
631
+ const eventName = state.isPlayingAd ? "adPlaybackStopped" : "playbackStopped";
632
+ const durationPropertyName = state.isPlayingAd ? "ad_played_duration" : "video_played_duration";
633
+ emitter.emit(eventName, {
634
+ ...commonProperties(),
635
+ [durationPropertyName]: played
636
+ });
637
+ };
638
+ const registered = [
639
+ on(video, "error", (event) => {
640
+ emitter.emit("playbackError", {
641
+ module_error_code: event.error?.code || event.error?.data?.code,
642
+ ...commonProperties()
643
+ });
644
+ }),
645
+ once(video, "playerStarted", () => {
646
+ state.playerStartTime = Date.now();
647
+ }),
648
+ on(video, "durationchange", () => {
649
+ if (!state.duration) state.duration = getPlaybackStatus().duration;
650
+ }),
651
+ once(video, "loadeddata", () => {
652
+ state.status = "began";
653
+ state.sessionId = uuidv4_default();
654
+ state.playedDuration = 0;
655
+ emitter.emit("playbackBegan", {
656
+ player_startup_time: (state.playerStartTime - state.moduleStartTime) / 1e3,
657
+ video_startup_time: (Date.now() - state.moduleStartTime) / 1e3,
658
+ ...commonProperties()
659
+ });
660
+ }),
661
+ on(video, "playing", dispatchStart),
662
+ on(video, "waiting", () => {
663
+ if (!state.bufferingStartTime) {
664
+ emitter.emit("bufferingStarted", commonProperties());
665
+ state.bufferingStartTime = Date.now();
666
+ }
667
+ }),
668
+ on(video, "timeupdate", () => {
669
+ const status = getPlaybackStatus();
670
+ state.currentTime = status.currentTime;
671
+ if (state.content.type === "lives") state.liveOffset = status.liveOffset < 10 ? 0 : status.liveOffset;
672
+ if (state.bufferingStartTime) {
673
+ emitter.emit("bufferingEnded", {
674
+ buffering_second: (Date.now() - state.bufferingStartTime) / 1e3,
675
+ ...commonProperties()
676
+ });
677
+ state.bufferingStartTime = void 0;
678
+ }
679
+ if (state.temporaryEvent && !state.seeking) {
680
+ emitter.emit(state.temporaryEvent, commonProperties());
681
+ state.temporaryEvent = null;
682
+ }
683
+ }),
684
+ on(video, "pause", dispatchStop),
685
+ on(video, "seeking", () => {
686
+ state.seeking = true;
687
+ state.seekingFrom = state.currentTime;
688
+ }),
689
+ on(video, "seeked", () => {
690
+ emitter.emit("seeked", {
691
+ seeking_from: state.seekingFrom,
692
+ seeking_to: video.currentTime,
693
+ ...commonProperties()
694
+ });
695
+ state.seeking = false;
696
+ }),
697
+ on(video, "skip", (data) => {
698
+ emitter.emit("skip", {
699
+ skip_from: data.skipFrom,
700
+ skip_to: data.skipTo,
701
+ trigger_method: data.triggerMethod,
702
+ ...commonProperties()
703
+ });
704
+ }),
705
+ on(video, "ratechange", () => {
706
+ if (video.playbackRate === 0) return;
707
+ emitter.emit("playbackSpeedChange", {
708
+ playbackSpeed: video.playbackRate,
709
+ ...commonProperties()
710
+ });
711
+ }),
712
+ on(session, "sectionChange", () => {
713
+ dispatchStop();
714
+ state.content = session.getContent();
715
+ dispatchStart();
716
+ }),
717
+ once(video, "ended", () => {
718
+ if (state.status === "started") dispatchStop();
719
+ state.status = "init";
720
+ emitter.emit("playbackEnded", {
721
+ video_playback_ended_at_percentage: state.currentTime / state.duration,
722
+ video_total_played_duration: state.playedDuration,
723
+ ...state.ssaiProvider && { ad_total_played_duration: state.adPlayedDuration },
724
+ ...commonProperties()
725
+ });
726
+ }),
727
+ once(video, "loadedAdMetadata", (event) => {
728
+ state.ssaiProvider = event.data.provider;
729
+ state.adPlayedDuration = 0;
730
+ }),
731
+ on(session, "adBreakStarted", () => {
732
+ dispatchStop();
733
+ state.isPlayingAd = true;
734
+ if (!state.seeking) dispatchStart();
735
+ }),
736
+ on(session, "adBreakEnded", () => {
737
+ dispatchStop();
738
+ state.isPlayingAd = false;
739
+ if (!state.seeking) dispatchStart();
740
+ })
741
+ ];
742
+ return {
743
+ addEventListener: (name, handler) => emitter.on(name, handler),
744
+ all: (handler) => emitter.on("*", handler),
745
+ emit: (name, { currentTime }, properties) => {
746
+ if (name in logEventNames) emitter.emit(name, {
747
+ current_position: currentTime,
748
+ ...properties,
749
+ ...commonProperties()
750
+ });
751
+ },
752
+ updateContent: (content) => {
753
+ state.content = content;
754
+ },
755
+ reset: () => registered.forEach((off) => off())
756
+ };
757
+ };
758
+
759
+ //#endregion
760
+ //#region src/playbackSession/getCache.js
761
+ const getCache = async (cache, key, { load, isValid }) => {
762
+ if (!cache) return load();
763
+ if (cache[key] && (!isValid || isValid(cache[key]))) return cache[key];
764
+ if (!cache[key]) cache[key] = {};
765
+ cache[key] = {
766
+ ...cache[key],
767
+ ...await load()
768
+ };
769
+ return cache[key];
770
+ };
771
+ var getCache_default = getCache;
772
+
773
+ //#endregion
774
+ //#region src/playbackSession/startSession.js
775
+ const deepEqual = (current, updated) => JSON.stringify(current) === JSON.stringify(updated);
776
+ const HEARTBEAT_INTERVAL_MS = 1e4;
777
+ const UPDATE_INTERVAL_MS = 1e4;
778
+ const isFreshCache = (content) => !content.end && Date.now() <= content.end_time * 1e3;
779
+ let lastSession = Promise.resolve();
780
+ const programRemainTime = (content, { media = {} }) => {
781
+ const programCurrentTime = isLiveDuration(media.duration) ? Date.now() / 1e3 : content.start_time + media.currentTime;
782
+ return content.end_time - programCurrentTime;
783
+ };
784
+ const startPlaybackSession = async (playbackApi, options = {}) => {
785
+ await Promise.race([lastSession, new Promise((resolve) => {
786
+ setTimeout(resolve, UPDATE_INTERVAL_MS / 2);
787
+ })]);
788
+ let unlockSession;
789
+ lastSession = new Promise((resolve) => {
790
+ unlockSession = resolve;
791
+ });
792
+ const emitter = mitt();
793
+ const { type, id, getCurrentTime, cache } = options;
794
+ const cacheKey = options.key || `${type}/${id}`;
795
+ const { onChangeContent, onSessionStart, onReset, heartbeatTime = HEARTBEAT_INTERVAL_MS, updateTime = UPDATE_INTERVAL_MS } = options;
796
+ const state = {
797
+ content: { end_time: Date.now() / 1e3 },
798
+ currentContent: { end_time: Date.now() / 1e3 }
799
+ };
800
+ const updateContent = async ({ useCache } = {}) => {
801
+ const content = await getCache_default(cache, cacheKey, {
802
+ load: () => playbackApi.getContent({
803
+ type,
804
+ id
805
+ }),
806
+ isValid: (data) => useCache && isFreshCache(data)
807
+ });
808
+ if (content.end) emitter.emit("liveEnd", content);
809
+ if (!state.content?.end_time && content?.end_time) return onReset({
810
+ reason: "program start",
811
+ sourceChange: true
812
+ });
813
+ const timeBeforeEnd = programRemainTime(state.currentContent, options) * 1e3;
814
+ if (timeBeforeEnd > 0 && timeBeforeEnd <= UPDATE_INTERVAL_MS) setTimeout(() => {
815
+ if (programRemainTime(state.currentContent, options) > .5) return;
816
+ if (isLiveDuration(options.media.duration)) updateContent({ useCache: true });
817
+ else onReset({
818
+ reason: "program end",
819
+ sourceChange: true
820
+ });
821
+ }, timeBeforeEnd);
822
+ if (!deepEqual(content, state.content)) {
823
+ state.content = content;
824
+ const skipUiUpdate = !useCache;
825
+ if (!skipUiUpdate) state.currentContent = content;
826
+ onChangeContent?.({
827
+ type,
828
+ ...content,
829
+ skipUiUpdate
830
+ });
831
+ }
832
+ };
833
+ const waitForContent = Promise.race([updateContent({ useCache: true }), new Promise((resolve) => {
834
+ setTimeout(resolve, UPDATE_INTERVAL_MS);
835
+ })]);
836
+ const sessionInfo = await playbackApi.startPlayback({
837
+ type,
838
+ id
839
+ }).catch((error) => {
840
+ unlockSession();
841
+ return Promise.reject(error);
842
+ });
843
+ onSessionStart?.(sessionInfo);
844
+ const requestParams = {
845
+ type,
846
+ id,
847
+ token: sessionInfo.token
848
+ };
849
+ state.token = sessionInfo.token;
850
+ state.sources = (await getCache_default(cache, cacheKey, {
851
+ load: () => playbackApi.getPlaybackInfo({
852
+ type,
853
+ id,
854
+ token: state.token
855
+ }),
856
+ isValid: (data) => data.sources && isFreshCache(data)
857
+ })).sources;
858
+ let updateIntervalId;
859
+ if (type === "lives") updateIntervalId = setInterval(updateContent, updateTime);
860
+ let lastPlayedTime;
861
+ const updateLastPlayed = () => {
862
+ const currentTime = getCurrentTime?.();
863
+ if (currentTime >= 0 && lastPlayedTime !== currentTime) {
864
+ lastPlayedTime = currentTime;
865
+ playbackApi.updateLastPlayed({
866
+ ...requestParams,
867
+ time: currentTime
868
+ });
869
+ }
870
+ };
871
+ if (type === "videos") updateIntervalId = setInterval(updateLastPlayed, updateTime);
872
+ const heartbeatIntervalId = setInterval(() => playbackApi.heartbeat(requestParams).catch((error) => {
873
+ if (/4\d\d/.test(error.response?.status)) {
874
+ clearInterval(heartbeatIntervalId);
875
+ onReset?.({
876
+ sourceChange: false,
877
+ reason: "Invalid token"
878
+ });
879
+ }
880
+ }), heartbeatTime);
881
+ const end = ({ preserveSource } = {}) => {
882
+ if (!preserveSource && cache?.[cacheKey]) cache[cacheKey].sources = void 0;
883
+ updateLastPlayed();
884
+ clearInterval(updateIntervalId);
885
+ clearInterval(heartbeatIntervalId);
886
+ clearTimeout(state.endTimeoutId);
887
+ emitter.emit("playbackEnded");
888
+ return playbackApi.endPlayback(requestParams).finally(unlockSession);
889
+ };
890
+ if (state.content?.end) end();
891
+ emitter.on("liveEnd", end);
892
+ await waitForContent;
893
+ return {
894
+ ...state,
895
+ token: sessionInfo.token,
896
+ drmPortalUrl: DEBUG_OPTIONS.DRM_PROTAL_URL || sessionInfo.drm_portal_url,
897
+ updateLastPlayed,
898
+ end,
899
+ replaceApi: (replacement) => {
900
+ playbackApi = replacement;
901
+ }
902
+ };
903
+ };
904
+ var startSession_default = startPlaybackSession;
905
+
906
+ //#endregion
907
+ //#region src/playbackSession/api.js
908
+ const waitMs = (time) => new Promise((resolve) => {
909
+ setTimeout(resolve, time);
910
+ });
911
+ const handleRequestError = (request, { onError, retryTimes = 0 }) => request().catch((error) => onError(error, {
912
+ retry: () => handleRequestError(request, {
913
+ onError,
914
+ retryTimes: retryTimes + 1
915
+ }),
916
+ retryTimes
917
+ }));
918
+ const ignoreMinorError = async (event, { retry, retryTimes } = {}) => {
919
+ console.warn(event);
920
+ if ((event.response?.message === "Network Error" || /502|503/.test(event.response?.status)) && retryTimes < 3) {
921
+ await waitMs(3e3);
922
+ return retry();
923
+ }
924
+ const url = new URL(event.response?.url || "http://unknown/");
925
+ if (/start$|info$|heartbeat$/.test(url.pathname)) return Promise.reject(event);
926
+ if (/end$/.test(url.pathname)) return Promise.resolve();
927
+ console.log("Ignore non-critical playback API fail", event);
928
+ return new Promise(() => {});
929
+ };
930
+ /** @param {Response} response */
931
+ const handleFetchResponse = (response) => {
932
+ if (response.ok) return response.status === 200 ? response.json() : "";
933
+ return response.json().then((errorData) => {
934
+ const error = /* @__PURE__ */ new Error(`HTTP error status: ${response.status} ${errorData.message}`);
935
+ error.data = errorData;
936
+ error.response = response;
937
+ return Promise.reject(error);
938
+ });
939
+ };
940
+ const createApi = (config, { onError = ignoreMinorError } = {}) => {
941
+ const { host, accessToken, deviceId, headers, params } = config;
942
+ const getHeaders = () => ({
943
+ ...accessToken && { Authorization: accessToken },
944
+ ...deviceId && { "X-Device-ID": deviceId },
945
+ "Content-type": "application/json",
946
+ ...headers
947
+ });
948
+ const request = (url, { method } = {}) => handleRequestError(() => fetch(`${url}?${new URLSearchParams(params).toString()}`, {
949
+ method,
950
+ headers: getHeaders()
951
+ }).then(handleFetchResponse), { onError });
952
+ const sessionRequest = (path, { method = "POST", type, id, token }) => handleRequestError(() => fetch(`${host}/sessions/${type}/${id}/playback/${deviceId}/${path}?${new URLSearchParams({
953
+ ...params,
954
+ playback_token: token
955
+ }).toString()}`, {
956
+ method,
957
+ headers: getHeaders()
958
+ }).then(handleFetchResponse), { onError });
959
+ return {
960
+ config,
961
+ getContent: ({ type, id }) => request(`${host}/${type}/${id}`, {}),
962
+ startPlayback: ({ type, id }) => request(`${host}/sessions/${type}/${id}/playback/${deviceId}/start`, { method: "POST" }),
963
+ getPlaybackInfo: ({ type, id, token }) => sessionRequest("info", {
964
+ method: "GET",
965
+ type,
966
+ id,
967
+ token
968
+ }),
969
+ heartbeat: ({ type, id, token }) => sessionRequest("heartbeat", {
970
+ type,
971
+ id,
972
+ token
973
+ }),
974
+ updateLastPlayed: ({ type, id, token, time }) => sessionRequest(`position/${Math.floor(time)}`, {
975
+ type,
976
+ id,
977
+ token
978
+ }),
979
+ endPlayback: ({ type, id, token }) => sessionRequest("end", {
980
+ type,
981
+ id,
982
+ token
983
+ })
984
+ };
985
+ };
986
+ const getStreamInfo = (sources = [], { type = "", licenseUri, certificateUri = `${licenseUri}/fairplay_cert`, licenseHeaders: headers, thumbnailEnabled } = {}) => {
987
+ const activeSource = sources.find((source) => (source.subdub || source.type || "").toLowerCase() === type) || sources[0];
988
+ return (activeSource?.manifests || []).map((manifest) => ({
989
+ ...manifest,
990
+ type: manifest.protocol,
991
+ src: manifest.url,
992
+ drm: {
993
+ fairplay: {
994
+ licenseUri,
995
+ certificateUri,
996
+ headers
997
+ },
998
+ widevine: {
999
+ licenseUri,
1000
+ headers
1001
+ },
1002
+ playready: {
1003
+ licenseUri,
1004
+ headers
1005
+ }
1006
+ },
1007
+ qualityOptions: manifest.resolutions.map(({ height }) => ({
1008
+ label: height,
1009
+ value: height,
1010
+ options: { maxHeight: height }
1011
+ }))
1012
+ })).concat(thumbnailEnabled && activeSource?.thumbnail_seeking_url ? {
1013
+ type: "thumbnail",
1014
+ src: activeSource.thumbnail_seeking_url
1015
+ } : []);
1016
+ };
1017
+ const getOpeningSkipChapters = (data) => {
1018
+ const { opening_begin_time: opBegin, opening_end_time: opEnd } = data.time || {};
1019
+ if (opBegin !== null && opEnd !== null && opEnd - opBegin > 3) return [{
1020
+ type: "opening",
1021
+ startTime: opBegin + .2
1022
+ }, {
1023
+ name: "part a",
1024
+ startTime: opEnd
1025
+ }];
1026
+ return [];
1027
+ };
1028
+ const getContentInfo = (data) => ({
1029
+ title: data.title,
1030
+ channelTitle: data.subtitle,
1031
+ channelIcon: data.channel_info?.image_url,
1032
+ end: data.end,
1033
+ section: {
1034
+ id: data.section_id,
1035
+ start: data.start_time,
1036
+ end: data.end_time
1037
+ },
1038
+ previous: data.prev_video,
1039
+ next: data.next_video,
1040
+ startTime: data.time?.last_position,
1041
+ seekable: data.seekable,
1042
+ chapters: [
1043
+ {
1044
+ type: "intro",
1045
+ startTime: 0
1046
+ },
1047
+ ...getOpeningSkipChapters(data),
1048
+ data.time?.end_start_position && {
1049
+ type: "ending",
1050
+ startTime: data.time.end_start_position
1051
+ }
1052
+ ].filter(Boolean),
1053
+ skipUiUpdate: data.skipUiUpdate
1054
+ });
1055
+
1056
+ //#endregion
1057
+ export { switchTracks as A, setVolume as C, subscribeCastState as D, startCastVideo as E, dispatchSkipEvent as F, getTimelineSegments as I, __commonJSMin as L, togglePlay as M, autoSkip as N, subscribeSessionState as O, dispatchChapterEvents as P, __exportAll as R, setAppId as S, startCastPlaylist as T, loadMedia as _, getCache_default as a, queuePrevious as b, uuidv4_default as c, ensureSenderFramework as d, getLiveTime as f, linkCast as g, initializeSenderState as h, startSession_default as i, toggleMute as j, subscribeVolumeChange as k, connect as l, initSender as m, getContentInfo as n, logEventNames as o, getMediaInfo as p, getStreamInfo as r, mapLogEvents as s, createApi as t, disconnect as u, parseMediaTracks as v, skipAd as w, seek as x, queueNext as y, __toESM as z };