@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.
- 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 +6833 -0
- package/dist/util-B2YBSBjR.mjs +29 -0
- package/package.json +24 -19
- package/dist/core.mjs +0 -3075
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -20943
- package/dist/modules.d.ts +0 -89
- package/dist/plugins.d.ts +0 -5
- package/dist/plugins.mjs +0 -1105
- package/dist/react.d.ts +0 -178
- package/dist/react.mjs +0 -13066
|
@@ -0,0 +1,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 };
|