rx-player 4.4.1-dev.2025101500 → 4.4.1
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 +3 -1
- package/README.md +111 -50
- 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/core/{entry → main/common}/FreezeResolver.d.ts +3 -3
- package/dist/commonjs/core/main/common/FreezeResolver.d.ts.map +1 -0
- package/dist/commonjs/core/{entry → main/common}/FreezeResolver.js +3 -3
- package/dist/commonjs/core/{entry → main/common}/content_time_boundaries_observer.d.ts +5 -5
- package/dist/commonjs/core/main/common/content_time_boundaries_observer.d.ts.map +1 -0
- package/dist/commonjs/core/{entry → main/common}/content_time_boundaries_observer.js +6 -6
- package/dist/{es2017/core/entry → commonjs/core/main/common}/create_content_time_boundaries_observer.d.ts +6 -6
- package/dist/commonjs/core/main/common/create_content_time_boundaries_observer.d.ts.map +1 -0
- package/dist/commonjs/core/{entry → main/common}/create_content_time_boundaries_observer.js +1 -1
- package/dist/{es2017/core/entry → commonjs/core/main/common}/get_buffered_data_per_media_buffer.d.ts +4 -4
- package/dist/commonjs/core/main/common/get_buffered_data_per_media_buffer.d.ts.map +1 -0
- package/dist/commonjs/core/{entry → main/common}/get_buffered_data_per_media_buffer.js +1 -1
- package/dist/{es2017/core/entry → commonjs/core/main/common}/get_thumbnail_data.d.ts +3 -3
- package/dist/commonjs/core/main/common/get_thumbnail_data.d.ts.map +1 -0
- package/dist/commonjs/core/{entry → main/common}/get_thumbnail_data.js +2 -2
- package/dist/{es2017/core/entry → commonjs/core/main/common}/synchronize_sinks_on_observation.d.ts +2 -2
- package/dist/commonjs/core/main/common/synchronize_sinks_on_observation.d.ts.map +1 -0
- package/dist/{es2017/core/entry → commonjs/core/main/worker}/content_preparer.d.ts +15 -22
- package/dist/commonjs/core/main/worker/content_preparer.d.ts.map +1 -0
- package/dist/commonjs/core/{entry → main/worker}/content_preparer.js +64 -62
- package/dist/commonjs/core/main/worker/globals.d.ts +14 -0
- package/dist/commonjs/core/main/worker/globals.d.ts.map +1 -0
- package/dist/commonjs/core/main/worker/globals.js +26 -0
- package/dist/commonjs/core/main/worker/index.d.ts +3 -0
- package/dist/commonjs/core/main/worker/index.d.ts.map +1 -0
- package/dist/commonjs/core/main/worker/index.js +4 -0
- package/dist/commonjs/core/main/worker/send_message.d.ts +4 -0
- package/dist/commonjs/core/main/worker/send_message.d.ts.map +1 -0
- package/dist/commonjs/core/main/worker/send_message.js +23 -0
- package/dist/{es2017/core/entry → commonjs/core/main/worker}/track_choice_setter.d.ts +4 -4
- package/dist/commonjs/core/main/worker/track_choice_setter.d.ts.map +1 -0
- package/dist/commonjs/core/{entry → main/worker}/track_choice_setter.js +4 -4
- package/dist/commonjs/core/main/worker/worker_main.d.ts +2 -0
- package/dist/commonjs/core/main/worker/worker_main.d.ts.map +1 -0
- package/dist/commonjs/core/{entry/core_entry.js → main/worker/worker_main.js} +153 -192
- package/dist/commonjs/core/{entry/core_text_displayer_interface.d.ts → main/worker/worker_text_displayer_interface.d.ts} +11 -11
- package/dist/commonjs/core/main/worker/worker_text_displayer_interface.d.ts.map +1 -0
- package/dist/commonjs/core/{entry/core_text_displayer_interface.js → main/worker/worker_text_displayer_interface.js} +22 -22
- package/dist/commonjs/core/types.d.ts +1 -519
- package/dist/commonjs/core/types.d.ts.map +1 -1
- package/dist/commonjs/core/types.js +0 -1
- package/dist/commonjs/experimental/features/local.d.ts.map +1 -1
- package/dist/commonjs/experimental/features/local.js +1 -7
- package/dist/commonjs/experimental/features/metaplaylist.d.ts.map +1 -1
- package/dist/commonjs/experimental/features/metaplaylist.js +1 -7
- package/dist/commonjs/experimental/features/multi_thread.d.ts.map +1 -1
- package/dist/commonjs/experimental/features/multi_thread.js +2 -6
- package/dist/commonjs/features/features_object.js +1 -1
- package/dist/commonjs/features/list/dash.d.ts.map +1 -1
- package/dist/commonjs/features/list/dash.js +1 -7
- package/dist/commonjs/features/list/dash_wasm.d.ts.map +1 -1
- package/dist/commonjs/features/list/dash_wasm.js +1 -7
- package/dist/commonjs/features/list/media_source_main.d.ts.map +1 -1
- package/dist/commonjs/features/list/media_source_main.js +1 -7
- package/dist/commonjs/features/list/smooth.d.ts.map +1 -1
- package/dist/commonjs/features/list/smooth.js +1 -7
- package/dist/commonjs/features/types.d.ts +4 -20
- package/dist/commonjs/features/types.d.ts.map +1 -1
- package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/commonjs/main_thread/api/public_api.js +44 -40
- package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts +108 -166
- 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 +918 -1491
- package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts +308 -0
- package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -0
- package/dist/commonjs/main_thread/init/multi_thread_content_initializer.js +1713 -0
- package/dist/commonjs/main_thread/init/send_message.d.ts +3 -0
- package/dist/commonjs/main_thread/init/send_message.d.ts.map +1 -0
- package/dist/commonjs/main_thread/init/send_message.js +13 -0
- package/dist/commonjs/main_thread/init/utils/create_core_playback_observer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/utils/create_core_playback_observer.js +1 -2
- package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.d.ts +1 -1
- package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.d.ts.map +1 -1
- package/dist/commonjs/main_thread/types.d.ts +0 -537
- package/dist/commonjs/main_thread/types.d.ts.map +1 -1
- package/dist/commonjs/manifest/utils.d.ts.map +1 -1
- package/dist/commonjs/manifest/utils.js +4 -18
- package/dist/commonjs/mse/worker_media_source_interface.d.ts +2 -2
- package/dist/commonjs/mse/worker_media_source_interface.d.ts.map +1 -1
- package/dist/commonjs/mse/worker_media_source_interface.js +12 -12
- package/dist/commonjs/multithread_types.d.ts +915 -0
- package/dist/commonjs/multithread_types.d.ts.map +1 -0
- package/dist/commonjs/multithread_types.js +7 -0
- package/dist/commonjs/parsers/manifest/smooth/create_parser.d.ts +1 -1
- package/dist/commonjs/parsers/manifest/smooth/create_parser.d.ts.map +1 -1
- package/dist/commonjs/parsers/manifest/smooth/create_parser.js +27 -31
- package/dist/commonjs/parsers/manifest/smooth/parse_C_nodes.d.ts +2 -3
- package/dist/commonjs/parsers/manifest/smooth/parse_C_nodes.d.ts.map +1 -1
- package/dist/commonjs/parsers/manifest/smooth/parse_C_nodes.js +7 -16
- package/dist/commonjs/parsers/manifest/smooth/parse_protection_node.d.ts +2 -3
- package/dist/commonjs/parsers/manifest/smooth/parse_protection_node.d.ts.map +1 -1
- package/dist/commonjs/parsers/manifest/smooth/parse_protection_node.js +6 -37
- package/dist/commonjs/parsers/manifest/smooth/utils/parseBoolean.d.ts +1 -1
- package/dist/commonjs/parsers/manifest/smooth/utils/parseBoolean.d.ts.map +1 -1
- package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.d.ts +2 -3
- package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.d.ts.map +1 -1
- package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.js +5 -28
- package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts +28 -8
- package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts.map +1 -1
- package/dist/commonjs/playback_observer/media_element_playback_observer.js +146 -64
- package/dist/{es2017/playback_observer/core_playback_observer.d.ts → commonjs/playback_observer/worker_playback_observer.d.ts} +8 -8
- package/dist/commonjs/playback_observer/worker_playback_observer.d.ts.map +1 -0
- package/dist/commonjs/playback_observer/{core_playback_observer.js → worker_playback_observer.js} +13 -13
- package/dist/commonjs/transports/smooth/pipelines.d.ts.map +1 -1
- package/dist/commonjs/transports/smooth/pipelines.js +3 -25
- package/dist/commonjs/worker_entry_point.js +2 -62
- 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/core/{entry → main/common}/FreezeResolver.d.ts +3 -3
- package/dist/es2017/core/main/common/FreezeResolver.d.ts.map +1 -0
- package/dist/es2017/core/{entry → main/common}/FreezeResolver.js +3 -3
- package/dist/es2017/core/{entry → main/common}/content_time_boundaries_observer.d.ts +5 -5
- package/dist/es2017/core/main/common/content_time_boundaries_observer.d.ts.map +1 -0
- package/dist/es2017/core/{entry → main/common}/content_time_boundaries_observer.js +6 -6
- package/dist/{commonjs/core/entry → es2017/core/main/common}/create_content_time_boundaries_observer.d.ts +6 -6
- package/dist/es2017/core/main/common/create_content_time_boundaries_observer.d.ts.map +1 -0
- package/dist/es2017/core/{entry → main/common}/create_content_time_boundaries_observer.js +1 -1
- package/dist/{commonjs/core/entry → es2017/core/main/common}/get_buffered_data_per_media_buffer.d.ts +4 -4
- package/dist/es2017/core/main/common/get_buffered_data_per_media_buffer.d.ts.map +1 -0
- package/dist/es2017/core/{entry → main/common}/get_buffered_data_per_media_buffer.js +1 -1
- package/dist/{commonjs/core/entry → es2017/core/main/common}/get_thumbnail_data.d.ts +3 -3
- package/dist/es2017/core/main/common/get_thumbnail_data.d.ts.map +1 -0
- package/dist/es2017/core/{entry → main/common}/get_thumbnail_data.js +2 -2
- package/dist/{commonjs/core/entry → es2017/core/main/common}/synchronize_sinks_on_observation.d.ts +2 -2
- package/dist/es2017/core/main/common/synchronize_sinks_on_observation.d.ts.map +1 -0
- package/dist/{commonjs/core/entry → es2017/core/main/worker}/content_preparer.d.ts +15 -22
- package/dist/es2017/core/main/worker/content_preparer.d.ts.map +1 -0
- package/dist/es2017/core/{entry → main/worker}/content_preparer.js +55 -53
- package/dist/es2017/core/main/worker/globals.d.ts +14 -0
- package/dist/es2017/core/main/worker/globals.d.ts.map +1 -0
- package/dist/es2017/core/main/worker/globals.js +18 -0
- package/dist/es2017/core/main/worker/index.d.ts +3 -0
- package/dist/es2017/core/main/worker/index.d.ts.map +1 -0
- package/dist/es2017/core/main/worker/index.js +2 -0
- package/dist/es2017/core/main/worker/send_message.d.ts +4 -0
- package/dist/es2017/core/main/worker/send_message.d.ts.map +1 -0
- package/dist/es2017/core/main/worker/send_message.js +19 -0
- package/dist/{commonjs/core/entry → es2017/core/main/worker}/track_choice_setter.d.ts +4 -4
- package/dist/es2017/core/main/worker/track_choice_setter.d.ts.map +1 -0
- package/dist/es2017/core/{entry → main/worker}/track_choice_setter.js +4 -4
- package/dist/es2017/core/main/worker/worker_main.d.ts +2 -0
- package/dist/es2017/core/main/worker/worker_main.d.ts.map +1 -0
- package/dist/es2017/core/{entry/core_entry.js → main/worker/worker_main.js} +114 -153
- package/dist/es2017/core/{entry/core_text_displayer_interface.d.ts → main/worker/worker_text_displayer_interface.d.ts} +11 -11
- package/dist/es2017/core/main/worker/worker_text_displayer_interface.d.ts.map +1 -0
- package/dist/es2017/core/{entry/core_text_displayer_interface.js → main/worker/worker_text_displayer_interface.js} +10 -10
- package/dist/es2017/core/types.d.ts +1 -519
- package/dist/es2017/core/types.d.ts.map +1 -1
- package/dist/es2017/core/types.js +0 -1
- package/dist/es2017/experimental/features/local.d.ts.map +1 -1
- package/dist/es2017/experimental/features/local.js +1 -7
- package/dist/es2017/experimental/features/metaplaylist.d.ts.map +1 -1
- package/dist/es2017/experimental/features/metaplaylist.js +1 -7
- package/dist/es2017/experimental/features/multi_thread.d.ts.map +1 -1
- package/dist/es2017/experimental/features/multi_thread.js +2 -6
- package/dist/es2017/features/features_object.js +1 -1
- package/dist/es2017/features/list/dash.d.ts.map +1 -1
- package/dist/es2017/features/list/dash.js +1 -7
- package/dist/es2017/features/list/dash_wasm.d.ts.map +1 -1
- package/dist/es2017/features/list/dash_wasm.js +1 -7
- package/dist/es2017/features/list/media_source_main.d.ts.map +1 -1
- package/dist/es2017/features/list/media_source_main.js +1 -7
- package/dist/es2017/features/list/smooth.d.ts.map +1 -1
- package/dist/es2017/features/list/smooth.js +1 -7
- package/dist/es2017/features/types.d.ts +4 -20
- package/dist/es2017/features/types.d.ts.map +1 -1
- package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/es2017/main_thread/api/public_api.js +45 -41
- package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts +108 -166
- 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 +760 -1405
- package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts +308 -0
- package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -0
- package/dist/es2017/main_thread/init/multi_thread_content_initializer.js +1559 -0
- package/dist/es2017/main_thread/init/send_message.d.ts +3 -0
- package/dist/es2017/main_thread/init/send_message.d.ts.map +1 -0
- package/dist/es2017/main_thread/init/send_message.js +10 -0
- package/dist/es2017/main_thread/init/utils/create_core_playback_observer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/utils/create_core_playback_observer.js +1 -2
- package/dist/es2017/main_thread/init/utils/update_manifest_codec_support.d.ts +1 -1
- package/dist/es2017/main_thread/init/utils/update_manifest_codec_support.d.ts.map +1 -1
- package/dist/es2017/main_thread/types.d.ts +0 -537
- package/dist/es2017/main_thread/types.d.ts.map +1 -1
- package/dist/es2017/manifest/utils.d.ts.map +1 -1
- package/dist/es2017/manifest/utils.js +4 -16
- package/dist/es2017/mse/worker_media_source_interface.d.ts +2 -2
- package/dist/es2017/mse/worker_media_source_interface.d.ts.map +1 -1
- package/dist/es2017/mse/worker_media_source_interface.js +12 -12
- package/dist/es2017/multithread_types.d.ts +915 -0
- package/dist/es2017/multithread_types.d.ts.map +1 -0
- package/dist/es2017/multithread_types.js +6 -0
- package/dist/es2017/parsers/manifest/smooth/create_parser.d.ts +1 -1
- package/dist/es2017/parsers/manifest/smooth/create_parser.d.ts.map +1 -1
- package/dist/es2017/parsers/manifest/smooth/create_parser.js +27 -31
- package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.d.ts +2 -3
- package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.d.ts.map +1 -1
- package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.js +7 -16
- package/dist/es2017/parsers/manifest/smooth/parse_protection_node.d.ts +2 -3
- package/dist/es2017/parsers/manifest/smooth/parse_protection_node.d.ts.map +1 -1
- package/dist/es2017/parsers/manifest/smooth/parse_protection_node.js +6 -15
- package/dist/es2017/parsers/manifest/smooth/utils/parseBoolean.d.ts +1 -1
- package/dist/es2017/parsers/manifest/smooth/utils/parseBoolean.d.ts.map +1 -1
- package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.d.ts +2 -3
- package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.d.ts.map +1 -1
- package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.js +5 -6
- package/dist/es2017/playback_observer/media_element_playback_observer.d.ts +28 -8
- package/dist/es2017/playback_observer/media_element_playback_observer.d.ts.map +1 -1
- package/dist/es2017/playback_observer/media_element_playback_observer.js +144 -64
- package/dist/{commonjs/playback_observer/core_playback_observer.d.ts → es2017/playback_observer/worker_playback_observer.d.ts} +8 -8
- package/dist/es2017/playback_observer/worker_playback_observer.d.ts.map +1 -0
- package/dist/es2017/playback_observer/{core_playback_observer.js → worker_playback_observer.js} +2 -2
- package/dist/es2017/transports/smooth/pipelines.d.ts.map +1 -1
- package/dist/es2017/transports/smooth/pipelines.js +3 -25
- package/dist/es2017/worker_entry_point.js +2 -62
- package/dist/mpd-parser.wasm +0 -0
- package/dist/rx-player.js +19165 -21886
- package/dist/rx-player.min.js +20 -20
- package/dist/worker.js +8 -8
- package/eslint.config.mjs +647 -348
- package/package.json +6 -6
- package/src/README.md +198 -88
- package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/core/{entry → main}/README.md +1 -1
- package/src/core/{entry → main/common}/FreezeResolver.ts +7 -7
- package/src/core/{entry → main/common}/content_time_boundaries_observer.ts +10 -10
- package/src/core/{entry → main/common}/create_content_time_boundaries_observer.ts +7 -7
- package/src/core/{entry → main/common}/get_buffered_data_per_media_buffer.ts +6 -6
- package/src/core/{entry → main/common}/get_thumbnail_data.ts +5 -5
- package/src/core/{entry → main/common}/synchronize_sinks_on_observation.ts +2 -2
- package/src/core/{entry → main/worker}/content_preparer.ts +76 -77
- package/src/core/main/worker/globals.ts +38 -0
- package/src/core/main/worker/index.ts +2 -0
- package/src/core/main/worker/send_message.ts +28 -0
- package/src/core/{entry → main/worker}/track_choice_setter.ts +7 -7
- package/src/core/{entry/core_entry.ts → main/worker/worker_main.ts} +149 -223
- package/src/core/{entry/core_text_displayer_interface.ts → main/worker/worker_text_displayer_interface.ts} +26 -26
- package/src/core/types.ts +3 -631
- package/src/experimental/features/__tests__/local.test.ts +2 -11
- package/src/experimental/features/__tests__/metaplaylist.test.ts +2 -11
- package/src/experimental/features/__tests__/multi_thread.test.ts +3 -8
- package/src/experimental/features/local.ts +1 -7
- package/src/experimental/features/metaplaylist.ts +1 -7
- package/src/experimental/features/multi_thread.ts +2 -6
- package/src/features/features_object.ts +1 -1
- package/src/features/list/__tests__/dash.test.ts +3 -12
- package/src/features/list/__tests__/smooth.test.ts +2 -11
- package/src/features/list/dash.ts +1 -7
- package/src/features/list/dash_wasm.ts +1 -7
- package/src/features/list/media_source_main.ts +1 -7
- package/src/features/list/smooth.ts +1 -7
- package/src/features/types.ts +4 -23
- package/src/main_thread/README.md +0 -8
- package/src/main_thread/api/public_api.ts +51 -47
- package/src/main_thread/init/media_source_content_initializer.ts +1164 -2046
- package/src/main_thread/init/multi_thread_content_initializer.ts +2330 -0
- package/src/main_thread/init/send_message.ts +15 -0
- package/src/main_thread/init/utils/create_core_playback_observer.ts +1 -2
- package/src/main_thread/init/utils/update_manifest_codec_support.ts +1 -1
- package/src/main_thread/types.ts +0 -610
- package/src/manifest/utils.ts +4 -20
- package/src/mse/worker_media_source_interface.ts +35 -35
- package/src/multithread_types.ts +1095 -0
- package/src/parsers/manifest/smooth/create_parser.ts +34 -40
- package/src/parsers/manifest/smooth/parse_C_nodes.ts +8 -19
- package/src/parsers/manifest/smooth/parse_protection_node.ts +9 -17
- package/src/parsers/manifest/smooth/utils/parseBoolean.ts +1 -1
- package/src/parsers/manifest/smooth/utils/reduceChildren.ts +7 -10
- package/src/playback_observer/media_element_playback_observer.ts +177 -73
- package/src/playback_observer/{core_playback_observer.ts → worker_playback_observer.ts} +13 -13
- package/src/transports/smooth/pipelines.ts +5 -25
- package/src/worker_entry_point.ts +2 -71
- package/dist/commonjs/core/entry/FreezeResolver.d.ts.map +0 -1
- package/dist/commonjs/core/entry/content_preparer.d.ts.map +0 -1
- package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +0 -1
- package/dist/commonjs/core/entry/core_entry.d.ts +0 -36
- package/dist/commonjs/core/entry/core_entry.d.ts.map +0 -1
- package/dist/commonjs/core/entry/core_text_displayer_interface.d.ts.map +0 -1
- package/dist/commonjs/core/entry/create_content_time_boundaries_observer.d.ts.map +0 -1
- package/dist/commonjs/core/entry/get_buffered_data_per_media_buffer.d.ts.map +0 -1
- package/dist/commonjs/core/entry/get_thumbnail_data.d.ts.map +0 -1
- package/dist/commonjs/core/entry/index.d.ts +0 -5
- package/dist/commonjs/core/entry/index.d.ts.map +0 -1
- package/dist/commonjs/core/entry/index.js +0 -4
- package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
- package/dist/commonjs/core/entry/track_choice_setter.d.ts.map +0 -1
- package/dist/commonjs/core/entry/utils.d.ts +0 -3
- package/dist/commonjs/core/entry/utils.d.ts.map +0 -1
- package/dist/commonjs/core/entry/utils.js +0 -11
- package/dist/commonjs/main_thread/core_interface/base.d.ts +0 -13
- package/dist/commonjs/main_thread/core_interface/base.d.ts.map +0 -1
- package/dist/commonjs/main_thread/core_interface/base.js +0 -32
- package/dist/commonjs/main_thread/core_interface/monothread.d.ts +0 -13
- package/dist/commonjs/main_thread/core_interface/monothread.d.ts.map +0 -1
- package/dist/commonjs/main_thread/core_interface/monothread.js +0 -56
- package/dist/commonjs/main_thread/core_interface/multithread.d.ts +0 -25
- package/dist/commonjs/main_thread/core_interface/multithread.d.ts.map +0 -1
- package/dist/commonjs/main_thread/core_interface/multithread.js +0 -67
- package/dist/commonjs/main_thread/core_interface/types.d.ts +0 -6
- package/dist/commonjs/main_thread/core_interface/types.d.ts.map +0 -1
- package/dist/commonjs/main_thread/core_interface/types.js +0 -2
- package/dist/commonjs/playback_observer/core_playback_observer.d.ts.map +0 -1
- package/dist/es2017/core/entry/FreezeResolver.d.ts.map +0 -1
- package/dist/es2017/core/entry/content_preparer.d.ts.map +0 -1
- package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +0 -1
- package/dist/es2017/core/entry/core_entry.d.ts +0 -36
- package/dist/es2017/core/entry/core_entry.d.ts.map +0 -1
- package/dist/es2017/core/entry/core_text_displayer_interface.d.ts.map +0 -1
- package/dist/es2017/core/entry/create_content_time_boundaries_observer.d.ts.map +0 -1
- package/dist/es2017/core/entry/get_buffered_data_per_media_buffer.d.ts.map +0 -1
- package/dist/es2017/core/entry/get_thumbnail_data.d.ts.map +0 -1
- package/dist/es2017/core/entry/index.d.ts +0 -5
- package/dist/es2017/core/entry/index.d.ts.map +0 -1
- package/dist/es2017/core/entry/index.js +0 -2
- package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
- package/dist/es2017/core/entry/track_choice_setter.d.ts.map +0 -1
- package/dist/es2017/core/entry/utils.d.ts +0 -3
- package/dist/es2017/core/entry/utils.d.ts.map +0 -1
- package/dist/es2017/core/entry/utils.js +0 -8
- package/dist/es2017/main_thread/core_interface/base.d.ts +0 -13
- package/dist/es2017/main_thread/core_interface/base.d.ts.map +0 -1
- package/dist/es2017/main_thread/core_interface/base.js +0 -28
- package/dist/es2017/main_thread/core_interface/monothread.d.ts +0 -13
- package/dist/es2017/main_thread/core_interface/monothread.d.ts.map +0 -1
- package/dist/es2017/main_thread/core_interface/monothread.js +0 -32
- package/dist/es2017/main_thread/core_interface/multithread.d.ts +0 -25
- package/dist/es2017/main_thread/core_interface/multithread.d.ts.map +0 -1
- package/dist/es2017/main_thread/core_interface/multithread.js +0 -45
- package/dist/es2017/main_thread/core_interface/types.d.ts +0 -6
- package/dist/es2017/main_thread/core_interface/types.d.ts.map +0 -1
- package/dist/es2017/main_thread/core_interface/types.js +0 -1
- package/dist/es2017/playback_observer/core_playback_observer.d.ts.map +0 -1
- package/src/core/entry/index.ts +0 -4
- package/src/core/entry/utils.ts +0 -11
- package/src/main_thread/core_interface/README.md +0 -22
- package/src/main_thread/core_interface/base.ts +0 -36
- package/src/main_thread/core_interface/monothread.ts +0 -46
- package/src/main_thread/core_interface/multithread.ts +0 -49
- package/src/main_thread/core_interface/types.ts +0 -5
- /package/dist/commonjs/core/{entry → main/common}/synchronize_sinks_on_observation.js +0 -0
- /package/dist/es2017/core/{entry → main/common}/synchronize_sinks_on_observation.js +0 -0
|
@@ -1,148 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2015 CANAL+ Group
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
1
17
|
import type { IMediaElement } from "../../compat/browser_compatibility_types";
|
|
2
|
-
import
|
|
18
|
+
import isCodecSupported from "../../compat/is_codec_supported";
|
|
3
19
|
import mayMediaElementFailOnUndecipherableData from "../../compat/may_media_element_fail_on_undecipherable_data";
|
|
4
20
|
import shouldReloadMediaSourceOnDecipherabilityUpdate from "../../compat/should_reload_media_source_on_decipherability_update";
|
|
5
|
-
import
|
|
21
|
+
import config from "../../config";
|
|
6
22
|
import type {
|
|
7
23
|
IAdaptiveRepresentationSelectorArguments,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ICoreMessage,
|
|
13
|
-
ISentLogValue,
|
|
14
|
-
} from "../../core/types";
|
|
15
|
-
import { CoreMessageType } from "../../core/types";
|
|
24
|
+
IRepresentationEstimator,
|
|
25
|
+
} from "../../core/adaptive";
|
|
26
|
+
import AdaptiveRepresentationSelector from "../../core/adaptive";
|
|
27
|
+
import CmcdDataBuilder from "../../core/cmcd";
|
|
16
28
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
CdnPrioritizer,
|
|
30
|
+
createThumbnailFetcher,
|
|
31
|
+
ManifestFetcher,
|
|
32
|
+
SegmentQueueCreator,
|
|
33
|
+
} from "../../core/fetchers";
|
|
34
|
+
import createContentTimeBoundariesObserver from "../../core/main/common/create_content_time_boundaries_observer";
|
|
35
|
+
import type { IFreezeResolution } from "../../core/main/common/FreezeResolver";
|
|
36
|
+
import FreezeResolver from "../../core/main/common/FreezeResolver";
|
|
37
|
+
import getThumbnailData from "../../core/main/common/get_thumbnail_data";
|
|
38
|
+
import synchronizeSegmentSinksOnObservation from "../../core/main/common/synchronize_sinks_on_observation";
|
|
39
|
+
import SegmentSinksStore from "../../core/segment_sinks";
|
|
40
|
+
import type {
|
|
41
|
+
IStreamOrchestratorOptions,
|
|
42
|
+
IStreamOrchestratorCallbacks,
|
|
43
|
+
INeedsBufferFlushPayload,
|
|
44
|
+
} from "../../core/stream";
|
|
45
|
+
import StreamOrchestrator from "../../core/stream";
|
|
46
|
+
import type { ITextDisplayerInterface } from "../../core/types";
|
|
47
|
+
import type { EncryptedMediaError } from "../../errors";
|
|
48
|
+
import { MediaError } from "../../errors";
|
|
23
49
|
import features from "../../features";
|
|
24
50
|
import log from "../../log";
|
|
25
|
-
import type {
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
updateDecipherabilityFromKeyIds,
|
|
29
|
-
updateDecipherabilityFromProtectionData,
|
|
30
|
-
} from "../../manifest";
|
|
31
|
-
import MainMediaSourceInterface from "../../mse/main_media_source_interface";
|
|
32
|
-
import type {
|
|
33
|
-
IReadOnlyPlaybackObserver,
|
|
34
|
-
IMediaElementPlaybackObserver,
|
|
35
|
-
} from "../../playback_observer";
|
|
51
|
+
import type { IManifest, IPeriodMetadata, ICodecSupportInfo } from "../../manifest";
|
|
52
|
+
import type MainMediaSourceInterface from "../../mse/main_media_source_interface";
|
|
53
|
+
import type { IMediaElementPlaybackObserver } from "../../playback_observer";
|
|
36
54
|
import type {
|
|
37
55
|
ICmcdOptions,
|
|
38
56
|
IInitialManifest,
|
|
39
57
|
IKeySystemOption,
|
|
40
58
|
IPlayerError,
|
|
41
|
-
IRepresentationFilter,
|
|
42
59
|
} from "../../public_types";
|
|
43
|
-
import type { IThumbnailResponse,
|
|
44
|
-
import
|
|
60
|
+
import type { IThumbnailResponse, ITransportPipelines } from "../../transports";
|
|
61
|
+
import areArraysOfNumbersEqual from "../../utils/are_arrays_of_numbers_equal";
|
|
45
62
|
import assert, { assertUnreachable } from "../../utils/assert";
|
|
46
|
-
import
|
|
63
|
+
import createCancellablePromise from "../../utils/create_cancellable_promise";
|
|
47
64
|
import isNullOrUndefined from "../../utils/is_null_or_undefined";
|
|
48
|
-
import
|
|
65
|
+
import noop from "../../utils/noop";
|
|
49
66
|
import objectAssign from "../../utils/object_assign";
|
|
50
67
|
import type { IReadOnlySharedReference } from "../../utils/reference";
|
|
51
|
-
import
|
|
52
|
-
import
|
|
68
|
+
import type { ISyncOrAsyncValue } from "../../utils/sync_or_async";
|
|
69
|
+
import SyncOrAsync from "../../utils/sync_or_async";
|
|
53
70
|
import type { CancellationSignal } from "../../utils/task_canceller";
|
|
54
|
-
import TaskCanceller
|
|
55
|
-
import type CoreInterface from "../core_interface/types";
|
|
56
|
-
import type { IContentProtection } from "../decrypt";
|
|
57
|
-
import type IContentDecryptor from "../decrypt";
|
|
71
|
+
import TaskCanceller from "../../utils/task_canceller";
|
|
58
72
|
import { ContentDecryptorState, getKeySystemConfiguration } from "../decrypt";
|
|
73
|
+
import type { IProcessedProtectionData } from "../decrypt";
|
|
74
|
+
import type ContentDecryptor from "../decrypt";
|
|
59
75
|
import type { ITextDisplayer } from "../text_displayer";
|
|
60
|
-
import { MainThreadMessageType } from "../types";
|
|
61
76
|
import type { ITextDisplayerOptions } from "./types";
|
|
62
77
|
import { ContentInitializer } from "./types";
|
|
63
|
-
import type { ICorePlaybackObservation } from "./utils/create_core_playback_observer";
|
|
64
78
|
import createCorePlaybackObserver from "./utils/create_core_playback_observer";
|
|
65
|
-
import
|
|
66
|
-
resetMediaElement,
|
|
67
|
-
disableRemotePlaybackOnManagedMediaSource,
|
|
68
|
-
} from "./utils/create_media_source";
|
|
79
|
+
import createMediaSource from "./utils/create_media_source";
|
|
69
80
|
import type { IInitialTimeOptions } from "./utils/get_initial_time";
|
|
70
81
|
import getInitialTime from "./utils/get_initial_time";
|
|
71
82
|
import getLoadedReference from "./utils/get_loaded_reference";
|
|
72
83
|
import performInitialSeekAndPlay from "./utils/initial_seek_and_play";
|
|
84
|
+
import initializeContentDecryption from "./utils/initialize_content_decryption";
|
|
85
|
+
import MainThreadTextDisplayerInterface from "./utils/main_thread_text_displayer_interface";
|
|
73
86
|
import RebufferingController from "./utils/rebuffering_controller";
|
|
74
|
-
import StreamEventsEmitter from "./utils/stream_events_emitter
|
|
87
|
+
import StreamEventsEmitter from "./utils/stream_events_emitter";
|
|
75
88
|
import listenToMediaError from "./utils/throw_on_media_error";
|
|
76
|
-
import { updateManifestCodecSupport } from "./utils/update_manifest_codec_support";
|
|
77
|
-
|
|
78
|
-
const generateContentId = idGenerator();
|
|
79
89
|
|
|
80
90
|
/**
|
|
91
|
+
* Allows to load a new content thanks to the MediaSource Extensions (a.k.a. MSE)
|
|
92
|
+
* Web APIs.
|
|
93
|
+
*
|
|
94
|
+
* Through this `ContentInitializer`, a Manifest will be fetched (and depending
|
|
95
|
+
* on the situation, refreshed), a `MediaSource` instance will be linked to the
|
|
96
|
+
* wanted `HTMLMediaElement` and chunks of media data, called segments, will be
|
|
97
|
+
* pushed on buffers associated to this `MediaSource` instance.
|
|
98
|
+
*
|
|
81
99
|
* @class MediaSourceContentInitializer
|
|
82
100
|
*/
|
|
83
101
|
export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
84
102
|
/** Constructor settings associated to this `MediaSourceContentInitializer`. */
|
|
85
|
-
private
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The Core may be sending messages as soon as we're preparing the content but
|
|
89
|
-
* the `MediaSourceContentInitializer` is only able to handle all of them only
|
|
90
|
-
* once `start`ed.
|
|
91
|
-
*
|
|
92
|
-
* As such `_queuedCoreMessages` is set to an Array when `prepare` has been
|
|
93
|
-
* called but not `start` yet, and contains all core messages that have to
|
|
94
|
-
* be processed when `start` is called.
|
|
95
|
-
*
|
|
96
|
-
* It is set to `null` when there's no need to rely on that queue (either not
|
|
97
|
-
* yet `prepare`d or already `start`ed).
|
|
98
|
-
*/
|
|
99
|
-
private _queuedCoreMessages: ICoreMessage[] | null;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Information relative to the current loaded content.
|
|
103
|
-
*
|
|
104
|
-
* `null` when no content is prepared yet.
|
|
105
|
-
*/
|
|
106
|
-
private _currentContentInfo: IMediaSourceContentInitializerContentInfos | null;
|
|
103
|
+
private _initSettings: IInitializeArguments;
|
|
107
104
|
/**
|
|
108
105
|
* `TaskCanceller` allowing to abort everything that the
|
|
109
106
|
* `MediaSourceContentInitializer` is doing.
|
|
110
107
|
*/
|
|
111
108
|
private _initCanceller: TaskCanceller;
|
|
109
|
+
/** Interface allowing to fetch and refresh the Manifest. */
|
|
110
|
+
private _manifestFetcher: ManifestFetcher;
|
|
112
111
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* the `_initCanceller`) or when reloading the content.
|
|
112
|
+
* Reference to the `Manifest` Object:
|
|
113
|
+
* - as an asynchronous value if it is still in the process of being loaded.
|
|
114
|
+
* - as an synchronous value if it has been loaded
|
|
115
|
+
* - `null` if the load task has not started yet.
|
|
118
116
|
*/
|
|
119
|
-
private
|
|
117
|
+
private _manifest: ISyncOrAsyncValue<IManifest> | null;
|
|
120
118
|
|
|
121
|
-
private
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
119
|
+
private _cmcdDataBuilder: CmcdDataBuilder | null;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Describes the decryption capabilities on the current content, discriminated
|
|
123
|
+
* by a `status` property:
|
|
124
|
+
*
|
|
125
|
+
* - If set to `"uninitialized"`, decryption capabilities have not been
|
|
126
|
+
* set up yet.
|
|
127
|
+
*
|
|
128
|
+
* - If set to `"disabled"`, decryption capabilities are explicitely
|
|
129
|
+
* disabled. If encrypted content needs to be decrypted, the accompanying
|
|
130
|
+
* error `value` describes the reason why decryption is not enabled.
|
|
131
|
+
*
|
|
132
|
+
* - If set to `"enabled"`, decryption capabilities are available, and
|
|
133
|
+
* `value` points to the corresponding `ContentDecryptor`.
|
|
134
|
+
*/
|
|
135
|
+
private _decryptionCapabilities:
|
|
136
|
+
| {
|
|
137
|
+
status: "uninitialized";
|
|
138
|
+
value: null;
|
|
132
139
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* receive image thumbnails.
|
|
137
|
-
*/
|
|
138
|
-
pendingThumbnailFetching: Map<
|
|
139
|
-
number /* request id */,
|
|
140
|
-
{
|
|
141
|
-
resolve: (value: IThumbnailResponse) => void;
|
|
142
|
-
reject: (error: Error) => void;
|
|
140
|
+
| {
|
|
141
|
+
status: "disabled";
|
|
142
|
+
value: EncryptedMediaError;
|
|
143
143
|
}
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
| {
|
|
145
|
+
status: "enabled";
|
|
146
|
+
value: ContentDecryptor;
|
|
147
|
+
};
|
|
146
148
|
|
|
147
149
|
/**
|
|
148
150
|
* Create a new `MediaSourceContentInitializer`, associated to the given
|
|
@@ -151,173 +153,78 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
151
153
|
*/
|
|
152
154
|
constructor(settings: IInitializeArguments) {
|
|
153
155
|
super();
|
|
154
|
-
this.
|
|
156
|
+
this._initSettings = settings;
|
|
155
157
|
this._initCanceller = new TaskCanceller();
|
|
156
|
-
this.
|
|
157
|
-
this.
|
|
158
|
-
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
this._manifest = null;
|
|
159
|
+
this._decryptionCapabilities = { status: "uninitialized", value: null };
|
|
160
|
+
const urls = settings.url === undefined ? undefined : [settings.url];
|
|
161
|
+
this._cmcdDataBuilder =
|
|
162
|
+
settings.cmcd === undefined ? null : new CmcdDataBuilder(settings.cmcd);
|
|
163
|
+
this._manifestFetcher = new ManifestFetcher(urls, settings.transport, {
|
|
164
|
+
...settings.manifestRequestSettings,
|
|
165
|
+
lowLatencyMode: settings.lowLatencyMode,
|
|
166
|
+
cmcdDataBuilder: this._cmcdDataBuilder,
|
|
167
|
+
});
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
/**
|
|
168
171
|
* Perform non-destructive preparation steps, to prepare a future content.
|
|
172
|
+
* For now, this mainly mean loading the Manifest document.
|
|
169
173
|
*/
|
|
170
174
|
public prepare(): void {
|
|
171
|
-
if (this.
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const contentId = generateContentId();
|
|
175
|
-
const {
|
|
176
|
-
adaptiveOptions,
|
|
177
|
-
transport,
|
|
178
|
-
transportOptions,
|
|
179
|
-
useMseInWorker,
|
|
180
|
-
coreInterface,
|
|
181
|
-
} = this._settings;
|
|
182
|
-
const { wantedBufferAhead, maxVideoBufferSize, maxBufferAhead, maxBufferBehind } =
|
|
183
|
-
this._settings.bufferOptions;
|
|
184
|
-
const initialVideoBitrate = adaptiveOptions.initialBitrates.video;
|
|
185
|
-
const initialAudioBitrate = adaptiveOptions.initialBitrates.audio;
|
|
186
|
-
this._currentContentInfo = {
|
|
187
|
-
contentId,
|
|
188
|
-
contentDecryptor: null,
|
|
189
|
-
manifest: null,
|
|
190
|
-
mediaSourceInfo: null,
|
|
191
|
-
rebufferingController: null,
|
|
192
|
-
streamEventsEmitter: null,
|
|
193
|
-
initialTime: undefined,
|
|
194
|
-
autoPlay: undefined,
|
|
195
|
-
initialPlayPerformed: null,
|
|
196
|
-
useMseInWorker,
|
|
197
|
-
};
|
|
198
|
-
coreInterface.sendMessage({
|
|
199
|
-
type: MainThreadMessageType.PrepareContent,
|
|
200
|
-
value: {
|
|
201
|
-
contentId,
|
|
202
|
-
cmcd: this._settings.cmcd,
|
|
203
|
-
enableRepresentationAvoidance: this._settings.enableRepresentationAvoidance,
|
|
204
|
-
url: this._settings.url,
|
|
205
|
-
hasText: this._hasTextBufferFeature(),
|
|
206
|
-
transport,
|
|
207
|
-
transportOptions,
|
|
208
|
-
initialVideoBitrate,
|
|
209
|
-
initialAudioBitrate,
|
|
210
|
-
manifestRetryOptions: {
|
|
211
|
-
...this._settings.manifestRequestSettings,
|
|
212
|
-
lowLatencyMode: this._settings.lowLatencyMode,
|
|
213
|
-
},
|
|
214
|
-
segmentRetryOptions: this._settings.segmentRequestOptions,
|
|
215
|
-
useMseInWorker,
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
this._initCanceller.signal.register(() => {
|
|
219
|
-
coreInterface.sendMessage({
|
|
220
|
-
type: MainThreadMessageType.StopContent,
|
|
221
|
-
contentId,
|
|
222
|
-
value: null,
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
if (this._initCanceller.isUsed()) {
|
|
175
|
+
if (this._manifest !== null) {
|
|
226
176
|
return;
|
|
227
177
|
}
|
|
228
|
-
this.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return l;
|
|
244
|
-
case "object":
|
|
245
|
-
if (l === null) {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
return formatSentLogObject(l);
|
|
249
|
-
default:
|
|
250
|
-
assertUnreachable(l);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
switch (msgData.value.logLevel) {
|
|
254
|
-
case "NONE":
|
|
255
|
-
break;
|
|
256
|
-
case "ERROR":
|
|
257
|
-
log.error(msgData.value.namespace, ...formatted);
|
|
258
|
-
break;
|
|
259
|
-
case "WARNING":
|
|
260
|
-
log.warn(msgData.value.namespace, ...formatted);
|
|
261
|
-
break;
|
|
262
|
-
case "INFO":
|
|
263
|
-
log.info(msgData.value.namespace, ...formatted);
|
|
264
|
-
break;
|
|
265
|
-
case "DEBUG":
|
|
266
|
-
log.debug(msgData.value.namespace, ...formatted);
|
|
267
|
-
break;
|
|
268
|
-
default:
|
|
269
|
-
assertUnreachable(msgData.value.logLevel);
|
|
270
|
-
}
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
default:
|
|
274
|
-
if (this._queuedCoreMessages !== null) {
|
|
275
|
-
this._queuedCoreMessages.push(msgData);
|
|
276
|
-
}
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
this._settings.coreInterface.addMessageListener(onmessage);
|
|
281
|
-
const onmessageerror = () => {
|
|
282
|
-
log.error("Init", "Error when receiving message from core.");
|
|
283
|
-
};
|
|
284
|
-
this._settings.coreInterface.addErrorListener(onmessageerror);
|
|
178
|
+
this._manifest = SyncOrAsync.createAsync(
|
|
179
|
+
createCancellablePromise(this._initCanceller.signal, (res, rej) => {
|
|
180
|
+
this._manifestFetcher.addEventListener("warning", (err: IPlayerError) =>
|
|
181
|
+
this.trigger("warning", err),
|
|
182
|
+
);
|
|
183
|
+
this._manifestFetcher.addEventListener("error", (err: unknown) => {
|
|
184
|
+
this.trigger("error", err);
|
|
185
|
+
rej(err);
|
|
186
|
+
});
|
|
187
|
+
this._manifestFetcher.addEventListener("manifestReady", (manifest) => {
|
|
188
|
+
res(manifest);
|
|
189
|
+
});
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
this._manifestFetcher.start();
|
|
285
193
|
this._initCanceller.signal.register(() => {
|
|
286
|
-
|
|
287
|
-
this._settings.coreInterface.removeMessageListener(onmessage);
|
|
288
|
-
this._settings.coreInterface.removeErrorListener(onmessageerror);
|
|
194
|
+
this._manifestFetcher.dispose();
|
|
289
195
|
});
|
|
196
|
+
}
|
|
290
197
|
|
|
291
|
-
|
|
198
|
+
/**
|
|
199
|
+
* @param {HTMLMediaElement} mediaElement
|
|
200
|
+
* @param {Object} playbackObserver
|
|
201
|
+
*/
|
|
202
|
+
public start(
|
|
203
|
+
mediaElement: IMediaElement,
|
|
204
|
+
playbackObserver: IMediaElementPlaybackObserver,
|
|
205
|
+
): void {
|
|
206
|
+
this.prepare(); // Load Manifest if not already done
|
|
292
207
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
208
|
+
/** Translate errors coming from the media element into RxPlayer errors. */
|
|
209
|
+
listenToMediaError(
|
|
210
|
+
mediaElement,
|
|
211
|
+
(error: MediaError) => this._onFatalError(error),
|
|
297
212
|
this._initCanceller.signal,
|
|
298
|
-
[wantedBufferAhead, "wantedBufferAhead"],
|
|
299
|
-
[maxVideoBufferSize, "maxVideoBufferSize"],
|
|
300
|
-
[maxBufferAhead, "maxBufferAhead"],
|
|
301
|
-
[maxBufferBehind, "maxBufferBehind"],
|
|
302
|
-
[throttleVideoBitrate, "throttleVideoBitrate"],
|
|
303
213
|
);
|
|
304
214
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
215
|
+
this._setupInitialMediaSourceAndDecryption(mediaElement)
|
|
216
|
+
.then((initResult) =>
|
|
217
|
+
this._onInitialMediaSourceReady(
|
|
218
|
+
mediaElement,
|
|
219
|
+
initResult.mediaSource,
|
|
220
|
+
playbackObserver,
|
|
221
|
+
initResult.drmSystemId,
|
|
222
|
+
initResult.unlinkMediaSource,
|
|
223
|
+
),
|
|
224
|
+
)
|
|
225
|
+
.catch((err) => {
|
|
226
|
+
this._onFatalError(err);
|
|
311
227
|
});
|
|
312
|
-
limitVideoResolution.onUpdate(
|
|
313
|
-
(newVal) => {
|
|
314
|
-
coreInterface.sendMessage({
|
|
315
|
-
type: MainThreadMessageType.ReferenceUpdate,
|
|
316
|
-
value: { name: "limitVideoResolution", newVal },
|
|
317
|
-
});
|
|
318
|
-
},
|
|
319
|
-
{ clearSignal: this._initCanceller.signal, emitCurrentValue: true },
|
|
320
|
-
);
|
|
321
228
|
}
|
|
322
229
|
|
|
323
230
|
/**
|
|
@@ -328,472 +235,589 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
328
235
|
* DASH's MPD) will be refreshed immediately.
|
|
329
236
|
*/
|
|
330
237
|
public updateContentUrls(urls: string[] | undefined, refreshNow: boolean): void {
|
|
331
|
-
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
this._settings.coreInterface.sendMessage({
|
|
335
|
-
type: MainThreadMessageType.ContentUrlsUpdate,
|
|
336
|
-
contentId: this._currentContentInfo.contentId,
|
|
337
|
-
value: { urls, refreshNow },
|
|
338
|
-
});
|
|
238
|
+
this._manifestFetcher.updateContentUrls(urls, refreshNow);
|
|
339
239
|
}
|
|
340
240
|
|
|
341
241
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
242
|
+
* Stop content and free all resources linked to this
|
|
243
|
+
* `MediaSourceContentInitializer`.
|
|
344
244
|
*/
|
|
345
|
-
public
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
245
|
+
public dispose(): void {
|
|
246
|
+
this._initCanceller.cancel();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Callback called when an error interrupting playback arised.
|
|
251
|
+
* @param {*} err
|
|
252
|
+
*/
|
|
253
|
+
private _onFatalError(err: unknown) {
|
|
350
254
|
if (this._initCanceller.isUsed()) {
|
|
351
255
|
return;
|
|
352
256
|
}
|
|
257
|
+
this._initCanceller.cancel();
|
|
258
|
+
this.trigger("error", err);
|
|
259
|
+
}
|
|
353
260
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Initialize decryption mechanisms if needed and begin creating and relying
|
|
263
|
+
* on the initial `MediaSourceInterface` for this content.
|
|
264
|
+
* @param {HTMLMediaElement|null} mediaElement
|
|
265
|
+
* @returns {Promise.<Object>}
|
|
266
|
+
*/
|
|
267
|
+
private _setupInitialMediaSourceAndDecryption(mediaElement: IMediaElement): Promise<{
|
|
268
|
+
mediaSource: MainMediaSourceInterface;
|
|
269
|
+
drmSystemId: string | undefined;
|
|
270
|
+
unlinkMediaSource: TaskCanceller;
|
|
271
|
+
}> {
|
|
272
|
+
const initCanceller = this._initCanceller;
|
|
273
|
+
return createCancellablePromise(initCanceller.signal, (resolve) => {
|
|
274
|
+
const { keySystems } = this._initSettings;
|
|
275
|
+
|
|
276
|
+
/** Initialize decryption capabilities. */
|
|
277
|
+
const { statusRef: drmInitRef, contentDecryptor } = initializeContentDecryption(
|
|
361
278
|
mediaElement,
|
|
362
|
-
|
|
279
|
+
keySystems,
|
|
280
|
+
{
|
|
281
|
+
onWarning: (err: IPlayerError) => this.trigger("warning", err),
|
|
282
|
+
onError: (err: Error) => this._onFatalError(err),
|
|
283
|
+
onBlackListProtectionData: (val) => {
|
|
284
|
+
// Ugly IIFE workaround to allow async event listener
|
|
285
|
+
(async () => {
|
|
286
|
+
if (this._manifest === null) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const manifest =
|
|
290
|
+
this._manifest.syncValue ?? (await this._manifest.getValueAsAsync());
|
|
291
|
+
blackListProtectionDataOnManifest(manifest, val);
|
|
292
|
+
})().catch(noop);
|
|
293
|
+
},
|
|
294
|
+
onKeyIdsCompatibilityUpdate: (updates) => {
|
|
295
|
+
// Ugly IIFE workaround to allow async event listener
|
|
296
|
+
(async () => {
|
|
297
|
+
if (this._manifest === null) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const manifest =
|
|
301
|
+
this._manifest.syncValue ?? (await this._manifest.getValueAsAsync());
|
|
302
|
+
updateKeyIdsDecipherabilityOnManifest(
|
|
303
|
+
manifest,
|
|
304
|
+
updates.whitelistedKeyIds,
|
|
305
|
+
updates.blacklistedKeyIds,
|
|
306
|
+
updates.delistedKeyIds,
|
|
307
|
+
);
|
|
308
|
+
})().catch(noop);
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
onCodecSupportUpdate: () => {
|
|
312
|
+
const syncManifest = this._manifest?.syncValue;
|
|
313
|
+
if (isNullOrUndefined(syncManifest)) {
|
|
314
|
+
// The Manifest is not yet fetched, but we will be able to check
|
|
315
|
+
// the codecs once it is the case
|
|
316
|
+
this._manifest?.getValueAsAsync().then((loadedManifest) => {
|
|
317
|
+
if (this._initCanceller.isUsed()) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this._refreshManifestCodecSupport(loadedManifest, mediaElement);
|
|
321
|
+
}, noop);
|
|
322
|
+
} else {
|
|
323
|
+
this._refreshManifestCodecSupport(syncManifest, mediaElement);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
initCanceller.signal,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
if (contentDecryptor.enabled) {
|
|
331
|
+
this._decryptionCapabilities = {
|
|
332
|
+
status: "enabled",
|
|
333
|
+
value: contentDecryptor.value,
|
|
334
|
+
};
|
|
335
|
+
} else {
|
|
336
|
+
this._decryptionCapabilities = {
|
|
337
|
+
status: "disabled",
|
|
338
|
+
value: contentDecryptor.value,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
drmInitRef.onUpdate(
|
|
343
|
+
(drmStatus, stopListeningToDrmUpdates) => {
|
|
344
|
+
if (drmStatus.initializationState.type === "uninitialized") {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
stopListeningToDrmUpdates();
|
|
348
|
+
|
|
349
|
+
const mediaSourceCanceller = new TaskCanceller();
|
|
350
|
+
mediaSourceCanceller.linkToSignal(initCanceller.signal);
|
|
351
|
+
createMediaSource(mediaElement, mediaSourceCanceller.signal)
|
|
352
|
+
.then((mediaSource) => {
|
|
353
|
+
const lastDrmStatus = drmInitRef.getValue();
|
|
354
|
+
if (lastDrmStatus.initializationState.type === "awaiting-media-link") {
|
|
355
|
+
lastDrmStatus.initializationState.value.isMediaLinked.setValue(true);
|
|
356
|
+
drmInitRef.onUpdate(
|
|
357
|
+
(newDrmStatus, stopListeningToDrmUpdatesAgain) => {
|
|
358
|
+
if (newDrmStatus.initializationState.type === "initialized") {
|
|
359
|
+
stopListeningToDrmUpdatesAgain();
|
|
360
|
+
resolve({
|
|
361
|
+
mediaSource,
|
|
362
|
+
drmSystemId: newDrmStatus.drmSystemId,
|
|
363
|
+
unlinkMediaSource: mediaSourceCanceller,
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
{ emitCurrentValue: true, clearSignal: initCanceller.signal },
|
|
369
|
+
);
|
|
370
|
+
} else if (drmStatus.initializationState.type === "initialized") {
|
|
371
|
+
resolve({
|
|
372
|
+
mediaSource,
|
|
373
|
+
drmSystemId: drmStatus.drmSystemId,
|
|
374
|
+
unlinkMediaSource: mediaSourceCanceller,
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
.catch((err) => {
|
|
380
|
+
if (mediaSourceCanceller.isUsed()) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this._onFatalError(err);
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
{ emitCurrentValue: true, clearSignal: initCanceller.signal },
|
|
363
387
|
);
|
|
364
|
-
} else if (features.nativeTextDisplayer !== null) {
|
|
365
|
-
assert(this._hasTextBufferFeature());
|
|
366
|
-
textDisplayer = new features.nativeTextDisplayer(mediaElement);
|
|
367
|
-
} else {
|
|
368
|
-
assert(!this._hasTextBufferFeature());
|
|
369
|
-
}
|
|
370
|
-
this._initCanceller.signal.register(() => {
|
|
371
|
-
textDisplayer?.stop();
|
|
372
388
|
});
|
|
389
|
+
}
|
|
373
390
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
391
|
+
private async _onInitialMediaSourceReady(
|
|
392
|
+
mediaElement: IMediaElement,
|
|
393
|
+
initialMediaSource: MainMediaSourceInterface,
|
|
394
|
+
playbackObserver: IMediaElementPlaybackObserver,
|
|
395
|
+
drmSystemId: string | undefined,
|
|
396
|
+
initialMediaSourceCanceller: TaskCanceller,
|
|
397
|
+
): Promise<void> {
|
|
398
|
+
const {
|
|
399
|
+
adaptiveOptions,
|
|
400
|
+
autoPlay,
|
|
401
|
+
bufferOptions,
|
|
402
|
+
lowLatencyMode,
|
|
403
|
+
segmentRequestOptions,
|
|
404
|
+
speed,
|
|
405
|
+
startAt,
|
|
406
|
+
textTrackOptions,
|
|
407
|
+
transport,
|
|
408
|
+
} = this._initSettings;
|
|
409
|
+
const initCanceller = this._initCanceller;
|
|
410
|
+
assert(this._manifest !== null);
|
|
411
|
+
let manifest: IManifest;
|
|
412
|
+
try {
|
|
413
|
+
manifest = this._manifest.syncValue ?? (await this._manifest.getValueAsAsync());
|
|
414
|
+
} catch (_e) {
|
|
415
|
+
return; // The error should already have been processed through an event listener
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
manifest.addEventListener(
|
|
419
|
+
"manifestUpdate",
|
|
420
|
+
(updates) => {
|
|
421
|
+
this.trigger("manifestUpdate", updates);
|
|
422
|
+
this._refreshManifestCodecSupport(manifest, mediaElement);
|
|
423
|
+
},
|
|
424
|
+
initCanceller.signal,
|
|
379
425
|
);
|
|
380
426
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
427
|
+
manifest.addEventListener(
|
|
428
|
+
"decipherabilityUpdate",
|
|
429
|
+
(elts) => {
|
|
430
|
+
this.trigger("decipherabilityUpdate", elts);
|
|
431
|
+
},
|
|
432
|
+
initCanceller.signal,
|
|
433
|
+
);
|
|
386
434
|
|
|
387
|
-
|
|
388
|
-
|
|
435
|
+
manifest.addEventListener(
|
|
436
|
+
"supportUpdate",
|
|
437
|
+
() => {
|
|
438
|
+
this.trigger("codecSupportUpdate", null);
|
|
439
|
+
},
|
|
440
|
+
initCanceller.signal,
|
|
389
441
|
);
|
|
390
442
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
443
|
+
log.debug("Init", "Calculating initial time");
|
|
444
|
+
const initialTime = getInitialTime(manifest, lowLatencyMode, startAt);
|
|
445
|
+
log.debug("Init", "Initial time calculated", { initialTime });
|
|
446
|
+
|
|
447
|
+
/** Choose the right "Representation" for a given "Adaptation". */
|
|
448
|
+
const representationEstimator = AdaptiveRepresentationSelector(adaptiveOptions);
|
|
449
|
+
const subBufferOptions = objectAssign(
|
|
450
|
+
{ textTrackOptions, drmSystemId },
|
|
451
|
+
bufferOptions,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const cdnPrioritizer = new CdnPrioritizer(initCanceller.signal);
|
|
455
|
+
const segmentQueueCreator = new SegmentQueueCreator(
|
|
456
|
+
transport,
|
|
457
|
+
cdnPrioritizer,
|
|
458
|
+
this._cmcdDataBuilder,
|
|
459
|
+
segmentRequestOptions,
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
this._refreshManifestCodecSupport(manifest, mediaElement);
|
|
463
|
+
this.trigger("manifestReady", manifest);
|
|
464
|
+
if (initCanceller.isUsed()) {
|
|
465
|
+
return;
|
|
402
466
|
}
|
|
403
467
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
468
|
+
// handle initial load and reloads
|
|
469
|
+
this._setupContentWithNewMediaSource(
|
|
470
|
+
{
|
|
471
|
+
mediaElement,
|
|
472
|
+
playbackObserver,
|
|
473
|
+
mediaSource: initialMediaSource,
|
|
474
|
+
initialTime,
|
|
475
|
+
autoPlay,
|
|
476
|
+
manifest,
|
|
477
|
+
representationEstimator,
|
|
478
|
+
cdnPrioritizer,
|
|
479
|
+
segmentQueueCreator,
|
|
480
|
+
speed,
|
|
481
|
+
bufferOptions: subBufferOptions,
|
|
417
482
|
},
|
|
418
|
-
|
|
483
|
+
initialMediaSourceCanceller,
|
|
419
484
|
);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Load the content defined by the Manifest in the mediaSource given at the
|
|
489
|
+
* given position and playing status.
|
|
490
|
+
* This function recursively re-call itself when a MediaSource reload is
|
|
491
|
+
* wanted.
|
|
492
|
+
* @param {Object} args
|
|
493
|
+
* @param {Object} currentCanceller
|
|
494
|
+
*/
|
|
495
|
+
private _setupContentWithNewMediaSource(
|
|
496
|
+
args: IBufferingMediaSettings,
|
|
497
|
+
currentCanceller: TaskCanceller,
|
|
498
|
+
): void {
|
|
499
|
+
this._startLoadingContentOnMediaSource(
|
|
500
|
+
args,
|
|
501
|
+
this._createReloadMediaSourceCallback(args, currentCanceller),
|
|
502
|
+
currentCanceller.signal,
|
|
428
503
|
);
|
|
504
|
+
}
|
|
429
505
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
maximumPosition: number | undefined,
|
|
444
|
-
): void => {
|
|
445
|
-
const reloadingContentInfo = this._currentContentInfo;
|
|
446
|
-
if (reloadingContentInfo === null) {
|
|
447
|
-
log.warn("Init", "Asked to reload when no content is loaded.");
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (
|
|
451
|
-
reloadingContentInfo === null ||
|
|
452
|
-
reloadingContentInfo.mediaSourceInfo === null
|
|
453
|
-
) {
|
|
454
|
-
log.warn("Init", "Asked to reload when no MediaSource is active.");
|
|
506
|
+
/**
|
|
507
|
+
* Create `IReloadMediaSourceCallback` allowing to handle reload orders.
|
|
508
|
+
* @param {Object} args
|
|
509
|
+
* @param {Object} currentCanceller
|
|
510
|
+
*/
|
|
511
|
+
private _createReloadMediaSourceCallback(
|
|
512
|
+
args: IBufferingMediaSettings,
|
|
513
|
+
currentCanceller: TaskCanceller,
|
|
514
|
+
): IReloadMediaSourceCallback {
|
|
515
|
+
const initCanceller = this._initCanceller;
|
|
516
|
+
return (reloadOrder: { position: number; autoPlay: boolean }): void => {
|
|
517
|
+
currentCanceller.cancel();
|
|
518
|
+
if (initCanceller.isUsed()) {
|
|
455
519
|
return;
|
|
456
520
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
reloadingContentInfo.mediaSourceInfo.type === "main"
|
|
460
|
-
? reloadingContentInfo.mediaSourceInfo.mediaSource.id
|
|
461
|
-
: reloadingContentInfo.mediaSourceInfo.mediaSourceId;
|
|
462
|
-
this._settings.coreInterface.sendMessage({
|
|
463
|
-
type: MainThreadMessageType.MediaSourceReload,
|
|
464
|
-
mediaSourceId,
|
|
465
|
-
value: null,
|
|
466
|
-
});
|
|
467
|
-
reloadMediaSource(deltaPosition, minimumPosition, maximumPosition);
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Reset directly (synchronously) the current `MediaSource`.
|
|
472
|
-
*
|
|
473
|
-
* It is assumed that `core` already knows about this action. If not, call
|
|
474
|
-
* `notifyAndStartMediaSourceReload` instead.
|
|
475
|
-
* @param {number} deltaPosition - Position you want to seek to after
|
|
476
|
-
* reloading, as a delta in seconds from the last polled playing position.
|
|
477
|
-
* @param {number|undefined} minimumPosition - If set, minimum time bound
|
|
478
|
-
* in seconds after `deltaPosition` has been applied.
|
|
479
|
-
* @param {number|undefined} maximumPosition - If set, minimum time bound
|
|
480
|
-
* in seconds after `deltaPosition` has been applied.
|
|
481
|
-
*/
|
|
482
|
-
const reloadMediaSource = (
|
|
483
|
-
deltaPosition: number,
|
|
484
|
-
minimumPosition: number | undefined,
|
|
485
|
-
maximumPosition: number | undefined,
|
|
486
|
-
): void => {
|
|
487
|
-
const reloadingContentInfo = this._currentContentInfo;
|
|
488
|
-
if (reloadingContentInfo === null) {
|
|
489
|
-
log.warn("Init", "Asked to reload when no content is loaded.");
|
|
521
|
+
this.trigger("reloadingMediaSource", reloadOrder);
|
|
522
|
+
if (initCanceller.isUsed()) {
|
|
490
523
|
return;
|
|
491
524
|
}
|
|
492
|
-
const lastObservation = playbackObserver.getReference().getValue();
|
|
493
|
-
const currentPosition = lastObservation.position.getWanted();
|
|
494
|
-
const isPaused =
|
|
495
|
-
reloadingContentInfo.initialPlayPerformed?.getValue() === true ||
|
|
496
|
-
reloadingContentInfo.autoPlay === undefined
|
|
497
|
-
? lastObservation.paused
|
|
498
|
-
: !reloadingContentInfo.autoPlay;
|
|
499
|
-
let position = currentPosition + deltaPosition;
|
|
500
|
-
if (minimumPosition !== undefined) {
|
|
501
|
-
position = Math.max(minimumPosition, position);
|
|
502
|
-
}
|
|
503
|
-
if (maximumPosition !== undefined) {
|
|
504
|
-
position = Math.min(maximumPosition, position);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
this._reload(
|
|
508
|
-
mediaElement,
|
|
509
|
-
textDisplayer,
|
|
510
|
-
playbackObserver,
|
|
511
|
-
mediaSourceStatus,
|
|
512
|
-
position,
|
|
513
|
-
!isPaused,
|
|
514
|
-
);
|
|
515
|
-
};
|
|
516
525
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
this._currentContentInfo.mediaSourceInfo = {
|
|
528
|
-
type: "core",
|
|
529
|
-
mediaSourceId: msgData.mediaSourceId,
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
const mediaSourceLink = msgData.value;
|
|
533
|
-
mediaSourceStatus.onUpdate(
|
|
534
|
-
(currStatus, stopListening) => {
|
|
535
|
-
if (currStatus === MediaSourceInitializationStatus.AttachNow) {
|
|
536
|
-
stopListening();
|
|
537
|
-
log.info("media", "Attaching MediaSource URL to the media element");
|
|
538
|
-
if (mediaSourceLink.type === "handle") {
|
|
539
|
-
mediaElement.srcObject = mediaSourceLink.value;
|
|
540
|
-
this._currentMediaSourceCanceller.signal.register(() => {
|
|
541
|
-
mediaElement.srcObject = null;
|
|
542
|
-
});
|
|
543
|
-
} else {
|
|
544
|
-
mediaElement.src = mediaSourceLink.value;
|
|
545
|
-
this._currentMediaSourceCanceller.signal.register(() => {
|
|
546
|
-
resetMediaElement(mediaElement, mediaSourceLink.value);
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
disableRemotePlaybackOnManagedMediaSource(
|
|
550
|
-
mediaElement,
|
|
551
|
-
this._currentMediaSourceCanceller.signal,
|
|
552
|
-
);
|
|
553
|
-
mediaSourceStatus.setValue(MediaSourceInitializationStatus.Attached);
|
|
554
|
-
}
|
|
526
|
+
const newCanceller = new TaskCanceller();
|
|
527
|
+
newCanceller.linkToSignal(initCanceller.signal);
|
|
528
|
+
createMediaSource(args.mediaElement, newCanceller.signal)
|
|
529
|
+
.then((newMediaSource) => {
|
|
530
|
+
this._setupContentWithNewMediaSource(
|
|
531
|
+
{
|
|
532
|
+
...args,
|
|
533
|
+
mediaSource: newMediaSource,
|
|
534
|
+
initialTime: reloadOrder.position,
|
|
535
|
+
autoPlay: reloadOrder.autoPlay,
|
|
555
536
|
},
|
|
556
|
-
|
|
537
|
+
newCanceller,
|
|
557
538
|
);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
case CoreMessageType.Warning:
|
|
562
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
this.trigger("warning", formatCoreError(msgData.value));
|
|
566
|
-
break;
|
|
567
|
-
|
|
568
|
-
case CoreMessageType.Error:
|
|
569
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
539
|
+
})
|
|
540
|
+
.catch((err) => {
|
|
541
|
+
if (newCanceller.isUsed()) {
|
|
570
542
|
return;
|
|
571
543
|
}
|
|
572
|
-
this._onFatalError(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
544
|
+
this._onFatalError(err);
|
|
545
|
+
});
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Buffer the content on the given MediaSource.
|
|
551
|
+
* @param {Object} args
|
|
552
|
+
* @param {function} onReloadOrder
|
|
553
|
+
* @param {Object} cancelSignal
|
|
554
|
+
*/
|
|
555
|
+
private _startLoadingContentOnMediaSource(
|
|
556
|
+
args: IBufferingMediaSettings,
|
|
557
|
+
onReloadOrder: IReloadMediaSourceCallback,
|
|
558
|
+
cancelSignal: CancellationSignal,
|
|
559
|
+
): void {
|
|
560
|
+
const {
|
|
561
|
+
autoPlay,
|
|
562
|
+
bufferOptions,
|
|
563
|
+
initialTime,
|
|
564
|
+
manifest,
|
|
565
|
+
mediaElement,
|
|
566
|
+
mediaSource,
|
|
567
|
+
playbackObserver,
|
|
568
|
+
representationEstimator,
|
|
569
|
+
cdnPrioritizer,
|
|
570
|
+
segmentQueueCreator,
|
|
571
|
+
speed,
|
|
572
|
+
} = args;
|
|
573
|
+
const { transport } = this._initSettings;
|
|
574
|
+
|
|
575
|
+
const initialPeriod =
|
|
576
|
+
manifest.getPeriodForTime(initialTime) ?? manifest.getNextPeriod(initialTime);
|
|
577
|
+
if (initialPeriod === undefined) {
|
|
578
|
+
const error = new MediaError(
|
|
579
|
+
"MEDIA_STARTING_TIME_NOT_FOUND",
|
|
580
|
+
"Wanted starting time not found in the Manifest.",
|
|
581
|
+
);
|
|
582
|
+
return this._onFatalError(error);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let textDisplayerInterface: ITextDisplayerInterface | null = null;
|
|
586
|
+
const textDisplayer = createTextDisplayer(
|
|
587
|
+
mediaElement,
|
|
588
|
+
this._initSettings.textTrackOptions,
|
|
589
|
+
);
|
|
590
|
+
if (textDisplayer !== null) {
|
|
591
|
+
const sender = new MainThreadTextDisplayerInterface(textDisplayer);
|
|
592
|
+
textDisplayerInterface = sender;
|
|
593
|
+
cancelSignal.register(() => {
|
|
594
|
+
sender.stop();
|
|
595
|
+
textDisplayer?.stop();
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Interface to create media buffers. */
|
|
600
|
+
const segmentSinksStore = new SegmentSinksStore(
|
|
601
|
+
mediaSource,
|
|
602
|
+
mediaElement.nodeName === "VIDEO",
|
|
603
|
+
textDisplayerInterface,
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
cancelSignal.register(() => {
|
|
607
|
+
segmentSinksStore.disposeAll();
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
const { autoPlayResult, initialPlayPerformed } = performInitialSeekAndPlay(
|
|
611
|
+
{
|
|
612
|
+
mediaElement,
|
|
613
|
+
playbackObserver,
|
|
614
|
+
startTime: initialTime,
|
|
615
|
+
mustAutoPlay: autoPlay,
|
|
616
|
+
onWarning: (err) => {
|
|
617
|
+
this.trigger("warning", err);
|
|
618
|
+
},
|
|
619
|
+
isDirectfile: false,
|
|
620
|
+
},
|
|
621
|
+
cancelSignal,
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
if (cancelSignal.isCancelled()) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
initialPlayPerformed.onUpdate(
|
|
629
|
+
(isPerformed, stopListening) => {
|
|
630
|
+
if (isPerformed) {
|
|
631
|
+
stopListening();
|
|
632
|
+
const streamEventsEmitter = new StreamEventsEmitter(manifest, playbackObserver);
|
|
633
|
+
manifest.addEventListener(
|
|
634
|
+
"manifestUpdate",
|
|
635
|
+
() => {
|
|
636
|
+
streamEventsEmitter.onManifestUpdate(manifest);
|
|
637
|
+
},
|
|
638
|
+
cancelSignal,
|
|
581
639
|
);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
: formatSourceBufferError(error).serialize(),
|
|
639
|
-
});
|
|
640
|
-
});
|
|
640
|
+
streamEventsEmitter.addEventListener(
|
|
641
|
+
"event",
|
|
642
|
+
(payload) => {
|
|
643
|
+
this.trigger("streamEvent", payload);
|
|
644
|
+
},
|
|
645
|
+
cancelSignal,
|
|
646
|
+
);
|
|
647
|
+
streamEventsEmitter.addEventListener(
|
|
648
|
+
"eventSkip",
|
|
649
|
+
(payload) => {
|
|
650
|
+
this.trigger("streamEventSkip", payload);
|
|
651
|
+
},
|
|
652
|
+
cancelSignal,
|
|
653
|
+
);
|
|
654
|
+
streamEventsEmitter.start();
|
|
655
|
+
cancelSignal.register(() => {
|
|
656
|
+
streamEventsEmitter.stop();
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
{ clearSignal: cancelSignal, emitCurrentValue: true },
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
const coreObserver = createCorePlaybackObserver(
|
|
664
|
+
playbackObserver,
|
|
665
|
+
{
|
|
666
|
+
autoPlay,
|
|
667
|
+
manifest,
|
|
668
|
+
mediaSource,
|
|
669
|
+
textDisplayer,
|
|
670
|
+
initialPlayPerformed,
|
|
671
|
+
speed,
|
|
672
|
+
},
|
|
673
|
+
cancelSignal,
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
this._cmcdDataBuilder?.startMonitoringPlayback(coreObserver);
|
|
677
|
+
cancelSignal.register(() => {
|
|
678
|
+
this._cmcdDataBuilder?.stopMonitoringPlayback();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const rebufferingController = this._createRebufferingController(
|
|
682
|
+
playbackObserver,
|
|
683
|
+
manifest,
|
|
684
|
+
speed,
|
|
685
|
+
cancelSignal,
|
|
686
|
+
);
|
|
687
|
+
const freezeResolver = new FreezeResolver(segmentSinksStore);
|
|
688
|
+
|
|
689
|
+
if (mayMediaElementFailOnUndecipherableData()) {
|
|
690
|
+
// On some devices, just reload immediately when data become undecipherable
|
|
691
|
+
manifest.addEventListener(
|
|
692
|
+
"decipherabilityUpdate",
|
|
693
|
+
(elts) => {
|
|
694
|
+
if (elts.some((e) => e.representation.decipherable !== true)) {
|
|
695
|
+
reloadMediaSource(0, undefined, undefined);
|
|
641
696
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
697
|
+
},
|
|
698
|
+
cancelSignal,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
coreObserver.listen(
|
|
703
|
+
(observation) => {
|
|
704
|
+
synchronizeSegmentSinksOnObservation(observation, segmentSinksStore);
|
|
705
|
+
const freezeResolution = freezeResolver.onNewObservation(observation);
|
|
706
|
+
if (freezeResolution === null) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// TODO: The following method looks generic, we may be able to factorize
|
|
711
|
+
// it with other reload handlers after some work.
|
|
712
|
+
const triggerReload = () => {
|
|
713
|
+
const lastObservation = playbackObserver.getReference().getValue();
|
|
714
|
+
const position = lastObservation.position.isAwaitingFuturePosition()
|
|
715
|
+
? lastObservation.position.getWanted()
|
|
716
|
+
: (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
|
|
717
|
+
const autoplay = initialPlayPerformed.getValue()
|
|
718
|
+
? !playbackObserver.getIsPaused()
|
|
719
|
+
: autoPlay;
|
|
720
|
+
onReloadOrder({ position, autoPlay: autoplay });
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
handleFreezeResolution(freezeResolution, {
|
|
724
|
+
enableRepresentationAvoidance: this._initSettings.enableRepresentationAvoidance,
|
|
725
|
+
manifest,
|
|
726
|
+
triggerReload,
|
|
727
|
+
playbackObserver,
|
|
728
|
+
});
|
|
729
|
+
},
|
|
730
|
+
{ clearSignal: cancelSignal },
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
const contentTimeBoundariesObserver = createContentTimeBoundariesObserver(
|
|
734
|
+
manifest,
|
|
735
|
+
mediaSource,
|
|
736
|
+
coreObserver,
|
|
737
|
+
segmentSinksStore,
|
|
738
|
+
{
|
|
739
|
+
onWarning: (err: IPlayerError) => this.trigger("warning", err),
|
|
740
|
+
onPeriodChanged: (period: IPeriodMetadata) =>
|
|
741
|
+
this.trigger("activePeriodChanged", { period }),
|
|
742
|
+
},
|
|
743
|
+
cancelSignal,
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Emit a "loaded" events once the initial play has been performed and the
|
|
748
|
+
* media can begin playback.
|
|
749
|
+
* Also emits warning events if issues arise when doing so.
|
|
750
|
+
*/
|
|
751
|
+
autoPlayResult
|
|
752
|
+
.then(() => {
|
|
753
|
+
getLoadedReference(playbackObserver, false, cancelSignal).onUpdate(
|
|
754
|
+
(isLoaded, stopListening) => {
|
|
755
|
+
if (isLoaded) {
|
|
756
|
+
const fetchThumbnails = createThumbnailFetcher(
|
|
757
|
+
transport.thumbnails,
|
|
758
|
+
cdnPrioritizer,
|
|
759
|
+
);
|
|
760
|
+
stopListening();
|
|
761
|
+
this.trigger("loaded", {
|
|
762
|
+
getSegmentSinkMetrics: async () => {
|
|
763
|
+
return new Promise((resolve) =>
|
|
764
|
+
resolve(segmentSinksStore.getSegmentSinksMetrics()),
|
|
765
|
+
);
|
|
766
|
+
},
|
|
767
|
+
getThumbnailData: async (
|
|
768
|
+
periodId: string,
|
|
769
|
+
thumbnailTrackId: string,
|
|
770
|
+
time: number,
|
|
771
|
+
): Promise<IThumbnailResponse> => {
|
|
772
|
+
return getThumbnailData(
|
|
773
|
+
fetchThumbnails,
|
|
774
|
+
manifest,
|
|
775
|
+
periodId,
|
|
776
|
+
thumbnailTrackId,
|
|
777
|
+
time,
|
|
778
|
+
);
|
|
779
|
+
},
|
|
683
780
|
});
|
|
684
|
-
}
|
|
685
|
-
break;
|
|
686
|
-
|
|
687
|
-
case CoreMessageType.AbortSourceBuffer:
|
|
688
|
-
{
|
|
689
|
-
if (
|
|
690
|
-
this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
|
|
691
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
|
|
692
|
-
msgData.mediaSourceId
|
|
693
|
-
) {
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
|
|
697
|
-
const sourceBuffer = arrayFind(
|
|
698
|
-
mediaSource.sourceBuffers,
|
|
699
|
-
(s) => s.type === msgData.sourceBufferType,
|
|
700
|
-
);
|
|
701
|
-
if (sourceBuffer === undefined) {
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
sourceBuffer.abort();
|
|
705
|
-
}
|
|
706
|
-
break;
|
|
707
|
-
|
|
708
|
-
case CoreMessageType.UpdateMediaSourceDuration:
|
|
709
|
-
{
|
|
710
|
-
if (
|
|
711
|
-
this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
|
|
712
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
|
|
713
|
-
msgData.mediaSourceId
|
|
714
|
-
) {
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
|
|
718
|
-
if (mediaSource?.id !== msgData.mediaSourceId) {
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
mediaSource.setDuration(msgData.value.duration, msgData.value.isRealEndKnown);
|
|
722
|
-
}
|
|
723
|
-
break;
|
|
724
|
-
|
|
725
|
-
case CoreMessageType.InterruptMediaSourceDurationUpdate:
|
|
726
|
-
{
|
|
727
|
-
if (
|
|
728
|
-
this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
|
|
729
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
|
|
730
|
-
msgData.mediaSourceId
|
|
731
|
-
) {
|
|
732
|
-
return;
|
|
733
781
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
|
|
746
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
|
|
747
|
-
msgData.mediaSourceId
|
|
748
|
-
) {
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
|
|
752
|
-
mediaSource.maintainEndOfStream();
|
|
753
|
-
}
|
|
754
|
-
break;
|
|
755
|
-
|
|
756
|
-
case CoreMessageType.InterruptEndOfStream:
|
|
757
|
-
{
|
|
758
|
-
if (
|
|
759
|
-
this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
|
|
760
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
|
|
761
|
-
msgData.mediaSourceId
|
|
762
|
-
) {
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
|
|
766
|
-
mediaSource.stopEndOfStream();
|
|
767
|
-
}
|
|
768
|
-
break;
|
|
769
|
-
|
|
770
|
-
case CoreMessageType.DisposeMediaSource:
|
|
771
|
-
{
|
|
772
|
-
if (
|
|
773
|
-
this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
|
|
774
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
|
|
775
|
-
msgData.mediaSourceId
|
|
776
|
-
) {
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
|
|
780
|
-
mediaSource.dispose();
|
|
781
|
-
}
|
|
782
|
-
break;
|
|
782
|
+
},
|
|
783
|
+
{ emitCurrentValue: true, clearSignal: cancelSignal },
|
|
784
|
+
);
|
|
785
|
+
})
|
|
786
|
+
.catch((err) => {
|
|
787
|
+
if (cancelSignal.isCancelled()) {
|
|
788
|
+
return; // Current loading cancelled, no need to trigger the error
|
|
789
|
+
}
|
|
790
|
+
this._onFatalError(err);
|
|
791
|
+
});
|
|
783
792
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
793
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
794
|
+
const self = this;
|
|
795
|
+
StreamOrchestrator(
|
|
796
|
+
{ manifest, initialPeriod },
|
|
797
|
+
coreObserver,
|
|
798
|
+
representationEstimator,
|
|
799
|
+
segmentSinksStore,
|
|
800
|
+
segmentQueueCreator,
|
|
801
|
+
bufferOptions,
|
|
802
|
+
handleStreamOrchestratorCallbacks(),
|
|
803
|
+
cancelSignal,
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Returns Object handling the callbacks from a `StreamOrchestrator`, which
|
|
808
|
+
* are basically how it communicates about events.
|
|
809
|
+
* @returns {Object}
|
|
810
|
+
*/
|
|
811
|
+
function handleStreamOrchestratorCallbacks(): IStreamOrchestratorCallbacks {
|
|
812
|
+
return {
|
|
813
|
+
needsBufferFlush: (payload?: INeedsBufferFlushPayload) => {
|
|
814
|
+
let wantedSeekingTime: number;
|
|
788
815
|
const lastObservation = playbackObserver.getReference().getValue();
|
|
789
816
|
const currentTime = lastObservation.position.isAwaitingFuturePosition()
|
|
790
817
|
? lastObservation.position.getWanted()
|
|
791
818
|
: mediaElement.currentTime;
|
|
792
|
-
const relativeResumingPosition =
|
|
793
|
-
const canBeApproximateSeek = Boolean(
|
|
794
|
-
msgData.value?.relativePosHasBeenDefaulted,
|
|
795
|
-
);
|
|
796
|
-
let wantedSeekingTime: number;
|
|
819
|
+
const relativeResumingPosition = payload?.relativeResumingPosition ?? 0;
|
|
820
|
+
const canBeApproximateSeek = Boolean(payload?.relativePosHasBeenDefaulted);
|
|
797
821
|
|
|
798
822
|
if (relativeResumingPosition === 0 && canBeApproximateSeek) {
|
|
799
823
|
// in case relativeResumingPosition is 0, we still perform
|
|
@@ -803,886 +827,251 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
803
827
|
wantedSeekingTime = currentTime + relativeResumingPosition;
|
|
804
828
|
}
|
|
805
829
|
playbackObserver.setCurrentTime(wantedSeekingTime);
|
|
806
|
-
break;
|
|
807
|
-
}
|
|
808
830
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
831
|
+
// Seek again once data begins to be buffered.
|
|
832
|
+
// This is sadly necessary on some browsers to avoid decoding
|
|
833
|
+
// issues after a flush.
|
|
834
|
+
//
|
|
835
|
+
// NOTE: there's in theory a potential race condition in the following
|
|
836
|
+
// logic as the callback could be called when media data is still
|
|
837
|
+
// being removed by the browser - which is an asynchronous process.
|
|
838
|
+
// The following condition checking for buffered data could thus lead
|
|
839
|
+
// to a false positive where we're actually checking previous data.
|
|
840
|
+
// For now, such scenario is avoided by setting the
|
|
841
|
+
// `includeLastObservation` option to `false` and calling
|
|
842
|
+
// `needsBufferFlush` once MSE media removal operations have been
|
|
843
|
+
// explicitely validated by the browser, but that's a complex and easy
|
|
844
|
+
// to break system.
|
|
845
|
+
playbackObserver.listen(
|
|
846
|
+
(obs, stopListening) => {
|
|
847
|
+
if (
|
|
848
|
+
// Data is buffered around the current position
|
|
849
|
+
obs.currentRange !== null ||
|
|
850
|
+
// Or, for whatever reason, we have no buffer but we're already advancing
|
|
851
|
+
obs.position.getPolled() > wantedSeekingTime + 0.1
|
|
852
|
+
) {
|
|
853
|
+
stopListening();
|
|
854
|
+
playbackObserver.setCurrentTime(obs.position.getWanted() + 0.001);
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
{ includeLastObservation: false, clearSignal: cancelSignal },
|
|
819
858
|
);
|
|
820
|
-
|
|
821
|
-
this.trigger("activePeriodChanged", { period });
|
|
822
|
-
}
|
|
823
|
-
break;
|
|
824
|
-
}
|
|
859
|
+
},
|
|
825
860
|
|
|
826
|
-
|
|
827
|
-
if
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
if (period === undefined) {
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
if (msgData.value.adaptationId === null) {
|
|
841
|
-
this.trigger("adaptationChange", {
|
|
842
|
-
period,
|
|
843
|
-
adaptation: null,
|
|
844
|
-
type: msgData.value.type,
|
|
845
|
-
});
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
const adaptations = period.adaptations[msgData.value.type] ?? [];
|
|
849
|
-
const adaptation = arrayFind(
|
|
850
|
-
adaptations,
|
|
851
|
-
(a) => a.id === msgData.value.adaptationId,
|
|
852
|
-
);
|
|
853
|
-
if (adaptation !== undefined) {
|
|
854
|
-
this.trigger("adaptationChange", {
|
|
855
|
-
period,
|
|
856
|
-
adaptation,
|
|
857
|
-
type: msgData.value.type,
|
|
858
|
-
});
|
|
861
|
+
streamStatusUpdate(value) {
|
|
862
|
+
// Announce discontinuities if found
|
|
863
|
+
const { period, bufferType, imminentDiscontinuity, position } = value;
|
|
864
|
+
rebufferingController.updateDiscontinuityInfo({
|
|
865
|
+
period,
|
|
866
|
+
bufferType,
|
|
867
|
+
discontinuity: imminentDiscontinuity,
|
|
868
|
+
position,
|
|
869
|
+
});
|
|
870
|
+
if (cancelSignal.isCancelled()) {
|
|
871
|
+
return; // Previous call has stopped streams due to a side-effect
|
|
859
872
|
}
|
|
860
|
-
break;
|
|
861
|
-
}
|
|
862
873
|
|
|
863
|
-
|
|
874
|
+
// If the status for the last Period indicates that segments are all loaded
|
|
875
|
+
// or on the contrary that the loading resumed, announce it to the
|
|
876
|
+
// ContentTimeBoundariesObserver.
|
|
864
877
|
if (
|
|
865
|
-
|
|
866
|
-
|
|
878
|
+
manifest.isLastPeriodKnown &&
|
|
879
|
+
value.period.id === manifest.periods[manifest.periods.length - 1].id
|
|
867
880
|
) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
if (msgData.value.representationId === null) {
|
|
878
|
-
this.trigger("representationChange", {
|
|
879
|
-
period,
|
|
880
|
-
type: msgData.value.type,
|
|
881
|
-
representation: null,
|
|
882
|
-
});
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
const adaptations = period.adaptations[msgData.value.type] ?? [];
|
|
886
|
-
const adaptation = arrayFind(
|
|
887
|
-
adaptations,
|
|
888
|
-
(a) => a.id === msgData.value.adaptationId,
|
|
889
|
-
);
|
|
890
|
-
if (adaptation === undefined) {
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
const representation = arrayFind(
|
|
894
|
-
adaptation.representations,
|
|
895
|
-
(r) => r.id === msgData.value.representationId,
|
|
896
|
-
);
|
|
897
|
-
if (representation !== undefined) {
|
|
898
|
-
this.trigger("representationChange", {
|
|
899
|
-
period,
|
|
900
|
-
type: msgData.value.type,
|
|
901
|
-
representation,
|
|
902
|
-
});
|
|
881
|
+
const hasFinishedLoadingLastPeriod =
|
|
882
|
+
value.hasFinishedLoading || value.isEmptyStream;
|
|
883
|
+
if (hasFinishedLoadingLastPeriod) {
|
|
884
|
+
contentTimeBoundariesObserver.onLastSegmentFinishedLoading(
|
|
885
|
+
value.bufferType,
|
|
886
|
+
);
|
|
887
|
+
} else {
|
|
888
|
+
contentTimeBoundariesObserver.onLastSegmentLoadingResume(value.bufferType);
|
|
889
|
+
}
|
|
903
890
|
}
|
|
904
|
-
|
|
905
|
-
}
|
|
891
|
+
},
|
|
906
892
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
893
|
+
needsManifestRefresh: () =>
|
|
894
|
+
self._manifestFetcher.scheduleManualRefresh({
|
|
895
|
+
enablePartialRefresh: true,
|
|
896
|
+
canUseUnsafeMode: true,
|
|
897
|
+
}),
|
|
898
|
+
|
|
899
|
+
manifestMightBeOufOfSync: () => {
|
|
900
|
+
const { OUT_OF_SYNC_MANIFEST_REFRESH_DELAY } = config.getCurrent();
|
|
901
|
+
self._manifestFetcher.scheduleManualRefresh({
|
|
902
|
+
enablePartialRefresh: false,
|
|
903
|
+
canUseUnsafeMode: false,
|
|
904
|
+
delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY,
|
|
905
|
+
});
|
|
906
|
+
},
|
|
913
907
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
const manifest = msgData.value.manifest;
|
|
919
|
-
this._currentContentInfo.manifest = manifest;
|
|
920
|
-
this._updateCodecSupport(manifest, mediaElement);
|
|
921
|
-
this._startPlaybackIfReady(playbackStartParams);
|
|
922
|
-
break;
|
|
923
|
-
}
|
|
908
|
+
lockedStream: (value) =>
|
|
909
|
+
rebufferingController.onLockedStream(value.bufferType, value.period),
|
|
924
910
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
911
|
+
adaptationChange: (value) => {
|
|
912
|
+
self.trigger("adaptationChange", value);
|
|
913
|
+
if (cancelSignal.isCancelled()) {
|
|
914
|
+
return; // Previous call has stopped streams due to a side-effect
|
|
928
915
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
916
|
+
contentTimeBoundariesObserver.onAdaptationChange(
|
|
917
|
+
value.type,
|
|
918
|
+
value.period,
|
|
919
|
+
value.adaptation,
|
|
920
|
+
);
|
|
921
|
+
},
|
|
922
|
+
|
|
923
|
+
representationChange: (value) => {
|
|
924
|
+
self.trigger("representationChange", value);
|
|
925
|
+
if (cancelSignal.isCancelled()) {
|
|
926
|
+
return; // Previous call has stopped streams due to a side-effect
|
|
933
927
|
}
|
|
928
|
+
contentTimeBoundariesObserver.onRepresentationChange(value.type, value.period);
|
|
929
|
+
},
|
|
934
930
|
|
|
935
|
-
|
|
936
|
-
manifest,
|
|
937
|
-
msgData.value.manifest,
|
|
938
|
-
msgData.value.updates,
|
|
939
|
-
);
|
|
940
|
-
this._currentContentInfo?.streamEventsEmitter?.onManifestUpdate(manifest);
|
|
931
|
+
inbandEvent: (value) => self.trigger("inbandEvents", value),
|
|
941
932
|
|
|
942
|
-
|
|
943
|
-
this.trigger("manifestUpdate", msgData.value.updates);
|
|
944
|
-
break;
|
|
945
|
-
}
|
|
933
|
+
warning: (value) => self.trigger("warning", value),
|
|
946
934
|
|
|
947
|
-
|
|
948
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
951
|
-
playbackObserver.setPlaybackRate(msgData.value);
|
|
952
|
-
break;
|
|
935
|
+
periodStreamReady: (value) => self.trigger("periodStreamReady", value),
|
|
953
936
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
937
|
+
periodStreamCleared: (value) => {
|
|
938
|
+
contentTimeBoundariesObserver.onPeriodCleared(value.type, value.period);
|
|
939
|
+
if (cancelSignal.isCancelled()) {
|
|
940
|
+
return; // Previous call has stopped streams due to a side-effect
|
|
957
941
|
}
|
|
958
|
-
|
|
959
|
-
type:
|
|
960
|
-
|
|
942
|
+
self.trigger("periodStreamCleared", {
|
|
943
|
+
type: value.type,
|
|
944
|
+
periodId: value.period.id,
|
|
961
945
|
});
|
|
962
|
-
|
|
946
|
+
},
|
|
963
947
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
948
|
+
bitrateEstimateChange: (value) => {
|
|
949
|
+
self._cmcdDataBuilder?.updateThroughput(value.type, value.bitrate);
|
|
950
|
+
self.trigger("bitrateEstimateChange", value);
|
|
951
|
+
},
|
|
952
|
+
|
|
953
|
+
needsMediaSourceReload: (payload) => {
|
|
954
|
+
reloadMediaSource(
|
|
955
|
+
payload.timeOffset,
|
|
956
|
+
payload.minimumPosition,
|
|
957
|
+
payload.maximumPosition,
|
|
958
|
+
);
|
|
959
|
+
},
|
|
960
|
+
|
|
961
|
+
needsDecipherabilityFlush() {
|
|
962
|
+
const keySystem = getKeySystemConfiguration(mediaElement);
|
|
963
|
+
if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem?.[0])) {
|
|
964
|
+
const lastObservation = coreObserver.getReference().getValue();
|
|
965
|
+
const position = lastObservation.position.isAwaitingFuturePosition()
|
|
966
|
+
? lastObservation.position.getWanted()
|
|
967
|
+
: (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
|
|
968
|
+
const isPaused =
|
|
969
|
+
lastObservation.paused.pending ??
|
|
970
|
+
coreObserver.getIsPaused() ??
|
|
971
|
+
lastObservation.paused.last;
|
|
972
|
+
onReloadOrder({ position, autoPlay: !isPaused });
|
|
973
|
+
} else {
|
|
974
|
+
const lastObservation = coreObserver.getReference().getValue();
|
|
975
|
+
const position = lastObservation.position.isAwaitingFuturePosition()
|
|
976
|
+
? lastObservation.position.getWanted()
|
|
977
|
+
: (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
|
|
978
|
+
// simple seek close to the current position
|
|
979
|
+
// to flush the buffers
|
|
980
|
+
if (position + 0.001 < lastObservation.duration) {
|
|
981
|
+
playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001);
|
|
982
|
+
} else {
|
|
983
|
+
playbackObserver.setCurrentTime(position);
|
|
984
|
+
}
|
|
967
985
|
}
|
|
968
|
-
|
|
969
|
-
break;
|
|
986
|
+
},
|
|
970
987
|
|
|
971
|
-
|
|
972
|
-
if (
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
) {
|
|
988
|
+
encryptionDataEncountered: (value) => {
|
|
989
|
+
if (self._decryptionCapabilities.status === "disabled") {
|
|
990
|
+
self._onFatalError(self._decryptionCapabilities.value);
|
|
991
|
+
return;
|
|
992
|
+
} else if (self._decryptionCapabilities.status === "uninitialized") {
|
|
993
|
+
// Should never happen
|
|
994
|
+
log.error(
|
|
995
|
+
"Init",
|
|
996
|
+
"received encryption data without known decryption capabilities",
|
|
997
|
+
);
|
|
976
998
|
return;
|
|
977
999
|
}
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
(
|
|
981
|
-
|
|
982
|
-
if (period === undefined) {
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
this._currentContentInfo.rebufferingController?.onLockedStream(
|
|
986
|
-
msgData.value.bufferType,
|
|
987
|
-
period,
|
|
988
|
-
);
|
|
989
|
-
break;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
case CoreMessageType.PeriodStreamReady: {
|
|
993
|
-
if (
|
|
994
|
-
this._currentContentInfo?.contentId !== msgData.contentId ||
|
|
995
|
-
this._currentContentInfo.manifest === null
|
|
996
|
-
) {
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
const period = arrayFind(
|
|
1000
|
-
this._currentContentInfo.manifest.periods,
|
|
1001
|
-
(p) => p.id === msgData.value.periodId,
|
|
1002
|
-
);
|
|
1003
|
-
if (period === undefined) {
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
const ref = new SharedReference<IAdaptationChoice | null | undefined>(
|
|
1007
|
-
undefined,
|
|
1008
|
-
);
|
|
1009
|
-
ref.onUpdate(
|
|
1010
|
-
(adapChoice) => {
|
|
1011
|
-
if (this._currentContentInfo === null) {
|
|
1012
|
-
ref.finish();
|
|
1013
|
-
return;
|
|
1014
|
-
}
|
|
1015
|
-
if (!isNullOrUndefined(adapChoice)) {
|
|
1016
|
-
adapChoice.representations.onUpdate(
|
|
1017
|
-
(repChoice, stopListening) => {
|
|
1018
|
-
if (this._currentContentInfo === null) {
|
|
1019
|
-
stopListening();
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
this._settings.coreInterface.sendMessage({
|
|
1023
|
-
type: MainThreadMessageType.RepresentationUpdate,
|
|
1024
|
-
contentId: this._currentContentInfo.contentId,
|
|
1025
|
-
value: {
|
|
1026
|
-
periodId: msgData.value.periodId,
|
|
1027
|
-
adaptationId: adapChoice.adaptationId,
|
|
1028
|
-
bufferType: msgData.value.bufferType,
|
|
1029
|
-
choice: repChoice,
|
|
1030
|
-
},
|
|
1031
|
-
});
|
|
1032
|
-
},
|
|
1033
|
-
{ clearSignal: this._initCanceller.signal },
|
|
1034
|
-
);
|
|
1035
|
-
}
|
|
1036
|
-
this._settings.coreInterface.sendMessage({
|
|
1037
|
-
type: MainThreadMessageType.TrackUpdate,
|
|
1038
|
-
contentId: this._currentContentInfo.contentId,
|
|
1039
|
-
value: {
|
|
1040
|
-
periodId: msgData.value.periodId,
|
|
1041
|
-
bufferType: msgData.value.bufferType,
|
|
1042
|
-
choice: isNullOrUndefined(adapChoice)
|
|
1043
|
-
? adapChoice
|
|
1044
|
-
: {
|
|
1045
|
-
adaptationId: adapChoice.adaptationId,
|
|
1046
|
-
switchingMode: adapChoice.switchingMode,
|
|
1047
|
-
initialRepresentations: adapChoice.representations.getValue(),
|
|
1048
|
-
relativeResumingPosition: adapChoice.relativeResumingPosition,
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1051
|
-
});
|
|
1052
|
-
},
|
|
1053
|
-
{ clearSignal: this._initCanceller.signal },
|
|
1054
|
-
);
|
|
1055
|
-
this.trigger("periodStreamReady", {
|
|
1056
|
-
period,
|
|
1057
|
-
type: msgData.value.bufferType,
|
|
1058
|
-
adaptationRef: ref,
|
|
1059
|
-
});
|
|
1060
|
-
break;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
case CoreMessageType.PeriodStreamCleared: {
|
|
1064
|
-
if (
|
|
1065
|
-
this._currentContentInfo?.contentId !== msgData.contentId ||
|
|
1066
|
-
this._currentContentInfo.manifest === null
|
|
1067
|
-
) {
|
|
1068
|
-
return;
|
|
1069
|
-
}
|
|
1070
|
-
this.trigger("periodStreamCleared", {
|
|
1071
|
-
periodId: msgData.value.periodId,
|
|
1072
|
-
type: msgData.value.bufferType,
|
|
1073
|
-
});
|
|
1074
|
-
break;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
case CoreMessageType.DiscontinuityUpdate: {
|
|
1078
|
-
if (
|
|
1079
|
-
this._currentContentInfo?.contentId !== msgData.contentId ||
|
|
1080
|
-
this._currentContentInfo.manifest === null
|
|
1081
|
-
) {
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
const period = arrayFind(
|
|
1085
|
-
this._currentContentInfo.manifest.periods,
|
|
1086
|
-
(p) => p.id === msgData.value.periodId,
|
|
1087
|
-
);
|
|
1088
|
-
if (period === undefined) {
|
|
1089
|
-
log.warn("Init", "Discontinuity's Period not found", {
|
|
1090
|
-
periodId: msgData.value.periodId,
|
|
1091
|
-
});
|
|
1092
|
-
return;
|
|
1093
|
-
}
|
|
1094
|
-
this._currentContentInfo.rebufferingController?.updateDiscontinuityInfo({
|
|
1095
|
-
period,
|
|
1096
|
-
bufferType: msgData.value.bufferType,
|
|
1097
|
-
discontinuity: msgData.value.discontinuity,
|
|
1098
|
-
position: msgData.value.position,
|
|
1099
|
-
});
|
|
1100
|
-
break;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
case CoreMessageType.PushTextData: {
|
|
1104
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
if (textDisplayer === null) {
|
|
1108
|
-
log.warn("text", "Received AddTextData message but no text displayer exists");
|
|
1109
|
-
} else {
|
|
1110
|
-
try {
|
|
1111
|
-
const ranges = textDisplayer.pushTextData(msgData.value);
|
|
1112
|
-
this._settings.coreInterface.sendMessage({
|
|
1113
|
-
type: MainThreadMessageType.PushTextDataSuccess,
|
|
1114
|
-
contentId: msgData.contentId,
|
|
1115
|
-
value: { ranges },
|
|
1116
|
-
});
|
|
1117
|
-
} catch (err) {
|
|
1118
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1119
|
-
this._settings.coreInterface.sendMessage({
|
|
1120
|
-
type: MainThreadMessageType.PushTextDataError,
|
|
1121
|
-
contentId: msgData.contentId,
|
|
1122
|
-
value: { message },
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
break;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
case CoreMessageType.RemoveTextData: {
|
|
1130
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
if (textDisplayer === null) {
|
|
1134
|
-
log.warn(
|
|
1135
|
-
"text",
|
|
1136
|
-
"Received RemoveTextData message but no text displayer exists",
|
|
1137
|
-
);
|
|
1138
|
-
} else {
|
|
1139
|
-
try {
|
|
1140
|
-
const ranges = textDisplayer.removeBuffer(
|
|
1141
|
-
msgData.value.start,
|
|
1142
|
-
msgData.value.end,
|
|
1143
|
-
);
|
|
1144
|
-
this._settings.coreInterface.sendMessage({
|
|
1145
|
-
type: MainThreadMessageType.RemoveTextDataSuccess,
|
|
1146
|
-
contentId: msgData.contentId,
|
|
1147
|
-
value: { ranges },
|
|
1148
|
-
});
|
|
1149
|
-
} catch (err) {
|
|
1150
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1151
|
-
this._settings.coreInterface.sendMessage({
|
|
1152
|
-
type: MainThreadMessageType.RemoveTextDataError,
|
|
1153
|
-
contentId: msgData.contentId,
|
|
1154
|
-
value: { message },
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
break;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
case CoreMessageType.ResetTextDisplayer: {
|
|
1162
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
if (textDisplayer === null) {
|
|
1166
|
-
log.warn(
|
|
1167
|
-
"text",
|
|
1168
|
-
"Received ResetTextDisplayer message but no text displayer exists",
|
|
1169
|
-
);
|
|
1170
|
-
} else {
|
|
1171
|
-
textDisplayer.reset();
|
|
1172
|
-
}
|
|
1173
|
-
break;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
case CoreMessageType.StopTextDisplayer: {
|
|
1177
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
if (textDisplayer === null) {
|
|
1181
|
-
log.warn(
|
|
1182
|
-
"text",
|
|
1183
|
-
"Received StopTextDisplayer message but no text displayer exists",
|
|
1184
|
-
);
|
|
1185
|
-
} else {
|
|
1186
|
-
textDisplayer.stop();
|
|
1187
|
-
}
|
|
1188
|
-
break;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
case CoreMessageType.ReloadingMediaSource:
|
|
1192
|
-
{
|
|
1193
|
-
if (
|
|
1194
|
-
this._currentContentInfo === null ||
|
|
1195
|
-
this._currentContentInfo.mediaSourceInfo === null
|
|
1196
|
-
) {
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
const mediaSourceId =
|
|
1200
|
-
this._currentContentInfo.mediaSourceInfo.type === "main"
|
|
1201
|
-
? this._currentContentInfo.mediaSourceInfo.mediaSource.id
|
|
1202
|
-
: this._currentContentInfo.mediaSourceInfo.mediaSourceId;
|
|
1203
|
-
|
|
1204
|
-
if (mediaSourceId !== msgData.mediaSourceId) {
|
|
1205
|
-
return;
|
|
1206
|
-
}
|
|
1207
|
-
reloadMediaSource(
|
|
1208
|
-
msgData.value.timeOffset,
|
|
1209
|
-
msgData.value.minimumPosition,
|
|
1210
|
-
msgData.value.maximumPosition,
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
|
-
break;
|
|
1214
|
-
|
|
1215
|
-
case CoreMessageType.NeedsDecipherabilityFlush:
|
|
1216
|
-
{
|
|
1217
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
const keySystem = getKeySystemConfiguration(mediaElement);
|
|
1222
|
-
if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem?.[0])) {
|
|
1223
|
-
notifyAndStartMediaSourceReload(0, undefined, undefined);
|
|
1224
|
-
} else {
|
|
1225
|
-
const lastObservation = playbackObserver.getReference().getValue();
|
|
1226
|
-
|
|
1227
|
-
const currentPosition = lastObservation.position.getWanted();
|
|
1228
|
-
|
|
1229
|
-
// simple seek close to the current position
|
|
1230
|
-
// to flush the buffers
|
|
1231
|
-
if (currentPosition + 0.001 < lastObservation.duration) {
|
|
1232
|
-
playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001);
|
|
1233
|
-
} else {
|
|
1234
|
-
playbackObserver.setCurrentTime(currentPosition);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
break;
|
|
1239
|
-
|
|
1240
|
-
case CoreMessageType.SegmentSinkStoreUpdate: {
|
|
1241
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
const sinkObj = this._awaitingRequests.pendingSinkMetrics.get(
|
|
1245
|
-
msgData.value.requestId,
|
|
1246
|
-
);
|
|
1247
|
-
if (sinkObj !== undefined) {
|
|
1248
|
-
sinkObj.resolve(msgData.value.segmentSinkMetrics);
|
|
1249
|
-
} else {
|
|
1250
|
-
log.error("Init", "Failed to send segment sink store update");
|
|
1251
|
-
}
|
|
1252
|
-
break;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
case CoreMessageType.InitSuccess:
|
|
1256
|
-
case CoreMessageType.InitError:
|
|
1257
|
-
// Should already be handled by the API
|
|
1258
|
-
break;
|
|
1259
|
-
|
|
1260
|
-
case CoreMessageType.LogMessage:
|
|
1261
|
-
// Already handled by prepare's handler
|
|
1262
|
-
break;
|
|
1263
|
-
case CoreMessageType.ThumbnailDataResponse: {
|
|
1264
|
-
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
const tObj = this._awaitingRequests.pendingThumbnailFetching.get(
|
|
1268
|
-
msgData.value.requestId,
|
|
1269
|
-
);
|
|
1270
|
-
if (tObj !== undefined) {
|
|
1271
|
-
if (msgData.value.status === "error") {
|
|
1272
|
-
tObj.reject(formatCoreError(msgData.value.error));
|
|
1273
|
-
} else {
|
|
1274
|
-
tObj.resolve(msgData.value.data);
|
|
1000
|
+
for (const protectionData of value) {
|
|
1001
|
+
self._decryptionCapabilities.value.onInitializationData(protectionData);
|
|
1002
|
+
if (cancelSignal.isCancelled()) {
|
|
1003
|
+
return; // Previous call has stopped streams due to a side-effect
|
|
1275
1004
|
}
|
|
1276
|
-
} else {
|
|
1277
|
-
log.error("Init", "Failed to send segment sink store update");
|
|
1278
|
-
}
|
|
1279
|
-
break;
|
|
1280
|
-
}
|
|
1281
|
-
default:
|
|
1282
|
-
assertUnreachable(msgData);
|
|
1283
|
-
}
|
|
1284
|
-
};
|
|
1285
|
-
|
|
1286
|
-
log.debug("Init", "addEventListener for core message");
|
|
1287
|
-
if (this._queuedCoreMessages !== null) {
|
|
1288
|
-
const bufferedMessages = this._queuedCoreMessages.slice();
|
|
1289
|
-
log.debug("Init", "Processing buffered messages", {
|
|
1290
|
-
ammount: bufferedMessages.length,
|
|
1291
|
-
});
|
|
1292
|
-
for (const message of bufferedMessages) {
|
|
1293
|
-
onmessage(message);
|
|
1294
|
-
}
|
|
1295
|
-
this._queuedCoreMessages = null;
|
|
1296
|
-
}
|
|
1297
|
-
this._settings.coreInterface.addMessageListener(onmessage);
|
|
1298
|
-
this._initCanceller.signal.register(() => {
|
|
1299
|
-
log.debug("Init", "removeEventListener for core message");
|
|
1300
|
-
this._settings.coreInterface.removeMessageListener(onmessage);
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
public dispose(): void {
|
|
1305
|
-
this._initCanceller.cancel();
|
|
1306
|
-
if (this._currentContentInfo !== null) {
|
|
1307
|
-
if (this._currentContentInfo.mediaSourceInfo?.type === "main") {
|
|
1308
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.dispose();
|
|
1309
|
-
}
|
|
1310
|
-
this._currentContentInfo = null;
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
private _onFatalError(err: unknown) {
|
|
1315
|
-
if (this._initCanceller.isUsed()) {
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
this._initCanceller.cancel();
|
|
1319
|
-
this.trigger("error", err);
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
private _initializeContentDecryption(
|
|
1323
|
-
mediaElement: IMediaElement,
|
|
1324
|
-
lastContentProtection: IReadOnlySharedReference<null | IContentProtection>,
|
|
1325
|
-
mediaSourceStatus: SharedReference<MediaSourceInitializationStatus>,
|
|
1326
|
-
reloadMediaSource: () => void,
|
|
1327
|
-
cancelSignal: CancellationSignal,
|
|
1328
|
-
): {
|
|
1329
|
-
statusRef: IReadOnlySharedReference<IDrmInitializationStatus>;
|
|
1330
|
-
contentDecryptor: IContentDecryptor | null;
|
|
1331
|
-
} {
|
|
1332
|
-
const { keySystems } = this._settings;
|
|
1333
|
-
|
|
1334
|
-
// TODO private?
|
|
1335
|
-
const createEmeDisabledReference = (errMsg: string) => {
|
|
1336
|
-
mediaSourceStatus.setValue(MediaSourceInitializationStatus.AttachNow);
|
|
1337
|
-
lastContentProtection.onUpdate(
|
|
1338
|
-
(data, stopListening) => {
|
|
1339
|
-
if (data === null) {
|
|
1340
|
-
// initial value
|
|
1341
|
-
return;
|
|
1342
1005
|
}
|
|
1343
|
-
stopListening();
|
|
1344
|
-
const err = new EncryptedMediaError("MEDIA_IS_ENCRYPTED_ERROR", errMsg, {
|
|
1345
|
-
keyStatuses: undefined,
|
|
1346
|
-
keySystemConfiguration: undefined,
|
|
1347
|
-
keySystem: undefined,
|
|
1348
|
-
});
|
|
1349
|
-
this._onFatalError(err);
|
|
1350
|
-
},
|
|
1351
|
-
{ clearSignal: cancelSignal },
|
|
1352
|
-
);
|
|
1353
|
-
const ref = new SharedReference({
|
|
1354
|
-
initializationState: {
|
|
1355
|
-
type: "initialized" as const,
|
|
1356
|
-
value: null,
|
|
1357
1006
|
},
|
|
1358
|
-
contentDecryptor: null,
|
|
1359
|
-
drmSystemId: undefined,
|
|
1360
|
-
});
|
|
1361
|
-
ref.finish(); // We know that no new value will be triggered
|
|
1362
|
-
return { statusRef: ref, contentDecryptor: null };
|
|
1363
|
-
};
|
|
1364
|
-
|
|
1365
|
-
if (keySystems.length === 0) {
|
|
1366
|
-
return createEmeDisabledReference("No `keySystems` option given.");
|
|
1367
|
-
} else if (features.decrypt === null) {
|
|
1368
|
-
return createEmeDisabledReference("EME feature not activated.");
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
const emeApi = mediaElement.FORCED_EME_API ?? getEmeApiImplementation("auto");
|
|
1372
|
-
if (emeApi === null) {
|
|
1373
|
-
return createEmeDisabledReference("EME API not available on the current page.");
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
log.debug("Init", "Creating ContentDecryptor");
|
|
1377
|
-
|
|
1378
|
-
const ContentDecryptor = features.decrypt;
|
|
1379
|
-
const contentDecryptor = new ContentDecryptor(emeApi, mediaElement, keySystems);
|
|
1380
|
-
const drmStatusRef = new SharedReference<IDrmInitializationStatus>(
|
|
1381
|
-
{
|
|
1382
|
-
initializationState: { type: "uninitialized", value: null },
|
|
1383
|
-
drmSystemId: undefined,
|
|
1384
|
-
},
|
|
1385
|
-
cancelSignal,
|
|
1386
|
-
);
|
|
1387
|
-
|
|
1388
|
-
const updateCodecSupportOnStateChange = (state: ContentDecryptorState) => {
|
|
1389
|
-
if (state > ContentDecryptorState.Initializing) {
|
|
1390
|
-
const manifest = this._currentContentInfo?.manifest;
|
|
1391
|
-
if (isNullOrUndefined(manifest)) {
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
this._updateCodecSupport(manifest, mediaElement);
|
|
1395
|
-
contentDecryptor.removeEventListener(
|
|
1396
|
-
"stateChange",
|
|
1397
|
-
updateCodecSupportOnStateChange,
|
|
1398
|
-
);
|
|
1399
|
-
}
|
|
1400
|
-
};
|
|
1401
|
-
contentDecryptor.addEventListener("stateChange", updateCodecSupportOnStateChange);
|
|
1402
|
-
|
|
1403
|
-
contentDecryptor.addEventListener("keyIdsCompatibilityUpdate", (updates) => {
|
|
1404
|
-
if (
|
|
1405
|
-
this._currentContentInfo === null ||
|
|
1406
|
-
this._currentContentInfo.manifest === null
|
|
1407
|
-
) {
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
const manUpdates = updateDecipherabilityFromKeyIds(
|
|
1411
|
-
this._currentContentInfo.manifest,
|
|
1412
|
-
updates,
|
|
1413
|
-
);
|
|
1414
|
-
if (
|
|
1415
|
-
mayMediaElementFailOnUndecipherableData() &&
|
|
1416
|
-
manUpdates.some((e) => e.representation.decipherable !== true)
|
|
1417
|
-
) {
|
|
1418
|
-
reloadMediaSource();
|
|
1419
|
-
} else {
|
|
1420
|
-
this._settings.coreInterface.sendMessage({
|
|
1421
|
-
type: MainThreadMessageType.DecipherabilityStatusUpdate,
|
|
1422
|
-
contentId: this._currentContentInfo.contentId,
|
|
1423
|
-
value: manUpdates.map((s) => ({
|
|
1424
|
-
representationUniqueId: s.representation.uniqueId,
|
|
1425
|
-
decipherable: s.representation.decipherable,
|
|
1426
|
-
})),
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
this.trigger("decipherabilityUpdate", manUpdates);
|
|
1430
|
-
});
|
|
1431
|
-
contentDecryptor.addEventListener("blackListProtectionData", (protData) => {
|
|
1432
|
-
if (
|
|
1433
|
-
this._currentContentInfo === null ||
|
|
1434
|
-
this._currentContentInfo.manifest === null
|
|
1435
|
-
) {
|
|
1436
|
-
return;
|
|
1437
|
-
}
|
|
1438
|
-
const manUpdates = updateDecipherabilityFromProtectionData(
|
|
1439
|
-
this._currentContentInfo.manifest,
|
|
1440
|
-
protData,
|
|
1441
|
-
);
|
|
1442
|
-
if (
|
|
1443
|
-
mayMediaElementFailOnUndecipherableData() &&
|
|
1444
|
-
manUpdates.some((e) => e.representation.decipherable !== true)
|
|
1445
|
-
) {
|
|
1446
|
-
reloadMediaSource();
|
|
1447
|
-
} else {
|
|
1448
|
-
this._settings.coreInterface.sendMessage({
|
|
1449
|
-
type: MainThreadMessageType.DecipherabilityStatusUpdate,
|
|
1450
|
-
contentId: this._currentContentInfo.contentId,
|
|
1451
|
-
value: manUpdates.map((s) => ({
|
|
1452
|
-
representationUniqueId: s.representation.uniqueId,
|
|
1453
|
-
decipherable: s.representation.decipherable,
|
|
1454
|
-
})),
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
this.trigger("decipherabilityUpdate", manUpdates);
|
|
1458
|
-
});
|
|
1459
|
-
contentDecryptor.addEventListener("stateChange", (state) => {
|
|
1460
|
-
if (state === ContentDecryptorState.WaitingForAttachment) {
|
|
1461
|
-
mediaSourceStatus.onUpdate(
|
|
1462
|
-
(currStatus, stopListening) => {
|
|
1463
|
-
if (currStatus === MediaSourceInitializationStatus.Nothing) {
|
|
1464
|
-
mediaSourceStatus.setValue(MediaSourceInitializationStatus.AttachNow);
|
|
1465
|
-
} else if (currStatus === MediaSourceInitializationStatus.Attached) {
|
|
1466
|
-
stopListening();
|
|
1467
|
-
if (state === ContentDecryptorState.WaitingForAttachment) {
|
|
1468
|
-
contentDecryptor.attach();
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
},
|
|
1472
|
-
{ clearSignal: cancelSignal, emitCurrentValue: true },
|
|
1473
|
-
);
|
|
1474
|
-
} else if (state === ContentDecryptorState.ReadyForContent) {
|
|
1475
|
-
drmStatusRef.setValue({
|
|
1476
|
-
initializationState: { type: "initialized", value: null },
|
|
1477
|
-
drmSystemId: contentDecryptor.systemId,
|
|
1478
|
-
});
|
|
1479
|
-
contentDecryptor.removeEventListener("stateChange");
|
|
1480
|
-
}
|
|
1481
|
-
});
|
|
1482
1007
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
});
|
|
1486
|
-
|
|
1487
|
-
contentDecryptor.addEventListener("warning", (error) => {
|
|
1488
|
-
this.trigger("warning", error);
|
|
1489
|
-
});
|
|
1490
|
-
|
|
1491
|
-
lastContentProtection.onUpdate(
|
|
1492
|
-
(data) => {
|
|
1493
|
-
if (data === null) {
|
|
1494
|
-
return;
|
|
1495
|
-
}
|
|
1496
|
-
contentDecryptor.onInitializationData(data);
|
|
1497
|
-
},
|
|
1498
|
-
{ clearSignal: cancelSignal },
|
|
1499
|
-
);
|
|
1500
|
-
|
|
1501
|
-
cancelSignal.register(() => {
|
|
1502
|
-
contentDecryptor.dispose();
|
|
1503
|
-
});
|
|
1504
|
-
|
|
1505
|
-
return { statusRef: drmStatusRef, contentDecryptor };
|
|
1506
|
-
}
|
|
1507
|
-
/**
|
|
1508
|
-
* Retrieves all unknown codecs from the current manifest, checks these unknown codecs
|
|
1509
|
-
* to determine if they are supported, updates the manifest with the support
|
|
1510
|
-
* status of these codecs, and forwards the list of supported codecs to core.
|
|
1511
|
-
* @param manifest
|
|
1512
|
-
*/
|
|
1513
|
-
private _updateCodecSupport(manifest: IManifestMetadata, mediaElement: IMediaElement) {
|
|
1514
|
-
try {
|
|
1515
|
-
const updatedCodecs = updateManifestCodecSupport(
|
|
1516
|
-
manifest,
|
|
1517
|
-
this._currentContentInfo?.contentDecryptor ?? null,
|
|
1518
|
-
mediaElement,
|
|
1519
|
-
this._currentContentInfo?.useMseInWorker ?? false,
|
|
1520
|
-
);
|
|
1521
|
-
if (updatedCodecs.length > 0) {
|
|
1522
|
-
this._settings.coreInterface.sendMessage({
|
|
1523
|
-
type: MainThreadMessageType.CodecSupportUpdate,
|
|
1524
|
-
value: updatedCodecs,
|
|
1525
|
-
});
|
|
1526
|
-
// TODO what if one day the core updates codec support by itself?
|
|
1527
|
-
// We wouldn't know...
|
|
1528
|
-
this.trigger("codecSupportUpdate", null);
|
|
1529
|
-
}
|
|
1530
|
-
} catch (err) {
|
|
1531
|
-
this._onFatalError(err);
|
|
1008
|
+
error: (err) => self._onFatalError(err),
|
|
1009
|
+
};
|
|
1532
1010
|
}
|
|
1533
|
-
}
|
|
1534
1011
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
(
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
initialTime: position,
|
|
1566
|
-
autoPlay,
|
|
1567
|
-
mediaElement,
|
|
1568
|
-
textDisplayer,
|
|
1569
|
-
playbackObserver,
|
|
1570
|
-
},
|
|
1571
|
-
this._currentMediaSourceCanceller.signal,
|
|
1572
|
-
);
|
|
1573
|
-
|
|
1574
|
-
if (
|
|
1575
|
-
!this._currentMediaSourceCanceller.isUsed() &&
|
|
1576
|
-
corePlaybackObserver !== null &&
|
|
1577
|
-
this._currentContentInfo !== null
|
|
1578
|
-
) {
|
|
1579
|
-
const contentId = this._currentContentInfo.contentId;
|
|
1580
|
-
corePlaybackObserver.listen(
|
|
1581
|
-
(obs) => {
|
|
1582
|
-
this._settings.coreInterface.sendMessage({
|
|
1583
|
-
type: MainThreadMessageType.PlaybackObservation,
|
|
1584
|
-
contentId,
|
|
1585
|
-
value: objectAssign(obs, {
|
|
1586
|
-
position: obs.position.serialize(),
|
|
1587
|
-
}),
|
|
1588
|
-
});
|
|
1589
|
-
},
|
|
1590
|
-
{
|
|
1591
|
-
includeLastObservation: true,
|
|
1592
|
-
clearSignal: this._currentMediaSourceCanceller.signal,
|
|
1593
|
-
},
|
|
1594
|
-
);
|
|
1595
|
-
}
|
|
1596
|
-
},
|
|
1597
|
-
{
|
|
1598
|
-
clearSignal: this._currentMediaSourceCanceller.signal,
|
|
1599
|
-
emitCurrentValue: true,
|
|
1600
|
-
},
|
|
1601
|
-
);
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
/**
|
|
1605
|
-
* Start-up modules and mechanisms (initial seek, auto-play etc.) needed each
|
|
1606
|
-
* time a content is loaded AND re-loaded on a `HTMLMediaElement`, when the
|
|
1607
|
-
* manifest is known.
|
|
1608
|
-
*
|
|
1609
|
-
* Note that this does not include reacting to incoming core messages nor
|
|
1610
|
-
* sending them, those actions have to be handled separately.
|
|
1611
|
-
*
|
|
1612
|
-
* @param {Object} parameters
|
|
1613
|
-
* @param {Object} cancelSignal
|
|
1614
|
-
* @returns {Object|null} - Playback Observer created for this content. `null`
|
|
1615
|
-
* only if playback initialization failed (most likely because it has been
|
|
1616
|
-
* cancelled).
|
|
1617
|
-
*/
|
|
1618
|
-
private _setUpModulesOnNewMediaSource(
|
|
1619
|
-
parameters: {
|
|
1620
|
-
initialTime: number;
|
|
1621
|
-
autoPlay: boolean;
|
|
1622
|
-
mediaElement: IMediaElement;
|
|
1623
|
-
textDisplayer: ITextDisplayer | null;
|
|
1624
|
-
playbackObserver: IMediaElementPlaybackObserver;
|
|
1625
|
-
},
|
|
1626
|
-
cancelSignal: CancellationSignal,
|
|
1627
|
-
): IReadOnlyPlaybackObserver<ICorePlaybackObservation> | null {
|
|
1628
|
-
if (cancelSignal.isCancelled()) {
|
|
1629
|
-
return null;
|
|
1630
|
-
}
|
|
1631
|
-
if (this._currentContentInfo === null) {
|
|
1632
|
-
log.error("Init", "Setting up modules without a contentId");
|
|
1633
|
-
return null;
|
|
1634
|
-
}
|
|
1635
|
-
if (this._currentContentInfo.manifest === null) {
|
|
1636
|
-
log.error("Init", "Setting up modules without a loaded Manifest");
|
|
1637
|
-
return null;
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
const { manifest, mediaSourceInfo } = this._currentContentInfo;
|
|
1641
|
-
const { speed } = this._settings;
|
|
1642
|
-
const { initialTime, autoPlay, mediaElement, textDisplayer, playbackObserver } =
|
|
1643
|
-
parameters;
|
|
1644
|
-
this._currentContentInfo.initialTime = initialTime;
|
|
1645
|
-
this._currentContentInfo.autoPlay = autoPlay;
|
|
1646
|
-
|
|
1647
|
-
const { autoPlayResult, initialPlayPerformed } = performInitialSeekAndPlay(
|
|
1648
|
-
{
|
|
1649
|
-
mediaElement,
|
|
1650
|
-
playbackObserver,
|
|
1651
|
-
startTime: initialTime,
|
|
1652
|
-
mustAutoPlay: autoPlay,
|
|
1653
|
-
onWarning: (err) => this.trigger("warning", err),
|
|
1654
|
-
isDirectfile: false,
|
|
1655
|
-
},
|
|
1656
|
-
cancelSignal,
|
|
1657
|
-
);
|
|
1658
|
-
this._currentContentInfo.initialPlayPerformed = initialPlayPerformed;
|
|
1659
|
-
const corePlaybackObserver = createCorePlaybackObserver(
|
|
1660
|
-
playbackObserver,
|
|
1661
|
-
{
|
|
1662
|
-
autoPlay,
|
|
1663
|
-
initialPlayPerformed,
|
|
1664
|
-
manifest,
|
|
1665
|
-
mediaSource:
|
|
1666
|
-
mediaSourceInfo?.type === "main" ? mediaSourceInfo.mediaSource : null,
|
|
1667
|
-
speed,
|
|
1668
|
-
textDisplayer,
|
|
1669
|
-
},
|
|
1670
|
-
cancelSignal,
|
|
1671
|
-
);
|
|
1672
|
-
|
|
1673
|
-
if (cancelSignal.isCancelled()) {
|
|
1674
|
-
return null;
|
|
1012
|
+
/**
|
|
1013
|
+
* Callback allowing to reload the current content.
|
|
1014
|
+
* @param {number} deltaPosition - Position you want to seek to after
|
|
1015
|
+
* reloading, as a delta in seconds from the last polled playing position.
|
|
1016
|
+
* @param {number|undefined} minimumPosition - If set, minimum time bound
|
|
1017
|
+
* in seconds after `deltaPosition` has been applied.
|
|
1018
|
+
* @param {number|undefined} maximumPosition - If set, minimum time bound
|
|
1019
|
+
* in seconds after `deltaPosition` has been applied.
|
|
1020
|
+
*/
|
|
1021
|
+
function reloadMediaSource(
|
|
1022
|
+
deltaPosition: number,
|
|
1023
|
+
minimumPosition: number | undefined,
|
|
1024
|
+
maximumPosition: number | undefined,
|
|
1025
|
+
): void {
|
|
1026
|
+
const lastObservation = coreObserver.getReference().getValue();
|
|
1027
|
+
const currentPosition = lastObservation.position.isAwaitingFuturePosition()
|
|
1028
|
+
? lastObservation.position.getWanted()
|
|
1029
|
+
: (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
|
|
1030
|
+
const isPaused =
|
|
1031
|
+
lastObservation.paused.pending ??
|
|
1032
|
+
coreObserver.getIsPaused() ??
|
|
1033
|
+
lastObservation.paused.last;
|
|
1034
|
+
let position = currentPosition + deltaPosition;
|
|
1035
|
+
if (minimumPosition !== undefined) {
|
|
1036
|
+
position = Math.max(minimumPosition, position);
|
|
1037
|
+
}
|
|
1038
|
+
if (maximumPosition !== undefined) {
|
|
1039
|
+
position = Math.min(maximumPosition, position);
|
|
1040
|
+
}
|
|
1041
|
+
onReloadOrder({ position, autoPlay: !isPaused });
|
|
1675
1042
|
}
|
|
1043
|
+
}
|
|
1676
1044
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1045
|
+
/**
|
|
1046
|
+
* Creates a `RebufferingController`, a class trying to avoid various stalling
|
|
1047
|
+
* situations (such as rebuffering periods), and returns it.
|
|
1048
|
+
*
|
|
1049
|
+
* Various methods from that class need then to be called at various events
|
|
1050
|
+
* (see `RebufferingController` definition).
|
|
1051
|
+
*
|
|
1052
|
+
* This function also handles the `RebufferingController`'s events:
|
|
1053
|
+
* - emit "stalled" events when stalling situations cannot be prevented,
|
|
1054
|
+
* - emit "unstalled" events when we could get out of one,
|
|
1055
|
+
* - emit "warning" on various rebuffering-related minor issues
|
|
1056
|
+
* like discontinuity skipping.
|
|
1057
|
+
* @param {Object} playbackObserver
|
|
1058
|
+
* @param {Object} manifest
|
|
1059
|
+
* @param {Object} speed
|
|
1060
|
+
* @param {Object} cancelSignal
|
|
1061
|
+
* @returns {Object}
|
|
1062
|
+
*/
|
|
1063
|
+
private _createRebufferingController(
|
|
1064
|
+
playbackObserver: IMediaElementPlaybackObserver,
|
|
1065
|
+
manifest: IManifest,
|
|
1066
|
+
speed: IReadOnlySharedReference<number>,
|
|
1067
|
+
cancelSignal: CancellationSignal,
|
|
1068
|
+
): RebufferingController {
|
|
1681
1069
|
const rebufferingController = new RebufferingController(
|
|
1682
1070
|
playbackObserver,
|
|
1683
1071
|
manifest,
|
|
1684
1072
|
speed,
|
|
1685
1073
|
);
|
|
1074
|
+
// Bubble-up events
|
|
1686
1075
|
rebufferingController.addEventListener("stalled", (evt) =>
|
|
1687
1076
|
this.trigger("stalled", evt),
|
|
1688
1077
|
);
|
|
@@ -1692,415 +1081,105 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
1692
1081
|
rebufferingController.addEventListener("warning", (err) =>
|
|
1693
1082
|
this.trigger("warning", err),
|
|
1694
1083
|
);
|
|
1695
|
-
cancelSignal.register(() =>
|
|
1696
|
-
rebufferingController.destroy();
|
|
1697
|
-
});
|
|
1084
|
+
cancelSignal.register(() => rebufferingController.destroy());
|
|
1698
1085
|
rebufferingController.start();
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
const currentContentInfo = this._currentContentInfo;
|
|
1702
|
-
initialPlayPerformed.onUpdate(
|
|
1703
|
-
(isPerformed, stopListening) => {
|
|
1704
|
-
if (isPerformed) {
|
|
1705
|
-
stopListening();
|
|
1706
|
-
const streamEventsEmitter = new StreamEventsEmitter(manifest, playbackObserver);
|
|
1707
|
-
currentContentInfo.streamEventsEmitter = streamEventsEmitter;
|
|
1708
|
-
streamEventsEmitter.addEventListener(
|
|
1709
|
-
"event",
|
|
1710
|
-
(payload) => {
|
|
1711
|
-
this.trigger("streamEvent", payload);
|
|
1712
|
-
},
|
|
1713
|
-
cancelSignal,
|
|
1714
|
-
);
|
|
1715
|
-
streamEventsEmitter.addEventListener(
|
|
1716
|
-
"eventSkip",
|
|
1717
|
-
(payload) => {
|
|
1718
|
-
this.trigger("streamEventSkip", payload);
|
|
1719
|
-
},
|
|
1720
|
-
cancelSignal,
|
|
1721
|
-
);
|
|
1722
|
-
streamEventsEmitter.start();
|
|
1723
|
-
cancelSignal.register(() => {
|
|
1724
|
-
streamEventsEmitter.stop();
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
|
-
},
|
|
1728
|
-
{ clearSignal: cancelSignal, emitCurrentValue: true },
|
|
1729
|
-
);
|
|
1730
|
-
|
|
1731
|
-
const _getSegmentSinkMetrics = async (): Promise<ISegmentSinkMetrics | undefined> => {
|
|
1732
|
-
this._awaitingRequests.nextRequestId++;
|
|
1733
|
-
const requestId = this._awaitingRequests.nextRequestId;
|
|
1734
|
-
this._settings.coreInterface.sendMessage({
|
|
1735
|
-
type: MainThreadMessageType.PullSegmentSinkStoreInfos,
|
|
1736
|
-
value: { requestId },
|
|
1737
|
-
});
|
|
1738
|
-
return new Promise((resolve, reject) => {
|
|
1739
|
-
const rejectFn = (err: CancellationError) => {
|
|
1740
|
-
cancelSignal.deregister(rejectFn);
|
|
1741
|
-
this._awaitingRequests.pendingSinkMetrics.delete(requestId);
|
|
1742
|
-
return reject(err);
|
|
1743
|
-
};
|
|
1744
|
-
this._awaitingRequests.pendingSinkMetrics.set(requestId, {
|
|
1745
|
-
resolve: (value: ISegmentSinkMetrics | undefined) => {
|
|
1746
|
-
cancelSignal.deregister(rejectFn);
|
|
1747
|
-
this._awaitingRequests.pendingSinkMetrics.delete(requestId);
|
|
1748
|
-
resolve(value);
|
|
1749
|
-
},
|
|
1750
|
-
});
|
|
1751
|
-
cancelSignal.register(rejectFn);
|
|
1752
|
-
});
|
|
1753
|
-
};
|
|
1754
|
-
const _getThumbnailsData = async (
|
|
1755
|
-
periodId: string,
|
|
1756
|
-
thumbnailTrackId: string,
|
|
1757
|
-
time: number,
|
|
1758
|
-
): Promise<IThumbnailResponse> => {
|
|
1759
|
-
if (this._currentContentInfo === null) {
|
|
1760
|
-
return Promise.reject(new Error("Cannot fetch thumbnails: No content loaded."));
|
|
1761
|
-
}
|
|
1762
|
-
this._awaitingRequests.nextRequestId++;
|
|
1763
|
-
const requestId = this._awaitingRequests.nextRequestId;
|
|
1764
|
-
this._settings.coreInterface.sendMessage({
|
|
1765
|
-
type: MainThreadMessageType.ThumbnailDataRequest,
|
|
1766
|
-
contentId: this._currentContentInfo.contentId,
|
|
1767
|
-
value: { requestId, periodId, thumbnailTrackId, time },
|
|
1768
|
-
});
|
|
1769
|
-
|
|
1770
|
-
return new Promise((resolve, reject) => {
|
|
1771
|
-
const rejectFn = (err: CancellationError) => {
|
|
1772
|
-
cleanUp();
|
|
1773
|
-
reject(err);
|
|
1774
|
-
};
|
|
1775
|
-
const cleanUp = () => {
|
|
1776
|
-
cancelSignal.deregister(rejectFn);
|
|
1777
|
-
this._awaitingRequests.pendingThumbnailFetching.delete(requestId);
|
|
1778
|
-
};
|
|
1779
|
-
|
|
1780
|
-
this._awaitingRequests.pendingThumbnailFetching.set(requestId, {
|
|
1781
|
-
resolve: (value: IThumbnailResponse) => {
|
|
1782
|
-
cleanUp();
|
|
1783
|
-
resolve(value);
|
|
1784
|
-
},
|
|
1785
|
-
reject: (value: unknown) => {
|
|
1786
|
-
cleanUp();
|
|
1787
|
-
reject(value);
|
|
1788
|
-
},
|
|
1789
|
-
});
|
|
1790
|
-
cancelSignal.register(rejectFn);
|
|
1791
|
-
});
|
|
1792
|
-
};
|
|
1793
|
-
/**
|
|
1794
|
-
* Emit a "loaded" events once the initial play has been performed and the
|
|
1795
|
-
* media can begin playback.
|
|
1796
|
-
* Also emits warning events if issues arise when doing so.
|
|
1797
|
-
*/
|
|
1798
|
-
autoPlayResult
|
|
1799
|
-
.then(() => {
|
|
1800
|
-
getLoadedReference(playbackObserver, false, cancelSignal).onUpdate(
|
|
1801
|
-
(isLoaded, stopListening) => {
|
|
1802
|
-
if (isLoaded) {
|
|
1803
|
-
stopListening();
|
|
1804
|
-
this.trigger("loaded", {
|
|
1805
|
-
getSegmentSinkMetrics: _getSegmentSinkMetrics,
|
|
1806
|
-
getThumbnailData: _getThumbnailsData,
|
|
1807
|
-
});
|
|
1808
|
-
}
|
|
1809
|
-
},
|
|
1810
|
-
{ emitCurrentValue: true, clearSignal: cancelSignal },
|
|
1811
|
-
);
|
|
1812
|
-
})
|
|
1813
|
-
.catch((err) => {
|
|
1814
|
-
if (cancelSignal.isCancelled()) {
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1817
|
-
this._onFatalError(err);
|
|
1818
|
-
});
|
|
1819
|
-
|
|
1820
|
-
return corePlaybackObserver;
|
|
1086
|
+
return rebufferingController;
|
|
1821
1087
|
}
|
|
1822
1088
|
|
|
1823
1089
|
/**
|
|
1824
|
-
*
|
|
1825
|
-
* - The Manifest is fetched and stored in `this._currentContentInfo`.
|
|
1826
|
-
* - `drmInitializationStatus` indicates that DRM matters are initialized.
|
|
1827
|
-
* - `mediaSourceStatus` indicates that the MediaSource is attached to the
|
|
1828
|
-
* `mediaElement`.
|
|
1829
|
-
*
|
|
1830
|
-
* In other cases, this method will do nothing.
|
|
1831
|
-
*
|
|
1832
|
-
* To call when any of those conditions might become `true`, to start-up
|
|
1833
|
-
* playback.
|
|
1090
|
+
* Evaluates a list of codecs to determine their support status.
|
|
1834
1091
|
*
|
|
1835
|
-
* @param {
|
|
1836
|
-
* @returns {
|
|
1837
|
-
* playback start.
|
|
1092
|
+
* @param {Array} codecsToCheck - The list of codecs to check.
|
|
1093
|
+
* @returns {Array} - The list of evaluated codecs with their support status updated.
|
|
1838
1094
|
*/
|
|
1839
|
-
private
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
position: initialObservation.position.serialize(),
|
|
1884
|
-
});
|
|
1885
|
-
this._settings.coreInterface.sendMessage({
|
|
1886
|
-
type: MainThreadMessageType.StartPreparedContent,
|
|
1887
|
-
contentId,
|
|
1888
|
-
value: {
|
|
1889
|
-
initialTime,
|
|
1890
|
-
initialObservation: sentInitialObservation,
|
|
1891
|
-
drmSystemId: drmInitStatus.drmSystemId,
|
|
1892
|
-
enableFastSwitching,
|
|
1893
|
-
onCodecSwitch,
|
|
1894
|
-
},
|
|
1095
|
+
private getCodecsSupportInfo(
|
|
1096
|
+
codecsToCheck: Array<{ mimeType: string; codec: string }>,
|
|
1097
|
+
mediaElement: IMediaElement,
|
|
1098
|
+
): ICodecSupportInfo[] {
|
|
1099
|
+
const codecsSupportInfo: ICodecSupportInfo[] = codecsToCheck.map((codecToCheck) => {
|
|
1100
|
+
const inputCodec = `${codecToCheck.mimeType};codecs="${codecToCheck.codec}"`;
|
|
1101
|
+
const isSupported = isCodecSupported(mediaElement, inputCodec);
|
|
1102
|
+
if (!isSupported) {
|
|
1103
|
+
return {
|
|
1104
|
+
mimeType: codecToCheck.mimeType,
|
|
1105
|
+
codec: codecToCheck.codec,
|
|
1106
|
+
supported: false,
|
|
1107
|
+
supportedIfEncrypted: false,
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* `true` if the codec is supported when encrypted, `false` if it is not
|
|
1112
|
+
* supported, or `undefined` if we cannot obtain that information.
|
|
1113
|
+
*/
|
|
1114
|
+
let supportedIfEncrypted: boolean | undefined;
|
|
1115
|
+
if (this._decryptionCapabilities.status === "uninitialized") {
|
|
1116
|
+
supportedIfEncrypted = undefined;
|
|
1117
|
+
} else if (this._decryptionCapabilities.status === "disabled") {
|
|
1118
|
+
// It's ambiguous here, but let's say that no ContentDecryptor means that
|
|
1119
|
+
// the codec is supported by it.
|
|
1120
|
+
supportedIfEncrypted = true;
|
|
1121
|
+
} else {
|
|
1122
|
+
const contentDecryptor = this._decryptionCapabilities.value;
|
|
1123
|
+
if (contentDecryptor.getState() !== ContentDecryptorState.Initializing) {
|
|
1124
|
+
// No information is available regarding the support status.
|
|
1125
|
+
// Defaulting to assume the codec is supported.
|
|
1126
|
+
supportedIfEncrypted =
|
|
1127
|
+
contentDecryptor.isCodecSupported(
|
|
1128
|
+
codecToCheck.mimeType,
|
|
1129
|
+
codecToCheck.codec,
|
|
1130
|
+
) ?? true;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return {
|
|
1134
|
+
mimeType: codecToCheck.mimeType,
|
|
1135
|
+
codec: codecToCheck.codec,
|
|
1136
|
+
supported: isSupported,
|
|
1137
|
+
supportedIfEncrypted,
|
|
1138
|
+
};
|
|
1895
1139
|
});
|
|
1896
|
-
|
|
1897
|
-
corePlaybackObserver.listen(
|
|
1898
|
-
(obs) => {
|
|
1899
|
-
this._settings.coreInterface.sendMessage({
|
|
1900
|
-
type: MainThreadMessageType.PlaybackObservation,
|
|
1901
|
-
contentId,
|
|
1902
|
-
value: objectAssign(obs, { position: obs.position.serialize() }),
|
|
1903
|
-
});
|
|
1904
|
-
},
|
|
1905
|
-
{
|
|
1906
|
-
includeLastObservation: false,
|
|
1907
|
-
clearSignal: this._currentMediaSourceCanceller.signal,
|
|
1908
|
-
},
|
|
1909
|
-
);
|
|
1910
|
-
this.trigger("manifestReady", manifest);
|
|
1911
|
-
return true;
|
|
1140
|
+
return codecsSupportInfo;
|
|
1912
1141
|
}
|
|
1913
1142
|
|
|
1914
1143
|
/**
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
1919
|
-
* @param {Object}
|
|
1144
|
+
* Update the support status of all Representations in the Manifest.
|
|
1145
|
+
*
|
|
1146
|
+
* To call anytime either the Manifest is linked to new codecs or new means
|
|
1147
|
+
* to test for codec support are available.
|
|
1148
|
+
* @param {Object} manifest
|
|
1920
1149
|
*/
|
|
1921
|
-
private
|
|
1922
|
-
|
|
1150
|
+
private _refreshManifestCodecSupport(
|
|
1151
|
+
manifest: IManifest,
|
|
1923
1152
|
mediaElement: IMediaElement,
|
|
1924
|
-
mediaSourceStatus: SharedReference<MediaSourceInitializationStatus>,
|
|
1925
|
-
coreInterface: CoreInterface,
|
|
1926
1153
|
): void {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
const { mediaSourceId } = msg;
|
|
1154
|
+
const codecsToTest = manifest.getCodecsWithUnknownSupport();
|
|
1155
|
+
const codecsSupportInfo = this.getCodecsSupportInfo(codecsToTest, mediaElement);
|
|
1156
|
+
if (codecsSupportInfo.length > 0) {
|
|
1931
1157
|
try {
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
stopListening();
|
|
1936
|
-
return;
|
|
1937
|
-
}
|
|
1938
|
-
if (currStatus === MediaSourceInitializationStatus.AttachNow) {
|
|
1939
|
-
stopListening();
|
|
1940
|
-
const mediaSource = new MainMediaSourceInterface(
|
|
1941
|
-
mediaSourceId,
|
|
1942
|
-
"FORCED_MEDIA_SOURCE" in mediaElement
|
|
1943
|
-
? mediaElement.FORCED_MEDIA_SOURCE
|
|
1944
|
-
: undefined,
|
|
1945
|
-
);
|
|
1946
|
-
if (this._currentContentInfo.mediaSourceInfo?.type === "main") {
|
|
1947
|
-
this._currentContentInfo.mediaSourceInfo.mediaSource.dispose();
|
|
1948
|
-
}
|
|
1949
|
-
this._currentContentInfo.mediaSourceInfo = {
|
|
1950
|
-
type: "main",
|
|
1951
|
-
mediaSource,
|
|
1952
|
-
};
|
|
1953
|
-
mediaSource.addEventListener("mediaSourceOpen", () => {
|
|
1954
|
-
coreInterface.sendMessage({
|
|
1955
|
-
type: MainThreadMessageType.MediaSourceReadyStateChange,
|
|
1956
|
-
mediaSourceId,
|
|
1957
|
-
value: "open",
|
|
1958
|
-
});
|
|
1959
|
-
});
|
|
1960
|
-
mediaSource.addEventListener("mediaSourceEnded", () => {
|
|
1961
|
-
coreInterface.sendMessage({
|
|
1962
|
-
type: MainThreadMessageType.MediaSourceReadyStateChange,
|
|
1963
|
-
mediaSourceId,
|
|
1964
|
-
value: "ended",
|
|
1965
|
-
});
|
|
1966
|
-
});
|
|
1967
|
-
mediaSource.addEventListener("mediaSourceClose", () => {
|
|
1968
|
-
coreInterface.sendMessage({
|
|
1969
|
-
type: MainThreadMessageType.MediaSourceReadyStateChange,
|
|
1970
|
-
mediaSourceId,
|
|
1971
|
-
value: "closed",
|
|
1972
|
-
});
|
|
1973
|
-
});
|
|
1974
|
-
let url: string | null = null;
|
|
1975
|
-
if (mediaSource.handle.type === "handle") {
|
|
1976
|
-
mediaElement.srcObject = mediaSource.handle.value;
|
|
1977
|
-
} else {
|
|
1978
|
-
url = URL.createObjectURL(mediaSource.handle.value);
|
|
1979
|
-
mediaElement.src = url;
|
|
1980
|
-
}
|
|
1981
|
-
this._currentMediaSourceCanceller.signal.register(() => {
|
|
1982
|
-
mediaSource.dispose();
|
|
1983
|
-
resetMediaElement(mediaElement, url);
|
|
1984
|
-
});
|
|
1985
|
-
mediaSourceStatus.setValue(MediaSourceInitializationStatus.Attached);
|
|
1986
|
-
disableRemotePlaybackOnManagedMediaSource(
|
|
1987
|
-
mediaElement,
|
|
1988
|
-
this._currentMediaSourceCanceller.signal,
|
|
1989
|
-
);
|
|
1990
|
-
}
|
|
1991
|
-
},
|
|
1992
|
-
{
|
|
1993
|
-
emitCurrentValue: true,
|
|
1994
|
-
clearSignal: this._currentMediaSourceCanceller.signal,
|
|
1995
|
-
},
|
|
1996
|
-
);
|
|
1997
|
-
} catch (_err) {
|
|
1998
|
-
const error = new OtherError(
|
|
1999
|
-
"NONE",
|
|
2000
|
-
"Unknown error when creating the MediaSource",
|
|
2001
|
-
);
|
|
2002
|
-
this._onFatalError(error);
|
|
1158
|
+
manifest.updateCodecSupport(codecsSupportInfo);
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
this._onFatalError(err);
|
|
2003
1161
|
}
|
|
2004
1162
|
}
|
|
2005
1163
|
}
|
|
2006
1164
|
}
|
|
2007
1165
|
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
/**
|
|
2022
|
-
* Current MediaSource linked to the content.
|
|
2023
|
-
*
|
|
2024
|
-
* `null` if no MediaSource is currently created for the content.
|
|
2025
|
-
*/
|
|
2026
|
-
mediaSourceInfo:
|
|
2027
|
-
| {
|
|
2028
|
-
type: "main";
|
|
2029
|
-
mediaSource: MainMediaSourceInterface;
|
|
2030
|
-
}
|
|
2031
|
-
| {
|
|
2032
|
-
type: "core";
|
|
2033
|
-
mediaSourceId: string;
|
|
2034
|
-
}
|
|
2035
|
-
| null;
|
|
2036
|
-
/**
|
|
2037
|
-
* Current `RebufferingController` linked to the content, allowing to
|
|
2038
|
-
* detect and handle rebuffering situations.
|
|
2039
|
-
*
|
|
2040
|
-
* `null` if none is currently created for the content.
|
|
2041
|
-
*/
|
|
2042
|
-
rebufferingController: RebufferingController | null;
|
|
2043
|
-
/**
|
|
2044
|
-
* Current `StreamEventsEmitter` linked to the content, allowing to
|
|
2045
|
-
* send events found in the Manifest.
|
|
2046
|
-
*
|
|
2047
|
-
* `null` if none is currently created for the content.
|
|
2048
|
-
*/
|
|
2049
|
-
streamEventsEmitter: StreamEventsEmitter | null;
|
|
2050
|
-
/**
|
|
2051
|
-
* The initial position to seek to in seconds once the content is loadeed.
|
|
2052
|
-
* `undefined` if unknown yet.
|
|
2053
|
-
*/
|
|
2054
|
-
initialTime: number | undefined;
|
|
2055
|
-
/**
|
|
2056
|
-
* Whether to automatically play once the content is loaded.
|
|
2057
|
-
* `undefined` if unknown yet.
|
|
2058
|
-
*/
|
|
2059
|
-
autoPlay: boolean | undefined;
|
|
2060
|
-
/**
|
|
2061
|
-
* Set to `true` once the initial play (or skipping the initial play when
|
|
2062
|
-
* autoplay is not enabled) has been done.
|
|
2063
|
-
* Set to `false` when it hasn't been done yet.
|
|
2064
|
-
*
|
|
2065
|
-
* Set to `null` when those considerations are not taken yet.
|
|
2066
|
-
*/
|
|
2067
|
-
initialPlayPerformed: IReadOnlySharedReference<boolean> | null;
|
|
2068
|
-
/**
|
|
2069
|
-
* Set to the initialized `ContentDecryptor` instance linked to that content.
|
|
2070
|
-
*
|
|
2071
|
-
* Set to `null` when those considerations are not taken.
|
|
2072
|
-
*/
|
|
2073
|
-
contentDecryptor: IContentDecryptor | null;
|
|
2074
|
-
/**
|
|
2075
|
-
* If `true`, MSE API should be used in the core part of the RxPlayer (in the
|
|
2076
|
-
* WebWorker).
|
|
2077
|
-
* If `false`, they should be relied on on main thread.
|
|
2078
|
-
*/
|
|
2079
|
-
useMseInWorker: boolean;
|
|
1166
|
+
function createTextDisplayer(
|
|
1167
|
+
mediaElement: IMediaElement,
|
|
1168
|
+
textTrackOptions: ITextDisplayerOptions,
|
|
1169
|
+
): ITextDisplayer | null {
|
|
1170
|
+
if (textTrackOptions.textTrackMode === "html" && features.htmlTextDisplayer !== null) {
|
|
1171
|
+
return new features.htmlTextDisplayer(
|
|
1172
|
+
mediaElement,
|
|
1173
|
+
textTrackOptions.textTrackElement,
|
|
1174
|
+
);
|
|
1175
|
+
} else if (features.nativeTextDisplayer !== null) {
|
|
1176
|
+
return new features.nativeTextDisplayer(mediaElement);
|
|
1177
|
+
}
|
|
1178
|
+
return null;
|
|
2080
1179
|
}
|
|
2081
1180
|
|
|
2082
1181
|
/** Arguments to give to the `InitializeOnMediaSource` function. */
|
|
2083
1182
|
export interface IInitializeArguments {
|
|
2084
|
-
/**
|
|
2085
|
-
* The `MediaSourceContentInitializer` will interact with the RxPlayer's core
|
|
2086
|
-
* logic (the one loading media data) by exchanging messages through an
|
|
2087
|
-
* interface called the `CoreInterface`.
|
|
2088
|
-
*
|
|
2089
|
-
* This `CoreInterface` allows to abstract its actual current implementation.
|
|
2090
|
-
* E.g., the core logic could be running in a WebWorker or in main thread, in
|
|
2091
|
-
* which cases message exchanging mechanisms would be different.
|
|
2092
|
-
*/
|
|
2093
|
-
coreInterface: CoreInterface;
|
|
2094
|
-
/**
|
|
2095
|
-
* If `true`, MSE API should be used in the core part of the RxPlayer (in the
|
|
2096
|
-
* WebWorker).
|
|
2097
|
-
* If `false`, they should be relied on on main thread.
|
|
2098
|
-
*
|
|
2099
|
-
* This might depend on both browser capabilities and preferences. It is
|
|
2100
|
-
* assumed that the caller perform all those checks, the `ContentInitializer`
|
|
2101
|
-
* won't check again the validity of this value.
|
|
2102
|
-
*/
|
|
2103
|
-
useMseInWorker: boolean;
|
|
2104
1183
|
/** Options concerning the ABR logic. */
|
|
2105
1184
|
adaptiveOptions: IAdaptiveRepresentationSelectorArguments;
|
|
2106
1185
|
/** `true` if we should play when loaded. */
|
|
@@ -2137,14 +1216,6 @@ export interface IInitializeArguments {
|
|
|
2137
1216
|
keySystems: IKeySystemOption[];
|
|
2138
1217
|
/** `true` to play low-latency contents optimally. */
|
|
2139
1218
|
lowLatencyMode: boolean;
|
|
2140
|
-
/**
|
|
2141
|
-
* The type of "transport" wanted, e.g. "dash" or "smooth".
|
|
2142
|
-
*/
|
|
2143
|
-
transport: string;
|
|
2144
|
-
/** Options relative to the streaming protocol. */
|
|
2145
|
-
transportOptions: Omit<ITransportOptions, "representationFilter"> & {
|
|
2146
|
-
representationFilter?: IRepresentationFilter | string | undefined;
|
|
2147
|
-
};
|
|
2148
1219
|
/** Settings linked to Manifest requests. */
|
|
2149
1220
|
manifestRequestSettings: {
|
|
2150
1221
|
/** Maximum number of time a request on error will be retried. */
|
|
@@ -2170,6 +1241,8 @@ export interface IInitializeArguments {
|
|
|
2170
1241
|
*/
|
|
2171
1242
|
initialManifest: IInitialManifest | undefined;
|
|
2172
1243
|
};
|
|
1244
|
+
/** Logic linked Manifest and segment loading and parsing. */
|
|
1245
|
+
transport: ITransportPipelines;
|
|
2173
1246
|
/** Configuration for the segment requesting logic. */
|
|
2174
1247
|
segmentRequestOptions: {
|
|
2175
1248
|
lowLatencyMode: boolean;
|
|
@@ -2198,151 +1271,196 @@ export interface IInitializeArguments {
|
|
|
2198
1271
|
url: string | undefined;
|
|
2199
1272
|
}
|
|
2200
1273
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
),
|
|
2214
|
-
]
|
|
2215
|
-
>
|
|
2216
|
-
): void {
|
|
2217
|
-
for (const ref of refs) {
|
|
2218
|
-
ref[0].onUpdate(
|
|
2219
|
-
(newVal) => {
|
|
2220
|
-
// NOTE: The TypeScript checks have already been made by this function's
|
|
2221
|
-
// overload, but the body here is not aware of that.
|
|
2222
|
-
coreInterface.sendMessage({
|
|
2223
|
-
type: MainThreadMessageType.ReferenceUpdate,
|
|
2224
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
2225
|
-
value: { name: ref[1] as any, newVal: newVal as any },
|
|
2226
|
-
});
|
|
2227
|
-
},
|
|
2228
|
-
{ clearSignal: cancellationSignal, emitCurrentValue: true },
|
|
2229
|
-
);
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
function formatCoreError(sentError: ISentError): IPlayerError {
|
|
2234
|
-
switch (sentError.name) {
|
|
2235
|
-
case "NetworkError":
|
|
2236
|
-
return new NetworkError(
|
|
2237
|
-
sentError.code,
|
|
2238
|
-
new RequestError(
|
|
2239
|
-
sentError.baseError.url,
|
|
2240
|
-
sentError.baseError.status,
|
|
2241
|
-
sentError.baseError.type,
|
|
2242
|
-
),
|
|
2243
|
-
);
|
|
2244
|
-
case "MediaError":
|
|
2245
|
-
// eslint-disable-next-line
|
|
2246
|
-
return new MediaError(sentError.code as any, sentError.reason, {
|
|
2247
|
-
tracks: sentError.tracks,
|
|
2248
|
-
});
|
|
2249
|
-
case "EncryptedMediaError":
|
|
2250
|
-
// We assume that everything have already been checked Worker-side here
|
|
2251
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2252
|
-
return new EncryptedMediaError(sentError.code, sentError.reason, {
|
|
2253
|
-
keyStatuses: sentError.keyStatuses,
|
|
2254
|
-
keySystemConfiguration: sentError.keySystemConfiguration,
|
|
2255
|
-
keySystem: sentError.keySystem,
|
|
2256
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2257
|
-
} as any);
|
|
2258
|
-
case "OtherError":
|
|
2259
|
-
return new OtherError(sentError.code, sentError.reason);
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
/** Enume allowing to state what is the current status of MediaSource initialization. */
|
|
2264
|
-
const enum MediaSourceInitializationStatus {
|
|
2265
|
-
/**
|
|
2266
|
-
* The `MediaSource` is not attached to the `HTMLMediaElement` and shouldn't
|
|
2267
|
-
* be yet.
|
|
2268
|
-
*/
|
|
2269
|
-
Nothing,
|
|
1274
|
+
/** Arguments needed when starting to buffer media on a specific MediaSource. */
|
|
1275
|
+
interface IBufferingMediaSettings {
|
|
1276
|
+
/** Various stream-related options. */
|
|
1277
|
+
bufferOptions: IStreamOrchestratorOptions;
|
|
1278
|
+
/* Manifest of the content we want to play. */
|
|
1279
|
+
manifest: IManifest;
|
|
1280
|
+
/** Media Element on which the content will be played. */
|
|
1281
|
+
mediaElement: IMediaElement;
|
|
1282
|
+
/** Emit playback conditions regularly. */
|
|
1283
|
+
playbackObserver: IMediaElementPlaybackObserver;
|
|
1284
|
+
/** Estimate the right Representation. */
|
|
1285
|
+
representationEstimator: IRepresentationEstimator;
|
|
2270
1286
|
/**
|
|
2271
|
-
*
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
2274
|
-
* The purpose of this enum variant is to be set when wanting to indicate
|
|
2275
|
-
* that `MediaSource` attachment has to be done, in code that do not have
|
|
2276
|
-
* the capability to do so.
|
|
2277
|
-
*
|
|
2278
|
-
* The code that can do so would then read that value and then set this enum
|
|
2279
|
-
* to `Attached` once the `MediaSource` is attached.
|
|
1287
|
+
* Interface allowing to prioritize CDN between one another depending on past
|
|
1288
|
+
* performances, content steering, etc.
|
|
2280
1289
|
*/
|
|
2281
|
-
|
|
2282
|
-
/**
|
|
2283
|
-
|
|
1290
|
+
cdnPrioritizer: CdnPrioritizer;
|
|
1291
|
+
/** Module to facilitate segment fetching. */
|
|
1292
|
+
segmentQueueCreator: SegmentQueueCreator;
|
|
1293
|
+
/** Last wanted playback rate. */
|
|
1294
|
+
speed: IReadOnlySharedReference<number>;
|
|
1295
|
+
/** `MediaSource` element on which the media will be buffered. */
|
|
1296
|
+
mediaSource: MainMediaSourceInterface;
|
|
1297
|
+
/** The initial position to seek to in media time, in seconds. */
|
|
1298
|
+
initialTime: number;
|
|
1299
|
+
/** If `true` it should automatically play once enough data is loaded. */
|
|
1300
|
+
autoPlay: boolean;
|
|
2284
1301
|
}
|
|
2285
1302
|
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
1303
|
+
/**
|
|
1304
|
+
* Change the decipherability of Representations which have their key id in one
|
|
1305
|
+
* of the given Arrays:
|
|
1306
|
+
*
|
|
1307
|
+
* - Those who have a key id listed in `whitelistedKeyIds` will have their
|
|
1308
|
+
* decipherability updated to `true`
|
|
1309
|
+
*
|
|
1310
|
+
* - Those who have a key id listed in `blacklistedKeyIds` will have their
|
|
1311
|
+
* decipherability updated to `false`
|
|
1312
|
+
*
|
|
1313
|
+
* - Those who have a key id listed in `delistedKeyIds` will have their
|
|
1314
|
+
* decipherability updated to `undefined`.
|
|
1315
|
+
*
|
|
1316
|
+
* @param {Object} manifest
|
|
1317
|
+
* @param {Array.<Uint8Array>} whitelistedKeyIds
|
|
1318
|
+
* @param {Array.<Uint8Array>} blacklistedKeyIds
|
|
1319
|
+
* @param {Array.<Uint8Array>} delistedKeyIds
|
|
1320
|
+
*/
|
|
1321
|
+
function updateKeyIdsDecipherabilityOnManifest(
|
|
1322
|
+
manifest: IManifest,
|
|
1323
|
+
whitelistedKeyIds: Uint8Array[],
|
|
1324
|
+
blacklistedKeyIds: Uint8Array[],
|
|
1325
|
+
delistedKeyIds: Uint8Array[],
|
|
1326
|
+
): void {
|
|
1327
|
+
manifest.updateRepresentationsDeciperability((ctx) => {
|
|
1328
|
+
const { representation } = ctx;
|
|
1329
|
+
if (representation.contentProtections === undefined) {
|
|
1330
|
+
return representation.decipherable;
|
|
1331
|
+
}
|
|
1332
|
+
const contentKIDs = representation.contentProtections.keyIds;
|
|
1333
|
+
if (contentKIDs !== undefined) {
|
|
1334
|
+
for (const elt of contentKIDs) {
|
|
1335
|
+
for (const blacklistedKeyId of blacklistedKeyIds) {
|
|
1336
|
+
if (areArraysOfNumbersEqual(blacklistedKeyId, elt)) {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
for (const whitelistedKeyId of whitelistedKeyIds) {
|
|
1341
|
+
if (areArraysOfNumbersEqual(whitelistedKeyId, elt)) {
|
|
1342
|
+
return true;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
for (const delistedKeyId of delistedKeyIds) {
|
|
1346
|
+
if (areArraysOfNumbersEqual(delistedKeyId, elt)) {
|
|
1347
|
+
return undefined;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return representation.decipherable;
|
|
1353
|
+
});
|
|
2295
1354
|
}
|
|
2296
1355
|
|
|
2297
|
-
/**
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
1356
|
+
/**
|
|
1357
|
+
* Update decipherability to `false` to any Representation which is linked to
|
|
1358
|
+
* the given initialization data.
|
|
1359
|
+
* @param {Object} manifest
|
|
1360
|
+
* @param {Object} initData
|
|
1361
|
+
*/
|
|
1362
|
+
function blackListProtectionDataOnManifest(
|
|
1363
|
+
manifest: IManifest,
|
|
1364
|
+
initData: IProcessedProtectionData,
|
|
1365
|
+
) {
|
|
1366
|
+
manifest.updateRepresentationsDeciperability((ctx) => {
|
|
1367
|
+
const rep = ctx.representation;
|
|
1368
|
+
if (rep.decipherable === false) {
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
const segmentProtections = rep.contentProtections?.initData ?? [];
|
|
1372
|
+
for (const protection of segmentProtections) {
|
|
1373
|
+
if (initData.type === undefined || protection.type === initData.type) {
|
|
1374
|
+
const containedInitData = initData.values
|
|
1375
|
+
.getFormattedValues()
|
|
1376
|
+
.every((undecipherableVal) => {
|
|
1377
|
+
return protection.values.some((currVal) => {
|
|
1378
|
+
return (
|
|
1379
|
+
(undecipherableVal.systemId === undefined ||
|
|
1380
|
+
currVal.systemId === undecipherableVal.systemId) &&
|
|
1381
|
+
areArraysOfNumbersEqual(currVal.data, undecipherableVal.data)
|
|
1382
|
+
);
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1385
|
+
if (containedInitData) {
|
|
1386
|
+
return false;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return rep.decipherable;
|
|
1391
|
+
});
|
|
2326
1392
|
}
|
|
2327
1393
|
|
|
2328
1394
|
/**
|
|
2329
|
-
*
|
|
2330
|
-
*
|
|
2331
|
-
*
|
|
2332
|
-
*
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2336
|
-
* @param {
|
|
2337
|
-
*
|
|
1395
|
+
* Function to call when you want to "reload" the MediaSource: basically
|
|
1396
|
+
* restarting playback on a new MediaSource for the same content (it may
|
|
1397
|
+
* be for varied reasons, such as ensuring data buffers are empty, or
|
|
1398
|
+
* restarting after some kind of fatal error).
|
|
1399
|
+
* @param {Object} reloadOrder
|
|
1400
|
+
* @param {number} reloadOrder.position - Position in seconds at which we
|
|
1401
|
+
* should restart from when playback restarts.
|
|
1402
|
+
* @param {boolean} reloadOrder.autoPlay - If `true` we will directly play
|
|
1403
|
+
* once enough data is re-loaded.
|
|
2338
1404
|
*/
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
1405
|
+
type IReloadMediaSourceCallback = (reloadOrder: {
|
|
1406
|
+
position: number;
|
|
1407
|
+
autoPlay: boolean;
|
|
1408
|
+
}) => void;
|
|
2343
1409
|
|
|
2344
|
-
|
|
2345
|
-
|
|
1410
|
+
/**
|
|
1411
|
+
* Handle accordingly an `IFreezeResolution` object.
|
|
1412
|
+
* @param {Object|null} freezeResolution - The `IFreezeResolution` suggested.
|
|
1413
|
+
* @param {Object} param - Parameters that might be needed to implement the
|
|
1414
|
+
* resolution.
|
|
1415
|
+
* @param {Object} param.manifest - The current content's Manifest object.
|
|
1416
|
+
* @param {Object} param.playbackObserver - Object regularly emitting playback
|
|
1417
|
+
* conditions.
|
|
1418
|
+
* @param {Function} param.triggerReload - Function to call if we need to ask
|
|
1419
|
+
* for a "MediaSource reload".
|
|
1420
|
+
* @param {Boolean} param.enableRepresentationAvoidance - If `true`, this
|
|
1421
|
+
* function is authorized to mark `Representation` as "to avoid" if the
|
|
1422
|
+
* `IFreezeResolution` object suggest it.
|
|
1423
|
+
*/
|
|
1424
|
+
function handleFreezeResolution(
|
|
1425
|
+
freezeResolution: IFreezeResolution,
|
|
1426
|
+
{
|
|
1427
|
+
playbackObserver,
|
|
1428
|
+
enableRepresentationAvoidance,
|
|
1429
|
+
manifest,
|
|
1430
|
+
triggerReload,
|
|
1431
|
+
}: {
|
|
1432
|
+
playbackObserver: IMediaElementPlaybackObserver;
|
|
1433
|
+
enableRepresentationAvoidance: boolean;
|
|
1434
|
+
manifest: IManifest;
|
|
1435
|
+
triggerReload: () => void;
|
|
1436
|
+
},
|
|
1437
|
+
): void {
|
|
1438
|
+
switch (freezeResolution.type) {
|
|
1439
|
+
case "reload": {
|
|
1440
|
+
log.info("Init", "Planning reload due to freeze");
|
|
1441
|
+
triggerReload();
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
case "flush": {
|
|
1445
|
+
log.info("Init", "Flushing buffer due to freeze");
|
|
1446
|
+
const observation = playbackObserver.getReference().getValue();
|
|
1447
|
+
const currentTime = observation.position.isAwaitingFuturePosition()
|
|
1448
|
+
? observation.position.getWanted()
|
|
1449
|
+
: playbackObserver.getCurrentTime();
|
|
1450
|
+
const relativeResumingPosition = freezeResolution.value.relativeSeek;
|
|
1451
|
+
const wantedSeekingTime = currentTime + relativeResumingPosition;
|
|
1452
|
+
playbackObserver.setCurrentTime(wantedSeekingTime);
|
|
1453
|
+
break;
|
|
1454
|
+
}
|
|
1455
|
+
case "avoid-representations": {
|
|
1456
|
+
const contents = freezeResolution.value;
|
|
1457
|
+
if (enableRepresentationAvoidance) {
|
|
1458
|
+
manifest.addRepresentationsToAvoid(contents);
|
|
1459
|
+
}
|
|
1460
|
+
triggerReload();
|
|
1461
|
+
break;
|
|
1462
|
+
}
|
|
1463
|
+
default:
|
|
1464
|
+
assertUnreachable(freezeResolution);
|
|
2346
1465
|
}
|
|
2347
|
-
return arg as Exclude<ISentLogValue, ISentError>;
|
|
2348
1466
|
}
|