rx-player 4.2.0-dev.2024090500 → 4.2.0-dev.2024091000
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 +5 -2
- package/VERSION +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/commonjs/compat/browser_detection.d.ts +3 -1
- package/dist/commonjs/compat/browser_detection.d.ts.map +1 -1
- package/dist/commonjs/compat/browser_detection.js +7 -1
- package/dist/commonjs/compat/can_reuse_media_keys.d.ts +4 -0
- package/dist/commonjs/compat/can_reuse_media_keys.d.ts.map +1 -1
- package/dist/commonjs/compat/can_reuse_media_keys.js +5 -1
- package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts +12 -0
- package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
- package/dist/commonjs/core/cmcd/cmcd_data_builder.js +27 -1
- package/dist/commonjs/core/fetchers/segment/segment_fetcher.d.ts +24 -0
- package/dist/commonjs/core/fetchers/segment/segment_fetcher.d.ts.map +1 -1
- package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts.map +1 -1
- package/dist/commonjs/core/fetchers/segment/segment_queue.js +8 -7
- package/dist/commonjs/core/main/worker/worker_main.js +4 -0
- package/dist/commonjs/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.d.ts.map +1 -1
- package/dist/commonjs/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +2 -2
- package/dist/commonjs/main_thread/api/public_api.js +2 -2
- package/dist/commonjs/parsers/manifest/dash/common/parse_mpd.js +2 -2
- package/dist/commonjs/parsers/manifest/dash/common/resolve_base_urls.js +2 -2
- package/dist/commonjs/parsers/manifest/metaplaylist/metaplaylist_parser.js +2 -2
- package/dist/commonjs/parsers/manifest/smooth/create_parser.js +2 -2
- package/dist/commonjs/transports/dash/construct_segment_url.js +2 -2
- package/dist/commonjs/transports/smooth/utils.js +2 -2
- package/dist/commonjs/utils/logger.d.ts.map +1 -1
- package/dist/commonjs/utils/logger.js +6 -7
- package/dist/commonjs/utils/{resolve_url.d.ts → url-utils.d.ts} +15 -4
- package/dist/commonjs/utils/url-utils.d.ts.map +1 -0
- package/dist/commonjs/utils/{resolve_url.js → url-utils.js} +71 -2
- package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/es2017/compat/browser_detection.d.ts +3 -1
- package/dist/es2017/compat/browser_detection.d.ts.map +1 -1
- package/dist/es2017/compat/browser_detection.js +10 -1
- package/dist/es2017/compat/can_reuse_media_keys.d.ts +4 -0
- package/dist/es2017/compat/can_reuse_media_keys.d.ts.map +1 -1
- package/dist/es2017/compat/can_reuse_media_keys.js +6 -2
- package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts +12 -0
- package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
- package/dist/es2017/core/cmcd/cmcd_data_builder.js +27 -1
- package/dist/es2017/core/fetchers/segment/segment_fetcher.d.ts +24 -0
- package/dist/es2017/core/fetchers/segment/segment_fetcher.d.ts.map +1 -1
- package/dist/es2017/core/fetchers/segment/segment_queue.d.ts.map +1 -1
- package/dist/es2017/core/fetchers/segment/segment_queue.js +8 -7
- package/dist/es2017/core/main/worker/worker_main.js +4 -0
- package/dist/es2017/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.d.ts.map +1 -1
- package/dist/es2017/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +2 -2
- package/dist/es2017/main_thread/api/public_api.js +2 -2
- package/dist/es2017/parsers/manifest/dash/common/parse_mpd.js +1 -1
- package/dist/es2017/parsers/manifest/dash/common/resolve_base_urls.js +1 -1
- package/dist/es2017/parsers/manifest/metaplaylist/metaplaylist_parser.js +1 -1
- package/dist/es2017/parsers/manifest/smooth/create_parser.js +1 -1
- package/dist/es2017/transports/dash/construct_segment_url.js +1 -1
- package/dist/es2017/transports/smooth/utils.js +1 -1
- package/dist/es2017/utils/logger.d.ts.map +1 -1
- package/dist/es2017/utils/logger.js +6 -7
- package/dist/es2017/utils/{resolve_url.d.ts → url-utils.d.ts} +15 -4
- package/dist/es2017/utils/url-utils.d.ts.map +1 -0
- package/dist/es2017/utils/{resolve_url.js → url-utils.js} +71 -3
- package/dist/rx-player.js +253 -181
- package/dist/rx-player.min.js +18 -18
- package/dist/worker.js +8 -8
- package/package.json +5 -12
- package/src/README.md +7 -7
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/compat/__tests__/can_reuse_media_keys.test.ts +24 -1
- package/src/compat/browser_detection.ts +13 -4
- package/src/compat/can_reuse_media_keys.ts +6 -2
- package/src/core/cmcd/cmcd_data_builder.ts +68 -2
- package/src/core/fetchers/segment/segment_fetcher.ts +24 -0
- package/src/core/fetchers/segment/segment_queue.ts +11 -9
- package/src/core/main/worker/worker_main.ts +4 -0
- package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +8 -2
- package/src/main_thread/api/public_api.ts +2 -2
- package/src/parsers/manifest/dash/common/parse_mpd.ts +1 -1
- package/src/parsers/manifest/dash/common/resolve_base_urls.ts +1 -1
- package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +1 -1
- package/src/parsers/manifest/smooth/create_parser.ts +1 -1
- package/src/transports/dash/construct_segment_url.ts +1 -1
- package/src/transports/smooth/utils.ts +1 -1
- package/src/utils/__tests__/{resolve_url.test.ts → url-utils.test.ts} +148 -1
- package/src/utils/logger.ts +6 -7
- package/src/utils/{resolve_url.ts → url-utils.ts} +83 -5
- package/dist/commonjs/utils/resolve_url.d.ts.map +0 -1
- package/dist/es2017/utils/resolve_url.d.ts.map +0 -1
|
@@ -8,7 +8,12 @@ describe("Compat - canReuseMediaKeys", () => {
|
|
|
8
8
|
|
|
9
9
|
it("should return true on most browsers", async () => {
|
|
10
10
|
vi.doMock("../browser_detection", () => {
|
|
11
|
-
return {
|
|
11
|
+
return {
|
|
12
|
+
isA1KStb40xx: false,
|
|
13
|
+
isWebOs: false,
|
|
14
|
+
isPhilipsNetTv: false,
|
|
15
|
+
isPanasonic: false,
|
|
16
|
+
};
|
|
12
17
|
});
|
|
13
18
|
const canReuseMediaKeys = (await vi.importActual("../can_reuse_media_keys.ts"))
|
|
14
19
|
.default as typeof ICanReuseMediaKeys;
|
|
@@ -18,6 +23,7 @@ describe("Compat - canReuseMediaKeys", () => {
|
|
|
18
23
|
it("should return false on WebOs", async () => {
|
|
19
24
|
vi.doMock("../browser_detection", () => {
|
|
20
25
|
return {
|
|
26
|
+
isA1KStb40xx: false,
|
|
21
27
|
isWebOs: true,
|
|
22
28
|
isWebOs2022: false,
|
|
23
29
|
isPanasonic: false,
|
|
@@ -32,6 +38,7 @@ describe("Compat - canReuseMediaKeys", () => {
|
|
|
32
38
|
it("should return false on Panasonic", async () => {
|
|
33
39
|
vi.doMock("../browser_detection", () => {
|
|
34
40
|
return {
|
|
41
|
+
isA1KStb40xx: false,
|
|
35
42
|
isWebOs: false,
|
|
36
43
|
isWebOs2022: false,
|
|
37
44
|
isPanasonic: true,
|
|
@@ -46,6 +53,7 @@ describe("Compat - canReuseMediaKeys", () => {
|
|
|
46
53
|
it("should return false on Philips' NETTV", async () => {
|
|
47
54
|
vi.doMock("../browser_detection", () => {
|
|
48
55
|
return {
|
|
56
|
+
isA1KStb40xx: false,
|
|
49
57
|
isWebOs: false,
|
|
50
58
|
isWebOs2022: false,
|
|
51
59
|
isPanasonic: false,
|
|
@@ -56,4 +64,19 @@ describe("Compat - canReuseMediaKeys", () => {
|
|
|
56
64
|
.default as typeof ICanReuseMediaKeys;
|
|
57
65
|
expect(canReuseMediaKeys()).toBe(false);
|
|
58
66
|
});
|
|
67
|
+
|
|
68
|
+
it("should return false on A1 KSTB 40xxx", async () => {
|
|
69
|
+
vi.doMock("../browser_detection", () => {
|
|
70
|
+
return {
|
|
71
|
+
isA1KStb40xx: true,
|
|
72
|
+
isWebOs: false,
|
|
73
|
+
isWebOs2022: false,
|
|
74
|
+
isPanasonic: false,
|
|
75
|
+
isPhilipsNetTv: false,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const canReuseMediaKeys = (await vi.importActual("../can_reuse_media_keys.ts"))
|
|
79
|
+
.default as typeof ICanReuseMediaKeys;
|
|
80
|
+
expect(canReuseMediaKeys()).toBe(false);
|
|
81
|
+
});
|
|
59
82
|
});
|
|
@@ -79,6 +79,9 @@ let isPlayStation5 = false;
|
|
|
79
79
|
/** `true` for the Xbox game consoles. */
|
|
80
80
|
let isXbox = false;
|
|
81
81
|
|
|
82
|
+
/** `true` for specific A1 STB: KSTB 40xx from Kaon Media. */
|
|
83
|
+
let isA1KStb40xx = false;
|
|
84
|
+
|
|
82
85
|
(function findCurrentBrowser(): void {
|
|
83
86
|
if (isNode) {
|
|
84
87
|
return;
|
|
@@ -167,24 +170,30 @@ let isXbox = false;
|
|
|
167
170
|
isPanasonic = true;
|
|
168
171
|
} else if (navigator.userAgent.indexOf("Xbox") !== -1) {
|
|
169
172
|
isXbox = true;
|
|
173
|
+
} else if (navigator.userAgent.indexOf("Model/a1-kstb40xx")) {
|
|
174
|
+
isA1KStb40xx = true;
|
|
170
175
|
}
|
|
171
176
|
})();
|
|
172
177
|
|
|
173
178
|
export {
|
|
179
|
+
// browsers
|
|
174
180
|
isEdgeChromium,
|
|
181
|
+
isFirefox,
|
|
175
182
|
isIE11,
|
|
176
183
|
isIEOrEdge,
|
|
177
|
-
|
|
184
|
+
isSafariDesktop,
|
|
185
|
+
isSafariMobile,
|
|
186
|
+
|
|
187
|
+
// specific devices
|
|
188
|
+
isA1KStb40xx,
|
|
178
189
|
isPanasonic,
|
|
179
190
|
isPhilipsNetTv,
|
|
180
191
|
isPlayStation4,
|
|
181
192
|
isPlayStation5,
|
|
182
|
-
isXbox,
|
|
183
|
-
isSafariDesktop,
|
|
184
|
-
isSafariMobile,
|
|
185
193
|
isSamsungBrowser,
|
|
186
194
|
isTizen,
|
|
187
195
|
isWebOs,
|
|
188
196
|
isWebOs2021,
|
|
189
197
|
isWebOs2022,
|
|
198
|
+
isXbox,
|
|
190
199
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isPanasonic, isPhilipsNetTv, isWebOs } from "./browser_detection";
|
|
1
|
+
import { isA1KStb40xx, isPanasonic, isPhilipsNetTv, isWebOs } from "./browser_detection";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Returns `true` if a `MediaKeys` instance (the `Encrypted Media Extension`
|
|
@@ -11,9 +11,13 @@ import { isPanasonic, isPhilipsNetTv, isWebOs } from "./browser_detection";
|
|
|
11
11
|
* HTMLMediaElement.
|
|
12
12
|
* - (2024-08-23): Seen on Philips 2024 and 2023 in:
|
|
13
13
|
* https://github.com/canalplus/rx-player/issues/1464
|
|
14
|
+
* - (2024-09-04): Another case seen on an "A1" set-top box model made by
|
|
15
|
+
* Kaonmedia we will call the KSTB40xx.
|
|
16
|
+
* It may share the problematic with other devices, but we have only seen
|
|
17
|
+
* the problem on this one for now.
|
|
14
18
|
*
|
|
15
19
|
* @returns {boolean}
|
|
16
20
|
*/
|
|
17
21
|
export default function canReuseMediaKeys(): boolean {
|
|
18
|
-
return !isWebOs && !isPhilipsNetTv && !isPanasonic;
|
|
22
|
+
return !isWebOs && !isPhilipsNetTv && !isPanasonic && !isA1KStb40xx;
|
|
19
23
|
}
|
|
@@ -16,6 +16,7 @@ import createUuid from "../../utils/create_uuid";
|
|
|
16
16
|
import isNullOrUndefined from "../../utils/is_null_or_undefined";
|
|
17
17
|
import type { IRange } from "../../utils/ranges";
|
|
18
18
|
import TaskCanceller from "../../utils/task_canceller";
|
|
19
|
+
import { getRelativeUrl } from "../../utils/url-utils";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* `rtp`, for "REQUESTED_MAXIMUM_THROUGHPUT", indicates the maximum throughput
|
|
@@ -44,6 +45,18 @@ export interface ICmcdSegmentInfo {
|
|
|
44
45
|
representation: IRepresentation;
|
|
45
46
|
/** Segment metadata linked to the wanted segment. */
|
|
46
47
|
segment: ISegment;
|
|
48
|
+
/**
|
|
49
|
+
* Optional next segment that may be requested after this one.
|
|
50
|
+
* Should only be set (to something else than `undefined`) if that following
|
|
51
|
+
* segment is part of the same `Representation`.
|
|
52
|
+
*
|
|
53
|
+
* This information is used to produce the "next object request" and "next
|
|
54
|
+
* range request" part of the CMCD payload, used for segment prefetching.
|
|
55
|
+
*
|
|
56
|
+
* If `null` no segment will be requested next for now.
|
|
57
|
+
* If `undefined` we do not know which next segment will be requested.
|
|
58
|
+
*/
|
|
59
|
+
nextSegment: ISegment | null | undefined;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
/**
|
|
@@ -221,7 +234,6 @@ export default class CmcdDataBuilder {
|
|
|
221
234
|
const props = this._getCommonCmcdData(this._lastThroughput[content.adaptation.type]);
|
|
222
235
|
props.br = Math.round(content.representation.bitrate / 1000);
|
|
223
236
|
props.d = Math.round(content.segment.duration * 1000);
|
|
224
|
-
// TODO nor (next object request) and nrr (next range request)
|
|
225
237
|
|
|
226
238
|
switch (content.adaptation.type) {
|
|
227
239
|
case "video":
|
|
@@ -238,6 +250,33 @@ export default class CmcdDataBuilder {
|
|
|
238
250
|
props.ot = "i";
|
|
239
251
|
}
|
|
240
252
|
|
|
253
|
+
if (
|
|
254
|
+
!isNullOrUndefined(content.nextSegment) &&
|
|
255
|
+
content.segment.url !== null &&
|
|
256
|
+
content.nextSegment.url !== null
|
|
257
|
+
) {
|
|
258
|
+
// We add a special case for some initialization segment which need
|
|
259
|
+
// multiple byte-ranges to fully request, as the `CmcdDataBuilder`
|
|
260
|
+
// is not supposed to keep track of how the requesting part of the
|
|
261
|
+
// RxPlayer actually perform its multi-byte-range requests
|
|
262
|
+
if (!content.nextSegment.isInit || content.nextSegment.indexRange === undefined) {
|
|
263
|
+
const currSegmentUrl = content.segment.url;
|
|
264
|
+
const nextSegmentUrl = content.nextSegment.url;
|
|
265
|
+
const relativeUrl = getRelativeUrl(currSegmentUrl, nextSegmentUrl);
|
|
266
|
+
if (relativeUrl !== null) {
|
|
267
|
+
if (relativeUrl !== ".") {
|
|
268
|
+
props.nor = encodeURIComponent(relativeUrl);
|
|
269
|
+
}
|
|
270
|
+
if (content.nextSegment.range !== undefined) {
|
|
271
|
+
props.nrr = String(content.nextSegment.range[0]) + "-";
|
|
272
|
+
if (isFinite(content.nextSegment.range[1])) {
|
|
273
|
+
props.nrr += String(content.nextSegment.range[1]);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
241
280
|
let precizeBufferLengthMs;
|
|
242
281
|
if (
|
|
243
282
|
lastObservation !== undefined &&
|
|
@@ -355,7 +394,7 @@ export default class CmcdDataBuilder {
|
|
|
355
394
|
};
|
|
356
395
|
|
|
357
396
|
const addStringProperty = (
|
|
358
|
-
prop: "cid" | "sid",
|
|
397
|
+
prop: "cid" | "sid" | "nor" | "nrr",
|
|
359
398
|
headerName: keyof typeof headers,
|
|
360
399
|
): void => {
|
|
361
400
|
const val = props[prop];
|
|
@@ -384,6 +423,8 @@ export default class CmcdDataBuilder {
|
|
|
384
423
|
addNumberProperty("d", "object");
|
|
385
424
|
addNumberProperty("dl", "request");
|
|
386
425
|
addNumberProperty("mtp", "request");
|
|
426
|
+
addStringProperty("nor", "request");
|
|
427
|
+
addStringProperty("nrr", "request");
|
|
387
428
|
addTokenProperty("ot", "object");
|
|
388
429
|
addNumberProperty("pr", "session");
|
|
389
430
|
addNumberProperty("rtp", "status");
|
|
@@ -518,6 +559,31 @@ interface ICmcdProperties {
|
|
|
518
559
|
* In kbps.
|
|
519
560
|
*/
|
|
520
561
|
mtp?: number | undefined;
|
|
562
|
+
/**
|
|
563
|
+
* Next Object Request (nor)
|
|
564
|
+
* Relative path of the next object to be requested.
|
|
565
|
+
* This can be used to trigger pre-fetching by the CDN.
|
|
566
|
+
* This MUST be a path relative to the current request.
|
|
567
|
+
* This string MUST be URLEncoded.
|
|
568
|
+
* The client SHOULD NOT depend upon any pre-fetch action being taken - it is
|
|
569
|
+
* merely a request for such a pre-fetch to take place.
|
|
570
|
+
*/
|
|
571
|
+
nor?: string | undefined;
|
|
572
|
+
/**
|
|
573
|
+
* Next Range Request (nrr)
|
|
574
|
+
* If the next request will be a partial object request, then this string
|
|
575
|
+
* denotes the byte range to be requested. If the ‘nor’ field is not set, then
|
|
576
|
+
* the object is assumed to match the object currently being requested.
|
|
577
|
+
* The client SHOULD NOT depend upon any pre-fetch action being taken – it is
|
|
578
|
+
* merely a request for such a pre-fetch to take place. Formatting is similar
|
|
579
|
+
* to the HTTP Range header, except that the unit MUST be ‘byte’, the ‘Range:’
|
|
580
|
+
* prefix is NOT required and specifying multiple ranges is NOT allowed.
|
|
581
|
+
* Valid combinations are:
|
|
582
|
+
* "<range-start>-"
|
|
583
|
+
* "<range-start>-<range-end>"
|
|
584
|
+
* "-<suffix-length>"
|
|
585
|
+
*/
|
|
586
|
+
nrr?: string | undefined;
|
|
521
587
|
/**
|
|
522
588
|
* Object type (ot)
|
|
523
589
|
* The media type of the current object being requested:
|
|
@@ -427,11 +427,35 @@ export interface ISegmentFetcherCallbacks<TSegmentDataType> {
|
|
|
427
427
|
|
|
428
428
|
/** Content used by the segment loader as a context to load a new segment. */
|
|
429
429
|
export interface ISegmentLoaderContent {
|
|
430
|
+
/** Manifest metadata linked to the wanted segment. */
|
|
430
431
|
manifest: IManifest;
|
|
432
|
+
/** Period metadata linked to the wanted segment. */
|
|
431
433
|
period: IPeriod;
|
|
434
|
+
/** Adaptation metadata linked to the wanted segment. */
|
|
432
435
|
adaptation: IAdaptation;
|
|
436
|
+
/** Representation metadata linked to the wanted segment. */
|
|
433
437
|
representation: IRepresentation;
|
|
438
|
+
/** Segment metadata linked to the wanted segment. */
|
|
434
439
|
segment: ISegment;
|
|
440
|
+
/**
|
|
441
|
+
* Optional next segment that may be requested after this one.
|
|
442
|
+
* Should only be set (to something else than `undefined`) if that following
|
|
443
|
+
* segment is part of the same `Representation`.
|
|
444
|
+
*
|
|
445
|
+
* This is only used as an hint, finally requesting another segment after this
|
|
446
|
+
* one due to unexpected changes (e.g. bandwidth update, track change etc.) is
|
|
447
|
+
* OK.
|
|
448
|
+
*
|
|
449
|
+
* This information is then used mostly for matters related yet not required
|
|
450
|
+
* by requests, such as CMCD reporting. In scenarios when it's not
|
|
451
|
+
* straightforward to guess which segment will be requested after this one,
|
|
452
|
+
* this property can be ignored (set to `undefined`).
|
|
453
|
+
*
|
|
454
|
+
* If `null` no segment will be requested next for now.
|
|
455
|
+
*
|
|
456
|
+
* If `undefined` we do not know which next segment will be requested.
|
|
457
|
+
*/
|
|
458
|
+
nextSegment: ISegment | null | undefined;
|
|
435
459
|
}
|
|
436
460
|
|
|
437
461
|
/**
|
|
@@ -255,11 +255,10 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
const { downloadQueue, content, initSegmentInfoRef, currentCanceller } = contentInfo;
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
startingSegment
|
|
262
|
-
): void => {
|
|
258
|
+
|
|
259
|
+
const recursivelyRequestSegments = (): void => {
|
|
260
|
+
const { segmentQueue } = downloadQueue.getValue();
|
|
261
|
+
const startingSegment = segmentQueue[0];
|
|
263
262
|
if (currentCanceller !== null && currentCanceller.isUsed()) {
|
|
264
263
|
contentInfo.mediaSegmentRequest = null;
|
|
265
264
|
return;
|
|
@@ -276,7 +275,10 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
|
|
|
276
275
|
: canceller.linkToSignal(currentCanceller.signal);
|
|
277
276
|
|
|
278
277
|
const { segment, priority } = startingSegment;
|
|
279
|
-
const context = objectAssign(
|
|
278
|
+
const context = objectAssign(
|
|
279
|
+
{ segment, nextSegment: segmentQueue[1]?.segment },
|
|
280
|
+
content,
|
|
281
|
+
);
|
|
280
282
|
|
|
281
283
|
/**
|
|
282
284
|
* If `true` , the current task has either errored, finished, or was
|
|
@@ -318,7 +320,7 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
|
|
|
318
320
|
lastQueue.shift();
|
|
319
321
|
}
|
|
320
322
|
isComplete = true;
|
|
321
|
-
recursivelyRequestSegments(
|
|
323
|
+
recursivelyRequestSegments();
|
|
322
324
|
};
|
|
323
325
|
|
|
324
326
|
/** Scheduled actual segment request. */
|
|
@@ -422,7 +424,7 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
|
|
|
422
424
|
|
|
423
425
|
contentInfo.mediaSegmentRequest = { segment, priority, request, canceller };
|
|
424
426
|
};
|
|
425
|
-
recursivelyRequestSegments(
|
|
427
|
+
recursivelyRequestSegments();
|
|
426
428
|
}
|
|
427
429
|
|
|
428
430
|
/**
|
|
@@ -449,7 +451,7 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
|
|
|
449
451
|
? noop
|
|
450
452
|
: canceller.linkToSignal(contentInfo.currentCanceller.signal);
|
|
451
453
|
const { segment, priority } = queuedInitSegment;
|
|
452
|
-
const context = objectAssign({ segment }, content);
|
|
454
|
+
const context = objectAssign({ segment, nextSegment: undefined }, content);
|
|
453
455
|
|
|
454
456
|
/**
|
|
455
457
|
* If `true` , the current task has either errored, finished, or was
|
|
@@ -884,6 +884,10 @@ function loadOrReloadPreparedContent(
|
|
|
884
884
|
);
|
|
885
885
|
},
|
|
886
886
|
(err: unknown) => {
|
|
887
|
+
if (TaskCanceller.isCancellationError(err)) {
|
|
888
|
+
log.info("WP: A reloading operation was cancelled");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
887
891
|
sendMessage({
|
|
888
892
|
type: WorkerMessageType.Error,
|
|
889
893
|
contentId,
|
|
@@ -207,7 +207,10 @@ export default class VideoThumbnailLoader {
|
|
|
207
207
|
lastRepInfo.initSegmentUniqueId = null;
|
|
208
208
|
return sourceBufferInterface;
|
|
209
209
|
}
|
|
210
|
-
const segmentInfo = objectAssign(
|
|
210
|
+
const segmentInfo = objectAssign(
|
|
211
|
+
{ segment: initSegment, nextSegment: undefined },
|
|
212
|
+
content,
|
|
213
|
+
);
|
|
211
214
|
await loadAndPushSegment(
|
|
212
215
|
segmentInfo,
|
|
213
216
|
sourceBufferInterface,
|
|
@@ -272,7 +275,10 @@ export default class VideoThumbnailLoader {
|
|
|
272
275
|
const unlinkSignal = requestCanceller.linkToSignal(
|
|
273
276
|
lastRepInfo.cleaner.signal,
|
|
274
277
|
);
|
|
275
|
-
const segmentInfo = objectAssign(
|
|
278
|
+
const segmentInfo = objectAssign(
|
|
279
|
+
{ segment, nextSegment: undefined },
|
|
280
|
+
content,
|
|
281
|
+
);
|
|
276
282
|
const prom = loadAndPushSegment(
|
|
277
283
|
segmentInfo,
|
|
278
284
|
sourceBufferInterface,
|
|
@@ -409,7 +409,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
409
409
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
410
410
|
videoElement.preload = "auto";
|
|
411
411
|
|
|
412
|
-
this.version = /* PLAYER_VERSION */ "4.2.0-dev.
|
|
412
|
+
this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024091000";
|
|
413
413
|
this.log = log;
|
|
414
414
|
this.state = "STOPPED";
|
|
415
415
|
this.videoElement = videoElement;
|
|
@@ -3313,7 +3313,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
3313
3313
|
}
|
|
3314
3314
|
}
|
|
3315
3315
|
}
|
|
3316
|
-
Player.version = /* PLAYER_VERSION */ "4.2.0-dev.
|
|
3316
|
+
Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024091000";
|
|
3317
3317
|
|
|
3318
3318
|
/** Every events sent by the RxPlayer's public API. */
|
|
3319
3319
|
interface IPublicAPIEvent {
|
|
@@ -20,7 +20,7 @@ import type { IManifest } from "../../../../manifest";
|
|
|
20
20
|
import arrayFind from "../../../../utils/array_find";
|
|
21
21
|
import isNullOrUndefined from "../../../../utils/is_null_or_undefined";
|
|
22
22
|
import getMonotonicTimeStamp from "../../../../utils/monotonic_timestamp";
|
|
23
|
-
import { getFilenameIndexInUrl } from "../../../../utils/
|
|
23
|
+
import { getFilenameIndexInUrl } from "../../../../utils/url-utils";
|
|
24
24
|
import type { IParsedManifest } from "../../types";
|
|
25
25
|
import type {
|
|
26
26
|
IMPDIntermediateRepresentation,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import resolveURL from "../../../../utils/
|
|
17
|
+
import { resolveURL } from "../../../../utils/url-utils";
|
|
18
18
|
import type { IBaseUrlIntermediateRepresentation } from "../node_parser_types";
|
|
19
19
|
|
|
20
20
|
export interface IResolvedBaseUrl {
|
|
@@ -22,7 +22,7 @@ import type { ITrackType } from "../../../public_types";
|
|
|
22
22
|
import idGenerator from "../../../utils/id_generator";
|
|
23
23
|
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
|
|
24
24
|
import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";
|
|
25
|
-
import { getFilenameIndexInUrl } from "../../../utils/
|
|
25
|
+
import { getFilenameIndexInUrl } from "../../../utils/url-utils";
|
|
26
26
|
import type {
|
|
27
27
|
IParsedAdaptation,
|
|
28
28
|
IParsedAdaptations,
|
|
@@ -23,8 +23,8 @@ import isNonEmptyString from "../../../utils/is_non_empty_string";
|
|
|
23
23
|
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
|
|
24
24
|
import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";
|
|
25
25
|
import objectAssign from "../../../utils/object_assign";
|
|
26
|
-
import { getFilenameIndexInUrl } from "../../../utils/resolve_url";
|
|
27
26
|
import { hexToBytes } from "../../../utils/string_parsing";
|
|
27
|
+
import { getFilenameIndexInUrl } from "../../../utils/url-utils";
|
|
28
28
|
import { createBox } from "../../containers/isobmff";
|
|
29
29
|
import type {
|
|
30
30
|
IParsedAdaptation,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import type { ISegment } from "../../manifest";
|
|
18
18
|
import type { ICdnMetadata } from "../../parsers/manifest";
|
|
19
|
-
import resolveURL from "../../utils/
|
|
19
|
+
import { resolveURL } from "../../utils/url-utils";
|
|
20
20
|
|
|
21
21
|
export default function constructSegmentUrl(
|
|
22
22
|
wantedCdn: ICdnMetadata | null,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import type { ISegment, IRepresentation } from "../../manifest";
|
|
18
18
|
import type { ICdnMetadata } from "../../parsers/manifest";
|
|
19
|
-
import resolveURL from "../../utils/
|
|
19
|
+
import { resolveURL } from "../../utils/url-utils";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Returns `true` if the given Representation refers to segments in an MP4
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import
|
|
2
|
+
import { getFilenameIndexInUrl, getRelativeUrl, resolveURL } from "../url-utils";
|
|
3
3
|
|
|
4
4
|
describe(`utils - resolveURL ${resolveURL.name}`, () => {
|
|
5
5
|
it("should return an empty string if no argument is given", () => {
|
|
@@ -155,3 +155,150 @@ describe("utils - getFilenameIndexInUrl", () => {
|
|
|
155
155
|
expect(getFilenameIndexInUrl("https://ww/rr/d/?test/toto/efewf/ffe/")).toEqual(16);
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
|
+
|
|
159
|
+
describe("utils - getRelativeUrl", () => {
|
|
160
|
+
it("should succeed on two absolute URLs on the same domain", () => {
|
|
161
|
+
expect(
|
|
162
|
+
getRelativeUrl("https://foo.com/foo/bar/cil/", "https://foo.com/foo/bar/cil/diub"),
|
|
163
|
+
).toEqual("diub");
|
|
164
|
+
expect(
|
|
165
|
+
getRelativeUrl(
|
|
166
|
+
"https://foo.com/foo/bar/cil/",
|
|
167
|
+
"https://foo.com/foo/bar/cil/diub/emp",
|
|
168
|
+
),
|
|
169
|
+
).toEqual("diub/emp");
|
|
170
|
+
expect(
|
|
171
|
+
getRelativeUrl(
|
|
172
|
+
"https://foo.com/foo/bar/cil/diub/emp/",
|
|
173
|
+
"https://foo.com/foo/bar/cil/f/g/h/i",
|
|
174
|
+
),
|
|
175
|
+
).toEqual("../../f/g/h/i");
|
|
176
|
+
|
|
177
|
+
expect(
|
|
178
|
+
getRelativeUrl(
|
|
179
|
+
"https://foo.com/foo/bar/cil/emp",
|
|
180
|
+
"https://foo.com/foo/bar/cil/emp",
|
|
181
|
+
),
|
|
182
|
+
).toEqual("");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should handle it right when one of the input is just a domain name without a path", () => {
|
|
186
|
+
expect(
|
|
187
|
+
getRelativeUrl(
|
|
188
|
+
"http://github.com" /** Without a starting slash */,
|
|
189
|
+
"http://github.com/rx-player",
|
|
190
|
+
),
|
|
191
|
+
).toBe("/rx-player");
|
|
192
|
+
expect(
|
|
193
|
+
getRelativeUrl(
|
|
194
|
+
"http://github.com/rx-player/",
|
|
195
|
+
"http://github.com" /** Without a starting slash */,
|
|
196
|
+
),
|
|
197
|
+
).toBe("..");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should handle it right when one of the input has a path of length 0", () => {
|
|
201
|
+
expect(getRelativeUrl("http://github.com/", "http://github.com/rx-player")).toBe(
|
|
202
|
+
"rx-player",
|
|
203
|
+
);
|
|
204
|
+
expect(
|
|
205
|
+
getRelativeUrl(
|
|
206
|
+
"http://github.com/rx-player/",
|
|
207
|
+
"http://github.com" /** Without a starting slash */,
|
|
208
|
+
),
|
|
209
|
+
).toBe("..");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should fail if scheme is different", () => {
|
|
213
|
+
expect(
|
|
214
|
+
getRelativeUrl("http://foo.com/foo/bar/cil/emp", "https://foo.com/foo/bar/cil/emp"),
|
|
215
|
+
).toEqual(null);
|
|
216
|
+
|
|
217
|
+
expect(
|
|
218
|
+
getRelativeUrl("https://foo.com/foo/bar/cil/emp", "http://foo.com/foo/bar/cil/emp"),
|
|
219
|
+
).toEqual(null);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should fail if domain is different", () => {
|
|
223
|
+
expect(
|
|
224
|
+
getRelativeUrl(
|
|
225
|
+
"https://bar.com/foo/bar/cil/emp",
|
|
226
|
+
"https://foo.com/foo/bar/cil/emp",
|
|
227
|
+
),
|
|
228
|
+
).toEqual(null);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should fail if one anounce a domain but not the other", () => {
|
|
232
|
+
expect(getRelativeUrl("https://foo.com/foo/bar/cil/diub/emp", "/a")).toEqual(null);
|
|
233
|
+
|
|
234
|
+
expect(getRelativeUrl("../url", "https://foo.com")).toEqual(null);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should output "." if the URL points to the same file', () => {
|
|
238
|
+
expect(getRelativeUrl("../url", "../url")).toEqual("");
|
|
239
|
+
expect(
|
|
240
|
+
getRelativeUrl(
|
|
241
|
+
"https://foo.com/foo/bar/cil/diub/emp",
|
|
242
|
+
"https://foo.com/foo/bar/cil/diub/emp",
|
|
243
|
+
),
|
|
244
|
+
).toEqual("");
|
|
245
|
+
expect(
|
|
246
|
+
getRelativeUrl(
|
|
247
|
+
"https://foo.com/foo/bar/cil/diub/emp",
|
|
248
|
+
"https://foo.com/foo/bar/../bar/cil/diub/emp",
|
|
249
|
+
),
|
|
250
|
+
).toEqual("");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should succeed on two relative URLs", () => {
|
|
254
|
+
expect(getRelativeUrl("../url/a", "../url/a/b")).toEqual("a/b");
|
|
255
|
+
expect(getRelativeUrl("../url/a", "../url/b/c")).toEqual("b/c");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should fail if one seems to be relative to the domain's root, yet we don't know it", () => {
|
|
259
|
+
expect(getRelativeUrl("/url/a", "../../../url/b/c")).toEqual(null);
|
|
260
|
+
expect(getRelativeUrl("./url/a", "/url/b/c")).toEqual(null);
|
|
261
|
+
expect(getRelativeUrl("http://foo.com/url/a", "../url/b/c")).toEqual(null);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should succeed if both seem to be relative to the domain's root, even if we don't know it", () => {
|
|
265
|
+
expect(getRelativeUrl("/url/a", "/url/b/c")).toEqual("b/c");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const normalExamples = [
|
|
269
|
+
{ input: "http://a/b/c/g", output: "g" },
|
|
270
|
+
{ input: "http://a/b/c/g/", output: "g/" },
|
|
271
|
+
{ input: "http://a/g", output: "../../g" },
|
|
272
|
+
{ input: "http://a/b/c/d;p?y", output: "?y" },
|
|
273
|
+
{ input: "http://a/b/c/g?y", output: "g?y" },
|
|
274
|
+
{ input: "http://a/b/c/d;p?q#s", output: "#s" },
|
|
275
|
+
{ input: "http://a/b/c/g#s", output: "g#s" },
|
|
276
|
+
{ input: "http://a/b/c/g?y#s", output: "g?y#s" },
|
|
277
|
+
{ input: "http://a/b/c/;x", output: ";x" },
|
|
278
|
+
{ input: "http://a/b/c/g;x", output: "g;x" },
|
|
279
|
+
{ input: "http://a/b/c/g;x?y#s", output: "g;x?y#s" },
|
|
280
|
+
{ input: "http://a/b/c/d;p?q", output: "" },
|
|
281
|
+
{ input: "http://a/b/c/", output: "." },
|
|
282
|
+
{ input: "http://a/b/", output: ".." },
|
|
283
|
+
{ input: "http://a/b/g", output: "../g" },
|
|
284
|
+
{ input: "http://a/", output: "../.." },
|
|
285
|
+
{ input: "http://a/g", output: "../../g" },
|
|
286
|
+
];
|
|
287
|
+
normalExamples.forEach((example) => {
|
|
288
|
+
it("should conform to RFC 3986 normal examples - case: " + example.input, () => {
|
|
289
|
+
const baseURL: string = "http://a/b/c/d;p?q";
|
|
290
|
+
// https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.1
|
|
291
|
+
expect(getRelativeUrl(baseURL, example.input)).toBe(example.output);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should succeed on two relative URLs", () => {
|
|
296
|
+
expect(getRelativeUrl("../url/a", "../url/a/b")).toEqual("a/b");
|
|
297
|
+
expect(getRelativeUrl("../url/a", "../url/b/c")).toEqual("b/c");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should return null if the relation between the two URLs is unclear", () => {
|
|
301
|
+
expect(getRelativeUrl("/url/a", "url/a/b")).toEqual(null);
|
|
302
|
+
expect(getRelativeUrl("url/a", "/url/a/b")).toEqual(null);
|
|
303
|
+
});
|
|
304
|
+
});
|
package/src/utils/logger.ts
CHANGED
|
@@ -138,18 +138,17 @@ export default class Logger extends EventEmitter<ILoggerEvents> {
|
|
|
138
138
|
/* eslint-enable no-console */
|
|
139
139
|
/* eslint-enable no-invalid-this */
|
|
140
140
|
} else {
|
|
141
|
-
const produceLogFn = (logLevel: ILoggerLevel
|
|
141
|
+
const produceLogFn = (logLevel: ILoggerLevel) => {
|
|
142
142
|
return level >= this._levels[logLevel]
|
|
143
143
|
? (...args: IAcceptedLogValue[]) => {
|
|
144
|
-
|
|
145
|
-
return logFn(logLevel, [now, namespace, ...args]);
|
|
144
|
+
return logFn(logLevel, args);
|
|
146
145
|
}
|
|
147
146
|
: noop;
|
|
148
147
|
};
|
|
149
|
-
this.error = produceLogFn("ERROR"
|
|
150
|
-
this.warn = produceLogFn("WARNING"
|
|
151
|
-
this.info = produceLogFn("INFO"
|
|
152
|
-
this.debug = produceLogFn("DEBUG"
|
|
148
|
+
this.error = produceLogFn("ERROR");
|
|
149
|
+
this.warn = produceLogFn("WARNING");
|
|
150
|
+
this.info = produceLogFn("INFO");
|
|
151
|
+
this.debug = produceLogFn("DEBUG");
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
this.trigger("onLogLevelChange", {
|