rx-player 4.5.0-dev.2026012302 → 4.5.0-dev.2026031700
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 +21 -3
- package/VERSION +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +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/should_seek_at_zero.d.ts +12 -0
- package/dist/commonjs/compat/should_seek_at_zero.d.ts.map +1 -0
- package/dist/commonjs/compat/should_seek_at_zero.js +23 -0
- package/dist/commonjs/core/cmcd/cmcd_data_builder.js +2 -2
- package/dist/commonjs/core/entry/FreezeResolver.js +2 -2
- package/dist/commonjs/core/entry/content_preparer.d.ts +1 -6
- package/dist/commonjs/core/entry/content_preparer.d.ts.map +1 -1
- package/dist/commonjs/core/entry/content_preparer.js +2 -125
- package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
- package/dist/commonjs/core/entry/content_time_boundaries_observer.js +8 -0
- package/dist/commonjs/core/entry/core_entry.d.ts +1 -1
- package/dist/commonjs/core/entry/core_entry.d.ts.map +1 -1
- package/dist/commonjs/core/entry/core_entry.js +2 -7
- package/dist/commonjs/core/entry/core_text_displayer_interface.d.ts.map +1 -1
- package/dist/commonjs/core/entry/core_text_displayer_interface.js +3 -1
- package/dist/commonjs/core/entry/index.d.ts +1 -1
- package/dist/commonjs/core/entry/index.d.ts.map +1 -1
- package/dist/commonjs/core/entry/utils.d.ts +64 -0
- package/dist/commonjs/core/entry/utils.d.ts.map +1 -1
- package/dist/commonjs/core/entry/utils.js +165 -0
- package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts +5 -6
- package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts.map +1 -1
- package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.d.ts.map +1 -1
- package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.js +34 -30
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts +70 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.js +107 -0
- package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/commonjs/core/stream/representation/representation_stream.js +12 -60
- package/dist/commonjs/core/stream/representation/utils/get_needed_segments.js +1 -1
- package/dist/commonjs/default_config.d.ts +5 -0
- package/dist/commonjs/default_config.d.ts.map +1 -1
- package/dist/commonjs/default_config.js +5 -0
- package/dist/commonjs/experimental/features/worker/index.d.ts +2 -1
- package/dist/commonjs/experimental/features/worker/index.d.ts.map +1 -1
- package/dist/commonjs/experimental/features/worker/index.js +4 -3
- package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/commonjs/main_thread/api/public_api.js +28 -4
- package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/directfile_content_initializer.js +23 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -1
- package/dist/commonjs/manifest/classes/index.d.ts +2 -1
- package/dist/commonjs/manifest/classes/index.d.ts.map +1 -1
- package/dist/commonjs/manifest/classes/representation.d.ts +1 -1
- package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
- package/dist/commonjs/manifest/classes/representation.js +2 -2
- package/dist/commonjs/manifest/classes/representation_index/types.d.ts +2 -2
- package/dist/commonjs/parsers/texttracks/ttml/html/apply_line_height.d.ts.map +1 -1
- package/dist/commonjs/parsers/texttracks/ttml/html/apply_line_height.js +16 -1
- package/dist/commonjs/utils/base64.d.ts +15 -1
- package/dist/commonjs/utils/base64.d.ts.map +1 -1
- package/dist/commonjs/utils/base64.js +6 -0
- package/dist/commonjs/utils/ranges.d.ts +1 -1
- package/dist/commonjs/utils/ranges.js +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- 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/should_seek_at_zero.d.ts +12 -0
- package/dist/es2017/compat/should_seek_at_zero.d.ts.map +1 -0
- package/dist/es2017/compat/should_seek_at_zero.js +20 -0
- package/dist/es2017/core/cmcd/cmcd_data_builder.js +2 -2
- package/dist/es2017/core/entry/FreezeResolver.js +1 -1
- package/dist/es2017/core/entry/content_preparer.d.ts +1 -6
- package/dist/es2017/core/entry/content_preparer.d.ts.map +1 -1
- package/dist/es2017/core/entry/content_preparer.js +1 -76
- package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
- package/dist/es2017/core/entry/content_time_boundaries_observer.js +8 -0
- package/dist/es2017/core/entry/core_entry.d.ts +1 -1
- package/dist/es2017/core/entry/core_entry.d.ts.map +1 -1
- package/dist/es2017/core/entry/core_entry.js +2 -7
- package/dist/es2017/core/entry/core_text_displayer_interface.d.ts.map +1 -1
- package/dist/es2017/core/entry/core_text_displayer_interface.js +3 -1
- package/dist/es2017/core/entry/index.d.ts +1 -1
- package/dist/es2017/core/entry/index.d.ts.map +1 -1
- package/dist/es2017/core/entry/utils.d.ts +64 -0
- package/dist/es2017/core/entry/utils.d.ts.map +1 -1
- package/dist/es2017/core/entry/utils.js +98 -0
- package/dist/es2017/core/fetchers/segment/segment_queue.d.ts +5 -6
- package/dist/es2017/core/fetchers/segment/segment_queue.d.ts.map +1 -1
- package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.d.ts.map +1 -1
- package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.js +21 -16
- package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts +70 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.js +80 -0
- package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/es2017/core/stream/representation/representation_stream.js +10 -38
- package/dist/es2017/core/stream/representation/utils/get_needed_segments.js +1 -1
- package/dist/es2017/default_config.d.ts +5 -0
- package/dist/es2017/default_config.d.ts.map +1 -1
- package/dist/es2017/default_config.js +5 -0
- package/dist/es2017/experimental/features/worker/index.d.ts +2 -1
- package/dist/es2017/experimental/features/worker/index.d.ts.map +1 -1
- package/dist/es2017/experimental/features/worker/index.js +2 -3
- package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/es2017/main_thread/api/public_api.js +28 -4
- package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/directfile_content_initializer.js +23 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -1
- package/dist/es2017/manifest/classes/index.d.ts +2 -1
- package/dist/es2017/manifest/classes/index.d.ts.map +1 -1
- package/dist/es2017/manifest/classes/representation.d.ts +1 -1
- package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
- package/dist/es2017/manifest/classes/representation.js +1 -1
- package/dist/es2017/manifest/classes/representation_index/types.d.ts +2 -2
- package/dist/es2017/parsers/texttracks/ttml/html/apply_line_height.d.ts.map +1 -1
- package/dist/es2017/parsers/texttracks/ttml/html/apply_line_height.js +16 -1
- package/dist/es2017/utils/base64.d.ts +15 -1
- package/dist/es2017/utils/base64.d.ts.map +1 -1
- package/dist/es2017/utils/base64.js +6 -0
- package/dist/es2017/utils/ranges.d.ts +1 -1
- package/dist/es2017/utils/ranges.js +1 -1
- package/dist/mpd-parser.wasm +0 -0
- package/dist/worker.js +8 -8
- package/package.json +26 -23
- package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/compat/should_seek_at_zero.ts +23 -0
- package/src/core/adaptive/__tests__/adaptive_representation_selector.test.ts +734 -0
- package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +793 -0
- package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
- package/src/core/adaptive/utils/__tests__/bandwidth_estimator.test.ts +117 -0
- package/src/core/adaptive/utils/__tests__/last_estimate_storage.test.ts +76 -0
- package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +288 -0
- package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +249 -0
- package/src/core/cmcd/__tests__/cmcd_data_builder.test.ts +464 -0
- package/src/core/cmcd/cmcd_data_builder.ts +2 -2
- package/src/core/entry/FreezeResolver.ts +1 -1
- package/src/core/entry/__tests__/FreezeResolver.test.ts +542 -0
- package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +867 -0
- package/src/core/entry/__tests__/core_text_displayer_interface.test.ts +205 -0
- package/src/core/entry/__tests__/get_buffered_data_per_media_buffer.test.ts +95 -0
- package/src/core/entry/__tests__/track_choice_setter.test.ts +288 -0
- package/src/core/entry/__tests__/utils.test.ts +309 -0
- package/src/core/entry/content_preparer.ts +7 -118
- package/src/core/entry/content_time_boundaries_observer.ts +10 -0
- package/src/core/entry/core_entry.ts +7 -14
- package/src/core/entry/core_text_displayer_interface.ts +3 -1
- package/src/core/entry/index.ts +1 -1
- package/src/core/entry/utils.ts +151 -0
- package/src/core/fetchers/__tests__/cdn_prioritizer.test.ts +166 -0
- package/src/core/fetchers/manifest/__tests__/manifest_fetcher.test.ts +803 -0
- package/src/core/fetchers/segment/__tests__/segment_fetcher.test.ts +872 -0
- package/src/core/fetchers/segment/__tests__/segment_queue.test.ts +557 -0
- package/src/core/fetchers/segment/segment_queue.ts +5 -6
- package/src/core/fetchers/thumbnails/__tests__/thumbnail_fetcher.test.ts +435 -0
- package/src/core/fetchers/thumbnails/thumbnail_fetcher.ts +28 -20
- package/src/core/fetchers/utils/__tests__/schedule_request.test.ts +331 -0
- package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +593 -0
- package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +646 -0
- package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +531 -0
- package/src/core/stream/representation/encryption_data_notifier.ts +122 -0
- package/src/core/stream/representation/representation_stream.ts +11 -49
- package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +321 -0
- package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +513 -0
- package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +132 -0
- package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +209 -0
- package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +343 -0
- package/src/core/stream/representation/utils/get_needed_segments.ts +1 -1
- package/src/default_config.ts +6 -0
- package/src/experimental/features/worker/index.ts +2 -4
- package/src/main_thread/api/public_api.ts +33 -4
- package/src/main_thread/init/directfile_content_initializer.ts +26 -1
- package/src/main_thread/init/utils/initial_seek_and_play.ts +5 -1
- package/src/manifest/classes/index.ts +2 -0
- package/src/manifest/classes/representation.ts +2 -1
- package/src/manifest/classes/representation_index/types.ts +2 -2
- package/src/mse/__tests__/main_media_source_interface.test.ts +846 -0
- package/src/parsers/texttracks/ttml/html/apply_line_height.ts +16 -1
- package/src/utils/__tests__/base64.test.ts +159 -191
- package/src/utils/base64.ts +30 -1
- package/src/utils/ranges.ts +1 -1
package/src/core/entry/utils.ts
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
+
import { MediaSource_ } from "../../compat/browser_compatibility_types";
|
|
1
2
|
import { formatError } from "../../errors";
|
|
3
|
+
import type { ICorePlaybackObservation } from "../../main_thread/init/utils/create_core_playback_observer";
|
|
4
|
+
import { createRepresentationFilterFromFnString } from "../../manifest";
|
|
5
|
+
import type Manifest from "../../manifest/classes";
|
|
6
|
+
import type {
|
|
7
|
+
IManifestLoader,
|
|
8
|
+
IRepresentationFilter,
|
|
9
|
+
ISegmentLoader,
|
|
10
|
+
} from "../../public_types";
|
|
11
|
+
import isNullOrUndefined from "../../utils/is_null_or_undefined";
|
|
12
|
+
import type SegmentSinksStore from "../segment_sinks";
|
|
2
13
|
import type { ISentError } from "../types";
|
|
3
14
|
|
|
15
|
+
/**
|
|
16
|
+
* "Plugins" are a specific kind of API where an application can define complex
|
|
17
|
+
* functions that the RxPlayer will call in some normally-core aspects of media
|
|
18
|
+
* playback: e.g. loading segments.
|
|
19
|
+
*
|
|
20
|
+
* This interface lists them.
|
|
21
|
+
*/
|
|
22
|
+
export interface ICorePlugins {
|
|
23
|
+
representationFilters: Map<string, IRepresentationFilter>;
|
|
24
|
+
segmentLoaders: Map<string, ISegmentLoader>;
|
|
25
|
+
manifestLoaders: Map<string, IManifestLoader>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format an error, which may be of any form, into an Object than can easily be
|
|
30
|
+
* serialized - e.g. to be communicated through a postMessage-like API.
|
|
31
|
+
* @param {*} error - The error to serialized
|
|
32
|
+
* @returns {Object} - A serialization-safe error format
|
|
33
|
+
*/
|
|
4
34
|
export function formatErrorForSender(error: unknown): ISentError {
|
|
5
35
|
const formattedError = formatError(error, {
|
|
6
36
|
defaultCode: "NONE",
|
|
@@ -9,3 +39,124 @@ export function formatErrorForSender(error: unknown): ISentError {
|
|
|
9
39
|
|
|
10
40
|
return formattedError.serialize();
|
|
11
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Synchronize SegmentSinks with what has been buffered.
|
|
45
|
+
* @param {Object} observation - The just-received playback observation,
|
|
46
|
+
* including what has been buffered on lower-level buffers
|
|
47
|
+
* @param {Object} segmentSinksStore - Interface allowing to interact
|
|
48
|
+
* with `SegmentSink`s, so their inventory can be updated accordingly.
|
|
49
|
+
*/
|
|
50
|
+
export function synchronizeSegmentSinksOnObservation(
|
|
51
|
+
observation: ICorePlaybackObservation,
|
|
52
|
+
segmentSinksStore: SegmentSinksStore,
|
|
53
|
+
): void {
|
|
54
|
+
// Synchronize SegmentSinks with what has been buffered.
|
|
55
|
+
["video" as const, "audio" as const, "text" as const].forEach((tType) => {
|
|
56
|
+
const segmentSinkStatus = segmentSinksStore.getStatus(tType);
|
|
57
|
+
if (segmentSinkStatus.type === "initialized") {
|
|
58
|
+
segmentSinkStatus.value.synchronizeInventory(observation.buffered[tType] ?? []);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set Representation.isCodecSupportedInWebWorker to true or false
|
|
65
|
+
* If the codec is supported in the current context.
|
|
66
|
+
* If MSE in worker is not available, the attribute is not set.
|
|
67
|
+
*/
|
|
68
|
+
export function updateCodecSupportInWorkerMode(manifestToUpdate: Manifest) {
|
|
69
|
+
if (isNullOrUndefined(MediaSource_)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const codecsMap = new Map<string, boolean>();
|
|
74
|
+
for (const period of manifestToUpdate.periods) {
|
|
75
|
+
const checkedAdaptations = [
|
|
76
|
+
...(period.adaptations.video ?? []),
|
|
77
|
+
...(period.adaptations.audio ?? []),
|
|
78
|
+
];
|
|
79
|
+
for (const adaptation of checkedAdaptations) {
|
|
80
|
+
for (const representation of adaptation.representations) {
|
|
81
|
+
const codec = `${representation.mimeType};codecs="${representation.codecs[0]}"`;
|
|
82
|
+
if (codecsMap.has(codec)) {
|
|
83
|
+
representation.isCodecSupportedInWebWorker = codecsMap.get(codec);
|
|
84
|
+
} else {
|
|
85
|
+
const supported = MediaSource_.isTypeSupported(codec);
|
|
86
|
+
representation.isCodecSupportedInWebWorker = supported;
|
|
87
|
+
codecsMap.set(codec, supported);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Some functions may be defined by the API, we call those "plugins".
|
|
96
|
+
* This function parses and extract the actual function from the different
|
|
97
|
+
* ways an application can provide it to us.
|
|
98
|
+
* @param {Object} input - The API input
|
|
99
|
+
* @param {Object} corePlugins - Context on what identified functions are
|
|
100
|
+
* defined right now.
|
|
101
|
+
* @returns {Function}
|
|
102
|
+
*/
|
|
103
|
+
export function extractExternalPlugins(
|
|
104
|
+
input: {
|
|
105
|
+
manifestLoader:
|
|
106
|
+
| {
|
|
107
|
+
fn?: IManifestLoader | undefined;
|
|
108
|
+
workerId?: string | undefined;
|
|
109
|
+
}
|
|
110
|
+
| undefined;
|
|
111
|
+
segmentLoader:
|
|
112
|
+
| {
|
|
113
|
+
fn?: ISegmentLoader | undefined;
|
|
114
|
+
workerId?: string | undefined;
|
|
115
|
+
}
|
|
116
|
+
| undefined;
|
|
117
|
+
representationFilter:
|
|
118
|
+
| undefined
|
|
119
|
+
| {
|
|
120
|
+
fn?: IRepresentationFilter | undefined;
|
|
121
|
+
eval?: string | undefined;
|
|
122
|
+
workerId?: string | undefined;
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
corePlugins: ICorePlugins,
|
|
126
|
+
): {
|
|
127
|
+
manifestLoader: IManifestLoader | undefined;
|
|
128
|
+
segmentLoader: ISegmentLoader | undefined;
|
|
129
|
+
representationFilter: IRepresentationFilter | undefined;
|
|
130
|
+
} {
|
|
131
|
+
let manifestLoader;
|
|
132
|
+
let segmentLoader;
|
|
133
|
+
let representationFilter;
|
|
134
|
+
if (typeof input.representationFilter?.fn === "function") {
|
|
135
|
+
representationFilter = input.representationFilter.fn;
|
|
136
|
+
} else if (typeof input.representationFilter?.eval === "string") {
|
|
137
|
+
representationFilter = createRepresentationFilterFromFnString(
|
|
138
|
+
input.representationFilter.eval,
|
|
139
|
+
);
|
|
140
|
+
} else if (typeof input.representationFilter?.workerId === "string") {
|
|
141
|
+
representationFilter = corePlugins.representationFilters.get(
|
|
142
|
+
input.representationFilter.workerId,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof input.manifestLoader?.fn === "function") {
|
|
147
|
+
manifestLoader = input.manifestLoader.fn;
|
|
148
|
+
} else if (typeof input.manifestLoader?.workerId === "string") {
|
|
149
|
+
manifestLoader = corePlugins.manifestLoaders.get(input.manifestLoader.workerId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (typeof input.segmentLoader?.fn === "function") {
|
|
153
|
+
segmentLoader = input.segmentLoader.fn;
|
|
154
|
+
} else if (typeof input.segmentLoader?.workerId === "string") {
|
|
155
|
+
segmentLoader = corePlugins.segmentLoaders.get(input.segmentLoader.workerId);
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
manifestLoader,
|
|
159
|
+
segmentLoader,
|
|
160
|
+
representationFilter,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import CdnPrioritizer from "../cdn_prioritizer";
|
|
3
|
+
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
8
|
+
|
|
9
|
+
const mockGetCurrent = vi.hoisted(() => {
|
|
10
|
+
return vi.fn(() => ({ DEFAULT_CDN_DOWNGRADE_TIME: 60000 }));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
vi.mock("../../../config", () => ({
|
|
14
|
+
default: { getCurrent: mockGetCurrent },
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("../../../utils/array_find_index", () => ({
|
|
18
|
+
// eslint-disable-next-line no-restricted-properties
|
|
19
|
+
default: (arr: unknown[], fn: (elt: unknown) => boolean) => arr.findIndex(fn),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock("../../../utils/event_emitter", () => {
|
|
23
|
+
class MockEventEmitter {
|
|
24
|
+
trigger = vi.fn();
|
|
25
|
+
addEventListener = vi.fn();
|
|
26
|
+
removeEventListener = vi.fn();
|
|
27
|
+
}
|
|
28
|
+
return { default: MockEventEmitter };
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function makeCancellationSignal(): any {
|
|
32
|
+
let _cb: (() => void) | null = null;
|
|
33
|
+
return {
|
|
34
|
+
signal: {
|
|
35
|
+
register: (cb: () => void) => {
|
|
36
|
+
_cb = cb;
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
cancel: () => _cb?.(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("CdnPrioritizer", () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
vi.useFakeTimers();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
vi.useRealTimers();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns single CDN as-is without prioritization", () => {
|
|
53
|
+
const { signal } = makeCancellationSignal();
|
|
54
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
55
|
+
const cdns = [{ id: "cdn1", baseUrl: "https://cdn1.example.com" }];
|
|
56
|
+
expect(prioritizer.getCdnPreferenceForResource(cdns)).toEqual(cdns);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns multiple CDNs in original order when none are downgraded", () => {
|
|
60
|
+
const { signal } = makeCancellationSignal();
|
|
61
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
62
|
+
const cdns = [
|
|
63
|
+
{ id: "cdn1", baseUrl: "https://cdn1.example.com" },
|
|
64
|
+
{ id: "cdn2", baseUrl: "https://cdn2.example.com" },
|
|
65
|
+
];
|
|
66
|
+
expect(prioritizer.getCdnPreferenceForResource(cdns)).toEqual(cdns);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("puts downgraded CDN last", () => {
|
|
70
|
+
const { signal } = makeCancellationSignal();
|
|
71
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
72
|
+
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
|
|
73
|
+
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
|
|
74
|
+
prioritizer.downgradeCdn(cdn1);
|
|
75
|
+
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
|
|
76
|
+
expect(result).toEqual([cdn2, cdn1]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("triggers priorityChange when downgrading a CDN", () => {
|
|
80
|
+
const { signal } = makeCancellationSignal();
|
|
81
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
82
|
+
const cdn = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
|
|
83
|
+
prioritizer.downgradeCdn(cdn);
|
|
84
|
+
expect((prioritizer as any).trigger).toHaveBeenCalledWith("priorityChange", null);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("triggers priorityChange when downgrade expires", () => {
|
|
88
|
+
mockGetCurrent.mockReturnValue({ DEFAULT_CDN_DOWNGRADE_TIME: 5000 });
|
|
89
|
+
const { signal } = makeCancellationSignal();
|
|
90
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
91
|
+
const cdn = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
|
|
92
|
+
prioritizer.downgradeCdn(cdn);
|
|
93
|
+
const triggerMock = (prioritizer as any).trigger;
|
|
94
|
+
const callCountBefore = triggerMock.mock.calls.length;
|
|
95
|
+
vi.advanceTimersByTime(5000);
|
|
96
|
+
expect(triggerMock.mock.calls.length).toBeGreaterThan(callCountBefore);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("CDN is no longer downgraded after timeout expires", () => {
|
|
100
|
+
mockGetCurrent.mockReturnValue({ DEFAULT_CDN_DOWNGRADE_TIME: 5000 });
|
|
101
|
+
const { signal } = makeCancellationSignal();
|
|
102
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
103
|
+
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
|
|
104
|
+
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
|
|
105
|
+
prioritizer.downgradeCdn(cdn1);
|
|
106
|
+
vi.advanceTimersByTime(5000);
|
|
107
|
+
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
|
|
108
|
+
expect(result[0]).toEqual(cdn1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("re-downgrading a CDN resets its downgrade timer", () => {
|
|
112
|
+
mockGetCurrent.mockReturnValue({ DEFAULT_CDN_DOWNGRADE_TIME: 5000 });
|
|
113
|
+
const { signal } = makeCancellationSignal();
|
|
114
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
115
|
+
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
|
|
116
|
+
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
|
|
117
|
+
|
|
118
|
+
prioritizer.downgradeCdn(cdn1);
|
|
119
|
+
vi.advanceTimersByTime(3000);
|
|
120
|
+
prioritizer.downgradeCdn(cdn1); // reset
|
|
121
|
+
vi.advanceTimersByTime(3000); // only 3s since reset, should still be downgraded
|
|
122
|
+
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
|
|
123
|
+
expect(result[0]).toEqual(cdn2);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("matches CDN by baseUrl when id is undefined", () => {
|
|
127
|
+
const { signal } = makeCancellationSignal();
|
|
128
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
129
|
+
const cdn1 = { baseUrl: "https://cdn1.example.com" } as any;
|
|
130
|
+
const cdn2 = { baseUrl: "https://cdn2.example.com" } as any;
|
|
131
|
+
prioritizer.downgradeCdn(cdn1);
|
|
132
|
+
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
|
|
133
|
+
expect(result).toEqual([cdn2, cdn1]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("clears timeouts and resets on destroy signal", () => {
|
|
137
|
+
const { signal, cancel } = makeCancellationSignal();
|
|
138
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
139
|
+
const cdn = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
|
|
140
|
+
prioritizer.downgradeCdn(cdn);
|
|
141
|
+
cancel();
|
|
142
|
+
// After destroy, downgraded list should be empty
|
|
143
|
+
expect((prioritizer as any)._downgradedCdnList.metadata).toHaveLength(0);
|
|
144
|
+
expect((prioritizer as any)._downgradedCdnList.timeouts).toHaveLength(0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("returns empty array when given empty array", () => {
|
|
148
|
+
const { signal } = makeCancellationSignal();
|
|
149
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
150
|
+
expect(prioritizer.getCdnPreferenceForResource([])).toEqual([]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("handles multiple downgraded CDNs correctly", () => {
|
|
154
|
+
const { signal } = makeCancellationSignal();
|
|
155
|
+
const prioritizer = new CdnPrioritizer(signal);
|
|
156
|
+
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
|
|
157
|
+
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
|
|
158
|
+
const cdn3 = { id: "cdn3", baseUrl: "https://cdn3.example.com" };
|
|
159
|
+
prioritizer.downgradeCdn(cdn1);
|
|
160
|
+
prioritizer.downgradeCdn(cdn2);
|
|
161
|
+
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2, cdn3]);
|
|
162
|
+
expect(result[0]).toEqual(cdn3);
|
|
163
|
+
expect(result).toContain(cdn1);
|
|
164
|
+
expect(result).toContain(cdn2);
|
|
165
|
+
});
|
|
166
|
+
});
|