rx-player 4.5.0-dev.2026033100 → 4.5.0-dev.2026041501
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 +22 -5
- 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/can_patch_out_pssh.d.ts +42 -0
- package/dist/commonjs/compat/can_patch_out_pssh.d.ts.map +1 -0
- package/dist/commonjs/compat/can_patch_out_pssh.js +53 -0
- package/dist/commonjs/compat/env_detector.d.ts +2 -0
- package/dist/commonjs/compat/env_detector.d.ts.map +1 -1
- package/dist/commonjs/compat/env_detector.js +5 -0
- package/dist/commonjs/core/adaptive/network_analyzer.d.ts +1 -2
- package/dist/commonjs/core/adaptive/network_analyzer.d.ts.map +1 -1
- package/dist/commonjs/core/adaptive/network_analyzer.js +3 -3
- package/dist/commonjs/core/adaptive/utils/representation_score_calculator.js +2 -2
- package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
- package/dist/commonjs/core/cmcd/cmcd_data_builder.js +9 -3
- package/dist/commonjs/core/entry/content_preparer.d.ts.map +1 -1
- package/dist/commonjs/core/entry/content_preparer.js +5 -7
- package/dist/commonjs/core/entry/core_entry.d.ts.map +1 -1
- package/dist/commonjs/core/entry/core_entry.js +6 -2
- package/dist/commonjs/core/entry/core_text_displayer_interface.js +3 -3
- package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.d.ts.map +1 -1
- package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.js +12 -0
- package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.js +1 -1
- package/dist/commonjs/core/fetchers/utils/schedule_request.d.ts.map +1 -1
- package/dist/commonjs/core/fetchers/utils/schedule_request.js +4 -3
- package/dist/commonjs/core/segment_sinks/garbage_collector.d.ts +0 -2
- package/dist/commonjs/core/segment_sinks/garbage_collector.d.ts.map +1 -1
- package/dist/commonjs/core/segment_sinks/garbage_collector.js +0 -3
- package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.d.ts +1 -1
- package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.d.ts.map +1 -1
- package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.js +2 -2
- package/dist/commonjs/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
- package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +6 -6
- package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
- package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.js +37 -40
- 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 +23 -16
- package/dist/commonjs/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
- package/dist/commonjs/main_thread/decrypt/content_decryptor.js +4 -1
- package/dist/commonjs/main_thread/decrypt/session_events_listener.js +1 -1
- package/dist/commonjs/main_thread/decrypt/set_server_certificate.d.ts +2 -0
- package/dist/commonjs/main_thread/decrypt/set_server_certificate.d.ts.map +1 -1
- package/dist/commonjs/main_thread/decrypt/set_server_certificate.js +4 -0
- package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts +0 -8
- package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/media_source_content_initializer.js +58 -50
- package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts +35 -5
- package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.js +60 -19
- package/dist/commonjs/main_thread/render_thumbnail.d.ts.map +1 -1
- package/dist/commonjs/main_thread/render_thumbnail.js +4 -0
- package/dist/commonjs/main_thread/tracks_store/media_element_tracks_store.d.ts.map +1 -1
- package/dist/commonjs/main_thread/tracks_store/media_element_tracks_store.js +1 -0
- package/dist/commonjs/main_thread/tracks_store/tracks_store.d.ts +1 -1
- package/dist/commonjs/main_thread/tracks_store/tracks_store.js +1 -1
- package/dist/{es2017/parsers/containers/isobmff/take_pssh_out.d.ts → commonjs/parsers/containers/isobmff/extract_pssh.d.ts} +6 -4
- package/dist/commonjs/parsers/containers/isobmff/extract_pssh.d.ts.map +1 -0
- package/dist/commonjs/parsers/containers/isobmff/{take_pssh_out.js → extract_pssh.js} +22 -17
- package/dist/commonjs/parsers/containers/isobmff/index.d.ts +2 -2
- package/dist/commonjs/parsers/containers/isobmff/index.d.ts.map +1 -1
- package/dist/commonjs/parsers/containers/isobmff/index.js +4 -4
- package/dist/commonjs/playback_observer/core_playback_observer.d.ts +4 -4
- package/dist/commonjs/playback_observer/core_playback_observer.d.ts.map +1 -1
- package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts +1 -2
- package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts.map +1 -1
- package/dist/commonjs/transports/dash/segment_parser.js +1 -1
- package/dist/commonjs/transports/local/segment_parser.js +1 -1
- package/dist/commonjs/utils/test-utils.d.ts +30 -0
- package/dist/commonjs/utils/test-utils.d.ts.map +1 -0
- package/dist/commonjs/utils/test-utils.js +79 -0
- 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/can_patch_out_pssh.d.ts +42 -0
- package/dist/es2017/compat/can_patch_out_pssh.d.ts.map +1 -0
- package/dist/es2017/compat/can_patch_out_pssh.js +50 -0
- package/dist/es2017/compat/env_detector.d.ts +2 -0
- package/dist/es2017/compat/env_detector.d.ts.map +1 -1
- package/dist/es2017/compat/env_detector.js +5 -0
- package/dist/es2017/core/adaptive/network_analyzer.d.ts +1 -2
- package/dist/es2017/core/adaptive/network_analyzer.d.ts.map +1 -1
- package/dist/es2017/core/adaptive/network_analyzer.js +3 -3
- package/dist/es2017/core/adaptive/utils/representation_score_calculator.js +2 -2
- package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
- package/dist/es2017/core/cmcd/cmcd_data_builder.js +9 -3
- package/dist/es2017/core/entry/content_preparer.d.ts.map +1 -1
- package/dist/es2017/core/entry/content_preparer.js +3 -5
- package/dist/es2017/core/entry/core_entry.d.ts.map +1 -1
- package/dist/es2017/core/entry/core_entry.js +6 -2
- package/dist/es2017/core/entry/core_text_displayer_interface.js +3 -3
- package/dist/es2017/core/fetchers/manifest/manifest_fetcher.d.ts.map +1 -1
- package/dist/es2017/core/fetchers/manifest/manifest_fetcher.js +12 -0
- package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.js +1 -1
- package/dist/es2017/core/fetchers/utils/schedule_request.d.ts.map +1 -1
- package/dist/es2017/core/fetchers/utils/schedule_request.js +2 -3
- package/dist/es2017/core/segment_sinks/garbage_collector.d.ts +0 -2
- package/dist/es2017/core/segment_sinks/garbage_collector.d.ts.map +1 -1
- package/dist/es2017/core/segment_sinks/garbage_collector.js +0 -3
- package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.d.ts +1 -1
- package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.d.ts.map +1 -1
- package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.js +2 -2
- package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
- package/dist/es2017/core/stream/adaptation/adaptation_stream.js +6 -6
- package/dist/es2017/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
- package/dist/es2017/core/stream/orchestrator/stream_orchestrator.js +40 -39
- 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 +20 -13
- package/dist/es2017/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
- package/dist/es2017/main_thread/decrypt/content_decryptor.js +4 -1
- package/dist/es2017/main_thread/decrypt/session_events_listener.js +1 -1
- package/dist/es2017/main_thread/decrypt/set_server_certificate.d.ts +2 -0
- package/dist/es2017/main_thread/decrypt/set_server_certificate.d.ts.map +1 -1
- package/dist/es2017/main_thread/decrypt/set_server_certificate.js +4 -0
- package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts +0 -8
- package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/media_source_content_initializer.js +58 -50
- package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts +35 -5
- package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.js +60 -19
- package/dist/es2017/main_thread/render_thumbnail.d.ts.map +1 -1
- package/dist/es2017/main_thread/render_thumbnail.js +4 -0
- package/dist/es2017/main_thread/tracks_store/media_element_tracks_store.d.ts.map +1 -1
- package/dist/es2017/main_thread/tracks_store/media_element_tracks_store.js +1 -0
- package/dist/es2017/main_thread/tracks_store/tracks_store.d.ts +1 -1
- package/dist/es2017/main_thread/tracks_store/tracks_store.js +1 -1
- package/dist/{commonjs/parsers/containers/isobmff/take_pssh_out.d.ts → es2017/parsers/containers/isobmff/extract_pssh.d.ts} +6 -4
- package/dist/es2017/parsers/containers/isobmff/extract_pssh.d.ts.map +1 -0
- package/dist/es2017/parsers/containers/isobmff/{take_pssh_out.js → extract_pssh.js} +21 -16
- package/dist/es2017/parsers/containers/isobmff/index.d.ts +2 -2
- package/dist/es2017/parsers/containers/isobmff/index.d.ts.map +1 -1
- package/dist/es2017/parsers/containers/isobmff/index.js +2 -2
- package/dist/es2017/playback_observer/core_playback_observer.d.ts +4 -4
- package/dist/es2017/playback_observer/core_playback_observer.d.ts.map +1 -1
- package/dist/es2017/playback_observer/media_element_playback_observer.d.ts +1 -2
- package/dist/es2017/playback_observer/media_element_playback_observer.d.ts.map +1 -1
- package/dist/es2017/transports/dash/segment_parser.js +2 -2
- package/dist/es2017/transports/local/segment_parser.js +2 -2
- package/dist/es2017/utils/test-utils.d.ts +30 -0
- package/dist/es2017/utils/test-utils.d.ts.map +1 -0
- package/dist/es2017/utils/test-utils.js +36 -0
- package/dist/mpd-parser.wasm +0 -0
- package/dist/worker.js +6 -6
- package/package.json +4 -2
- package/src/README.md +7 -7
- package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/compat/__tests__/can_patch_out_pssh.test.ts +40 -0
- package/src/compat/can_patch_out_pssh.ts +53 -0
- package/src/compat/env_detector.ts +4 -0
- package/src/core/README.md +1 -1
- package/src/core/adaptive/README.md +3 -3
- package/src/core/adaptive/__tests__/adaptive_representation_selector.test.ts +181 -110
- package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +229 -123
- package/src/core/adaptive/__tests__/mocks.ts +100 -0
- package/src/core/adaptive/__tests__/network_analyzer.test.ts +152 -59
- package/src/core/adaptive/network_analyzer.ts +4 -4
- package/src/core/adaptive/utils/__tests__/filter_by_bitrate.test.ts +11 -19
- package/src/core/adaptive/utils/__tests__/filter_by_resolution.test.ts +12 -12
- package/src/core/adaptive/utils/__tests__/last_estimate_storage.test.ts +25 -12
- package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +13 -9
- package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +11 -11
- package/src/core/adaptive/utils/__tests__/select_optimal_representation.test.ts +13 -23
- package/src/core/adaptive/utils/representation_score_calculator.ts +2 -2
- package/src/core/cmcd/__tests__/cmcd_data_builder.test.ts +60 -15
- package/src/core/cmcd/cmcd_data_builder.ts +9 -3
- package/src/core/entry/README.md +2 -2
- package/src/core/entry/__tests__/core_text_displayer_interface.test.ts +20 -0
- package/src/core/entry/content_preparer.ts +2 -5
- package/src/core/entry/core_entry.ts +6 -2
- package/src/core/entry/core_text_displayer_interface.ts +3 -3
- package/src/core/fetchers/manifest/__tests__/manifest_fetcher.test.ts +52 -3
- package/src/core/fetchers/manifest/manifest_fetcher.ts +12 -0
- package/src/core/fetchers/thumbnails/__tests__/thumbnail_fetcher.test.ts +70 -0
- package/src/core/fetchers/thumbnails/thumbnail_fetcher.ts +1 -1
- package/src/core/fetchers/utils/schedule_request.ts +5 -3
- package/src/core/segment_sinks/__tests__/garbage_collector.test.ts +434 -0
- package/src/core/segment_sinks/__tests__/mocks.ts +49 -0
- package/src/core/segment_sinks/garbage_collector.ts +0 -3
- package/src/core/segment_sinks/implementations/text/__tests__/text_segment_sink.test.ts +177 -0
- package/src/core/segment_sinks/implementations/text/text_segment_sink.ts +2 -2
- package/src/core/segment_sinks/inventory/__tests__/buffered_history.test.ts +215 -0
- package/src/core/segment_sinks/inventory/__tests__/segment_inventory.test.ts +448 -0
- package/src/core/stream/adaptation/__tests__/adaptation_stream.test.ts +973 -0
- package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +283 -374
- package/src/core/stream/adaptation/adaptation_stream.ts +6 -8
- package/src/core/stream/orchestrator/README.md +4 -4
- package/src/core/stream/orchestrator/__tests__/stream_orchestrator.test.ts +707 -0
- package/src/core/stream/orchestrator/stream_orchestrator.ts +41 -46
- package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +290 -220
- package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +93 -63
- package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +106 -63
- package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +179 -204
- package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +7 -7
- package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +103 -60
- package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +231 -165
- package/src/default_config.ts +6 -0
- package/src/experimental/README.md +1 -1
- package/src/features/README.md +3 -3
- package/src/main_thread/api/README.md +6 -7
- package/src/main_thread/api/public_api.ts +16 -10
- package/src/main_thread/decrypt/README.md +4 -4
- package/src/main_thread/decrypt/__tests__/__global__/content_decryptor.test.ts +135 -0
- package/src/main_thread/decrypt/__tests__/__global__/get_license.test.ts +70 -0
- package/src/main_thread/decrypt/__tests__/__global__/server_certificate.test.ts +44 -0
- package/src/main_thread/decrypt/__tests__/__global__/utils.ts +2 -2
- package/src/main_thread/decrypt/content_decryptor.ts +6 -1
- package/src/main_thread/decrypt/session_events_listener.ts +1 -1
- package/src/main_thread/decrypt/set_server_certificate.ts +5 -0
- package/src/main_thread/init/media_source_content_initializer.ts +69 -55
- package/src/main_thread/init/utils/__tests__/stream_events_emitter.test.ts +175 -0
- package/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts +90 -26
- package/src/main_thread/render_thumbnail.ts +4 -0
- package/src/main_thread/tracks_store/README.md +12 -0
- package/src/main_thread/tracks_store/__tests__/media_element_tracks_store.test.ts +25 -18
- package/src/main_thread/tracks_store/media_element_tracks_store.ts +1 -0
- package/src/main_thread/tracks_store/tracks_store.ts +1 -1
- package/src/manifest/classes/__tests__/mocks.ts +202 -0
- package/src/parsers/containers/isobmff/__tests__/extract_pssh.test.ts +199 -0
- package/src/parsers/containers/isobmff/{take_pssh_out.ts → extract_pssh.ts} +21 -17
- package/src/parsers/containers/isobmff/index.ts +2 -2
- package/src/parsers/manifest/dash/wasm-parser/README.md +9 -9
- package/src/playback_observer/__tests__/mocks.ts +152 -0
- package/src/playback_observer/core_playback_observer.ts +4 -4
- package/src/playback_observer/media_element_playback_observer.ts +1 -1
- package/src/tools/README.md +2 -2
- package/src/transports/README.md +5 -5
- package/src/transports/dash/segment_parser.ts +2 -2
- package/src/transports/local/segment_parser.ts +2 -2
- package/src/transports/metaplaylist/README.md +4 -4
- package/src/utils/README.md +3 -3
- package/src/utils/test-utils.ts +50 -0
- package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts +0 -11
- package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
- package/dist/commonjs/core/entry/synchronize_sinks_on_observation.js +0 -20
- package/dist/commonjs/parsers/containers/isobmff/take_pssh_out.d.ts.map +0 -1
- package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts +0 -11
- package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
- package/dist/es2017/core/entry/synchronize_sinks_on_observation.js +0 -17
- package/dist/es2017/parsers/containers/isobmff/take_pssh_out.d.ts.map +0 -1
- package/src/core/adaptive/utils/__tests__/bandwith_estimator.test.ts +0 -117
- package/src/core/entry/synchronize_sinks_on_observation.ts +0 -22
|
@@ -3,24 +3,27 @@ import type { IMediaElement } from "../../../compat/browser_compatibility_types"
|
|
|
3
3
|
import assert from "../../../utils/assert";
|
|
4
4
|
import MediaElementTracksStore from "../media_element_tracks_store";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
function createFakeMediaElement(): IMediaElement {
|
|
7
|
+
return {
|
|
8
|
+
audioTracks: [
|
|
9
|
+
{ language: "en", enabled: false },
|
|
10
|
+
{ language: "fr", enabled: true },
|
|
11
|
+
{ language: "el", enabled: false },
|
|
12
|
+
{ language: "pt-BR", enabled: false },
|
|
13
|
+
],
|
|
14
|
+
textTracks: [
|
|
15
|
+
{ language: "en", mode: "hidden" },
|
|
16
|
+
{ language: "fr", mode: "showing" },
|
|
17
|
+
{ language: "el", mode: "hidden" },
|
|
18
|
+
{ language: "pt-BR", mode: "hidden" },
|
|
19
|
+
],
|
|
20
|
+
videoTracks: [{ language: "", selected: true }],
|
|
21
|
+
} as unknown as IMediaElement;
|
|
22
|
+
}
|
|
21
23
|
|
|
22
24
|
describe("API - MediaElementTracksStore", () => {
|
|
23
25
|
it("should returns correct results for getter", () => {
|
|
26
|
+
const fakeMediaElement = createFakeMediaElement();
|
|
24
27
|
const trackManager = new MediaElementTracksStore(fakeMediaElement);
|
|
25
28
|
const audioTracks = trackManager.getAvailableAudioTracks();
|
|
26
29
|
const textTracks = trackManager.getAvailableTextTracks();
|
|
@@ -54,6 +57,7 @@ describe("API - MediaElementTracksStore", () => {
|
|
|
54
57
|
});
|
|
55
58
|
});
|
|
56
59
|
it("should returns correct results for setters", () => {
|
|
60
|
+
const fakeMediaElement = createFakeMediaElement();
|
|
57
61
|
const trackManager = new MediaElementTracksStore(fakeMediaElement);
|
|
58
62
|
|
|
59
63
|
trackManager.setAudioTrackById("gen_audio_en_1");
|
|
@@ -80,6 +84,7 @@ describe("API - MediaElementTracksStore", () => {
|
|
|
80
84
|
});
|
|
81
85
|
});
|
|
82
86
|
it("should emit available tracks change when changing text contents", () => {
|
|
87
|
+
const fakeMediaElement = createFakeMediaElement();
|
|
83
88
|
const trackManager = new MediaElementTracksStore(fakeMediaElement);
|
|
84
89
|
|
|
85
90
|
return new Promise<void>((res) => {
|
|
@@ -107,6 +112,7 @@ describe("API - MediaElementTracksStore", () => {
|
|
|
107
112
|
});
|
|
108
113
|
|
|
109
114
|
it("should emit available tracks change when changing video contents", () => {
|
|
115
|
+
const fakeMediaElement = createFakeMediaElement();
|
|
110
116
|
const trackManager = new MediaElementTracksStore(fakeMediaElement);
|
|
111
117
|
return new Promise<void>((res) => {
|
|
112
118
|
trackManager.addEventListener("availableVideoTracksChange", (tracks) => {
|
|
@@ -129,6 +135,7 @@ describe("API - MediaElementTracksStore", () => {
|
|
|
129
135
|
});
|
|
130
136
|
|
|
131
137
|
it("should emit available tracks change when changing audio contents", () => {
|
|
138
|
+
const fakeMediaElement = createFakeMediaElement();
|
|
132
139
|
const trackManager = new MediaElementTracksStore(fakeMediaElement);
|
|
133
140
|
return new Promise<void>((res) => {
|
|
134
141
|
trackManager.addEventListener("availableAudioTracksChange", (tracks) => {
|
|
@@ -154,18 +161,18 @@ describe("API - MediaElementTracksStore", () => {
|
|
|
154
161
|
});
|
|
155
162
|
|
|
156
163
|
it("should emit chosen track when changing text content", () => {
|
|
164
|
+
const fakeMediaElement = createFakeMediaElement();
|
|
157
165
|
const trackManager = new MediaElementTracksStore(fakeMediaElement);
|
|
158
166
|
|
|
159
167
|
return new Promise<void>((res) => {
|
|
160
168
|
trackManager.addEventListener("textTrackChange", (chosenTrack) => {
|
|
161
|
-
expect(chosenTrack?.id).toBe("
|
|
169
|
+
expect(chosenTrack?.id).toBe("gen_text_en_1");
|
|
162
170
|
res();
|
|
163
171
|
});
|
|
164
172
|
|
|
165
|
-
trackManager.setTextTrackById("
|
|
173
|
+
trackManager.setTextTrackById("gen_text_en_1");
|
|
166
174
|
|
|
167
175
|
// Fake browser behavior
|
|
168
|
-
fakeMediaElement.textTracks[0].mode = "hidden";
|
|
169
176
|
(
|
|
170
177
|
fakeMediaElement.textTracks as {
|
|
171
178
|
onchange: ((arg: Event) => void) | undefined;
|
|
@@ -627,6 +627,7 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
627
627
|
const newAudioTracks = createAudioTracks(this._nativeAudioTracks);
|
|
628
628
|
if (areTrackArraysDifferent(this._audioTracks, newAudioTracks)) {
|
|
629
629
|
this._audioTracks = newAudioTracks;
|
|
630
|
+
this._setPreviouslyLockedAudioTrack();
|
|
630
631
|
this.trigger("availableAudioTracksChange", this.getAvailableAudioTracks());
|
|
631
632
|
const chosenAudioTrack = this._getCurrentAudioTrack();
|
|
632
633
|
if (chosenAudioTrack?.nativeTrack !== this._lastEmittedNativeAudioTrack) {
|
|
@@ -1153,7 +1153,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
|
|
|
1153
1153
|
* Period.
|
|
1154
1154
|
*
|
|
1155
1155
|
* Returns `null` is the the current audio track is disabled or not
|
|
1156
|
-
* set yet.
|
|
1156
|
+
* set yet.
|
|
1157
1157
|
*
|
|
1158
1158
|
* Returns `undefined` if the given Period's id is not currently found in the
|
|
1159
1159
|
* `TracksStore`. The cause being most probably that the corresponding
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains "dummy" versions of the exported classes imported from
|
|
3
|
+
* this location:
|
|
4
|
+
*
|
|
5
|
+
* - They respect the same API (e.g. a `DummyRepresentation` is a
|
|
6
|
+
* `Representation`-compliant type).
|
|
7
|
+
*
|
|
8
|
+
* - They're doing no side-effect.
|
|
9
|
+
*
|
|
10
|
+
* - All of their methods throw an error immediately on call (you have to
|
|
11
|
+
* implement the ones you want, generally through testing tools).
|
|
12
|
+
*/
|
|
13
|
+
import type { IRepresentationIndex, ISegment } from "../..";
|
|
14
|
+
import { makeMockedClass } from "../../../utils/test-utils";
|
|
15
|
+
import { ManifestMetadataFormat } from "../../types";
|
|
16
|
+
import type Adaptation from "../adaptation";
|
|
17
|
+
import type Manifest from "../manifest";
|
|
18
|
+
import type Period from "../period";
|
|
19
|
+
import type Representation from "../representation";
|
|
20
|
+
|
|
21
|
+
export const DummyManifest = makeMockedClass<Manifest>(
|
|
22
|
+
{
|
|
23
|
+
addEventListener: notImplemented("addEventListener"),
|
|
24
|
+
removeEventListener: notImplemented("removeEventListener"),
|
|
25
|
+
updateCodecSupport: notImplemented("updateCodecSupport"),
|
|
26
|
+
getPeriod: notImplemented("getPeriod"),
|
|
27
|
+
getPeriodForTime: notImplemented("getPeriodForTime"),
|
|
28
|
+
getNextPeriod: notImplemented("getNextPeriod"),
|
|
29
|
+
getPeriodAfter: notImplemented("getPeriodAfter"),
|
|
30
|
+
getUrls: notImplemented("getUrls"),
|
|
31
|
+
replace: notImplemented("replace"),
|
|
32
|
+
update: notImplemented("update"),
|
|
33
|
+
getMinimumSafePosition: notImplemented("getMinimumSafePosition"),
|
|
34
|
+
getLivePosition: notImplemented("getLivePosition"),
|
|
35
|
+
getMaximumSafePosition: notImplemented("getMaximumSafePosition"),
|
|
36
|
+
updateCodecSupportList: notImplemented("updateCodecSupportList"),
|
|
37
|
+
updateRepresentationsDeciperability: notImplemented(
|
|
38
|
+
"updateRepresentationsDecipherability",
|
|
39
|
+
),
|
|
40
|
+
addRepresentationsToAvoid: notImplemented("addRepresentationsToAvoid"),
|
|
41
|
+
getAdaptations: notImplemented("getAdaptations"),
|
|
42
|
+
getAdaptation: notImplemented("getAdaptation"),
|
|
43
|
+
getAdaptationsForType: notImplemented("getAdaptationsForType"),
|
|
44
|
+
getMetadataSnapshot: notImplemented("getMetadataSnapshot"),
|
|
45
|
+
getCodecsWithUnknownSupport: notImplemented("getCodecsWithUnknownSupport"),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "n/a",
|
|
49
|
+
manifestFormat: ManifestMetadataFormat.Class,
|
|
50
|
+
transport: "test",
|
|
51
|
+
periods: [],
|
|
52
|
+
expired: null,
|
|
53
|
+
adaptations: {},
|
|
54
|
+
isDynamic: false,
|
|
55
|
+
isLive: false,
|
|
56
|
+
isLastPeriodKnown: true,
|
|
57
|
+
uris: [],
|
|
58
|
+
updateUrl: undefined,
|
|
59
|
+
suggestedPresentationDelay: undefined,
|
|
60
|
+
lifetime: undefined,
|
|
61
|
+
availabilityStartTime: undefined,
|
|
62
|
+
publishTime: undefined,
|
|
63
|
+
clockOffset: undefined,
|
|
64
|
+
timeBounds: {
|
|
65
|
+
minimumSafePosition: undefined,
|
|
66
|
+
timeshiftDepth: null,
|
|
67
|
+
maximumTimeData: {
|
|
68
|
+
livePosition: undefined,
|
|
69
|
+
isLinear: false,
|
|
70
|
+
maximumSafePosition: Number.MAX_SAFE_INTEGER,
|
|
71
|
+
time: 0,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
export const DummyPeriod = makeMockedClass<Period>(
|
|
78
|
+
{
|
|
79
|
+
getAdaptations: notImplemented("getAdaptations"),
|
|
80
|
+
getAdaptation: notImplemented("getAdaptation"),
|
|
81
|
+
getAdaptationsForType: notImplemented("getAdaptationsForType"),
|
|
82
|
+
getSupportedAdaptations: notImplemented("getSupportedAdaptations"),
|
|
83
|
+
getMetadataSnapshot: notImplemented("getMetadataSnapshot"),
|
|
84
|
+
containsTime: notImplemented("containsTime"),
|
|
85
|
+
refreshCodecSupport: notImplemented("refreshCodecSupport"),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "n/a",
|
|
89
|
+
start: 0,
|
|
90
|
+
adaptations: {},
|
|
91
|
+
streamEvents: [],
|
|
92
|
+
thumbnailTracks: [],
|
|
93
|
+
duration: undefined,
|
|
94
|
+
end: undefined,
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
export const DummyAdaptation = makeMockedClass<Adaptation>(
|
|
99
|
+
{
|
|
100
|
+
getRepresentation: notImplemented("getRepresentation"),
|
|
101
|
+
getMetadataSnapshot: notImplemented("getMetadataSnapshot"),
|
|
102
|
+
refreshCodecSupport: notImplemented("refreshCodecSupport"),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "n/a",
|
|
106
|
+
type: "video",
|
|
107
|
+
representations: [],
|
|
108
|
+
supportStatus: {
|
|
109
|
+
hasCodecWithUndefinedSupport: false,
|
|
110
|
+
hasSupportedCodec: true,
|
|
111
|
+
isDecipherable: undefined,
|
|
112
|
+
},
|
|
113
|
+
isDub: undefined,
|
|
114
|
+
label: undefined,
|
|
115
|
+
language: undefined,
|
|
116
|
+
manuallyAdded: undefined,
|
|
117
|
+
isClosedCaption: undefined,
|
|
118
|
+
trickModeTracks: undefined,
|
|
119
|
+
isTrickModeTrack: undefined,
|
|
120
|
+
isForcedSubtitles: undefined,
|
|
121
|
+
isSignInterpreted: undefined,
|
|
122
|
+
isAudioDescription: undefined,
|
|
123
|
+
normalizedLanguage: undefined,
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
export const DummyRepresentationIndex = makeMockedClass<IRepresentationIndex>(
|
|
128
|
+
{
|
|
129
|
+
getLastAvailablePosition: notImplemented("getLastAvailablePosition"),
|
|
130
|
+
addPredictedSegments: notImplemented("addPredictedSegments"),
|
|
131
|
+
getFirstAvailablePosition: notImplemented("getFirstAvailablePosition"),
|
|
132
|
+
getTargetSegmentDuration: notImplemented("getTargetSegmentDuration"),
|
|
133
|
+
getSegments: notImplemented("getSegments"),
|
|
134
|
+
getEnd: notImplemented("getEnd"),
|
|
135
|
+
getInitSegment: notImplemented("getInitSegment"),
|
|
136
|
+
shouldRefresh: notImplemented("shouldRefresh"),
|
|
137
|
+
isInitialized: notImplemented("isInitialized"),
|
|
138
|
+
isSegmentStillAvailable: notImplemented("isSegmentStillAvailable"),
|
|
139
|
+
initialize: notImplemented("initialize"),
|
|
140
|
+
isStillAwaitingFutureSegments: notImplemented("isStillAwaitingFutureSegments"),
|
|
141
|
+
canBeOutOfSyncError: notImplemented("canBeOutOfSyncError"),
|
|
142
|
+
checkDiscontinuity: notImplemented("checkDiscontinuity"),
|
|
143
|
+
awaitSegmentBetween: notImplemented("awaitSegmentBetween"),
|
|
144
|
+
_replace: notImplemented("_replace"),
|
|
145
|
+
_update: notImplemented("_update"),
|
|
146
|
+
},
|
|
147
|
+
{},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
export const DummyRepresentation = makeMockedClass<Representation>(
|
|
151
|
+
{
|
|
152
|
+
isPlayable: notImplemented("isPlayable"),
|
|
153
|
+
addProtectionData: notImplemented("addProtectionData"),
|
|
154
|
+
getMimeTypeString: notImplemented("getMimeTypeString"),
|
|
155
|
+
getEncryptionData: notImplemented("getEncryptionData"),
|
|
156
|
+
getAllEncryptionData: notImplemented("getAllEncryptionData"),
|
|
157
|
+
getMetadataSnapshot: notImplemented("getMetadataSnapshot"),
|
|
158
|
+
refreshCodecSupport: notImplemented("refreshCodecSupport"),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "n/a",
|
|
162
|
+
isSupported: undefined,
|
|
163
|
+
index: new DummyRepresentationIndex(),
|
|
164
|
+
chosenCodec: undefined,
|
|
165
|
+
baseCodecs: [],
|
|
166
|
+
bitrate: 1000,
|
|
167
|
+
uniqueId: "n/a",
|
|
168
|
+
decipherable: undefined,
|
|
169
|
+
isSpatialAudio: undefined,
|
|
170
|
+
shouldBeAvoided: false,
|
|
171
|
+
isCodecSupportedInWebWorker: undefined,
|
|
172
|
+
trackType: "video",
|
|
173
|
+
cdnMetadata: null,
|
|
174
|
+
width: undefined,
|
|
175
|
+
height: undefined,
|
|
176
|
+
mimeType: undefined,
|
|
177
|
+
frameRate: undefined,
|
|
178
|
+
hdrInfo: undefined,
|
|
179
|
+
contentProtections: undefined,
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
export function createSegment(props: Partial<ISegment> = {}): ISegment {
|
|
184
|
+
return {
|
|
185
|
+
id: "n/a",
|
|
186
|
+
isInit: false,
|
|
187
|
+
time: 0,
|
|
188
|
+
end: 2,
|
|
189
|
+
duration: 2,
|
|
190
|
+
timescale: 1,
|
|
191
|
+
complete: true,
|
|
192
|
+
url: null,
|
|
193
|
+
privateInfos: {},
|
|
194
|
+
...props,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function notImplemented(name: string): () => never {
|
|
199
|
+
return () => {
|
|
200
|
+
throw new Error(`${name} not implemented`);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { concat } from "../../../../utils/byte_parsing";
|
|
3
|
+
import extractPssh, { getPsshSystemID } from "../extract_pssh";
|
|
4
|
+
|
|
5
|
+
const { mockCanPatchOutPsshBox, mockLog } = vi.hoisted(() => {
|
|
6
|
+
return {
|
|
7
|
+
mockCanPatchOutPsshBox: vi.fn(() => false),
|
|
8
|
+
mockLog: {
|
|
9
|
+
warn: vi.fn(),
|
|
10
|
+
info: vi.fn(),
|
|
11
|
+
debug: vi.fn(),
|
|
12
|
+
log: vi.fn(),
|
|
13
|
+
error: vi.fn(),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
vi.mock("../../../../compat/can_patch_out_pssh", () => ({
|
|
19
|
+
default: mockCanPatchOutPsshBox,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock("../../../../log", () => ({
|
|
23
|
+
default: mockLog,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
/** Build a minimal moov box wrapping `innerBytes`. */
|
|
27
|
+
function buildMoovBox(innerBytes: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer> {
|
|
28
|
+
const size = 8 + innerBytes.length;
|
|
29
|
+
const buf = new Uint8Array(size);
|
|
30
|
+
const view = new DataView(buf.buffer);
|
|
31
|
+
view.setUint32(0, size, false);
|
|
32
|
+
buf[4] = 0x6d;
|
|
33
|
+
buf[5] = 0x6f;
|
|
34
|
+
buf[6] = 0x6f;
|
|
35
|
+
buf[7] = 0x76; // "moov"
|
|
36
|
+
buf.set(innerBytes, 8);
|
|
37
|
+
return buf;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a pssh box.
|
|
42
|
+
*
|
|
43
|
+
* Layout (after size + name):
|
|
44
|
+
* 1 byte version
|
|
45
|
+
* 3 bytes flags
|
|
46
|
+
* 16 bytes systemId
|
|
47
|
+
* N bytes extra data
|
|
48
|
+
*/
|
|
49
|
+
function buildPsshBox(
|
|
50
|
+
systemId: Uint8Array<ArrayBuffer>,
|
|
51
|
+
version = 0,
|
|
52
|
+
extraData: Uint8Array<ArrayBuffer> = new Uint8Array(0),
|
|
53
|
+
): Uint8Array<ArrayBuffer> {
|
|
54
|
+
// 8 (size+name) + 1 (version) + 3 (flags) + 16 (systemId) + extraData
|
|
55
|
+
const size = 8 + 1 + 3 + 16 + extraData.length;
|
|
56
|
+
const buf = new Uint8Array(size);
|
|
57
|
+
const view = new DataView(buf.buffer);
|
|
58
|
+
view.setUint32(0, size, false);
|
|
59
|
+
buf[4] = 0x70;
|
|
60
|
+
buf[5] = 0x73;
|
|
61
|
+
buf[6] = 0x73;
|
|
62
|
+
buf[7] = 0x68; // "pssh"
|
|
63
|
+
buf[8] = version; // version (bytes 0 of version+flags)
|
|
64
|
+
// flags stay 0x000000
|
|
65
|
+
buf.set(systemId, 12); // offset 8+4 = 12
|
|
66
|
+
buf.set(extraData, 28);
|
|
67
|
+
return buf;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const SYSTEM_ID_A = new Uint8Array([
|
|
71
|
+
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2,
|
|
72
|
+
0xfb, 0x4b,
|
|
73
|
+
]);
|
|
74
|
+
const SYSTEM_ID_B = new Uint8Array([
|
|
75
|
+
0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d,
|
|
76
|
+
0x21, 0xed,
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const systemIdAHex = Array.from(SYSTEM_ID_A)
|
|
80
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
81
|
+
.join("");
|
|
82
|
+
const systemIdBHex = Array.from(SYSTEM_ID_B)
|
|
83
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
84
|
+
.join("");
|
|
85
|
+
|
|
86
|
+
describe("extractPssh", () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
mockCanPatchOutPsshBox.mockReturnValue(true);
|
|
89
|
+
});
|
|
90
|
+
afterEach(() => {
|
|
91
|
+
vi.resetAllMocks();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns [] when there is no moov box", () => {
|
|
95
|
+
const data = new Uint8Array(32); // no moov, only `0`
|
|
96
|
+
expect(extractPssh(data)).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns [] when moov contains no pssh box", () => {
|
|
100
|
+
// An empty moov (just the 8-byte header, no inner boxes)
|
|
101
|
+
const data = buildMoovBox(new Uint8Array(0));
|
|
102
|
+
expect(extractPssh(data)).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("extracts a single pssh box", () => {
|
|
106
|
+
const pssh = buildPsshBox(SYSTEM_ID_A);
|
|
107
|
+
const data = buildMoovBox(pssh);
|
|
108
|
+
const result = extractPssh(data);
|
|
109
|
+
expect(result).toHaveLength(1);
|
|
110
|
+
expect(result[0].systemId).toBe(systemIdAHex);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("extracts multiple pssh boxes in order", () => {
|
|
114
|
+
const psshA = buildPsshBox(SYSTEM_ID_A);
|
|
115
|
+
const psshB = buildPsshBox(SYSTEM_ID_B);
|
|
116
|
+
const data = buildMoovBox(concat(psshA, psshB));
|
|
117
|
+
const result = extractPssh(data);
|
|
118
|
+
expect(result).toHaveLength(2);
|
|
119
|
+
expect(result[0].systemId).toBe(systemIdAHex);
|
|
120
|
+
expect(result[1].systemId).toBe(systemIdBHex);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("skips a pssh whose systemId cannot be parsed and keeps going", () => {
|
|
124
|
+
// A pssh with version > 1 triggers the 'un-handled PSSH version' warn path,
|
|
125
|
+
// so getPsshSystemID returns undefined and the box is omitted.
|
|
126
|
+
const psshBad = buildPsshBox(SYSTEM_ID_A, 2 /* unsupported version */);
|
|
127
|
+
const psshGood = buildPsshBox(SYSTEM_ID_B);
|
|
128
|
+
const data = buildMoovBox(concat(psshBad, psshGood));
|
|
129
|
+
const result = extractPssh(data);
|
|
130
|
+
expect(result).toHaveLength(1);
|
|
131
|
+
expect(result[0].systemId).toBe(systemIdBHex);
|
|
132
|
+
expect(mockLog.warn).toHaveBeenCalledWith("isobmff", "un-handled PSSH version");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("patches out pssh bytes when canPatchOutPsshBox returns true", () => {
|
|
136
|
+
mockCanPatchOutPsshBox.mockReturnValue(true);
|
|
137
|
+
const pssh = buildPsshBox(SYSTEM_ID_A);
|
|
138
|
+
const moov = buildMoovBox(pssh);
|
|
139
|
+
// The pssh box starts at offset 8 inside moov content (right after moov header).
|
|
140
|
+
// After patching, bytes 4–7 of the pssh box should be "free" (0x66 0x72 0x65 0x65).
|
|
141
|
+
extractPssh(moov);
|
|
142
|
+
// moov content starts at offset 8 of the full segment.
|
|
143
|
+
// pssh name is at moov_content[4..7] = segment[12..15]
|
|
144
|
+
expect(moov[12]).toBe(0x66); // 'f'
|
|
145
|
+
expect(moov[13]).toBe(0x72); // 'r'
|
|
146
|
+
expect(moov[14]).toBe(0x65); // 'e'
|
|
147
|
+
expect(moov[15]).toBe(0x65); // 'e'
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("does not patch bytes when canPatchOutPsshBox returns false", () => {
|
|
151
|
+
mockCanPatchOutPsshBox.mockReturnValue(false);
|
|
152
|
+
const pssh = buildPsshBox(SYSTEM_ID_A);
|
|
153
|
+
const originalNameByte1 = pssh[4]; // should be 0x70 ('p')
|
|
154
|
+
const originalNameByte2 = pssh[5]; // 's'
|
|
155
|
+
const originalNameByte3 = pssh[6]; // 's'
|
|
156
|
+
const originalNameByte4 = pssh[7]; // 'h'
|
|
157
|
+
const moov = buildMoovBox(pssh);
|
|
158
|
+
extractPssh(moov);
|
|
159
|
+
expect(moov[12]).toBe(originalNameByte1);
|
|
160
|
+
expect(moov[13]).toBe(originalNameByte2);
|
|
161
|
+
expect(moov[14]).toBe(originalNameByte3);
|
|
162
|
+
expect(moov[15]).toBe(originalNameByte4);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("getPsshSystemID", () => {
|
|
167
|
+
afterEach(() => {
|
|
168
|
+
vi.resetAllMocks();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("parses a valid version-0 pssh and returns hex systemId", () => {
|
|
172
|
+
const pssh = buildPsshBox(SYSTEM_ID_A);
|
|
173
|
+
// initialDataOffset is the offset right after size (4) + name (4) = 8
|
|
174
|
+
const result = getPsshSystemID(pssh, 8);
|
|
175
|
+
expect(result).toBe(systemIdAHex);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("parses a valid version-1 pssh", () => {
|
|
179
|
+
const pssh = buildPsshBox(SYSTEM_ID_B, 1);
|
|
180
|
+
const result = getPsshSystemID(pssh, 8);
|
|
181
|
+
expect(result).toBe(systemIdBHex);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("returns undefined and warns for version > 1", () => {
|
|
185
|
+
const pssh = buildPsshBox(SYSTEM_ID_A, 2);
|
|
186
|
+
const result = getPsshSystemID(pssh, 8);
|
|
187
|
+
expect(result).toBeUndefined();
|
|
188
|
+
expect(mockLog.warn).toHaveBeenCalledWith("isobmff", "un-handled PSSH version");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("returns undefined when buffer is too short to contain a full systemId", () => {
|
|
192
|
+
// Build a buffer that's big enough for the header (version + flags = 4 bytes)
|
|
193
|
+
// but lacks the 16-byte systemId.
|
|
194
|
+
const buf = new Uint8Array(8 + 4 + 10); // only 10 bytes where 16 are needed
|
|
195
|
+
const result = getPsshSystemID(buf, 8);
|
|
196
|
+
expect(result).toBeUndefined();
|
|
197
|
+
expect(mockLog.warn).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import canPatchOutPsshBox from "../../../compat/can_patch_out_pssh";
|
|
17
18
|
import log from "../../../log";
|
|
18
19
|
import sliceUint8Array from "../../../utils/slice_uint8array";
|
|
19
20
|
import { bytesToHex } from "../../../utils/string_parsing";
|
|
@@ -28,46 +29,49 @@ export interface IISOBMFFPSSHInfo {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
32
|
+
* Return every `pssh` box from an ISOBMFF segment.
|
|
33
|
+
*
|
|
34
|
+
* If the current device has no issue with it, also replace them by `free` boxes.
|
|
33
35
|
* Useful to manually manage encryption while avoiding the round-trip with the
|
|
34
36
|
* browser's encrypted event.
|
|
37
|
+
*
|
|
35
38
|
* @param {Uint8Array} data - the ISOBMFF segment
|
|
36
39
|
* @returns {Array.<Uint8Array>} - The extracted PSSH boxes. In the order they
|
|
37
40
|
* are encountered.
|
|
38
41
|
*/
|
|
39
|
-
export default function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (moov === null) {
|
|
42
|
+
export default function extractPssh(data: Uint8Array<ArrayBuffer>): IISOBMFFPSSHInfo[] {
|
|
43
|
+
const moovContent = getBoxContent(data, 0x6d6f6f76 /* moov */);
|
|
44
|
+
if (moovContent === null) {
|
|
43
45
|
return [];
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
const psshBoxes: IISOBMFFPSSHInfo[] = [];
|
|
47
|
-
|
|
49
|
+
let remainingMoovContent = moovContent;
|
|
50
|
+
while (remainingMoovContent.length > 0) {
|
|
48
51
|
let psshOffsets;
|
|
49
52
|
try {
|
|
50
|
-
psshOffsets = getBoxOffsets(
|
|
53
|
+
psshOffsets = getBoxOffsets(remainingMoovContent, 0x70737368 /* pssh */);
|
|
51
54
|
} catch (e) {
|
|
52
|
-
const err = e instanceof Error ? e : "";
|
|
55
|
+
const err = e instanceof Error ? e : new Error("Unknown ISOBMFF box reading error");
|
|
53
56
|
log.warn("isobmff", "Error while removing PSSH from ISOBMFF", err);
|
|
54
57
|
return psshBoxes;
|
|
55
58
|
}
|
|
56
59
|
if (psshOffsets === null) {
|
|
57
60
|
return psshBoxes;
|
|
58
61
|
}
|
|
59
|
-
const pssh = sliceUint8Array(
|
|
62
|
+
const pssh = sliceUint8Array(remainingMoovContent, psshOffsets[0], psshOffsets[2]);
|
|
60
63
|
const systemId = getPsshSystemID(pssh, psshOffsets[1] - psshOffsets[0]);
|
|
61
64
|
if (systemId !== undefined) {
|
|
62
65
|
psshBoxes.push({ systemId, data: pssh });
|
|
63
66
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
if (canPatchOutPsshBox()) {
|
|
68
|
+
// replace by `free` box.
|
|
69
|
+
remainingMoovContent[psshOffsets[0] + 4] = 0x66;
|
|
70
|
+
remainingMoovContent[psshOffsets[0] + 5] = 0x72;
|
|
71
|
+
remainingMoovContent[psshOffsets[0] + 6] = 0x65;
|
|
72
|
+
remainingMoovContent[psshOffsets[0] + 7] = 0x65;
|
|
73
|
+
}
|
|
74
|
+
remainingMoovContent = remainingMoovContent.subarray(psshOffsets[2]);
|
|
71
75
|
}
|
|
72
76
|
return psshBoxes;
|
|
73
77
|
}
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import extractCompleteChunks, { extractInitSegment } from "./extract_complete_chunks";
|
|
18
|
+
import extractPssh, { getPsshSystemID } from "./extract_pssh";
|
|
18
19
|
import findCompleteBox from "./find_complete_box";
|
|
19
20
|
import removeDolbyVisionConfigData from "./remove_dolby_vision_config_data";
|
|
20
|
-
import takePSSHOut, { getPsshSystemID } from "./take_pssh_out";
|
|
21
21
|
|
|
22
22
|
export { extractInitSegment };
|
|
23
23
|
export { createBox, createBoxWithChildren } from "./create_box";
|
|
@@ -44,5 +44,5 @@ export {
|
|
|
44
44
|
findCompleteBox,
|
|
45
45
|
getPsshSystemID,
|
|
46
46
|
removeDolbyVisionConfigData,
|
|
47
|
-
|
|
47
|
+
extractPssh,
|
|
48
48
|
};
|
|
@@ -7,7 +7,7 @@ the default JS-based one, especially on very large (multiple megabytes) MPDs.
|
|
|
7
7
|
|
|
8
8
|
### How Manifest parsing works in the RxPlayer
|
|
9
9
|
|
|
10
|
-
In the RxPlayer every Manifest parser
|
|
10
|
+
In the RxPlayer every Manifest parser outputs the same final Manifest format which follows
|
|
11
11
|
the same structure regardless of the transport protocol used (e.g. DASH, Smooth etc.).
|
|
12
12
|
|
|
13
13
|
For the MPD (DASH's Manifest format), this transformation (from an XML to that final
|
|
@@ -30,7 +30,7 @@ XML format in JavaScript.
|
|
|
30
30
|
Before the DASH-WASM parser, we only had the JavaScript parser, which relied on the
|
|
31
31
|
browser's DOM APIs (e.g. `DOMParser`).
|
|
32
32
|
|
|
33
|
-
This parser works fine but we found that its performance was really poor when
|
|
33
|
+
This parser works fine but we found that its performance was really poor when encountering
|
|
34
34
|
huge MPDs (several MBs), that we're dealing with at Canal+ on a regular basis. Usually
|
|
35
35
|
those problematic MPDs have several gigantic `<SegmentTimeline>` elements that takes a lot
|
|
36
36
|
of time to parse.
|
|
@@ -39,11 +39,11 @@ After multiple tests, profiling and improvements, it appeared that the issue had
|
|
|
39
39
|
with XML parsing and the DOM APIs (regarding the speed of the parsing operation, but also
|
|
40
40
|
some stability issues most likely related to garbage collection pressure).
|
|
41
41
|
|
|
42
|
-
Moreover, on some embedded devices, this operation could take several seconds. This
|
|
43
|
-
|
|
42
|
+
Moreover, on some embedded devices, this operation could take several seconds. This led us
|
|
43
|
+
to try to implement a parser running in a WebWorker (to avoid blocking the main thread,
|
|
44
44
|
which is also handling the user interface).
|
|
45
45
|
|
|
46
|
-
As no DOM
|
|
46
|
+
As no DOM APIs are available in a WebWorker, we had to re-define the XML parsing APIs. We
|
|
47
47
|
first opted for a JavaScript library, yet found performance in our initial tests to be
|
|
48
48
|
subpar. We ended up trying with WebAssembly instead.
|
|
49
49
|
|
|
@@ -82,7 +82,7 @@ In the context of the RxPlayer, we only use it for optimization reasons, when we
|
|
|
82
82
|
that switching the DASH MPD parser to WebAssembly has a high impact in terms of
|
|
83
83
|
performance when compared to the equivalent JavaScript code.
|
|
84
84
|
|
|
85
|
-
### Where does the DASH-WASM parser
|
|
85
|
+
### Where does the DASH-WASM parser fit into this
|
|
86
86
|
|
|
87
87
|
The DASH-WASM MPD parser only does the first step of transforming the initial XML format
|
|
88
88
|
into the "intermediate representation", by using WebAssembly.
|
|
@@ -111,7 +111,7 @@ This logic was written as such because:
|
|
|
111
111
|
|
|
112
112
|
### Directories
|
|
113
113
|
|
|
114
|
-
The DASH-WASM parser comes
|
|
114
|
+
The DASH-WASM parser comes in two parts:
|
|
115
115
|
|
|
116
116
|
- the Rust part, written in the `rs` directory, that browses the XML and convert values to
|
|
117
117
|
the right format.
|
|
@@ -142,7 +142,7 @@ Currently, the DASH-WASM parser parses in one go the XML.
|
|
|
142
142
|
If the XML element encountered is recognized, it will call a JavaScript callback with
|
|
143
143
|
the right number, to signal that this element has been encountered.
|
|
144
144
|
|
|
145
|
-
4. This callback, registered by the TypeScript code,
|
|
145
|
+
4. This callback, registered by the TypeScript code, begins to create the object
|
|
146
146
|
corresponding to that element and update some callbacks called by Rust so that new
|
|
147
147
|
elements and attributes are considered as part of this element.
|
|
148
148
|
|
|
@@ -181,7 +181,7 @@ preferable for any reason, don't hesitate to propose a change (through an issue
|
|
|
181
181
|
|
|
182
182
|
### Details on FFI
|
|
183
183
|
|
|
184
|
-
Technically, some data and function calls
|
|
184
|
+
Technically, some data and function calls need to cross the language boundaries between
|
|
185
185
|
the compiled JavaScript and WebAssembly code.
|
|
186
186
|
|
|
187
187
|
On the JavaScript/TypeScript-side, everything stays relatively simple, with few
|