@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,13 @@
|
|
|
1
|
+
//#region src/util/loadScript.js
|
|
2
|
+
const loadScript = (url) => new Promise((resolve) => {
|
|
3
|
+
const script = Object.assign(document.createElement("script"), {
|
|
4
|
+
async: true,
|
|
5
|
+
src: url
|
|
6
|
+
});
|
|
7
|
+
script.addEventListener("load", resolve);
|
|
8
|
+
document.body.appendChild(script);
|
|
9
|
+
});
|
|
10
|
+
var loadScript_default = loadScript;
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { loadScript_default as t };
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import UAParser from "ua-parser-js";
|
|
2
|
+
|
|
3
|
+
//#region src/util/events.js
|
|
4
|
+
const on = (target, name, handler, ...rest) => {
|
|
5
|
+
target.addEventListener(name, handler, ...rest);
|
|
6
|
+
return () => target.removeEventListener(name, handler, ...rest);
|
|
7
|
+
};
|
|
8
|
+
const once = (target, name, handler) => {
|
|
9
|
+
const oneTime = (...args) => {
|
|
10
|
+
handler(...args);
|
|
11
|
+
target.removeEventListener(name, oneTime);
|
|
12
|
+
};
|
|
13
|
+
target.addEventListener(name, oneTime);
|
|
14
|
+
return () => target.removeEventListener(name, oneTime);
|
|
15
|
+
};
|
|
16
|
+
const waitFor = (check, handler) => {
|
|
17
|
+
const checkInterval = setInterval(() => {
|
|
18
|
+
if (check()) {
|
|
19
|
+
clearInterval(checkInterval);
|
|
20
|
+
handler();
|
|
21
|
+
}
|
|
22
|
+
}, 50);
|
|
23
|
+
return () => clearInterval(checkInterval);
|
|
24
|
+
};
|
|
25
|
+
const dispatchCustomEvent = (element, name, detail) => {
|
|
26
|
+
if (element && typeof element.dispatchEvent === "function") return element.dispatchEvent(new CustomEvent(name, { detail }));
|
|
27
|
+
else console.error("The media element is undefined or does not have the dispatchEvent method");
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/Enum.js
|
|
32
|
+
const LanguageCode = {
|
|
33
|
+
EN: "en",
|
|
34
|
+
JA: "ja",
|
|
35
|
+
ZHTW: "zh-TW"
|
|
36
|
+
};
|
|
37
|
+
const EnvironmentErrorName = {
|
|
38
|
+
NOT_SUPPORT_DEVICE: "KKS.ERROR.DEVICE_IS_NOT_SUPPORTED",
|
|
39
|
+
NOT_SUPPORT_OS: "KKS.ERROR.OS_IS_NOT_SUPPORTED",
|
|
40
|
+
NOT_SUPPORT_OS_VERSION: "KKS.ERROR.PLEASE_UPGRADE_OS",
|
|
41
|
+
NOT_SUPPORT_BROWSER: "KKS.ERROR.BROWSER_IS_NOT_SUPPORTED",
|
|
42
|
+
NOT_SUPPORT_BROWSER_VERSION: "KKS.ERROR.PLEASE_UPGRADE_BROWSER"
|
|
43
|
+
};
|
|
44
|
+
const SeekOrigin = {
|
|
45
|
+
START: "START",
|
|
46
|
+
CURRENT: "CURRENT"
|
|
47
|
+
};
|
|
48
|
+
const CastState = {
|
|
49
|
+
NO_DEVICES_AVAILABLE: "NO_DEVICES_AVAILABLE",
|
|
50
|
+
CONNECTED: "CONNECTED",
|
|
51
|
+
CONNECTING: "CONNECTING",
|
|
52
|
+
NOT_CONNECTED: "NOT_CONNECTED"
|
|
53
|
+
};
|
|
54
|
+
const ItemType = {
|
|
55
|
+
VIDEOS: "videos",
|
|
56
|
+
LIVES: "lives"
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/util/environment.js
|
|
61
|
+
const parser = new UAParser();
|
|
62
|
+
function getOS() {
|
|
63
|
+
return parser.getOS();
|
|
64
|
+
}
|
|
65
|
+
function getDevice() {
|
|
66
|
+
const device = parser.getDevice();
|
|
67
|
+
const osName = getOS().name;
|
|
68
|
+
if (device.type === void 0 && osName === "Android") device.type = "tablet";
|
|
69
|
+
return device;
|
|
70
|
+
}
|
|
71
|
+
function getBrowser() {
|
|
72
|
+
return parser.getBrowser();
|
|
73
|
+
}
|
|
74
|
+
const isSafari = () => /^((?!chrome|android|X11|Linux).)*(safari|iPad|iPhone|Version)/i.test(navigator.userAgent);
|
|
75
|
+
function needNativeHls() {
|
|
76
|
+
return /android|X11|Linux/i.test(navigator.userAgent) || /firefox/i.test(navigator.userAgent) ? "" : isSafari() ? "maybe" : document.createElement("video").canPlayType("application/vnd.apple.mpegURL");
|
|
77
|
+
}
|
|
78
|
+
const isDesktop = () => !getDevice().type;
|
|
79
|
+
const isIOS = () => /iPad|iPhone|iPod/.test(navigator.platform) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
|
|
80
|
+
function compareVersion(v1, v2) {
|
|
81
|
+
if (!/\d+(\.\d+)*/.test(v1)) throw Error(`the version format ${v1} is wrong`);
|
|
82
|
+
if (!/\d+(\.\d+)*/.test(v2)) throw Error(`the version format ${v2} is wrong`);
|
|
83
|
+
const v1parts = v1.split(".").map((p) => Number(p));
|
|
84
|
+
const v2parts = v2.split(".").map((p) => Number(p));
|
|
85
|
+
for (let i = 0, I = Math.max(v1parts.length, v2parts.length); i < I; i++) if (v1parts[i] !== v2parts[i]) return (v1parts[i] || 0) - (v2parts[i] || 0);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
const validateEnvironment = (supportEnvironmentList = []) => {
|
|
89
|
+
if (supportEnvironmentList.length === 0) return;
|
|
90
|
+
const device = getDevice();
|
|
91
|
+
const os = getOS();
|
|
92
|
+
const browser = getBrowser();
|
|
93
|
+
const toUnique = (list) => Array.from(new Set(list));
|
|
94
|
+
const validators = [
|
|
95
|
+
{
|
|
96
|
+
filter: ({ device: { name, type } }) => name === "*" || type === "desktop" && device.type === void 0 || type === device.type,
|
|
97
|
+
errorName: EnvironmentErrorName.NOT_SUPPORT_DEVICE,
|
|
98
|
+
getErrorProps: (list) => ({ allowDevices: toUnique(list.map((env) => env.device.type)) })
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
filter: ({ os: { name } }) => name === "*" || name === os.name,
|
|
102
|
+
errorName: EnvironmentErrorName.NOT_SUPPORT_OS,
|
|
103
|
+
getErrorProps: (list) => ({ allowOSs: toUnique(list.map((env) => env.os.name)) })
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
filter: ({ os: { version } }) => version === "*" || compareVersion(os.version, version) >= 0,
|
|
107
|
+
errorName: EnvironmentErrorName.NOT_SUPPORT_OS_VERSION,
|
|
108
|
+
getErrorProps: (list) => ({ minVersion: list[0].os.version })
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
filter: ({ browser: { name } }) => name === browser.name,
|
|
112
|
+
errorName: EnvironmentErrorName.NOT_SUPPORT_BROWSER,
|
|
113
|
+
getErrorProps: (list) => ({ allowBrowsers: toUnique(list.map((env) => env.browser.name)) })
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
filter: ({ browser: { version } }) => compareVersion(browser.version, version) >= 0,
|
|
117
|
+
errorName: EnvironmentErrorName.NOT_SUPPORT_BROWSER_VERSION,
|
|
118
|
+
getErrorProps: (list) => ({ minVersion: list[0].browser.version })
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
let scopes = supportEnvironmentList;
|
|
122
|
+
for (let i = 0; i < validators.length; i++) {
|
|
123
|
+
const validator = validators[i];
|
|
124
|
+
const newScopes = scopes.filter(validator.filter);
|
|
125
|
+
if (newScopes.length === 0) return {
|
|
126
|
+
name: validator.errorName,
|
|
127
|
+
...validator.getErrorProps(scopes)
|
|
128
|
+
};
|
|
129
|
+
scopes = newScopes;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const havePointer = () => {
|
|
133
|
+
if (havePointer.memo) return havePointer.memo;
|
|
134
|
+
havePointer.memo = typeof window !== "undefined" && ["(hover: hover) and (pointer: fine)", "not all and (any-pointer: coarse)"].every((query) => window.matchMedia(query).matches);
|
|
135
|
+
return havePointer.memo;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/playerCore/source.js
|
|
140
|
+
const protocolExtensions = {
|
|
141
|
+
hls: "m3u8",
|
|
142
|
+
dash: "mpd"
|
|
143
|
+
};
|
|
144
|
+
const mimeTypes = {
|
|
145
|
+
hls: "application/x-mpegurl",
|
|
146
|
+
dash: "application/dash+xml"
|
|
147
|
+
};
|
|
148
|
+
const matchType = (source, manifestType) => source.type?.toLowerCase().includes(manifestType) || source.type?.toLowerCase() === mimeTypes[manifestType] || (source.src || source)?.endsWith?.(protocolExtensions[manifestType]);
|
|
149
|
+
const getDrmOptions$1 = (fallbackDrm) => {
|
|
150
|
+
if (!fallbackDrm?.url) return;
|
|
151
|
+
const drmOptions = {
|
|
152
|
+
licenseUri: fallbackDrm.url,
|
|
153
|
+
headers: fallbackDrm.headers
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
widevine: drmOptions,
|
|
157
|
+
fairplay: {
|
|
158
|
+
...drmOptions,
|
|
159
|
+
certificateUri: `${fallbackDrm.url}/fairplay_cert`,
|
|
160
|
+
...fallbackDrm.fairplay
|
|
161
|
+
},
|
|
162
|
+
playready: drmOptions
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* @typedef {{src: string, type: string}} SourceObject
|
|
167
|
+
* @typedef {{hls: string, dash: string}} SourceObjectAlt backward compatiable form
|
|
168
|
+
*
|
|
169
|
+
* @param {SourceObject[]|SourceObject|SourceObjectAlt|string} sourceOptions
|
|
170
|
+
* @param {{preferManifestType?: ('dash'|'hls'|'platform')}} options
|
|
171
|
+
* @return {{src: string, type: string, drm: Object}}
|
|
172
|
+
*/
|
|
173
|
+
const getSource = (sourceOptions, { preferManifestType, fallbackDrm } = {}) => {
|
|
174
|
+
if (sourceOptions.dash || sourceOptions.hls) {
|
|
175
|
+
const { dash, hls } = sourceOptions;
|
|
176
|
+
return getSource([hls && {
|
|
177
|
+
src: hls,
|
|
178
|
+
type: mimeTypes.hls
|
|
179
|
+
}, dash && {
|
|
180
|
+
src: dash,
|
|
181
|
+
type: mimeTypes.dash
|
|
182
|
+
}].filter(Boolean), {
|
|
183
|
+
preferManifestType,
|
|
184
|
+
fallbackDrm
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (!Array.isArray(sourceOptions)) return getSource([sourceOptions], {
|
|
188
|
+
preferManifestType,
|
|
189
|
+
fallbackDrm
|
|
190
|
+
});
|
|
191
|
+
if (fallbackDrm) return getSource(sourceOptions.map((option) => ({
|
|
192
|
+
...option.src ? option : { src: option },
|
|
193
|
+
drm: getDrmOptions$1(fallbackDrm)
|
|
194
|
+
})), { preferManifestType });
|
|
195
|
+
const targetType = preferManifestType !== "platform" ? preferManifestType : isSafari() ? "hls" : "dash";
|
|
196
|
+
const matched = sourceOptions.find((source) => matchType(source, targetType));
|
|
197
|
+
const selected = matched || sourceOptions[0];
|
|
198
|
+
if (!selected) return;
|
|
199
|
+
const type = matched && preferManifestType === "hls" && mimeTypes.hls;
|
|
200
|
+
return {
|
|
201
|
+
...selected.src ? selected : { src: selected },
|
|
202
|
+
type
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
const getSourceText = (source) => [].concat(source).filter((item) => item.type === "text/vtt");
|
|
206
|
+
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/playerCore/drm.js
|
|
209
|
+
const keySystems = {
|
|
210
|
+
widevine: "com.widevine.alpha",
|
|
211
|
+
fairplay: "com.apple.fps.1_0",
|
|
212
|
+
playready: "com.microsoft.playready"
|
|
213
|
+
};
|
|
214
|
+
const getDrmOptions = (source) => {
|
|
215
|
+
return [source.drm && Object.entries(source.drm).reduce((result, [keySystemId, options]) => {
|
|
216
|
+
const uri = typeof options === "string" ? options : options.licenseUri;
|
|
217
|
+
if (uri) {
|
|
218
|
+
const keySystemName = keySystems[keySystemId] || keySystemId;
|
|
219
|
+
result.servers[keySystemName] = uri;
|
|
220
|
+
const { headers, certificateUri } = options;
|
|
221
|
+
const advanced = [headers && { headers }, certificateUri && { serverCertificateUri: certificateUri }].filter(Boolean);
|
|
222
|
+
if (advanced.length > 0) result.advanced[keySystemName] = Object.assign({}, ...advanced);
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}, {
|
|
226
|
+
servers: {},
|
|
227
|
+
advanced: {}
|
|
228
|
+
}), { drm: source.drm && Object.entries(source.drm).reduce((result, [keySystemId, options]) => {
|
|
229
|
+
const keySystemName = keySystems[keySystemId] || keySystemId;
|
|
230
|
+
if (options.headers || options.certificateHeaders) result[keySystemName] = {
|
|
231
|
+
headers: options.headers,
|
|
232
|
+
...options.certificateHeaders && { certificateHeaders: options.certificateHeaders }
|
|
233
|
+
};
|
|
234
|
+
return result;
|
|
235
|
+
}, {}) }];
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/playerCore/mediaBindings.js
|
|
240
|
+
const LIVE_EDGE_GAP = 10;
|
|
241
|
+
const SHAKA_LIVE_DURATION = 4294967296;
|
|
242
|
+
const isLiveDuration = (duration) => duration >= SHAKA_LIVE_DURATION;
|
|
243
|
+
const isEnded = (media) => !isLiveDuration(media.initialDuration) && media.initialDuration - media.currentTime < 1;
|
|
244
|
+
const isBuffered = (media) => isSafari() || Array.from({ length: media.buffered.length }, (_, index) => ({
|
|
245
|
+
start: media.buffered.start(index),
|
|
246
|
+
end: media.buffered.end(index)
|
|
247
|
+
})).some((range) => range.start <= media.currentTime && media.currentTime <= range.end + 1);
|
|
248
|
+
const getLiveTime = (media, { player }) => {
|
|
249
|
+
const now = Date.now() / 1e3;
|
|
250
|
+
const currentOffset = media.currentTime - media.defaultLiveOffset - now;
|
|
251
|
+
const seekDuration = media.seekDurationDiff + now;
|
|
252
|
+
const { start, end } = player.seekRange();
|
|
253
|
+
return {
|
|
254
|
+
streamType: "live",
|
|
255
|
+
startTime: -seekDuration,
|
|
256
|
+
currentTime: currentOffset < -LIVE_EDGE_GAP ? currentOffset : 0,
|
|
257
|
+
duration: end - start > 5 * LIVE_EDGE_GAP ? seekDuration : 0
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
const getMediaTime = (media, { player, plugins = [] }) => {
|
|
261
|
+
const { duration, ...data } = Object.assign(isLiveDuration(media.initialDuration) ? getLiveTime(media, {
|
|
262
|
+
player,
|
|
263
|
+
plugins
|
|
264
|
+
}) : {
|
|
265
|
+
currentTime: media.currentTime,
|
|
266
|
+
bufferTime: Math.max(...Array.from({ length: media.buffered.length }, (_, index) => media.buffered.end(index))),
|
|
267
|
+
duration: media.initialDuration
|
|
268
|
+
}, ...plugins.map((plugin) => plugin.getPlaybackStatus?.()));
|
|
269
|
+
return {
|
|
270
|
+
...data,
|
|
271
|
+
...(isLiveDuration(media.initialDuration) || Math.abs(media.duration - media.initialDuration) < .5) && { duration }
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
const HAVE_METADATA = 1;
|
|
275
|
+
const getCurrentPlaybackState = (media) => media.autoplay ? "playing" : "paused";
|
|
276
|
+
const subscribePlaybackState = (media, updateState, { iOSFullscreenDetectThreshold = 500 } = {}) => {
|
|
277
|
+
const lastUpdate = {
|
|
278
|
+
state: "",
|
|
279
|
+
time: 0
|
|
280
|
+
};
|
|
281
|
+
const updateIfChanged = (event, state) => {
|
|
282
|
+
if (media.currentTime > 0x9184e72a000) return;
|
|
283
|
+
lastUpdate.time = media.currentTime;
|
|
284
|
+
lastUpdate.eventTime = Date.now();
|
|
285
|
+
lastUpdate.eventType = event.type;
|
|
286
|
+
if (state !== lastUpdate.state) {
|
|
287
|
+
lastUpdate.state = state;
|
|
288
|
+
lastUpdate.batched = media.webkitDisplayingFullscreen;
|
|
289
|
+
if (!lastUpdate.batched) updateState(event, state);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const updateBufferingState = (event) => {
|
|
293
|
+
if (!media.paused && !media.ended) updateIfChanged(event, "buffering");
|
|
294
|
+
};
|
|
295
|
+
const updatePlaybackTime = (event) => {
|
|
296
|
+
if (!media.paused && isBuffered(media) && media.currentTime - lastUpdate.time > .01) updateIfChanged(event, "playing");
|
|
297
|
+
};
|
|
298
|
+
const updateEnd = (event) => {
|
|
299
|
+
if (event.type === "emptied" && lastUpdate.state !== "loading") {
|
|
300
|
+
updateIfChanged(event, "emptied");
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
if (isEnded(media) || event.type === "ended") {
|
|
304
|
+
updateIfChanged(event, "ended");
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const registered = [
|
|
309
|
+
on(media, "error", (event) => updateIfChanged(event, "error")),
|
|
310
|
+
on(media, "waiting", updateBufferingState),
|
|
311
|
+
on(media, "loadsource", (event) => updateIfChanged(event, "loading")),
|
|
312
|
+
on(media, "loadedmetadata", (event) => setTimeout(() => {
|
|
313
|
+
if (media.paused && !(media.readyState > HAVE_METADATA)) updateIfChanged(event, getCurrentPlaybackState(media));
|
|
314
|
+
}, 1500)),
|
|
315
|
+
on(media, "loadeddata", (event) => setTimeout(() => {
|
|
316
|
+
if (media.paused && !event.defaultPrevented) updateIfChanged(event, getCurrentPlaybackState(media));
|
|
317
|
+
}, 1)),
|
|
318
|
+
on(media, "canplay", (event) => {
|
|
319
|
+
if (media.paused && (lastUpdate.state !== "loading" || isIOS())) updateIfChanged(event, getCurrentPlaybackState(media));
|
|
320
|
+
updatePlaybackTime(event);
|
|
321
|
+
}),
|
|
322
|
+
on(media, "pause", (event) => {
|
|
323
|
+
if (Date.now() - lastUpdate.endFullscreenAt < 5 * iOSFullscreenDetectThreshold) {
|
|
324
|
+
lastUpdate.endFullscreenAt = 0;
|
|
325
|
+
media.play();
|
|
326
|
+
console.debug("Ignore pause from iOS exit fullscreen");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!updateEnd(event)) updateIfChanged(event, "paused");
|
|
330
|
+
}),
|
|
331
|
+
on(media, "seeking", updateBufferingState),
|
|
332
|
+
on(media, "seeked", (event) => {
|
|
333
|
+
if (lastUpdate.state === "loading") updateIfChanged(event, getCurrentPlaybackState(media));
|
|
334
|
+
}),
|
|
335
|
+
on(media, "timeupdate", updatePlaybackTime),
|
|
336
|
+
on(media, "ended", updateEnd),
|
|
337
|
+
on(media, "emptied", updateEnd),
|
|
338
|
+
on(media, "webkitendfullscreen", (event) => {
|
|
339
|
+
if (lastUpdate.state === "paused" && Date.now() - lastUpdate.eventTime < iOSFullscreenDetectThreshold) {
|
|
340
|
+
lastUpdate.endFullscreenAt = Date.now();
|
|
341
|
+
waitFor(() => !media.webkitDisplayingFullscreen, () => updateState(event, "playing"));
|
|
342
|
+
} else updateState(event, lastUpdate.state);
|
|
343
|
+
})
|
|
344
|
+
];
|
|
345
|
+
return () => registered.forEach((off) => off());
|
|
346
|
+
};
|
|
347
|
+
const seek = async (media, { player, plugins = [] }, time, issuer) => {
|
|
348
|
+
if (media.readyState < HAVE_METADATA) await new Promise((resolve) => {
|
|
349
|
+
media.addEventListener("loadeddata", resolve, { once: true });
|
|
350
|
+
});
|
|
351
|
+
const seekPlugin = plugins.find((plugin) => typeof plugin.handleSeek === "function" && plugin.isActive());
|
|
352
|
+
const seekInternal = (seekTime) => {
|
|
353
|
+
if (seekTime <= media.duration + 7 && seekTime >= media.duration - 1) return seekInternal(media.duration - 1.1);
|
|
354
|
+
player.shouldPlayFromEdge = false;
|
|
355
|
+
const seekOrigin = player.isLive?.() ? media.defaultLiveOffset + Date.now() / 1e3 : 0;
|
|
356
|
+
player.seek?.(seekTime, issuer);
|
|
357
|
+
if (Math.abs(seekTime) <= LIVE_EDGE_GAP && seekOrigin !== 0) player.goToLive();
|
|
358
|
+
else media.currentTime = seekTime + seekOrigin;
|
|
359
|
+
once(media, "seeked", () => {
|
|
360
|
+
if (Math.abs(seekTime + seekOrigin - media.currentTime) > .5) media.currentTime = seekTime + seekOrigin;
|
|
361
|
+
});
|
|
362
|
+
};
|
|
363
|
+
if (seekPlugin) seekPlugin.handleSeek(time, seekInternal);
|
|
364
|
+
else seekInternal(time);
|
|
365
|
+
};
|
|
366
|
+
const load = async (media, { player, startTime, plugins = [] }, source) => {
|
|
367
|
+
const preferred = getSource(source, { preferManifestType: "platform" });
|
|
368
|
+
if (player.lastSrc === preferred?.src) {
|
|
369
|
+
console.info("src is unchanged, skip load", preferred.src);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
player.lastSrc = preferred?.src;
|
|
373
|
+
media.dispatchEvent(new CustomEvent("loadsource"));
|
|
374
|
+
const merged = await plugins.reduce(async (loadChain, plugin) => {
|
|
375
|
+
const currentSource = await loadChain;
|
|
376
|
+
const overrides = await plugin.load?.(currentSource, {
|
|
377
|
+
video: media,
|
|
378
|
+
player,
|
|
379
|
+
source: currentSource,
|
|
380
|
+
startTime,
|
|
381
|
+
streamFormat: source.type,
|
|
382
|
+
reload: async () => {
|
|
383
|
+
const restoreMuted = player.isMuted && { muted: player.isMuted() };
|
|
384
|
+
player.lastSrc = "";
|
|
385
|
+
await load(media, {
|
|
386
|
+
player,
|
|
387
|
+
startTime,
|
|
388
|
+
plugins
|
|
389
|
+
}, source);
|
|
390
|
+
if (restoreMuted) player[restoreMuted.muted ? "mute" : "unmute"]();
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
return overrides ? {
|
|
394
|
+
...currentSource,
|
|
395
|
+
...overrides.url && { src: overrides.url },
|
|
396
|
+
...overrides.startTime >= 0 && { startTime: overrides.startTime }
|
|
397
|
+
} : currentSource;
|
|
398
|
+
}, {
|
|
399
|
+
...preferred,
|
|
400
|
+
startTime
|
|
401
|
+
});
|
|
402
|
+
media.addEventListener("durationchange", () => {
|
|
403
|
+
media.initialDuration = media.duration;
|
|
404
|
+
}, { once: true });
|
|
405
|
+
once(media, "loadeddata", () => {
|
|
406
|
+
const seekToStart = (delay = 1) => {
|
|
407
|
+
if (merged.startTime > 0 || merged.startTime < 0) setTimeout(() => seek(media, {
|
|
408
|
+
player,
|
|
409
|
+
plugins
|
|
410
|
+
}, merged.startTime), delay);
|
|
411
|
+
};
|
|
412
|
+
if (player.isLive()) {
|
|
413
|
+
player.shouldPlayFromEdge = player.isLive() && !(merged.startTime < 0);
|
|
414
|
+
once(media, "timeupdate", () => {
|
|
415
|
+
player.shouldPlayFromEdge = false;
|
|
416
|
+
const { start, end } = player.seekRange();
|
|
417
|
+
media.defaultLiveOffset = media.currentTime - Date.now() / 1e3;
|
|
418
|
+
media.seekDurationDiff = end - start - Date.now() / 1e3;
|
|
419
|
+
seekToStart();
|
|
420
|
+
});
|
|
421
|
+
} else seekToStart();
|
|
422
|
+
});
|
|
423
|
+
const [drmOptions, extensions] = getDrmOptions(preferred);
|
|
424
|
+
player.configure({ drm: drmOptions });
|
|
425
|
+
player.configureExtensions(extensions);
|
|
426
|
+
let loadStartTime;
|
|
427
|
+
if (merged.type !== "application/x-mpegurl") loadStartTime = merged.startTime;
|
|
428
|
+
return player.unload().then(() => player.load(merged.src, loadStartTime, merged.type)).then((loadResult) => {
|
|
429
|
+
getSourceText(source).forEach(({ src, language = "en", type = "text/vtt", label = language }) => player.addTextTrackAsync(src, language, "subtitles", type, "", label).catch((error) => console.warn("Failed to add text track", error)));
|
|
430
|
+
return loadResult;
|
|
431
|
+
}).catch((error) => {
|
|
432
|
+
media.dispatchEvent(Object.assign(new CustomEvent("error"), { error }));
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
const waitMediaReady = (media) => media.readyState >= HAVE_METADATA || new Promise((resolve) => {
|
|
436
|
+
media.addEventListener("loadedmetadata", resolve, { once: true });
|
|
437
|
+
});
|
|
438
|
+
const toggleMute = (media) => {
|
|
439
|
+
media.muted = !media.muted;
|
|
440
|
+
if (!media.muted) media.volume = Math.max(media.volume, .05);
|
|
441
|
+
};
|
|
442
|
+
const setVolume = (media, { player }, level) => {
|
|
443
|
+
const capped = Math.max(0, Math.min(level, 1));
|
|
444
|
+
media.muted = capped <= 0;
|
|
445
|
+
if (capped > 0) {
|
|
446
|
+
player?.setVolume?.(capped * 100);
|
|
447
|
+
media.volume = capped;
|
|
448
|
+
}
|
|
449
|
+
if (capped <= 0) player?.mute?.();
|
|
450
|
+
};
|
|
451
|
+
const syncPlaybackState = async (media, { player, liveResume }, target) => {
|
|
452
|
+
if (media.webkitDisplayingFullscreen || media.paused === (target === "paused")) return;
|
|
453
|
+
await player?.pendingOperation?.catch(() => 1);
|
|
454
|
+
if (target === "paused") {
|
|
455
|
+
media.autoplay = false;
|
|
456
|
+
if (player) player.pendingOperation = new Promise((resolve) => {
|
|
457
|
+
once(media, "pause", resolve);
|
|
458
|
+
setTimeout(resolve, 1);
|
|
459
|
+
});
|
|
460
|
+
return media.pause();
|
|
461
|
+
}
|
|
462
|
+
await waitMediaReady(media);
|
|
463
|
+
if (isEnded(media)) seek(media, { player }, 0);
|
|
464
|
+
if (media.paused) {
|
|
465
|
+
media.autoplay = true;
|
|
466
|
+
const { streamType, duration, currentTime } = getMediaTime(media, { player });
|
|
467
|
+
if (streamType === "live" && (liveResume || player.shouldPlayFromEdge || duration === 0) && currentTime < 0) seek(media, { player }, 0);
|
|
468
|
+
player.play?.().catch((error) => console.warn(error));
|
|
469
|
+
player.pendingOperation = media.play().catch((error) => {
|
|
470
|
+
media.autoplay = false;
|
|
471
|
+
media.dispatchEvent(new CustomEvent("pause"));
|
|
472
|
+
console.log("autoplay blocked", error);
|
|
473
|
+
return Promise.reject(error);
|
|
474
|
+
});
|
|
475
|
+
return player.pendingOperation;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const setPlaybackRate = (media, { player }, rate) => {
|
|
479
|
+
if (!rate) return;
|
|
480
|
+
player?.setPlaybackSpeed?.(rate);
|
|
481
|
+
media.playbackRate = rate;
|
|
482
|
+
};
|
|
483
|
+
const getTextTracks = (_, { player }) => {
|
|
484
|
+
if (!player) return [];
|
|
485
|
+
return (player.getTextTracks?.() || []).map((track) => {
|
|
486
|
+
const label = track.label?.trim() || track.language;
|
|
487
|
+
const id = `${track.language}:${label}`;
|
|
488
|
+
return {
|
|
489
|
+
...track,
|
|
490
|
+
label,
|
|
491
|
+
type: "subtitles",
|
|
492
|
+
selected: player.isTextTrackVisible() ? track.active : false,
|
|
493
|
+
value: {
|
|
494
|
+
id,
|
|
495
|
+
label,
|
|
496
|
+
language: track.language
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}).filter((track) => track.label !== "unknown" && track.language && !/^un/i.test(track.language));
|
|
500
|
+
};
|
|
501
|
+
const textTrackOptionOff = {
|
|
502
|
+
label: "none",
|
|
503
|
+
language: "none"
|
|
504
|
+
};
|
|
505
|
+
const textTrackLabel = "playcraft-text-track";
|
|
506
|
+
/** @param media HTMLMediaElement */
|
|
507
|
+
const syncTextTrack = (media, { player }, selected) => {
|
|
508
|
+
if (!media) return;
|
|
509
|
+
const { label, language, textTracks = [] } = selected === textTrackOptionOff.label ? textTrackOptionOff : selected;
|
|
510
|
+
const cues = textTracks.find((track) => track.mode === "showing" && track.cues?.length > 0)?.cues;
|
|
511
|
+
let customTextTrack = Array.from(media.textTracks).find((track) => track.label === textTrackLabel);
|
|
512
|
+
Array.from(customTextTrack?.cues || []).forEach((cue) => customTextTrack.removeCue(cue));
|
|
513
|
+
player.setTextTrackVisibility(true);
|
|
514
|
+
if (cues) {
|
|
515
|
+
player?.setTextTrackVisibility(false);
|
|
516
|
+
if (!customTextTrack) customTextTrack = media.addTextTrack("subtitles", textTrackLabel);
|
|
517
|
+
Array.from(cues).forEach((cue) => customTextTrack.addCue(new VTTCue(cue.startTime, cue.endTime, cue.text)));
|
|
518
|
+
customTextTrack.mode = "showing";
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (customTextTrack) customTextTrack.mode = "hidden";
|
|
522
|
+
const matchedTrack = getTextTracks(media, { player }).find((t) => (!language || t.language === language) && (!label || t.label === label));
|
|
523
|
+
if (matchedTrack) {
|
|
524
|
+
player?.selectTextTrack(matchedTrack);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
player.setTextTrackVisibility(false);
|
|
528
|
+
if (!/off|none/i.test(label + language)) console.warn(`turn off text track: ${label} ${language} is not found`);
|
|
529
|
+
return media.textTracks?.addEventListener && once(media.textTracks, "change", () => player.setTextTrackVisibility(false));
|
|
530
|
+
};
|
|
531
|
+
const setAudioTrack = (_, { player }, next) => {
|
|
532
|
+
if (!next || !player) return;
|
|
533
|
+
try {
|
|
534
|
+
player.selectAudioLanguage(next.language);
|
|
535
|
+
if (next.label) player.selectVariantsByLabel(next.label);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
console.warn("Unable to set audio", error, next);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
//#endregion
|
|
542
|
+
export { once as A, validateEnvironment as C, SeekOrigin as D, LanguageCode as E, dispatchCustomEvent as O, needNativeHls as S, ItemType as T, getOS as _, load as a, isIOS as b, setPlaybackRate as c, syncPlaybackState as d, syncTextTrack as f, getBrowser as g, mimeTypes as h, isLiveDuration as i, on as k, setVolume as l, getSource as m, getTextTracks as n, seek as o, toggleMute as p, isBuffered as r, setAudioTrack as s, getMediaTime as t, subscribePlaybackState as u, havePointer as v, CastState as w, isSafari as x, isDesktop as y };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getVersion } from "util/index";
|
|
2
|
+
import { dispatchChapterEvents } from "premium/timeline";
|
|
3
|
+
import latencyManager from "playerCore/latencyManager";
|
|
4
|
+
import { createApi, getContentInfo, getStreamInfo } from "playbackSession/api";
|
|
5
|
+
import startSession from "playbackSession/startSession";
|
|
6
|
+
import { logEventNames, mapLogEvents } from "playerCore/playlogv2";
|
|
7
|
+
import * as playlogv3 from "playerCore/playlogv3";
|
|
8
|
+
import { selectHlsQualities } from "playerCore/adaptation";
|
|
9
|
+
import fixDashManifest from "plugins/shaka/fixDashManifest";
|
|
10
|
+
import ensureTabLock from "util/ensureTabLock";
|
|
11
|
+
import { validateEnvironment } from "util/environment";
|
|
12
|
+
import addSentry from "util/addSentry";
|
|
13
|
+
import handleIOSHeadphonesDisconnection from "util/handleIOSHeadphonesDisconnection";
|
|
14
|
+
import { disconnect, initSender, linkCast, loadMedia, subscribeCastState } from "cast/framework";
|
|
15
|
+
|
|
16
|
+
//#region src/modules/retrieveModuleConfig.d.ts
|
|
17
|
+
type ModulesConfig = {
|
|
18
|
+
[key: string]: string;
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/modules/analytics.d.ts
|
|
22
|
+
type createAnalyticsParameter = {
|
|
23
|
+
video: HTMLVideoElement;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
onPlaylogFired: any;
|
|
26
|
+
modulesConfig: ModulesConfig;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* This method creates analytics module instance and return it.
|
|
30
|
+
* @param createAnalyticsParameter
|
|
31
|
+
* @returns Analytics module instance
|
|
32
|
+
*/
|
|
33
|
+
declare const createAnalytics: ({
|
|
34
|
+
video,
|
|
35
|
+
onPlaylogFired,
|
|
36
|
+
modulesConfig
|
|
37
|
+
}: createAnalyticsParameter) => {
|
|
38
|
+
sendEvent: (name: any, {
|
|
39
|
+
currentTime
|
|
40
|
+
}: {
|
|
41
|
+
currentTime?: string | undefined;
|
|
42
|
+
}, properties: any) => void;
|
|
43
|
+
sendLog: (name: any, {
|
|
44
|
+
currentTime
|
|
45
|
+
}: {
|
|
46
|
+
currentTime?: string | undefined;
|
|
47
|
+
}, properties: any) => void;
|
|
48
|
+
reset: () => void;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { addSentry, loadMedia as castMedia, createAnalytics, createApi, dispatchChapterEvents, ensureTabLock, fixDashManifest, getContentInfo, getStreamInfo, getVersion, handleIOSHeadphonesDisconnection, initSender, latencyManager, linkCast, logEventNames, mapLogEvents, playlogv3, selectHlsQualities, startSession, disconnect as stopCast, subscribeCastState, validateEnvironment };
|