rx-player 4.5.0-dev.2026012302 → 4.5.0-dev.2026021900
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 +9 -1
- 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/entry/FreezeResolver.js +2 -2
- 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/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/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/commonjs/main_thread/api/public_api.js +27 -3
- 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/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/entry/FreezeResolver.js +1 -1
- 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/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/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/es2017/main_thread/api/public_api.js +27 -3
- 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/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 +6 -6
- package/package.json +9 -3
- 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__/guess_based_chooser.test.ts +790 -0
- package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
- package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +285 -0
- package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +247 -0
- 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 +868 -0
- package/src/core/entry/content_time_boundaries_observer.ts +10 -0
- package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +592 -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/main_thread/api/public_api.ts +32 -3
- 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/utils/base64.ts +30 -1
- package/src/utils/ranges.ts +1 -1
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import sleep from "../../../utils/sleep";
|
|
3
|
+
import type { IBufferType } from "../../segment_sinks";
|
|
4
|
+
import ContentTimeBoundariesObserver from "../content_time_boundaries_observer";
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
9
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
mockTrigger,
|
|
13
|
+
mockCancellerSignal,
|
|
14
|
+
mockCanceller,
|
|
15
|
+
mockPlaybackObserverListen,
|
|
16
|
+
mockManifestEventListener,
|
|
17
|
+
mockGetMinimumSafePosition,
|
|
18
|
+
mockGetMaximumSafePosition,
|
|
19
|
+
mockGetLastAvailablePosition,
|
|
20
|
+
mockGetEnd,
|
|
21
|
+
} = vi.hoisted(() => ({
|
|
22
|
+
mockTrigger: vi.fn(),
|
|
23
|
+
mockCancellerSignal: { isCancelled: vi.fn(() => false) },
|
|
24
|
+
mockCanceller: {
|
|
25
|
+
signal: { isCancelled: vi.fn(() => false) },
|
|
26
|
+
isUsed: vi.fn(() => false),
|
|
27
|
+
cancel: vi.fn(),
|
|
28
|
+
},
|
|
29
|
+
mockPlaybackObserverListen: vi.fn(),
|
|
30
|
+
mockManifestEventListener: vi.fn(),
|
|
31
|
+
mockGetMinimumSafePosition: vi.fn(() => 0),
|
|
32
|
+
mockGetMaximumSafePosition: vi.fn((): number | undefined => 100),
|
|
33
|
+
mockGetLastAvailablePosition: vi.fn(),
|
|
34
|
+
mockGetEnd: vi.fn(),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock("../../../utils/event_emitter", () => ({
|
|
38
|
+
default: class EventEmitter {
|
|
39
|
+
trigger = mockTrigger;
|
|
40
|
+
removeEventListener = vi.fn();
|
|
41
|
+
addEventListener = vi.fn();
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
vi.mock("../../../utils/task_canceller", () => ({
|
|
46
|
+
default: class TaskCanceller {
|
|
47
|
+
signal = mockCancellerSignal;
|
|
48
|
+
isUsed = mockCanceller.isUsed;
|
|
49
|
+
cancel = mockCanceller.cancel;
|
|
50
|
+
},
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
vi.mock("../../../utils/queue_microtask", () => ({
|
|
54
|
+
default: (fn: () => void) => Promise.resolve().then(() => fn()),
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
describe("ContentTimeBoundariesObserver", () => {
|
|
58
|
+
const createMockManifest = (overrides = {}): any => ({
|
|
59
|
+
periods: [],
|
|
60
|
+
isLastPeriodKnown: false,
|
|
61
|
+
isDynamic: false,
|
|
62
|
+
getMinimumSafePosition: mockGetMinimumSafePosition,
|
|
63
|
+
getMaximumSafePosition: mockGetMaximumSafePosition,
|
|
64
|
+
addEventListener: mockManifestEventListener,
|
|
65
|
+
...overrides,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const createMockPlaybackObserver = (): any => ({
|
|
69
|
+
listen: mockPlaybackObserverListen,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const createMockPeriod = (id: string, start: number = 0): any => ({
|
|
73
|
+
id,
|
|
74
|
+
start,
|
|
75
|
+
duration: 10,
|
|
76
|
+
end: start + 10,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const createMockAdaptation = (representations: any[] = []): any => ({
|
|
80
|
+
id: "adaptation-1",
|
|
81
|
+
representations,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const createMockRepresentation = (): any => ({
|
|
85
|
+
id: "representation-1",
|
|
86
|
+
index: {
|
|
87
|
+
getLastAvailablePosition: mockGetLastAvailablePosition,
|
|
88
|
+
getEnd: mockGetEnd,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
vi.clearAllMocks();
|
|
94
|
+
mockGetMinimumSafePosition.mockReturnValue(0);
|
|
95
|
+
mockGetMaximumSafePosition.mockReturnValue(100);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
vi.clearAllMocks();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("constructor", () => {
|
|
103
|
+
it("should initialize and set up playback observer", async () => {
|
|
104
|
+
const manifest = createMockManifest();
|
|
105
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
106
|
+
const bufferTypes: IBufferType[] = ["audio", "video"];
|
|
107
|
+
|
|
108
|
+
new ContentTimeBoundariesObserver(manifest, playbackObserver, bufferTypes);
|
|
109
|
+
await sleep(0);
|
|
110
|
+
expect(mockPlaybackObserverListen).toHaveBeenCalledWith(
|
|
111
|
+
expect.any(Function),
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
includeLastObservation: false,
|
|
114
|
+
clearSignal: mockCancellerSignal,
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should set up manifest event listener", async () => {
|
|
120
|
+
const manifest = createMockManifest();
|
|
121
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
122
|
+
|
|
123
|
+
new ContentTimeBoundariesObserver(manifest, playbackObserver, ["audio"]);
|
|
124
|
+
await sleep(0);
|
|
125
|
+
|
|
126
|
+
expect(mockManifestEventListener).toHaveBeenCalledWith(
|
|
127
|
+
"manifestUpdate",
|
|
128
|
+
expect.any(Function),
|
|
129
|
+
mockCancellerSignal,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should trigger warning when position is before manifest minimum", async () => {
|
|
134
|
+
mockGetMinimumSafePosition.mockReturnValue(10);
|
|
135
|
+
const manifest = createMockManifest();
|
|
136
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
137
|
+
|
|
138
|
+
new ContentTimeBoundariesObserver(manifest, playbackObserver, ["audio"]);
|
|
139
|
+
await sleep(0);
|
|
140
|
+
|
|
141
|
+
const positionCallback = mockPlaybackObserverListen.mock.calls[0][0];
|
|
142
|
+
positionCallback({ position: { getWanted: () => 5 } });
|
|
143
|
+
|
|
144
|
+
expect(mockTrigger).toHaveBeenCalledWith("warning", expect.any(Object));
|
|
145
|
+
const warning = mockTrigger.mock.calls[0][1];
|
|
146
|
+
expect(warning.code).toBe("MEDIA_TIME_BEFORE_MANIFEST");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should trigger warning when position is after manifest maximum", async () => {
|
|
150
|
+
mockGetMaximumSafePosition.mockReturnValue(100);
|
|
151
|
+
const manifest = createMockManifest();
|
|
152
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
153
|
+
|
|
154
|
+
new ContentTimeBoundariesObserver(manifest, playbackObserver, ["audio"]);
|
|
155
|
+
await sleep(0);
|
|
156
|
+
|
|
157
|
+
const positionCallback = mockPlaybackObserverListen.mock.calls[0][0];
|
|
158
|
+
positionCallback({ position: { getWanted: () => 150 } });
|
|
159
|
+
|
|
160
|
+
expect(mockTrigger).toHaveBeenCalledWith("warning", expect.any(Object));
|
|
161
|
+
const warning = mockTrigger.mock.calls[0][1];
|
|
162
|
+
expect(warning.code).toBe("MEDIA_TIME_AFTER_MANIFEST");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("getCurrentEndingTime", () => {
|
|
167
|
+
it("should return ending position when manifest is not dynamic", async () => {
|
|
168
|
+
mockGetMaximumSafePosition.mockReturnValue(100);
|
|
169
|
+
const manifest = createMockManifest({ isDynamic: false });
|
|
170
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
171
|
+
|
|
172
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
173
|
+
"audio",
|
|
174
|
+
]);
|
|
175
|
+
await sleep(0);
|
|
176
|
+
|
|
177
|
+
const result = observer.getCurrentEndingTime();
|
|
178
|
+
|
|
179
|
+
expect(result).toEqual({
|
|
180
|
+
isEnd: true,
|
|
181
|
+
endingPosition: 100,
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should return isEnd: true when ending position is determined for dynamic content", () => {
|
|
186
|
+
const representation = createMockRepresentation();
|
|
187
|
+
mockGetEnd.mockReturnValue(100);
|
|
188
|
+
const adaptation = createMockAdaptation([representation]);
|
|
189
|
+
const period = createMockPeriod("period-1");
|
|
190
|
+
const manifest = createMockManifest({
|
|
191
|
+
isDynamic: true,
|
|
192
|
+
isLastPeriodKnown: true,
|
|
193
|
+
periods: [period],
|
|
194
|
+
});
|
|
195
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
196
|
+
|
|
197
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
198
|
+
"audio",
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
observer.onAdaptationChange("audio", period, adaptation);
|
|
202
|
+
|
|
203
|
+
const result = observer.getCurrentEndingTime();
|
|
204
|
+
|
|
205
|
+
expect(result).toEqual({
|
|
206
|
+
isEnd: true,
|
|
207
|
+
endingPosition: 100,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should return isEnd: false when ending position is undetermined for non-dynamic content", () => {
|
|
212
|
+
mockGetMaximumSafePosition.mockReturnValue(undefined);
|
|
213
|
+
const manifest = createMockManifest({ isDynamic: false });
|
|
214
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
215
|
+
|
|
216
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
217
|
+
"audio",
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
const result = observer.getCurrentEndingTime();
|
|
221
|
+
|
|
222
|
+
expect(result).toEqual({
|
|
223
|
+
isEnd: false,
|
|
224
|
+
endingPosition: undefined,
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should return isEnd: false when ending position is undetermined for dynamic content", () => {
|
|
229
|
+
const representation = createMockRepresentation();
|
|
230
|
+
mockGetEnd.mockReturnValue(100);
|
|
231
|
+
const adaptation = createMockAdaptation([representation]);
|
|
232
|
+
const period = createMockPeriod("period-1");
|
|
233
|
+
const manifest = createMockManifest({
|
|
234
|
+
isDynamic: true,
|
|
235
|
+
isLastPeriodKnown: false,
|
|
236
|
+
periods: [period],
|
|
237
|
+
});
|
|
238
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
239
|
+
|
|
240
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
241
|
+
"audio",
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
observer.onAdaptationChange("audio", period, adaptation);
|
|
245
|
+
|
|
246
|
+
const result = observer.getCurrentEndingTime();
|
|
247
|
+
|
|
248
|
+
expect(result).toEqual({
|
|
249
|
+
isEnd: false,
|
|
250
|
+
endingPosition: 100,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("onAdaptationChange", () => {
|
|
256
|
+
it("should trigger endingPositionChange for audio adaptation on last period", () => {
|
|
257
|
+
const representation = createMockRepresentation();
|
|
258
|
+
mockGetLastAvailablePosition.mockReturnValue(80);
|
|
259
|
+
const adaptation = createMockAdaptation([representation]);
|
|
260
|
+
const period = createMockPeriod("period-1");
|
|
261
|
+
const manifest = createMockManifest({
|
|
262
|
+
isLastPeriodKnown: true,
|
|
263
|
+
periods: [period],
|
|
264
|
+
});
|
|
265
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
266
|
+
|
|
267
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
268
|
+
"audio",
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
observer.onAdaptationChange("audio", period, adaptation);
|
|
272
|
+
|
|
273
|
+
expect(mockTrigger).toHaveBeenCalledWith(
|
|
274
|
+
"endingPositionChange",
|
|
275
|
+
expect.objectContaining({
|
|
276
|
+
endingPosition: expect.any(Number),
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("should trigger endingPositionChange for video adaptation on last period", () => {
|
|
282
|
+
const representation = createMockRepresentation();
|
|
283
|
+
mockGetLastAvailablePosition.mockReturnValue(90);
|
|
284
|
+
const adaptation = createMockAdaptation([representation]);
|
|
285
|
+
const period = createMockPeriod("period-1");
|
|
286
|
+
const manifest = createMockManifest({
|
|
287
|
+
isLastPeriodKnown: true,
|
|
288
|
+
periods: [period],
|
|
289
|
+
});
|
|
290
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
291
|
+
|
|
292
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
293
|
+
"video",
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
observer.onAdaptationChange("video", period, adaptation);
|
|
297
|
+
|
|
298
|
+
expect(mockTrigger).toHaveBeenCalledWith(
|
|
299
|
+
"endingPositionChange",
|
|
300
|
+
expect.objectContaining({
|
|
301
|
+
endingPosition: expect.any(Number),
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should not trigger events after canceller is used", () => {
|
|
307
|
+
mockCanceller.isUsed.mockReturnValue(true);
|
|
308
|
+
const manifest = createMockManifest();
|
|
309
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
310
|
+
const period = createMockPeriod("period-1");
|
|
311
|
+
|
|
312
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
313
|
+
"audio",
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
mockTrigger.mockClear();
|
|
317
|
+
observer.onAdaptationChange("audio", period, null);
|
|
318
|
+
|
|
319
|
+
// Only manifestUpdate related triggers should have been called
|
|
320
|
+
const periodChangeCalls = mockTrigger.mock.calls.filter(
|
|
321
|
+
(call) => call[0] === "periodChange",
|
|
322
|
+
);
|
|
323
|
+
expect(periodChangeCalls).toHaveLength(0);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("should calculate minimum of audio and video positions", () => {
|
|
327
|
+
const audioRep = createMockRepresentation();
|
|
328
|
+
const videoRep = createMockRepresentation();
|
|
329
|
+
let i = 0;
|
|
330
|
+
mockGetLastAvailablePosition.mockImplementation(() => {
|
|
331
|
+
i++;
|
|
332
|
+
return (10 - i) * 10;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const audioAdaptation = createMockAdaptation([audioRep]);
|
|
336
|
+
const videoAdaptation = createMockAdaptation([videoRep]);
|
|
337
|
+
const period = createMockPeriod("period-1");
|
|
338
|
+
const manifest = createMockManifest({
|
|
339
|
+
isLastPeriodKnown: true,
|
|
340
|
+
periods: [period],
|
|
341
|
+
});
|
|
342
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
343
|
+
|
|
344
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
345
|
+
"audio",
|
|
346
|
+
"video",
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
observer.onAdaptationChange("audio", period, audioAdaptation);
|
|
350
|
+
observer.onAdaptationChange("video", period, videoAdaptation);
|
|
351
|
+
|
|
352
|
+
const result = observer.getCurrentEndingTime();
|
|
353
|
+
|
|
354
|
+
expect(result.endingPosition).toBe(60);
|
|
355
|
+
expect(i).toEqual(4);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe("onRepresentationChange", () => {
|
|
360
|
+
it("should add period to active periods", () => {
|
|
361
|
+
const manifest = createMockManifest();
|
|
362
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
363
|
+
const period1 = createMockPeriod("period-1", 0);
|
|
364
|
+
|
|
365
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
366
|
+
"audio",
|
|
367
|
+
]);
|
|
368
|
+
|
|
369
|
+
observer.onRepresentationChange("audio", period1);
|
|
370
|
+
|
|
371
|
+
// Should trigger periodChange when period becomes active across all buffer types
|
|
372
|
+
expect(mockTrigger).toHaveBeenCalledWith("periodChange", period1);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe("onPeriodCleared", () => {
|
|
377
|
+
it("should remove period from active periods", () => {
|
|
378
|
+
const manifest = createMockManifest();
|
|
379
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
380
|
+
const period1 = createMockPeriod("period-1", 0);
|
|
381
|
+
const period2 = createMockPeriod("period-2", 10);
|
|
382
|
+
|
|
383
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
384
|
+
"audio",
|
|
385
|
+
]);
|
|
386
|
+
|
|
387
|
+
observer.onRepresentationChange("audio", period1);
|
|
388
|
+
observer.onRepresentationChange("audio", period2);
|
|
389
|
+
|
|
390
|
+
mockTrigger.mockClear();
|
|
391
|
+
|
|
392
|
+
observer.onPeriodCleared("audio", period1);
|
|
393
|
+
|
|
394
|
+
// Should trigger periodChange for period2
|
|
395
|
+
expect(mockTrigger).toHaveBeenCalledWith("periodChange", period2);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should handle clearing non-existent period gracefully", () => {
|
|
399
|
+
const manifest = createMockManifest();
|
|
400
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
401
|
+
const period1 = createMockPeriod("period-1", 0);
|
|
402
|
+
|
|
403
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
404
|
+
"audio",
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
// Should not throw
|
|
408
|
+
expect(() => {
|
|
409
|
+
observer.onPeriodCleared("audio", period1);
|
|
410
|
+
}).not.toThrow();
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("periodChange event", () => {
|
|
415
|
+
it("should trigger periodChange only when period is active in all buffer types", () => {
|
|
416
|
+
const manifest = createMockManifest();
|
|
417
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
418
|
+
const period = createMockPeriod("period-1", 0);
|
|
419
|
+
|
|
420
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
421
|
+
"audio",
|
|
422
|
+
"video",
|
|
423
|
+
]);
|
|
424
|
+
|
|
425
|
+
mockTrigger.mockClear();
|
|
426
|
+
|
|
427
|
+
observer.onRepresentationChange("audio", period);
|
|
428
|
+
|
|
429
|
+
// Should not trigger yet - not active in video
|
|
430
|
+
const periodChangeCalls = mockTrigger.mock.calls.filter(
|
|
431
|
+
(call) => call[0] === "periodChange",
|
|
432
|
+
);
|
|
433
|
+
expect(periodChangeCalls).toHaveLength(0);
|
|
434
|
+
|
|
435
|
+
observer.onRepresentationChange("video", period);
|
|
436
|
+
|
|
437
|
+
// Now should trigger
|
|
438
|
+
expect(mockTrigger).toHaveBeenCalledWith("periodChange", period);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("should not trigger periodChange multiple times for same period", () => {
|
|
442
|
+
const manifest = createMockManifest();
|
|
443
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
444
|
+
const period = createMockPeriod("period-1", 0);
|
|
445
|
+
|
|
446
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
447
|
+
"audio",
|
|
448
|
+
]);
|
|
449
|
+
|
|
450
|
+
observer.onRepresentationChange("audio", period);
|
|
451
|
+
|
|
452
|
+
mockTrigger.mockClear();
|
|
453
|
+
|
|
454
|
+
observer.onRepresentationChange("audio", period);
|
|
455
|
+
|
|
456
|
+
const periodChangeCalls = mockTrigger.mock.calls.filter(
|
|
457
|
+
(call) => call[0] === "periodChange",
|
|
458
|
+
);
|
|
459
|
+
expect(periodChangeCalls).toHaveLength(0);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("should trigger periodChange when switching to different period", () => {
|
|
463
|
+
const manifest = createMockManifest();
|
|
464
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
465
|
+
const period1 = createMockPeriod("period-1", 0);
|
|
466
|
+
const period2 = createMockPeriod("period-2", 10);
|
|
467
|
+
const adap1 = createMockAdaptation([]);
|
|
468
|
+
const adap2 = createMockAdaptation([]);
|
|
469
|
+
|
|
470
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
471
|
+
"audio",
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
observer.onAdaptationChange("audio", period1, adap1);
|
|
475
|
+
observer.onRepresentationChange("audio", period1);
|
|
476
|
+
expect(mockTrigger).toHaveBeenCalledWith("periodChange", period1);
|
|
477
|
+
mockTrigger.mockClear();
|
|
478
|
+
observer.onPeriodCleared("audio", period1);
|
|
479
|
+
observer.onAdaptationChange("audio", period2, adap2);
|
|
480
|
+
observer.onRepresentationChange("audio", period2);
|
|
481
|
+
expect(mockTrigger).toHaveBeenCalledWith("periodChange", period2);
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
describe("onLastSegmentFinishedLoading", () => {
|
|
486
|
+
it("should trigger endOfStream when all buffer types finished loading", () => {
|
|
487
|
+
const manifest = createMockManifest({ isLastPeriodKnown: true });
|
|
488
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
489
|
+
|
|
490
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
491
|
+
"audio",
|
|
492
|
+
"video",
|
|
493
|
+
]);
|
|
494
|
+
|
|
495
|
+
mockTrigger.mockClear();
|
|
496
|
+
|
|
497
|
+
observer.onLastSegmentFinishedLoading("audio");
|
|
498
|
+
observer.onLastSegmentFinishedLoading("video");
|
|
499
|
+
|
|
500
|
+
expect(mockTrigger).toHaveBeenCalledWith("endOfStream", null);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("should not trigger endOfStream if not all buffer types finished", () => {
|
|
504
|
+
const manifest = createMockManifest({ isLastPeriodKnown: true });
|
|
505
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
506
|
+
|
|
507
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
508
|
+
"audio",
|
|
509
|
+
"video",
|
|
510
|
+
]);
|
|
511
|
+
|
|
512
|
+
mockTrigger.mockClear();
|
|
513
|
+
|
|
514
|
+
observer.onLastSegmentFinishedLoading("audio");
|
|
515
|
+
|
|
516
|
+
const endOfStreamCalls = mockTrigger.mock.calls.filter(
|
|
517
|
+
(call) => call[0] === "endOfStream",
|
|
518
|
+
);
|
|
519
|
+
expect(endOfStreamCalls).toHaveLength(0);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("should not trigger endOfStream if last period is not known", () => {
|
|
523
|
+
const manifest = createMockManifest({ isLastPeriodKnown: false });
|
|
524
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
525
|
+
|
|
526
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
527
|
+
"audio",
|
|
528
|
+
]);
|
|
529
|
+
|
|
530
|
+
mockTrigger.mockClear();
|
|
531
|
+
|
|
532
|
+
observer.onLastSegmentFinishedLoading("audio");
|
|
533
|
+
|
|
534
|
+
const endOfStreamCalls = mockTrigger.mock.calls.filter(
|
|
535
|
+
(call) => call[0] === "endOfStream",
|
|
536
|
+
);
|
|
537
|
+
expect(endOfStreamCalls).toHaveLength(0);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("should not trigger multiple times for same buffer type", () => {
|
|
541
|
+
const manifest = createMockManifest({ isLastPeriodKnown: true });
|
|
542
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
543
|
+
|
|
544
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
545
|
+
"audio",
|
|
546
|
+
]);
|
|
547
|
+
|
|
548
|
+
observer.onLastSegmentFinishedLoading("audio");
|
|
549
|
+
|
|
550
|
+
mockTrigger.mockClear();
|
|
551
|
+
|
|
552
|
+
observer.onLastSegmentFinishedLoading("audio");
|
|
553
|
+
|
|
554
|
+
const endOfStreamCalls = mockTrigger.mock.calls.filter(
|
|
555
|
+
(call) => call[0] === "endOfStream",
|
|
556
|
+
);
|
|
557
|
+
expect(endOfStreamCalls).toHaveLength(0);
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
describe("onLastSegmentLoadingResume", () => {
|
|
562
|
+
it("should trigger resumeStream after endOfStream", () => {
|
|
563
|
+
const manifest = createMockManifest({ isLastPeriodKnown: true });
|
|
564
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
565
|
+
|
|
566
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
567
|
+
"audio",
|
|
568
|
+
"video",
|
|
569
|
+
]);
|
|
570
|
+
|
|
571
|
+
observer.onLastSegmentFinishedLoading("audio");
|
|
572
|
+
observer.onLastSegmentFinishedLoading("video");
|
|
573
|
+
|
|
574
|
+
mockTrigger.mockClear();
|
|
575
|
+
|
|
576
|
+
observer.onLastSegmentLoadingResume("audio");
|
|
577
|
+
|
|
578
|
+
expect(mockTrigger).toHaveBeenCalledWith("resumeStream", null);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("should not trigger if already in loading state", () => {
|
|
582
|
+
const manifest = createMockManifest({ isLastPeriodKnown: true });
|
|
583
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
584
|
+
|
|
585
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
586
|
+
"audio",
|
|
587
|
+
]);
|
|
588
|
+
|
|
589
|
+
observer.onLastSegmentLoadingResume("audio");
|
|
590
|
+
|
|
591
|
+
mockTrigger.mockClear();
|
|
592
|
+
|
|
593
|
+
observer.onLastSegmentLoadingResume("audio");
|
|
594
|
+
|
|
595
|
+
const resumeStreamCalls = mockTrigger.mock.calls.filter(
|
|
596
|
+
(call) => call[0] === "resumeStream",
|
|
597
|
+
);
|
|
598
|
+
expect(resumeStreamCalls).toHaveLength(0);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
describe("manifestUpdate event", () => {
|
|
603
|
+
it("should trigger endingPositionChange on manifest update", () => {
|
|
604
|
+
const manifest = createMockManifest();
|
|
605
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
606
|
+
|
|
607
|
+
new ContentTimeBoundariesObserver(manifest, playbackObserver, ["audio"]);
|
|
608
|
+
|
|
609
|
+
const manifestUpdateCallback = mockManifestEventListener.mock.calls[0][1];
|
|
610
|
+
|
|
611
|
+
mockTrigger.mockClear();
|
|
612
|
+
manifestUpdateCallback();
|
|
613
|
+
|
|
614
|
+
expect(mockTrigger).toHaveBeenCalledWith(
|
|
615
|
+
"endingPositionChange",
|
|
616
|
+
expect.objectContaining({
|
|
617
|
+
endingPosition: expect.any(Number),
|
|
618
|
+
isEnd: expect.any(Boolean),
|
|
619
|
+
}),
|
|
620
|
+
);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("should not trigger events if canceller is cancelled", () => {
|
|
624
|
+
mockCancellerSignal.isCancelled.mockReturnValue(true);
|
|
625
|
+
const manifest = createMockManifest();
|
|
626
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
627
|
+
|
|
628
|
+
new ContentTimeBoundariesObserver(manifest, playbackObserver, ["audio"]);
|
|
629
|
+
|
|
630
|
+
const manifestUpdateCallback = mockManifestEventListener.mock.calls[0][1];
|
|
631
|
+
|
|
632
|
+
mockTrigger.mockClear();
|
|
633
|
+
manifestUpdateCallback();
|
|
634
|
+
|
|
635
|
+
// endingPositionChange should still be called, but endOfStream check skipped
|
|
636
|
+
const endOfStreamCalls = mockTrigger.mock.calls.filter(
|
|
637
|
+
(call) => call[0] === "endOfStream",
|
|
638
|
+
);
|
|
639
|
+
expect(endOfStreamCalls).toHaveLength(0);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
describe("dispose", () => {
|
|
644
|
+
it("should cancel operations with provided reason", () => {
|
|
645
|
+
const manifest = createMockManifest();
|
|
646
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
647
|
+
|
|
648
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
649
|
+
"audio",
|
|
650
|
+
]);
|
|
651
|
+
|
|
652
|
+
observer.dispose("test reason");
|
|
653
|
+
|
|
654
|
+
expect(mockCanceller.cancel).toHaveBeenCalledWith("test reason");
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it("should cancel operations with default reason when none provided", () => {
|
|
658
|
+
const manifest = createMockManifest();
|
|
659
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
660
|
+
|
|
661
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
662
|
+
"audio",
|
|
663
|
+
]);
|
|
664
|
+
|
|
665
|
+
observer.dispose(undefined);
|
|
666
|
+
|
|
667
|
+
expect(mockCanceller.cancel).toHaveBeenCalledWith(
|
|
668
|
+
"ContentTimeBoundariesObserver dispose",
|
|
669
|
+
);
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
describe("MaximumPositionCalculator", () => {
|
|
674
|
+
describe("getMaximumAvailablePosition", () => {
|
|
675
|
+
it("should return manifest safe position for dynamic content", () => {
|
|
676
|
+
mockGetMaximumSafePosition.mockReturnValue(120);
|
|
677
|
+
const manifest = createMockManifest({ isDynamic: true });
|
|
678
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
679
|
+
|
|
680
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
681
|
+
"audio",
|
|
682
|
+
]);
|
|
683
|
+
|
|
684
|
+
const result = observer.getCurrentEndingTime();
|
|
685
|
+
|
|
686
|
+
expect(result.endingPosition).toBe(120);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("should return manifest safe position when adaptations not set", () => {
|
|
690
|
+
mockGetMaximumSafePosition.mockReturnValue(100);
|
|
691
|
+
const manifest = createMockManifest({ isDynamic: false });
|
|
692
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
693
|
+
|
|
694
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
695
|
+
"audio",
|
|
696
|
+
"video",
|
|
697
|
+
]);
|
|
698
|
+
|
|
699
|
+
const result = observer.getCurrentEndingTime();
|
|
700
|
+
|
|
701
|
+
expect(result.endingPosition).toBe(100);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it("should handle null audio adaptation", () => {
|
|
705
|
+
const videoRep = createMockRepresentation();
|
|
706
|
+
mockGetLastAvailablePosition.mockReturnValue(90);
|
|
707
|
+
const videoAdaptation = createMockAdaptation([videoRep]);
|
|
708
|
+
const period = createMockPeriod("period-1");
|
|
709
|
+
const manifest = createMockManifest({
|
|
710
|
+
isDynamic: false,
|
|
711
|
+
isLastPeriodKnown: true,
|
|
712
|
+
periods: [period],
|
|
713
|
+
});
|
|
714
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
715
|
+
|
|
716
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
717
|
+
"audio",
|
|
718
|
+
"video",
|
|
719
|
+
]);
|
|
720
|
+
|
|
721
|
+
observer.onAdaptationChange("audio", period, null);
|
|
722
|
+
observer.onAdaptationChange("video", period, videoAdaptation);
|
|
723
|
+
|
|
724
|
+
const result = observer.getCurrentEndingTime();
|
|
725
|
+
|
|
726
|
+
expect(result.endingPosition).toBe(90);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it("should handle null video adaptation", () => {
|
|
730
|
+
const audioRep = createMockRepresentation();
|
|
731
|
+
mockGetLastAvailablePosition.mockReturnValue(85);
|
|
732
|
+
const audioAdaptation = createMockAdaptation([audioRep]);
|
|
733
|
+
const period = createMockPeriod("period-1");
|
|
734
|
+
const manifest = createMockManifest({
|
|
735
|
+
isDynamic: false,
|
|
736
|
+
isLastPeriodKnown: true,
|
|
737
|
+
periods: [period],
|
|
738
|
+
});
|
|
739
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
740
|
+
|
|
741
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
742
|
+
"audio",
|
|
743
|
+
"video",
|
|
744
|
+
]);
|
|
745
|
+
|
|
746
|
+
observer.onAdaptationChange("audio", period, audioAdaptation);
|
|
747
|
+
observer.onAdaptationChange("video", period, null);
|
|
748
|
+
|
|
749
|
+
const result = observer.getCurrentEndingTime();
|
|
750
|
+
|
|
751
|
+
expect(result.endingPosition).toBe(85);
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
it("should handle both adaptations being null", () => {
|
|
755
|
+
mockGetMaximumSafePosition.mockReturnValue(100);
|
|
756
|
+
const period = createMockPeriod("period-1");
|
|
757
|
+
const manifest = createMockManifest({
|
|
758
|
+
isDynamic: false,
|
|
759
|
+
isLastPeriodKnown: true,
|
|
760
|
+
periods: [period],
|
|
761
|
+
});
|
|
762
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
763
|
+
|
|
764
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
765
|
+
"audio",
|
|
766
|
+
"video",
|
|
767
|
+
]);
|
|
768
|
+
|
|
769
|
+
observer.onAdaptationChange("audio", period, null);
|
|
770
|
+
observer.onAdaptationChange("video", period, null);
|
|
771
|
+
|
|
772
|
+
const result = observer.getCurrentEndingTime();
|
|
773
|
+
|
|
774
|
+
expect(result.endingPosition).toBe(100);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it("should fallback to manifest position when representation returns undefined", () => {
|
|
778
|
+
mockGetMaximumSafePosition.mockReturnValue(100);
|
|
779
|
+
const audioRep = createMockRepresentation();
|
|
780
|
+
mockGetLastAvailablePosition.mockReturnValue(undefined);
|
|
781
|
+
const audioAdaptation = createMockAdaptation([audioRep]);
|
|
782
|
+
const period = createMockPeriod("period-1");
|
|
783
|
+
const manifest = createMockManifest({
|
|
784
|
+
isDynamic: false,
|
|
785
|
+
isLastPeriodKnown: true,
|
|
786
|
+
periods: [period],
|
|
787
|
+
});
|
|
788
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
789
|
+
|
|
790
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
791
|
+
"audio",
|
|
792
|
+
]);
|
|
793
|
+
|
|
794
|
+
observer.onAdaptationChange("audio", period, audioAdaptation);
|
|
795
|
+
|
|
796
|
+
const result = observer.getCurrentEndingTime();
|
|
797
|
+
|
|
798
|
+
expect(result.endingPosition).toBe(100);
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
describe("getEndingPosition for dynamic content", () => {
|
|
803
|
+
it("should return undefined when adaptations not set", () => {
|
|
804
|
+
const manifest = createMockManifest({ isDynamic: true });
|
|
805
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
806
|
+
|
|
807
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
808
|
+
"audio",
|
|
809
|
+
"video",
|
|
810
|
+
]);
|
|
811
|
+
|
|
812
|
+
const result = observer.getCurrentEndingTime();
|
|
813
|
+
|
|
814
|
+
expect(result.isEnd).toBe(false);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it("should return ending position from audio when video is null", () => {
|
|
818
|
+
const audioRep = createMockRepresentation();
|
|
819
|
+
mockGetEnd.mockReturnValue(95);
|
|
820
|
+
const audioAdaptation = createMockAdaptation([audioRep]);
|
|
821
|
+
const period = createMockPeriod("period-1");
|
|
822
|
+
const manifest = createMockManifest({
|
|
823
|
+
isDynamic: true,
|
|
824
|
+
isLastPeriodKnown: true,
|
|
825
|
+
periods: [period],
|
|
826
|
+
});
|
|
827
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
828
|
+
|
|
829
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
830
|
+
"audio",
|
|
831
|
+
"video",
|
|
832
|
+
]);
|
|
833
|
+
|
|
834
|
+
observer.onAdaptationChange("audio", period, audioAdaptation);
|
|
835
|
+
observer.onAdaptationChange("video", period, null);
|
|
836
|
+
|
|
837
|
+
const result = observer.getCurrentEndingTime();
|
|
838
|
+
|
|
839
|
+
expect(result).toEqual({
|
|
840
|
+
isEnd: true,
|
|
841
|
+
endingPosition: 95,
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
it("should return undefined when both adaptations are null", () => {
|
|
846
|
+
const period = createMockPeriod("period-1");
|
|
847
|
+
const manifest = createMockManifest({
|
|
848
|
+
isDynamic: true,
|
|
849
|
+
isLastPeriodKnown: true,
|
|
850
|
+
periods: [period],
|
|
851
|
+
});
|
|
852
|
+
const playbackObserver = createMockPlaybackObserver();
|
|
853
|
+
|
|
854
|
+
const observer = new ContentTimeBoundariesObserver(manifest, playbackObserver, [
|
|
855
|
+
"audio",
|
|
856
|
+
"video",
|
|
857
|
+
]);
|
|
858
|
+
|
|
859
|
+
observer.onAdaptationChange("audio", period, null);
|
|
860
|
+
observer.onAdaptationChange("video", period, null);
|
|
861
|
+
|
|
862
|
+
const result = observer.getCurrentEndingTime();
|
|
863
|
+
|
|
864
|
+
expect(result.isEnd).toBe(false);
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
});
|