@kkcompany/player 2.25.0-canary.23 → 2.25.0-canary.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -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 -10148
- package/dist/loadPlayer-CQdGA3Te.mjs +1560 -0
- package/dist/loadScript-Ct19kU9g.mjs +13 -0
- package/dist/mediaBindings-CoY60lQw.mjs +542 -0
- package/dist/modules.d.mts +51 -0
- package/dist/modules.mjs +631 -2201
- package/dist/playerCore/index.d.mts +3 -0
- package/dist/playerCore/index.mjs +4 -0
- package/dist/plugins/index.d.mts +2 -0
- package/dist/plugins/index.mjs +3 -0
- package/dist/reactEntry.d.mts +20 -0
- package/dist/reactEntry.mjs +6339 -0
- package/dist/util-B2YBSBjR.mjs +29 -0
- package/package.json +24 -19
- package/dist/core.mjs +0 -3075
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -20938
- 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 -13061
package/dist/modules.mjs
CHANGED
|
@@ -1,2219 +1,649 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { D as subscribeCastState, P as dispatchChapterEvents, R as __exportAll, _ as loadMedia, c as uuidv4_default, g as linkCast, i as startSession_default, m as initSender, n as getContentInfo, o as logEventNames, r as getStreamInfo, s as mapLogEvents, t as createApi, u as disconnect } from "./api-2BOrEA5d.mjs";
|
|
2
|
+
import { r as getVersion } from "./util-B2YBSBjR.mjs";
|
|
3
|
+
import { A as once, C as validateEnvironment, S as needNativeHls, _ as getOS, g as getBrowser, i as isLiveDuration, k as on, y as isDesktop } from "./mediaBindings-CoY60lQw.mjs";
|
|
4
|
+
import { t as fixDashManifest_default } from "./fixDashManifest-CJ63KKaA.mjs";
|
|
5
|
+
import { t as selectHlsQualities } from "./adaptation-BcTsh-wx.mjs";
|
|
6
|
+
import mitt from "mitt";
|
|
7
|
+
|
|
8
|
+
//#region src/playerCore/playlogv3.js
|
|
9
|
+
var playlogv3_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
logEventNames: () => logEventNames$1,
|
|
11
|
+
mapLogEvents: () => mapLogEvents$1
|
|
7
12
|
});
|
|
8
|
-
|
|
9
|
-
const handleRequestError = (request, {
|
|
10
|
-
onError,
|
|
11
|
-
retryTimes = 0
|
|
12
|
-
}) => request().catch(error => onError(error, {
|
|
13
|
-
retry: () => handleRequestError(request, {
|
|
14
|
-
onError,
|
|
15
|
-
retryTimes: retryTimes + 1
|
|
16
|
-
}),
|
|
17
|
-
retryTimes
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
const ignoreMinorError = async (event, {
|
|
21
|
-
retry,
|
|
22
|
-
retryTimes
|
|
23
|
-
} = {}) => {
|
|
24
|
-
var _event$response, _event$response2, _event$response3;
|
|
25
|
-
|
|
26
|
-
console.warn(event);
|
|
27
|
-
|
|
28
|
-
if ((((_event$response = event.response) === null || _event$response === void 0 ? void 0 : _event$response.message) === 'Network Error' || /502|503/.test((_event$response2 = event.response) === null || _event$response2 === void 0 ? void 0 : _event$response2.status)) && retryTimes < 3) {
|
|
29
|
-
await waitMs$1(3000);
|
|
30
|
-
return retry();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const url = new URL(((_event$response3 = event.response) === null || _event$response3 === void 0 ? void 0 : _event$response3.url) || 'http://unknown/');
|
|
34
|
-
|
|
35
|
-
if (/start$|info$|heartbeat$/.test(url.pathname)) {
|
|
36
|
-
return Promise.reject(event);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (/end$/.test(url.pathname)) {
|
|
40
|
-
return Promise.resolve();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
console.log('Ignore non-critical playback API fail', event);
|
|
44
|
-
return new Promise(() => {});
|
|
45
|
-
};
|
|
46
|
-
/** @param {Response} response */
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const handleFetchResponse = response => {
|
|
50
|
-
if (response.ok) {
|
|
51
|
-
return response.status === 200 ? response.json() : '';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return response.json().then(errorData => {
|
|
55
|
-
const error = new Error(`HTTP error status: ${response.status} ${errorData.message}`);
|
|
56
|
-
error.data = errorData;
|
|
57
|
-
error.response = response;
|
|
58
|
-
return Promise.reject(error);
|
|
59
|
-
});
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const createApi = (config, {
|
|
63
|
-
onError = ignoreMinorError
|
|
64
|
-
} = {}) => {
|
|
65
|
-
const {
|
|
66
|
-
host,
|
|
67
|
-
accessToken,
|
|
68
|
-
deviceId,
|
|
69
|
-
headers,
|
|
70
|
-
params
|
|
71
|
-
} = config;
|
|
72
|
-
|
|
73
|
-
const getHeaders = () => ({ ...(accessToken && {
|
|
74
|
-
Authorization: accessToken
|
|
75
|
-
}),
|
|
76
|
-
...(deviceId && {
|
|
77
|
-
'X-Device-ID': deviceId
|
|
78
|
-
}),
|
|
79
|
-
'Content-type': 'application/json',
|
|
80
|
-
...headers
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const request = (url, {
|
|
84
|
-
method
|
|
85
|
-
} = {}) => handleRequestError(() => fetch(`${url}?${new URLSearchParams(params).toString()}`, {
|
|
86
|
-
method,
|
|
87
|
-
headers: getHeaders()
|
|
88
|
-
}).then(handleFetchResponse), {
|
|
89
|
-
onError
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const sessionRequest = (path, {
|
|
93
|
-
method = 'POST',
|
|
94
|
-
type,
|
|
95
|
-
id,
|
|
96
|
-
token
|
|
97
|
-
}) => handleRequestError(() => fetch(`${host}/sessions/${type}/${id}/playback/${deviceId}/${path}?${new URLSearchParams({ ...params,
|
|
98
|
-
playback_token: token
|
|
99
|
-
}).toString()}`, {
|
|
100
|
-
method,
|
|
101
|
-
headers: getHeaders()
|
|
102
|
-
}).then(handleFetchResponse), {
|
|
103
|
-
onError
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
config,
|
|
108
|
-
getContent: ({
|
|
109
|
-
type,
|
|
110
|
-
id
|
|
111
|
-
}) => request(`${host}/${type}/${id}`, {}),
|
|
112
|
-
startPlayback: ({
|
|
113
|
-
type,
|
|
114
|
-
id
|
|
115
|
-
}) => request(`${host}/sessions/${type}/${id}/playback/${deviceId}/start`, {
|
|
116
|
-
method: 'POST'
|
|
117
|
-
}),
|
|
118
|
-
getPlaybackInfo: ({
|
|
119
|
-
type,
|
|
120
|
-
id,
|
|
121
|
-
token
|
|
122
|
-
}) => sessionRequest('info', {
|
|
123
|
-
method: 'GET',
|
|
124
|
-
type,
|
|
125
|
-
id,
|
|
126
|
-
token
|
|
127
|
-
}),
|
|
128
|
-
heartbeat: ({
|
|
129
|
-
type,
|
|
130
|
-
id,
|
|
131
|
-
token
|
|
132
|
-
}) => sessionRequest('heartbeat', {
|
|
133
|
-
type,
|
|
134
|
-
id,
|
|
135
|
-
token
|
|
136
|
-
}),
|
|
137
|
-
updateLastPlayed: ({
|
|
138
|
-
type,
|
|
139
|
-
id,
|
|
140
|
-
token,
|
|
141
|
-
time
|
|
142
|
-
}) => sessionRequest(`position/${Math.floor(time)}`, {
|
|
143
|
-
type,
|
|
144
|
-
id,
|
|
145
|
-
token
|
|
146
|
-
}),
|
|
147
|
-
endPlayback: ({
|
|
148
|
-
type,
|
|
149
|
-
id,
|
|
150
|
-
token
|
|
151
|
-
}) => sessionRequest('end', {
|
|
152
|
-
type,
|
|
153
|
-
id,
|
|
154
|
-
token
|
|
155
|
-
})
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const getStreamInfo = (sources = [], {
|
|
160
|
-
type = '',
|
|
161
|
-
licenseUri,
|
|
162
|
-
certificateUri = `${licenseUri}/fairplay_cert`,
|
|
163
|
-
licenseHeaders: headers,
|
|
164
|
-
thumbnailEnabled
|
|
165
|
-
} = {}) => {
|
|
166
|
-
const activeSource = sources.find(source => (source.subdub || source.type || '').toLowerCase() === type) || sources[0];
|
|
167
|
-
return ((activeSource === null || activeSource === void 0 ? void 0 : activeSource.manifests) || []).map(manifest => ({ ...manifest,
|
|
168
|
-
type: manifest.protocol,
|
|
169
|
-
src: manifest.url,
|
|
170
|
-
drm: {
|
|
171
|
-
fairplay: {
|
|
172
|
-
licenseUri,
|
|
173
|
-
certificateUri,
|
|
174
|
-
headers
|
|
175
|
-
},
|
|
176
|
-
widevine: {
|
|
177
|
-
licenseUri,
|
|
178
|
-
headers
|
|
179
|
-
},
|
|
180
|
-
playready: {
|
|
181
|
-
licenseUri,
|
|
182
|
-
headers
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
qualityOptions: manifest.resolutions.map(({
|
|
186
|
-
height
|
|
187
|
-
}) => ({
|
|
188
|
-
label: height,
|
|
189
|
-
value: height,
|
|
190
|
-
options: {
|
|
191
|
-
maxHeight: height
|
|
192
|
-
}
|
|
193
|
-
}))
|
|
194
|
-
})).concat(thumbnailEnabled && activeSource !== null && activeSource !== void 0 && activeSource.thumbnail_seeking_url ? {
|
|
195
|
-
type: 'thumbnail',
|
|
196
|
-
src: activeSource.thumbnail_seeking_url
|
|
197
|
-
} : []);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const getOpeningSkipChapters = data => {
|
|
201
|
-
const {
|
|
202
|
-
opening_begin_time: opBegin,
|
|
203
|
-
opening_end_time: opEnd
|
|
204
|
-
} = data.time || {};
|
|
205
|
-
|
|
206
|
-
if (opBegin !== null && opEnd !== null && opEnd - opBegin > 3) {
|
|
207
|
-
const res = [{
|
|
208
|
-
type: 'opening',
|
|
209
|
-
startTime: opBegin + 0.2
|
|
210
|
-
}, {
|
|
211
|
-
name: 'part a',
|
|
212
|
-
startTime: opEnd
|
|
213
|
-
}];
|
|
214
|
-
return res;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return [];
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const getContentInfo = data => {
|
|
221
|
-
var _data$channel_info, _data$time, _data$time2;
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
title: data.title,
|
|
225
|
-
channelTitle: data.subtitle,
|
|
226
|
-
channelIcon: (_data$channel_info = data.channel_info) === null || _data$channel_info === void 0 ? void 0 : _data$channel_info.image_url,
|
|
227
|
-
end: data.end,
|
|
228
|
-
section: {
|
|
229
|
-
id: data.section_id,
|
|
230
|
-
start: data.start_time,
|
|
231
|
-
end: data.end_time
|
|
232
|
-
},
|
|
233
|
-
previous: data.prev_video,
|
|
234
|
-
next: data.next_video,
|
|
235
|
-
startTime: (_data$time = data.time) === null || _data$time === void 0 ? void 0 : _data$time.last_position,
|
|
236
|
-
seekable: data.seekable,
|
|
237
|
-
chapters: [{
|
|
238
|
-
type: 'intro',
|
|
239
|
-
startTime: 0
|
|
240
|
-
}, ...getOpeningSkipChapters(data), ((_data$time2 = data.time) === null || _data$time2 === void 0 ? void 0 : _data$time2.end_start_position) && {
|
|
241
|
-
type: 'ending',
|
|
242
|
-
startTime: data.time.end_start_position
|
|
243
|
-
}].filter(Boolean),
|
|
244
|
-
skipUiUpdate: data.skipUiUpdate
|
|
245
|
-
};
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const on = (target, name, handler, ...rest) => {
|
|
249
|
-
target.addEventListener(name, handler, ...rest);
|
|
250
|
-
return () => target.removeEventListener(name, handler, ...rest);
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
const once = (target, name, handler) => {
|
|
254
|
-
const oneTime = (...args) => {
|
|
255
|
-
handler(...args);
|
|
256
|
-
target.removeEventListener(name, oneTime);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
target.addEventListener(name, oneTime);
|
|
260
|
-
return () => target.removeEventListener(name, oneTime);
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const EnvironmentErrorName = {
|
|
264
|
-
NOT_SUPPORT_DEVICE: 'KKS.ERROR.DEVICE_IS_NOT_SUPPORTED',
|
|
265
|
-
NOT_SUPPORT_OS: 'KKS.ERROR.OS_IS_NOT_SUPPORTED',
|
|
266
|
-
NOT_SUPPORT_OS_VERSION: 'KKS.ERROR.PLEASE_UPGRADE_OS',
|
|
267
|
-
NOT_SUPPORT_BROWSER: 'KKS.ERROR.BROWSER_IS_NOT_SUPPORTED',
|
|
268
|
-
NOT_SUPPORT_BROWSER_VERSION: 'KKS.ERROR.PLEASE_UPGRADE_BROWSER'
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
/* eslint-disable no-plusplus */
|
|
272
|
-
const parser = new UAParser();
|
|
273
|
-
function getOS() {
|
|
274
|
-
return parser.getOS();
|
|
275
|
-
}
|
|
276
|
-
function getDevice() {
|
|
277
|
-
const device = parser.getDevice();
|
|
278
|
-
const osName = getOS().name;
|
|
279
|
-
if (device.type === undefined && osName === 'Android') device.type = 'tablet';
|
|
280
|
-
return device;
|
|
281
|
-
}
|
|
282
|
-
function getBrowser() {
|
|
283
|
-
return parser.getBrowser();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const isSafari = () => /^((?!chrome|android|X11|Linux).)*(safari|iPad|iPhone|Version)/i.test(navigator.userAgent);
|
|
287
|
-
|
|
288
|
-
function needNativeHls() {
|
|
289
|
-
// Don't let Android phones play HLS, even if some of them report supported
|
|
290
|
-
// This covers Samsung & OPPO special cases
|
|
291
|
-
const isAndroid = /android|X11|Linux/i.test(navigator.userAgent);
|
|
292
|
-
return isAndroid || /firefox/i.test(navigator.userAgent) ? '' : // canPlayType isn't reliable across all iOS verion / device combinations, so also check user agent
|
|
293
|
-
isSafari() ? 'maybe' : document.createElement('video').canPlayType('application/vnd.apple.mpegURL');
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const isDesktop = () => !getDevice().type; // TODO solve lint error:
|
|
297
|
-
|
|
298
|
-
function compareVersion(v1, v2) {
|
|
299
|
-
if (!/\d+(\.\d+)*/.test(v1)) throw Error(`the version format ${v1} is wrong`);
|
|
300
|
-
if (!/\d+(\.\d+)*/.test(v2)) throw Error(`the version format ${v2} is wrong`);
|
|
301
|
-
const v1parts = v1.split('.').map(p => Number(p));
|
|
302
|
-
const v2parts = v2.split('.').map(p => Number(p));
|
|
303
|
-
|
|
304
|
-
for (let i = 0, I = Math.max(v1parts.length, v2parts.length); i < I; i++) {
|
|
305
|
-
if (v1parts[i] !== v2parts[i]) {
|
|
306
|
-
return (v1parts[i] || 0) - (v2parts[i] || 0);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return 0;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const validateEnvironment = (supportEnvironmentList = []) => {
|
|
314
|
-
if (supportEnvironmentList.length === 0) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const device = getDevice();
|
|
319
|
-
const os = getOS();
|
|
320
|
-
const browser = getBrowser();
|
|
321
|
-
|
|
322
|
-
const toUnique = list => Array.from(new Set(list));
|
|
323
|
-
|
|
324
|
-
const validators = [{
|
|
325
|
-
filter: ({
|
|
326
|
-
device: {
|
|
327
|
-
name,
|
|
328
|
-
type
|
|
329
|
-
}
|
|
330
|
-
}) => name === '*' || type === 'desktop' && device.type === undefined || type === device.type,
|
|
331
|
-
errorName: EnvironmentErrorName.NOT_SUPPORT_DEVICE,
|
|
332
|
-
getErrorProps: list => ({
|
|
333
|
-
allowDevices: toUnique(list.map(env => env.device.type))
|
|
334
|
-
})
|
|
335
|
-
}, {
|
|
336
|
-
filter: ({
|
|
337
|
-
os: {
|
|
338
|
-
name
|
|
339
|
-
}
|
|
340
|
-
}) => name === '*' || name === os.name,
|
|
341
|
-
errorName: EnvironmentErrorName.NOT_SUPPORT_OS,
|
|
342
|
-
getErrorProps: list => ({
|
|
343
|
-
allowOSs: toUnique(list.map(env => env.os.name))
|
|
344
|
-
})
|
|
345
|
-
}, {
|
|
346
|
-
filter: ({
|
|
347
|
-
os: {
|
|
348
|
-
version
|
|
349
|
-
}
|
|
350
|
-
}) => version === '*' || compareVersion(os.version, version) >= 0,
|
|
351
|
-
errorName: EnvironmentErrorName.NOT_SUPPORT_OS_VERSION,
|
|
352
|
-
getErrorProps: list => ({
|
|
353
|
-
minVersion: list[0].os.version
|
|
354
|
-
})
|
|
355
|
-
}, {
|
|
356
|
-
filter: ({
|
|
357
|
-
browser: {
|
|
358
|
-
name
|
|
359
|
-
}
|
|
360
|
-
}) => name === browser.name,
|
|
361
|
-
errorName: EnvironmentErrorName.NOT_SUPPORT_BROWSER,
|
|
362
|
-
getErrorProps: list => ({
|
|
363
|
-
allowBrowsers: toUnique(list.map(env => env.browser.name))
|
|
364
|
-
})
|
|
365
|
-
}, {
|
|
366
|
-
filter: ({
|
|
367
|
-
browser: {
|
|
368
|
-
version
|
|
369
|
-
}
|
|
370
|
-
}) => compareVersion(browser.version, version) >= 0,
|
|
371
|
-
errorName: EnvironmentErrorName.NOT_SUPPORT_BROWSER_VERSION,
|
|
372
|
-
getErrorProps: list => ({
|
|
373
|
-
minVersion: list[0].browser.version
|
|
374
|
-
})
|
|
375
|
-
}];
|
|
376
|
-
let scopes = supportEnvironmentList;
|
|
377
|
-
|
|
378
|
-
for (let i = 0; i < validators.length; i++) {
|
|
379
|
-
const validator = validators[i];
|
|
380
|
-
const newScopes = scopes.filter(validator.filter);
|
|
381
|
-
|
|
382
|
-
if (newScopes.length === 0) {
|
|
383
|
-
return {
|
|
384
|
-
name: validator.errorName,
|
|
385
|
-
...validator.getErrorProps(scopes)
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
scopes = newScopes;
|
|
390
|
-
}
|
|
391
|
-
}; // Some touch devices with a mouse can't be distinguished, assume no mouse
|
|
392
|
-
|
|
393
|
-
const protocolExtensions = {
|
|
394
|
-
hls: 'm3u8',
|
|
395
|
-
dash: 'mpd'
|
|
396
|
-
};
|
|
397
|
-
const mimeTypes = {
|
|
398
|
-
hls: 'application/x-mpegurl',
|
|
399
|
-
dash: 'application/dash+xml'
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
const matchType = (source, manifestType) => {
|
|
403
|
-
var _source$type, _source$type2, _ref, _ref$endsWith;
|
|
404
|
-
|
|
405
|
-
return ((_source$type = source.type) === null || _source$type === void 0 ? void 0 : _source$type.toLowerCase().includes(manifestType)) || ((_source$type2 = source.type) === null || _source$type2 === void 0 ? void 0 : _source$type2.toLowerCase()) === mimeTypes[manifestType] || ((_ref = source.src || source) === null || _ref === void 0 ? void 0 : (_ref$endsWith = _ref.endsWith) === null || _ref$endsWith === void 0 ? void 0 : _ref$endsWith.call(_ref, protocolExtensions[manifestType]));
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
const getDrmOptions = fallbackDrm => {
|
|
409
|
-
if (!(fallbackDrm !== null && fallbackDrm !== void 0 && fallbackDrm.url)) {
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const drmOptions = {
|
|
414
|
-
licenseUri: fallbackDrm.url,
|
|
415
|
-
headers: fallbackDrm.headers
|
|
416
|
-
};
|
|
417
|
-
return {
|
|
418
|
-
widevine: drmOptions,
|
|
419
|
-
fairplay: { ...drmOptions,
|
|
420
|
-
certificateUri: `${fallbackDrm.url}/fairplay_cert`,
|
|
421
|
-
...fallbackDrm.fairplay
|
|
422
|
-
},
|
|
423
|
-
playready: drmOptions
|
|
424
|
-
};
|
|
425
|
-
};
|
|
426
|
-
/**
|
|
427
|
-
* @typedef {{src: string, type: string}} SourceObject
|
|
428
|
-
* @typedef {{hls: string, dash: string}} SourceObjectAlt backward compatiable form
|
|
429
|
-
*
|
|
430
|
-
* @param {SourceObject[]|SourceObject|SourceObjectAlt|string} sourceOptions
|
|
431
|
-
* @param {{preferManifestType?: ('dash'|'hls'|'platform')}} options
|
|
432
|
-
* @return {{src: string, type: string, drm: Object}}
|
|
433
|
-
*/
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const getSource = (sourceOptions, {
|
|
437
|
-
preferManifestType,
|
|
438
|
-
fallbackDrm
|
|
439
|
-
} = {}) => {
|
|
440
|
-
if (sourceOptions.dash || sourceOptions.hls) {
|
|
441
|
-
const {
|
|
442
|
-
dash,
|
|
443
|
-
hls
|
|
444
|
-
} = sourceOptions;
|
|
445
|
-
return getSource([hls && {
|
|
446
|
-
src: hls,
|
|
447
|
-
type: mimeTypes.hls
|
|
448
|
-
}, dash && {
|
|
449
|
-
src: dash,
|
|
450
|
-
type: mimeTypes.dash
|
|
451
|
-
}].filter(Boolean), {
|
|
452
|
-
preferManifestType,
|
|
453
|
-
fallbackDrm
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (!Array.isArray(sourceOptions)) {
|
|
458
|
-
return getSource([sourceOptions], {
|
|
459
|
-
preferManifestType,
|
|
460
|
-
fallbackDrm
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (fallbackDrm) {
|
|
465
|
-
return getSource(sourceOptions.map(option => ({ ...(option.src ? option : {
|
|
466
|
-
src: option
|
|
467
|
-
}),
|
|
468
|
-
drm: getDrmOptions(fallbackDrm)
|
|
469
|
-
})), {
|
|
470
|
-
preferManifestType
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const targetType = preferManifestType !== 'platform' ? preferManifestType : isSafari() ? 'hls' : 'dash';
|
|
475
|
-
const matched = sourceOptions.find(source => matchType(source, targetType));
|
|
476
|
-
const selected = matched || sourceOptions[0];
|
|
477
|
-
|
|
478
|
-
if (!selected) {
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const type = matched && preferManifestType === 'hls' && mimeTypes.hls;
|
|
483
|
-
return { ...(selected.src ? selected : {
|
|
484
|
-
src: selected
|
|
485
|
-
}),
|
|
486
|
-
type
|
|
487
|
-
};
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
/* eslint-disable no-param-reassign */
|
|
491
|
-
|
|
492
|
-
const SHAKA_LIVE_DURATION = 4294967296;
|
|
493
|
-
|
|
494
|
-
const isLiveDuration = duration => duration >= SHAKA_LIVE_DURATION;
|
|
495
|
-
|
|
496
|
-
var _window, _window$localStorage;
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* @typedef {{
|
|
500
|
-
* DRM_PROTAL_URL?: string
|
|
501
|
-
* }} debugOption
|
|
502
|
-
*/
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* @type {debugOption}
|
|
506
|
-
*/
|
|
507
|
-
const DEBUG_OPTIONS = {
|
|
508
|
-
DRM_PROTAL_URL: typeof window !== 'undefined' ? (_window = window) === null || _window === void 0 ? void 0 : (_window$localStorage = _window.localStorage) === null || _window$localStorage === void 0 ? void 0 : _window$localStorage.DRM_PROTAL_URL : ""
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
/* eslint-disable no-param-reassign */
|
|
512
|
-
const getCache = async (cache, key, {
|
|
513
|
-
load,
|
|
514
|
-
isValid
|
|
515
|
-
}) => {
|
|
516
|
-
if (!cache) {
|
|
517
|
-
return load();
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (cache[key] && (!isValid || isValid(cache[key]))) {
|
|
521
|
-
return cache[key];
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (!cache[key]) {
|
|
525
|
-
cache[key] = {};
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
cache[key] = { ...cache[key],
|
|
529
|
-
...(await load())
|
|
530
|
-
};
|
|
531
|
-
return cache[key];
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
var getCache$1 = getCache;
|
|
535
|
-
|
|
536
|
-
/* eslint-disable no-param-reassign */
|
|
537
|
-
|
|
538
|
-
const deepEqual = (current, updated) => JSON.stringify(current) === JSON.stringify(updated);
|
|
539
|
-
|
|
540
|
-
const HEARTBEAT_INTERVAL_MS$1 = 10000;
|
|
541
|
-
const UPDATE_INTERVAL_MS = 10000; // If content.end_time is `undefined`, like `videos`, we never use cache.
|
|
542
|
-
|
|
543
|
-
const isFreshCache = content => !content.end && Date.now() <= content.end_time * 1000; // TODO: separate by host
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
let lastSession = Promise.resolve();
|
|
547
|
-
|
|
548
|
-
const programRemainTime = (content, {
|
|
549
|
-
media = {}
|
|
550
|
-
}) => {
|
|
551
|
-
const programCurrentTime = isLiveDuration(media.duration) ? Date.now() / 1000 : content.start_time + media.currentTime;
|
|
552
|
-
return content.end_time - programCurrentTime;
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
const startPlaybackSession = async (playbackApi, options = {}) => {
|
|
556
|
-
var _state$content2;
|
|
557
|
-
|
|
558
|
-
await Promise.race([lastSession, new Promise(resolve => {
|
|
559
|
-
setTimeout(resolve, UPDATE_INTERVAL_MS / 2);
|
|
560
|
-
})]);
|
|
561
|
-
let unlockSession;
|
|
562
|
-
lastSession = new Promise(resolve => {
|
|
563
|
-
unlockSession = resolve;
|
|
564
|
-
});
|
|
565
|
-
const emitter = mitt();
|
|
566
|
-
const {
|
|
567
|
-
type,
|
|
568
|
-
id,
|
|
569
|
-
getCurrentTime,
|
|
570
|
-
cache
|
|
571
|
-
} = options;
|
|
572
|
-
const cacheKey = options.key || `${type}/${id}`;
|
|
573
|
-
const {
|
|
574
|
-
onChangeContent,
|
|
575
|
-
onSessionStart,
|
|
576
|
-
onReset,
|
|
577
|
-
heartbeatTime = HEARTBEAT_INTERVAL_MS$1,
|
|
578
|
-
updateTime = UPDATE_INTERVAL_MS
|
|
579
|
-
} = options;
|
|
580
|
-
const state = {
|
|
581
|
-
content: {
|
|
582
|
-
end_time: Date.now() / 1000
|
|
583
|
-
},
|
|
584
|
-
currentContent: {
|
|
585
|
-
end_time: Date.now() / 1000
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
const updateContent = async ({
|
|
590
|
-
useCache
|
|
591
|
-
} = {}) => {
|
|
592
|
-
var _state$content;
|
|
593
|
-
|
|
594
|
-
const content = await getCache$1(cache, cacheKey, {
|
|
595
|
-
load: () => playbackApi.getContent({
|
|
596
|
-
type,
|
|
597
|
-
id
|
|
598
|
-
}),
|
|
599
|
-
isValid: data => useCache && isFreshCache(data)
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
if (content.end) {
|
|
603
|
-
emitter.emit('liveEnd', content);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
if (!((_state$content = state.content) !== null && _state$content !== void 0 && _state$content.end_time) && content !== null && content !== void 0 && content.end_time) {
|
|
607
|
-
// transition from non-scheduled program to scheduled
|
|
608
|
-
return onReset({
|
|
609
|
-
reason: 'program start',
|
|
610
|
-
sourceChange: true
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const timeBeforeEnd = programRemainTime(state.currentContent, options) * 1000;
|
|
615
|
-
|
|
616
|
-
if (timeBeforeEnd > 0 && timeBeforeEnd <= UPDATE_INTERVAL_MS) {
|
|
617
|
-
setTimeout(() => {
|
|
618
|
-
if (programRemainTime(state.currentContent, options) > 0.5) {
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (isLiveDuration(options.media.duration)) {
|
|
623
|
-
// request new content for next ip linear program
|
|
624
|
-
updateContent({
|
|
625
|
-
useCache: true
|
|
626
|
-
});
|
|
627
|
-
} else {
|
|
628
|
-
onReset({
|
|
629
|
-
reason: 'program end',
|
|
630
|
-
sourceChange: true
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
}, timeBeforeEnd);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (!deepEqual(content, state.content)) {
|
|
637
|
-
state.content = content; // keep watching current program
|
|
638
|
-
|
|
639
|
-
const skipUiUpdate = !useCache;
|
|
640
|
-
|
|
641
|
-
if (!skipUiUpdate) {
|
|
642
|
-
state.currentContent = content;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
onChangeContent === null || onChangeContent === void 0 ? void 0 : onChangeContent({
|
|
646
|
-
type,
|
|
647
|
-
...content,
|
|
648
|
-
skipUiUpdate
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
}; // get last playback time to start playback fast
|
|
652
|
-
// getContent is not critical, so don't block playback if it hangs or fails(ignored in API logic)
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const waitForContent = Promise.race([updateContent({
|
|
656
|
-
useCache: true
|
|
657
|
-
}), new Promise(resolve => {
|
|
658
|
-
setTimeout(resolve, UPDATE_INTERVAL_MS);
|
|
659
|
-
})]);
|
|
660
|
-
const sessionInfo = await playbackApi.startPlayback({
|
|
661
|
-
type,
|
|
662
|
-
id
|
|
663
|
-
}).catch(error => {
|
|
664
|
-
unlockSession();
|
|
665
|
-
return Promise.reject(error);
|
|
666
|
-
});
|
|
667
|
-
onSessionStart === null || onSessionStart === void 0 ? void 0 : onSessionStart(sessionInfo);
|
|
668
|
-
const requestParams = {
|
|
669
|
-
type,
|
|
670
|
-
id,
|
|
671
|
-
token: sessionInfo.token
|
|
672
|
-
};
|
|
673
|
-
state.token = sessionInfo.token;
|
|
674
|
-
state.sources = (await getCache$1(cache, cacheKey, {
|
|
675
|
-
load: () => playbackApi.getPlaybackInfo({
|
|
676
|
-
type,
|
|
677
|
-
id,
|
|
678
|
-
token: state.token
|
|
679
|
-
}),
|
|
680
|
-
isValid: data => data.sources && isFreshCache(data)
|
|
681
|
-
})).sources;
|
|
682
|
-
let updateIntervalId;
|
|
683
|
-
|
|
684
|
-
if (type === 'lives') {
|
|
685
|
-
updateIntervalId = setInterval(updateContent, updateTime);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
let lastPlayedTime;
|
|
689
|
-
|
|
690
|
-
const updateLastPlayed = () => {
|
|
691
|
-
const currentTime = getCurrentTime === null || getCurrentTime === void 0 ? void 0 : getCurrentTime();
|
|
692
|
-
|
|
693
|
-
if (currentTime >= 0 && lastPlayedTime !== currentTime) {
|
|
694
|
-
lastPlayedTime = currentTime;
|
|
695
|
-
playbackApi.updateLastPlayed({ ...requestParams,
|
|
696
|
-
time: currentTime
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
if (type === 'videos') {
|
|
702
|
-
updateIntervalId = setInterval(updateLastPlayed, updateTime);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
const heartbeatIntervalId = setInterval(() => playbackApi.heartbeat(requestParams).catch(error => {
|
|
706
|
-
var _error$response;
|
|
707
|
-
|
|
708
|
-
if (/4\d\d/.test((_error$response = error.response) === null || _error$response === void 0 ? void 0 : _error$response.status)) {
|
|
709
|
-
clearInterval(heartbeatIntervalId);
|
|
710
|
-
onReset === null || onReset === void 0 ? void 0 : onReset({
|
|
711
|
-
sourceChange: false,
|
|
712
|
-
reason: 'Invalid token'
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
}), heartbeatTime);
|
|
716
|
-
|
|
717
|
-
const end = ({
|
|
718
|
-
preserveSource
|
|
719
|
-
} = {}) => {
|
|
720
|
-
if (!preserveSource && cache !== null && cache !== void 0 && cache[cacheKey]) {
|
|
721
|
-
cache[cacheKey].sources = undefined;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
updateLastPlayed();
|
|
725
|
-
clearInterval(updateIntervalId);
|
|
726
|
-
clearInterval(heartbeatIntervalId);
|
|
727
|
-
clearTimeout(state.endTimeoutId);
|
|
728
|
-
emitter.emit('playbackEnded');
|
|
729
|
-
return playbackApi.endPlayback(requestParams).finally(unlockSession);
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
if ((_state$content2 = state.content) !== null && _state$content2 !== void 0 && _state$content2.end) {
|
|
733
|
-
end();
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
emitter.on('liveEnd', end);
|
|
737
|
-
await waitForContent;
|
|
738
|
-
return { ...state,
|
|
739
|
-
token: sessionInfo.token,
|
|
740
|
-
drmPortalUrl: DEBUG_OPTIONS.DRM_PROTAL_URL || sessionInfo.drm_portal_url,
|
|
741
|
-
updateLastPlayed,
|
|
742
|
-
end,
|
|
743
|
-
replaceApi: replacement => {
|
|
744
|
-
playbackApi = replacement;
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
};
|
|
748
|
-
|
|
749
|
-
/* eslint-disable no-bitwise */
|
|
750
|
-
const uuidv4 = () => {
|
|
751
|
-
const crypto = window.crypto || window.msCrypto;
|
|
752
|
-
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));
|
|
753
|
-
};
|
|
754
|
-
|
|
755
|
-
const modes = {
|
|
756
|
-
videos: 'video',
|
|
757
|
-
lives: 'live'
|
|
758
|
-
};
|
|
759
13
|
const logEventNames$1 = {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
},
|
|
984
|
-
updateContent: content => {
|
|
985
|
-
state.content = content;
|
|
986
|
-
},
|
|
987
|
-
reset: () => registered.forEach(off => off())
|
|
988
|
-
};
|
|
989
|
-
};
|
|
990
|
-
|
|
991
|
-
function getVersion() {
|
|
992
|
-
try {
|
|
993
|
-
// eslint-disable-next-line no-undef
|
|
994
|
-
return "2.25.0-canary.23";
|
|
995
|
-
} catch (e) {
|
|
996
|
-
return undefined;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
const logEventNames = {
|
|
1001
|
-
playbackBeganLoading: 'playback_began_player_loading',
|
|
1002
|
-
playbackBeganPlayerStartupTime: 'playback_began_player_startup_time',
|
|
1003
|
-
playbackBeganVideoStartupTime: 'playback_began_video_startup_time',
|
|
1004
|
-
playbackVideoBegan: 'playback_video_began',
|
|
1005
|
-
playbackVideoPaused: 'playback_video_paused',
|
|
1006
|
-
playbackVideoBufferingBegan: 'playback_video_buffering_began',
|
|
1007
|
-
playbackVideoBufferingEnded: 'playback_video_buffering_ended',
|
|
1008
|
-
playbackVideoEnded: 'playback_video_ended',
|
|
1009
|
-
playbackSeekingBegan: 'playback_seeking_began',
|
|
1010
|
-
playbackSeekingEnded: 'playback_seeking_ended',
|
|
1011
|
-
playbackError: 'playback_error_occurred',
|
|
1012
|
-
playbackSpeedChange: 'playback_speed_change',
|
|
1013
|
-
playbackAudioVolumeChange: 'playback_audio_volume_change',
|
|
1014
|
-
playbackAudioMuteChange: 'playback_audio_mute_change',
|
|
1015
|
-
playbackStreamingQualityChangeDownload: 'playback_streaming_quality_change_download',
|
|
1016
|
-
playbackStreamingQualityChangeRender: 'playback_streaming_quality_change_render',
|
|
1017
|
-
playbackAudioTrackChange: 'playback_audio_track_change',
|
|
1018
|
-
playbackSubtitleChange: 'playback_subtitle_change',
|
|
1019
|
-
playbackLoopChange: 'playback_loop_change',
|
|
1020
|
-
playing: 'play',
|
|
1021
|
-
paused: 'pause',
|
|
1022
|
-
seek: 'seek',
|
|
1023
|
-
rewind: 'rewind',
|
|
1024
|
-
forward: 'forward',
|
|
1025
|
-
openSettings: 'setting_page_entered',
|
|
1026
|
-
closeSettings: 'setting_page_exited',
|
|
1027
|
-
speedSettingChange: 'speed_setting_change',
|
|
1028
|
-
qualitySettingChange: 'quality_setting_change',
|
|
1029
|
-
audioVolumeSettingChange: 'audio_volume_setting_change',
|
|
1030
|
-
audioMuteSettingChange: 'audio_mute_setting_change',
|
|
1031
|
-
audioTrackSettingChange: 'audio_track_setting_change',
|
|
1032
|
-
subtitleSettingChange: 'subtitle_setting_change',
|
|
1033
|
-
loopSettingChange: 'loop_setting_change'
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
const mapLogEvents = ({
|
|
1037
|
-
video,
|
|
1038
|
-
version,
|
|
1039
|
-
playerName,
|
|
1040
|
-
getPlaybackStatus = () => video
|
|
1041
|
-
}) => {
|
|
1042
|
-
const emitter = mitt();
|
|
1043
|
-
const state = {
|
|
1044
|
-
status: 'init',
|
|
1045
|
-
seeking: false,
|
|
1046
|
-
volume: undefined,
|
|
1047
|
-
muted: undefined,
|
|
1048
|
-
loop: undefined,
|
|
1049
|
-
lastPlaybackRate: 1,
|
|
1050
|
-
hasEnabledSubtitle: false,
|
|
1051
|
-
resourceType: undefined
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
|
-
const baseProperties = () => ({
|
|
1055
|
-
player_name: playerName,
|
|
1056
|
-
playback_module_version: version || getVersion(),
|
|
1057
|
-
system_time: Date.now() / 1000
|
|
1058
|
-
});
|
|
1059
|
-
|
|
1060
|
-
const commonProperties = () => ({ ...baseProperties(),
|
|
1061
|
-
current_position: state.currentTime,
|
|
1062
|
-
resource_type: state.resourceType
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
const dispatchStart = () => {
|
|
1066
|
-
if (state.status === 'started') {
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
state.status = 'started';
|
|
1071
|
-
emitter.emit('playbackVideoBegan', commonProperties());
|
|
1072
|
-
};
|
|
1073
|
-
|
|
1074
|
-
const dispatchStop = () => {
|
|
1075
|
-
if (state.status !== 'started') {
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
state.status = 'stopped';
|
|
1080
|
-
emitter.emit('playbackVideoPaused', commonProperties());
|
|
1081
|
-
};
|
|
1082
|
-
|
|
1083
|
-
const handleEnd = () => {
|
|
1084
|
-
if (state.status === 'started') {
|
|
1085
|
-
dispatchStop();
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
if (state.status !== 'init') {
|
|
1089
|
-
state.status = 'init';
|
|
1090
|
-
emitter.emit('playbackVideoEnded', commonProperties());
|
|
1091
|
-
}
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
const playbackEvents = [on(video, 'error', event => {
|
|
1095
|
-
var _event$error, _event$error2, _event$error2$data;
|
|
1096
|
-
|
|
1097
|
-
emitter.emit('playbackError', {
|
|
1098
|
-
error_code: ((_event$error = event.error) === null || _event$error === void 0 ? void 0 : _event$error.code) || ((_event$error2 = event.error) === null || _event$error2 === void 0 ? void 0 : (_event$error2$data = _event$error2.data) === null || _event$error2$data === void 0 ? void 0 : _event$error2$data.code),
|
|
1099
|
-
...commonProperties()
|
|
1100
|
-
});
|
|
1101
|
-
}), on(video, 'durationchange', () => {
|
|
1102
|
-
// duration may change when playing an ad stitched stream, take only initial value
|
|
1103
|
-
if (!state.duration) {
|
|
1104
|
-
state.duration = getPlaybackStatus().duration;
|
|
1105
|
-
}
|
|
1106
|
-
}), once(video, 'playerStarted', () => {
|
|
1107
|
-
emitter.emit('playbackBeganLoading', baseProperties());
|
|
1108
|
-
}), once(video, 'loadstart', () => {
|
|
1109
|
-
emitter.emit('playbackBeganPlayerStartupTime', baseProperties());
|
|
1110
|
-
}), once(video, 'canplay', () => {
|
|
1111
|
-
state.status = 'began';
|
|
1112
|
-
state.resourceType = isLiveDuration(video.duration) ? 'live' : 'vod';
|
|
1113
|
-
emitter.emit('playbackBeganVideoStartupTime', { ...baseProperties(),
|
|
1114
|
-
resource_type: state.resourceType
|
|
1115
|
-
}); // sync state from video
|
|
1116
|
-
|
|
1117
|
-
state.volume = video.volume;
|
|
1118
|
-
state.muted = video.muted;
|
|
1119
|
-
}), on(video, 'playing', dispatchStart), on(video, 'waiting', () => {
|
|
1120
|
-
if (!state.buffering) {
|
|
1121
|
-
emitter.emit('playbackVideoBufferingBegan', commonProperties());
|
|
1122
|
-
state.buffering = true;
|
|
1123
|
-
}
|
|
1124
|
-
}), on(video, 'timeupdate', () => {
|
|
1125
|
-
state.currentTime = getPlaybackStatus().currentTime;
|
|
1126
|
-
|
|
1127
|
-
if (state.buffering) {
|
|
1128
|
-
emitter.emit('playbackVideoBufferingEnded', commonProperties());
|
|
1129
|
-
state.buffering = false;
|
|
1130
|
-
}
|
|
1131
|
-
}), on(video, 'pause', dispatchStop), on(video, 'seeking', () => {
|
|
1132
|
-
state.seeking = true;
|
|
1133
|
-
emitter.emit('playbackSeekingBegan', commonProperties());
|
|
1134
|
-
}), on(video, 'seeked', () => {
|
|
1135
|
-
if (state.seeking) {
|
|
1136
|
-
emitter.emit('playbackSeekingEnded', commonProperties());
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
state.seeking = false;
|
|
1140
|
-
}), on(video, 'ratechange', () => {
|
|
1141
|
-
/*
|
|
1142
|
-
Monkey patch for shaka player legecy issue:
|
|
1143
|
-
For more info plz see https://github.com/shaka-project/shaka-player/issues/951
|
|
1144
|
-
`video.playbackRate` may be 0.
|
|
1145
|
-
*/
|
|
1146
|
-
const {
|
|
1147
|
-
lastPlaybackRate
|
|
1148
|
-
} = state;
|
|
1149
|
-
state.lastPlaybackRate = video.playbackRate || state.lastPlaybackRate;
|
|
1150
|
-
|
|
1151
|
-
if (lastPlaybackRate === state.lastPlaybackRate) {
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
state.lastPlaybackRate = video.playbackRate;
|
|
1156
|
-
emitter.emit('playbackSpeedChange', {
|
|
1157
|
-
playback_speed: video.playbackRate,
|
|
1158
|
-
...commonProperties()
|
|
1159
|
-
});
|
|
1160
|
-
}), on(video, 'loopChange', () => {
|
|
1161
|
-
if (video.loop === state.loop) {
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
emitter.emit('playbackLoopChange', {
|
|
1166
|
-
loop: video.loop,
|
|
1167
|
-
...commonProperties()
|
|
1168
|
-
});
|
|
1169
|
-
state.loop = video.loop;
|
|
1170
|
-
}), on(video, 'ended', handleEnd), on(video, 'volumechange', () => {
|
|
1171
|
-
if (video.volume !== state.volume && state.volume !== undefined) {
|
|
1172
|
-
emitter.emit('playbackAudioVolumeChange', {
|
|
1173
|
-
volume: video.volume,
|
|
1174
|
-
...commonProperties()
|
|
1175
|
-
});
|
|
1176
|
-
state.volume = video.volume;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
if (video.muted !== state.muted && state.muted !== undefined) {
|
|
1180
|
-
emitter.emit('playbackAudioMuteChange', {
|
|
1181
|
-
muted: video.muted,
|
|
1182
|
-
...commonProperties()
|
|
1183
|
-
});
|
|
1184
|
-
state.muted = video.muted;
|
|
1185
|
-
}
|
|
1186
|
-
}), on(video, 'downloadQualityChange', event => {
|
|
1187
|
-
emitter.emit('playbackStreamingQualityChangeDownload', { ...event.detail,
|
|
1188
|
-
...commonProperties()
|
|
1189
|
-
});
|
|
1190
|
-
}), on(video, 'audioTrackChange', event => {
|
|
1191
|
-
emitter.emit('playbackAudioTrackChange', { ...event.detail,
|
|
1192
|
-
...commonProperties()
|
|
1193
|
-
});
|
|
1194
|
-
}), on(video, 'textTrackChange', event => {
|
|
1195
|
-
var _event$detail$getText, _event$detail;
|
|
1196
|
-
|
|
1197
|
-
const activeTrack = ((_event$detail$getText = (_event$detail = event.detail).getTextTracks) === null || _event$detail$getText === void 0 ? void 0 : _event$detail$getText.call(_event$detail).find(track => track.active)) || {
|
|
1198
|
-
language: 'off',
|
|
1199
|
-
name: 'off'
|
|
1200
|
-
};
|
|
1201
|
-
|
|
1202
|
-
if (!state.hasEnabledSubtitle && activeTrack.language === 'off') {
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
state.hasEnabledSubtitle = activeTrack.language !== 'off';
|
|
1207
|
-
emitter.emit('playbackSubtitleChange', {
|
|
1208
|
-
lang: activeTrack.language,
|
|
1209
|
-
// TODO also add display name here
|
|
1210
|
-
...commonProperties()
|
|
1211
|
-
});
|
|
1212
|
-
}), on(video, 'resize', () => {
|
|
1213
|
-
emitter.emit('playbackStreamingQualityChangeRender', {
|
|
1214
|
-
height: video.videoHeight,
|
|
1215
|
-
width: video.videoWidth,
|
|
1216
|
-
...commonProperties()
|
|
1217
|
-
});
|
|
1218
|
-
})];
|
|
1219
|
-
const uiEvents = [on(video, 'audioVolumeSettingChange', event => {
|
|
1220
|
-
var _event$detail2;
|
|
1221
|
-
|
|
1222
|
-
emitter.emit('audioVolumeSettingChange', {
|
|
1223
|
-
volume: (_event$detail2 = event.detail) === null || _event$detail2 === void 0 ? void 0 : _event$detail2.volume,
|
|
1224
|
-
...commonProperties()
|
|
1225
|
-
});
|
|
1226
|
-
})];
|
|
1227
|
-
const registered = playbackEvents.concat(uiEvents);
|
|
1228
|
-
return {
|
|
1229
|
-
addEventListener: (name, handler) => emitter.on(name, handler),
|
|
1230
|
-
all: handler => emitter.on('*', handler),
|
|
1231
|
-
|
|
1232
|
-
/**
|
|
1233
|
-
* @param
|
|
1234
|
-
* @param {{currentTime?: string}}
|
|
1235
|
-
* */
|
|
1236
|
-
emit: (name, {
|
|
1237
|
-
currentTime
|
|
1238
|
-
}, properties) => {
|
|
1239
|
-
emitter.emit(name, {
|
|
1240
|
-
current_position: currentTime,
|
|
1241
|
-
...properties,
|
|
1242
|
-
...commonProperties()
|
|
1243
|
-
});
|
|
1244
|
-
},
|
|
1245
|
-
updateContent: content => {
|
|
1246
|
-
state.content = content;
|
|
1247
|
-
},
|
|
1248
|
-
getCommonProperties: () => commonProperties(),
|
|
1249
|
-
reset: () => {
|
|
1250
|
-
registered.forEach(off => off());
|
|
1251
|
-
handleEnd();
|
|
1252
|
-
}
|
|
1253
|
-
};
|
|
1254
|
-
};
|
|
1255
|
-
|
|
1256
|
-
var playlogv3 = /*#__PURE__*/Object.freeze({
|
|
1257
|
-
__proto__: null,
|
|
1258
|
-
mapLogEvents: mapLogEvents,
|
|
1259
|
-
logEventNames: logEventNames
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
const waitMs = time => new Promise(resolve => {
|
|
1263
|
-
setTimeout(resolve, time);
|
|
14
|
+
playbackBeganLoading: "playback_began_player_loading",
|
|
15
|
+
playbackBeganPlayerStartupTime: "playback_began_player_startup_time",
|
|
16
|
+
playbackBeganVideoStartupTime: "playback_began_video_startup_time",
|
|
17
|
+
playbackVideoBegan: "playback_video_began",
|
|
18
|
+
playbackVideoPaused: "playback_video_paused",
|
|
19
|
+
playbackVideoBufferingBegan: "playback_video_buffering_began",
|
|
20
|
+
playbackVideoBufferingEnded: "playback_video_buffering_ended",
|
|
21
|
+
playbackVideoEnded: "playback_video_ended",
|
|
22
|
+
playbackSeekingBegan: "playback_seeking_began",
|
|
23
|
+
playbackSeekingEnded: "playback_seeking_ended",
|
|
24
|
+
playbackError: "playback_error_occurred",
|
|
25
|
+
playbackSpeedChange: "playback_speed_change",
|
|
26
|
+
playbackAudioVolumeChange: "playback_audio_volume_change",
|
|
27
|
+
playbackAudioMuteChange: "playback_audio_mute_change",
|
|
28
|
+
playbackStreamingQualityChangeDownload: "playback_streaming_quality_change_download",
|
|
29
|
+
playbackStreamingQualityChangeRender: "playback_streaming_quality_change_render",
|
|
30
|
+
playbackAudioTrackChange: "playback_audio_track_change",
|
|
31
|
+
playbackSubtitleChange: "playback_subtitle_change",
|
|
32
|
+
playbackLoopChange: "playback_loop_change",
|
|
33
|
+
playing: "play",
|
|
34
|
+
paused: "pause",
|
|
35
|
+
seek: "seek",
|
|
36
|
+
rewind: "rewind",
|
|
37
|
+
forward: "forward",
|
|
38
|
+
openSettings: "setting_page_entered",
|
|
39
|
+
closeSettings: "setting_page_exited",
|
|
40
|
+
speedSettingChange: "speed_setting_change",
|
|
41
|
+
qualitySettingChange: "quality_setting_change",
|
|
42
|
+
audioVolumeSettingChange: "audio_volume_setting_change",
|
|
43
|
+
audioMuteSettingChange: "audio_mute_setting_change",
|
|
44
|
+
audioTrackSettingChange: "audio_track_setting_change",
|
|
45
|
+
subtitleSettingChange: "subtitle_setting_change",
|
|
46
|
+
loopSettingChange: "loop_setting_change"
|
|
47
|
+
};
|
|
48
|
+
const mapLogEvents$1 = ({ video, version, playerName, getPlaybackStatus = () => video }) => {
|
|
49
|
+
const emitter = mitt();
|
|
50
|
+
const state = {
|
|
51
|
+
status: "init",
|
|
52
|
+
seeking: false,
|
|
53
|
+
volume: void 0,
|
|
54
|
+
muted: void 0,
|
|
55
|
+
loop: void 0,
|
|
56
|
+
lastPlaybackRate: 1,
|
|
57
|
+
hasEnabledSubtitle: false,
|
|
58
|
+
resourceType: void 0
|
|
59
|
+
};
|
|
60
|
+
const baseProperties = () => ({
|
|
61
|
+
player_name: playerName,
|
|
62
|
+
playback_module_version: version || getVersion(),
|
|
63
|
+
system_time: Date.now() / 1e3
|
|
64
|
+
});
|
|
65
|
+
const commonProperties = () => ({
|
|
66
|
+
...baseProperties(),
|
|
67
|
+
current_position: state.currentTime,
|
|
68
|
+
resource_type: state.resourceType
|
|
69
|
+
});
|
|
70
|
+
const dispatchStart = () => {
|
|
71
|
+
if (state.status === "started") return;
|
|
72
|
+
state.status = "started";
|
|
73
|
+
emitter.emit("playbackVideoBegan", commonProperties());
|
|
74
|
+
};
|
|
75
|
+
const dispatchStop = () => {
|
|
76
|
+
if (state.status !== "started") return;
|
|
77
|
+
state.status = "stopped";
|
|
78
|
+
emitter.emit("playbackVideoPaused", commonProperties());
|
|
79
|
+
};
|
|
80
|
+
const handleEnd = () => {
|
|
81
|
+
if (state.status === "started") dispatchStop();
|
|
82
|
+
if (state.status !== "init") {
|
|
83
|
+
state.status = "init";
|
|
84
|
+
emitter.emit("playbackVideoEnded", commonProperties());
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const playbackEvents = [
|
|
88
|
+
on(video, "error", (event) => {
|
|
89
|
+
emitter.emit("playbackError", {
|
|
90
|
+
error_code: event.error?.code || event.error?.data?.code,
|
|
91
|
+
...commonProperties()
|
|
92
|
+
});
|
|
93
|
+
}),
|
|
94
|
+
on(video, "durationchange", () => {
|
|
95
|
+
if (!state.duration) state.duration = getPlaybackStatus().duration;
|
|
96
|
+
}),
|
|
97
|
+
once(video, "playerStarted", () => {
|
|
98
|
+
emitter.emit("playbackBeganLoading", baseProperties());
|
|
99
|
+
}),
|
|
100
|
+
once(video, "loadstart", () => {
|
|
101
|
+
emitter.emit("playbackBeganPlayerStartupTime", baseProperties());
|
|
102
|
+
}),
|
|
103
|
+
once(video, "canplay", () => {
|
|
104
|
+
state.status = "began";
|
|
105
|
+
state.resourceType = isLiveDuration(video.duration) ? "live" : "vod";
|
|
106
|
+
emitter.emit("playbackBeganVideoStartupTime", {
|
|
107
|
+
...baseProperties(),
|
|
108
|
+
resource_type: state.resourceType
|
|
109
|
+
});
|
|
110
|
+
state.volume = video.volume;
|
|
111
|
+
state.muted = video.muted;
|
|
112
|
+
}),
|
|
113
|
+
on(video, "playing", dispatchStart),
|
|
114
|
+
on(video, "waiting", () => {
|
|
115
|
+
if (!state.buffering) {
|
|
116
|
+
emitter.emit("playbackVideoBufferingBegan", commonProperties());
|
|
117
|
+
state.buffering = true;
|
|
118
|
+
}
|
|
119
|
+
}),
|
|
120
|
+
on(video, "timeupdate", () => {
|
|
121
|
+
state.currentTime = getPlaybackStatus().currentTime;
|
|
122
|
+
if (state.buffering) {
|
|
123
|
+
emitter.emit("playbackVideoBufferingEnded", commonProperties());
|
|
124
|
+
state.buffering = false;
|
|
125
|
+
}
|
|
126
|
+
}),
|
|
127
|
+
on(video, "pause", dispatchStop),
|
|
128
|
+
on(video, "seeking", () => {
|
|
129
|
+
state.seeking = true;
|
|
130
|
+
emitter.emit("playbackSeekingBegan", commonProperties());
|
|
131
|
+
}),
|
|
132
|
+
on(video, "seeked", () => {
|
|
133
|
+
if (state.seeking) emitter.emit("playbackSeekingEnded", commonProperties());
|
|
134
|
+
state.seeking = false;
|
|
135
|
+
}),
|
|
136
|
+
on(video, "ratechange", () => {
|
|
137
|
+
const { lastPlaybackRate } = state;
|
|
138
|
+
state.lastPlaybackRate = video.playbackRate || state.lastPlaybackRate;
|
|
139
|
+
if (lastPlaybackRate === state.lastPlaybackRate) return;
|
|
140
|
+
state.lastPlaybackRate = video.playbackRate;
|
|
141
|
+
emitter.emit("playbackSpeedChange", {
|
|
142
|
+
playback_speed: video.playbackRate,
|
|
143
|
+
...commonProperties()
|
|
144
|
+
});
|
|
145
|
+
}),
|
|
146
|
+
on(video, "loopChange", () => {
|
|
147
|
+
if (video.loop === state.loop) return;
|
|
148
|
+
emitter.emit("playbackLoopChange", {
|
|
149
|
+
loop: video.loop,
|
|
150
|
+
...commonProperties()
|
|
151
|
+
});
|
|
152
|
+
state.loop = video.loop;
|
|
153
|
+
}),
|
|
154
|
+
on(video, "ended", handleEnd),
|
|
155
|
+
on(video, "volumechange", () => {
|
|
156
|
+
if (video.volume !== state.volume && state.volume !== void 0) {
|
|
157
|
+
emitter.emit("playbackAudioVolumeChange", {
|
|
158
|
+
volume: video.volume,
|
|
159
|
+
...commonProperties()
|
|
160
|
+
});
|
|
161
|
+
state.volume = video.volume;
|
|
162
|
+
}
|
|
163
|
+
if (video.muted !== state.muted && state.muted !== void 0) {
|
|
164
|
+
emitter.emit("playbackAudioMuteChange", {
|
|
165
|
+
muted: video.muted,
|
|
166
|
+
...commonProperties()
|
|
167
|
+
});
|
|
168
|
+
state.muted = video.muted;
|
|
169
|
+
}
|
|
170
|
+
}),
|
|
171
|
+
on(video, "downloadQualityChange", (event) => {
|
|
172
|
+
emitter.emit("playbackStreamingQualityChangeDownload", {
|
|
173
|
+
...event.detail,
|
|
174
|
+
...commonProperties()
|
|
175
|
+
});
|
|
176
|
+
}),
|
|
177
|
+
on(video, "audioTrackChange", (event) => {
|
|
178
|
+
emitter.emit("playbackAudioTrackChange", {
|
|
179
|
+
...event.detail,
|
|
180
|
+
...commonProperties()
|
|
181
|
+
});
|
|
182
|
+
}),
|
|
183
|
+
on(video, "textTrackChange", (event) => {
|
|
184
|
+
const activeTrack = event.detail.getTextTracks?.().find((track) => track.active) || {
|
|
185
|
+
language: "off",
|
|
186
|
+
name: "off"
|
|
187
|
+
};
|
|
188
|
+
if (!state.hasEnabledSubtitle && activeTrack.language === "off") return;
|
|
189
|
+
state.hasEnabledSubtitle = activeTrack.language !== "off";
|
|
190
|
+
emitter.emit("playbackSubtitleChange", {
|
|
191
|
+
lang: activeTrack.language,
|
|
192
|
+
...commonProperties()
|
|
193
|
+
});
|
|
194
|
+
}),
|
|
195
|
+
on(video, "resize", () => {
|
|
196
|
+
emitter.emit("playbackStreamingQualityChangeRender", {
|
|
197
|
+
height: video.videoHeight,
|
|
198
|
+
width: video.videoWidth,
|
|
199
|
+
...commonProperties()
|
|
200
|
+
});
|
|
201
|
+
})
|
|
202
|
+
];
|
|
203
|
+
const uiEvents = [on(video, "audioVolumeSettingChange", (event) => {
|
|
204
|
+
emitter.emit("audioVolumeSettingChange", {
|
|
205
|
+
volume: event.detail?.volume,
|
|
206
|
+
...commonProperties()
|
|
207
|
+
});
|
|
208
|
+
})];
|
|
209
|
+
const registered = playbackEvents.concat(uiEvents);
|
|
210
|
+
return {
|
|
211
|
+
addEventListener: (name, handler) => emitter.on(name, handler),
|
|
212
|
+
all: (handler) => emitter.on("*", handler),
|
|
213
|
+
emit: (name, { currentTime }, properties) => {
|
|
214
|
+
emitter.emit(name, {
|
|
215
|
+
current_position: currentTime,
|
|
216
|
+
...properties,
|
|
217
|
+
...commonProperties()
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
updateContent: (content) => {
|
|
221
|
+
state.content = content;
|
|
222
|
+
},
|
|
223
|
+
getCommonProperties: () => commonProperties(),
|
|
224
|
+
reset: () => {
|
|
225
|
+
registered.forEach((off) => off());
|
|
226
|
+
handleEnd();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/modules/wait.js
|
|
233
|
+
const waitMs = (time) => new Promise((resolve) => {
|
|
234
|
+
setTimeout(resolve, time);
|
|
1264
235
|
});
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1288
|
-
};
|
|
1289
|
-
|
|
1290
|
-
var retryWithBackoff$1 = retryWithBackoff;
|
|
1291
|
-
|
|
236
|
+
var wait_default = waitMs;
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/modules/retryWithBackoff.js
|
|
240
|
+
const retryWithBackoff = async ({ fn, maxRetries = 10, initialDelay = 1e3 }) => {
|
|
241
|
+
let retryDelay = initialDelay;
|
|
242
|
+
for (let retry = 1; retry <= maxRetries; retry++) try {
|
|
243
|
+
return await fn();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(`Retry #${retry} failed:`, error);
|
|
246
|
+
if (retry === maxRetries) {
|
|
247
|
+
console.error("Max retries reached. Giving up.");
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
await wait_default(retryDelay);
|
|
251
|
+
retryDelay += retry * 1e3;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var retryWithBackoff_default = retryWithBackoff;
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/modules/retrieveModuleConfig.ts
|
|
1292
258
|
const retrieveModuleConfig = (modulesConfig, query) => {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
};
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
const axios$1 = {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
user_id: userId,
|
|
1337
|
-
...rawData.analyticsConfig
|
|
1338
|
-
}),
|
|
1339
|
-
event_time: new Date().toISOString()
|
|
1340
|
-
}; // Avoid template literals because the env variable would be replaced in the rollup
|
|
1341
|
-
|
|
1342
|
-
await retryWithBackoff$1({
|
|
1343
|
-
fn: () => axios$1.post(endpoint, payload, {
|
|
1344
|
-
headers: {
|
|
1345
|
-
'Kks-Bv-Token': token
|
|
1346
|
-
}
|
|
1347
|
-
})
|
|
1348
|
-
});
|
|
1349
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
1350
|
-
};
|
|
1351
|
-
|
|
1352
|
-
var eventHeartbeat$1 = eventHeartbeat;
|
|
1353
|
-
|
|
1354
|
-
/* eslint-disable camelcase */
|
|
1355
|
-
|
|
1356
|
-
const axios = {
|
|
1357
|
-
post: (...args) => {
|
|
1358
|
-
console.log(args);
|
|
1359
|
-
}
|
|
1360
|
-
}; // TODO
|
|
1361
|
-
|
|
259
|
+
const result = {};
|
|
260
|
+
Object.entries(modulesConfig).forEach(([key, value]) => {
|
|
261
|
+
if (key.startsWith(`${query}.`)) {
|
|
262
|
+
const targetKey = key.replace(`${query}.`, "");
|
|
263
|
+
result[targetKey] = value;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
return result;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/modules/heartbeat.ts
|
|
271
|
+
const HEARTBEAT_INTERVAL_MS = 1e4;
|
|
272
|
+
const axios$1 = { post: (...args) => {
|
|
273
|
+
console.log(args);
|
|
274
|
+
} };
|
|
275
|
+
const eventHeartbeat = ({ token, resourceId, sessionId, userId, deviceId, rawData, endpoint = process.env.BV_ONE_EVENT_HEARTBEAT_API + "/v1/events", resourceTypes: resourceTypes$1 }) => {
|
|
276
|
+
if (!token) return;
|
|
277
|
+
const resourceType = rawData.analyticsConfig.resource_type || resourceTypes$1[rawData.logTarget.getCommonProperties().resource_type || "vod"];
|
|
278
|
+
return setInterval(async () => {
|
|
279
|
+
const payload = {
|
|
280
|
+
event_type: "heartbeat_log",
|
|
281
|
+
event_name: "playback_player_active",
|
|
282
|
+
event_content: JSON.stringify({
|
|
283
|
+
resource_type: resourceType,
|
|
284
|
+
resource_id: resourceId,
|
|
285
|
+
session_id: sessionId,
|
|
286
|
+
device_id: deviceId,
|
|
287
|
+
user_id: userId,
|
|
288
|
+
...rawData.analyticsConfig
|
|
289
|
+
}),
|
|
290
|
+
event_time: (/* @__PURE__ */ new Date()).toISOString()
|
|
291
|
+
};
|
|
292
|
+
await retryWithBackoff_default({ fn: () => axios$1.post(endpoint, payload, { headers: { "Kks-Bv-Token": token } }) });
|
|
293
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
294
|
+
};
|
|
295
|
+
var heartbeat_default = eventHeartbeat;
|
|
296
|
+
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/modules/analytics.ts
|
|
299
|
+
const axios = { post: (...args) => {
|
|
300
|
+
console.log(args);
|
|
301
|
+
} };
|
|
1362
302
|
const getDeviceInfo = () => {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
};
|
|
1370
|
-
|
|
1371
|
-
const DEVICE_ID_KEY = 'kks-device-id';
|
|
1372
|
-
|
|
303
|
+
const browser = getBrowser();
|
|
304
|
+
return {
|
|
305
|
+
browser: `${browser.name} ${browser.version}`,
|
|
306
|
+
device_category: isDesktop() ? "Desktop" : "Mobile",
|
|
307
|
+
device_platform: getOS().name
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
const DEVICE_ID_KEY = "kks-device-id";
|
|
1373
311
|
const getDefaultDeviceId = () => {
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
312
|
+
const deviceId = window.localStorage.getItem(DEVICE_ID_KEY) || uuidv4_default();
|
|
313
|
+
window.localStorage.setItem(DEVICE_ID_KEY, deviceId);
|
|
314
|
+
return deviceId;
|
|
1377
315
|
};
|
|
1378
|
-
|
|
1379
316
|
const resourceTypes = {
|
|
1380
|
-
|
|
1381
|
-
|
|
317
|
+
vod: "RESOURCE_TYPE_VOD_EVENT",
|
|
318
|
+
live: "RESOURCE_TYPE_LIVE_EVENT"
|
|
1382
319
|
};
|
|
1383
320
|
/**
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
token,
|
|
1451
|
-
sessionId,
|
|
1452
|
-
resourceId,
|
|
1453
|
-
userId,
|
|
1454
|
-
deviceId,
|
|
1455
|
-
endpoint: heartbeatEndpointUrl,
|
|
1456
|
-
rawData: {
|
|
1457
|
-
analyticsConfig,
|
|
1458
|
-
// @ts-ignore
|
|
1459
|
-
logTarget
|
|
1460
|
-
},
|
|
1461
|
-
resourceTypes
|
|
1462
|
-
});
|
|
1463
|
-
return {
|
|
1464
|
-
sendEvent: logTarget.emit,
|
|
1465
|
-
sendLog: logTarget.emit,
|
|
1466
|
-
reset: () => {
|
|
1467
|
-
logTarget.reset();
|
|
1468
|
-
clearInterval(heartbeatTimer);
|
|
1469
|
-
}
|
|
1470
|
-
};
|
|
1471
|
-
};
|
|
1472
|
-
|
|
1473
|
-
const timeoutError = () => new Error('request timeout');
|
|
1474
|
-
/**
|
|
1475
|
-
* @param {URL|RequestInfo} url
|
|
1476
|
-
* @param {RequestInit} options
|
|
1477
|
-
* @param {{responseType: 'json'|'text'}}
|
|
1478
|
-
*/
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
const retryRequest = (url, options = {}, {
|
|
1482
|
-
responseType = 'json',
|
|
1483
|
-
timeout = 6,
|
|
1484
|
-
retryTimes = 6
|
|
1485
|
-
} = {}) => new Promise((resolve, reject) => {
|
|
1486
|
-
setTimeout(() => reject(timeoutError()), timeout * 1000);
|
|
1487
|
-
fetch(url, options).then(response => {
|
|
1488
|
-
var _response$responseTyp;
|
|
1489
|
-
|
|
1490
|
-
return resolve(((_response$responseTyp = response[responseType]) === null || _response$responseTyp === void 0 ? void 0 : _response$responseTyp.call(response)) || response);
|
|
1491
|
-
}).catch(reject);
|
|
1492
|
-
}).catch(error => {
|
|
1493
|
-
console.log(error);
|
|
1494
|
-
|
|
1495
|
-
if (retryTimes > 0) {
|
|
1496
|
-
return retryRequest(url, options, {
|
|
1497
|
-
timeout,
|
|
1498
|
-
retryTimes: retryTimes - 1
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
return error;
|
|
1503
|
-
});
|
|
1504
|
-
|
|
1505
|
-
/* eslint-disable no-param-reassign */
|
|
1506
|
-
|
|
1507
|
-
const matchAll = (input, pattern) => {
|
|
1508
|
-
const flags = [pattern.global && 'g', pattern.ignoreCase && 'i', pattern.multiline && 'm'].filter(Boolean).join('');
|
|
1509
|
-
const clone = new RegExp(pattern, flags);
|
|
1510
|
-
return Array.from(function* () {
|
|
1511
|
-
let matched = true;
|
|
1512
|
-
|
|
1513
|
-
while (1) {
|
|
1514
|
-
matched = clone.exec(input);
|
|
1515
|
-
|
|
1516
|
-
if (!matched) {
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
yield matched;
|
|
1521
|
-
}
|
|
1522
|
-
}());
|
|
1523
|
-
};
|
|
1524
|
-
|
|
1525
|
-
const rewriteUrls = (manifest, sourceUrl) => manifest.replace(/((#EXT-X-MEDIA:.*URI=")([^"]*))|((#EXT-X-STREAM-INF.*\n)(.*)(?=\n))/g, (...matches) => [matches[2], matches[5], new URL(matches[3] || matches[6], sourceUrl)].filter(Boolean).join(''));
|
|
1526
|
-
|
|
1527
|
-
const filterHlsManifestQualities = (originalManifest, filter) => {
|
|
1528
|
-
if (!filter) {
|
|
1529
|
-
return;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
const manifest = `${originalManifest}\n`;
|
|
1533
|
-
const profiles = matchAll(manifest, /RESOLUTION=(\d+)x(\d+)/g).map(([, width, height]) => ({
|
|
1534
|
-
width: +width,
|
|
1535
|
-
height: +height
|
|
1536
|
-
}));
|
|
1537
|
-
const allowed = filter(profiles) || profiles;
|
|
1538
|
-
const newManifest = manifest.replace(/#EXT-X-STREAM-INF.*RESOLUTION=(\d+)x(\d+).*\n.*\n/g, (item, width, height) => allowed.some(p => p.width === +width && p.height === +height) ? item : '');
|
|
1539
|
-
return newManifest !== manifest && newManifest;
|
|
1540
|
-
};
|
|
1541
|
-
|
|
1542
|
-
const meetRestriction = (quality, {
|
|
1543
|
-
minHeight,
|
|
1544
|
-
maxHeight
|
|
1545
|
-
} = {}) => !(quality.height < minHeight || quality.height > maxHeight);
|
|
1546
|
-
|
|
1547
|
-
const selectHlsQualities = async (source, restrictions = {}) => {
|
|
1548
|
-
if (!isSafari() || !(restrictions.minHeight || restrictions.maxHeight)) {
|
|
1549
|
-
return source;
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
const selected = getSource(source, {
|
|
1553
|
-
preferManifestType: 'hls'
|
|
1554
|
-
});
|
|
1555
|
-
|
|
1556
|
-
if (!((selected === null || selected === void 0 ? void 0 : selected.type.toLowerCase()) === mimeTypes.hls)) {
|
|
1557
|
-
return source;
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
const filtered = filterHlsManifestQualities(await retryRequest(selected.src, {}, {
|
|
1561
|
-
responseType: 'text'
|
|
1562
|
-
}), items => items.filter(item => meetRestriction(item, restrictions)));
|
|
1563
|
-
|
|
1564
|
-
if (filtered) {
|
|
1565
|
-
return { ...selected,
|
|
1566
|
-
|
|
1567
|
-
/*
|
|
1568
|
-
Native Safari couldn't support blob .m3u8. and will throw MediaError: 4
|
|
1569
|
-
We find the hacky method: dataURI.
|
|
1570
|
-
By the way, bitmovin also use this form even user gives the blob URI.
|
|
1571
|
-
*/
|
|
1572
|
-
src: `data:application/x-mpegURL,${encodeURI(rewriteUrls(filtered, selected.src))}`
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
return source;
|
|
1577
|
-
};
|
|
1578
|
-
// for unit test
|
|
1579
|
-
|
|
1580
|
-
/* eslint-disable no-empty */
|
|
1581
|
-
const storageKey = 'playcraft-tab-lock';
|
|
1582
|
-
const lockRenewTime = 3000;
|
|
1583
|
-
|
|
321
|
+
* This method creates analytics module instance and return it.
|
|
322
|
+
* @param createAnalyticsParameter
|
|
323
|
+
* @returns Analytics module instance
|
|
324
|
+
*/
|
|
325
|
+
const createAnalytics = ({ video, onPlaylogFired, modulesConfig }) => {
|
|
326
|
+
const { token, eventEndpoint, heartbeatEndpoint, ...analyticsConfig } = retrieveModuleConfig(modulesConfig, "analytics");
|
|
327
|
+
const resourceId = analyticsConfig.resourceId || analyticsConfig.resource_id || "";
|
|
328
|
+
const resourceType = analyticsConfig.resourceType || analyticsConfig.resource_type || "";
|
|
329
|
+
const userId = analyticsConfig.userId || analyticsConfig.user_id || "";
|
|
330
|
+
const sessionId = analyticsConfig.sessionId || analyticsConfig.session_id || uuidv4_default();
|
|
331
|
+
const deviceId = analyticsConfig.deviceId || analyticsConfig.device_id || getDefaultDeviceId();
|
|
332
|
+
const eventEndpointUrl = eventEndpoint || process.env.BV_ONE_EVENT_API + "/v1/events";
|
|
333
|
+
const heartbeatEndpointUrl = heartbeatEndpoint || process.env.BV_ONE_EVENT_HEARTBEAT_API + "/v1/events";
|
|
334
|
+
if (!resourceType) console.warn("Please check resource_type in your modulesConfig. Otherwise, the player will send default resource_type, ex: RESOURCE_TYPE_VOD_EVENT or RESOURCE_TYPE_LIVE_EVENT !");
|
|
335
|
+
const logTarget = mapLogEvents$1({
|
|
336
|
+
video,
|
|
337
|
+
playerName: "shaka",
|
|
338
|
+
version: process.env.npm_package_version
|
|
339
|
+
});
|
|
340
|
+
logTarget.all((type, data) => {
|
|
341
|
+
const payload = {
|
|
342
|
+
event_type: "playback_log",
|
|
343
|
+
event_name: logEventNames$1[type] || type,
|
|
344
|
+
event_content: JSON.stringify({
|
|
345
|
+
session_id: sessionId,
|
|
346
|
+
...data,
|
|
347
|
+
...getDeviceInfo(),
|
|
348
|
+
...analyticsConfig,
|
|
349
|
+
resource_id: resourceId,
|
|
350
|
+
resource_type: resourceType || resourceTypes[data.resource_type || "vod"],
|
|
351
|
+
user_id: userId,
|
|
352
|
+
device_id: deviceId
|
|
353
|
+
}),
|
|
354
|
+
event_time: (/* @__PURE__ */ new Date(data.system_time * 1e3)).toISOString()
|
|
355
|
+
};
|
|
356
|
+
onPlaylogFired?.(payload.event_name, data);
|
|
357
|
+
if (!token) return;
|
|
358
|
+
retryWithBackoff_default({ fn: () => axios.post(eventEndpointUrl, payload, { headers: { "Kks-Bv-Token": token } }) });
|
|
359
|
+
});
|
|
360
|
+
const heartbeatTimer = heartbeat_default({
|
|
361
|
+
token,
|
|
362
|
+
sessionId,
|
|
363
|
+
resourceId,
|
|
364
|
+
userId,
|
|
365
|
+
deviceId,
|
|
366
|
+
endpoint: heartbeatEndpointUrl,
|
|
367
|
+
rawData: {
|
|
368
|
+
analyticsConfig,
|
|
369
|
+
logTarget
|
|
370
|
+
},
|
|
371
|
+
resourceTypes
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
sendEvent: logTarget.emit,
|
|
375
|
+
sendLog: logTarget.emit,
|
|
376
|
+
reset: () => {
|
|
377
|
+
logTarget.reset();
|
|
378
|
+
clearInterval(heartbeatTimer);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/util/ensureTabLock.js
|
|
385
|
+
const storageKey = "playcraft-tab-lock";
|
|
386
|
+
const lockRenewTime = 3e3;
|
|
1584
387
|
const ensureTabLock = () => {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
localStorage[storageKey] = {
|
|
1617
|
-
expireTime: Date.now() - 1
|
|
1618
|
-
};
|
|
1619
|
-
};
|
|
1620
|
-
|
|
1621
|
-
window.addEventListener('beforeunload', releaseLock);
|
|
1622
|
-
window.addEventListener('unload', releaseLock);
|
|
1623
|
-
return releaseLock;
|
|
1624
|
-
};
|
|
1625
|
-
|
|
1626
|
-
let lastError = '';
|
|
388
|
+
let saved = {};
|
|
389
|
+
try {
|
|
390
|
+
saved = JSON.parse(localStorage[storageKey]);
|
|
391
|
+
} catch (e) {
|
|
392
|
+
console.log("Can read saved data for tab lock.", e);
|
|
393
|
+
}
|
|
394
|
+
const { expireTime } = saved;
|
|
395
|
+
if (Date.now() <= expireTime) return;
|
|
396
|
+
const id = uuidv4_default();
|
|
397
|
+
const renewLock = () => {
|
|
398
|
+
localStorage[storageKey] = JSON.stringify({
|
|
399
|
+
id,
|
|
400
|
+
expireTime: Date.now() + lockRenewTime * 3
|
|
401
|
+
});
|
|
402
|
+
};
|
|
403
|
+
const renewInterval = setInterval(renewLock, lockRenewTime);
|
|
404
|
+
const releaseLock = () => {
|
|
405
|
+
clearInterval(renewInterval);
|
|
406
|
+
window.removeEventListener("beforeunload", releaseLock);
|
|
407
|
+
window.removeEventListener("unload", releaseLock);
|
|
408
|
+
localStorage[storageKey] = { expireTime: Date.now() - 1 };
|
|
409
|
+
};
|
|
410
|
+
window.addEventListener("beforeunload", releaseLock);
|
|
411
|
+
window.addEventListener("unload", releaseLock);
|
|
412
|
+
return releaseLock;
|
|
413
|
+
};
|
|
414
|
+
var ensureTabLock_default = ensureTabLock;
|
|
415
|
+
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/util/addSentry.js
|
|
418
|
+
let lastError = "";
|
|
1627
419
|
const defaultOptions = {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
document.body.append(script);
|
|
1660
|
-
};
|
|
1661
|
-
|
|
420
|
+
ignoreErrors: ["AbortError: The play() request was interrupted", "i.context.logger"],
|
|
421
|
+
beforeSend: (event) => {
|
|
422
|
+
if (lastError.message === event.exception.values[0].value && Date.now() - lastError.date < 1e4) {
|
|
423
|
+
lastError.date = Date.now();
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
lastError = {
|
|
427
|
+
date: Date.now(),
|
|
428
|
+
message: event.exception.values[0].value
|
|
429
|
+
};
|
|
430
|
+
return event;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const addSentry = ({ key, ...options }) => {
|
|
434
|
+
const script = document.createElement("script");
|
|
435
|
+
script.crossorigin = "anonymous";
|
|
436
|
+
script.src = `https://js.sentry-cdn.com/${key}.min.js`;
|
|
437
|
+
script.addEventListener("load", () => {
|
|
438
|
+
window.Sentry.onLoad(() => {
|
|
439
|
+
window.Sentry.init({
|
|
440
|
+
...defaultOptions,
|
|
441
|
+
...options
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
}, { once: true });
|
|
445
|
+
document.body.append(script);
|
|
446
|
+
};
|
|
447
|
+
var addSentry_default = addSentry;
|
|
448
|
+
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/util/handleIOSHeadphonesDisconnection.js
|
|
1662
451
|
/**
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
const
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
}
|
|
1743
|
-
};
|
|
1744
|
-
|
|
1745
|
-
const ewma = halfLife => {
|
|
1746
|
-
let alpha = Math.exp(Math.log(0.5) / halfLife);
|
|
1747
|
-
let estimate = 0;
|
|
1748
|
-
let totalWeight = 0;
|
|
1749
|
-
return {
|
|
1750
|
-
updateAlpha: newHalfLife => {
|
|
1751
|
-
alpha = Math.exp(Math.log(0.5) / newHalfLife);
|
|
1752
|
-
},
|
|
1753
|
-
sample: (weight, value) => {
|
|
1754
|
-
const adjAlpha = alpha ** weight;
|
|
1755
|
-
const newEstimate = value * (1 - adjAlpha) + adjAlpha * estimate;
|
|
1756
|
-
|
|
1757
|
-
if (!Number.isNaN(newEstimate)) {
|
|
1758
|
-
estimate = newEstimate;
|
|
1759
|
-
totalWeight += weight;
|
|
1760
|
-
}
|
|
1761
|
-
},
|
|
1762
|
-
getEstimate: () => {
|
|
1763
|
-
const zeroFactor = 1 - alpha ** totalWeight;
|
|
1764
|
-
return estimate / zeroFactor;
|
|
1765
|
-
}
|
|
1766
|
-
};
|
|
1767
|
-
};
|
|
1768
|
-
|
|
1769
|
-
/* eslint-disable no-param-reassign */
|
|
1770
|
-
|
|
452
|
+
* @description Unplugging / disconnecting headphones will pause video in iOS,
|
|
453
|
+
* and in some iOS versions, video is paused without firing a pause event.
|
|
454
|
+
* Pause the video if paused by iOS in this case.
|
|
455
|
+
* @param {HTMLMediaElement} video
|
|
456
|
+
*/
|
|
457
|
+
const handleIOSHeadphonesDisconnection = ({ maxStuckSeconds = 1 } = {}) => {
|
|
458
|
+
const video = document.querySelector("video");
|
|
459
|
+
if (video && getOS().name === "iOS") {
|
|
460
|
+
let playState = { playing: false };
|
|
461
|
+
const saveState = ({ playing = playState.playing } = {}) => {
|
|
462
|
+
playState = {
|
|
463
|
+
playing,
|
|
464
|
+
...playing && { lastTimeUpdate: Date.now() }
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
video.addEventListener("pause", () => {
|
|
468
|
+
playState = { playing: false };
|
|
469
|
+
});
|
|
470
|
+
video.addEventListener("seeking", () => {
|
|
471
|
+
playState = { playing: false };
|
|
472
|
+
});
|
|
473
|
+
video.addEventListener("waiting", () => {
|
|
474
|
+
playState = { playing: false };
|
|
475
|
+
});
|
|
476
|
+
video.addEventListener("webkitpresentationmodechanged", () => {
|
|
477
|
+
playState = {
|
|
478
|
+
playing: false,
|
|
479
|
+
pauseDetection: Date.now() + 5e3
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
video.addEventListener("timeupdate", () => {
|
|
483
|
+
if (!(video.currentTime >= 0 && video.currentTime <= Date.now())) return;
|
|
484
|
+
if (!video.paused) {
|
|
485
|
+
const delta = Date.now() - playState.lastTimeUpdate;
|
|
486
|
+
playState.lastTimeUpdate = Date.now();
|
|
487
|
+
if (delta > 0 && delta < 1e3) playState.playing = true;
|
|
488
|
+
}
|
|
489
|
+
saveState({ playing: !video.paused });
|
|
490
|
+
});
|
|
491
|
+
video.addEventListener("ratechange", saveState);
|
|
492
|
+
setInterval(() => {
|
|
493
|
+
if (video.paused || !playState.playing || playState.pauseDetection >= Date.now()) return;
|
|
494
|
+
if ((Date.now() - playState.lastTimeUpdate) / 1e3 >= maxStuckSeconds) {
|
|
495
|
+
console.log("Video is not playing, pause to workaround iOS unpluging headphones");
|
|
496
|
+
video.pause();
|
|
497
|
+
}
|
|
498
|
+
}, 200);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
var handleIOSHeadphonesDisconnection_default = handleIOSHeadphonesDisconnection;
|
|
502
|
+
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region src/util/ewma.js
|
|
505
|
+
const ewma = (halfLife) => {
|
|
506
|
+
let alpha = Math.exp(Math.log(.5) / halfLife);
|
|
507
|
+
let estimate = 0;
|
|
508
|
+
let totalWeight = 0;
|
|
509
|
+
return {
|
|
510
|
+
updateAlpha: (newHalfLife) => {
|
|
511
|
+
alpha = Math.exp(Math.log(.5) / newHalfLife);
|
|
512
|
+
},
|
|
513
|
+
sample: (weight, value) => {
|
|
514
|
+
const adjAlpha = alpha ** weight;
|
|
515
|
+
const newEstimate = value * (1 - adjAlpha) + adjAlpha * estimate;
|
|
516
|
+
if (!Number.isNaN(newEstimate)) {
|
|
517
|
+
estimate = newEstimate;
|
|
518
|
+
totalWeight += weight;
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
getEstimate: () => {
|
|
522
|
+
const zeroFactor = 1 - alpha ** totalWeight;
|
|
523
|
+
return estimate / zeroFactor;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
var ewma_default = ewma;
|
|
528
|
+
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/playerCore/latencyManager.js
|
|
1771
531
|
const getBufferedAhead = (mediaSource, video) => {
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
return 0;
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
return Math.min(...items.map(buffered => Math.max(video.currentTime, ...Array.from({
|
|
1779
|
-
length: (buffered === null || buffered === void 0 ? void 0 : buffered.length) || 0
|
|
1780
|
-
}, (_, i) => buffered.end(i))))) - video.currentTime;
|
|
532
|
+
const items = needNativeHls() ? [video.buffered] : Array.from(mediaSource.sourceBuffers, (item) => item.buffered);
|
|
533
|
+
if (items.length < 1) return 0;
|
|
534
|
+
return Math.min(...items.map((buffered) => Math.max(video.currentTime, ...Array.from({ length: buffered?.length || 0 }, (_, i) => buffered.end(i))))) - video.currentTime;
|
|
1781
535
|
};
|
|
1782
|
-
|
|
1783
536
|
const constants = {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
};
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
var _player$getPresentati;
|
|
1794
|
-
|
|
1795
|
-
return {
|
|
1796
|
-
systemTime: Date.now(),
|
|
1797
|
-
playbackTime: video.currentTime + ((player === null || player === void 0 ? void 0 : (_player$getPresentati = player.getPresentationStartTimeAsDate()) === null || _player$getPresentati === void 0 ? void 0 : _player$getPresentati.getTime()) || 0),
|
|
1798
|
-
playbackRate: video.playbackRate,
|
|
1799
|
-
bufferedAhead: getBufferedAhead(player.mediaSource, video),
|
|
1800
|
-
downloadSpeed: bufferLength.getEstimate()
|
|
1801
|
-
};
|
|
1802
|
-
};
|
|
1803
|
-
|
|
1804
|
-
const manageLatencySafariNative = ({
|
|
1805
|
-
player,
|
|
1806
|
-
video,
|
|
1807
|
-
bufferLength
|
|
1808
|
-
}, options) => {
|
|
1809
|
-
let lastBuffered;
|
|
1810
|
-
let remainingAttempts = 2; // TODO assume no handle start-over
|
|
1811
|
-
|
|
1812
|
-
const updateIntervalId = setInterval(() => {
|
|
1813
|
-
var _options$onUpdate;
|
|
1814
|
-
|
|
1815
|
-
const info = getInfo({
|
|
1816
|
-
player,
|
|
1817
|
-
video,
|
|
1818
|
-
bufferLength
|
|
1819
|
-
});
|
|
1820
|
-
bufferLength.sample(1, Math.max(0, info.bufferedAhead));
|
|
1821
|
-
(_options$onUpdate = options.onUpdate) === null || _options$onUpdate === void 0 ? void 0 : _options$onUpdate.call(options, { ...options,
|
|
1822
|
-
...info
|
|
1823
|
-
}); // Magic numbers to be tuned further
|
|
1824
|
-
|
|
1825
|
-
if (video.paused) {
|
|
1826
|
-
remainingAttempts = 2;
|
|
1827
|
-
} else if (info.bufferedAhead > 8) {
|
|
1828
|
-
console.debug(`Jump`, info.bufferedAhead, lastBuffered);
|
|
1829
|
-
video.currentTime += info.bufferedAhead;
|
|
1830
|
-
} else if (remainingAttempts > 0 && // by default target buffer length * factor is 2.1
|
|
1831
|
-
info.bufferedAhead > options.targetBufferLength * constants.hlsHighBufferFactor && // Safari updates seek range when a segment is completely added
|
|
1832
|
-
info.bufferedAhead > lastBuffered + options.hlsSegmentLength / 4) {
|
|
1833
|
-
console.debug(`Seek ahead`, info.bufferedAhead, lastBuffered);
|
|
1834
|
-
video.currentTime += 10;
|
|
1835
|
-
remainingAttempts -= 1;
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
if (bufferLength.getEstimate() > options.targetBufferLength * constants.hlsHighBufferFactor) {
|
|
1839
|
-
remainingAttempts = 2;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
lastBuffered = info.bufferedAhead;
|
|
1843
|
-
}, options.updateInterval);
|
|
1844
|
-
return () => clearInterval(updateIntervalId);
|
|
1845
|
-
};
|
|
1846
|
-
|
|
1847
|
-
const manageLatencyMse = ({
|
|
1848
|
-
player,
|
|
1849
|
-
video,
|
|
1850
|
-
bufferLength
|
|
1851
|
-
}, options) => {
|
|
1852
|
-
let lastPlaybackTime = 0;
|
|
1853
|
-
player.configure({
|
|
1854
|
-
manifest: {
|
|
1855
|
-
dash: {
|
|
1856
|
-
ignoreSuggestedPresentationDelay: true
|
|
1857
|
-
}
|
|
1858
|
-
},
|
|
1859
|
-
streaming: {
|
|
1860
|
-
lowLatencyMode: true,
|
|
1861
|
-
bufferingGoal: 10.0,
|
|
1862
|
-
rebufferingGoal: 0.01,
|
|
1863
|
-
updateIntervalSeconds: 0.2,
|
|
1864
|
-
gapDetectionThreshold: 0.001,
|
|
1865
|
-
inaccurateManifestTolerance: 1,
|
|
1866
|
-
retryParameters: {
|
|
1867
|
-
baseDelay: 50,
|
|
1868
|
-
backoffFactor: 1.2,
|
|
1869
|
-
fuzzFactor: 0,
|
|
1870
|
-
maxAttempts: 66
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
});
|
|
1874
|
-
const updateIntervalId = setInterval(() => {
|
|
1875
|
-
var _options$onUpdate2;
|
|
1876
|
-
|
|
1877
|
-
const info = getInfo({
|
|
1878
|
-
player,
|
|
1879
|
-
video,
|
|
1880
|
-
bufferLength
|
|
1881
|
-
});
|
|
1882
|
-
bufferLength.sample(1, Math.max(0, info.bufferedAhead));
|
|
1883
|
-
(_options$onUpdate2 = options.onUpdate) === null || _options$onUpdate2 === void 0 ? void 0 : _options$onUpdate2.call(options, { ...options,
|
|
1884
|
-
...info
|
|
1885
|
-
});
|
|
1886
|
-
|
|
1887
|
-
if (!player || !video || video.currentTime < lastPlaybackTime + 0.08) {
|
|
1888
|
-
return;
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
lastPlaybackTime = video.currentTime;
|
|
1892
|
-
|
|
1893
|
-
if (info.bufferedAhead > 5) {
|
|
1894
|
-
video.currentTime = video.currentTime + info.bufferedAhead - 2.5;
|
|
1895
|
-
console.debug(`Buffer abundant, seek from ${video.currentTime} to ${video.currentTime}`);
|
|
1896
|
-
return;
|
|
1897
|
-
} // start speedup at 120%, stop when under 100%, to avoid frequent speed changes when near 100%
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
const bufferCap = +options.targetBufferLength * (info.playbackRate > 1 ? 1 : constants.dashHighBufferFactor);
|
|
1901
|
-
const rate = bufferLength.getEstimate() > bufferCap && options.speedup ? +options.speedup : 0;
|
|
1902
|
-
|
|
1903
|
-
if ((video === null || video === void 0 ? void 0 : video.currentTime) > 0) {
|
|
1904
|
-
video.playbackRate = 1 + rate / 100;
|
|
1905
|
-
}
|
|
1906
|
-
}, options.updateInterval);
|
|
1907
|
-
return () => clearInterval(updateIntervalId);
|
|
1908
|
-
};
|
|
1909
|
-
|
|
1910
|
-
const latencyManager = (player, video) => {
|
|
1911
|
-
const currentOptions = {
|
|
1912
|
-
speedup: 7,
|
|
1913
|
-
targetBufferLength: 1.5,
|
|
1914
|
-
hlsSegmentLength: 4,
|
|
1915
|
-
updateInterval: 100
|
|
1916
|
-
};
|
|
1917
|
-
let stop; // TODO reset on updateInterval change
|
|
1918
|
-
|
|
1919
|
-
const bufferLength = ewma(1500 / currentOptions.updateInterval);
|
|
1920
|
-
|
|
1921
|
-
const configure = config => {
|
|
1922
|
-
if (!config) {
|
|
1923
|
-
return getInfo({
|
|
1924
|
-
player,
|
|
1925
|
-
video,
|
|
1926
|
-
bufferLength
|
|
1927
|
-
});
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
if ('enabled' in config && Boolean(config.enabled) !== currentOptions.enabled) {
|
|
1931
|
-
var _stop;
|
|
1932
|
-
|
|
1933
|
-
(_stop = stop) === null || _stop === void 0 ? void 0 : _stop();
|
|
1934
|
-
|
|
1935
|
-
if (config.enabled) {
|
|
1936
|
-
stop = (needNativeHls() ? manageLatencySafariNative : manageLatencyMse)({
|
|
1937
|
-
player,
|
|
1938
|
-
video,
|
|
1939
|
-
bufferLength
|
|
1940
|
-
}, currentOptions);
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
Object.assign(currentOptions, config);
|
|
1945
|
-
if ('segmentTimestampOffset' in config) window.segmentTimestampOffset = config.segmentTimestampOffset;
|
|
1946
|
-
};
|
|
1947
|
-
|
|
1948
|
-
return {
|
|
1949
|
-
configure
|
|
1950
|
-
};
|
|
1951
|
-
};
|
|
1952
|
-
|
|
1953
|
-
/**
|
|
1954
|
-
* Parses an XML duration string.
|
|
1955
|
-
* Negative values are not supported. Years and months are treated as exactly
|
|
1956
|
-
* 365 and 30 days respectively.
|
|
1957
|
-
* @param {string} durationString The duration string, e.g., "PT1H3M43.2S",
|
|
1958
|
-
* which means 1 hour, 3 minutes, and 43.2 seconds.
|
|
1959
|
-
* @return {?number} The parsed duration in seconds on success; otherwise,
|
|
1960
|
-
* return null.
|
|
1961
|
-
* @see {@link http://www.datypic.com/sc/xsd/t-xsd_duration.html}
|
|
1962
|
-
*/
|
|
1963
|
-
const parseDuration = durationString => {
|
|
1964
|
-
if (!durationString) {
|
|
1965
|
-
return null;
|
|
1966
|
-
}
|
|
1967
|
-
|
|
1968
|
-
const re = '^P(?:([0-9]*)Y)?(?:([0-9]*)M)?(?:([0-9]*)D)?' + '(?:T(?:([0-9]*)H)?(?:([0-9]*)M)?(?:([0-9.]*)S)?)?$';
|
|
1969
|
-
const matches = new RegExp(re).exec(durationString);
|
|
1970
|
-
|
|
1971
|
-
if (!matches) {
|
|
1972
|
-
console.warning('Invalid duration string:', durationString);
|
|
1973
|
-
return null;
|
|
1974
|
-
} // Note: Number(null) == 0 but Number(undefined) == NaN.
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
const years = Number(matches[1] || null);
|
|
1978
|
-
const months = Number(matches[2] || null);
|
|
1979
|
-
const days = Number(matches[3] || null);
|
|
1980
|
-
const hours = Number(matches[4] || null);
|
|
1981
|
-
const minutes = Number(matches[5] || null);
|
|
1982
|
-
const seconds = Number(matches[6] || null); // Assume a year always has 365 days and a month always has 30 days.
|
|
1983
|
-
|
|
1984
|
-
const d = 60 * 60 * 24 * 365 * years + 60 * 60 * 24 * 30 * months + 60 * 60 * 24 * days + 60 * 60 * hours + 60 * minutes + seconds;
|
|
1985
|
-
return Number.isFinite(d) ? d : null;
|
|
1986
|
-
};
|
|
1987
|
-
|
|
1988
|
-
const normalize = (doc, template, period) => {
|
|
1989
|
-
const segmentDuration = parseFloat(template.getAttribute('duration'), 10);
|
|
1990
|
-
|
|
1991
|
-
if (!segmentDuration) {
|
|
1992
|
-
return;
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
const timescale = parseFloat(template.getAttribute('timescale'), 10);
|
|
1996
|
-
const periodDuration = parseDuration(period.getAttribute('duration'));
|
|
1997
|
-
const item = doc.createElement('S');
|
|
1998
|
-
item.setAttribute('d', segmentDuration);
|
|
1999
|
-
item.setAttribute('r', Math.ceil(periodDuration * timescale / segmentDuration) - 1);
|
|
2000
|
-
const timeline = doc.createElement('SegmentTimeline');
|
|
2001
|
-
timeline.appendChild(item);
|
|
2002
|
-
template.appendChild(timeline);
|
|
2003
|
-
template.removeAttribute('duration');
|
|
2004
|
-
};
|
|
2005
|
-
|
|
2006
|
-
const fixDashManifest = (data, {
|
|
2007
|
-
minTimeShiftBufferDepth
|
|
2008
|
-
} = {}) => {
|
|
2009
|
-
const doc = new DOMParser().parseFromString(data, 'text/xml'); // only dynamic manifest needs timeShiftBufferDepth
|
|
2010
|
-
|
|
2011
|
-
const root = doc.children[0];
|
|
2012
|
-
|
|
2013
|
-
if (root.getAttribute('type') === 'dynamic') {
|
|
2014
|
-
if (root.getAttribute('timeShiftBufferDepth') < minTimeShiftBufferDepth) {
|
|
2015
|
-
root.setAttribute('timeShiftBufferDepth', minTimeShiftBufferDepth);
|
|
2016
|
-
}
|
|
2017
|
-
} else if (root.hasAttribute('timeShiftBufferDepth')) {
|
|
2018
|
-
root.removeAttribute('timeShiftBufferDepth');
|
|
2019
|
-
} // workaround multi-period segment template bug, normalize to segment timelines
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
if (doc.querySelectorAll('Period').length > 1) {
|
|
2023
|
-
Array.from(doc.querySelectorAll('Period')).forEach(period => {
|
|
2024
|
-
Array.from(period.querySelectorAll('SegmentTemplate')).forEach(template => normalize(doc, template, period));
|
|
2025
|
-
});
|
|
2026
|
-
}
|
|
2027
|
-
|
|
2028
|
-
window.manifestDoc = doc;
|
|
2029
|
-
return new XMLSerializer().serializeToString(doc);
|
|
2030
|
-
};
|
|
2031
|
-
|
|
2032
|
-
const loadScript = url => new Promise(resolve => {
|
|
2033
|
-
const script = Object.assign(document.createElement('script'), {
|
|
2034
|
-
async: true,
|
|
2035
|
-
src: url
|
|
2036
|
-
});
|
|
2037
|
-
script.addEventListener('load', resolve);
|
|
2038
|
-
document.body.appendChild(script);
|
|
2039
|
-
});
|
|
2040
|
-
|
|
2041
|
-
/* eslint-disable no-empty */
|
|
2042
|
-
const SENDER_URL = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
|
|
2043
|
-
|
|
2044
|
-
const getContext = () => {
|
|
2045
|
-
var _cast$framework;
|
|
2046
|
-
|
|
2047
|
-
return window.cast && ((_cast$framework = cast.framework) === null || _cast$framework === void 0 ? void 0 : _cast$framework.CastContext.getInstance());
|
|
2048
|
-
};
|
|
2049
|
-
/* global chrome, cast */
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
const ensureSenderFramework = () => {
|
|
2053
|
-
if (window.cast && cast.framework && window.chrome && chrome.cast) {
|
|
2054
|
-
return Promise.resolve(getContext());
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
if (!window.loadSenderFramework) {
|
|
2058
|
-
window.loadSenderFramework = new Promise((resolve, reject) => {
|
|
2059
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
2060
|
-
window.__onGCastApiAvailable = isAvailable => {
|
|
2061
|
-
if (isAvailable) {
|
|
2062
|
-
resolve(getContext());
|
|
2063
|
-
} else {
|
|
2064
|
-
reject();
|
|
2065
|
-
}
|
|
2066
|
-
};
|
|
2067
|
-
|
|
2068
|
-
loadScript(SENDER_URL);
|
|
2069
|
-
});
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
return window.loadSenderFramework;
|
|
2073
|
-
};
|
|
2074
|
-
|
|
2075
|
-
const initSender = async ({
|
|
2076
|
-
appId,
|
|
2077
|
-
...options
|
|
2078
|
-
}) => {
|
|
2079
|
-
const context = await ensureSenderFramework();
|
|
2080
|
-
context.setOptions({
|
|
2081
|
-
receiverApplicationId: appId,
|
|
2082
|
-
resumeSavedSession: true,
|
|
2083
|
-
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
|
|
2084
|
-
...options
|
|
2085
|
-
});
|
|
2086
|
-
};
|
|
2087
|
-
|
|
2088
|
-
const subscribeCastState = handleStateChange => {
|
|
2089
|
-
const waitFramework = ensureSenderFramework().then(() => {
|
|
2090
|
-
const name = cast.framework.CastContextEventType.CAST_STATE_CHANGED;
|
|
2091
|
-
const context = getContext();
|
|
2092
|
-
|
|
2093
|
-
const onChange = ({
|
|
2094
|
-
castState
|
|
2095
|
-
}) => handleStateChange(castState);
|
|
2096
|
-
|
|
2097
|
-
handleStateChange(context.getCastState());
|
|
2098
|
-
context.addEventListener(name, onChange);
|
|
2099
|
-
return () => context.removeEventListener(name, onChange);
|
|
2100
|
-
});
|
|
2101
|
-
return () => waitFramework.then(removeListeners => removeListeners());
|
|
2102
|
-
};
|
|
2103
|
-
|
|
2104
|
-
const disconnect = async () => {
|
|
2105
|
-
await ensureSenderFramework();
|
|
2106
|
-
|
|
2107
|
-
try {
|
|
2108
|
-
getContext().endCurrentSession(true);
|
|
2109
|
-
} catch (e) {
|
|
2110
|
-
// eslint-disable-next-line no-console
|
|
2111
|
-
console.log(e);
|
|
2112
|
-
}
|
|
2113
|
-
};
|
|
2114
|
-
|
|
2115
|
-
const loadMedia = ({
|
|
2116
|
-
source,
|
|
2117
|
-
contentType,
|
|
2118
|
-
contentId: paramContentId,
|
|
2119
|
-
sourceType,
|
|
2120
|
-
apiConfig,
|
|
2121
|
-
customData
|
|
2122
|
-
}) => {
|
|
2123
|
-
var _sourceInfo$drm, _sourceInfo$drm2, _sourceInfo$drm2$wide, _session$getMediaSess, _currentMedia$customD;
|
|
2124
|
-
|
|
2125
|
-
const session = getContext().getCurrentSession();
|
|
2126
|
-
|
|
2127
|
-
if (!session) {
|
|
2128
|
-
return 'receiver_unavailable';
|
|
2129
|
-
} // not connected actually, but there is no error code for the situation
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
const sourceInfo = source && getSource(source);
|
|
2133
|
-
const contentId = paramContentId || (sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.src);
|
|
2134
|
-
const drm = (sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.drm) && { ...((_sourceInfo$drm = sourceInfo.drm) === null || _sourceInfo$drm === void 0 ? void 0 : _sourceInfo$drm.widevine),
|
|
2135
|
-
licenseUrl: (_sourceInfo$drm2 = sourceInfo.drm) === null || _sourceInfo$drm2 === void 0 ? void 0 : (_sourceInfo$drm2$wide = _sourceInfo$drm2.widevine) === null || _sourceInfo$drm2$wide === void 0 ? void 0 : _sourceInfo$drm2$wide.licenseUri
|
|
2136
|
-
};
|
|
2137
|
-
const request = new chrome.cast.media.LoadRequest(Object.assign(new chrome.cast.media.MediaInfo(contentId), {
|
|
2138
|
-
contentId,
|
|
2139
|
-
contentID: contentId,
|
|
2140
|
-
customData: {
|
|
2141
|
-
itemType: contentType,
|
|
2142
|
-
...apiConfig,
|
|
2143
|
-
customHeaders: apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.headers,
|
|
2144
|
-
customQuery: apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.params,
|
|
2145
|
-
mediaSource: sourceType,
|
|
2146
|
-
...(drm && {
|
|
2147
|
-
drm
|
|
2148
|
-
}),
|
|
2149
|
-
...customData
|
|
2150
|
-
},
|
|
2151
|
-
metadata: new chrome.cast.media.GenericMediaMetadata()
|
|
2152
|
-
})); // TODO playback start time: request.currentTime
|
|
2153
|
-
|
|
2154
|
-
const currentMedia = (session === null || session === void 0 ? void 0 : (_session$getMediaSess = session.getMediaSession()) === null || _session$getMediaSess === void 0 ? void 0 : _session$getMediaSess.media) || {}; // no need to load if already playing TODO extrack checking
|
|
2155
|
-
|
|
2156
|
-
if (contentId === currentMedia.contentId && contentType === ((_currentMedia$customD = currentMedia.customData) === null || _currentMedia$customD === void 0 ? void 0 : _currentMedia$customD.itemType)) {
|
|
2157
|
-
return Promise.resolve();
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
return session.loadMedia(request).catch(e => console.log(e));
|
|
2161
|
-
}; // options: {contentId, customData: {...}}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
const linkCast = ({
|
|
2165
|
-
onChange,
|
|
2166
|
-
...options
|
|
2167
|
-
}) => subscribeCastState(state => {
|
|
2168
|
-
if (state === 'CONNECTED') {
|
|
2169
|
-
loadMedia(options); // TODO refactor the interface
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(state);
|
|
537
|
+
hlsHighBufferFactor: 1.4,
|
|
538
|
+
dashHighBufferFactor: 1.2
|
|
539
|
+
};
|
|
540
|
+
const getInfo = ({ video, player, bufferLength }) => ({
|
|
541
|
+
systemTime: Date.now(),
|
|
542
|
+
playbackTime: video.currentTime + (player?.getPresentationStartTimeAsDate()?.getTime() || 0),
|
|
543
|
+
playbackRate: video.playbackRate,
|
|
544
|
+
bufferedAhead: getBufferedAhead(player.mediaSource, video),
|
|
545
|
+
downloadSpeed: bufferLength.getEstimate()
|
|
2173
546
|
});
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
547
|
+
const manageLatencySafariNative = ({ player, video, bufferLength }, options) => {
|
|
548
|
+
let lastBuffered;
|
|
549
|
+
let remainingAttempts = 2;
|
|
550
|
+
const updateIntervalId = setInterval(() => {
|
|
551
|
+
const info = getInfo({
|
|
552
|
+
player,
|
|
553
|
+
video,
|
|
554
|
+
bufferLength
|
|
555
|
+
});
|
|
556
|
+
bufferLength.sample(1, Math.max(0, info.bufferedAhead));
|
|
557
|
+
options.onUpdate?.({
|
|
558
|
+
...options,
|
|
559
|
+
...info
|
|
560
|
+
});
|
|
561
|
+
if (video.paused) remainingAttempts = 2;
|
|
562
|
+
else if (info.bufferedAhead > 8) {
|
|
563
|
+
console.debug(`Jump`, info.bufferedAhead, lastBuffered);
|
|
564
|
+
video.currentTime += info.bufferedAhead;
|
|
565
|
+
} else if (remainingAttempts > 0 && info.bufferedAhead > options.targetBufferLength * constants.hlsHighBufferFactor && info.bufferedAhead > lastBuffered + options.hlsSegmentLength / 4) {
|
|
566
|
+
console.debug(`Seek ahead`, info.bufferedAhead, lastBuffered);
|
|
567
|
+
video.currentTime += 10;
|
|
568
|
+
remainingAttempts -= 1;
|
|
569
|
+
}
|
|
570
|
+
if (bufferLength.getEstimate() > options.targetBufferLength * constants.hlsHighBufferFactor) remainingAttempts = 2;
|
|
571
|
+
lastBuffered = info.bufferedAhead;
|
|
572
|
+
}, options.updateInterval);
|
|
573
|
+
return () => clearInterval(updateIntervalId);
|
|
574
|
+
};
|
|
575
|
+
const manageLatencyMse = ({ player, video, bufferLength }, options) => {
|
|
576
|
+
let lastPlaybackTime = 0;
|
|
577
|
+
player.configure({
|
|
578
|
+
manifest: { dash: { ignoreSuggestedPresentationDelay: true } },
|
|
579
|
+
streaming: {
|
|
580
|
+
lowLatencyMode: true,
|
|
581
|
+
bufferingGoal: 10,
|
|
582
|
+
rebufferingGoal: .01,
|
|
583
|
+
updateIntervalSeconds: .2,
|
|
584
|
+
gapDetectionThreshold: .001,
|
|
585
|
+
inaccurateManifestTolerance: 1,
|
|
586
|
+
retryParameters: {
|
|
587
|
+
baseDelay: 50,
|
|
588
|
+
backoffFactor: 1.2,
|
|
589
|
+
fuzzFactor: 0,
|
|
590
|
+
maxAttempts: 66
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
const updateIntervalId = setInterval(() => {
|
|
595
|
+
const info = getInfo({
|
|
596
|
+
player,
|
|
597
|
+
video,
|
|
598
|
+
bufferLength
|
|
599
|
+
});
|
|
600
|
+
bufferLength.sample(1, Math.max(0, info.bufferedAhead));
|
|
601
|
+
options.onUpdate?.({
|
|
602
|
+
...options,
|
|
603
|
+
...info
|
|
604
|
+
});
|
|
605
|
+
if (!player || !video || video.currentTime < lastPlaybackTime + .08) return;
|
|
606
|
+
lastPlaybackTime = video.currentTime;
|
|
607
|
+
if (info.bufferedAhead > 5) {
|
|
608
|
+
video.currentTime = video.currentTime + info.bufferedAhead - 2.5;
|
|
609
|
+
console.debug(`Buffer abundant, seek from ${video.currentTime} to ${video.currentTime}`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const bufferCap = +options.targetBufferLength * (info.playbackRate > 1 ? 1 : constants.dashHighBufferFactor);
|
|
613
|
+
const rate = bufferLength.getEstimate() > bufferCap && options.speedup ? +options.speedup : 0;
|
|
614
|
+
if (video?.currentTime > 0) video.playbackRate = 1 + rate / 100;
|
|
615
|
+
}, options.updateInterval);
|
|
616
|
+
return () => clearInterval(updateIntervalId);
|
|
2217
617
|
};
|
|
2218
|
-
|
|
2219
|
-
|
|
618
|
+
const latencyManager = (player, video) => {
|
|
619
|
+
const currentOptions = {
|
|
620
|
+
speedup: 7,
|
|
621
|
+
targetBufferLength: 1.5,
|
|
622
|
+
hlsSegmentLength: 4,
|
|
623
|
+
updateInterval: 100
|
|
624
|
+
};
|
|
625
|
+
let stop;
|
|
626
|
+
const bufferLength = ewma_default(1500 / currentOptions.updateInterval);
|
|
627
|
+
const configure = (config) => {
|
|
628
|
+
if (!config) return getInfo({
|
|
629
|
+
player,
|
|
630
|
+
video,
|
|
631
|
+
bufferLength
|
|
632
|
+
});
|
|
633
|
+
if ("enabled" in config && Boolean(config.enabled) !== currentOptions.enabled) {
|
|
634
|
+
stop?.();
|
|
635
|
+
if (config.enabled) stop = (needNativeHls() ? manageLatencySafariNative : manageLatencyMse)({
|
|
636
|
+
player,
|
|
637
|
+
video,
|
|
638
|
+
bufferLength
|
|
639
|
+
}, currentOptions);
|
|
640
|
+
}
|
|
641
|
+
Object.assign(currentOptions, config);
|
|
642
|
+
if ("segmentTimestampOffset" in config) window.segmentTimestampOffset = config.segmentTimestampOffset;
|
|
643
|
+
};
|
|
644
|
+
return { configure };
|
|
645
|
+
};
|
|
646
|
+
var latencyManager_default = latencyManager;
|
|
647
|
+
|
|
648
|
+
//#endregion
|
|
649
|
+
export { addSentry_default as addSentry, loadMedia as castMedia, createAnalytics, createApi, dispatchChapterEvents, ensureTabLock_default as ensureTabLock, fixDashManifest_default as fixDashManifest, getContentInfo, getStreamInfo, getVersion, handleIOSHeadphonesDisconnection_default as handleIOSHeadphonesDisconnection, initSender, latencyManager_default as latencyManager, linkCast, logEventNames, mapLogEvents, playlogv3_exports as playlogv3, selectHlsQualities, startSession_default as startSession, disconnect as stopCast, subscribeCastState, validateEnvironment };
|