diff --git a/.eslintrc.js b/.eslintrc.js index 4d39ebef80..73f4d96c5f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,10 +16,18 @@ module.exports = { "plugins": [ "eslint-plugin-import", "eslint-plugin-jsdoc", + "ban", "@typescript-eslint", "@typescript-eslint/tslint" ], "rules": { + "ban/ban": [ + 2, + { + "name": ["*", "finally"], + "message": "Promise.prototype.finally is forbidden due to poor support from older devices.\nNote that this linting rule just bans naively all \"finally\" method calls, if in this case it wasn't called on a Promise, you can safely ignore this error", + } + ], "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": [ "error", diff --git a/CHANGELOG.md b/CHANGELOG.md index 570d675a18..449c7f1189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## v3.30.0 (2023-03-07) + +### Features + + - Add `updateContentUrls` API, allowing to update the Manifest's URL during playback [#1182] + - DASH: implement forced-subtitles, adding the `forced` property to the audio tracks API and selecting by default a forced text track linked to the audio track's language if present [#1187] + - DRM: add the `getKeySystemConfiguration` method to the RxPlayer [#1202] + - add experimental `DEBUG_ELEMENT` feature and `createDebugElement` method to render a default debugging HTML element [#1200] + +### Deprecated + + - Deprecate the `getVideoLoadedTime` method which can be easily replaced (see Deprecated method documentation) + - Deprecate the `getVideoPlayedTime` method which can be easily replaced (see Deprecated method documentation) + - Deprecate the `transportOptions.aggressiveMode` option + - DRM: Deprecate the `keySystems[].onKeyStatusesChange` callback as no good use case was found for it. + +### Bug fixes + + - Fix segment requesting error when playing a DASH content without an url and without BaseURL elements [#1192] + - API: Stop sending events if the content is stopped due to a side-effect of one of the event handler [#1197] + - text-tracks/ttml: fix inconsistent line spacing when resizing the `textTrackElement` [#1191] + - DRM: Fix race condition leading to a JS error instead of a `NO_PLAYABLE_REPRESENTATION` [#1201] + - DRM/Compat: Renew MediaKeys at each `loadVideo` on all WebOS (LG TV) platforms to work around issues [#1188] + +### Other improvements + + - DASH: better detect closed captions [#1187] + - DASH: handle `endNumber` DASH attribute [#1186] + - Support encrypted contents on Panasonic 2019 TVs [#1226] + - Better handle SourceBuffer's QuotaExceededError, responsible for `MediaError` with the `BUFFER_FULL_ERROR` code [#1221] + - API: send available...TracksChange events in the very unlikely scenario where tracks are added after a manifest update [#1197] + - Completely remove RxJS dependency from the RxPlayer's source code [#1193] + - DRM: Request PR recommendation when PlayReady is asked and try default recommendation robustnesses [#1189] + + ## v3.29.0 (2022-11-16) ### Features diff --git a/VERSION b/VERSION index c7c9773262..1cfe511a3e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.29.0 +3.30.0 diff --git a/demo/full/scripts/components/Options/AudioAdaptiveSettings.jsx b/demo/full/scripts/components/Options/AudioAdaptiveSettings.jsx index 21c05284be..d72ad2d916 100644 --- a/demo/full/scripts/components/Options/AudioAdaptiveSettings.jsx +++ b/demo/full/scripts/components/Options/AudioAdaptiveSettings.jsx @@ -1,164 +1,198 @@ -import React, { Fragment, useState } from "react"; - +import React, { Fragment, useCallback, useEffect, useState } from "react"; import getCheckBoxValue from "../../lib/getCheckboxValue"; import Checkbox from "../../components/CheckBox"; -import Button from "../Button"; import DEFAULT_VALUES from "../../lib/defaultOptionsValues"; +import PlayerOptionNumberInput from "./PlayerOptionNumberInput"; + +const defaultInitialAudioBitrate = DEFAULT_VALUES.player.initialAudioBitrate; +const defaultMinAudioBitrate = DEFAULT_VALUES.player.minAudioBitrate; +const defaultMaxAudioBitrate = DEFAULT_VALUES.player.maxAudioBitrate; /** * @param {Object} props * @returns {Object} */ function AudioAdaptiveSettings({ - initialAudioBr, - minAudioBr, - maxAudioBr, - onInitialAudioBrInput, - onMinAudioBrInput, - onMaxAudioBrInput, + initialAudioBitrate, + minAudioBitrate, + maxAudioBitrate, + onInitialAudioBitrateChange, + onMinAudioBitrateChange, + onMaxAudioBitrateChange, }) { - const [isMinAudioBrLimited, setMinAudioBrLimit] = useState(minAudioBr !== 0); - const [isMaxAudioBrLimited, setMaxAudioBrLimit] = useState( - maxAudioBr !== Infinity + /* Value of the `initialVideoBitrate` input */ + const [initialAudioBitrateStr, setInitialAudioBitrateStr] = useState( + String(initialAudioBitrate) + ); + /* Value of the `minAudioBitrate` input */ + const [minAudioBitrateStr, setMinAudioBitrateStr] = useState( + String(minAudioBitrate) + ); + /* Value of the `maxAudioBitrate` input */ + const [maxAudioBitrateStr, setMaxAudioBitrateStr] = useState( + String(maxAudioBitrate) + ); + /* + * Keep track of the "limit minAudioBitrate" toggle: + * `false` == checkbox enabled + */ + const [isMinAudioBitrateLimited, setMinAudioBitrateLimit] = useState( + minAudioBitrate !== 0 + ); + /* + * Keep track of the "limit maxAudioBitrate" toggle: + * `false` == checkbox enabled + */ + const [isMaxAudioBitrateLimited, setMaxAudioBitrateLimit] = useState( + maxAudioBitrate !== Infinity ); - const onChangeLimitMinAudioBr = (evt) => { + // Update initialAudioBitrate when its linked text change + useEffect(() => { + // Note that this unnecessarily also run on first render - there seem to be + // no quick and easy way to disable this in react. + // This is not too problematic so I put up with it. + let newBitrate = parseFloat(initialAudioBitrateStr); + newBitrate = isNaN(newBitrate) ? + defaultInitialAudioBitrate : + newBitrate; + onInitialAudioBitrateChange(newBitrate); + }, [initialAudioBitrateStr]); + + // Update minAudioBitrate when its linked text change + useEffect(() => { + let newBitrate = parseFloat(minAudioBitrateStr); + newBitrate = isNaN(newBitrate) ? + defaultMinAudioBitrate : + newBitrate; + onMinAudioBitrateChange(newBitrate); + }, [minAudioBitrateStr]); + + // Update maxAudioBitrate when its linked text change + useEffect(() => { + let newBitrate = parseFloat(maxAudioBitrateStr); + newBitrate = isNaN(newBitrate) ? + defaultMaxAudioBitrate : + newBitrate; + onMaxAudioBitrateChange(newBitrate); + }, [maxAudioBitrateStr]); + + const onChangeLimitMinAudioBitrate = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { - setMinAudioBrLimit(false); - onMinAudioBrInput(0); + setMinAudioBitrateLimit(false); + setMinAudioBitrateStr(String(0)); } else { - setMinAudioBrLimit(true); - onMinAudioBrInput(DEFAULT_VALUES.minAudioBr); + setMinAudioBitrateLimit(true); + setMinAudioBitrateStr(String(defaultMinAudioBitrate)); } - }; + }, []); - const onChangeLimitMaxAudioBr = (evt) => { + const onChangeLimitMaxAudioBitrate = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { - setMaxAudioBrLimit(false); - onMaxAudioBrInput(Infinity); + setMaxAudioBitrateLimit(false); + setMaxAudioBitrateStr(String(Infinity)); } else { - setMaxAudioBrLimit(true); - onMaxAudioBrInput(DEFAULT_VALUES.maxAudioBr); + setMaxAudioBitrateLimit(true); + setMaxAudioBitrateStr(String(defaultMaxAudioBitrate)); } - }; + }, []); return (
  • -
    - - - onInitialAudioBrInput(evt.target.value)} - value={initialAudioBr} - className="optionInput" - /> -
    + { + setInitialAudioBitrateStr(String( + defaultInitialAudioBitrate + )); + }} + /> + + { + initialAudioBitrate === 0 ? + "Starts loading the lowest audio bitrate" : + `Starts with an audio bandwidth estimate of ${initialAudioBitrate}` + + " bits per seconds." + } +
  • -
    - - - onMinAudioBrInput(evt.target.value)} - value={minAudioBr} - disabled={isMinAudioBrLimited === false} - className="optionInput" - /> -
    + { + setMinAudioBitrateStr(String(defaultMinAudioBitrate)); + setMinAudioBitrateLimit(defaultMinAudioBitrate !== 0); + }} + /> Do not limit + + { + !isMinAudioBitrateLimited || minAudioBitrate <= 0 ? + "Not limiting the lowest audio bitrate reachable through the adaptive logic" : + "Limiting the lowest audio bitrate reachable through the adaptive " + + `logic to ${minAudioBitrate} bits per seconds` + } +
  • -
    - - - onMaxAudioBrInput(evt.target.value)} - value={String(maxAudioBr)} - disabled={isMaxAudioBrLimited === false} - className="optionInput" - /> -
    + { + setMaxAudioBitrateStr(String(defaultMaxAudioBitrate)); + setMaxAudioBitrateLimit(defaultMaxAudioBitrate !== Infinity); + }} + />
    Do not limit
    + + { + !isMaxAudioBitrateLimited || parseFloat( + maxAudioBitrate + ) === Infinity ? + "Not limiting the highest audio bitrate reachable through " + + "the adaptive logic" : + "Limiting the highest audio bitrate reachable through the " + + `adaptive logic to ${maxAudioBitrate} bits per seconds` + } +
  • ); diff --git a/demo/full/scripts/components/Options/BufferOptions.jsx b/demo/full/scripts/components/Options/BufferOptions.jsx index c342deefa9..000449f87f 100644 --- a/demo/full/scripts/components/Options/BufferOptions.jsx +++ b/demo/full/scripts/components/Options/BufferOptions.jsx @@ -1,9 +1,13 @@ -import React, { Fragment, useState } from "react"; - +import React, { Fragment, useCallback, useEffect, useState } from "react"; import getCheckBoxValue from "../../lib/getCheckboxValue"; import Checkbox from "../CheckBox"; -import Button from "../Button"; import DEFAULT_VALUES from "../../lib/defaultOptionsValues"; +import PlayerOptionNumberInput from "./PlayerOptionNumberInput"; + +const defaultMaxBufferAhead = DEFAULT_VALUES.player.maxBufferAhead; +const defaultMaxBufferBehind = DEFAULT_VALUES.player.maxBufferBehind; +const defaultMaxVideoBufferSize = DEFAULT_VALUES.player.maxVideoBufferSize; +const defaultWantedBufferAhead = DEFAULT_VALUES.player.wantedBufferAhead; /** * @param {Object} props @@ -14,123 +18,157 @@ function BufferOptions({ maxVideoBufferSize, maxBufferAhead, maxBufferBehind, - onWantedBufferAheadInput, - onMaxVideoBufferSizeInput, - onMaxBufferAheadInput, - onMaxBufferBehindInput, + onWantedBufferAheadChange, + onMaxVideoBufferSizeChange, + onMaxBufferAheadChange, + onMaxBufferBehindChange, }) { - const [isMaxBufferAHeadLimited, setMaxBufferAHeadLimit] = useState( + /* Value of the `wantedBufferAhead` input */ + const [wantedBufferAheadStr, setWantedBufferAheadStr] = useState( + wantedBufferAhead + ); + /* Value of the `maxVideoBufferSize` input */ + const [maxVideoBufferSizeStr, setMaxVideoBufferSizeStr] = useState( + maxVideoBufferSize + ); + /* Value of the `maxBufferBehind` input */ + const [maxBufferBehindStr, setMaxBufferBehindStr] = useState( + maxBufferBehind + ); + /* Value of the `maxBufferAhead` input */ + const [maxBufferAheadStr, setMaxBufferAheadStr] = useState( + maxBufferAhead + ); + /* + * Keep track of the "limit maxBufferAhead" toggle: + * `false` == checkbox enabled + */ + const [isMaxBufferAheadLimited, setMaxBufferAheadLimit] = useState( maxBufferAhead !== Infinity ); + /* + * Keep track of the "limit maxBufferBehind" toggle: + * `false` == checkbox enabled + */ const [isMaxBufferBehindLimited, setMaxBufferBehindLimit] = useState( maxBufferBehind !== Infinity ); - + /* + * Keep track of the "limit maxVideoBufferSize" toggle: + * `false` == checkbox enabled + */ const [isMaxVideoBufferSizeLimited, setMaxVideoBufferSizeLimit] = useState( maxVideoBufferSize !== Infinity ); - const onChangeLimitMaxBufferAHead = (evt) => { + // Update `wantedBufferAhead` when its linked text change + useEffect(() => { + // Note that this unnecessarily also run on first render - there seem to be + // no quick and easy way to disable this in react. + // This is not too problematic so I put up with it. + let newVal = parseFloat(wantedBufferAheadStr); + newVal = isNaN(newVal) ? + defaultWantedBufferAhead : + newVal; + onWantedBufferAheadChange(newVal); + }, [wantedBufferAheadStr]); + + // Update `maxVideoBufferSize` when its linked text change + useEffect(() => { + let newVal = parseFloat(maxVideoBufferSizeStr); + newVal = isNaN(newVal) ? + defaultMaxVideoBufferSize : + newVal; + onMaxVideoBufferSizeChange(newVal); + }, [maxVideoBufferSizeStr]); + + // Update `maxBufferAhead` when its linked text change + useEffect(() => { + let newVal = parseFloat(maxBufferAheadStr); + newVal = isNaN(newVal) ? + defaultMaxBufferAhead : + newVal; + onMaxBufferAheadChange(newVal); + }, [maxBufferAheadStr]); + + // Update `maxBufferBehind` when its linked text change + useEffect(() => { + let newVal = parseFloat(maxBufferBehindStr); + newVal = isNaN(newVal) ? + defaultMaxBufferBehind : + newVal; + onMaxBufferBehindChange(newVal); + }, [maxBufferBehindStr]); + + const onChangeLimitMaxBufferAhead = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { - setMaxBufferAHeadLimit(false); - onMaxBufferAheadInput(Infinity); + setMaxBufferAheadLimit(false); + setMaxBufferAheadStr(String(Infinity)); } else { - setMaxBufferAHeadLimit(true); - onMaxBufferAheadInput(DEFAULT_VALUES.maxBufferAhead); + setMaxBufferAheadLimit(true); + setMaxBufferAheadStr(String(defaultMaxBufferAhead)); } - }; + }, []); - const onChangeLimitMaxBufferBehind = (evt) => { + const onChangeLimitMaxBufferBehind = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { setMaxBufferBehindLimit(false); - onMaxBufferBehindInput(Infinity); + setMaxBufferBehindStr(String(defaultMaxBufferAhead)); } else { setMaxBufferBehindLimit(true); - onMaxBufferAheadInput(DEFAULT_VALUES.maxBufferBehind); + setMaxBufferBehindStr(String(defaultMaxBufferBehind)); } - }; + }, []); - const onChangeLimitMaxVideoBufferSize = (evt) => { + const onChangeLimitMaxVideoBufferSize = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited){ setMaxVideoBufferSizeLimit(false); - onMaxVideoBufferSizeInput(Infinity) + setMaxVideoBufferSizeStr(String(Infinity)); } else { setMaxVideoBufferSizeLimit(true); - onMaxVideoBufferSizeInput(DEFAULT_VALUES.maxVideoBufferSize) + setMaxVideoBufferSizeStr(String(defaultMaxVideoBufferSize)); } - } + }, []); return (
  • -
    - - - onWantedBufferAheadInput(evt.target.value)} - value={wantedBufferAhead} - className="optionInput" - /> -
    + { + setWantedBufferAheadStr(String( + defaultWantedBufferAhead + )); + }} + /> + + Buffering around {wantedBufferAhead} second(s) ahead of the current + position +
  • -
    - - - onMaxVideoBufferSizeInput(evt.target.value)} - value={maxVideoBufferSize} - disabled={isMaxVideoBufferSizeLimited === false} - className="optionInput" - /> -
    + { + setMaxVideoBufferSizeStr(String(defaultMaxVideoBufferSize)); + setMaxVideoBufferSizeLimit(defaultMaxVideoBufferSize !== Infinity); + }} + /> Do not limit + + { + maxVideoBufferSize === Infinity || + !isMaxVideoBufferSizeLimited ? + "Not setting a size limit to the video buffer (relying only on the wantedBufferAhead option)" : + `Buffering at most around ${maxVideoBufferSize} kilobyte(s) on the video buffer` + } +
  • -
    - - - onMaxBufferAheadInput(evt.target.value)} - value={maxBufferAhead} - disabled={isMaxBufferAHeadLimited === false} - className="optionInput" - /> -
    + { + setMaxBufferAheadStr(String(defaultMaxBufferAhead)); + setMaxBufferAheadLimit(defaultMaxBufferAhead !== Infinity); + }} + /> Do not limit + + { + maxBufferAhead === Infinity || + !isMaxBufferAheadLimited ? + "Not manually cleaning buffer far ahead of the current position" : + `Manually cleaning data ${maxBufferAhead} second(s) ahead of the current position` + } +
  • -
    - - - onMaxBufferBehindInput(evt.target.value)} - value={maxBufferBehind} - disabled={isMaxBufferBehindLimited === false} - className="optionInput" - /> -
    + { + setMaxBufferBehindStr(String(defaultMaxBufferBehind)); + setMaxBufferBehindLimit(defaultMaxBufferBehind !== Infinity); + }} + /> Do not limit + + { + maxBufferBehind === Infinity || + !isMaxBufferBehindLimited ? + "Not manually cleaning buffer behind the current position" : + `Manually cleaning data ${maxBufferBehind} second(s) behind the current position` + } +
  • ); diff --git a/demo/full/scripts/components/Options/NetworkConfig.jsx b/demo/full/scripts/components/Options/NetworkConfig.jsx index 63ced55fe3..11726df0a8 100644 --- a/demo/full/scripts/components/Options/NetworkConfig.jsx +++ b/demo/full/scripts/components/Options/NetworkConfig.jsx @@ -1,99 +1,220 @@ -import React, { Fragment, useState } from "react"; - +import React, { Fragment, useCallback, useEffect, useState } from "react"; import getCheckBoxValue from "../../lib/getCheckboxValue"; -import Button from "../Button"; import Checkbox from "../CheckBox"; import DEFAULT_VALUES from "../../lib/defaultOptionsValues"; +import PlayerOptionNumberInput from "./PlayerOptionNumberInput"; + +const defaultSegmentRetry = + DEFAULT_VALUES.loadVideo.networkConfig.segmentRetry; +const defaultSegmentRequestTimeout = + DEFAULT_VALUES.loadVideo.networkConfig.segmentRequestTimeout; +const defaultManifestRetry = + DEFAULT_VALUES.loadVideo.networkConfig.manifestRetry; +const defaultManifestRequestTimeout = + DEFAULT_VALUES.loadVideo.networkConfig.manifestRequestTimeout; +const defaultOfflineRetry = + DEFAULT_VALUES.loadVideo.networkConfig.offlineRetry; /** * @param {Object} props * @returns {Object} */ -function NetworkConfig({ - segmentRetry, +function RequestConfig({ + manifestRequestTimeout, manifestRetry, offlineRetry, - onSegmentRetryInput, - onManifestRetryInput, - onOfflineRetryInput, + onManifestRequestTimeoutChange, + onManifestRetryChange, + onOfflineRetryChange, + onSegmentRequestTimeoutChange, + onSegmentRetryChange, + segmentRequestTimeout, + segmentRetry, }) { + /* Value of the `segmentRetry` input */ + const [segmentRetryStr, setSegmentRetryStr] = useState( + segmentRetry + ); + /* Value of the `segmentRequestTimeout` input */ + const [segmentRequestTimeoutStr, setSegmentRequestTimeoutStr] = useState( + segmentRequestTimeout + ); + /* Value of the `manifestRetry` input */ + const [manifestRetryStr, setManifestRetryStr] = useState( + manifestRetry + ); + /* Value of the `offlineRetry` input */ + const [offlineRetryStr, setOfflineRetryStr] = useState( + offlineRetry + ); + /* Value of the `manifestRequestTimeout` input */ + const [ + manifestRequestTimeoutStr, + setManifestRequestTimeoutStr + ] = useState( + manifestRequestTimeout + ); + /* + * Keep track of the "limit segmentRetry" toggle: + * `false` == checkbox enabled + */ const [isSegmentRetryLimited, setSegmentRetryLimit] = useState( segmentRetry !== Infinity ); + /* + * Keep track of the "limit segmentRequestTimeout" toggle: + * `false` == checkbox enabled + */ + const [ + isSegmentRequestTimeoutLimited, + setSegmentRequestTimeoutLimit + ] = useState( + segmentRequestTimeout !== -1 + ); + /* + * Keep track of the "limit manifestRetry" toggle: + * `false` == checkbox enabled + */ const [isManifestRetryLimited, setManifestRetryLimit] = useState( manifestRetry !== Infinity ); + /* + * Keep track of the "limit offlineRetry" toggle: + * `false` == checkbox enabled + */ const [isOfflineRetryLimited, setOfflineRetryLimit] = useState( offlineRetry !== Infinity ); + /* + * Keep track of the "limit manifestRequestTimeout" toggle: + * `false` == checkbox enabled + */ + const [ + isManifestRequestTimeoutLimited, + setManifestRequestTimeoutLimit + ] = useState( + manifestRequestTimeout !== -1 + ); - const onChangeLimitSegmentRetry = (evt) => { + // Update manifestRequestTimeout when its linked text change + useEffect(() => { + // Note that this unnecessarily also run on first render - there seem to be + // no quick and easy way to disable this in react. + // This is not too problematic so I put up with it. + let newVal = parseFloat(manifestRequestTimeoutStr); + newVal = isNaN(newVal) ? + defaultManifestRequestTimeout : + newVal; + onManifestRequestTimeoutChange(newVal); + }, [manifestRequestTimeoutStr]); + + // Update manifestRetry when its linked text change + useEffect(() => { + let newVal = parseFloat(manifestRetryStr); + newVal = isNaN(newVal) ? + defaultManifestRetry : + newVal; + onManifestRetryChange(newVal); + }, [manifestRetryStr]); + + // Update segmentRequestTimeout when its linked text change + useEffect(() => { + let newVal = parseFloat(segmentRequestTimeoutStr); + newVal = isNaN(newVal) ? + defaultSegmentRequestTimeout : + newVal; + onSegmentRequestTimeoutChange(newVal); + }, [segmentRequestTimeoutStr]); + + // Update segmentRetry when its linked text change + useEffect(() => { + let newVal = parseFloat(segmentRetryStr); + newVal = isNaN(newVal) ? + defaultSegmentRetry : + newVal; + onSegmentRetryChange(newVal); + }, [segmentRetryStr]); + + // Update offlineRetry when its linked text change + useEffect(() => { + let newVal = parseFloat(offlineRetryStr); + newVal = isNaN(newVal) ? + defaultOfflineRetry : + newVal; + onOfflineRetryChange(newVal); + }, [offlineRetryStr]); + + const onChangeLimitSegmentRetry = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { setSegmentRetryLimit(false); - onSegmentRetryInput("Infinity"); + setSegmentRetryStr(String(Infinity)); } else { setSegmentRetryLimit(true); - onSegmentRetryInput(DEFAULT_VALUES.segmentRetry); + setSegmentRetryStr(String(defaultSegmentRetry)); } - }; + }, []); - const onChangeLimitManifestRetry = (evt) => { + const onChangeLimitSegmentRequestTimeout = useCallback((evt) => { + const isNotLimited = getCheckBoxValue(evt.target); + if (isNotLimited) { + setSegmentRequestTimeoutLimit(false); + setSegmentRequestTimeoutStr(String(-1)); + } else { + setSegmentRequestTimeoutLimit(true); + setSegmentRequestTimeoutStr(String(defaultSegmentRequestTimeout)); + } + }, []); + + const onChangeLimitManifestRetry = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { setManifestRetryLimit(false); - onManifestRetryInput(Infinity); + setManifestRetryStr(String(Infinity)); } else { setManifestRetryLimit(true); - onManifestRetryInput(DEFAULT_VALUES.manifestRetry); + setManifestRetryStr(String(defaultManifestRetry)); } - }; + }, []); - const onChangeLimitOfflineRetry = (evt) => { + const onChangeLimitOfflineRetry = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { setOfflineRetryLimit(false); - onOfflineRetryInput(Infinity); + setOfflineRetryStr(String(Infinity)); } else { setOfflineRetryLimit(true); - onOfflineRetryInput(DEFAULT_VALUES.offlineRetry); + setOfflineRetryStr(String(defaultOfflineRetry)); } - }; + }, []); + + const onChangeLimitManifestRequestTimeout = useCallback((evt) => { + const isNotLimited = getCheckBoxValue(evt.target); + if (isNotLimited) { + setManifestRequestTimeoutLimit(false); + setManifestRequestTimeoutStr(String(-1)); + } else { + setManifestRequestTimeoutLimit(true); + setManifestRequestTimeoutStr(String(defaultManifestRequestTimeout)); + } + }, []); return (
  • -
    - - - onSegmentRetryInput(evt.target.value)} - value={segmentRetry} - disabled={isSegmentRetryLimited === false} - className="optionInput" - /> -
    + { + setSegmentRetryStr(String(defaultSegmentRetry)); + setSegmentRetryLimit(defaultSegmentRetry !== Infinity); + }} + /> Do not limit + + {segmentRetry === Infinity || !isSegmentRetryLimited ? + "Retry \"retryable\" segment requests with no limit" : + `Retry "retryable" segment requests at most ${segmentRetry} time(s)`} +
  • +
  • -
    - - - onManifestRetryInput(evt.target.value)} - value={manifestRetry} - disabled={isManifestRetryLimited === false} - className="optionInput" - /> -
    + { + setSegmentRequestTimeoutStr(String(defaultSegmentRequestTimeout)); + setSegmentRequestTimeoutLimit( + defaultSegmentRequestTimeout !== Infinity + ); + }} + /> + + Do not limit + + + {segmentRequestTimeout === -1 || !isSegmentRequestTimeoutLimited ? + "Perform segment requests without timeout" : + `Stop segment requests after ${segmentRequestTimeout} millisecond(s)`} + +
  • + +
  • + { + setManifestRetryStr(String(defaultManifestRetry)); + setManifestRetryLimit(defaultManifestRetry !== Infinity); + }} + /> Do not limit + + {manifestRetry === Infinity || !isManifestRetryLimited ? + "Retry \"retryable\" manifest requests with no limit" : + `Retry "retryable" manifest requests at most ${manifestRetry} time(s)`} +
  • -
    - - - onOfflineRetryInput(evt.target.value)} - value={offlineRetry} - disabled={isOfflineRetryLimited === false} - /> -
    + { + setOfflineRetryStr(String(defaultOfflineRetry)); + setOfflineRetryLimit(defaultOfflineRetry !== Infinity); + }} + /> Do not limit + + {offlineRetry === Infinity || !isOfflineRetryLimited ? + "Retry \"retryable\" requests when offline with no limit" : + `Retry "retryable" requests when offline at most ${offlineRetry} time(s)`} + +
  • + +
  • + { + setManifestRequestTimeoutStr(String(defaultManifestRequestTimeout)); + setManifestRequestTimeoutLimit( + defaultManifestRequestTimeout !== Infinity + ); + }} + /> + + Do not limit + + + {manifestRequestTimeout === -1 || !isManifestRequestTimeoutLimited ? + "Perform manifest requests without timeout" : + `Stop manifest requests after ${manifestRequestTimeout} millisecond(s)`} +
  • ); } -export default React.memo(NetworkConfig); +export default React.memo(RequestConfig); diff --git a/demo/full/scripts/components/Options/Playback.jsx b/demo/full/scripts/components/Options/Playback.jsx index af5f64ed5e..4452857925 100644 --- a/demo/full/scripts/components/Options/Playback.jsx +++ b/demo/full/scripts/components/Options/Playback.jsx @@ -1,4 +1,5 @@ import React, { Fragment } from "react"; +import getCheckBoxValue from "../../lib/getCheckboxValue"; import Checkbox from "../CheckBox"; import Select from "../Select"; @@ -7,13 +8,28 @@ import Select from "../Select"; * @returns {Object} */ function TrackSwitch({ - onAutoPlayClick, autoPlay, - onManualBrSwitchingModeChange, - manualBrSwitchingMode, + manualBitrateSwitchingMode, + onAutoPlayChange, + onManualBitrateSwitchingModeChange, + onStopAtEndChange, stopAtEnd, - onStopAtEndClick, }) { + let manualBitrateSwitchingModeDesc; + switch (manualBitrateSwitchingMode) { + case "direct": + manualBitrateSwitchingModeDesc = + "Directly visible transition when a Representation is manually changed"; + break; + case "seamless": + manualBitrateSwitchingModeDesc = + "Smooth transition when a Representation is manually changed"; + break; + default: + manualBitrateSwitchingModeDesc = + "Unknown value"; + break; + } return (
  • @@ -22,21 +38,31 @@ function TrackSwitch({ name="autoPlay" ariaLabel="Auto play option" checked={autoPlay} - onChange={onAutoPlayClick} + onChange={(evt) => { + onAutoPlayChange(getCheckBoxValue(evt.target)); + }} > Auto Play + + {autoPlay ? + "Playing directly when the content is loaded." : + "Staying in pause when the content is loaded."} +
  • + + {manualBitrateSwitchingModeDesc} +
  • { + onStopAtEndChange(getCheckBoxValue(evt.target)); + }} > Stop At End + + {stopAtEnd ? + "Automatically stop when reaching the end of the content." : + "Don't stop when reaching the end of the content."} +
  • ); diff --git a/demo/full/scripts/components/Options/PlayerOptionNumberInput.jsx b/demo/full/scripts/components/Options/PlayerOptionNumberInput.jsx new file mode 100644 index 0000000000..68e813e371 --- /dev/null +++ b/demo/full/scripts/components/Options/PlayerOptionNumberInput.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import Button from "../Button"; + +/** + * Creates an input text box for a number-formatted player option. + * + * These are still text boxes (and not of type number), to allow typing special + * float values such as `Infinity`. + */ +function PlayerOptionNumberInput({ + ariaLabel, + label, + title, + onUpdateValue, + valueAsString, + defaultValueAsNumber, + isDisabled, + onResetClick, +}) { + return
    + + + onUpdateValue(evt.target.value)} + value={valueAsString} + disabled={isDisabled} + className="optionInput" + /> +
    ; +} + +export default PlayerOptionNumberInput; diff --git a/demo/full/scripts/components/Options/TrackSwitch.jsx b/demo/full/scripts/components/Options/TrackSwitch.jsx index ab8412feed..edeb315486 100644 --- a/demo/full/scripts/components/Options/TrackSwitch.jsx +++ b/demo/full/scripts/components/Options/TrackSwitch.jsx @@ -1,4 +1,5 @@ import React, { Fragment } from "react"; +import getCheckBoxValue from "../../lib/getCheckboxValue"; import Checkbox from "../CheckBox"; import Select from "../Select"; @@ -7,13 +8,48 @@ import Select from "../Select"; * @returns {Object} */ function NetworkConfig({ - enableFastSwitching, audioTrackSwitchingMode, - onCodecSwitch, - onEnableFastSwitchingClick, + enableFastSwitching, onAudioTrackSwitchingModeChange, + onCodecSwitch, onCodecSwitchChange, + onEnableFastSwitchingChange, }) { + let audioTrackSwitchingModeDescMsg; + switch (audioTrackSwitchingMode) { + case "reload": + audioTrackSwitchingModeDescMsg = + "Reloading when the audio track is changed"; + break; + case "direct": + audioTrackSwitchingModeDescMsg = + "Directly audible transition when the audio track is changed"; + break; + case "seamless": + audioTrackSwitchingModeDescMsg = + "Smooth transition when the audio track is changed"; + break; + default: + audioTrackSwitchingModeDescMsg = + "Unknown value"; + break; + } + + let onCodecSwitchDescMsg; + switch (onCodecSwitch) { + case "reload": + onCodecSwitchDescMsg = "Reloading buffers when the codec changes"; + break; + case "continue": + onCodecSwitchDescMsg = + "Keeping the same buffers even when the codec changes"; + break; + default: + onCodecSwitchDescMsg = + "Unknown value"; + break; + } + return (
  • @@ -23,10 +59,17 @@ function NetworkConfig({ name="fastSwitching" id="fastSwitching" checked={enableFastSwitching} - onChange={onEnableFastSwitchingClick} + onChange={(evt) => { + onEnableFastSwitchingChange(getCheckBoxValue(evt.target)); + }} > Fast Switching + + {enableFastSwitching ? + "Fast quality switch by replacing lower qualities in the buffer by higher ones when possible." : + "Not replacing lower qualities in the buffer by an higher one when possible."} +
  • + + {audioTrackSwitchingModeDescMsg} +
  • + + {onCodecSwitchDescMsg} +
  • ); diff --git a/demo/full/scripts/components/Options/VideoAdaptiveSettings.jsx b/demo/full/scripts/components/Options/VideoAdaptiveSettings.jsx index 6d3107ca21..76d000bc1a 100644 --- a/demo/full/scripts/components/Options/VideoAdaptiveSettings.jsx +++ b/demo/full/scripts/components/Options/VideoAdaptiveSettings.jsx @@ -1,191 +1,243 @@ -import React, { Fragment, useState } from "react"; - +import React, { Fragment, useCallback, useEffect, useState } from "react"; import getCheckBoxValue from "../../lib/getCheckboxValue"; import Checkbox from "../../components/CheckBox"; -import Button from "../Button"; import DEFAULT_VALUES from "../../lib/defaultOptionsValues"; +import PlayerOptionNumberInput from "./PlayerOptionNumberInput"; + +const defaultInitialVideoBitrate = DEFAULT_VALUES.player.initialVideoBitrate; +const defaultMinVideoBitrate = DEFAULT_VALUES.player.minVideoBitrate; +const defaultMaxVideoBitrate = DEFAULT_VALUES.player.maxVideoBitrate; /** * @param {Object} props * @returns {Object} */ function VideoAdaptiveSettings({ - initialVideoBr, - minVideoBr, - maxVideoBr, - onInitialVideoBrInput, - onMinVideoBrInput, - onMaxVideoBrInput, + initialVideoBitrate, + minVideoBitrate, + maxVideoBitrate, + onInitialVideoBitrateChange, + onMinVideoBitrateChange, + onMaxVideoBitrateChange, limitVideoWidth, throttleVideoBitrateWhenHidden, - onLimitVideoWidthClick, - onThrottleVideoBitrateWhenHiddenClick, + onLimitVideoWidthChange, + onThrottleVideoBitrateWhenHiddenChange, }) { - const [isMinVideoBrLimited, setMinVideoBrLimit] = useState(minVideoBr !== 0); - const [isMaxVideoBrLimited, setMaxVideoBrLimit] = useState( - maxVideoBr !== Infinity + /* Value of the `initialVideoBitrate` input */ + const [initialVideoBitrateStr, setInitialVideoBitrateStr] = useState( + initialVideoBitrate + ); + /* Value of the `minVideoBitrate` input */ + const [minVideoBitrateStr, setMinVideoBitrateStr] = useState( + minVideoBitrate + ); + /* Value of the `maxVideoBitrate` input */ + const [maxVideoBitrateStr, setMaxVideoBitrateStr] = useState( + maxVideoBitrate + ); + /* + * Keep track of the "limit minVideoBitrate" toggle: + * `false` == checkbox enabled + */ + const [isMinVideoBitrateLimited, setMinVideoBitrateLimit] = useState( + minVideoBitrate !== 0 ); + /* + * Keep track of the "limit maxVideoBitrate" toggle: + * `false` == checkbox enabled + */ + const [isMaxVideoBitrateLimited, setMaxVideoBitrateLimit] = useState( + maxVideoBitrate !== Infinity + ); + + // Update initialVideoBitrate when its linked text change + useEffect(() => { + // Note that this unnecessarily also run on first render - there seem to be + // no quick and easy way to disable this in react. + // This is not too problematic so I put up with it. + let newBitrate = parseFloat(initialVideoBitrateStr); + newBitrate = isNaN(newBitrate) ? + defaultInitialVideoBitrate : + newBitrate; + onInitialVideoBitrateChange(newBitrate); + }, [initialVideoBitrateStr]); + + // Update minVideoBitrate when its linked text change + useEffect(() => { + let newBitrate = parseFloat(minVideoBitrateStr); + newBitrate = isNaN(newBitrate) ? + defaultMinVideoBitrate : + newBitrate; + onMinVideoBitrateChange(newBitrate); + }, [minVideoBitrateStr]); + + // Update maxVideoBitrate when its linked text change + useEffect(() => { + let newBitrate = parseFloat(maxVideoBitrateStr); + newBitrate = isNaN(newBitrate) ? + defaultMaxVideoBitrate : + newBitrate; + onMaxVideoBitrateChange(newBitrate); + }, [maxVideoBitrateStr]); - const onChangeLimitMinVideoBr = (evt) => { + const onChangeLimitMinVideoBitrate = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { - setMinVideoBrLimit(false); - onMinVideoBrInput(0); + setMinVideoBitrateLimit(false); + setMinVideoBitrateStr(String(0)); } else { - setMinVideoBrLimit(true); - onMinVideoBrInput(DEFAULT_VALUES.minVideoBr); + setMinVideoBitrateLimit(true); + setMinVideoBitrateStr(String(defaultMinVideoBitrate)); } - }; + }, []); - const onChangeLimitMaxVideoBr = (evt) => { + const onChangeLimitMaxVideoBitrate = useCallback((evt) => { const isNotLimited = getCheckBoxValue(evt.target); if (isNotLimited) { - setMaxVideoBrLimit(false); - onMaxVideoBrInput(Infinity); + setMaxVideoBitrateLimit(false); + setMaxVideoBitrateStr(String(Infinity)); } else { - setMaxVideoBrLimit(true); - onMaxVideoBrInput(DEFAULT_VALUES.maxVideoBr); + setMaxVideoBitrateLimit(true); + setMaxVideoBitrateStr(String(defaultMaxVideoBitrate)); } - }; + }, []); return (
  • -
    - - - onInitialVideoBrInput(evt.target.value)} - value={initialVideoBr} - className="optionInput" - /> -
    + { + setInitialVideoBitrateStr(String( + defaultInitialVideoBitrate + )); + }} + /> + + { + initialVideoBitrate === 0 ? + "Starts loading the lowest video bitrate" : + `Starts with a video bandwidth estimate of ${initialVideoBitrate}` + + " bits per seconds." + } +
  • -
    - - - onMinVideoBrInput(evt.target.value)} - value={minVideoBr} - disabled={isMinVideoBrLimited === false} - className="optionInput" - /> -
    + { + setMinVideoBitrateStr(String(defaultMinVideoBitrate)); + setMinVideoBitrateLimit(defaultMinVideoBitrate !== 0); + }} + /> Do not limit + + { + !isMinVideoBitrateLimited || minVideoBitrate <= 0 ? + "Not limiting the lowest video bitrate reachable through the adaptive logic" : + "Limiting the lowest video bitrate reachable through the adaptive " + + `logic to ${minVideoBitrate} bits per seconds` + } +
  • -
    - - - onMaxVideoBrInput(evt.target.value)} - value={maxVideoBr} - disabled={isMaxVideoBrLimited === false} - className="optionInput" - /> -
    + { + setMaxVideoBitrateStr(String(defaultMaxVideoBitrate)); + setMaxVideoBitrateLimit(defaultMaxVideoBitrate !== Infinity); + }} + />
    Do not limit
    + + { + !isMaxVideoBitrateLimited || + parseFloat(maxVideoBitrate) === Infinity ? + "Not limiting the highest video bitrate reachable through the adaptive logic" : + "Limiting the highest video bitrate reachable through the adaptive " + + `logic to ${maxVideoBitrate} bits per seconds` + } +
  • - - Limit Video Width - +
    + { + onLimitVideoWidthChange(getCheckBoxValue(evt.target)); + }} + > + Limit Video Width + + + {limitVideoWidth ? + "Limiting video width to the current +
  • - - Throttle Video Bitrate When Hidden - +
    + { + onThrottleVideoBitrateWhenHiddenChange( + getCheckBoxValue(evt.target) + ); + }} + > + Throttle Video Bitrate When Hidden + + + {throttleVideoBitrateWhenHidden ? + "Throttling the video bitrate when the page is hidden for a time" : + "Not throttling the video bitrate when the page is hidden for a time"} + +
  • ); diff --git a/demo/full/scripts/controllers/LogDisplayer.jsx b/demo/full/scripts/controllers/LogDisplayer.jsx deleted file mode 100644 index 1a8c481907..0000000000 --- a/demo/full/scripts/controllers/LogDisplayer.jsx +++ /dev/null @@ -1,258 +0,0 @@ -import React from "react"; -import { - filter, - skip, - Subject, - takeUntil, -} from "rxjs"; -import Button from "../components/Button.jsx"; - -const LogElement = ({ text, date }) => ( -
    - {date.toISOString() + " - " + text} -
    -); - -class LogDisplayer extends React.Component { - constructor(...args) { - super(...args); - this.state = { logs: [] }; - - // A weird React behavior obligates me to mutate a this._logs array instead - // of calling setState directly to allow multiple setState in a row before - // rendering. - // The case seen was that this.state.logs would not change right after - // setState, so the last addLog call would be the only one really considered - this._logs = []; - - // Only scroll to bottom if already scrolled to bottom - this.hasScrolledToBottom = true; - } - - addLog(text) { - this._logs = [...this._logs, { - text, - date: new Date(), - }]; - - this.setState({ logs: this._logs.slice() }); - } - - resetLogs() { - this._logs = []; - this.setState({ logs: [] }); - } - - componentDidMount() { - this.destructionSubject = new Subject(); - const { player } = this.props; - - player.$get("videoBitrateAuto").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(vbAuto => { - const text = "Video Bitrate selection changed to " + - (vbAuto ? "automatic" : "manual"); - this.addLog(text); - }); - - player.$get("audioBitrateAuto").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(abAuto => { - const text = "Audio Bitrate selection changed to " + - (abAuto ? "automatic" : "manual"); - this.addLog(text); - }); - - player.$get("videoBitrate").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(vb => { - const text = "Video Bitrate changed to " + vb; - this.addLog(text); - }); - - player.$get("audioBitrate").pipe( - takeUntil(this.destructionSubject), - skip(1), // skip initial value - ).subscribe(ab => { - const text = "Audio Bitrate changed to " + ab; - this.addLog(text); - }); - - player.$get("error").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - filter(x => x), - ).subscribe(error => { - const message = error.message ? error.message : error; - const text = "The player encountered a fatal Error: " + message; - this.addLog(text); - }); - - player.$get("isLoading").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - filter(x => x), - ).subscribe(() => { - const text = "A new content is Loading."; - this.addLog(text); - }); - - player.$get("hasCurrentContent").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - filter(x => x), - ).subscribe(() => { - const text = "The new content has been loaded."; - this.addLog(text); - }); - - player.$get("isStopped").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - filter(x => x), - ).subscribe(() => { - const text = "The current content is stopped"; - this.addLog(text); - }); - - player.$get("hasEnded").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - filter(x => x), - ).subscribe(() => { - const text = "The current content has ended"; - this.addLog(text); - }); - - player.$get("isBuffering").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe((ib) => { - const text = ib ? - "The current content is buffering" : - "The current content is not buffering anymore"; - this.addLog(text); - }); - - player.$get("isSeeking").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe((ib) => { - const text = ib ? - "The current content is seeking" : - "The current content is not seeking anymore"; - this.addLog(text); - }); - - player.$get("availableLanguages").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(() => { - const text = "The audio track list has changed"; - this.addLog(text); - }); - - player.$get("availableSubtitles").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(() => { - const text = "The text track list has changed"; - this.addLog(text); - }); - - player.$get("availableVideoTracks").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(() => { - const text = "The video track list has changed"; - this.addLog(text); - }); - - player.$get("availableAudioBitrates").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(() => { - const text = "The audio bitrate list has changed"; - this.addLog(text); - }); - - player.$get("availableVideoBitrates").pipe( - skip(1), // skip initial value - takeUntil(this.destructionSubject), - ).subscribe(() => { - const text = "The video bitrate list has changed"; - this.addLog(text); - }); - - this.scrollToBottom(); - - const onScroll = () => { - if ( - this.element.scrollHeight - this.element.offsetHeight === - this.element.scrollTop - ) { - this.hasScrolledToBottom = true; - } else { - this.hasScrolledToBottom = false; - } - }; - - this.element.addEventListener("scroll", onScroll, { passive: true }); - this.destructionSubject.subscribe(() => - this.element.removeEventListener("scroll", onScroll)); - } - - scrollToBottom() { - if (this.hasScrolledToBottom) { - this.element.scrollTop = this.element.scrollHeight; - } - } - - componentDidUpdate() { - this.scrollToBottom(); - } - - componentWillUnmount() { - this.destructionSubject.next(); - this.destructionSubject.complete(); - } - - render() { - const { logs } = this.state; - - const logTexts = logs.map(({ text, date }, i) => - ); - - const clearLogs = () => this.resetLogs(); - return ( -
    -
    - Logs -
    -
    this.element = el} - > -
    -
    - ); - } -} - -export default React.memo(LogDisplayer); diff --git a/demo/full/scripts/controllers/Player.jsx b/demo/full/scripts/controllers/Player.jsx index 92f577bf14..e09408d2e4 100644 --- a/demo/full/scripts/controllers/Player.jsx +++ b/demo/full/scripts/controllers/Player.jsx @@ -4,165 +4,149 @@ import React, { useRef, useState, } from "react"; -import { - Subject, - takeUntil, -} from "rxjs"; import { createModule } from "../lib/vespertine.js"; import PlayerModule from "../modules/player"; import ControlBar from "./ControlBar.jsx"; import ContentList from "./ContentList.jsx"; import Settings from "./Settings.jsx"; import ErrorDisplayer from "./ErrorDisplayer.jsx"; -import LogDisplayer from "./LogDisplayer.jsx"; import ChartsManager from "./charts/index.jsx"; import PlayerKnobsSettings from "./PlayerKnobsSettings.jsx"; -import isEqual from "../lib/isEqual" +import defaultOptionsValues from "../lib/defaultOptionsValues.js"; // time in ms while seeking/loading/buffering after which the spinner is shown const SPINNER_TIMEOUT = 300; function Player() { - const [player, setPlayer] = useState(null); + const [playerModule, setPlayerModule] = useState(null); const [autoPlayBlocked, setAutoPlayBlocked] = useState(false); const [displaySpinner, setDisplaySpinner] = useState(false); const [displaySettings, setDisplaySettings] = useState(false); const [isStopped, setIsStopped] = useState(false); const [enableVideoThumbnails, setEnableVideoThumbnails] = useState(false); const [showOptions, setShowOptions] = useState(false); - - const initOptsRef = useRef(null); - const $destroySubjectRef = useRef(null); - const displaySpinnerTimeoutRef = useRef(0); + const [playerOpts, setPlayerOpts] = useState(defaultOptionsValues.player); + const [loadVideoOpts, setLoadVideoOpts] = useState( + defaultOptionsValues.loadVideo + ); + const [hasUpdatedPlayerOptions, setHasUpdatedPlayerOptions] = useState(false); + const displaySpinnerTimeoutRef = useRef(null); const videoElementRef = useRef(null); - const textTrackElement = useRef(null); - const playerWrapperElement = useRef(null); - const optionsComp = useRef(null); + const textTrackElementRef = useRef(null); + const playerWrapperElementRef = useRef(null); - const onOptionToggle = () => + const onOptionToggle = useCallback(() => { setShowOptions((prevState) => !prevState); + }, []); - const createNewPlayerModule = (videoContent) => { - const { initOpts } = optionsComp.current.getOptions(); - const playerMod = createModule(PlayerModule, { - videoElement: videoElementRef.current, - textTrackElement: textTrackElement.current, - ...initOpts - }); - initOptsRef.current = initOpts; - - $destroySubjectRef.current = new Subject(); - $destroySubjectRef.current.subscribe(() => playerMod.destroy()); - - // update isStopped and displaySpinner - playerMod.$get("autoPlayBlocked" , - "isSeeking", - "isBuffering", - "isLoading", - "isReloading", - "isStopped", - "videoTrackHasTrickMode", - "videoElement") - .pipe(takeUntil($destroySubjectRef.current)) - .subscribe(([ - newAutoPlayBlocked, - isSeeking, - isBuffering, - isLoading, - isReloading, - newIsStopped, - videoTrackHasTrickMode, - ]) => { - setAutoPlayBlocked(newAutoPlayBlocked); - setIsStopped(newIsStopped); - if (isLoading || isReloading) { - setDisplaySpinner(true); - } else if (isSeeking || isBuffering) { - if (displaySpinnerTimeoutRef.current) { - clearTimeout(displaySpinnerTimeoutRef.current); - } + const clearSpinner = useCallback(() => { + if (displaySpinnerTimeoutRef.current) { + clearTimeout(displaySpinnerTimeoutRef.current); + displaySpinnerTimeoutRef.current = null; + } + }, []); + + // Bind events on player module creation and destroy old when it changes + useEffect(() => { + if (playerModule === null) { + return; + } + function reCheckSpinner() { + const isSeeking = playerModule.get("isSeeking"); + const isBuffering = playerModule.get("isBuffering"); + const isLoading = playerModule.get("isLoading"); + const isReloading = playerModule.get("isReloading"); + if (isLoading || isReloading) { + // When loading/reloading: show spinner immediately + if (displaySpinnerTimeoutRef.current !== null) { + clearTimeout(displaySpinnerTimeoutRef.current); + displaySpinnerTimeoutRef.current = null; + } + setDisplaySpinner(true); + } else if (isSeeking || isBuffering) { + // When seeking/rebuffering: show spinner after a delay + if (displaySpinnerTimeoutRef.current === null) { displaySpinnerTimeoutRef.current = setTimeout(() => { setDisplaySpinner(true); }, SPINNER_TIMEOUT); - } else { - if (displaySpinnerTimeoutRef.current) { - clearTimeout(displaySpinnerTimeoutRef.current); - displaySpinnerTimeoutRef.current = 0; - } - setDisplaySpinner(false); - } - if (enableVideoThumbnails !== videoTrackHasTrickMode) { - setEnableVideoThumbnails(videoTrackHasTrickMode); } - }); - if (videoContent) { - const { loadVideoOpts } = optionsComp.current.getOptions(); - if (videoContent.lowLatencyMode) { - playerMod.dispatch("ENABLE_LIVE_CATCH_UP"); } else { - playerMod.dispatch("DISABLE_LIVE_CATCH_UP"); - } - playerMod.dispatch("SET_PLAYBACK_RATE", 1); - playerMod.dispatch("LOAD", { ...videoContent, - ...loadVideoOpts }); + if (displaySpinnerTimeoutRef.current !== null) { + clearTimeout(displaySpinnerTimeoutRef.current); + displaySpinnerTimeoutRef.current = null; + } + setDisplaySpinner(false); } - setPlayer(playerMod); - } - - const cleanCurrentPlayer = () => { - if ($destroySubjectRef.current) { - $destroySubjectRef.current.next(); - $destroySubjectRef.current.complete(); } - if (displaySpinnerTimeoutRef.current) { - clearTimeout(displaySpinnerTimeoutRef.current); - } - setPlayer(null); - } - useEffect(() => { - createNewPlayerModule(); + playerModule.addStateListener("autoPlayBlocked", (newAutoPlayBlocked) => { + setAutoPlayBlocked(newAutoPlayBlocked); + }); + playerModule.addStateListener("isStopped", (newIsStopped) => { + setIsStopped(newIsStopped); + }); + playerModule.addStateListener("videoTrackHasTrickMode", (videoTrackHasTrickMode) => { + setEnableVideoThumbnails(videoTrackHasTrickMode); + }); + + playerModule.addStateListener("isSeeking", reCheckSpinner); + playerModule.addStateListener("isBuffering", reCheckSpinner); + playerModule.addStateListener("isLoading", reCheckSpinner); + playerModule.addStateListener("isReloading", reCheckSpinner); + reCheckSpinner(); return () => { - cleanCurrentPlayer(); - } - }, []); + playerModule.destroy(); + clearSpinner(); + }; + }, [playerModule]); + + const createNewPlayerModule = useCallback(() => { + const playerMod = createModule( + PlayerModule, + Object.assign( + {}, + { + videoElement: videoElementRef.current, + textTrackElement: textTrackElementRef.current, + }, + playerOpts + ) + ); + setPlayerModule(playerMod); + return playerMod; + }, [playerOpts]); const onVideoClick = useCallback(() => { - const { isPaused, isContentLoaded } = player.get(); + const { isPaused, isContentLoaded } = playerModule.get(); if (!isContentLoaded) { return; } if (isPaused) { - player.dispatch("PLAY"); + playerModule.dispatch("PLAY"); } else { - player.dispatch("DISABLE_LIVE_CATCH_UP"); - player.dispatch("PAUSE"); + playerModule.dispatch("DISABLE_LIVE_CATCH_UP"); + playerModule.dispatch("PAUSE"); } - }, [player]); - - const loadVideo = useCallback((video) => { - const { initOpts: newInitOpts, loadVideoOpts } = optionsComp.current.getOptions(); - if (!isEqual(initOptsRef.current, newInitOpts)) { - initOptsRef.current = newInitOpts; - cleanCurrentPlayer(); - createNewPlayerModule(video); - } else { - if (video.lowLatencyMode) { - player.dispatch("ENABLE_LIVE_CATCH_UP"); - } else { - player.dispatch("DISABLE_LIVE_CATCH_UP"); - } - player.dispatch("SET_PLAYBACK_RATE", 1); - player.dispatch("LOAD", { ...video, - ...loadVideoOpts }); - } - }, [player]); + }, [playerModule]); + const startContent = useCallback((contentInfo) => { + let playerMod = playerModule; + if (playerMod === null || hasUpdatedPlayerOptions) { + setHasUpdatedPlayerOptions(false); + playerMod = createNewPlayerModule(); + } + loadContent(playerMod, contentInfo, loadVideoOpts); + }, [ + playerModule, + hasUpdatedPlayerOptions, + createNewPlayerModule, + loadVideoOpts, + ]); - const stopVideo = useCallback(() => player.dispatch("STOP"), [player]); + const stopVideo = useCallback(() => playerModule.dispatch("STOP"), [playerModule]); const closeSettings = useCallback(() => { setDisplaySettings(false); @@ -176,22 +160,28 @@ function Player() {
    - +
    onVideoClick()} > - { player ? : null } + { playerModule ? : null } { autoPlayBlocked ?
    @@ -212,36 +202,51 @@ function Player() { }
    - { player ? + { + playerModule ? : null }
    { - player ? + playerModule ? : null }
    - - {player ? : null} +
    ); + + function updatePlayerOptions(fn) { + setHasUpdatedPlayerOptions(true); + setPlayerOpts(fn); + } +} + +function loadContent(playerModule, contentInfo, loadVideoOpts) { + if (contentInfo.lowLatencyMode) { + playerModule.dispatch("ENABLE_LIVE_CATCH_UP"); + } else { + playerModule.dispatch("DISABLE_LIVE_CATCH_UP"); + } + playerModule.dispatch("SET_PLAYBACK_RATE", 1); + playerModule.dispatch("LOAD", Object.assign({}, contentInfo, loadVideoOpts)); } export default Player; diff --git a/demo/full/scripts/controllers/Settings.jsx b/demo/full/scripts/controllers/Settings.jsx index c0bcba8457..31cb19a48a 100644 --- a/demo/full/scripts/controllers/Settings.jsx +++ b/demo/full/scripts/controllers/Settings.jsx @@ -1,7 +1,4 @@ -import React from "react"; - -import getCheckBoxValue from "../lib/getCheckboxValue"; - +import React, {useCallback} from "react"; import Option from "../components/Options/Option"; import Playback from "../components/Options/Playback.jsx"; import AudioAdaptiveSettings from "../components/Options/AudioAdaptiveSettings"; @@ -10,247 +7,385 @@ import NetworkConfig from "../components/Options/NetworkConfig.jsx"; import TrackSwitch from "../components/Options/TrackSwitch.jsx"; import BufferOptions from "../components/Options/BufferOptions.jsx"; -import defaultOptionsValues from "../lib/defaultOptionsValues"; - -class Settings extends React.Component { - constructor(...args) { - super(...args); - - this.state = { - ...defaultOptionsValues, - }; - } - - getOptions() { - const { - initialVideoBr, - initialAudioBr, - minVideoBr, - minAudioBr, - maxVideoBr, - maxAudioBr, - wantedBufferAhead, - maxVideoBufferSize, - maxBufferAhead, - maxBufferBehind, - limitVideoWidth, - throttleVideoBitrateWhenHidden, - stopAtEnd, - autoPlay, - audioTrackSwitchingMode, - manualBrSwitchingMode, - onCodecSwitch, - enableFastSwitching, - segmentRetry, - manifestRetry, - offlineRetry, - } = this.state; - return { - initOpts: { - initialVideoBitrate: parseFloat(initialVideoBr), - initialAudioBitrate: parseFloat(initialAudioBr), - minVideoBitrate: parseFloat(minVideoBr), - minAudioBitrate: parseFloat(minAudioBr), - maxVideoBitrate: parseFloat(maxVideoBr), - maxAudioBitrate: parseFloat(maxAudioBr), - wantedBufferAhead: parseFloat(wantedBufferAhead), - maxVideoBufferSize: parseFloat(maxVideoBufferSize), - maxBufferAhead: parseFloat(maxBufferAhead), - maxBufferBehind: parseFloat(maxBufferBehind), - limitVideoWidth, - throttleVideoBitrateWhenHidden, - stopAtEnd, - }, - loadVideoOpts: { - autoPlay, - audioTrackSwitchingMode, - manualBitrateSwitchingMode: manualBrSwitchingMode, - onCodecSwitch, - enableFastSwitching, - networkConfig: { - segmentRetry: parseFloat(segmentRetry), - manifestRetry: parseFloat(manifestRetry), - offlineRetry: parseFloat(offlineRetry), - }, - }, - }; - } - - onAutoPlayClick = (evt) => - this.setState({ autoPlay: getCheckBoxValue(evt.target) }); - - onManualBrSwitchingModeChange = (value) => - this.setState({ manualBrSwitchingMode: value }); - - onInitialVideoBrInput = (value) => - this.setState({ initialVideoBr: value }); - - onInitialAudioBrInput = (value) => - this.setState({ initialAudioBr: value }); - - onMinVideoBrInput = (value) => - this.setState({ minVideoBr: value }); - - onMinAudioBrInput = (value) => - this.setState({ minAudioBr: value }); - - onMaxVideoBrInput = (value) => - this.setState({ maxVideoBr: value }); - - onMaxAudioBrInput = (value) => - this.setState({ maxAudioBr: value }); - - onLimitVideoWidthClick = (evt) => - this.setState({ limitVideoWidth: getCheckBoxValue(evt.target) }); - - onThrottleVideoBitrateWhenHiddenClick = (evt) => - this.setState({ - throttleVideoBitrateWhenHidden: getCheckBoxValue(evt.target), +function Settings({ + playerOptions, + updatePlayerOptions, + loadVideoOptions, + updateLoadVideoOptions, + showOptions, +}) { + const { + initialAudioBitrate, + initialVideoBitrate, + limitVideoWidth, + maxAudioBitrate, + maxBufferAhead, + maxBufferBehind, + maxVideoBitrate, + maxVideoBufferSize, + minAudioBitrate, + minVideoBitrate, + stopAtEnd, + throttleVideoBitrateWhenHidden, + wantedBufferAhead, + } = playerOptions; + const { + audioTrackSwitchingMode, + autoPlay, + enableFastSwitching, + manualBitrateSwitchingMode, + networkConfig, + onCodecSwitch, + } = loadVideoOptions; + const { + segmentRetry, + segmentRequestTimeout, + manifestRetry, + manifestRequestTimeout, + offlineRetry, + } = networkConfig; + + const onAutoPlayChange = useCallback((autoPlay) => { + updateLoadVideoOptions((prevOptions) => { + if (autoPlay === prevOptions.autoPlay) { + return prevOptions; + } + return Object.assign({}, prevOptions, { autoPlay }); }); + }, [updateLoadVideoOptions]); + + const onManualBitrateSwitchingModeChange = useCallback((value) => { + updateLoadVideoOptions((prevOptions) => { + if (value === prevOptions.manualBitrateSwitchingMode) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + manualBitrateSwitchingMode: value, + }); + }); + }, [updateLoadVideoOptions]); + + const onStopAtEndChange = useCallback((stopAtEnd) => { + updatePlayerOptions((prevOptions) => { + if (stopAtEnd === prevOptions.stopAtEnd) { + return prevOptions; + } + return Object.assign({}, prevOptions, { stopAtEnd }); + }); + }, [updatePlayerOptions]); + + const onInitialVideoBitrateChange = useCallback((initialVideoBitrate) => { + updatePlayerOptions((prevOptions) => { + if (initialVideoBitrate === prevOptions.initialVideoBitrate) { + return prevOptions; + } + return Object.assign({}, prevOptions, { initialVideoBitrate }); + }); + }, [updatePlayerOptions]); + + const onInitialAudioBitrateChange = useCallback((initialAudioBitrate) => { + updatePlayerOptions((prevOptions) => { + if (initialAudioBitrate === prevOptions.initialAudioBitrate) { + return prevOptions; + } + return Object.assign({}, prevOptions, { initialAudioBitrate }); + }); + }, [updatePlayerOptions]); + + const onMinVideoBitrateChange = useCallback((minVideoBitrate) => { + updatePlayerOptions((prevOptions) => { + if (minVideoBitrate === prevOptions.minVideoBitrate) { + return prevOptions; + } + return Object.assign({}, prevOptions, { minVideoBitrate }); + }); + }, [updatePlayerOptions]); + + const onMinAudioBitrateChange = useCallback((minAudioBitrate) => { + updatePlayerOptions((prevOptions) => { + if (minAudioBitrate === prevOptions.minAudioBitrate) { + return prevOptions; + } + return Object.assign({}, prevOptions, { minAudioBitrate }); + }); + }, [updatePlayerOptions]); + + const onMaxVideoBitrateChange = useCallback((maxVideoBitrate) => { + updatePlayerOptions((prevOptions) => { + if (maxVideoBitrate === prevOptions.maxVideoBitrate) { + return prevOptions; + } + return Object.assign({}, prevOptions, { maxVideoBitrate }); + }); + }, [updatePlayerOptions]); + + const onMaxAudioBitrateChange = useCallback((maxAudioBitrate) => { + updatePlayerOptions((prevOptions) => { + if (maxAudioBitrate === prevOptions.maxAudioBitrate) { + return prevOptions; + } + return Object.assign({}, prevOptions, { maxAudioBitrate }); + }); + }, [updatePlayerOptions]); + + const onLimitVideoWidthChange = useCallback((limitVideoWidth) => { + updatePlayerOptions((prevOptions) => { + if (limitVideoWidth === prevOptions.limitVideoWidth) { + return prevOptions; + } + return Object.assign({}, prevOptions, { limitVideoWidth }); + }); + }, [updatePlayerOptions]); + + const onThrottleVideoBitrateWhenHiddenChange = useCallback((value) => { + updatePlayerOptions((prevOptions) => { + if (value === prevOptions.throttleVideoBitrateWhenHidden) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + throttleVideoBitrateWhenHidden: value, + }); + }); + }, [updatePlayerOptions]); + + const onSegmentRetryChange = useCallback((segmentRetry) => { + updateLoadVideoOptions((prevOptions) => { + if (segmentRetry === prevOptions.networkConfig.segmentRetry) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + networkConfig: Object.assign({}, prevOptions.networkConfig, { + segmentRetry, + }), + }); + }); + }, [updateLoadVideoOptions]); + + const onSegmentRequestTimeoutChange = useCallback((segmentRequestTimeout) => { + updateLoadVideoOptions((prevOptions) => { + if ( + segmentRequestTimeout === + prevOptions.networkConfig.segmentRequestTimeout + ) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + networkConfig: Object.assign({}, prevOptions.networkConfig, { + segmentRequestTimeout, + }), + }); + }); + }, [updateLoadVideoOptions]); + + const onManifestRetryChange = useCallback((manifestRetry) => { + updateLoadVideoOptions((prevOptions) => { + if (manifestRetry === prevOptions.networkConfig.manifestRetry) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + networkConfig: Object.assign({}, prevOptions.networkConfig, { + manifestRetry, + }), + }); + }); + }, [updateLoadVideoOptions]); + + const onOfflineRetryChange = useCallback((offlineRetry) => { + updateLoadVideoOptions((prevOptions) => { + if (offlineRetry === prevOptions.networkConfig.offlineRetry) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + networkConfig: Object.assign({}, prevOptions.networkConfig, { + offlineRetry, + }), + }); + }); + }, [updateLoadVideoOptions]); + + const onManifestRequestTimeoutChange = useCallback( + (manifestRequestTimeout) => { + updateLoadVideoOptions((prevOptions) => { + if ( + manifestRequestTimeout === + prevOptions.networkConfig.manifestRequestTimeout + ) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + networkConfig: Object.assign({}, prevOptions.networkConfig, { + manifestRequestTimeout, + }), + }); + }); + }, + [updateLoadVideoOptions] + ); + + const onEnableFastSwitchingChange = useCallback((enableFastSwitching) => { + updateLoadVideoOptions((prevOptions) => { + if (enableFastSwitching === prevOptions.enableFastSwitching) { + return prevOptions; + } + return Object.assign({}, prevOptions, { enableFastSwitching }); + }); + }, [updateLoadVideoOptions]); + + const onAudioTrackSwitchingModeChange = useCallback((value) => { + updateLoadVideoOptions((prevOptions) => { + if (value === prevOptions.audioTrackSwitchingMode) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + audioTrackSwitchingMode: value, + }); + }); + }, [updateLoadVideoOptions]); + + const onCodecSwitchChange = useCallback((value) => { + updateLoadVideoOptions((prevOptions) => { + if (value === prevOptions.onCodecSwitch) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + onCodecSwitch: value, + }); + }); + }, [updateLoadVideoOptions]); + + const onWantedBufferAheadChange = useCallback((wantedBufferAhead) => { + updatePlayerOptions((prevOptions) => { + if (wantedBufferAhead === prevOptions.wantedBufferAhead) { + return prevOptions; + } + return Object.assign({}, prevOptions, { wantedBufferAhead }); + }); + }, [playerOptions]); + + const onMaxVideoBufferSizeChange = useCallback((maxVideoBufferSize) => { + updatePlayerOptions((prevOptions) => { + if (maxVideoBufferSize === prevOptions.maxVideoBufferSize) { + return prevOptions; + } + return Object.assign({}, prevOptions, { maxVideoBufferSize }); + }); + }, [playerOptions]); + + const onMaxBufferBehindChange = useCallback((maxBufferBehind) => { + updatePlayerOptions((prevOptions) => { + if (maxBufferBehind === prevOptions.maxBufferBehind) { + return prevOptions; + } + return Object.assign({}, prevOptions, { maxBufferBehind }); + }); + }, [playerOptions]); + + const onMaxBufferAheadChange = useCallback((maxBufferAhead) => { + updatePlayerOptions((prevOptions) => { + if (maxBufferAhead === prevOptions.maxBufferAhead) { + return prevOptions; + } + return Object.assign({}, prevOptions, { maxBufferAhead }); + }); + }, [playerOptions]); - onStopAtEndClick = (evt) => - this.setState({ stopAtEnd: getCheckBoxValue(evt.target) }); - - onSegmentRetryInput = (value) => - this.setState({ segmentRetry: value }); - - onManifestRetryInput = (value) => - this.setState({ manifestRetry: value }); - - onOfflineRetryInput = (value) => - this.setState({ offlineRetry: value }); - - onEnableFastSwitchingClick = (evt) => - this.setState({ enableFastSwitching: getCheckBoxValue(evt.target) }); - - onAudioTrackSwitchingModeChange = (value) => - this.setState({ audioTrackSwitchingMode: value }); - - onCodecSwitchChange = (value) => - this.setState({ onCodecSwitch: value }); - - onWantedBufferAheadInput = (value) => - this.setState({ wantedBufferAhead: value }); - - onMaxVideoBufferSizeInput = (value) => - this.setState({ maxVideoBufferSize: value}); - - onMaxBufferBehindInput = (value) => - this.setState({ maxBufferBehind: value }); - - onMaxBufferAheadInput = (value) => - this.setState({ maxBufferAhead: value }); - - render() { - const { - autoPlay, - manualBrSwitchingMode, - initialVideoBr, - initialAudioBr, - minVideoBr, - minAudioBr, - maxVideoBr, - maxAudioBr, - limitVideoWidth, - throttleVideoBitrateWhenHidden, - stopAtEnd, - segmentRetry, - manifestRetry, - offlineRetry, - enableFastSwitching, - audioTrackSwitchingMode, - onCodecSwitch, - wantedBufferAhead, - maxVideoBufferSize, - maxBufferAhead, - maxBufferBehind, - } = this.state; - - const initialSettings = { - initialVideoBr, - initialAudioBr, - minAudioBr, - minVideoBr, - maxVideoBr, - maxAudioBr, - limitVideoWidth, - throttleVideoBitrateWhenHidden, - onInitialVideoBrInput: this.onInitialVideoBrInput, - onInitialAudioBrInput: this.onInitialAudioBrInput, - onMinAudioBrInput: this.onMinAudioBrInput, - onMinVideoBrInput: this.onMinVideoBrInput, - onMaxAudioBrInput: this.onMaxAudioBrInput, - onMaxVideoBrInput: this.onMaxVideoBrInput, - onLimitVideoWidthClick: this.onLimitVideoWidthClick, - onThrottleVideoBitrateWhenHiddenClick: - this.onThrottleVideoBitrateWhenHiddenClick, - }; - - const networkConfig = { - segmentRetry, - manifestRetry, - offlineRetry, - onSegmentRetryInput: this.onSegmentRetryInput, - onManifestRetryInput: this.onManifestRetryInput, - onOfflineRetryInput: this.onOfflineRetryInput, - }; - - const trackSwitchModeConfig = { - enableFastSwitching, - audioTrackSwitchingMode, - onCodecSwitch, - onEnableFastSwitchingClick: this.onEnableFastSwitchingClick, - onAudioTrackSwitchingModeChange: this.onAudioTrackSwitchingModeChange, - onCodecSwitchChange: this.onCodecSwitchChange, - }; - - if (!this.props.showOptions) { - return null; - } + if (!showOptions) { + return null; + } - return ( -
    -
    - - - -
    -
    - - - -
    + return ( +
    +
    + Content options
    - ); - } +
    + Note: Those options won't be retroactively applied to + already-loaded contents +
    +
    + + + +
    +
    + + + +
    +
    + ); } export default Settings; diff --git a/demo/full/scripts/controllers/charts/BufferSize.jsx b/demo/full/scripts/controllers/charts/BufferSize.jsx index 3563277402..c1f7ed89de 100644 --- a/demo/full/scripts/controllers/charts/BufferSize.jsx +++ b/demo/full/scripts/controllers/charts/BufferSize.jsx @@ -74,28 +74,24 @@ function BufferSizeChart({ module }) { }, []); useEffect(() => { - const subscription = module.$get("data") - .subscribe(data => { - if (data.length > 0) { - const lastDate = data.length === 0 ? - null : - data[data.length - 1].date; - const minimumTime = lastDate - TIME_SAMPLES_MS; - let i; - for (i = data.length - 1; i >= 1; i--) { - if (data[i].date <= minimumTime) { - break; - } + return module.addStateListener("data", data => { + if (data.length > 0) { + const lastDate = data.length === 0 ? + null : + data[data.length - 1].date; + const minimumTime = lastDate - TIME_SAMPLES_MS; + let i; + for (i = data.length - 1; i >= 1; i--) { + if (data[i].date <= minimumTime) { + break; } - const consideredData = data.slice(i); - onNewData(consideredData); - } else { - onNewData([]); } - }); - return function cleanUpSubscription() { - subscription.unsubscribe(); - }; + const consideredData = data.slice(i); + onNewData(consideredData); + } else { + onNewData([]); + } + }); }, [module]); return ( diff --git a/demo/full/scripts/index.jsx b/demo/full/scripts/index.jsx index 6085286452..a92f8840f0 100644 --- a/demo/full/scripts/index.jsx +++ b/demo/full/scripts/index.jsx @@ -10,5 +10,5 @@ import Main from "./controllers/Main.jsx"; window.onload = function() { const root = ReactDOM.createRoot(document.getElementById("player-container")); - root.render(
    ); + root.render(
    ); }; diff --git a/demo/full/scripts/lib/defaultOptionsValues.js b/demo/full/scripts/lib/defaultOptionsValues.js index 3fa4bb3063..7848b44617 100644 --- a/demo/full/scripts/lib/defaultOptionsValues.js +++ b/demo/full/scripts/lib/defaultOptionsValues.js @@ -1,25 +1,33 @@ const defaultOptionsValues = { - autoPlay: true, - manualBrSwitchingMode: "direct", - initialVideoBr: 0, - initialAudioBr: 0, - minVideoBr: 0, - minAudioBr: 0, - maxVideoBr: Infinity, - maxAudioBr: Infinity, - limitVideoWidth: false, - throttleVideoBitrateWhenHidden: false, - stopAtEnd: false, - segmentRetry: 4, - manifestRetry: 4, - offlineRetry: Infinity, - enableFastSwitching: true, - audioTrackSwitchingMode: "reload", - onCodecSwitch: "continue", - wantedBufferAhead: 30, - maxVideoBufferSize: Infinity, - maxBufferAhead: Infinity, - maxBufferBehind: Infinity, + player: { + initialAudioBitrate: 0, + initialVideoBitrate: 0, + limitVideoWidth: false, + maxAudioBitrate: Infinity, + maxBufferAhead: Infinity, + maxBufferBehind: Infinity, + maxVideoBitrate: Infinity, + maxVideoBufferSize: Infinity, + minAudioBitrate: 0, + minVideoBitrate: 0, + stopAtEnd: false, + throttleVideoBitrateWhenHidden: false, + wantedBufferAhead: 30, + }, + loadVideo: { + audioTrackSwitchingMode: "reload", + autoPlay: true, + enableFastSwitching: true, + manualBitrateSwitchingMode: "direct", + networkConfig: { + manifestRetry: 4, + manifestRequestTimeout: 30000, + offlineRetry: Infinity, + segmentRetry: 4, + segmentRequestTimeout: 30000, + }, + onCodecSwitch: "continue", + }, }; export default defaultOptionsValues; diff --git a/demo/full/scripts/lib/vespertine.js b/demo/full/scripts/lib/vespertine.js index c5c2a4bd40..26be2786d4 100644 --- a/demo/full/scripts/lib/vespertine.js +++ b/demo/full/scripts/lib/vespertine.js @@ -1,11 +1,3 @@ -import { - combineLatest as observableCombineLatest, - distinctUntilChanged, - map, - Subject, - takeUntil, -} from "rxjs"; - /** * Homemade redux and r9webapp-core inspired state management architecture. * @@ -77,11 +69,15 @@ import { * // 2 - The interactions with it * const todoList = createModule(TodoList, { maxLength: 2 }); * - * // display todos when they change ($get is asynchronous, get is synchronous) - * todoList.$get("todos") - * .subscribe(todos => { - * display(todos); - * }); + * // display todos when they change + * const stopListening = todoList.addStateListener("todos", todos => { + * display(todos); + * }); + * + * // stop the callback given to the previous `addStateListener` from being + * // triggered again + * stopListening(); + * * console.log(todoList.get("todos").length); // 0 * * const firstId = todoList.dispatch("ADD_TODO", "do something"); @@ -98,7 +94,8 @@ import { * todoList.dispatch("REMOVE_TODO", firstId); * console.log(todoList.get("todos").length); // back to 1 * - * todoList.destroy(); // cleanup and stop $get subscriptions + * todoList.destroy(); // cleanup and stop callbacks added through + * // `addStateListener` from being triggered * ``` * * @param {Function} module @@ -112,9 +109,8 @@ import { * - get: get the entire module state, or the property named after the * argument (a string). * - * - $get: same as get, but returns an observable instead. Start emitting at - * the first change (I do not know yet if it's better to first emit the - * initial value immediately). + * - addStateListener: Allow to add a callback that will be triggered when the + * asked state changes. * * - destroy: destroy the module. Completes all subscriptions. */ @@ -124,8 +120,7 @@ const createModule = (module, payload) => { } const moduleState = {}; - const $destroy = new Subject(); - const $updates = new Subject().pipe(takeUntil($destroy)); + const stateListeners = {}; const getFromModule = (...args) => { if (!args.length) { @@ -137,42 +132,51 @@ const createModule = (module, payload) => { return args.map(arg => moduleState[arg]); }; - const $getFromModule = (...args) => { - if (!args.length) { - return $updates; - } - - if (args.length === 1) { - return $updates - .pipe( - map(state => state[args]), - distinctUntilChanged() - ); + const addStateListener = (arg, cb) => { + if (typeof arg === "string") { + if (stateListeners[arg] === undefined) { + stateListeners[arg] = []; + } + stateListeners[arg].push(onStateUpdate); + function onStateUpdate() { + cb(moduleState[arg]); + } + return () => { + if (stateListeners[arg] !== undefined) { + const indexOf = stateListeners[arg].indexOf(onStateUpdate); + stateListeners[arg].splice(indexOf, 1); + if (stateListeners[arg].length === 0) { + delete stateListeners[arg]; + } + } + }; } - const observables = args.map(arg => - $updates - .pipe( - map(state => state[arg]), - distinctUntilChanged() - ) - ); - - return observableCombineLatest(observables); + /* eslint-disable-next-line no-console */ + console.error("Invalid usage of `addStateListener`"); }; - const moduleArgs = { - state: { - get: getFromModule, - set: (arg) => { - const newState = Object.assign(moduleState, arg); - $updates.next(newState); - }, + const abortController = new AbortController(); + const state = { + get: getFromModule, + set: (arg) => { + Object.assign(moduleState, arg); + for (const key of Object.keys(arg)) { + if (stateListeners[key] !== undefined) { + for (const listener of stateListeners[key].slice()) { + try { + listener(moduleState[key]); + } catch (err) { + /* eslint-disable-next-line no-console */ + console.error("Error when calling listener for", key, err); + } + } + } + } }, - $destroy, }; - const moduleActions = module(moduleArgs, payload); + const moduleActions = module(state, payload, abortController.signal); return { dispatch: (actionName, actionPayload) => { @@ -185,10 +189,9 @@ const createModule = (module, payload) => { }, get: getFromModule, - $get: $getFromModule, + addStateListener, destroy: () => { - $destroy.next(); - $destroy.complete(); + abortController.abort(); }, }; }; diff --git a/demo/full/scripts/lib/withModulesState.jsx b/demo/full/scripts/lib/withModulesState.jsx index 4ba72f4704..8410a54a65 100644 --- a/demo/full/scripts/lib/withModulesState.jsx +++ b/demo/full/scripts/lib/withModulesState.jsx @@ -45,7 +45,7 @@ import React from "react"; */ const withModulesState = (modulesState) => (Comp) => { const modulesProps = Object.keys(modulesState); - const modulesSubscriptions = {}; + const stopListenersForProp = {}; return class extends React.Component { constructor(...args) { super(...args); @@ -71,19 +71,18 @@ const withModulesState = (modulesState) => (Comp) => { return; } - modulesSubscriptions[moduleProp] = []; + stopListenersForProp[moduleProp] = []; const translations = modulesState[modulesProps]; const module = this.props[moduleProp]; const wantedProps = Object.keys(modulesState[moduleProp]); wantedProps.forEach((state) => { - const sub = module - .$get(state) - .subscribe(val => this.setState({ + const stopListening = module.addStateListener(state, val => + this.setState({ [translations[state]]: val, })); - modulesSubscriptions[moduleProp].push(sub); + stopListenersForProp[moduleProp].push(stopListening); }); }); } @@ -93,38 +92,37 @@ const withModulesState = (modulesState) => (Comp) => { if (!Object.prototype.hasOwnProperty.call(nextProps, moduleProp) || nextProps[moduleProp] !== this.props[moduleProp]) { - if (modulesSubscriptions[moduleProp]) { - modulesSubscriptions[moduleProp] - .forEach(sub => sub.unsubscribe()); - delete modulesSubscriptions[moduleProp]; + if (stopListenersForProp[moduleProp]) { + stopListenersForProp[moduleProp] + .forEach(stop => stop()); + delete stopListenersForProp[moduleProp]; } } if (Object.prototype.hasOwnProperty.call(nextProps, moduleProp) && - !modulesSubscriptions[moduleProp]) + !stopListenersForProp[moduleProp]) { - modulesSubscriptions[moduleProp] = []; + stopListenersForProp[moduleProp] = []; const translations = modulesState[modulesProps]; const module = nextProps[moduleProp]; const wantedProps = Object.keys(modulesState[moduleProp]); wantedProps.forEach((state) => { - const sub = module - .$get(state) - .subscribe(val => this.setState({ + const stopListening = module.addStateListener(state, val => + this.setState({ [translations[state]]: val, })); - modulesSubscriptions[moduleProp].push(sub); + stopListenersForProp[moduleProp].push(stopListening); }); } }); } componentWillUnmount() { - Object.keys(modulesSubscriptions).forEach(moduleProp => { - modulesSubscriptions[moduleProp] - .forEach(sub => sub.unsubscribe()); - delete modulesSubscriptions[moduleProp]; + Object.keys(stopListenersForProp).forEach(moduleProp => { + stopListenersForProp[moduleProp] + .forEach(stop => stop()); + delete stopListenersForProp[moduleProp]; }); } diff --git a/demo/full/scripts/modules/ChartData.js b/demo/full/scripts/modules/ChartData.js index 69531d0145..0699285da3 100644 --- a/demo/full/scripts/modules/ChartData.js +++ b/demo/full/scripts/modules/ChartData.js @@ -1,4 +1,4 @@ -export default ({ state }, { maxSize }) => { +export default (state, { maxSize }) => { const data = []; state.set({ data: data.slice() }); diff --git a/demo/full/scripts/modules/player/catchUp.js b/demo/full/scripts/modules/player/catchUp.js index 87b37c8985..e56157e918 100644 --- a/demo/full/scripts/modules/player/catchUp.js +++ b/demo/full/scripts/modules/player/catchUp.js @@ -1,14 +1,3 @@ -import { - distinctUntilChanged, - EMPTY, - interval, - map, - of as observableOf, - startWith, - switchMap, -} from "rxjs"; -import fromPlayerEvent from "./fromPlayerEvent"; - // Distance from live edge we try to reach when the catching up button // is enabled. const LIVE_GAP_GOAL_WHEN_CATCHING_UP = 3.5; @@ -38,81 +27,116 @@ const MAX_RATE = 5; * - Update playback rate and `isCatchingUp` if it is far from the live edge * but not enough too trigger the seek */ -export default function $handleCatchUpMode( - $switchCatchUpMode, - rxPlayer, - state -) { - let isCatchingUp = false; +export default class CatchUpModeController { + constructor(rxPlayer, state) { + this._rxPlayer = rxPlayer; + this._state = state; + this._catchUpAborter = null; + } - function stopCatchingUp() { - if (!isCatchingUp) { - return EMPTY; + enableCatchUp() { + if (this._catchUpAborter !== null) { + return; } - rxPlayer.setPlaybackRate(1); - isCatchingUp = false; - state.set({ isCatchingUp, playbackRate: 1 }); - return observableOf(false); - } + this._catchUpAborter = new AbortController(); + let interval = null; + const onStateChange = () => { + if (interval !== null) { + clearInterval(interval); + interval = null; + } + const playerState = this._rxPlayer.getPlayerState(); + const canCatchUp = playerState === "LOADED" || + playerState === "PLAYING" || + playerState === "PAUSED" || + playerState === "BUFFERING" || + playerState === "SEEKING"; + if (!this._rxPlayer.isLive()) { + // Catch-up is only authorized for live contents + this.stopCatchUp(); + } else if (!canCatchUp) { + // Stop catching up if the state does not make sense for it + this._rxPlayer.setPlaybackRate(1); + this._state.set({ isCatchingUp: false, playbackRate: 1 }); + } else { + const checkCatchUp = () => { + const maximumPosition = this._rxPlayer.getMaximumPosition(); + const position = this._rxPlayer.getPosition(); + const liveGap = maximumPosition - position; + if (liveGap >= CATCH_UP_SEEKING_STEP) { + // If we're too far from the live to just change the playback rate, + // seek directly close to live + this._rxPlayer + .seekTo(maximumPosition - LIVE_GAP_GOAL_WHEN_CATCHING_UP); + this._rxPlayer.setPlaybackRate(1); + this._state.set({ isCatchingUp: false, playbackRate: 1 }); + return; + } - return $switchCatchUpMode.pipe(switchMap((isCatchUpEnabled) => { - return fromPlayerEvent(rxPlayer, "playerStateChange").pipe( - startWith(rxPlayer.getPlayerState()), - distinctUntilChanged(), - map((playerState) => { - return playerState === "LOADED" || - playerState === "PLAYING" || - playerState === "PAUSED" || - playerState === "BUFFERING" || - playerState === "SEEKING"; - }), - switchMap(canCatchUp => { - if (!rxPlayer.isLive()) { - state.set({ isCatchUpEnabled: false }); - return stopCatchingUp(); - } - state.set({ isCatchUpEnabled }); + if (this._state.get().isCatchingUp) { + if (liveGap <= LIVE_GAP_GOAL_WHEN_CATCHING_UP) { + // If we reached `LIVE_GAP_GOAL_WHEN_CATCHING_UP`, we can stop + // catching up + this._rxPlayer.setPlaybackRate(1); + this._state.set({ isCatchingUp: false, playbackRate: 1 }); + return; + } + } else if (liveGap < CATCH_UP_CHANGE_RATE_STEP) { + // Not catching up but we're close enough to the live, so no problem + // here. + return; + } - if (!isCatchUpEnabled || !canCatchUp) { - return stopCatchingUp(); - } + // If we reached this point, we need to catch up by modifiying the + // playback rate. The following code determine by how much - return interval(200).pipe( - startWith(0), - map(() => [ rxPlayer.getMaximumPosition(), rxPlayer.getPosition() ]), - switchMap(([maximumPosition, position]) => { - const liveGap = maximumPosition - position; - if (liveGap >= CATCH_UP_SEEKING_STEP) { - rxPlayer.seekTo(maximumPosition - LIVE_GAP_GOAL_WHEN_CATCHING_UP); - return stopCatchingUp(); - } + const factor = (liveGap - LIVE_GAP_GOAL_WHEN_CATCHING_UP) / 4; + const rate = Math.round(Math.min(MAX_RATE, 1.1 + factor) * 10) / 10; + if (rate <= 1) { + this._rxPlayer.setPlaybackRate(1); + this._state.set({ isCatchingUp: false, playbackRate: 1 }); + return; + } - if (isCatchingUp) { - if (liveGap <= LIVE_GAP_GOAL_WHEN_CATCHING_UP) { - return stopCatchingUp(); - } - } else if (liveGap < CATCH_UP_CHANGE_RATE_STEP) { - return stopCatchingUp(); - } + const currentPlaybackRate = this._rxPlayer.getPlaybackRate(); + if (rate !== currentPlaybackRate) { + this._rxPlayer.setPlaybackRate(rate); + } + if (!this._state.get().isCatchingUp) { + this._state.set({ isCatchingUp: true, playbackRate: rate }); + } else { + this._state.set({ playbackRate: rate }); + } + }; + interval = setInterval(checkCatchUp, 200); + checkCatchUp(); - const factor = (liveGap - LIVE_GAP_GOAL_WHEN_CATCHING_UP) / 4; - const rate = Math.round(Math.min(MAX_RATE, 1.1 + factor) * 10) / 10; - if (rate <= 1) { - return stopCatchingUp(); - } + this._catchUpAborter.signal.addEventListener("abort", () => { + if (interval !== null) { + clearInterval(interval); + interval = null; + } + }); + } + }; + this._rxPlayer.addEventListener("playerStateChange", onStateChange); - if (!isCatchingUp) { - isCatchingUp = true; - state.set({ isCatchingUp: true }); - } + this._catchUpAborter.signal.addEventListener("abort", () => { + this._rxPlayer.removeEventListener("playerStateChange", onStateChange); + }); + } - state.set({ playbackRate: rate }); - const currentPlaybackRate = rxPlayer.getPlaybackRate(); - if (rate !== currentPlaybackRate) { - rxPlayer.setPlaybackRate(rate); - } - return observableOf(true); - })); - })); - })); + stopCatchUp() { + if (this._catchUpAborter === null) { + return ; + } + this._catchUpAborter.abort(); + this._catchUpAborter = null; + this._rxPlayer.setPlaybackRate(1); + this._state.set({ + isCatchUpEnabled: false, + isCatchingUp: false, + playbackRate: 1, + }); + } } diff --git a/demo/full/scripts/modules/player/events.js b/demo/full/scripts/modules/player/events.js index 858a192238..8d135bd96d 100644 --- a/demo/full/scripts/modules/player/events.js +++ b/demo/full/scripts/modules/player/events.js @@ -1,31 +1,27 @@ -import { - distinctUntilChanged, - EMPTY, - interval as intervalObservable, - map, - startWith, - switchMap, - takeUntil, - tap, -} from "rxjs"; -import fromPlayerEvent from "./fromPlayerEvent"; - const POSITION_UPDATES_INTERVAL = 100; const BUFFERED_DATA_UPDATES_INTERVAL = 100; +function addEventListener(eventTarget, event, fn, abortSignal) { + eventTarget.addEventListener(event, fn); + abortSignal.addEventListener("abort", () => { + eventTarget.removeEventListener(event, fn); + }); +} + /** * Add event listeners to the RxPlayer to update the module's state at the right * time. * Unsubscribe when $destroy emit. * @param {RxPlayer} player - * @param {Subject} state - * @param {Subject} $destroy + * @param {Object} state + * @param {AbortSignal} abortSignal */ -const linkPlayerEventsToState = (player, state, $destroy) => { - const linkPlayerEventToState = (event, stateItem) => - fromPlayerEvent(player, event) - .pipe(takeUntil($destroy)) - .subscribe(arg => state.set({ [stateItem]: arg })); +const linkPlayerEventsToState = (player, state, abortSignal) => { + const linkPlayerEventToState = (event, stateItem) => { + addEventListener(player, event, (payload) => { + state.set({ [stateItem]: payload }); + }, abortSignal); + }; linkPlayerEventToState("textTrackChange", "subtitle"); linkPlayerEventToState("audioTrackChange", "language"); @@ -40,57 +36,52 @@ const linkPlayerEventsToState = (player, state, $destroy) => { linkPlayerEventToState("availableAudioBitratesChange", "availableAudioBitrates"); linkPlayerEventToState("availableVideoBitratesChange", "availableVideoBitrates"); - - fromPlayerEvent(player, "imageTrackUpdate").pipe( - distinctUntilChanged(), - takeUntil($destroy), - map(({ data }) => data) - ).subscribe(images => state.set({ images })); + addEventListener(player, "imageTrackUpdate", ({ data }) => { + state.set({ images: data }); + }, abortSignal); // use an interval for current position // TODO Only active for content playback - intervalObservable(POSITION_UPDATES_INTERVAL).pipe( - map(() => { - const position = player.getPosition(); - const duration = player.getVideoDuration(); - const videoTrack = player.getVideoTrack(); - return { - currentTime: player.getPosition(), - wallClockDiff: player.getWallClockTime() - position, - bufferGap: player.getVideoLoadedTime() - player.getVideoPlayedTime(), - duration: Number.isNaN(duration) ? undefined : duration, - minimumPosition: player.getMinimumPosition(), - maximumPosition: player.getMaximumPosition(), - liveGap: player.getMaximumPosition() - player.getPosition(), - playbackPosition: player.getPlaybackRate(), - videoTrackHasTrickMode: videoTrack !== null && - videoTrack !== undefined && - videoTrack.trickModeTracks !== undefined && - videoTrack.trickModeTracks.length > 0, - }; - }), - takeUntil($destroy) - ).subscribe(arg => { - state.set(arg); + const intervalId = setInterval(() => { + if (player.getPlayerState() === "STOPPED") { + return; + } + const position = player.getPosition(); + const duration = player.getVideoDuration(); + const videoTrack = player.getVideoTrack(); + state.set({ + currentTime: player.getPosition(), + wallClockDiff: player.getWallClockTime() - position, + bufferGap: player.getVideoLoadedTime() - player.getVideoPlayedTime(), + duration: Number.isNaN(duration) ? undefined : duration, + minimumPosition: player.getMinimumPosition(), + maximumPosition: player.getMaximumPosition(), + liveGap: player.getMaximumPosition() - player.getPosition(), + playbackPosition: player.getPlaybackRate(), + videoTrackHasTrickMode: videoTrack !== null && + videoTrack !== undefined && + videoTrack.trickModeTracks !== undefined && + videoTrack.trickModeTracks.length > 0, + }); + }, POSITION_UPDATES_INTERVAL); + abortSignal.addEventListener("abort", () => { + clearInterval(intervalId); }); - fromPlayerEvent(player, "playerStateChange").pipe( - distinctUntilChanged(), - takeUntil($destroy) - ).subscribe((arg) => { + addEventListener(player, "playerStateChange", (playerState) => { const stateUpdates = { cannotLoadMetadata: false, - hasEnded: arg === "ENDED", - hasCurrentContent: !["STOPPED", "LOADING"].includes(arg), - isContentLoaded: !["STOPPED", "LOADING", "RELOADING"].includes(arg), - isBuffering: arg === "BUFFERING", - isLoading: arg === "LOADING", - isReloading: arg === "RELOADING", - isSeeking: arg === "SEEKING", - isStopped: arg === "STOPPED", + hasEnded: playerState === "ENDED", + hasCurrentContent: !["STOPPED", "LOADING"].includes(playerState), + isContentLoaded: !["STOPPED", "LOADING", "RELOADING"].includes(playerState), + isBuffering: playerState === "BUFFERING", + isLoading: playerState === "LOADING", + isReloading: playerState === "RELOADING", + isSeeking: playerState === "SEEKING", + isStopped: playerState === "STOPPED", }; - switch (arg) { + switch (playerState) { case "ENDED": stateUpdates.autoPlayBlocked = false; stateUpdates.isPaused = true; @@ -122,7 +113,7 @@ const linkPlayerEventsToState = (player, state, $destroy) => { stateUpdates.videoTrack = null; stateUpdates.currentTime = undefined; stateUpdates.wallClockDiff = undefined; - stateUpdates.bufferGap = undefined; + stateUpdates.bufferGap = 0; stateUpdates.bufferedData = null; stateUpdates.duration = undefined; stateUpdates.minimumPosition = undefined; @@ -130,53 +121,45 @@ const linkPlayerEventsToState = (player, state, $destroy) => { break; } - if (arg !== "STOPPED") { + if (playerState !== "STOPPED") { // error is never cleaned up stateUpdates.error = null; } state.set(stateUpdates); - }); + }, abortSignal); - // update bufferedData - fromPlayerEvent(player, "playerStateChange").pipe( - map((playerState) => playerState === "STOPPED"), - distinctUntilChanged(), - takeUntil($destroy), - switchMap((isStopped) => { - if (isStopped) { - state.set({ bufferedData: null }); - return EMPTY; - } - return intervalObservable(BUFFERED_DATA_UPDATES_INTERVAL).pipe( - startWith(0), - tap(() => { - let audioContent = player.__priv_getSegmentBufferContent("audio"); - if (Array.isArray(audioContent)) { - audioContent = audioContent.slice(); - } - let textContent = player.__priv_getSegmentBufferContent("text"); - if (Array.isArray(textContent)) { - textContent = textContent.slice(); - } - let videoContent = player.__priv_getSegmentBufferContent("video"); - if (Array.isArray(videoContent)) { - videoContent = videoContent.slice(); - } - state.set({ bufferedData: { audio: audioContent, - video: videoContent, - text: textContent } }); - })); + const updateBufferedData = () => { + if (player.getPlayerState === "STOPPED") { + return; + } + let audioContent = player.__priv_getSegmentBufferContent("audio"); + if (Array.isArray(audioContent)) { + audioContent = audioContent.slice(); + } + let textContent = player.__priv_getSegmentBufferContent("text"); + if (Array.isArray(textContent)) { + textContent = textContent.slice(); + } + let videoContent = player.__priv_getSegmentBufferContent("video"); + if (Array.isArray(videoContent)) { + videoContent = videoContent.slice(); + } + state.set({ bufferedData: { audio: audioContent, + video: videoContent, + text: textContent } }); + }; - }) - ).subscribe(); + const bufferedDataItv = setInterval( + updateBufferedData, + BUFFERED_DATA_UPDATES_INTERVAL + ); + updateBufferedData(); + abortSignal.addEventListener("abort", () => { + clearInterval(bufferedDataItv); + }); - fromPlayerEvent(player, "warning").pipe( - takeUntil($destroy) - ).subscribe(warning => { - if (warning === null || warning === undefined) { - return ; - } + addEventListener(player, "warning", (warning) => { switch (warning.code) { case "MEDIA_ERR_NOT_LOADED_METADATA": state.set({ cannotLoadMetadata: true }); @@ -185,7 +168,7 @@ const linkPlayerEventsToState = (player, state, $destroy) => { state.set({ autoPlayBlocked: true }); break; } - }); + }, abortSignal); }; export { diff --git a/demo/full/scripts/modules/player/fromPlayerEvent.js b/demo/full/scripts/modules/player/fromPlayerEvent.js deleted file mode 100644 index b74c4f7888..0000000000 --- a/demo/full/scripts/modules/player/fromPlayerEvent.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Observable } from "rxjs"; - -/** - * Simple utils converting an Event-listener-based player event into an - * Observable. - * @param {RxPlayer} player - * @param {string} event - * @returns {Observable} - */ -export default function fromPlayerEvent(player, event) { - return new Observable(obs => { - const func = (payload) => obs.next(payload); - player.addEventListener(event, func); - - return () => { - player.removeEventListener(event, func); - }; - }); -} diff --git a/demo/full/scripts/modules/player/index.js b/demo/full/scripts/modules/player/index.js index 2aa8c82d5d..0cfa2b994d 100644 --- a/demo/full/scripts/modules/player/index.js +++ b/demo/full/scripts/modules/player/index.js @@ -6,10 +6,6 @@ * application. */ -import { - Subject, - takeUntil, -} from "rxjs"; import { BIF_PARSER, DASH, @@ -26,13 +22,14 @@ import { import { DASH_WASM, METAPLAYLIST, + DEBUG_ELEMENT, } from "../../../../../src/experimental/features"; import RxPlayer from "../../../../../src/minimal.ts"; import { linkPlayerEventsToState } from "./events.js"; -import $handleCatchUpMode from "./catchUp"; import VideoThumbnailLoader, { DASH_LOADER } from "../../../../../src/experimental/tools/VideoThumbnailLoader"; +import CatchUpModeController from "./catchUp"; RxPlayer.addFeatures([ BIF_PARSER, @@ -47,6 +44,7 @@ RxPlayer.addFeatures([ IMAGE_BUFFER, SMOOTH, METAPLAYLIST, + DEBUG_ELEMENT, ]); /* eslint-disable no-undef */ @@ -59,7 +57,13 @@ if (__INCLUDE_WASM_PARSER__) { VideoThumbnailLoader.addLoader(DASH_LOADER); -const PLAYER = ({ $destroy, state }, initOpts) => { +/** + * @param {Object} state + * @param {Object} initOpts + * @param {AbortSignal} abortSignal + * @returns {Object} + */ +function PLAYER(state, initOpts, abortSignal) { const { textTrackElement } = initOpts; const player = new RxPlayer(initOpts); @@ -77,7 +81,7 @@ const PLAYER = ({ $destroy, state }, initOpts) => { availableSubtitles: [], availableVideoBitrates: [], availableVideoTracks: [], - bufferGap: undefined, + bufferGap: 0, bufferedData: null, cannotLoadMetadata: false, currentTime: undefined, @@ -124,15 +128,14 @@ const PLAYER = ({ $destroy, state }, initOpts) => { videoThumbnailsData: null, }); - linkPlayerEventsToState(player, state, $destroy); + linkPlayerEventsToState(player, state, abortSignal); - const $switchCatchUpMode = new Subject(); - $handleCatchUpMode($switchCatchUpMode, player, state) - .pipe(takeUntil($destroy)) - .subscribe(); + const catchUpModeController = new CatchUpModeController(player, state); // dispose of the RxPlayer when destroyed - $destroy.subscribe(() => player.dispose()); + abortSignal.addEventListener("abort", () => { + player.dispose(); + }); function dettachVideoThumbnailLoader() { const { videoThumbnailsData } = state.get(); @@ -248,13 +251,13 @@ const PLAYER = ({ $destroy, state }, initOpts) => { }, ENABLE_LIVE_CATCH_UP() { - $switchCatchUpMode.next(true); + catchUpModeController.enableCatchUp(); }, DISABLE_LIVE_CATCH_UP() { - $switchCatchUpMode.next(false); + catchUpModeController.stopCatchUp(); }, }; -}; +} export default PLAYER; diff --git a/demo/full/styles/style.css b/demo/full/styles/style.css index e772e924fc..40c3a08b47 100644 --- a/demo/full/styles/style.css +++ b/demo/full/styles/style.css @@ -96,7 +96,7 @@ body { } .video-player-content { - max-width: 1000px; + max-width: 1050px; margin: auto; text-align: left; } @@ -425,6 +425,13 @@ body { margin: 5px; } +.option-desc { + font-weight: normal; + font-style: italic; + color: #242424; + font-size: 0.95em; +} + .choice-input-button { font-family: "icons", sans-serif; border: solid 1px #d1d1d1; @@ -641,6 +648,17 @@ body { width: auto; } +.settings-title { + text-align: center; + margin-bottom: 12px; + font-size: 1.5em; +} + +.settings-note { + font-style: italic; + text-align: center; +} + .volume { position: relative; display: flex; @@ -1155,7 +1173,7 @@ input:checked + .slider:before { } .canvas-buffer-graph { - position: relative; + position: absolute; top: 0px; left: 0px; width: 100%; @@ -1212,7 +1230,7 @@ input:checked + .slider:before { } .loadVideooptions li { - padding: 5px 10px; + padding: 10px; border-top: dashed 1px black; display: flex; flex-direction: column; @@ -1251,7 +1269,9 @@ select { } .settingsWrapper { - margin: 10px 0; + border: 1px dashed #d1d1d1; + padding: 10px; + margin-top: 10px; } .featureWrapperWithSelectMode { diff --git a/dist/mpd-parser.wasm b/dist/mpd-parser.wasm index b45298a72d..48be19e1ef 100644 Binary files a/dist/mpd-parser.wasm and b/dist/mpd-parser.wasm differ diff --git a/dist/rx-player.js b/dist/rx-player.js index a97d7427b1..89fdfbc893 100644 --- a/dist/rx-player.js +++ b/dist/rx-player.js @@ -58,18 +58,18 @@ var READY_STATES = { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "$u": function() { return /* binding */ isWebOs; }, /* harmony export */ "SB": function() { return /* binding */ isSafariMobile; }, /* harmony export */ "YM": function() { return /* binding */ isIEOrEdge; }, /* harmony export */ "fq": function() { return /* binding */ isIE11; }, -/* harmony export */ "gM": function() { return /* binding */ isWebOs2021; }, /* harmony export */ "kD": function() { return /* binding */ isEdgeChromium; }, +/* harmony export */ "l_": function() { return /* binding */ isPanasonic; }, /* harmony export */ "op": function() { return /* binding */ isSamsungBrowser; }, -/* harmony export */ "uz": function() { return /* binding */ isWebOs2022; }, /* harmony export */ "vS": function() { return /* binding */ isSafariDesktop; }, /* harmony export */ "vU": function() { return /* binding */ isFirefox; }, /* harmony export */ "yS": function() { return /* binding */ isTizen; } /* harmony export */ }); -/* unused harmony export isWebOs */ +/* unused harmony exports isWebOs2021, isWebOs2022 */ /* harmony import */ var _is_node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2203); /** * Copyright 2015 CANAL+ Group @@ -86,27 +86,72 @@ var READY_STATES = { * See the License for the specific language governing permissions and * limitations under the License. */ -var _a, _b; -// true on IE11 -// false on Edge and other IEs/browsers. -var isIE11 = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && typeof window.MSInputMethodContext !== "undefined" && typeof document.documentMode !== "undefined"; -// true for IE / Edge -var isIEOrEdge = _is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z ? false : navigator.appName === "Microsoft Internet Explorer" || navigator.appName === "Netscape" && /(Trident|Edge)\//.test(navigator.userAgent); -var isEdgeChromium = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && navigator.userAgent.toLowerCase().indexOf("edg/") !== -1; -var isFirefox = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && navigator.userAgent.toLowerCase().indexOf("firefox") !== -1; -var isSamsungBrowser = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && /SamsungBrowser/.test(navigator.userAgent); -var isTizen = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && /Tizen/.test(navigator.userAgent); -var isWebOs = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && navigator.userAgent.indexOf("Web0S") >= 0; -// Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/ -// Note: even that page doesn't correspond to what we've actually seen in the -// wild -var isWebOs2021 = isWebOs && (/[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) || /[Cc]hr[o0]me\/79/.test(navigator.userAgent)); -var isWebOs2022 = isWebOs && (/[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) || /[Cc]hr[o0]me\/87/.test(navigator.userAgent)); +/** Edge Chromium, regardless of the device */ +var isEdgeChromium = false; +/** IE11, regardless of the device */ +var isIE11 = false; +/** IE11 or Edge __Legacy__ (not Edge Chromium), regardless of the device */ +var isIEOrEdge = false; +/** Firefox, regardless of the device */ +var isFirefox = false; /** `true` on Safari on a PC platform (i.e. not iPhone / iPad etc.) */ -var isSafariDesktop = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && (Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") >= 0 || ((_b = (_a = window.safari) === null || _a === void 0 ? void 0 : _a.pushNotification) === null || _b === void 0 ? void 0 : _b.toString()) === "[object SafariRemoteNotification]"); +var isSafariDesktop = false; /** `true` on Safari on an iPhone, iPad & iPod platform */ -var isSafariMobile = !_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z && typeof navigator.platform === "string" && /iPad|iPhone|iPod/.test(navigator.platform); +var isSafariMobile = false; +/** Samsung's own browser application */ +var isSamsungBrowser = false; +/** `true` on devices where Tizen is the OS (e.g. Samsung TVs). */ +var isTizen = false; +/** `true` on devices where WebOS is the OS (e.g. LG TVs). */ +var isWebOs = false; +/** `true` specifically for WebOS 2021 version. */ +var isWebOs2021 = false; +/** `true` specifically for WebOS 2022 version. */ +var isWebOs2022 = false; +/** `true` for Panasonic devices. */ +var isPanasonic = false; +(function findCurrentBrowser() { + var _a, _b; + if (_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z) { + return; + } + // 1 - Find out browser between IE/Edge Legacy/Edge Chromium/Firefox/Safari + if (typeof window.MSInputMethodContext !== "undefined" && typeof document.documentMode !== "undefined") { + isIE11 = true; + isIEOrEdge = true; + } else if (navigator.appName === "Microsoft Internet Explorer" || navigator.appName === "Netscape" && /(Trident|Edge)\//.test(navigator.userAgent)) { + isIEOrEdge = true; + } else if (navigator.userAgent.toLowerCase().indexOf("edg/") !== -1) { + isEdgeChromium = true; + } else if (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1) { + isFirefox = true; + } else if (typeof navigator.platform === "string" && /iPad|iPhone|iPod/.test(navigator.platform)) { + isSafariMobile = true; + } else if (Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") >= 0 || ((_b = (_a = window.safari) === null || _a === void 0 ? void 0 : _a.pushNotification) === null || _b === void 0 ? void 0 : _b.toString()) === "[object SafariRemoteNotification]") { + isSafariDesktop = true; + } + // 2 - Find out specific device/platform information + // Samsung browser e.g. on Android + if (/SamsungBrowser/.test(navigator.userAgent)) { + isSamsungBrowser = true; + } + if (/Tizen/.test(navigator.userAgent)) { + isTizen = true; + // Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/ + // Note: even that page doesn't correspond to what we've actually seen in the + // wild + } else if (/[Ww]eb[O0]S/.test(navigator.userAgent)) { + isWebOs = true; + if (/[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) || /[Cc]hr[o0]me\/87/.test(navigator.userAgent)) { + isWebOs2022 = true; + } else if (/[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) || /[Cc]hr[o0]me\/79/.test(navigator.userAgent)) { + isWebOs2021 = true; + } + } else if (/[Pp]anasonic/.test(navigator.userAgent)) { + isPanasonic = true; + } +})(); /***/ }), @@ -279,16 +324,12 @@ if (!is_node/* default */.Z) { // EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js var inheritsLoose = __webpack_require__(4578); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subject.js + 1 modules -var Subject = __webpack_require__(6716); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/merge.js -var merge = __webpack_require__(3071); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/takeUntil.js -var takeUntil = __webpack_require__(3505); // EXTERNAL MODULE: ./src/utils/event_emitter.ts var event_emitter = __webpack_require__(1959); -// EXTERNAL MODULE: ./src/compat/event_listeners.ts + 1 modules -var event_listeners = __webpack_require__(1381); +// EXTERNAL MODULE: ./src/utils/task_canceller.ts +var task_canceller = __webpack_require__(288); +// EXTERNAL MODULE: ./src/compat/event_listeners.ts +var event_listeners = __webpack_require__(3038); ;// CONCATENATED MODULE: ./src/compat/eme/custom_media_keys/ie11_media_keys.ts @@ -319,9 +360,11 @@ var IE11MediaKeySession = /*#__PURE__*/function (_EventEmitter) { _this.expiration = NaN; _this.keyStatuses = new Map(); _this._mk = mk; - _this._closeSession$ = new Subject/* Subject */.x(); + _this._sessionClosingCanceller = new task_canceller/* default */.ZP(); _this.closed = new Promise(function (resolve) { - _this._closeSession$.subscribe(resolve); + _this._sessionClosingCanceller.signal.register(function () { + return resolve(); + }); }); _this.update = function (license) { return new Promise(function (resolve, reject) { @@ -343,9 +386,18 @@ var IE11MediaKeySession = /*#__PURE__*/function (_EventEmitter) { return new Promise(function (resolve) { var initDataU8 = initData instanceof Uint8Array ? initData : initData instanceof ArrayBuffer ? new Uint8Array(initData) : new Uint8Array(initData.buffer); _this2._ss = _this2._mk.createSession("video/mp4", initDataU8); - (0,merge/* merge */.T)(event_listeners/* onKeyMessage$ */.GJ(_this2._ss), event_listeners/* onKeyAdded$ */.GV(_this2._ss), event_listeners/* onKeyError$ */.Xe(_this2._ss)).pipe((0,takeUntil/* takeUntil */.R)(_this2._closeSession$)).subscribe(function (evt) { - return _this2.trigger(evt.type, evt); - }); + event_listeners/* onKeyMessage */.RV(_this2._ss, function (evt) { + var _a; + _this2.trigger((_a = evt.type) !== null && _a !== void 0 ? _a : "message", evt); + }, _this2._sessionClosingCanceller.signal); + event_listeners/* onKeyAdded */.kk(_this2._ss, function (evt) { + var _a; + _this2.trigger((_a = evt.type) !== null && _a !== void 0 ? _a : "keyadded", evt); + }, _this2._sessionClosingCanceller.signal); + event_listeners/* onKeyError */.Dl(_this2._ss, function (evt) { + var _a; + _this2.trigger((_a = evt.type) !== null && _a !== void 0 ? _a : "keyerror", evt); + }, _this2._sessionClosingCanceller.signal); resolve(); }); }; @@ -356,8 +408,7 @@ var IE11MediaKeySession = /*#__PURE__*/function (_EventEmitter) { _this3._ss.close(); _this3._ss = undefined; } - _this3._closeSession$.next(); - _this3._closeSession$.complete(); + _this3._sessionClosingCanceller.cancel(); resolve(); }); }; @@ -1158,68 +1209,40 @@ if (!_is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z) { /***/ }), -/***/ 1381: +/***/ 3038: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "XR": function() { return /* binding */ getPageActivityRef; }, - "w0": function() { return /* binding */ getPictureOnPictureStateRef; }, - "it": function() { return /* binding */ getVideoVisibilityRef; }, - "O0": function() { return /* binding */ getVideoWidthRef; }, - "Oh": function() { return /* binding */ onEncrypted$; }, - "M4": function() { return /* binding */ onEnded; }, - "Q1": function() { return /* binding */ onFullscreenChange$; }, - "GV": function() { return /* binding */ onKeyAdded$; }, - "Xe": function() { return /* binding */ onKeyError$; }, - "GJ": function() { return /* binding */ onKeyMessage$; }, - "eX": function() { return /* binding */ onKeyStatusesChange$; }, - "K4": function() { return /* binding */ onLoadedMetadata$; }, - "gg": function() { return /* binding */ onRemoveSourceBuffers$; }, - "bQ": function() { return /* binding */ onSeeked; }, - "Q$": function() { return /* binding */ onSeeking; }, - "UG": function() { return /* binding */ onSourceClose$; }, - "ep": function() { return /* binding */ onSourceEnded$; }, - "ym": function() { return /* binding */ onSourceOpen$; }, - "UA": function() { return /* binding */ onTextTrackChanges$; }, - "_E": function() { return /* binding */ onUpdate$; } -}); - -// UNUSED EXPORTS: addEventListener, onEnded$, onSeeked$, onSeeking$, onTimeUpdate$ - -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/fromEvent.js -var fromEvent = __webpack_require__(2401); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Observable.js + 1 modules -var Observable = __webpack_require__(1480); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/noop.js -var noop = __webpack_require__(2967); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/never.js - - -var NEVER = new Observable/* Observable */.y(noop/* noop */.Z); -function never() { - return NEVER; -} -//# sourceMappingURL=never.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/merge.js -var merge = __webpack_require__(3071); -// EXTERNAL MODULE: ./src/config.ts + 2 modules -var config = __webpack_require__(6872); -// EXTERNAL MODULE: ./src/utils/is_non_empty_string.ts -var is_non_empty_string = __webpack_require__(6923); -// EXTERNAL MODULE: ./src/utils/is_null_or_undefined.ts -var is_null_or_undefined = __webpack_require__(1946); -// EXTERNAL MODULE: ./src/utils/noop.ts -var utils_noop = __webpack_require__(8894); -// EXTERNAL MODULE: ./src/utils/reference.ts -var reference = __webpack_require__(5095); -// EXTERNAL MODULE: ./src/compat/is_node.ts -var is_node = __webpack_require__(2203); -// EXTERNAL MODULE: ./src/compat/should_favour_custom_safari_EME.ts -var should_favour_custom_safari_EME = __webpack_require__(5059); -;// CONCATENATED MODULE: ./src/compat/event_listeners.ts +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "Dl": function() { return /* binding */ onKeyError; }, +/* harmony export */ "M4": function() { return /* binding */ onEnded; }, +/* harmony export */ "N8": function() { return /* binding */ onSourceEnded; }, +/* harmony export */ "O0": function() { return /* binding */ getVideoWidthRef; }, +/* harmony export */ "Q$": function() { return /* binding */ onSeeking; }, +/* harmony export */ "Q4": function() { return /* binding */ onTextTrackRemoved; }, +/* harmony export */ "RV": function() { return /* binding */ onKeyMessage; }, +/* harmony export */ "XR": function() { return /* binding */ getPageActivityRef; }, +/* harmony export */ "Zl": function() { return /* binding */ onEncrypted; }, +/* harmony export */ "bQ": function() { return /* binding */ onSeeked; }, +/* harmony export */ "it": function() { return /* binding */ getVideoVisibilityRef; }, +/* harmony export */ "k6": function() { return /* binding */ onSourceClose; }, +/* harmony export */ "kJ": function() { return /* binding */ onTextTrackAdded; }, +/* harmony export */ "kk": function() { return /* binding */ onKeyAdded; }, +/* harmony export */ "qo": function() { return /* binding */ onKeyStatusesChange; }, +/* harmony export */ "u_": function() { return /* binding */ onSourceOpen; }, +/* harmony export */ "w0": function() { return /* binding */ getPictureOnPictureStateRef; }, +/* harmony export */ "x6": function() { return /* binding */ onRemoveSourceBuffers; }, +/* harmony export */ "y4": function() { return /* binding */ onSourceBufferUpdate; }, +/* harmony export */ "zU": function() { return /* binding */ onFullscreenChange; } +/* harmony export */ }); +/* unused harmony exports addEventListener, onLoadedMetadata, onTimeUpdate */ +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6872); +/* harmony import */ var _utils_is_non_empty_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6923); +/* harmony import */ var _utils_is_null_or_undefined__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(1946); +/* harmony import */ var _utils_noop__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8894); +/* harmony import */ var _utils_reference__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5095); +/* harmony import */ var _is_node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2203); +/* harmony import */ var _should_favour_custom_safari_EME__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5059); /** * Copyright 2015 CANAL+ Group * @@ -1235,11 +1258,6 @@ var should_favour_custom_safari_EME = __webpack_require__(5059); * See the License for the specific language governing permissions and * limitations under the License. */ -/** - * This file provides browser-agnostic event listeners under the form of - * RxJS Observables - */ - @@ -1249,7 +1267,7 @@ var should_favour_custom_safari_EME = __webpack_require__(5059); var BROWSER_PREFIXES = ["", "webkit", "moz", "ms"]; -var pixelRatio = is_node/* default */.Z || window.devicePixelRatio == null || window.devicePixelRatio === 0 ? 1 : window.devicePixelRatio; +var pixelRatio = _is_node__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z || window.devicePixelRatio == null || window.devicePixelRatio === 0 ? 1 : window.devicePixelRatio; /** * Find the first supported event from the list given. * @param {HTMLElement} element @@ -1284,7 +1302,7 @@ function findSupportedEvent(element, eventNames) { */ function eventPrefixed(eventNames, prefixes) { return eventNames.reduce(function (parent, name) { - return parent.concat((prefixes == null ? BROWSER_PREFIXES : prefixes).map(function (p) { + return parent.concat((prefixes === undefined ? BROWSER_PREFIXES : prefixes).map(function (p) { return p + name; })); }, []); @@ -1294,14 +1312,17 @@ function eventPrefixed(eventNames, prefixes) { * optionally automatically adding browser prefixes if needed. * @param {Array.} eventNames - The event(s) to listen to. If multiple * events are set, the event listener will be triggered when any of them emits. + * @param {Array.|undefined} [prefixes] - Optional vendor prefixes with + * which the event might also be sent. If not defined, default prefixes might be + * tested. * @returns {Function} - Returns function allowing to easily add a callback to * be triggered when that event is emitted on a given event target. */ -function createCompatibleEventListener(eventNames) { +function createCompatibleEventListener(eventNames, prefixes) { var mem; - var prefixedEvents = eventPrefixed(eventNames); + var prefixedEvents = eventPrefixed(eventNames, prefixes); return function (element, listener, cancelSignal) { - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } // if the element is a HTMLElement we can detect @@ -1310,7 +1331,7 @@ function createCompatibleEventListener(eventNames) { if (typeof mem === "undefined") { mem = findSupportedEvent(element, prefixedEvents); } - if ((0,is_non_empty_string/* default */.Z)(mem)) { + if ((0,_utils_is_non_empty_string__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z)(mem)) { element.addEventListener(mem, listener); cancelSignal.register(function () { if (mem !== undefined) { @@ -1323,42 +1344,31 @@ function createCompatibleEventListener(eventNames) { } } prefixedEvents.forEach(function (eventName) { - element.addEventListener(eventName, listener); + var hasSetOnFn = false; + if (typeof element.addEventListener === "function") { + element.addEventListener(eventName, listener); + } else { + hasSetOnFn = true; + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + element["on" + eventName] = listener; + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ + } + cancelSignal.register(function () { - element.removeEventListener(eventName, listener); + if (typeof element.removeEventListener === "function") { + element.removeEventListener(eventName, listener); + } + if (hasSetOnFn) { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + delete element["on" + eventName]; + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ + } }); }); }; } -/** - * @param {Array.} eventNames - * @param {Array.|undefined} prefixes - * @returns {Observable} - */ -function compatibleListener(eventNames, prefixes) { - var mem; - var prefixedEvents = eventPrefixed(eventNames, prefixes); - return function (element) { - // if the element is a HTMLElement we can detect - // the supported event, and memoize it in `mem` - if (element instanceof HTMLElement) { - if (typeof mem === "undefined") { - mem = findSupportedEvent(element, prefixedEvents); - } - if ((0,is_non_empty_string/* default */.Z)(mem)) { - return (0,fromEvent/* fromEvent */.R)(element, mem); - } else { - if (false) {} - return NEVER; - } - } - // otherwise, we need to listen to all the events - // and merge them into one observable sequence - return merge/* merge.apply */.T.apply(void 0, prefixedEvents.map(function (eventName) { - return (0,fromEvent/* fromEvent */.R)(element, eventName); - })); - }; -} /** * Returns a reference: * - set to `true` when the document is visible @@ -1379,17 +1389,14 @@ function getDocumentVisibilityRef(stopListening) { } else if (doc.webkitHidden != null) { prefix = "webkit"; } - var hidden = (0,is_non_empty_string/* default */.Z)(prefix) ? prefix + "Hidden" : "hidden"; - var visibilityChangeEvent = (0,is_non_empty_string/* default */.Z)(prefix) ? prefix + "visibilitychange" : "visibilitychange"; + var hidden = (0,_utils_is_non_empty_string__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z)(prefix) ? prefix + "Hidden" : "hidden"; + var visibilityChangeEvent = (0,_utils_is_non_empty_string__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z)(prefix) ? prefix + "visibilitychange" : "visibilitychange"; var isHidden = document[hidden]; - var ref = (0,reference/* default */.ZP)(!isHidden); + var ref = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .ZP)(!isHidden, stopListening); addEventListener(document, visibilityChangeEvent, function () { var isVisible = !document[hidden]; ref.setValueIfChanged(isVisible); }, stopListening); - stopListening.register(function () { - ref.finish(); - }); return ref; } /** @@ -1403,17 +1410,16 @@ function getDocumentVisibilityRef(stopListening) { function getPageActivityRef(stopListening) { var isDocVisibleRef = getDocumentVisibilityRef(stopListening); var currentTimeout; - var ref = (0,reference/* default */.ZP)(true); + var ref = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .ZP)(true, stopListening); stopListening.register(function () { clearTimeout(currentTimeout); currentTimeout = undefined; - ref.finish(); }); isDocVisibleRef.onUpdate(function onDocVisibilityChange(isVisible) { clearTimeout(currentTimeout); // clear potential previous timeout currentTimeout = undefined; if (!isVisible) { - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + var _config$getCurrent = _config__WEBPACK_IMPORTED_MODULE_3__/* ["default"].getCurrent */ .Z.getCurrent(), INACTIVITY_DELAY = _config$getCurrent.INACTIVITY_DELAY; currentTimeout = window.setTimeout(function () { ref.setValueIfChanged(false); @@ -1443,16 +1449,16 @@ function getVideoWidthFromPIPWindow(mediaElement, pipWindow) { * Emit when video enters and leaves Picture-In-Picture mode. * @param {HTMLMediaElement} elt * @param {Object} stopListening - * @returns {Observable} + * @returns {Object} */ function getPictureOnPictureStateRef(elt, stopListening) { var mediaElement = elt; if (mediaElement.webkitSupportsPresentationMode === true && typeof mediaElement.webkitSetPresentationMode === "function") { var isWebKitPIPEnabled = mediaElement.webkitPresentationMode === "picture-in-picture"; - var _ref = (0,reference/* default */.ZP)({ + var _ref = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .ZP)({ isEnabled: isWebKitPIPEnabled, pipWindow: null - }); + }, stopListening); addEventListener(mediaElement, "webkitpresentationmodechanged", function () { var isEnabled = mediaElement.webkitPresentationMode === "picture-in-picture"; _ref.setValue({ @@ -1460,16 +1466,13 @@ function getPictureOnPictureStateRef(elt, stopListening) { pipWindow: null }); }, stopListening); - stopListening.register(function () { - _ref.finish(); - }); return _ref; } var isPIPEnabled = document.pictureInPictureElement === mediaElement; - var ref = (0,reference/* default */.ZP)({ + var ref = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .ZP)({ isEnabled: isPIPEnabled, pipWindow: null - }); + }, stopListening); addEventListener(mediaElement, "enterpictureinpicture", function (evt) { var _a; ref.setValue({ @@ -1483,9 +1486,6 @@ function getPictureOnPictureStateRef(elt, stopListening) { pipWindow: null }); }, stopListening); - stopListening.register(function () { - ref.finish(); - }); return ref; } /** @@ -1496,16 +1496,15 @@ function getPictureOnPictureStateRef(elt, stopListening) { * @param {Object} pipStatus * @param {Object} stopListening - `CancellationSignal` allowing to free the * resources reserved to listen to video visibility change. - * @returns {Observable} + * @returns {Object} */ function getVideoVisibilityRef(pipStatus, stopListening) { var isDocVisibleRef = getDocumentVisibilityRef(stopListening); var currentTimeout; - var ref = (0,reference/* default */.ZP)(true); + var ref = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .ZP)(true, stopListening); stopListening.register(function () { clearTimeout(currentTimeout); currentTimeout = undefined; - ref.finish(); }); isDocVisibleRef.onUpdate(checkCurrentVisibility, { clearSignal: stopListening @@ -1521,7 +1520,7 @@ function getVideoVisibilityRef(pipStatus, stopListening) { if (pipStatus.getValue().isEnabled || isDocVisibleRef.getValue()) { ref.setValueIfChanged(true); } else { - var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), + var _config$getCurrent2 = _config__WEBPACK_IMPORTED_MODULE_3__/* ["default"].getCurrent */ .Z.getCurrent(), INACTIVITY_DELAY = _config$getCurrent2.INACTIVITY_DELAY; currentTimeout = window.setTimeout(function () { ref.setValueIfChanged(false); @@ -1538,8 +1537,8 @@ function getVideoVisibilityRef(pipStatus, stopListening) { * @returns {Object} */ function getVideoWidthRef(mediaElement, pipStatusRef, stopListening) { - var ref = (0,reference/* default */.ZP)(mediaElement.clientWidth * pixelRatio); - var _clearPreviousEventListener = utils_noop/* default */.Z; + var ref = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .ZP)(mediaElement.clientWidth * pixelRatio, stopListening); + var _clearPreviousEventListener = _utils_noop__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z; pipStatusRef.onUpdate(checkVideoWidth, { clearSignal: stopListening }); @@ -1549,7 +1548,6 @@ function getVideoWidthRef(mediaElement, pipStatusRef, stopListening) { stopListening.register(function stopUpdatingVideoWidthRef() { _clearPreviousEventListener(); clearInterval(interval); - ref.finish(); }); return ref; function checkVideoWidth() { @@ -1557,7 +1555,7 @@ function getVideoWidthRef(mediaElement, pipStatusRef, stopListening) { var pipStatus = pipStatusRef.getValue(); if (!pipStatus.isEnabled) { ref.setValueIfChanged(mediaElement.clientWidth * pixelRatio); - } else if (!(0,is_null_or_undefined/* default */.Z)(pipStatus.pipWindow)) { + } else if (!(0,_utils_is_null_or_undefined__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .Z)(pipStatus.pipWindow)) { var pipWindow = pipStatus.pipWindow; var firstWidth = getVideoWidthFromPIPWindow(mediaElement, pipWindow); var onPipResize = function onPipResize() { @@ -1566,7 +1564,7 @@ function getVideoWidthRef(mediaElement, pipStatusRef, stopListening) { pipWindow.addEventListener("resize", onPipResize); _clearPreviousEventListener = function clearPreviousEventListener() { pipWindow.removeEventListener("resize", onPipResize); - _clearPreviousEventListener = utils_noop/* default */.Z; + _clearPreviousEventListener = _utils_noop__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z; }; ref.setValueIfChanged(firstWidth * pixelRatio); } else { @@ -1576,106 +1574,86 @@ function getVideoWidthRef(mediaElement, pipStatusRef, stopListening) { } /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ -var onLoadedMetadata$ = compatibleListener(["loadedmetadata"]); +var onLoadedMetadata = createCompatibleEventListener(["loadedmetadata"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ -var onSeeking$ = compatibleListener(["seeking"]); -/** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -var onSeeked$ = compatibleListener(["seeked"]); -/** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -var onEnded$ = compatibleListener(["ended"]); -/** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -var onTimeUpdate$ = compatibleListener(["timeupdate"]); +var onTimeUpdate = createCompatibleEventListener(["timeupdate"]); /** * @param {HTMLElement} element - * @returns {Observable} */ -var onFullscreenChange$ = compatibleListener(["fullscreenchange", "FullscreenChange"], +var onFullscreenChange = createCompatibleEventListener(["fullscreenchange", "FullscreenChange"], // On IE11, fullscreen change events is called MSFullscreenChange BROWSER_PREFIXES.concat("MS")); /** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {TextTrackList} textTrackList */ -var onTextTrackChanges$ = function onTextTrackChanges$(textTrackList) { - return (0,merge/* merge */.T)(compatibleListener(["addtrack"])(textTrackList), compatibleListener(["removetrack"])(textTrackList)); -}; +var onTextTrackAdded = createCompatibleEventListener(["addtrack"]); /** - * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {TextTrackList} textTrackList */ -var onSourceOpen$ = compatibleListener(["sourceopen", "webkitsourceopen"]); +var onTextTrackRemoved = createCompatibleEventListener(["removetrack"]); /** * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {Function} listener + * @param {Object} cancelSignal */ -var onSourceClose$ = compatibleListener(["sourceclose", "webkitsourceclose"]); +var onSourceOpen = createCompatibleEventListener(["sourceopen", "webkitsourceopen"]); /** * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {Function} listener + * @param {Object} cancelSignal */ -var onSourceEnded$ = compatibleListener(["sourceended", "webkitsourceended"]); +var onSourceClose = createCompatibleEventListener(["sourceclose", "webkitsourceclose"]); /** - * @param {SourceBuffer} sourceBuffer - * @returns {Observable} + * @param {MediaSource} mediaSource + * @param {Function} listener + * @param {Object} cancelSignal */ -var onUpdate$ = compatibleListener(["update"]); +var onSourceEnded = createCompatibleEventListener(["sourceended", "webkitsourceended"]); /** * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {Function} listener + * @param {Object} cancelSignal + */ +var onSourceBufferUpdate = createCompatibleEventListener(["update"]); +/** + * @param {SourceBufferList} sourceBuffers + * @param {Function} listener + * @param {Object} cancelSignal */ -var onRemoveSourceBuffers$ = compatibleListener(["onremovesourcebuffer"]); +var onRemoveSourceBuffers = createCompatibleEventListener(["removesourcebuffer"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ -var onEncrypted$ = compatibleListener((0,should_favour_custom_safari_EME/* default */.Z)() ? ["needkey"] : ["encrypted", "needkey"]); +var onEncrypted = createCompatibleEventListener((0,_should_favour_custom_safari_EME__WEBPACK_IMPORTED_MODULE_6__/* ["default"] */ .Z)() ? ["needkey"] : ["encrypted", "needkey"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -var onKeyMessage$ = compatibleListener(["keymessage", "message"]); +var onKeyMessage = createCompatibleEventListener(["keymessage", "message"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -var onKeyAdded$ = compatibleListener(["keyadded", "ready"]); +var onKeyAdded = createCompatibleEventListener(["keyadded", "ready"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -var onKeyError$ = compatibleListener(["keyerror", "error"]); +var onKeyError = createCompatibleEventListener(["keyerror", "error"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -var onKeyStatusesChange$ = compatibleListener(["keystatuseschange"]); +var onKeyStatusesChange = createCompatibleEventListener(["keystatuseschange"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ var onSeeking = createCompatibleEventListener(["seeking"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ var onSeeked = createCompatibleEventListener(["seeked"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ var onEnded = createCompatibleEventListener(["ended"]); /** @@ -2753,6 +2731,22 @@ var DEFAULT_CONFIG = { * @type {number} */ MIN_CANCELABLE_PRIORITY: 3, + /** + * Codecs used in the videoCapabilities of the MediaKeySystemConfiguration + * (DRM). + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ + EME_DEFAULT_VIDEO_CODECS: ["video/mp4;codecs=\"avc1.4d401e\"", "video/mp4;codecs=\"avc1.42e01e\"", "video/webm;codecs=\"vp8\""], + /** + * Codecs used in the audioCapabilities of the MediaKeySystemConfiguration + * (DRM). + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ + EME_DEFAULT_AUDIO_CODECS: ["audio/mp4;codecs=\"mp4a.40.2\"", "audio/webm;codecs=opus"], /** * Robustnesses used in the {audio,video}Capabilities of the * MediaKeySystemConfiguration (DRM). @@ -2763,6 +2757,16 @@ var DEFAULT_CONFIG = { * @type {Array.} */ EME_DEFAULT_WIDEVINE_ROBUSTNESSES: ["HW_SECURE_ALL", "HW_SECURE_DECODE", "HW_SECURE_CRYPTO", "SW_SECURE_DECODE", "SW_SECURE_CRYPTO"], + /** + * Robustnesses used in the {audio,video}Capabilities of the + * MediaKeySystemConfiguration (DRM). + * + * Only used for "com.microsoft.playready.recommendation" key system. + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ + EME_DEFAULT_PLAYREADY_ROBUSTNESSES: ["3000", "2000"], /** * Link canonical key systems names to their respective reverse domain name, * used in the EME APIs. @@ -2774,7 +2778,7 @@ var DEFAULT_CONFIG = { EME_KEY_SYSTEMS: { clearkey: ["webkit-org.w3.clearkey", "org.w3.clearkey"], widevine: ["com.widevine.alpha"], - playready: ["com.microsoft.playready", "com.chromecast.playready", "com.youtube.playready"], + playready: ["com.microsoft.playready.recommendation", "com.microsoft.playready", "com.chromecast.playready", "com.youtube.playready"], fairplay: ["com.apple.fps.1_0"] }, /* eslint-enable @typescript-eslint/consistent-type-assertions */ @@ -3958,7 +3962,7 @@ function disableVideoTracks(videoTracks) { /***/ }), -/***/ 7425: +/***/ 1960: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -3976,8 +3980,8 @@ var inheritsLoose = __webpack_require__(4578); // EXTERNAL MODULE: ./node_modules/@babel/runtime/regenerator/index.js var regenerator = __webpack_require__(4687); var regenerator_default = /*#__PURE__*/__webpack_require__.n(regenerator); -// EXTERNAL MODULE: ./src/compat/event_listeners.ts + 1 modules -var event_listeners = __webpack_require__(1381); +// EXTERNAL MODULE: ./src/compat/event_listeners.ts +var event_listeners = __webpack_require__(3038); // EXTERNAL MODULE: ./src/log.ts + 1 modules var log = __webpack_require__(3887); // EXTERNAL MODULE: ./src/parsers/containers/isobmff/take_pssh_out.ts + 1 modules @@ -4184,9 +4188,10 @@ function disableMediaKeys(mediaElement) { * Attach MediaKeys and its associated state to an HTMLMediaElement. * * /!\ Mutates heavily MediaKeysInfosStore - * @param {Object} mediaKeysInfos * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {Object} mediaKeysInfos + * @param {Object} cancelSignal + * @returns {Promise} */ function attachMediaKeys(_x, _x2, _x3) { return _attachMediaKeys.apply(this, arguments); @@ -4204,7 +4209,7 @@ function _attachMediaKeys() { _context.next = 5; return closeAllSessions; case 5: - if (!cancelSignal.isCancelled) { + if (!cancelSignal.isCancelled()) { _context.next = 7; break; } @@ -4517,7 +4522,7 @@ function _createAndTryToRetrievePersistentSession() { * Emit event when a MediaKeySession begin to be closed and another when the * MediaKeySession is closed. * @param {Object} loadedSessionsStore - * @returns {Observable} + * @returns {Promise} */ function cleanOldLoadedSessions(_x, _x2) { return _cleanOldLoadedSessions.apply(this, arguments); @@ -4687,13 +4692,14 @@ var browser_detection = __webpack_require__(3666); * * This should usually be the case but we found rare devices where this would * cause problem: - * - (2022-10-26): WebOS (LG TVs) 2021 and 2022 just rebuffered indefinitely - * when loading a content already-loaded on the HTMLMediaElement. + * - (2022-11-21): WebOS (LG TVs), for some encrypted contents, just + * rebuffered indefinitely when loading a content already-loaded on the + * HTMLMediaElement. * * @returns {boolean} */ function canReuseMediaKeys() { - return !(browser_detection/* isWebOs2021 */.gM || browser_detection/* isWebOs2022 */.uz); + return !browser_detection/* isWebOs */.$u && !browser_detection/* isPanasonic */.l_; } ;// CONCATENATED MODULE: ./src/compat/should_renew_media_key_system_access.ts /** @@ -4747,6 +4753,7 @@ var flat_map = __webpack_require__(9592); + /** * @param {Array.} keySystems * @param {MediaKeySystemAccess} currentKeySystemAccess @@ -4798,13 +4805,16 @@ function findKeySystemCanonicalName(ksType) { /** * Build configuration for the requestMediaKeySystemAccess EME API, based * on the current keySystem object. - * @param {string} [ksName] - Generic name for the key system. e.g. "clearkey", - * "widevine", "playready". Can be used to make exceptions depending on it. + * @param {string|undefined} ksName - Generic name for the key system. e.g. + * "clearkey", "widevine", "playready". Can be used to make exceptions depending + * on it. + * @param {string|undefined} ksType - KeySystem complete type (e.g. + * "com.widevine.alpha"). * @param {Object} keySystem * @returns {Array.} - Configuration to give to the * requestMediaKeySystemAccess API. */ -function buildKeySystemConfigurations(ksName, keySystem) { +function buildKeySystemConfigurations(ksName, ksType, keySystem) { var sessionTypes = ["temporary"]; var persistentState = "optional"; var distinctiveIdentifier = "optional"; @@ -4819,14 +4829,35 @@ function buildKeySystemConfigurations(ksName, keySystem) { distinctiveIdentifier = "required"; } var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), - EME_DEFAULT_WIDEVINE_ROBUSTNESSES = _config$getCurrent2.EME_DEFAULT_WIDEVINE_ROBUSTNESSES; + EME_DEFAULT_AUDIO_CODECS = _config$getCurrent2.EME_DEFAULT_AUDIO_CODECS, + EME_DEFAULT_VIDEO_CODECS = _config$getCurrent2.EME_DEFAULT_VIDEO_CODECS, + EME_DEFAULT_WIDEVINE_ROBUSTNESSES = _config$getCurrent2.EME_DEFAULT_WIDEVINE_ROBUSTNESSES, + EME_DEFAULT_PLAYREADY_ROBUSTNESSES = _config$getCurrent2.EME_DEFAULT_PLAYREADY_ROBUSTNESSES; // Set robustness, in order of consideration: // 1. the user specified its own robustnesses // 2. a "widevine" key system is used, in that case set the default widevine // robustnesses as defined in the config // 3. set an undefined robustness - var videoRobustnesses = keySystem.videoRobustnesses != null ? keySystem.videoRobustnesses : ksName === "widevine" ? EME_DEFAULT_WIDEVINE_ROBUSTNESSES : []; - var audioRobustnesses = keySystem.audioRobustnesses != null ? keySystem.audioRobustnesses : ksName === "widevine" ? EME_DEFAULT_WIDEVINE_ROBUSTNESSES : []; + var videoRobustnesses; + if (!(0,is_null_or_undefined/* default */.Z)(keySystem.videoRobustnesses)) { + videoRobustnesses = keySystem.videoRobustnesses; + } else if (ksName === "widevine") { + videoRobustnesses = EME_DEFAULT_WIDEVINE_ROBUSTNESSES; + } else if (ksType === "com.microsoft.playready.recommendation") { + videoRobustnesses = EME_DEFAULT_PLAYREADY_ROBUSTNESSES; + } else { + videoRobustnesses = []; + } + var audioRobustnesses; + if (!(0,is_null_or_undefined/* default */.Z)(keySystem.audioRobustnesses)) { + audioRobustnesses = keySystem.audioRobustnesses; + } else if (ksName === "widevine") { + audioRobustnesses = EME_DEFAULT_WIDEVINE_ROBUSTNESSES; + } else if (ksType === "com.microsoft.playready.recommendation") { + audioRobustnesses = EME_DEFAULT_PLAYREADY_ROBUSTNESSES; + } else { + audioRobustnesses = []; + } if (videoRobustnesses.length === 0) { videoRobustnesses.push(undefined); } @@ -4845,46 +4876,41 @@ function buildKeySystemConfigurations(ksName, keySystem) { // https://storage.googleapis.com/wvdocs/Chrome_EME_Changes_and_Best_Practices.pdf // https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent var videoCapabilities = (0,flat_map/* default */.Z)(videoRobustnesses, function (robustness) { - return ["video/mp4;codecs=\"avc1.4d401e\"", "video/mp4;codecs=\"avc1.42e01e\"", "video/webm;codecs=\"vp8\""].map(function (contentType) { - return robustness !== undefined ? { + return EME_DEFAULT_VIDEO_CODECS.map(function (contentType) { + return robustness === undefined ? { + contentType: contentType + } : { contentType: contentType, robustness: robustness - } : { - contentType: contentType }; }); }); var audioCapabilities = (0,flat_map/* default */.Z)(audioRobustnesses, function (robustness) { - return ["audio/mp4;codecs=\"mp4a.40.2\"", "audio/webm;codecs=opus"].map(function (contentType) { - return robustness !== undefined ? { + return EME_DEFAULT_AUDIO_CODECS.map(function (contentType) { + return robustness === undefined ? { + contentType: contentType + } : { contentType: contentType, robustness: robustness - } : { - contentType: contentType }; }); }); - // TODO Re-test with a set contentType but an undefined robustness on the - // STBs on which this problem was found. - // - // add another with no {audio,video}Capabilities for some legacy browsers. - // As of today's spec, this should return NotSupported but the first - // candidate configuration should be good, so we should have no downside - // doing that. - // initDataTypes: ["cenc"], - // videoCapabilities: undefined, - // audioCapabilities: undefined, - // distinctiveIdentifier, - // persistentState, - // sessionTypes, - return [{ + var wantedMediaKeySystemConfiguration = { initDataTypes: ["cenc"], videoCapabilities: videoCapabilities, audioCapabilities: audioCapabilities, distinctiveIdentifier: distinctiveIdentifier, persistentState: persistentState, sessionTypes: sessionTypes - }]; + }; + return [wantedMediaKeySystemConfiguration, + // Some legacy implementations have issues with `audioCapabilities` and + // `videoCapabilities`, so we're including a supplementary + // `MediaKeySystemConfiguration` without those properties. + Object.assign(Object.assign({}, wantedMediaKeySystemConfiguration), { + audioCapabilities: undefined, + videoCapabilities: undefined + })]; } /** * Try to find a compatible key system from the keySystems array given. @@ -4987,7 +5013,7 @@ function getMediaKeySystemAccess(mediaElement, keySystemsConfigs, cancelSignal) throw new Error("requestMediaKeySystemAccess is not implemented in your browser."); case 4: _keySystemsType$index = keySystemsType[index], keyName = _keySystemsType$index.keyName, keyType = _keySystemsType$index.keyType, keySystemOptions = _keySystemsType$index.keySystemOptions; - keySystemConfigurations = buildKeySystemConfigurations(keyName, keySystemOptions); + keySystemConfigurations = buildKeySystemConfigurations(keyName, keyType, keySystemOptions); log/* default.debug */.Z.debug("DRM: Request keysystem access " + keyType + "," + (index + 1 + " of " + keySystemsType.length)); _context.prev = 7; _context.next = 10; @@ -5344,7 +5370,7 @@ function closeSession(session) { case 5: _context2.prev = 5; _context2.t0 = _context2["catch"](0); - if (!timeoutCanceller.isUsed) { + if (!timeoutCanceller.isUsed()) { _context2.next = 9; break; } @@ -5359,7 +5385,7 @@ function closeSession(session) { _context2.next = 13; return (0,cancellable_sleep/* default */.Z)(1000, timeoutCanceller.signal); case 13: - if (!timeoutCanceller.isUsed) { + if (!timeoutCanceller.isUsed()) { _context2.next = 15; break; } @@ -6150,7 +6176,7 @@ var LoadedSessionsStore = /*#__PURE__*/function () { * Close a MediaKeySession and just log an error if it fails (while resolving). * Emits then complete when done. * @param {MediaKeySession} mediaKeySession - * @returns {Observable} + * @returns {Promise} */ function safelyCloseMediaKeySession(_x8) { @@ -6875,7 +6901,7 @@ function getMediaKeysInfos(_x, _x2, _x3) { * Create `MediaKeys` from the `MediaKeySystemAccess` given. * Throws the right formatted error if it fails. * @param {MediaKeySystemAccess} mediaKeySystemAccess - * @returns {Observable.} + * @returns {Promise.} */ function _getMediaKeysInfos() { _getMediaKeysInfos = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(mediaElement, keySystemsConfigs, cancelSignal) { @@ -7035,146 +7061,35 @@ function _initMediaKeys() { var assertThisInitialized = __webpack_require__(7326); // EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/wrapNativeSuper.js + 4 modules var wrapNativeSuper = __webpack_require__(2146); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subject.js + 1 modules -var Subject = __webpack_require__(6716); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/map.js -var map = __webpack_require__(9127); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/mergeMap.js + 1 modules -var mergeMap = __webpack_require__(7877); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/defer.js -var defer = __webpack_require__(9917); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/async.js -var scheduler_async = __webpack_require__(7991); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isDate.js -var isDate = __webpack_require__(1454); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/lift.js -var lift = __webpack_require__(6798); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/innerFrom.js -var innerFrom = __webpack_require__(7878); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/createErrorClass.js -var createErrorClass = __webpack_require__(1819); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/OperatorSubscriber.js -var OperatorSubscriber = __webpack_require__(2566); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/executeSchedule.js -var executeSchedule = __webpack_require__(7845); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/timeout.js - - - - - - - -var TimeoutError = (0,createErrorClass/* createErrorClass */.d)(function (_super) { - return function TimeoutErrorImpl(info) { - if (info === void 0) { info = null; } - _super(this); - this.message = 'Timeout has occurred'; - this.name = 'TimeoutError'; - this.info = info; - }; -}); -function timeout(config, schedulerArg) { - var _a = ((0,isDate/* isValidDate */.q)(config) ? { first: config } : typeof config === 'number' ? { each: config } : config), first = _a.first, each = _a.each, _b = _a.with, _with = _b === void 0 ? timeoutErrorFactory : _b, _c = _a.scheduler, scheduler = _c === void 0 ? schedulerArg !== null && schedulerArg !== void 0 ? schedulerArg : scheduler_async/* asyncScheduler */.z : _c, _d = _a.meta, meta = _d === void 0 ? null : _d; - if (first == null && each == null) { - throw new TypeError('No timeout provided.'); - } - return (0,lift/* operate */.e)(function (source, subscriber) { - var originalSourceSubscription; - var timerSubscription; - var lastValue = null; - var seen = 0; - var startTimer = function (delay) { - timerSubscription = (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { - try { - originalSourceSubscription.unsubscribe(); - (0,innerFrom/* innerFrom */.Xf)(_with({ - meta: meta, - lastValue: lastValue, - seen: seen, - })).subscribe(subscriber); - } - catch (err) { - subscriber.error(err); - } - }, delay); - }; - originalSourceSubscription = source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - timerSubscription === null || timerSubscription === void 0 ? void 0 : timerSubscription.unsubscribe(); - seen++; - subscriber.next((lastValue = value)); - each > 0 && startTimer(each); - }, undefined, undefined, function () { - if (!(timerSubscription === null || timerSubscription === void 0 ? void 0 : timerSubscription.closed)) { - timerSubscription === null || timerSubscription === void 0 ? void 0 : timerSubscription.unsubscribe(); - } - lastValue = null; - })); - !seen && startTimer(first != null ? (typeof first === 'number' ? first : +first - scheduler.now()) : each); - }); -} -function timeoutErrorFactory(info) { - throw new TimeoutError(info); -} -//# sourceMappingURL=timeout.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/identity.js -var identity = __webpack_require__(278); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/catchError.js -var catchError = __webpack_require__(9878); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/merge.js -var merge = __webpack_require__(3071); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isFunction.js -var isFunction = __webpack_require__(8474); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/concatMap.js - - -function concatMap(project, resultSelector) { - return (0,isFunction/* isFunction */.m)(resultSelector) ? (0,mergeMap/* mergeMap */.z)(project, resultSelector, 1) : (0,mergeMap/* mergeMap */.z)(project, 1); -} -//# sourceMappingURL=concatMap.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/empty.js -var empty = __webpack_require__(1545); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/of.js -var of = __webpack_require__(2817); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/takeUntil.js -var takeUntil = __webpack_require__(3505); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/concat.js + 1 modules -var concat = __webpack_require__(2034); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/tap.js -var tap = __webpack_require__(2006); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/ignoreElements.js -var ignoreElements = __webpack_require__(533); -// EXTERNAL MODULE: ./src/utils/cast_to_observable.ts -var cast_to_observable = __webpack_require__(8117); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/timer.js -var timer = __webpack_require__(6625); // EXTERNAL MODULE: ./src/utils/get_fuzzed_delay.ts var get_fuzzed_delay = __webpack_require__(2572); -;// CONCATENATED MODULE: ./src/utils/rx-retry_with_backoff.ts +;// CONCATENATED MODULE: ./src/utils/sleep.ts /** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Convert a setTimeout to a Promise. * - * http://www.apache.org/licenses/LICENSE-2.0 + * You can use it to have a much more readable blocking code with async/await + * in some asynchronous tests. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * @param {number} timeInMs + * @returns {Promise} */ +function sleep(timeInMs) { + return new Promise(function (res) { + setTimeout(res, timeInMs); + }); +} +;// CONCATENATED MODULE: ./src/utils/retry_promise_with_backoff.ts + + /** - * Retry the given observable (if it triggers an error) with an exponential + * Retry the given Promise (if it rejects) with an exponential * backoff. * The backoff behavior can be tweaked through the options given. * - * @param {Observable} obs$ + * @param {Function} runProm * @param {Object} options - Configuration object. * This object contains the following properties: * @@ -7187,46 +7102,88 @@ var get_fuzzed_delay = __webpack_require__(2572); * - totalRetry {Number} - The amount of time we should retry. 0 * means no retry, 1 means a single retry, Infinity means infinite retry * etc. - * If the observable still fails after this number of retry, the error will - * be throwed through this observable. + * If the Promise still rejects after this number of retry, the error will + * be throwed through the returned Promise. * * - shouldRetry {Function|undefined} - Function which will receive the - * observable error each time it fails, and should return a boolean. If this - * boolean is false, the error will be directly thrown (without anymore - * retry). + * error each time it fails, and should return a boolean. If this boolean + * is false, the error will be directly thrown (without anymore retry). * * - onRetry {Function|undefined} - Function which will be triggered at * each retry. Will receive two arguments: - * 1. The observable error + * 1. The error * 2. The current retry count, beginning at 1 for the first retry * - * @returns {Observable} + * @param {Object} cancelSignal + * @returns {Promise} * TODO Take errorSelector out. Should probably be entirely managed in the * calling code via a catch (much simpler to use and to understand). */ -function retryObsWithBackoff(obs$, options) { +function retryPromiseWithBackoff(runProm, options, cancelSignal) { var baseDelay = options.baseDelay, maxDelay = options.maxDelay, totalRetry = options.totalRetry, shouldRetry = options.shouldRetry, onRetry = options.onRetry; var retryCount = 0; - return obs$.pipe((0,catchError/* catchError */.K)(function (error, source) { - if (!(0,is_null_or_undefined/* default */.Z)(shouldRetry) && !shouldRetry(error) || retryCount++ >= totalRetry) { - throw error; - } - if (typeof onRetry === "function") { - onRetry(error, retryCount); - } - var delay = Math.min(baseDelay * Math.pow(2, retryCount - 1), maxDelay); - var fuzzedDelay = (0,get_fuzzed_delay/* default */.Z)(delay); - return (0,timer/* timer */.H)(fuzzedDelay).pipe((0,mergeMap/* mergeMap */.z)(function () { - return source; + return iterate(); + function iterate() { + return _iterate.apply(this, arguments); + } + function _iterate() { + _iterate = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee() { + var res, delay, fuzzedDelay, _res; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (!(cancelSignal.cancellationError !== null)) { + _context.next = 2; + break; + } + throw cancelSignal.cancellationError; + case 2: + _context.prev = 2; + _context.next = 5; + return runProm(); + case 5: + res = _context.sent; + return _context.abrupt("return", res); + case 9: + _context.prev = 9; + _context.t0 = _context["catch"](2); + if (!(cancelSignal.cancellationError !== null)) { + _context.next = 13; + break; + } + throw cancelSignal.cancellationError; + case 13: + if (!(!(0,is_null_or_undefined/* default */.Z)(shouldRetry) && !shouldRetry(_context.t0) || retryCount++ >= totalRetry)) { + _context.next = 15; + break; + } + throw _context.t0; + case 15: + if (typeof onRetry === "function") { + onRetry(_context.t0, retryCount); + } + delay = Math.min(baseDelay * Math.pow(2, retryCount - 1), maxDelay); + fuzzedDelay = (0,get_fuzzed_delay/* default */.Z)(delay); + _context.next = 20; + return sleep(fuzzedDelay); + case 20: + _res = iterate(); + return _context.abrupt("return", _res); + case 22: + case "end": + return _context.stop(); + } + } + }, _callee, null, [[2, 9]]); })); - })); + return _iterate.apply(this, arguments); + } } -// EXTERNAL MODULE: ./src/utils/rx-try_catch.ts -var rx_try_catch = __webpack_require__(5561); ;// CONCATENATED MODULE: ./src/compat/eme/get_uuid_kid_from_keystatus_kid.ts /** * Copyright 2015 CANAL+ Group @@ -7284,6 +7241,7 @@ var assert_unreachable = __webpack_require__(7904); + /** * Error thrown when the MediaKeySession has to be closed due to a trigger * specified by user configuration. @@ -7346,6 +7304,9 @@ function checkKeyStatuses(session, options, keySystem) { keyId: keyId.buffer, keyStatus: keyStatus }; + if (log/* default.hasLevel */.Z.hasLevel("DEBUG")) { + log/* default.debug */.Z.debug("DRM: key status update (" + (0,string_parsing/* bytesToHex */.ci)(keyId) + "): " + keyStatus); + } switch (keyStatus) { case KEY_STATUSES.EXPIRED: { @@ -7418,6 +7379,8 @@ function checkKeyStatuses(session, options, keySystem) { + + /** * Copyright 2015 CANAL+ Group * @@ -7441,135 +7404,252 @@ function checkKeyStatuses(session, options, keySystem) { - - -var onKeyError$ = event_listeners/* onKeyError$ */.Xe, - onKeyMessage$ = event_listeners/* onKeyMessage$ */.GJ, - onKeyStatusesChange$ = event_listeners/* onKeyStatusesChange$ */.eX; +var onKeyError = event_listeners/* onKeyError */.Dl, + onKeyMessage = event_listeners/* onKeyMessage */.RV, + onKeyStatusesChange = event_listeners/* onKeyStatusesChange */.qo; /** - * Error thrown when the MediaKeySession is blacklisted. - * Such MediaKeySession should not be re-used but other MediaKeySession for the - * same content can still be used. - * @class BlacklistedSessionError - * @extends Error - */ -var BlacklistedSessionError = /*#__PURE__*/function (_Error) { - (0,inheritsLoose/* default */.Z)(BlacklistedSessionError, _Error); - function BlacklistedSessionError(sessionError) { - var _this; - _this = _Error.call(this) || this; - // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class - Object.setPrototypeOf((0,assertThisInitialized/* default */.Z)(_this), BlacklistedSessionError.prototype); - _this.sessionError = sessionError; - return _this; - } - return BlacklistedSessionError; -}( /*#__PURE__*/(0,wrapNativeSuper/* default */.Z)(Error)); -/** - * listen to various events from a MediaKeySession and react accordingly + * Listen to various events from a MediaKeySession and react accordingly * depending on the configuration given. * @param {MediaKeySession} session - The MediaKeySession concerned. * @param {Object} keySystemOptions - The key system options. * @param {String} keySystem - The configuration keySystem used for deciphering - * @returns {Observable} + * @param {Object} callbacks + * @param {Object} cancelSignal */ -function SessionEventsListener(session, keySystemOptions, keySystem) { +function SessionEventsListener(session, keySystemOptions, keySystem, callbacks, cancelSignal) { log/* default.info */.Z.info("DRM: Binding session events", session.sessionId); - var sessionWarningSubject$ = new Subject/* Subject */.x(); var _keySystemOptions$get = keySystemOptions.getLicenseConfig, getLicenseConfig = _keySystemOptions$get === void 0 ? {} : _keySystemOptions$get; - var keyErrors = onKeyError$(session).pipe((0,map/* map */.U)(function (error) { - throw new encrypted_media_error/* default */.Z("KEY_ERROR", error.type); - })); - var keyStatusesChange$ = onKeyStatusesChange$(session).pipe((0,mergeMap/* mergeMap */.z)(function (keyStatusesEvent) { - return handleKeyStatusesChangeEvent(session, keySystemOptions, keySystem, keyStatusesEvent); - })); - var keyMessages$ = onKeyMessage$(session).pipe((0,mergeMap/* mergeMap */.z)(function (messageEvent) { + /** Allows to manually cancel everything the `SessionEventsListener` is doing. */ + var manualCanceller = new task_canceller/* default */.ZP(); + manualCanceller.linkToSignal(cancelSignal); + if (!(0,is_null_or_undefined/* default */.Z)(session.closed)) { + session.closed.then(function () { + return manualCanceller.cancel(); + })["catch"](function (err) { + if (cancelSignal.isCancelled()) { + return; + } + manualCanceller.cancel(); + callbacks.onError(err); + }); + } + onKeyError(session, function (evt) { + manualCanceller.cancel(); + callbacks.onError(new encrypted_media_error/* default */.Z("KEY_ERROR", evt.type)); + }, manualCanceller.signal); + onKeyStatusesChange(session, function (keyStatusesEvent) { + handleKeyStatusesChangeEvent(keyStatusesEvent)["catch"](function (error) { + if (cancelSignal.isCancelled() || manualCanceller.isUsed() && error instanceof task_canceller/* CancellationSignal */.XG) { + return; + } + manualCanceller.cancel(); + callbacks.onError(error); + }); + }, manualCanceller.signal); + onKeyMessage(session, function (evt) { + var messageEvent = evt; var message = new Uint8Array(messageEvent.message); var messageType = (0,is_non_empty_string/* default */.Z)(messageEvent.messageType) ? messageEvent.messageType : "license-request"; log/* default.info */.Z.info("DRM: Received message event, type " + messageType, session.sessionId); - var getLicense$ = (0,defer/* defer */.P)(function () { - var getLicense = keySystemOptions.getLicense(message, messageType); - var getLicenseTimeout = (0,is_null_or_undefined/* default */.Z)(getLicenseConfig.timeout) ? 10 * 1000 : getLicenseConfig.timeout; - return (0,cast_to_observable/* default */.Z)(getLicense).pipe(getLicenseTimeout >= 0 ? timeout(getLicenseTimeout) : identity/* identity */.y /* noop */); - }); - - var backoffOptions = getLicenseBackoffOptions(sessionWarningSubject$, getLicenseConfig.retry); - return retryObsWithBackoff(getLicense$, backoffOptions).pipe((0,map/* map */.U)(function (licenseObject) { - return { - type: "key-message-handled", - value: { - session: session, - license: licenseObject - } - }; - }), (0,catchError/* catchError */.K)(function (err) { + var backoffOptions = getLicenseBackoffOptions(getLicenseConfig.retry); + retryPromiseWithBackoff(function () { + return runGetLicense(message, messageType); + }, backoffOptions, manualCanceller.signal).then(function (licenseObject) { + if (manualCanceller.isUsed()) { + return Promise.resolve(); + } + if ((0,is_null_or_undefined/* default */.Z)(licenseObject)) { + log/* default.info */.Z.info("DRM: No license given, skipping session.update"); + } else { + return updateSessionWithMessage(session, licenseObject); + } + })["catch"](function (err) { + if (manualCanceller.isUsed()) { + return; + } + manualCanceller.cancel(); var formattedError = formatGetLicenseError(err); if (!(0,is_null_or_undefined/* default */.Z)(err)) { var fallbackOnLastTry = err.fallbackOnLastTry; if (fallbackOnLastTry === true) { log/* default.warn */.Z.warn("DRM: Last `getLicense` attempt failed. " + "Blacklisting the current session."); - throw new BlacklistedSessionError(formattedError); + callbacks.onError(new BlacklistedSessionError(formattedError)); + return; } } - throw formattedError; + callbacks.onError(formattedError); + }); + }, manualCanceller.signal); + checkAndHandleCurrentKeyStatuses(); + return; + /** + * @param {Event} keyStatusesEvent + * @returns {Promise} + */ + function handleKeyStatusesChangeEvent(_x) { + return _handleKeyStatusesChangeEvent.apply(this, arguments); + } + /** + * Check current MediaKeyStatus for each key in the given MediaKeySession and: + * - throw if at least one status is a non-recoverable error + * - call warning callback for recoverable errors + * - call onKeyUpdate callback when the MediaKeyStatus of any key is updated + */ + function _handleKeyStatusesChangeEvent() { + _handleKeyStatusesChangeEvent = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee2(keyStatusesEvent) { + var runOnKeyStatusesChangeCallback, _runOnKeyStatusesChangeCallback; + return regenerator_default().wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _runOnKeyStatusesChangeCallback = function _runOnKeyStatusesChan2() { + _runOnKeyStatusesChangeCallback = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee() { + var ret, err; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (!manualCanceller.isUsed()) { + _context.next = 2; + break; + } + return _context.abrupt("return"); + case 2: + if (!(typeof keySystemOptions.onKeyStatusesChange === "function")) { + _context.next = 24; + break; + } + _context.prev = 3; + _context.next = 6; + return keySystemOptions.onKeyStatusesChange(keyStatusesEvent, session); + case 6: + ret = _context.sent; + if (!manualCanceller.isUsed()) { + _context.next = 9; + break; + } + return _context.abrupt("return"); + case 9: + _context.next = 18; + break; + case 11: + _context.prev = 11; + _context.t0 = _context["catch"](3); + if (!cancelSignal.isCancelled()) { + _context.next = 15; + break; + } + return _context.abrupt("return"); + case 15: + err = new encrypted_media_error/* default */.Z("KEY_STATUS_CHANGE_ERROR", "Unknown `onKeyStatusesChange` error"); + if (!(0,is_null_or_undefined/* default */.Z)(_context.t0) && (0,is_non_empty_string/* default */.Z)(_context.t0.message)) { + err.message = _context.t0.message; + } + throw err; + case 18: + if (!(0,is_null_or_undefined/* default */.Z)(ret)) { + _context.next = 22; + break; + } + log/* default.info */.Z.info("DRM: No license given, skipping session.update"); + _context.next = 24; + break; + case 22: + _context.next = 24; + return updateSessionWithMessage(session, ret); + case 24: + case "end": + return _context.stop(); + } + } + }, _callee, null, [[3, 11]]); + })); + return _runOnKeyStatusesChangeCallback.apply(this, arguments); + }; + runOnKeyStatusesChangeCallback = function _runOnKeyStatusesChan() { + return _runOnKeyStatusesChangeCallback.apply(this, arguments); + }; + log/* default.info */.Z.info("DRM: keystatuseschange event received", session.sessionId); + _context2.next = 5; + return Promise.all([runOnKeyStatusesChangeCallback(), Promise.resolve(checkAndHandleCurrentKeyStatuses())]); + case 5: + case "end": + return _context2.stop(); + } + } + }, _callee2); })); - })); - var sessionUpdates = (0,merge/* merge */.T)(keyMessages$, keyStatusesChange$).pipe(concatMap(function (evt) { - switch (evt.type) { - case "key-message-handled": - case "key-status-change-handled": - if ((0,is_null_or_undefined/* default */.Z)(evt.value.license)) { - log/* default.info */.Z.info("DRM: No message given, skipping session.update"); - return empty/* EMPTY */.E; - } - return updateSessionWithMessage(session, evt.value.license); - default: - return (0,of.of)(evt); - } - })); - var sessionEvents = (0,merge/* merge */.T)(getKeyStatusesEvents(session, keySystemOptions, keySystem), sessionUpdates, keyErrors, sessionWarningSubject$); - return !(0,is_null_or_undefined/* default */.Z)(session.closed) ? sessionEvents - // TODO There is a subtle TypeScript issue there that made casting - // to a type-compatible type mandatory. If a more elegant solution - // can be found, it should be preffered. - .pipe((0,takeUntil/* takeUntil */.R)((0,cast_to_observable/* default */.Z)(session.closed))) : sessionEvents; -} -/** - * Check current MediaKeyStatus for each key in the given MediaKeySession and - * return an Observable which either: - * - throw if at least one status is a non-recoverable error - * - emit warning events for recoverable errors - * - emit blacklist-keys events for key IDs that are not decipherable - * @param {MediaKeySession} session - The MediaKeySession concerned. - * @param {Object} options - Options related to key statuses checks. - * @param {String} keySystem - The name of the key system used for deciphering - * @returns {Observable} - */ -function getKeyStatusesEvents(session, options, keySystem) { - return (0,defer/* defer */.P)(function () { - if (session.keyStatuses.size === 0) { - return empty/* EMPTY */.E; + return _handleKeyStatusesChangeEvent.apply(this, arguments); + } + function checkAndHandleCurrentKeyStatuses() { + if (manualCanceller.isUsed() || session.keyStatuses.size === 0) { + return; } - var _checkKeyStatuses = checkKeyStatuses(session, options, keySystem), + var _checkKeyStatuses = checkKeyStatuses(session, keySystemOptions, keySystem), warning = _checkKeyStatuses.warning, blacklistedKeyIds = _checkKeyStatuses.blacklistedKeyIds, whitelistedKeyIds = _checkKeyStatuses.whitelistedKeyIds; - var keysUpdate$ = (0,of.of)({ - type: "keys-update", - value: { - whitelistedKeyIds: whitelistedKeyIds, - blacklistedKeyIds: blacklistedKeyIds - } - }); if (warning !== undefined) { - return (0,concat/* concat */.z)((0,of.of)({ - type: "warning", - value: warning - }), keysUpdate$); + callbacks.onWarning(warning); + if (manualCanceller.isUsed()) { + return; + } } - return keysUpdate$; - }); + callbacks.onKeyUpdate({ + whitelistedKeyIds: whitelistedKeyIds, + blacklistedKeyIds: blacklistedKeyIds + }); + } + function runGetLicense(message, messageType) { + var timeoutId; + return new Promise(function (res, rej) { + try { + log/* default.debug */.Z.debug("DRM: Calling `getLicense`", messageType); + var getLicense = keySystemOptions.getLicense(message, messageType); + var getLicenseTimeout = (0,is_null_or_undefined/* default */.Z)(getLicenseConfig.timeout) ? 10 * 1000 : getLicenseConfig.timeout; + if (getLicenseTimeout >= 0) { + timeoutId = setTimeout(function () { + rej(new GetLicenseTimeoutError("\"getLicense\" timeout exceeded (" + getLicenseTimeout + " ms)")); + }, getLicenseTimeout); + } + Promise.resolve(getLicense).then(clearTimeoutAndResolve, clearTimeoutAndReject); + } catch (err) { + clearTimeoutAndReject(err); + } + function clearTimeoutAndResolve(data) { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + res(data); + } + function clearTimeoutAndReject(err) { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + rej(err); + } + }); + } + /** + * Construct backoff options for the getLicense call. + * @param {number|undefined} numberOfRetry - Maximum of amount retried. + * Equal to `2` if not defined. + * @returns {Object} + */ + function getLicenseBackoffOptions(numberOfRetry) { + return { + totalRetry: numberOfRetry !== null && numberOfRetry !== void 0 ? numberOfRetry : 2, + baseDelay: 200, + maxDelay: 3000, + shouldRetry: function shouldRetry(error) { + return error instanceof GetLicenseTimeoutError || (0,is_null_or_undefined/* default */.Z)(error) || error.noRetry !== true; + }, + onRetry: function onRetry(error) { + return callbacks.onWarning(formatGetLicenseError(error)); + } + }; + } } /** * Format an error returned by a `getLicense` call to a proper form as defined @@ -7578,7 +7658,7 @@ function getKeyStatusesEvents(session, options, keySystem) { * @returns {Error} */ function formatGetLicenseError(error) { - if (error instanceof TimeoutError) { + if (error instanceof GetLicenseTimeoutError) { return new encrypted_media_error/* default */.Z("KEY_LOAD_TIMEOUT", "The license server took too much time to " + "respond."); } var err = new encrypted_media_error/* default */.Z("KEY_LOAD_ERROR", "An error occured when calling `getLicense`."); @@ -7589,83 +7669,79 @@ function formatGetLicenseError(error) { } /** * Call MediaKeySession.update with the given `message`, if defined. - * Returns the right event depending on the action taken. * @param {MediaKeySession} session * @param {ArrayBuffer|TypedArray|null} message - * @returns {Observable} - */ -function updateSessionWithMessage(session, message) { - log/* default.info */.Z.info("DRM: Updating MediaKeySession with message"); - return (0,cast_to_observable/* default */.Z)(session.update(message)).pipe((0,catchError/* catchError */.K)(function (error) { - var reason = error instanceof Error ? error.toString() : "`session.update` failed"; - throw new encrypted_media_error/* default */.Z("KEY_UPDATE_ERROR", reason); - }), (0,tap/* tap */.b)(function () { - log/* default.info */.Z.info("DRM: MediaKeySession update succeeded."); - }), - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - (0,ignoreElements/* ignoreElements */.l)()); + * @returns {Promise} + */ +function updateSessionWithMessage(_x2, _x3) { + return _updateSessionWithMessage.apply(this, arguments); } /** - * @param {MediaKeySession} session - * @param {Object} keySystemOptions - * @param {string} keySystem - * @param {Event} keyStatusesEvent - * @returns {Observable} - */ -function handleKeyStatusesChangeEvent(session, keySystemOptions, keySystem, keyStatusesEvent) { - log/* default.info */.Z.info("DRM: keystatuseschange event received", session.sessionId); - var callback$ = (0,defer/* defer */.P)(function () { - return (0,rx_try_catch/* default */.Z)(function () { - if (typeof keySystemOptions.onKeyStatusesChange !== "function") { - return empty/* EMPTY */.E; - } - return (0,cast_to_observable/* default */.Z)(keySystemOptions.onKeyStatusesChange(keyStatusesEvent, session)); - }, undefined); - }).pipe((0,map/* map */.U)(function (licenseObject) { - return { - type: "key-status-change-handled", - value: { - session: session, - license: licenseObject + * Error thrown when the MediaKeySession is blacklisted. + * Such MediaKeySession should not be re-used but other MediaKeySession for the + * same content can still be used. + * @class BlacklistedSessionError + * @extends Error + */ +function _updateSessionWithMessage() { + _updateSessionWithMessage = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee3(session, message) { + var reason; + return regenerator_default().wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + log/* default.info */.Z.info("DRM: Updating MediaKeySession with message"); + _context3.prev = 1; + _context3.next = 4; + return session.update(message); + case 4: + _context3.next = 10; + break; + case 6: + _context3.prev = 6; + _context3.t0 = _context3["catch"](1); + reason = _context3.t0 instanceof Error ? _context3.t0.toString() : "`session.update` failed"; + throw new encrypted_media_error/* default */.Z("KEY_UPDATE_ERROR", reason); + case 10: + log/* default.info */.Z.info("DRM: MediaKeySession update succeeded."); + case 11: + case "end": + return _context3.stop(); + } } - }; - }), (0,catchError/* catchError */.K)(function (error) { - var err = new encrypted_media_error/* default */.Z("KEY_STATUS_CHANGE_ERROR", "Unknown `onKeyStatusesChange` error"); - if (!(0,is_null_or_undefined/* default */.Z)(error) && (0,is_non_empty_string/* default */.Z)(error.message)) { - err.message = error.message; - } - throw err; + }, _callee3, null, [[1, 6]]); })); - return (0,merge/* merge */.T)(getKeyStatusesEvents(session, keySystemOptions, keySystem), callback$); + return _updateSessionWithMessage.apply(this, arguments); } +var BlacklistedSessionError = /*#__PURE__*/function (_Error) { + (0,inheritsLoose/* default */.Z)(BlacklistedSessionError, _Error); + function BlacklistedSessionError(sessionError) { + var _this; + _this = _Error.call(this) || this; + // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class + Object.setPrototypeOf((0,assertThisInitialized/* default */.Z)(_this), BlacklistedSessionError.prototype); + _this.sessionError = sessionError; + return _this; + } + return BlacklistedSessionError; +}( /*#__PURE__*/(0,wrapNativeSuper/* default */.Z)(Error)); /** - * Construct backoff options for the getLicense call. - * @param {Subject} sessionWarningSubject$ - Subject through which retry - * warnings will be sent. - * @param {number|undefined} numberOfRetry - Maximum of amount retried. - * Equal to `2` if not defined. - * @returns {Object} + * Error thrown when a `getLicense` call timeouts. + * @class GetLicenseTimeoutError + * @extends Error */ -function getLicenseBackoffOptions(sessionWarningSubject$, numberOfRetry) { - return { - totalRetry: numberOfRetry !== null && numberOfRetry !== void 0 ? numberOfRetry : 2, - baseDelay: 200, - maxDelay: 3000, - shouldRetry: function shouldRetry(error) { - return error instanceof TimeoutError || (0,is_null_or_undefined/* default */.Z)(error) || error.noRetry !== true; - }, - onRetry: function onRetry(error) { - return sessionWarningSubject$.next({ - type: "warning", - value: formatGetLicenseError(error) - }); - } - }; -} +var GetLicenseTimeoutError = /*#__PURE__*/function (_Error2) { + (0,inheritsLoose/* default */.Z)(GetLicenseTimeoutError, _Error2); + function GetLicenseTimeoutError(message) { + var _this2; + _this2 = _Error2.call(this) || this; + // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class + Object.setPrototypeOf((0,assertThisInitialized/* default */.Z)(_this2), BlacklistedSessionError.prototype); + _this2.message = message; + return _this2; + } + return GetLicenseTimeoutError; +}( /*#__PURE__*/(0,wrapNativeSuper/* default */.Z)(Error)); // EXTERNAL MODULE: ./src/errors/is_known_error.ts var is_known_error = __webpack_require__(9822); ;// CONCATENATED MODULE: ./src/core/decrypt/set_server_certificate.ts @@ -7711,7 +7787,7 @@ function setServerCertificate(_x, _x2) { * and complete. * @param {MediaKeys} mediaKeys * @param {ArrayBuffer} serverCertificate - * @returns {Observable} + * @returns {Promise.} */ function _setServerCertificate() { _setServerCertificate = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(mediaKeys, serverCertificate) { @@ -8027,7 +8103,7 @@ function content_decryptor_arrayLikeToArray(arr, len) { if (len == null || len > -var onEncrypted$ = event_listeners/* onEncrypted$ */.Oh; +var onEncrypted = event_listeners/* onEncrypted */.Zl; /** * Module communicating with the Content Decryption Module (or CDM) to be able * to decrypt contents. @@ -8072,16 +8148,13 @@ var ContentDecryptor = /*#__PURE__*/function (_EventEmitter) { data: null }; _this.error = null; - var listenerSub = onEncrypted$(mediaElement).subscribe(function (evt) { + onEncrypted(mediaElement, function (evt) { log/* default.debug */.Z.debug("DRM: Encrypted event received from media element."); var initData = getInitData(evt); if (initData !== null) { _this.onInitializationData(initData); } - }); - canceller.signal.register(function () { - listenerSub.unsubscribe(); - }); + }, canceller.signal); initMediaKeys(mediaElement, ksOptions, canceller.signal).then(function (mediaKeysInfo) { var options = mediaKeysInfo.options, mediaKeySystemAccess = mediaKeysInfo.mediaKeySystemAccess; @@ -8279,7 +8352,7 @@ var ContentDecryptor = /*#__PURE__*/function (_EventEmitter) { function () { var _processInitializationData2 = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee2(initializationData, mediaKeysData) { var _this4 = this; - var mediaKeySystemAccess, stores, options, firstCreatedSession, keyIds, hexKids, period, createdSessions, periodKeys, _iterator, _step, createdSess, periodKeysArr, _i, _periodKeysArr, kid, _iterator2, _step2, innerKid, wantedSessionType, _config$getCurrent, EME_DEFAULT_MAX_SIMULTANEOUS_MEDIA_KEY_SESSIONS, EME_MAX_STORED_PERSISTENT_SESSION_INFORMATION, maxSessionCacheSize, sessionRes, sessionInfo, _sessionRes$value, mediaKeySession, sessionType, isSessionPersisted, sub, requestData, entry, indexInCurrent; + var mediaKeySystemAccess, stores, options, firstCreatedSession, keyIds, hexKids, period, createdSessions, periodKeys, _iterator, _step, createdSess, periodKeysArr, _i, _periodKeysArr, kid, _iterator2, _step2, innerKid, wantedSessionType, _config$getCurrent, EME_DEFAULT_MAX_SIMULTANEOUS_MEDIA_KEY_SESSIONS, EME_MAX_STORED_PERSISTENT_SESSION_INFORMATION, maxSessionCacheSize, sessionRes, sessionInfo, _sessionRes$value, mediaKeySession, sessionType, isSessionPersisted, requestData, entry, indexInCurrent; return regenerator_default().wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { @@ -8423,13 +8496,9 @@ var ContentDecryptor = /*#__PURE__*/function (_EventEmitter) { * persisted or not. */ isSessionPersisted = false; - sub = SessionEventsListener(mediaKeySession, options, mediaKeySystemAccess.keySystem).subscribe({ - next: function next(evt) { - if (evt.type === "warning") { - _this4.trigger("warning", evt.value); - return; - } - var linkedKeys = getKeyIdsLinkedToSession(initializationData, sessionInfo.record, options.singleLicensePer, sessionInfo.source === "created-session" /* MediaKeySessionLoadingType.Created */, evt.value.whitelistedKeyIds, evt.value.blacklistedKeyIds); + SessionEventsListener(mediaKeySession, options, mediaKeySystemAccess.keySystem, { + onKeyUpdate: function onKeyUpdate(value) { + var linkedKeys = getKeyIdsLinkedToSession(initializationData, sessionInfo.record, options.singleLicensePer, sessionInfo.source === "created-session" /* MediaKeySessionLoadingType.Created */, value.whitelistedKeyIds, value.blacklistedKeyIds); sessionInfo.record.associateKeyIds(linkedKeys.whitelisted); sessionInfo.record.associateKeyIds(linkedKeys.blacklisted); sessionInfo.keyStatuses = { @@ -8447,7 +8516,10 @@ var ContentDecryptor = /*#__PURE__*/function (_EventEmitter) { } _this4._unlockInitDataQueue(); }, - error: function error(err) { + onWarning: function onWarning(value) { + _this4.trigger("warning", value); + }, + onError: function onError(err) { var _a; if (err instanceof DecommissionedSessionError) { log/* default.warn */.Z.warn("DRM: A session's closing condition has been triggered"); @@ -8486,33 +8558,29 @@ var ContentDecryptor = /*#__PURE__*/function (_EventEmitter) { _this4._unlockInitDataQueue(); // TODO warning for blacklisted session? } - }); - - this._canceller.signal.register(function () { - sub.unsubscribe(); - }); + }, this._canceller.signal); if (options.singleLicensePer === undefined || options.singleLicensePer === "init-data") { this._unlockInitDataQueue(); } if (!(sessionRes.type === "created-session" /* MediaKeySessionLoadingType.Created */)) { - _context2.next = 68; + _context2.next = 67; break; } requestData = initializationData.values.constructRequestData(); - _context2.prev = 55; - _context2.next = 58; + _context2.prev = 54; + _context2.next = 57; return stores.loadedSessionsStore.generateLicenseRequest(mediaKeySession, initializationData.type, requestData); - case 58: - _context2.next = 68; + case 57: + _context2.next = 67; break; - case 60: - _context2.prev = 60; - _context2.t0 = _context2["catch"](55); + case 59: + _context2.prev = 59; + _context2.t0 = _context2["catch"](54); // First check that the error was not due to the MediaKeySession closing // or being closed entry = stores.loadedSessionsStore.getEntryForSession(mediaKeySession); if (!(entry === null || entry.closingStatus.type !== "none")) { - _context2.next = 67; + _context2.next = 66; break; } // MediaKeySession closing/closed: Just remove from handled list and abort. @@ -8521,16 +8589,16 @@ var ContentDecryptor = /*#__PURE__*/function (_EventEmitter) { this._currentSessions.splice(indexInCurrent, 1); } return _context2.abrupt("return", Promise.resolve()); - case 67: + case 66: throw new encrypted_media_error/* default */.Z("KEY_GENERATE_REQUEST_ERROR", _context2.t0 instanceof Error ? _context2.t0.toString() : "Unknown error"); - case 68: + case 67: return _context2.abrupt("return", Promise.resolve()); - case 69: + case 68: case "end": return _context2.stop(); } } - }, _callee2, this, [[55, 60]]); + }, _callee2, this, [[54, 59]]); })); function _processInitializationData(_x, _x2) { return _processInitializationData2.apply(this, arguments); @@ -8632,7 +8700,7 @@ var ContentDecryptor = /*#__PURE__*/function (_EventEmitter) { * formatted and sent in an "error" event. */; _proto._onFatalError = function _onFatalError(err) { - if (this._canceller.isUsed) { + if (this._canceller.isUsed()) { return; } var formattedErr = err instanceof Error ? err : new other_error/* default */.Z("NONE", "Unknown decryption error"); @@ -9019,7 +9087,7 @@ function addKeyIdsFromPeriod(set, period) { /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; -/* harmony import */ var _content_decryptor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7425); +/* harmony import */ var _content_decryptor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1960); /** * Copyright 2015 CANAL+ Group * @@ -9099,21 +9167,25 @@ var currentMediaState = new WeakMap(); /***/ }), -/***/ 5039: +/***/ 9372: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "Z": function() { return /* binding */ DirectFileContentInitializer; } +/* harmony export */ }); +/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4578); +/* harmony import */ var _compat__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5767); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(3887); +/* harmony import */ var _utils_reference__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5095); +/* harmony import */ var _utils_task_canceller__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(288); +/* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(379); +/* harmony import */ var _utils_get_loaded_reference__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(1757); +/* harmony import */ var _utils_initial_seek_and_play__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(8833); +/* harmony import */ var _utils_initialize_content_decryption__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8799); +/* harmony import */ var _utils_rebuffering_controller__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6199); +/* harmony import */ var _utils_throw_on_media_error__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4576); -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "Z": function() { return /* binding */ emitLoadedEvent; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/take.js -var take = __webpack_require__(4727); -// EXTERNAL MODULE: ./src/compat/browser_detection.ts -var browser_detection = __webpack_require__(3666); -;// CONCATENATED MODULE: ./src/compat/should_wait_for_data_before_loaded.ts /** * Copyright 2015 CANAL+ Group * @@ -9129,588 +9201,159 @@ var browser_detection = __webpack_require__(3666); * See the License for the specific language governing permissions and * limitations under the License. */ - -/** - * On some browsers, the ready state might never go above `1` when autoplay is - * blocked. On these cases, for now, we just advertise the content as "loaded". - * We might go into BUFFERING just after that state, but that's a small price to - * pay. - * @param {Boolean} isDirectfile - * @returns {Boolean} - */ -function shouldWaitForDataBeforeLoaded(isDirectfile, mustPlayInline) { - if (isDirectfile && browser_detection/* isSafariMobile */.SB) { - return mustPlayInline; - } - return true; -} -// EXTERNAL MODULE: ./src/compat/should_validate_metadata.ts -var should_validate_metadata = __webpack_require__(1669); -// EXTERNAL MODULE: ./src/utils/filter_map.ts -var filter_map = __webpack_require__(2793); -// EXTERNAL MODULE: ./src/core/init/events_generators.ts -var events_generators = __webpack_require__(8343); -;// CONCATENATED MODULE: ./src/core/init/emit_loaded_event.ts /** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - -/** - * Emit a `ILoadedEvent` once the content can be considered as loaded. - * @param {Observable} observation$ - * @param {HTMLMediaElement} mediaElement - * @param {Object|null} segmentBuffersStore - * @param {boolean} isDirectfile - `true` if this is a directfile content - * @returns {Observable} + * /!\ This file is feature-switchable. + * It always should be imported through the `features` object. */ -function emitLoadedEvent(observation$, mediaElement, segmentBuffersStore, isDirectfile) { - return observation$.pipe((0,filter_map/* default */.Z)(function (observation) { - if (observation.rebuffering !== null || observation.freezing !== null || observation.readyState === 0) { - return null; - } - if (!shouldWaitForDataBeforeLoaded(isDirectfile, mediaElement.hasAttribute("playsinline"))) { - return mediaElement.duration > 0 ? events_generators/* default.loaded */.Z.loaded(segmentBuffersStore) : null; - } - if (observation.readyState >= 3 && observation.currentRange !== null) { - if (!(0,should_validate_metadata/* default */.Z)() || mediaElement.duration > 0) { - return events_generators/* default.loaded */.Z.loaded(segmentBuffersStore); - } - return null; - } - return null; - }, null), (0,take/* take */.q)(1)); -} -/***/ }), - -/***/ 8343: -/***/ (function(__unused_webpack_module, __webpack_exports__) { - -"use strict"; -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Construct a "loaded" event. - * @returns {Object} - */ -function loaded(segmentBuffersStore) { - return { - type: "loaded", - value: { - segmentBuffersStore: segmentBuffersStore - } - }; -} -/** - * Construct a "stalled" event. - * @param {Object|null} rebuffering - * @returns {Object} - */ -function stalled(rebuffering) { - return { - type: "stalled", - value: rebuffering - }; -} -/** - * Construct a "stalled" event. - * @returns {Object} - */ -function unstalled() { - return { - type: "unstalled", - value: null - }; -} -/** - * Construct a "decipherabilityUpdate" event. - * @param {Array.} arg - * @returns {Object} - */ -function decipherabilityUpdate(arg) { - return { - type: "decipherabilityUpdate", - value: arg - }; -} -/** - * Construct a "manifestReady" event. - * @param {Object} manifest - * @returns {Object} - */ -function manifestReady(manifest) { - return { - type: "manifestReady", - value: { - manifest: manifest - } - }; -} -/** - * Construct a "manifestUpdate" event. - * @returns {Object} - */ -function manifestUpdate() { - return { - type: "manifestUpdate", - value: null - }; -} -/** - * Construct a "representationChange" event. - * @param {string} type - * @param {Object} period - * @returns {Object} - */ -function nullRepresentation(type, period) { - return { - type: "representationChange", - value: { - type: type, - representation: null, - period: period - } - }; -} -/** - * construct a "warning" event. - * @param {error} value - * @returns {object} - */ -function warning(value) { - return { - type: "warning", - value: value - }; -} -/** - * construct a "reloading-media-source" event. - * @returns {object} - */ -function reloadingMediaSource() { - return { - type: "reloading-media-source", - value: undefined - }; -} -var INIT_EVENTS = { - loaded: loaded, - decipherabilityUpdate: decipherabilityUpdate, - manifestReady: manifestReady, - manifestUpdate: manifestUpdate, - nullRepresentation: nullRepresentation, - reloadingMediaSource: reloadingMediaSource, - stalled: stalled, - unstalled: unstalled, - warning: warning -}; -/* harmony default export */ __webpack_exports__["Z"] = (INIT_EVENTS); -/***/ }), -/***/ 7920: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -"use strict"; -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "Z": function() { return /* binding */ initialSeekAndPlay; } -}); -// UNUSED EXPORTS: waitUntilPlayable -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/filter.js -var filter = __webpack_require__(4975); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/take.js -var take = __webpack_require__(4727); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/map.js -var map = __webpack_require__(9127); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/catchError.js -var catchError = __webpack_require__(9878); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/of.js -var of = __webpack_require__(2817); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/tap.js -var tap = __webpack_require__(2006); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/shareReplay.js -var shareReplay = __webpack_require__(8515); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/mergeMap.js + 1 modules -var mergeMap = __webpack_require__(7877); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/startWith.js -var startWith = __webpack_require__(6108); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/concat.js + 1 modules -var concat = __webpack_require__(2034); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/defer.js -var defer = __webpack_require__(9917); -// EXTERNAL MODULE: ./src/utils/cast_to_observable.ts -var cast_to_observable = __webpack_require__(8117); -// EXTERNAL MODULE: ./src/utils/rx-try_catch.ts -var rx_try_catch = __webpack_require__(5561); -;// CONCATENATED MODULE: ./src/compat/play.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Call play on the media element on subscription and return the response as an - * observable. - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function play(mediaElement) { - return (0,defer/* defer */.P)(function () { - return ( - // mediaElement.play is not always a Promise. In the improbable case it - // throws, I prefer still to catch to return the error wrapped in an - // Observable - (0,rx_try_catch/* default */.Z)(function () { - return (0,cast_to_observable/* default */.Z)(mediaElement.play()); - }, undefined) - ); - }); -} -// EXTERNAL MODULE: ./src/compat/browser_compatibility_types.ts -var browser_compatibility_types = __webpack_require__(3774); -// EXTERNAL MODULE: ./src/compat/event_listeners.ts + 1 modules -var event_listeners = __webpack_require__(1381); -;// CONCATENATED MODULE: ./src/compat/when_loaded_metadata.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -/** - * Returns an observable emitting a single time, as soon as a seek is possible - * (the metadata are loaded). - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function whenLoadedMetadata$(mediaElement) { - if (mediaElement.readyState >= browser_compatibility_types/* READY_STATES.HAVE_METADATA */.c.HAVE_METADATA) { - return (0,of.of)(null); - } else { - return (0,event_listeners/* onLoadedMetadata$ */.K4)(mediaElement).pipe((0,take/* take */.q)(1)); +var DirectFileContentInitializer = /*#__PURE__*/function (_ContentInitializer) { + (0,_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)(DirectFileContentInitializer, _ContentInitializer); + function DirectFileContentInitializer(settings) { + var _this; + _this = _ContentInitializer.call(this) || this; + _this._settings = settings; + _this._initCanceller = new _utils_task_canceller__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .ZP(); + return _this; } -} -// EXTERNAL MODULE: ./src/compat/should_validate_metadata.ts -var should_validate_metadata = __webpack_require__(1669); -// EXTERNAL MODULE: ./src/errors/media_error.ts -var media_error = __webpack_require__(3714); -// EXTERNAL MODULE: ./src/log.ts + 1 modules -var log = __webpack_require__(3887); -// EXTERNAL MODULE: ./src/utils/reference.ts -var reference = __webpack_require__(5095); -// EXTERNAL MODULE: ./src/core/init/events_generators.ts -var events_generators = __webpack_require__(8343); -;// CONCATENATED MODULE: ./src/core/init/initial_seek_and_play.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - - - -/** - * Emit once as soon as the playback observation announces that the content can - * begin to be played by calling the `play` method. - * - * This depends on browser-defined criteria (e.g. the readyState status) as well - * as RxPlayer-defined ones (e.g.) not rebuffering. - * - * @param {Observable} observation$ - * @returns {Observable.} - */ -function waitUntilPlayable(observation$) { - return observation$.pipe((0,filter/* filter */.h)(function (_ref) { - var seeking = _ref.seeking, - rebuffering = _ref.rebuffering, - readyState = _ref.readyState; - return !seeking && rebuffering === null && readyState >= 1; - }), (0,take/* take */.q)(1), (0,map/* map */.U)(function () { - return undefined; - })); -} -/** - * Try to play content then handle autoplay errors. - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function autoPlay(mediaElement) { - return play(mediaElement).pipe((0,map/* map */.U)(function () { - return "autoplay"; - }), (0,catchError/* catchError */.K)(function (error) { - if (error instanceof Error && error.name === "NotAllowedError") { - // auto-play was probably prevented. - log/* default.warn */.Z.warn("Init: Media element can't play." + " It may be due to browser auto-play policies."); - return (0,of.of)("autoplay-blocked"); - } else { - throw error; - } - })); -} -/** - * Creates an Observable allowing to seek at the initially wanted position and - * to play if autoPlay is wanted. - * @param {Object} args - * @returns {Object} - */ -function initialSeekAndPlay(_ref2) { - var mediaElement = _ref2.mediaElement, - playbackObserver = _ref2.playbackObserver, - startTime = _ref2.startTime, - mustAutoPlay = _ref2.mustAutoPlay; - var initialSeekPerformed = (0,reference/* createSharedReference */.$l)(false); - var initialPlayPerformed = (0,reference/* createSharedReference */.$l)(false); - var seek$ = whenLoadedMetadata$(mediaElement).pipe((0,take/* take */.q)(1), (0,tap/* tap */.b)(function () { - var initialTime = typeof startTime === "function" ? startTime() : startTime; - log/* default.info */.Z.info("Init: Set initial time", initialTime); - playbackObserver.setCurrentTime(initialTime); - initialSeekPerformed.setValue(true); - initialSeekPerformed.finish(); - }), (0,shareReplay/* shareReplay */.d)({ - refCount: true - })); - var seekAndPlay$ = seek$.pipe((0,mergeMap/* mergeMap */.z)(function () { - if (!(0,should_validate_metadata/* default */.Z)() || mediaElement.duration > 0) { - return waitUntilPlayable(playbackObserver.getReference().asObservable()); - } else { - var error = new media_error/* default */.Z("MEDIA_ERR_NOT_LOADED_METADATA", "Cannot load automatically: your browser " + "falsely announced having loaded the content."); - return waitUntilPlayable(playbackObserver.getReference().asObservable()).pipe((0,startWith/* startWith */.O)(events_generators/* default.warning */.Z.warning(error))); - } - }), (0,mergeMap/* mergeMap */.z)(function (evt) { - if (evt !== undefined) { - return (0,of.of)(evt); + var _proto = DirectFileContentInitializer.prototype; + _proto.prepare = function prepare() { + return; // Directfile contents do not have any preparation + }; + _proto.start = function start(mediaElement, playbackObserver) { + var _this2 = this; + var cancelSignal = this._initCanceller.signal; + var _this$_settings = this._settings, + keySystems = _this$_settings.keySystems, + speed = _this$_settings.speed, + url = _this$_settings.url; + (0,_compat__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .Z)(mediaElement); + if (url == null) { + throw new Error("No URL for a DirectFile content"); } - log/* default.info */.Z.info("Init: Can begin to play content"); - if (!mustAutoPlay) { - if (mediaElement.autoplay) { - log/* default.warn */.Z.warn("Init: autoplay is enabled on HTML media element. " + "Media will play as soon as possible."); + var decryptionRef = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .ZP)(null); + decryptionRef.finish(); + var drmInitRef = (0,_utils_initialize_content_decryption__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z)(mediaElement, keySystems, decryptionRef, { + onError: function onError(err) { + return _this2._onFatalError(err); + }, + onWarning: function onWarning(err) { + return _this2.trigger("warning", err); + } + }, cancelSignal); + /** Translate errors coming from the media element into RxPlayer errors. */ + (0,_utils_throw_on_media_error__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .Z)(mediaElement, function (error) { + return _this2._onFatalError(error); + }, cancelSignal); + /** + * Class trying to avoid various stalling situations, emitting "stalled" + * events when it cannot, as well as "unstalled" events when it get out of one. + */ + var rebufferingController = new _utils_rebuffering_controller__WEBPACK_IMPORTED_MODULE_6__/* ["default"] */ .Z(playbackObserver, null, speed); + rebufferingController.addEventListener("stalled", function (evt) { + return _this2.trigger("stalled", evt); + }); + rebufferingController.addEventListener("unstalled", function () { + return _this2.trigger("unstalled", null); + }); + rebufferingController.addEventListener("warning", function (err) { + return _this2.trigger("warning", err); + }); + cancelSignal.register(function () { + rebufferingController.destroy(); + }); + rebufferingController.start(); + drmInitRef.onUpdate(function (evt, stopListeningToDrmUpdates) { + if (evt.initializationState.type === "uninitialized") { + return; } - initialPlayPerformed.setValue(true); - initialPlayPerformed.finish(); - return (0,of.of)({ - type: "skipped" + stopListeningToDrmUpdates(); + // Start everything! (Just put the URL in the element's src). + _log__WEBPACK_IMPORTED_MODULE_7__/* ["default"].info */ .Z.info("Setting URL to HTMLMediaElement", url); + mediaElement.src = url; + cancelSignal.register(function () { + (0,_compat__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .Z)(mediaElement); }); - } - return autoPlay(mediaElement).pipe((0,mergeMap/* mergeMap */.z)(function (autoplayEvt) { - initialPlayPerformed.setValue(true); - initialPlayPerformed.finish(); - if (autoplayEvt === "autoplay") { - return (0,of.of)({ - type: "autoplay" + if (evt.initializationState.type === "awaiting-media-link") { + evt.initializationState.value.isMediaLinked.setValue(true); + drmInitRef.onUpdate(function (newDrmStatus, stopListeningToDrmUpdatesAgain) { + if (newDrmStatus.initializationState.type === "initialized") { + stopListeningToDrmUpdatesAgain(); + _this2._seekAndPlay(mediaElement, playbackObserver); + return; + } + }, { + emitCurrentValue: true, + clearSignal: cancelSignal }); } else { - var error = new media_error/* default */.Z("MEDIA_ERR_BLOCKED_AUTOPLAY", "Cannot trigger auto-play automatically: " + "your browser does not allow it."); - return (0,concat/* concat */.z)((0,of.of)(events_generators/* default.warning */.Z.warning(error)), (0,of.of)({ - type: "autoplay-blocked" - })); + _this2._seekAndPlay(mediaElement, playbackObserver); + return; } - })); - }), (0,shareReplay/* shareReplay */.d)({ - refCount: true - })); - return { - seekAndPlay$: seekAndPlay$, - initialPlayPerformed: initialPlayPerformed, - initialSeekPerformed: initialSeekPerformed + }, { + emitCurrentValue: true, + clearSignal: cancelSignal + }); }; -} - -/***/ }), - -/***/ 8969: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "Z": function() { return /* binding */ initializeDirectfileContent; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/empty.js -var empty = __webpack_require__(1545); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/share.js -var share = __webpack_require__(5583); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/filter.js -var filter = __webpack_require__(4975); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/take.js -var take = __webpack_require__(4727); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/mergeMap.js + 1 modules -var mergeMap = __webpack_require__(7877); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/switchMap.js -var switchMap = __webpack_require__(4978); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/of.js -var of = __webpack_require__(2817); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/merge.js -var merge = __webpack_require__(3071); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/ignoreElements.js -var ignoreElements = __webpack_require__(533); -// EXTERNAL MODULE: ./src/compat/clear_element_src.ts -var clear_element_src = __webpack_require__(5767); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Observable.js + 1 modules -var Observable = __webpack_require__(1480); -// EXTERNAL MODULE: ./src/log.ts + 1 modules -var log = __webpack_require__(3887); -;// CONCATENATED MODULE: ./src/compat/set_element_src.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -/** - * Set an URL to the element's src. - * Emit ``undefined`` when done. - * Unlink src on unsubscription. - * - * @param {HTMLMediaElement} mediaElement - * @param {string} url - * @returns {Observable} - */ -function setElementSrc$(mediaElement, url) { - return new Observable/* Observable */.y(function (observer) { - log/* default.info */.Z.info("Setting URL to HTMLMediaElement", url); - mediaElement.src = url; - observer.next(undefined); - return function () { - (0,clear_element_src/* default */.Z)(mediaElement); + _proto.updateContentUrls = function updateContentUrls(_urls, _refreshNow) { + throw new Error("Cannot update content URL of directfile contents"); + }; + _proto.dispose = function dispose() { + this._initCanceller.cancel(); + }; + _proto._onFatalError = function _onFatalError(err) { + this._initCanceller.cancel(); + this.trigger("error", err); + }; + _proto._seekAndPlay = function _seekAndPlay(mediaElement, playbackObserver) { + var _this3 = this; + var cancelSignal = this._initCanceller.signal; + var _this$_settings2 = this._settings, + autoPlay = _this$_settings2.autoPlay, + startAt = _this$_settings2.startAt; + var initialTime = function initialTime() { + _log__WEBPACK_IMPORTED_MODULE_7__/* ["default"].debug */ .Z.debug("Init: Calculating initial time"); + var initTime = getDirectFileInitialTime(mediaElement, startAt); + _log__WEBPACK_IMPORTED_MODULE_7__/* ["default"].debug */ .Z.debug("Init: Initial time calculated:", initTime); + return initTime; }; - }); -} -// EXTERNAL MODULE: ./src/utils/defer_subscriptions.ts + 5 modules -var defer_subscriptions = __webpack_require__(8333); -// EXTERNAL MODULE: ./src/core/init/emit_loaded_event.ts + 1 modules -var emit_loaded_event = __webpack_require__(5039); -// EXTERNAL MODULE: ./src/core/init/initial_seek_and_play.ts + 2 modules -var initial_seek_and_play = __webpack_require__(7920); -// EXTERNAL MODULE: ./src/core/init/link_drm_and_content.ts + 1 modules -var link_drm_and_content = __webpack_require__(9607); -// EXTERNAL MODULE: ./src/core/init/rebuffering_controller.ts + 1 modules -var rebuffering_controller = __webpack_require__(342); -// EXTERNAL MODULE: ./src/core/init/throw_on_media_error.ts -var throw_on_media_error = __webpack_require__(2447); -;// CONCATENATED MODULE: ./src/core/init/initialize_directfile.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * /!\ This file is feature-switchable. - * It always should be imported through the `features` object. - */ - - - - - - - - - -// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default -// first type parameter as `any` instead of the perfectly fine `unknown`, -// leading to linter issues, as it forbids the usage of `any`. -// This is why we're disabling the eslint rule. -/* eslint-disable @typescript-eslint/no-unsafe-argument */ + (0,_utils_initial_seek_and_play__WEBPACK_IMPORTED_MODULE_8__/* ["default"] */ .Z)(mediaElement, playbackObserver, initialTime, autoPlay, function (err) { + return _this3.trigger("warning", err); + }, cancelSignal).autoPlayResult.then(function () { + return (0,_utils_get_loaded_reference__WEBPACK_IMPORTED_MODULE_9__/* ["default"] */ .Z)(playbackObserver, mediaElement, true, cancelSignal).onUpdate(function (isLoaded, stopListening) { + if (isLoaded) { + stopListening(); + _this3.trigger("loaded", { + segmentBuffersStore: null + }); + } + }, { + emitCurrentValue: true, + clearSignal: cancelSignal + }); + })["catch"](function (err) { + if (!cancelSignal.isCancelled()) { + _this3._onFatalError(err); + } + }); + }; + return DirectFileContentInitializer; +}(_types__WEBPACK_IMPORTED_MODULE_10__/* .ContentInitializer */ .K); /** * calculate initial time as a position in seconds. * @param {HTMLMediaElement} mediaElement * @param {Object|undefined} startAt * @returns {number} */ + function getDirectFileInitialTime(mediaElement, startAt) { if (startAt == null) { return 0; @@ -9724,7 +9367,7 @@ function getDirectFileInitialTime(mediaElement, startAt) { } var duration = mediaElement.duration; if (duration == null || !isFinite(duration)) { - log/* default.warn */.Z.warn("startAt.fromLastPosition set but no known duration, " + "beginning at 0."); + _log__WEBPACK_IMPORTED_MODULE_7__/* ["default"].warn */ .Z.warn("startAt.fromLastPosition set but no known duration, " + "beginning at 0."); return 0; } if (typeof startAt.fromLastPosition === "number") { @@ -9741,90 +9384,70 @@ function getDirectFileInitialTime(mediaElement, startAt) { } return 0; } + +/***/ }), + +/***/ 379: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "K": function() { return /* binding */ ContentInitializer; } +/* harmony export */ }); +/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4578); +/* harmony import */ var _utils_event_emitter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1959); + /** - * Launch a content in "Directfile mode". - * @param {Object} directfileOptions - * @returns {Observable} + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ -function initializeDirectfileContent(_ref) { - var autoPlay = _ref.autoPlay, - keySystems = _ref.keySystems, - mediaElement = _ref.mediaElement, - playbackObserver = _ref.playbackObserver, - speed = _ref.speed, - startAt = _ref.startAt, - url = _ref.url; - (0,clear_element_src/* default */.Z)(mediaElement); - if (url == null) { - throw new Error("No URL for a DirectFile content"); - } - // Start everything! (Just put the URL in the element's src). - var linkURL$ = setElementSrc$(mediaElement, url); - var initialTime = function initialTime() { - log/* default.debug */.Z.debug("Init: Calculating initial time"); - var initTime = getDirectFileInitialTime(mediaElement, startAt); - log/* default.debug */.Z.debug("Init: Initial time calculated:", initTime); - return initTime; - }; - var _initialSeekAndPlay = (0,initial_seek_and_play/* default */.Z)({ - mediaElement: mediaElement, - playbackObserver: playbackObserver, - startTime: initialTime, - mustAutoPlay: autoPlay - }), - seekAndPlay$ = _initialSeekAndPlay.seekAndPlay$; - /** Initialize decryption capabilities and the HTMLMediaElement's src attribute. */ - var drmEvents$ = (0,link_drm_and_content/* default */.Z)(mediaElement, keySystems, empty/* EMPTY */.E, linkURL$).pipe((0,defer_subscriptions/* default */.Z)(), (0,share/* share */.B)()); - // Translate errors coming from the media element into RxPlayer errors - // through a throwing Observable. - var mediaError$ = (0,throw_on_media_error/* default */.Z)(mediaElement); - var observation$ = playbackObserver.getReference().asObservable(); - /** - * Observable trying to avoid various stalling situations, emitting "stalled" - * events when it cannot, as well as "unstalled" events when it get out of one. - */ - var rebuffer$ = (0,rebuffering_controller/* default */.Z)(playbackObserver, null, speed, empty/* EMPTY */.E, empty/* EMPTY */.E); - /** - * Emit a "loaded" events once the initial play has been performed and the - * media can begin playback. - * Also emits warning events if issues arise when doing so. - */ - var loadingEvts$ = drmEvents$.pipe((0,filter/* filter */.h)(function (evt) { - return evt.type === "decryption-ready" || evt.type === "decryption-disabled"; - }), (0,take/* take */.q)(1), (0,mergeMap/* mergeMap */.z)(function () { - return seekAndPlay$; - }), (0,switchMap/* switchMap */.w)(function (evt) { - if (evt.type === "warning") { - return (0,of.of)(evt); - } - return (0,emit_loaded_event/* default */.Z)(observation$, mediaElement, null, true); - })); - return (0,merge/* merge */.T)(loadingEvts$, drmEvents$.pipe((0,ignoreElements/* ignoreElements */.l)()), mediaError$, rebuffer$); -} + +/** + * Class allowing to start playing a content on an `HTMLMediaElement`. + * + * The actual constructor arguments depend on the `ContentInitializer` defined, + * but should reflect all potential configuration wanted relative to this + * content's playback. + * + * Various events may be emitted by a `ContentInitializer`. However, no event + * should be emitted before `prepare` or `start` is called and no event should + * be emitted after `dispose` is called. + */ +var ContentInitializer = /*#__PURE__*/function (_EventEmitter) { + (0,_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)(ContentInitializer, _EventEmitter); + function ContentInitializer() { + return _EventEmitter.apply(this, arguments) || this; + } + return ContentInitializer; +}(_utils_event_emitter__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z); +// a decryption key) /***/ }), -/***/ 9607: +/***/ 1757: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; // EXPORTS __webpack_require__.d(__webpack_exports__, { - "Z": function() { return /* binding */ linkDrmAndContent; } + "Z": function() { return /* binding */ getLoadedReference; } }); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/merge.js -var merge = __webpack_require__(3071); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/map.js -var map = __webpack_require__(9127); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Observable.js + 1 modules -var Observable = __webpack_require__(1480); -// EXTERNAL MODULE: ./src/compat/event_listeners.ts + 1 modules -var event_listeners = __webpack_require__(1381); -// EXTERNAL MODULE: ./src/compat/eme/custom_media_keys/index.ts + 7 modules -var custom_media_keys = __webpack_require__(6139); -;// CONCATENATED MODULE: ./src/compat/has_eme_apis.ts +// EXTERNAL MODULE: ./src/compat/browser_detection.ts +var browser_detection = __webpack_require__(3666); +;// CONCATENATED MODULE: ./src/compat/should_wait_for_data_before_loaded.ts /** * Copyright 2015 CANAL+ Group * @@ -9842,22 +9465,26 @@ var custom_media_keys = __webpack_require__(6139); */ /** - * Returns true if the browser has the minimum needed EME APIs to decrypt a - * content. + * On some browsers, the ready state might never go above `1` when autoplay is + * blocked. On these cases, for now, we just advertise the content as "loaded". + * We might go into BUFFERING just after that state, but that's a small price to + * pay. + * @param {Boolean} isDirectfile * @returns {Boolean} */ -function hasEMEAPIs() { - return typeof custom_media_keys/* requestMediaKeySystemAccess */.N === "function"; +function shouldWaitForDataBeforeLoaded(isDirectfile, mustPlayInline) { + if (isDirectfile && browser_detection/* isSafariMobile */.SB) { + return mustPlayInline; + } + return true; } -// EXTERNAL MODULE: ./src/errors/encrypted_media_error.ts -var encrypted_media_error = __webpack_require__(5157); -// EXTERNAL MODULE: ./src/features/index.ts -var features = __webpack_require__(7874); -// EXTERNAL MODULE: ./src/log.ts + 1 modules -var log = __webpack_require__(3887); -// EXTERNAL MODULE: ./src/core/decrypt/content_decryptor.ts + 32 modules -var content_decryptor = __webpack_require__(7425); -;// CONCATENATED MODULE: ./src/core/init/link_drm_and_content.ts +// EXTERNAL MODULE: ./src/compat/should_validate_metadata.ts +var should_validate_metadata = __webpack_require__(1669); +// EXTERNAL MODULE: ./src/utils/reference.ts +var reference = __webpack_require__(5095); +// EXTERNAL MODULE: ./src/utils/task_canceller.ts +var task_canceller = __webpack_require__(288); +;// CONCATENATED MODULE: ./src/core/init/utils/get_loaded_reference.ts /** * Copyright 2015 CANAL+ Group * @@ -9876,109 +9503,384 @@ var content_decryptor = __webpack_require__(7425); +/** + * Returns an `IReadOnlySharedReference` that switches to `true` once the + * content is considered loaded (i.e. once it can begin to be played). + * @param {Object} playbackObserver + * @param {HTMLMediaElement} mediaElement + * @param {boolean} isDirectfile - `true` if this is a directfile content + * @param {Object} cancelSignal + * @returns {Object} + */ +function getLoadedReference(playbackObserver, mediaElement, isDirectfile, cancelSignal) { + var listenCanceller = new task_canceller/* default */.ZP(); + listenCanceller.linkToSignal(cancelSignal); + var isLoaded = (0,reference/* default */.ZP)(false, listenCanceller.signal); + playbackObserver.listen(function (observation) { + if (observation.rebuffering !== null || observation.freezing !== null || observation.readyState === 0) { + return; + } + if (!shouldWaitForDataBeforeLoaded(isDirectfile, mediaElement.hasAttribute("playsinline"))) { + if (mediaElement.duration > 0) { + isLoaded.setValue(true); + listenCanceller.cancel(); + return; + } + } + if (observation.readyState >= 3 && observation.currentRange !== null) { + if (!(0,should_validate_metadata/* default */.Z)() || mediaElement.duration > 0) { + isLoaded.setValue(true); + listenCanceller.cancel(); + return; + } + } + }, { + includeLastObservation: true, + clearSignal: listenCanceller.signal + }); + return isLoaded; +} +/***/ }), +/***/ 8833: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -var onEncrypted$ = event_listeners/* onEncrypted$ */.Oh; +"use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "Z": function() { return /* binding */ performInitialSeekAndPlay; } +/* harmony export */ }); +/* harmony import */ var _compat__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1669); +/* harmony import */ var _compat_browser_compatibility_types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3774); +/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3714); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3887); +/* harmony import */ var _utils_reference__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5095); /** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + + + +/** + * Seek as soon as possible at the initially wanted position and play if + * autoPlay is wanted. * @param {HTMLMediaElement} mediaElement - * @param {Array.} keySystems - * @param {Observable} contentProtections$ - * @param {Promise} linkingMedia$ - * @returns {Observable} - */ -function linkDrmAndContent(mediaElement, keySystems, contentProtections$, linkingMedia$) { - var encryptedEvents$ = (0,merge/* merge */.T)(onEncrypted$(mediaElement), contentProtections$); - if (features/* default.ContentDecryptor */.Z.ContentDecryptor == null) { - return (0,merge/* merge */.T)(encryptedEvents$.pipe((0,map/* map */.U)(function () { - log/* default.error */.Z.error("Init: Encrypted event but EME feature not activated"); - throw new encrypted_media_error/* default */.Z("MEDIA_IS_ENCRYPTED_ERROR", "EME feature not activated."); - })), linkingMedia$.pipe((0,map/* map */.U)(function (mediaSource) { - return { - type: "decryption-disabled", - value: { - drmSystemId: undefined, - mediaSource: mediaSource - } - }; - }))); - } - if (keySystems.length === 0) { - return (0,merge/* merge */.T)(encryptedEvents$.pipe((0,map/* map */.U)(function () { - log/* default.error */.Z.error("Init: Ciphered media and no keySystem passed"); - throw new encrypted_media_error/* default */.Z("MEDIA_IS_ENCRYPTED_ERROR", "Media is encrypted and no `keySystems` given"); - })), linkingMedia$.pipe((0,map/* map */.U)(function (mediaSource) { - return { - type: "decryption-disabled", - value: { - drmSystemId: undefined, - mediaSource: mediaSource - } - }; - }))); + * @param {Object} playbackObserver + * @param {number|Function} startTime + * @param {boolean} mustAutoPlay + * @param {Function} onWarning + * @param {Object} cancelSignal + * @returns {Object} + */ +function performInitialSeekAndPlay(mediaElement, playbackObserver, startTime, mustAutoPlay, onWarning, cancelSignal) { + var resolveAutoPlay; + var rejectAutoPlay; + var autoPlayResult = new Promise(function (res, rej) { + resolveAutoPlay = res; + rejectAutoPlay = rej; + }); + var initialSeekPerformed = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_0__/* .createSharedReference */ .$l)(false, cancelSignal); + var initialPlayPerformed = (0,_utils_reference__WEBPACK_IMPORTED_MODULE_0__/* .createSharedReference */ .$l)(false, cancelSignal); + mediaElement.addEventListener("loadedmetadata", onLoadedMetadata); + if (mediaElement.readyState >= _compat_browser_compatibility_types__WEBPACK_IMPORTED_MODULE_1__/* .READY_STATES.HAVE_METADATA */ .c.HAVE_METADATA) { + onLoadedMetadata(); + } + var deregisterCancellation = cancelSignal.register(function (err) { + mediaElement.removeEventListener("loadedmetadata", onLoadedMetadata); + rejectAutoPlay(err); + }); + return { + autoPlayResult: autoPlayResult, + initialPlayPerformed: initialPlayPerformed, + initialSeekPerformed: initialSeekPerformed + }; + function onLoadedMetadata() { + mediaElement.removeEventListener("loadedmetadata", onLoadedMetadata); + var initialTime = typeof startTime === "function" ? startTime() : startTime; + _log__WEBPACK_IMPORTED_MODULE_2__/* ["default"].info */ .Z.info("Init: Set initial time", initialTime); + playbackObserver.setCurrentTime(initialTime); + initialSeekPerformed.setValue(true); + initialSeekPerformed.finish(); + if ((0,_compat__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z)() && mediaElement.duration === 0) { + var error = new _errors__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z("MEDIA_ERR_NOT_LOADED_METADATA", "Cannot load automatically: your browser " + "falsely announced having loaded the content."); + onWarning(error); + } + if (cancelSignal.isCancelled()) { + return; + } + playbackObserver.listen(function (observation, stopListening) { + if (!observation.seeking && observation.rebuffering === null && observation.readyState >= 1) { + stopListening(); + onPlayable(); + } + }, { + includeLastObservation: true, + clearSignal: cancelSignal + }); } - if (!hasEMEAPIs()) { - return (0,merge/* merge */.T)(encryptedEvents$.pipe((0,map/* map */.U)(function () { - log/* default.error */.Z.error("Init: Encrypted event but no EME API available"); - throw new encrypted_media_error/* default */.Z("MEDIA_IS_ENCRYPTED_ERROR", "Encryption APIs not found."); - })), linkingMedia$.pipe((0,map/* map */.U)(function (mediaSource) { - return { - type: "decryption-disabled", - value: { - drmSystemId: undefined, - mediaSource: mediaSource + function onPlayable() { + var _a; + _log__WEBPACK_IMPORTED_MODULE_2__/* ["default"].info */ .Z.info("Init: Can begin to play content"); + if (!mustAutoPlay) { + if (mediaElement.autoplay) { + _log__WEBPACK_IMPORTED_MODULE_2__/* ["default"].warn */ .Z.warn("Init: autoplay is enabled on HTML media element. " + "Media will play as soon as possible."); + } + initialPlayPerformed.setValue(true); + initialPlayPerformed.finish(); + deregisterCancellation(); + return resolveAutoPlay({ + type: "skipped" + }); + } + var playResult; + try { + playResult = (_a = mediaElement.play()) !== null && _a !== void 0 ? _a : Promise.resolve(); + } catch (playError) { + deregisterCancellation(); + return rejectAutoPlay(playError); + } + playResult.then(function () { + if (cancelSignal.isCancelled()) { + return; + } + initialPlayPerformed.setValue(true); + initialPlayPerformed.finish(); + deregisterCancellation(); + return resolveAutoPlay({ + type: "autoplay" + }); + })["catch"](function (playError) { + deregisterCancellation(); + if (cancelSignal.isCancelled()) { + return; + } + if (playError instanceof Error && playError.name === "NotAllowedError") { + // auto-play was probably prevented. + _log__WEBPACK_IMPORTED_MODULE_2__/* ["default"].warn */ .Z.warn("Init: Media element can't play." + " It may be due to browser auto-play policies."); + var error = new _errors__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z("MEDIA_ERR_BLOCKED_AUTOPLAY", "Cannot trigger auto-play automatically: " + "your browser does not allow it."); + onWarning(error); + if (cancelSignal.isCancelled()) { + return; } - }; - }))); - } - log/* default.debug */.Z.debug("Init: Creating ContentDecryptor"); - var ContentDecryptor = features/* default.ContentDecryptor */.Z.ContentDecryptor; - return new Observable/* Observable */.y(function (obs) { - var contentDecryptor = new ContentDecryptor(mediaElement, keySystems); - var mediaSub; - contentDecryptor.addEventListener("stateChange", function (state) { - if (state === content_decryptor/* ContentDecryptorState.WaitingForAttachment */.u.WaitingForAttachment) { - contentDecryptor.removeEventListener("stateChange"); - mediaSub = linkingMedia$.subscribe(function (mediaSource) { - contentDecryptor.addEventListener("stateChange", function (newState) { - if (newState === content_decryptor/* ContentDecryptorState.ReadyForContent */.u.ReadyForContent) { - obs.next({ - type: "decryption-ready", - value: { - drmSystemId: contentDecryptor.systemId, - mediaSource: mediaSource - } - }); - contentDecryptor.removeEventListener("stateChange"); - } - }); - contentDecryptor.attach(); + return resolveAutoPlay({ + type: "autoplay-blocked" }); + } else { + rejectAutoPlay(playError); + } + }); + } +} + +/***/ }), + +/***/ 8799: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; + +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + "Z": function() { return /* binding */ initializeContentDecryption; } +}); + +// EXTERNAL MODULE: ./src/compat/eme/custom_media_keys/index.ts + 7 modules +var custom_media_keys = __webpack_require__(6139); +;// CONCATENATED MODULE: ./src/compat/has_eme_apis.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Returns true if the browser has the minimum needed EME APIs to decrypt a + * content. + * @returns {Boolean} + */ +function hasEMEAPIs() { + return typeof custom_media_keys/* requestMediaKeySystemAccess */.N === "function"; +} +// EXTERNAL MODULE: ./src/errors/encrypted_media_error.ts +var encrypted_media_error = __webpack_require__(5157); +// EXTERNAL MODULE: ./src/log.ts + 1 modules +var log = __webpack_require__(3887); +// EXTERNAL MODULE: ./src/utils/reference.ts +var reference = __webpack_require__(5095); +// EXTERNAL MODULE: ./src/utils/task_canceller.ts +var task_canceller = __webpack_require__(288); +// EXTERNAL MODULE: ./src/core/decrypt/index.ts +var decrypt = __webpack_require__(1266); +// EXTERNAL MODULE: ./src/core/decrypt/content_decryptor.ts + 31 modules +var content_decryptor = __webpack_require__(1960); +;// CONCATENATED MODULE: ./src/core/init/utils/initialize_content_decryption.ts + + + + + + +/** + * Initialize content decryption capabilities on the given `HTMLMediaElement`. + * + * You can call this function even if you don't want decrytpion capabilities, in + * which case you can just set the `keySystems` option as an empty array. + * In this situation, the returned object will directly correspond to an + * "`initialized`" state and the `onError` callback will be triggered as soon + * as protection information is received. + * + * @param {HTMLMediaElement} mediaElement - `HTMLMediaElement` on which content + * decryption may be wanted. + * @param {Array.} keySystems - Key system configuration(s) wanted + * Empty array if no content decryption capability is wanted. + * @param {Object} protectionRef - Reference through which content + * protection initialization data will be sent through. + * @param {Object} callbacks - Callbacks called at various decryption-related + * events. + * @param {Object} cancelSignal - When that signal emits, this function will + * stop listening to various events as well as items sent through the + * `protectionRef` parameter. + * @returns {Object} - Reference emitting the current status regarding DRM + * initialization. + */ +function initializeContentDecryption(mediaElement, keySystems, protectionRef, callbacks, cancelSignal) { + if (keySystems.length === 0) { + protectionRef.onUpdate(function (data, stopListening) { + if (data === null) { + // initial value + return; } + stopListening(); + log/* default.error */.Z.error("Init: Encrypted event but EME feature not activated"); + var err = new encrypted_media_error/* default */.Z("MEDIA_IS_ENCRYPTED_ERROR", "EME feature not activated."); + callbacks.onError(err); + }, { + clearSignal: cancelSignal }); - contentDecryptor.addEventListener("error", function (e) { - obs.error(e); + var ref = (0,reference/* default */.ZP)({ + initializationState: { + type: "initialized", + value: null + }, + drmSystemId: undefined }); - contentDecryptor.addEventListener("warning", function (w) { - obs.next({ - type: "warning", - value: w - }); + ref.finish(); // We know that no new value will be triggered + return ref; + } else if (!hasEMEAPIs()) { + protectionRef.onUpdate(function (data, stopListening) { + if (data === null) { + // initial value + return; + } + stopListening(); + log/* default.error */.Z.error("Init: Encrypted event but no EME API available"); + var err = new encrypted_media_error/* default */.Z("MEDIA_IS_ENCRYPTED_ERROR", "Encryption APIs not found."); + callbacks.onError(err); + }, { + clearSignal: cancelSignal }); - var protectionDataSub = contentProtections$.subscribe(function (data) { - contentDecryptor.onInitializationData(data); + var _ref = (0,reference/* default */.ZP)({ + initializationState: { + type: "initialized", + value: null + }, + drmSystemId: undefined }); - return function () { - protectionDataSub.unsubscribe(); - mediaSub === null || mediaSub === void 0 ? void 0 : mediaSub.unsubscribe(); - contentDecryptor.dispose(); - }; + _ref.finish(); // We know that no new value will be triggered + return _ref; + } + var decryptorCanceller = new task_canceller/* default */.ZP(); + decryptorCanceller.linkToSignal(cancelSignal); + var drmStatusRef = (0,reference/* default */.ZP)({ + initializationState: { + type: "uninitialized", + value: null + }, + drmSystemId: undefined + }, cancelSignal); + log/* default.debug */.Z.debug("Init: Creating ContentDecryptor"); + var contentDecryptor = new decrypt/* default */.ZP(mediaElement, keySystems); + contentDecryptor.addEventListener("stateChange", function (state) { + if (state === content_decryptor/* ContentDecryptorState.WaitingForAttachment */.u.WaitingForAttachment) { + var isMediaLinked = (0,reference/* default */.ZP)(false); + isMediaLinked.onUpdate(function (isAttached, stopListening) { + if (isAttached) { + stopListening(); + if (state === content_decryptor/* ContentDecryptorState.WaitingForAttachment */.u.WaitingForAttachment) { + contentDecryptor.attach(); + } + } + }, { + clearSignal: decryptorCanceller.signal + }); + drmStatusRef.setValue({ + initializationState: { + type: "awaiting-media-link", + value: { + isMediaLinked: isMediaLinked + } + }, + drmSystemId: contentDecryptor.systemId + }); + } else if (state === content_decryptor/* ContentDecryptorState.ReadyForContent */.u.ReadyForContent) { + drmStatusRef.setValue({ + initializationState: { + type: "initialized", + value: null + }, + drmSystemId: contentDecryptor.systemId + }); + contentDecryptor.removeEventListener("stateChange"); + } + }); + contentDecryptor.addEventListener("error", function (error) { + decryptorCanceller.cancel(); + callbacks.onError(error); }); + contentDecryptor.addEventListener("warning", function (error) { + callbacks.onWarning(error); + }); + protectionRef.onUpdate(function (data) { + if (data === null) { + return; + } + contentDecryptor.onInitializationData(data); + }, { + clearSignal: decryptorCanceller.signal + }); + decryptorCanceller.signal.register(function () { + contentDecryptor.dispose(); + }); + return drmStatusRef; } /***/ }), -/***/ 342: +/***/ 6199: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -9988,20 +9890,8 @@ __webpack_require__.d(__webpack_exports__, { "Z": function() { return /* binding */ RebufferingController; } }); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/withLatestFrom.js -var withLatestFrom = __webpack_require__(3428); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/scan.js + 1 modules -var scan = __webpack_require__(3074); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/tap.js -var tap = __webpack_require__(2006); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/ignoreElements.js -var ignoreElements = __webpack_require__(533); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/map.js -var map = __webpack_require__(9127); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/merge.js -var merge = __webpack_require__(3071); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/finalize.js -var finalize = __webpack_require__(3286); +// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js +var inheritsLoose = __webpack_require__(4578); // EXTERNAL MODULE: ./src/compat/browser_detection.ts var browser_detection = __webpack_require__(3666); ;// CONCATENATED MODULE: ./src/compat/is_seeking_approximate.ts @@ -10040,13 +9930,14 @@ var config = __webpack_require__(6872); var media_error = __webpack_require__(3714); // EXTERNAL MODULE: ./src/log.ts + 1 modules var log = __webpack_require__(3887); +// EXTERNAL MODULE: ./src/utils/event_emitter.ts +var event_emitter = __webpack_require__(1959); // EXTERNAL MODULE: ./src/utils/ranges.ts var ranges = __webpack_require__(2829); // EXTERNAL MODULE: ./src/utils/task_canceller.ts var task_canceller = __webpack_require__(288); -// EXTERNAL MODULE: ./src/core/stream/events_generators.ts -var events_generators = __webpack_require__(8567); -;// CONCATENATED MODULE: ./src/core/init/rebuffering_controller.ts +;// CONCATENATED MODULE: ./src/core/init/utils/rebuffering_controller.ts + /** * Copyright 2015 CANAL+ Group * @@ -10069,7 +9960,6 @@ var events_generators = __webpack_require__(8567); - /** * Work-around rounding errors with floating points by setting an acceptable, * very short, deviation when checking equalities. @@ -10082,228 +9972,249 @@ var EPSILON = 1 / 60; * * Emit "stalled" then "unstalled" respectively when an unavoidable stall is * encountered and exited. - * @param {object} playbackObserver - emit the current playback conditions. - * @param {Object} manifest - The Manifest of the currently-played content. - * @param {Object} speed - The last speed set by the user - * @param {Observable} lockedStream$ - Emit information on currently "locked" - * streams. - * @param {Observable} discontinuityUpdate$ - Observable emitting encountered - * discontinuities for loaded Period and buffer types. - * @returns {Observable} - */ -function RebufferingController(playbackObserver, manifest, speed, lockedStream$, discontinuityUpdate$) { - var initialDiscontinuitiesStore = []; - /** - * Emit every known audio and video buffer discontinuities in chronological - * order (first ordered by Period's start, then by bufferType in any order. - */ - var discontinuitiesStore$ = discontinuityUpdate$.pipe((0,withLatestFrom/* withLatestFrom */.M)(playbackObserver.getReference().asObservable()), (0,scan/* scan */.R)(function (discontinuitiesStore, _ref) { - var evt = _ref[0], - observation = _ref[1]; - return updateDiscontinuitiesStore(discontinuitiesStore, evt, observation); - }, initialDiscontinuitiesStore)); - /** - * On some devices (right now only seen on Tizen), seeking through the - * `currentTime` property can lead to the browser re-seeking once the - * segments have been loaded to improve seeking performances (for - * example, by seeking right to an intra video frame). - * In that case, we risk being in a conflict with that behavior: if for - * example we encounter a small discontinuity at the position the browser - * seeks to, we will seek over it, the browser would seek back and so on. - * - * This variable allows to store the last known position we were seeking to - * so we can detect when the browser seeked back (to avoid performing another - * seek after that). When browsers seek back to a position behind a - * discontinuity, they are usually able to skip them without our help. - */ - var lastSeekingPosition = null; - /** - * In some conditions (see `lastSeekingPosition`), we might want to not - * automatically seek over discontinuities because the browser might do it - * itself instead. - * In that case, we still want to perform the seek ourselves if the browser - * doesn't do it after sufficient time. - * This variable allows to store the timestamp at which a discontinuity began - * to be ignored. - */ - var ignoredStallTimeStamp = null; - var prevFreezingState; - /** - * If we're rebuffering waiting on data of a "locked stream", seek into the - * Period handled by that stream to unlock the situation. - */ - var unlock$ = lockedStream$.pipe((0,withLatestFrom/* withLatestFrom */.M)(playbackObserver.getReference().asObservable()), (0,tap/* tap */.b)(function (_ref2) { - var lockedStreamEvt = _ref2[0], - observation = _ref2[1]; - var _a; - if (!observation.rebuffering || observation.paused || speed.getValue() <= 0 || lockedStreamEvt.bufferType !== "audio" && lockedStreamEvt.bufferType !== "video") { + */ +var RebufferingController = /*#__PURE__*/function (_EventEmitter) { + (0,inheritsLoose/* default */.Z)(RebufferingController, _EventEmitter); + /** + * @param {object} playbackObserver - emit the current playback conditions. + * @param {Object} manifest - The Manifest of the currently-played content. + * @param {Object} speed - The last speed set by the user + */ + function RebufferingController(playbackObserver, manifest, speed) { + var _this; + _this = _EventEmitter.call(this) || this; + _this._playbackObserver = playbackObserver; + _this._manifest = manifest; + _this._speed = speed; + _this._discontinuitiesStore = []; + _this._isStarted = false; + _this._canceller = new task_canceller/* default */.ZP(); + return _this; + } + var _proto = RebufferingController.prototype; + _proto.start = function start() { + var _this2 = this; + if (this._isStarted) { return; } - var currPos = observation.position; - var rebufferingPos = (_a = observation.rebuffering.position) !== null && _a !== void 0 ? _a : currPos; - var lockedPeriodStart = lockedStreamEvt.period.start; - if (currPos < lockedPeriodStart && Math.abs(rebufferingPos - lockedPeriodStart) < 1) { - log/* default.warn */.Z.warn("Init: rebuffering because of a future locked stream.\n" + "Trying to unlock by seeking to the next Period"); - playbackObserver.setCurrentTime(lockedPeriodStart + 0.001); - } - }), - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - (0,ignoreElements/* ignoreElements */.l)()); - var playbackRateUpdater = new PlaybackRateUpdater(playbackObserver, speed); - var stall$ = playbackObserver.getReference().asObservable().pipe((0,withLatestFrom/* withLatestFrom */.M)(discontinuitiesStore$), (0,map/* map */.U)(function (_ref3) { - var observation = _ref3[0], - discontinuitiesStore = _ref3[1]; - var _a; - var buffered = observation.buffered, - position = observation.position, - readyState = observation.readyState, - rebuffering = observation.rebuffering, - freezing = observation.freezing; - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - BUFFER_DISCONTINUITY_THRESHOLD = _config$getCurrent.BUFFER_DISCONTINUITY_THRESHOLD, - FORCE_DISCONTINUITY_SEEK_DELAY = _config$getCurrent.FORCE_DISCONTINUITY_SEEK_DELAY, - FREEZING_STALLED_DELAY = _config$getCurrent.FREEZING_STALLED_DELAY, - UNFREEZING_SEEK_DELAY = _config$getCurrent.UNFREEZING_SEEK_DELAY, - UNFREEZING_DELTA_POSITION = _config$getCurrent.UNFREEZING_DELTA_POSITION; - if (!observation.seeking && is_seeking_approximate && ignoredStallTimeStamp === null && lastSeekingPosition !== null && observation.position < lastSeekingPosition) { - log/* default.debug */.Z.debug("Init: the device appeared to have seeked back by itself."); - var now = performance.now(); - ignoredStallTimeStamp = now; - } - lastSeekingPosition = observation.seeking ? Math.max((_a = observation.pendingInternalSeek) !== null && _a !== void 0 ? _a : 0, observation.position) : null; - if (freezing !== null) { - var _now = performance.now(); - var referenceTimestamp = prevFreezingState === null ? freezing.timestamp : prevFreezingState.attemptTimestamp; - if (_now - referenceTimestamp > UNFREEZING_SEEK_DELAY) { - log/* default.warn */.Z.warn("Init: trying to seek to un-freeze player"); - playbackObserver.setCurrentTime(playbackObserver.getCurrentTime() + UNFREEZING_DELTA_POSITION); - prevFreezingState = { - attemptTimestamp: _now - }; + this._isStarted = true; + /** + * On some devices (right now only seen on Tizen), seeking through the + * `currentTime` property can lead to the browser re-seeking once the + * segments have been loaded to improve seeking performances (for + * example, by seeking right to an intra video frame). + * In that case, we risk being in a conflict with that behavior: if for + * example we encounter a small discontinuity at the position the browser + * seeks to, we will seek over it, the browser would seek back and so on. + * + * This variable allows to store the last known position we were seeking to + * so we can detect when the browser seeked back (to avoid performing another + * seek after that). When browsers seek back to a position behind a + * discontinuity, they are usually able to skip them without our help. + */ + var lastSeekingPosition; + /** + * In some conditions (see `lastSeekingPosition`), we might want to not + * automatically seek over discontinuities because the browser might do it + * itself instead. + * In that case, we still want to perform the seek ourselves if the browser + * doesn't do it after sufficient time. + * This variable allows to store the timestamp at which a discontinuity began + * to be ignored. + */ + var ignoredStallTimeStamp = null; + var playbackRateUpdater = new PlaybackRateUpdater(this._playbackObserver, this._speed); + this._canceller.signal.register(function () { + playbackRateUpdater.dispose(); + }); + var prevFreezingState = null; + this._playbackObserver.listen(function (observation) { + var _a; + var discontinuitiesStore = _this2._discontinuitiesStore; + var buffered = observation.buffered, + position = observation.position, + readyState = observation.readyState, + rebuffering = observation.rebuffering, + freezing = observation.freezing; + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + BUFFER_DISCONTINUITY_THRESHOLD = _config$getCurrent.BUFFER_DISCONTINUITY_THRESHOLD, + FORCE_DISCONTINUITY_SEEK_DELAY = _config$getCurrent.FORCE_DISCONTINUITY_SEEK_DELAY, + FREEZING_STALLED_DELAY = _config$getCurrent.FREEZING_STALLED_DELAY, + UNFREEZING_SEEK_DELAY = _config$getCurrent.UNFREEZING_SEEK_DELAY, + UNFREEZING_DELTA_POSITION = _config$getCurrent.UNFREEZING_DELTA_POSITION; + if (!observation.seeking && is_seeking_approximate && ignoredStallTimeStamp === null && lastSeekingPosition !== null && observation.position < lastSeekingPosition) { + log/* default.debug */.Z.debug("Init: the device appeared to have seeked back by itself."); + var now = performance.now(); + ignoredStallTimeStamp = now; + } + lastSeekingPosition = observation.seeking ? Math.max((_a = observation.pendingInternalSeek) !== null && _a !== void 0 ? _a : 0, observation.position) : null; + if (freezing !== null) { + var _now = performance.now(); + var referenceTimestamp = prevFreezingState === null ? freezing.timestamp : prevFreezingState.attemptTimestamp; + if (_now - referenceTimestamp > UNFREEZING_SEEK_DELAY) { + log/* default.warn */.Z.warn("Init: trying to seek to un-freeze player"); + _this2._playbackObserver.setCurrentTime(_this2._playbackObserver.getCurrentTime() + UNFREEZING_DELTA_POSITION); + prevFreezingState = { + attemptTimestamp: _now + }; + } + if (_now - freezing.timestamp > FREEZING_STALLED_DELAY) { + if (rebuffering === null || ignoredStallTimeStamp !== null) { + playbackRateUpdater.stopRebuffering(); + } else { + playbackRateUpdater.startRebuffering(); + } + _this2.trigger("stalled", "freezing"); + return; + } + } else { + prevFreezingState = null; } - if (_now - freezing.timestamp > FREEZING_STALLED_DELAY) { - if (rebuffering === null || ignoredStallTimeStamp !== null) { - playbackRateUpdater.stopRebuffering(); - } else { - playbackRateUpdater.startRebuffering(); + if (rebuffering === null) { + playbackRateUpdater.stopRebuffering(); + if (readyState === 1) { + // With a readyState set to 1, we should still not be able to play: + // Return that we're stalled + var reason; + if (observation.seeking) { + reason = observation.pendingInternalSeek !== null ? "internal-seek" : "seeking"; + } else { + reason = "not-ready"; + } + _this2.trigger("stalled", reason); + return; } - return { - type: "stalled", - value: "freezing" - }; + _this2.trigger("unstalled", null); + return; } - } else { - prevFreezingState = null; - } - if (rebuffering === null) { - playbackRateUpdater.stopRebuffering(); - if (readyState === 1) { - // With a readyState set to 1, we should still not be able to play: - // Return that we're stalled - var reason; - if (observation.seeking) { - reason = observation.pendingInternalSeek !== null ? "internal-seek" : "seeking"; + // We want to separate a stall situation when a seek is due to a seek done + // internally by the player to when its due to a regular user seek. + var stalledReason = rebuffering.reason === "seeking" && observation.pendingInternalSeek !== null ? "internal-seek" : rebuffering.reason; + if (ignoredStallTimeStamp !== null) { + var _now2 = performance.now(); + if (_now2 - ignoredStallTimeStamp < FORCE_DISCONTINUITY_SEEK_DELAY) { + playbackRateUpdater.stopRebuffering(); + log/* default.debug */.Z.debug("Init: letting the device get out of a stall by itself"); + _this2.trigger("stalled", stalledReason); + return; } else { - reason = "not-ready"; + log/* default.warn */.Z.warn("Init: ignored stall for too long, checking discontinuity", _now2 - ignoredStallTimeStamp); } - return { - type: "stalled", - value: reason - }; } - return { - type: "unstalled", - value: null - }; - } - // We want to separate a stall situation when a seek is due to a seek done - // internally by the player to when its due to a regular user seek. - var stalledReason = rebuffering.reason === "seeking" && observation.pendingInternalSeek !== null ? "internal-seek" : rebuffering.reason; - if (ignoredStallTimeStamp !== null) { - var _now2 = performance.now(); - if (_now2 - ignoredStallTimeStamp < FORCE_DISCONTINUITY_SEEK_DELAY) { - playbackRateUpdater.stopRebuffering(); - log/* default.debug */.Z.debug("Init: letting the device get out of a stall by itself"); - return { - type: "stalled", - value: stalledReason - }; - } else { - log/* default.warn */.Z.warn("Init: ignored stall for too long, checking discontinuity", _now2 - ignoredStallTimeStamp); + ignoredStallTimeStamp = null; + playbackRateUpdater.startRebuffering(); + if (_this2._manifest === null) { + _this2.trigger("stalled", stalledReason); + return; } - } - ignoredStallTimeStamp = null; - playbackRateUpdater.startRebuffering(); - if (manifest === null) { - return { - type: "stalled", - value: stalledReason - }; - } - /** Position at which data is awaited. */ - var stalledPosition = rebuffering.position; - if (stalledPosition !== null && stalledPosition !== undefined && speed.getValue() > 0) { - var skippableDiscontinuity = findSeekableDiscontinuity(discontinuitiesStore, manifest, stalledPosition); - if (skippableDiscontinuity !== null) { - var realSeekTime = skippableDiscontinuity + 0.001; - if (realSeekTime <= playbackObserver.getCurrentTime()) { - log/* default.info */.Z.info("Init: position to seek already reached, no seeking", playbackObserver.getCurrentTime(), realSeekTime); - } else { - log/* default.warn */.Z.warn("SA: skippable discontinuity found in the stream", position, realSeekTime); - playbackObserver.setCurrentTime(realSeekTime); - return events_generators/* default.warning */.Z.warning(generateDiscontinuityError(stalledPosition, realSeekTime)); + /** Position at which data is awaited. */ + var stalledPosition = rebuffering.position; + if (stalledPosition !== null && stalledPosition !== undefined && _this2._speed.getValue() > 0) { + var skippableDiscontinuity = findSeekableDiscontinuity(discontinuitiesStore, _this2._manifest, stalledPosition); + if (skippableDiscontinuity !== null) { + var realSeekTime = skippableDiscontinuity + 0.001; + if (realSeekTime <= _this2._playbackObserver.getCurrentTime()) { + log/* default.info */.Z.info("Init: position to seek already reached, no seeking", _this2._playbackObserver.getCurrentTime(), realSeekTime); + } else { + log/* default.warn */.Z.warn("SA: skippable discontinuity found in the stream", position, realSeekTime); + _this2._playbackObserver.setCurrentTime(realSeekTime); + _this2.trigger("warning", generateDiscontinuityError(stalledPosition, realSeekTime)); + return; + } } } - } - var freezePosition = stalledPosition !== null && stalledPosition !== void 0 ? stalledPosition : position; - // Is it a very short discontinuity in buffer ? -> Seek at the beginning of the - // next range - // - // Discontinuity check in case we are close a buffered range but still - // calculate a stalled state. This is useful for some - // implementation that might drop an injected segment, or in - // case of small discontinuity in the content. - var nextBufferRangeGap = (0,ranges/* getNextRangeGap */.XS)(buffered, freezePosition); - if (speed.getValue() > 0 && nextBufferRangeGap < BUFFER_DISCONTINUITY_THRESHOLD) { - var seekTo = freezePosition + nextBufferRangeGap + EPSILON; - if (playbackObserver.getCurrentTime() < seekTo) { - log/* default.warn */.Z.warn("Init: discontinuity encountered inferior to the threshold", freezePosition, seekTo, BUFFER_DISCONTINUITY_THRESHOLD); - playbackObserver.setCurrentTime(seekTo); - return events_generators/* default.warning */.Z.warning(generateDiscontinuityError(freezePosition, seekTo)); - } - } - // Are we in a discontinuity between periods ? -> Seek at the beginning of the - // next period - for (var i = manifest.periods.length - 2; i >= 0; i--) { - var period = manifest.periods[i]; - if (period.end !== undefined && period.end <= freezePosition) { - if (manifest.periods[i + 1].start > freezePosition && manifest.periods[i + 1].start > playbackObserver.getCurrentTime()) { - var nextPeriod = manifest.periods[i + 1]; - playbackObserver.setCurrentTime(nextPeriod.start); - return events_generators/* default.warning */.Z.warning(generateDiscontinuityError(freezePosition, nextPeriod.start)); + var freezePosition = stalledPosition !== null && stalledPosition !== void 0 ? stalledPosition : position; + // Is it a very short discontinuity in buffer ? -> Seek at the beginning of the + // next range + // + // Discontinuity check in case we are close a buffered range but still + // calculate a stalled state. This is useful for some + // implementation that might drop an injected segment, or in + // case of small discontinuity in the content. + var nextBufferRangeGap = (0,ranges/* getNextRangeGap */.XS)(buffered, freezePosition); + if (_this2._speed.getValue() > 0 && nextBufferRangeGap < BUFFER_DISCONTINUITY_THRESHOLD) { + var seekTo = freezePosition + nextBufferRangeGap + EPSILON; + if (_this2._playbackObserver.getCurrentTime() < seekTo) { + log/* default.warn */.Z.warn("Init: discontinuity encountered inferior to the threshold", freezePosition, seekTo, BUFFER_DISCONTINUITY_THRESHOLD); + _this2._playbackObserver.setCurrentTime(seekTo); + _this2.trigger("warning", generateDiscontinuityError(freezePosition, seekTo)); + return; + } + } + // Are we in a discontinuity between periods ? -> Seek at the beginning of the + // next period + for (var i = _this2._manifest.periods.length - 2; i >= 0; i--) { + var period = _this2._manifest.periods[i]; + if (period.end !== undefined && period.end <= freezePosition) { + if (_this2._manifest.periods[i + 1].start > freezePosition && _this2._manifest.periods[i + 1].start > _this2._playbackObserver.getCurrentTime()) { + var nextPeriod = _this2._manifest.periods[i + 1]; + _this2._playbackObserver.setCurrentTime(nextPeriod.start); + _this2.trigger("warning", generateDiscontinuityError(freezePosition, nextPeriod.start)); + return; + } + break; } - break; } + _this2.trigger("stalled", stalledReason); + }, { + includeLastObservation: true, + clearSignal: this._canceller.signal + }); + } + /** + * Update information on an upcoming discontinuity for a given buffer type and + * Period. + * Each new update for the same Period and type overwrites the previous one. + * @param {Object} evt + */; + _proto.updateDiscontinuityInfo = function updateDiscontinuityInfo(evt) { + if (!this._isStarted) { + this.start(); } - return { - type: "stalled", - value: stalledReason - }; - })); - return (0,merge/* merge */.T)(unlock$, stall$).pipe((0,finalize/* finalize */.x)(function () { - playbackRateUpdater.dispose(); - })); -} + var lastObservation = this._playbackObserver.getReference().getValue(); + updateDiscontinuitiesStore(this._discontinuitiesStore, evt, lastObservation); + } + /** + * Function to call when a Stream is currently locked, i.e. we cannot load + * segments for the corresponding Period and buffer type until it is seeked + * to. + * @param {string} bufferType - Buffer type for which no segment will + * currently load. + * @param {Object} period - Period for which no segment will currently load. + */; + _proto.onLockedStream = function onLockedStream(bufferType, period) { + var _a; + if (!this._isStarted) { + this.start(); + } + var observation = this._playbackObserver.getReference().getValue(); + if (!observation.rebuffering || observation.paused || this._speed.getValue() <= 0 || bufferType !== "audio" && bufferType !== "video") { + return; + } + var currPos = observation.position; + var rebufferingPos = (_a = observation.rebuffering.position) !== null && _a !== void 0 ? _a : currPos; + var lockedPeriodStart = period.start; + if (currPos < lockedPeriodStart && Math.abs(rebufferingPos - lockedPeriodStart) < 1) { + log/* default.warn */.Z.warn("Init: rebuffering because of a future locked stream.\n" + "Trying to unlock by seeking to the next Period"); + this._playbackObserver.setCurrentTime(lockedPeriodStart + 0.001); + } + } + /** + * Stops the `RebufferingController` from montoring stalling situations, + * forever. + */; + _proto.destroy = function destroy() { + this._canceller.cancel(); + }; + return RebufferingController; +}(event_emitter/* default */.Z); /** * @param {Array.} discontinuitiesStore * @param {Object} manifest * @param {number} stalledPosition * @returns {number|null} */ + function findSeekableDiscontinuity(discontinuitiesStore, manifest, stalledPosition) { if (discontinuitiesStore.length === 0) { return null; @@ -10373,7 +10284,7 @@ function updateDiscontinuitiesStore(discontinuitiesStore, evt, observation) { var period = evt.period, bufferType = evt.bufferType; if (bufferType !== "audio" && bufferType !== "video") { - return discontinuitiesStore; + return; } for (var i = 0; i < discontinuitiesStore.length; i++) { if (discontinuitiesStore[i].period.id === period.id) { @@ -10383,19 +10294,19 @@ function updateDiscontinuitiesStore(discontinuitiesStore, evt, observation) { } else { discontinuitiesStore[i] = evt; } - return discontinuitiesStore; + return; } } else if (discontinuitiesStore[i].period.start > period.start) { if (eventContainsDiscontinuity(evt)) { discontinuitiesStore.splice(i, 0, evt); } - return discontinuitiesStore; + return; } } if (eventContainsDiscontinuity(evt)) { discontinuitiesStore.push(evt); } - return discontinuitiesStore; + return; } /** * Generate error emitted when a discontinuity has been encountered. @@ -10435,8 +10346,8 @@ var PlaybackRateUpdater = /*#__PURE__*/function () { * * You can call `stopRebuffering` when you want the rebuffering phase to end. */ - var _proto = PlaybackRateUpdater.prototype; - _proto.startRebuffering = function startRebuffering() { + var _proto2 = PlaybackRateUpdater.prototype; + _proto2.startRebuffering = function startRebuffering() { if (this._isRebuffering || this._isDisposed) { return; } @@ -10451,7 +10362,7 @@ var PlaybackRateUpdater = /*#__PURE__*/function () { * * Do nothing if not in a rebuffering phase. */; - _proto.stopRebuffering = function stopRebuffering() { + _proto2.stopRebuffering = function stopRebuffering() { if (!this._isRebuffering || this._isDisposed) { return; } @@ -10466,15 +10377,15 @@ var PlaybackRateUpdater = /*#__PURE__*/function () { * Consequently, you should call the `dispose` method, when you don't want the * `PlaybackRateUpdater` to have an effect anymore. */; - _proto.dispose = function dispose() { + _proto2.dispose = function dispose() { this._speedUpdateCanceller.cancel(); this._isDisposed = true; }; - _proto._updateSpeed = function _updateSpeed() { - var _this = this; + _proto2._updateSpeed = function _updateSpeed() { + var _this3 = this; this._speed.onUpdate(function (lastSpeed) { log/* default.info */.Z.info("Init: Resume playback speed", lastSpeed); - _this._playbackObserver.setPlaybackRate(lastSpeed); + _this3._playbackObserver.setPlaybackRate(lastSpeed); }, { clearSignal: this._speedUpdateCanceller.signal, emitCurrentValue: true @@ -10485,17 +10396,15 @@ var PlaybackRateUpdater = /*#__PURE__*/function () { /***/ }), -/***/ 2447: +/***/ 4576: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ throwOnMediaError; } +/* harmony export */ "Z": function() { return /* binding */ listenToMediaError; } /* harmony export */ }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2401); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7877); -/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3714); -/* harmony import */ var _utils_is_null_or_undefined__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1946); +/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3714); +/* harmony import */ var _utils_is_null_or_undefined__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1946); /** * Copyright 2015 CANAL+ Group * @@ -10513,40 +10422,45 @@ var PlaybackRateUpdater = /*#__PURE__*/function () { */ - /** - * Returns an observable which throws the right MediaError as soon an "error" - * event is received through the media element. * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {Function} onError + * @param {Object} cancelSignal */ -function throwOnMediaError(mediaElement) { - return (0,rxjs__WEBPACK_IMPORTED_MODULE_0__/* .fromEvent */ .R)(mediaElement, "error").pipe((0,rxjs__WEBPACK_IMPORTED_MODULE_1__/* .mergeMap */ .z)(function () { +function listenToMediaError(mediaElement, onError, cancelSignal) { + if (cancelSignal.isCancelled()) { + return; + } + mediaElement.addEventListener("error", onMediaError); + cancelSignal.register(function () { + mediaElement.removeEventListener("error", onMediaError); + }); + function onMediaError() { var mediaError = mediaElement.error; var errorCode; var errorMessage; - if (!(0,_utils_is_null_or_undefined__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .Z)(mediaError)) { + if (!(0,_utils_is_null_or_undefined__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)(mediaError)) { errorCode = mediaError.code; errorMessage = mediaError.message; } switch (errorCode) { case 1: errorMessage = errorMessage !== null && errorMessage !== void 0 ? errorMessage : "The fetching of the associated resource was aborted by the user's request."; - throw new _errors__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z("MEDIA_ERR_ABORTED", errorMessage); + return onError(new _errors__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z("MEDIA_ERR_ABORTED", errorMessage)); case 2: errorMessage = errorMessage !== null && errorMessage !== void 0 ? errorMessage : "A network error occurred which prevented the media from being " + "successfully fetched"; - throw new _errors__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z("MEDIA_ERR_NETWORK", errorMessage); + return onError(new _errors__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z("MEDIA_ERR_NETWORK", errorMessage)); case 3: errorMessage = errorMessage !== null && errorMessage !== void 0 ? errorMessage : "An error occurred while trying to decode the media resource"; - throw new _errors__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z("MEDIA_ERR_DECODE", errorMessage); + return onError(new _errors__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z("MEDIA_ERR_DECODE", errorMessage)); case 4: errorMessage = errorMessage !== null && errorMessage !== void 0 ? errorMessage : "The media resource has been found to be unsuitable."; - throw new _errors__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z("MEDIA_ERR_SRC_NOT_SUPPORTED", errorMessage); + return onError(new _errors__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z("MEDIA_ERR_SRC_NOT_SUPPORTED", errorMessage)); default: errorMessage = errorMessage !== null && errorMessage !== void 0 ? errorMessage : "The HTMLMediaElement errored due to an unknown reason."; - throw new _errors__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z("MEDIA_ERR_UNKNOWN", errorMessage); + return onError(new _errors__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z("MEDIA_ERR_UNKNOWN", errorMessage)); } - })); + } } /***/ }), @@ -10658,9 +10572,9 @@ var ImageSegmentBuffer = /*#__PURE__*/function (_SegmentBuffer) { * Indicate that every chunks from a Segment has been given to pushChunk so * far. * This will update our internal Segment inventory accordingly. - * The returned Observable will emit and complete successively once the whole - * segment has been pushed and this indication is acknowledged. - * @param {Object} infos + * The returned Promise will resolve once the whole segment has been pushed + * and this indication is acknowledged. + * @param {Object} _infos * @returns {Promise} */; _proto.endOfSegment = function endOfSegment(_infos) { @@ -10714,8 +10628,8 @@ __webpack_require__.d(__webpack_exports__, { // EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js var inheritsLoose = __webpack_require__(4578); -// EXTERNAL MODULE: ./src/compat/event_listeners.ts + 1 modules -var event_listeners = __webpack_require__(1381); +// EXTERNAL MODULE: ./src/compat/event_listeners.ts +var event_listeners = __webpack_require__(3038); // EXTERNAL MODULE: ./src/log.ts + 1 modules var log = __webpack_require__(3887); // EXTERNAL MODULE: ./src/utils/reference.ts @@ -10755,7 +10669,7 @@ var _ResizeObserver = is_node/* default */.Z ? undefined : window.ResizeObserver * milliseconds at which we should query that element's size. * @param {HTMLElement} element * @param {number} interval - * @returns {Observable} + * @returns {Object} */ function onHeightWidthChange(element, interval, cancellationSignal) { var _element$getBoundingC = element.getBoundingClientRect(), @@ -10764,7 +10678,7 @@ function onHeightWidthChange(element, interval, cancellationSignal) { var ref = (0,reference/* default */.ZP)({ height: initHeight, width: initWidth - }); + }, cancellationSignal); var lastHeight = initHeight; var lastWidth = initWidth; if (_ResizeObserver !== undefined) { @@ -11455,7 +11369,7 @@ var HTMLTextSegmentBuffer = /*#__PURE__*/function (_SegmentBuffer) { return _this; } /** - * Push segment on Subscription. + * Push text segment to the HTMLTextSegmentBuffer. * @param {Object} infos * @returns {Promise} */ @@ -11643,9 +11557,8 @@ var HTMLTextSegmentBuffer = /*#__PURE__*/function (_SegmentBuffer) { return cue.resolution !== null; }); if (proportionalCues.length > 0) { - this._sizeUpdateCanceller = new task_canceller/* default */.ZP({ - cancelOn: this._canceller.signal - }); + this._sizeUpdateCanceller = new task_canceller/* default */.ZP(); + this._sizeUpdateCanceller.linkToSignal(this._canceller.signal); var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), TEXT_TRACK_SIZE_CHECKS_INTERVAL = _config$getCurrent.TEXT_TRACK_SIZE_CHECKS_INTERVAL; // update propertionally-sized elements periodically @@ -11677,9 +11590,8 @@ var HTMLTextSegmentBuffer = /*#__PURE__*/function (_SegmentBuffer) { MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL = _config$getCurrent2.MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL; var startAutoRefresh = function startAutoRefresh() { stopAutoRefresh(); - autoRefreshCanceller = new task_canceller/* default */.ZP({ - cancelOn: cancellationSignal - }); + autoRefreshCanceller = new task_canceller/* default */.ZP(); + autoRefreshCanceller.linkToSignal(cancellationSignal); var intervalId = setInterval(function () { return _this2.refreshSubtitles(); }, MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL); @@ -13344,211 +13256,6 @@ var ManualTimeRanges = /*#__PURE__*/function () { }(); -/***/ }), - -/***/ 8567: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony import */ var _utils_object_assign__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8026); -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var EVENTS = { - activePeriodChanged: function activePeriodChanged(period) { - return { - type: "activePeriodChanged", - value: { - period: period - } - }; - }, - adaptationChange: function adaptationChange(bufferType, adaptation, period) { - return { - type: "adaptationChange", - value: { - type: bufferType, - adaptation: adaptation, - period: period - } - }; - }, - addedSegment: function addedSegment(content, segment, buffered, segmentData) { - return { - type: "added-segment", - value: { - content: content, - segment: segment, - segmentData: segmentData, - buffered: buffered - } - }; - }, - bitrateEstimationChange: function bitrateEstimationChange(type, bitrate) { - return { - type: "bitrateEstimationChange", - value: { - type: type, - bitrate: bitrate - } - }; - }, - streamComplete: function streamComplete(bufferType) { - return { - type: "complete-stream", - value: { - type: bufferType - } - }; - }, - endOfStream: function endOfStream() { - return { - type: "end-of-stream", - value: undefined - }; - }, - needsManifestRefresh: function needsManifestRefresh() { - return { - type: "needs-manifest-refresh", - value: undefined - }; - }, - manifestMightBeOufOfSync: function manifestMightBeOufOfSync() { - return { - type: "manifest-might-be-out-of-sync", - value: undefined - }; - }, - /** - * @param {number} reloadAt - Position at which we should reload - * @param {boolean} reloadOnPause - If `false`, stay on pause after reloading. - * if `true`, automatically play once reloaded. - * @returns {Object} - */ - needsMediaSourceReload: function needsMediaSourceReload(reloadAt, reloadOnPause) { - return { - type: "needs-media-source-reload", - value: { - position: reloadAt, - autoPlay: reloadOnPause - } - }; - }, - /** - * @param {string} bufferType - The buffer type for which the stream cannot - * currently load segments. - * @param {Object} period - The Period for which the stream cannot - * currently load segments. - * media source reload is linked. - * @returns {Object} - */ - lockedStream: function lockedStream(bufferType, period) { - return { - type: "locked-stream", - value: { - bufferType: bufferType, - period: period - } - }; - }, - needsBufferFlush: function needsBufferFlush() { - return { - type: "needs-buffer-flush", - value: undefined - }; - }, - needsDecipherabilityFlush: function needsDecipherabilityFlush(position, autoPlay, duration) { - return { - type: "needs-decipherability-flush", - value: { - position: position, - autoPlay: autoPlay, - duration: duration - } - }; - }, - periodStreamReady: function periodStreamReady(type, period, adaptation$) { - return { - type: "periodStreamReady", - value: { - type: type, - period: period, - adaptation$: adaptation$ - } - }; - }, - periodStreamCleared: function periodStreamCleared(type, period) { - return { - type: "periodStreamCleared", - value: { - type: type, - period: period - } - }; - }, - encryptionDataEncountered: function encryptionDataEncountered(reprProtData, content) { - return { - type: "encryption-data-encountered", - value: (0,_utils_object_assign__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)({ - content: content - }, reprProtData) - }; - }, - representationChange: function representationChange(type, period, representation) { - return { - type: "representationChange", - value: { - type: type, - period: period, - representation: representation - } - }; - }, - streamTerminating: function streamTerminating() { - return { - type: "stream-terminating", - value: undefined - }; - }, - resumeStream: function resumeStream() { - return { - type: "resume-stream", - value: undefined - }; - }, - warning: function warning(value) { - return { - type: "warning", - value: value - }; - }, - waitingMediaSourceReload: function waitingMediaSourceReload(bufferType, period, position, autoPlay) { - return { - type: "waiting-media-source-reload", - value: { - bufferType: bufferType, - period: period, - position: position, - autoPlay: autoPlay - } - }; - } -}; -/* harmony default export */ __webpack_exports__["Z"] = (EVENTS); - /***/ }), /***/ 7839: @@ -14168,6 +13875,7 @@ var features = { wasm: null, js: null }, + createDebugElement: null, directfile: null, ContentDecryptor: null, htmlTextTracksBuffer: null, @@ -14348,6 +14056,8 @@ __webpack_require__.d(__webpack_exports__, { "Z": function() { return /* binding */ Adaptation; } }); +// EXTERNAL MODULE: ./src/log.ts + 1 modules +var log = __webpack_require__(3887); // EXTERNAL MODULE: ./src/utils/array_find.ts var array_find = __webpack_require__(3274); // EXTERNAL MODULE: ./src/utils/is_null_or_undefined.ts @@ -14437,8 +14147,6 @@ function isCodecSupported(mimeType) { } return true; } -// EXTERNAL MODULE: ./src/log.ts + 1 modules -var log = __webpack_require__(3887); // EXTERNAL MODULE: ./src/utils/are_arrays_of_numbers_equal.ts var are_arrays_of_numbers_equal = __webpack_require__(4791); ;// CONCATENATED MODULE: ./src/manifest/representation.ts @@ -14495,7 +14203,16 @@ var Representation = /*#__PURE__*/function () { } this.cdnMetadata = args.cdnMetadata; this.index = args.index; - this.isSupported = opts.type === "audio" || opts.type === "video" ? isCodecSupported(this.getMimeTypeString()) : true; // TODO for other types + if (opts.type === "audio" || opts.type === "video") { + var mimeTypeStr = this.getMimeTypeString(); + var isSupported = isCodecSupported(mimeTypeStr); + if (!isSupported) { + log/* default.info */.Z.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate); + } + this.isSupported = isSupported; + } else { + this.isSupported = true; // TODO for other types + } } /** * Returns "mime-type string" which includes both the mime-type and the codec, @@ -14707,6 +14424,7 @@ var Representation = /*#__PURE__*/function () { + /** List in an array every possible value for the Adaptation's `type` property. */ var SUPPORTED_ADAPTATIONS_TYPE = ["audio", "video", "text", "image"]; /** @@ -14749,6 +14467,9 @@ var Adaptation = /*#__PURE__*/function () { if (parsedAdaptation.isDub !== undefined) { this.isDub = parsedAdaptation.isDub; } + if (parsedAdaptation.forcedSubtitles !== undefined) { + this.isForcedSubtitles = parsedAdaptation.forcedSubtitles; + } if (parsedAdaptation.isSignInterpreted !== undefined) { this.isSignInterpreted = parsedAdaptation.isSignInterpreted; } @@ -14781,6 +14502,8 @@ var Adaptation = /*#__PURE__*/function () { if (!isSupported && representation.isSupported) { isSupported = true; } + } else { + log/* default.debug */.Z.debug("Filtering Representation due to representationFilter", this.type, "Adaptation: " + this.id, "Representation: " + representation.id, "(" + representation.bitrate + ")"); } } representations.sort(function (a, b) { @@ -14849,6 +14572,8 @@ __webpack_require__.d(__webpack_exports__, { var inheritsLoose = __webpack_require__(4578); // EXTERNAL MODULE: ./src/errors/media_error.ts var media_error = __webpack_require__(3714); +// EXTERNAL MODULE: ./src/log.ts + 1 modules +var log = __webpack_require__(3887); // EXTERNAL MODULE: ./src/utils/array_find.ts var array_find = __webpack_require__(3274); // EXTERNAL MODULE: ./src/utils/event_emitter.ts @@ -15001,8 +14726,6 @@ var Period = /*#__PURE__*/function () { return Period; }(); -// EXTERNAL MODULE: ./src/log.ts + 1 modules -var log = __webpack_require__(3887); ;// CONCATENATED MODULE: ./src/manifest/representation_index/static.ts /** * Copyright 2015 CANAL+ Group @@ -15176,6 +14899,9 @@ var MANIFEST_UPDATE_TYPE; // EXTERNAL MODULE: ./src/utils/array_find_index.ts var array_find_index = __webpack_require__(5138); ;// CONCATENATED MODULE: ./src/manifest/update_period_in_place.ts +function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -15199,32 +14925,61 @@ var array_find_index = __webpack_require__(5138); * the Manifest). * @param {Object} oldPeriod * @param {Object} newPeriod + * @param {number} updateType + * @returns {Object} */ function updatePeriodInPlace(oldPeriod, newPeriod, updateType) { + var res = { + updatedAdaptations: [], + removedAdaptations: [], + addedAdaptations: [] + }; oldPeriod.start = newPeriod.start; oldPeriod.end = newPeriod.end; oldPeriod.duration = newPeriod.duration; oldPeriod.streamEvents = newPeriod.streamEvents; var oldAdaptations = oldPeriod.getAdaptations(); var newAdaptations = newPeriod.getAdaptations(); - var _loop = function _loop(j) { - var oldAdaptation = oldAdaptations[j]; - var newAdaptation = (0,array_find/* default */.Z)(newAdaptations, function (a) { + var _loop = function _loop(_j) { + var oldAdaptation = oldAdaptations[_j]; + var newAdaptationIdx = (0,array_find_index/* default */.Z)(newAdaptations, function (a) { return a.id === oldAdaptation.id; }); - if (newAdaptation === undefined) { - log/* default.warn */.Z.warn("Manifest: Adaptation \"" + oldAdaptations[j].id + "\" not found when merging."); + if (newAdaptationIdx === -1) { + log/* default.warn */.Z.warn("Manifest: Adaptation \"" + oldAdaptations[_j].id + "\" not found when merging."); + var _oldAdaptations$splic = oldAdaptations.splice(_j, 1), + removed = _oldAdaptations$splic[0]; + _j--; + res.removedAdaptations.push(removed); } else { - var oldRepresentations = oldAdaptations[j].representations; - var newRepresentations = newAdaptation.representations; - var _loop2 = function _loop2(k) { - var oldRepresentation = oldRepresentations[k]; - var newRepresentation = (0,array_find/* default */.Z)(newRepresentations, function (representation) { + var _newAdaptations$splic = newAdaptations.splice(newAdaptationIdx, 1), + newAdaptation = _newAdaptations$splic[0]; + var updatedRepresentations = []; + var addedRepresentations = []; + var removedRepresentations = []; + res.updatedAdaptations.push({ + adaptation: oldAdaptation, + updatedRepresentations: updatedRepresentations, + addedRepresentations: addedRepresentations, + removedRepresentations: removedRepresentations + }); + var oldRepresentations = oldAdaptation.representations; + var newRepresentations = newAdaptation.representations.slice(); + var _loop2 = function _loop2(_k) { + var oldRepresentation = oldRepresentations[_k]; + var newRepresentationIdx = (0,array_find_index/* default */.Z)(newRepresentations, function (representation) { return representation.id === oldRepresentation.id; }); - if (newRepresentation === undefined) { - log/* default.warn */.Z.warn("Manifest: Representation \"" + oldRepresentations[k].id + "\" " + "not found when merging."); + if (newRepresentationIdx === -1) { + log/* default.warn */.Z.warn("Manifest: Representation \"" + oldRepresentations[_k].id + "\" " + "not found when merging."); + var _oldRepresentations$s = oldRepresentations.splice(_k, 1), + _removed = _oldRepresentations$s[0]; + _k--; + removedRepresentations.push(_removed); } else { + var _newRepresentations$s = newRepresentations.splice(newRepresentationIdx, 1), + newRepresentation = _newRepresentations$s[0]; + updatedRepresentations.push(oldRepresentation); oldRepresentation.cdnMetadata = newRepresentation.cdnMetadata; if (updateType === MANIFEST_UPDATE_TYPE.Full) { oldRepresentation.index._replace(newRepresentation.index); @@ -15232,15 +14987,37 @@ function updatePeriodInPlace(oldPeriod, newPeriod, updateType) { oldRepresentation.index._update(newRepresentation.index); } } + k = _k; }; for (var k = 0; k < oldRepresentations.length; k++) { _loop2(k); } + if (newRepresentations.length > 0) { + var _oldAdaptation$repres; + log/* default.warn */.Z.warn("Manifest: " + newRepresentations.length + " new Representations " + "found when merging."); + (_oldAdaptation$repres = oldAdaptation.representations).push.apply(_oldAdaptation$repres, newRepresentations); + addedRepresentations.push.apply(addedRepresentations, newRepresentations); + } } + j = _j; }; for (var j = 0; j < oldAdaptations.length; j++) { _loop(j); } + if (newAdaptations.length > 0) { + log/* default.warn */.Z.warn("Manifest: " + newAdaptations.length + " new Adaptations " + "found when merging."); + for (var _iterator = _createForOfIteratorHelperLoose(newAdaptations), _step; !(_step = _iterator()).done;) { + var adap = _step.value; + var prevAdaps = oldPeriod.adaptations[adap.type]; + if (prevAdaps === undefined) { + oldPeriod.adaptations[adap.type] = [adap]; + } else { + prevAdaps.push(adap); + } + res.addedAdaptations.push(adap); + } + } + return res; } ;// CONCATENATED MODULE: ./src/manifest/update_periods.ts /** @@ -15268,8 +15045,14 @@ function updatePeriodInPlace(oldPeriod, newPeriod, updateType) { * not available ones. * @param {Array.} oldPeriods * @param {Array.} newPeriods + * @returns {Object} */ function replacePeriods(oldPeriods, newPeriods) { + var res = { + updatedPeriods: [], + addedPeriods: [], + removedPeriods: [] + }; var firstUnhandledPeriodIdx = 0; for (var i = 0; i < newPeriods.length; i++) { var newPeriod = newPeriods[i]; @@ -15280,47 +15063,70 @@ function replacePeriods(oldPeriods, newPeriods) { oldPeriod = oldPeriods[j]; } if (oldPeriod != null) { - updatePeriodInPlace(oldPeriod, newPeriod, MANIFEST_UPDATE_TYPE.Full); + var _res$removedPeriods, _res$addedPeriods; + var result = updatePeriodInPlace(oldPeriod, newPeriod, MANIFEST_UPDATE_TYPE.Full); + res.updatedPeriods.push({ + period: oldPeriod, + result: result + }); var periodsToInclude = newPeriods.slice(firstUnhandledPeriodIdx, i); var nbrOfPeriodsToRemove = j - firstUnhandledPeriodIdx; - oldPeriods.splice.apply(oldPeriods, [firstUnhandledPeriodIdx, nbrOfPeriodsToRemove].concat(periodsToInclude)); + var removed = oldPeriods.splice.apply(oldPeriods, [firstUnhandledPeriodIdx, nbrOfPeriodsToRemove].concat(periodsToInclude)); + (_res$removedPeriods = res.removedPeriods).push.apply(_res$removedPeriods, removed); + (_res$addedPeriods = res.addedPeriods).push.apply(_res$addedPeriods, periodsToInclude); firstUnhandledPeriodIdx = i + 1; } } if (firstUnhandledPeriodIdx > oldPeriods.length) { log/* default.error */.Z.error("Manifest: error when updating Periods"); - return; + return res; } if (firstUnhandledPeriodIdx < oldPeriods.length) { - oldPeriods.splice(firstUnhandledPeriodIdx, oldPeriods.length - firstUnhandledPeriodIdx); + var _res$removedPeriods2; + var _removed = oldPeriods.splice(firstUnhandledPeriodIdx, oldPeriods.length - firstUnhandledPeriodIdx); + (_res$removedPeriods2 = res.removedPeriods).push.apply(_res$removedPeriods2, _removed); } var remainingNewPeriods = newPeriods.slice(firstUnhandledPeriodIdx, newPeriods.length); if (remainingNewPeriods.length > 0) { + var _res$addedPeriods2; oldPeriods.push.apply(oldPeriods, remainingNewPeriods); + (_res$addedPeriods2 = res.addedPeriods).push.apply(_res$addedPeriods2, remainingNewPeriods); } + return res; } /** * Update old periods by adding new periods and removing * not available ones. * @param {Array.} oldPeriods * @param {Array.} newPeriods + * @returns {Object} */ function updatePeriods(oldPeriods, newPeriods) { + var res = { + updatedPeriods: [], + addedPeriods: [], + removedPeriods: [] + }; if (oldPeriods.length === 0) { + var _res$addedPeriods3; oldPeriods.splice.apply(oldPeriods, [0, 0].concat(newPeriods)); - return; + (_res$addedPeriods3 = res.addedPeriods).push.apply(_res$addedPeriods3, newPeriods); + return res; } if (newPeriods.length === 0) { - return; + return res; } var oldLastPeriod = oldPeriods[oldPeriods.length - 1]; if (oldLastPeriod.start < newPeriods[0].start) { + var _res$addedPeriods4; if (oldLastPeriod.end !== newPeriods[0].start) { throw new media_error/* default */.Z("MANIFEST_UPDATE_ERROR", "Cannot perform partial update: not enough data"); } oldPeriods.push.apply(oldPeriods, newPeriods); - return; + (_res$addedPeriods4 = res.addedPeriods).push.apply(_res$addedPeriods4, newPeriods); + return res; } + /** Index, in `oldPeriods` of the first element of `newPeriods` */ var indexOfNewFirstPeriod = (0,array_find_index/* default */.Z)(oldPeriods, function (_ref) { var id = _ref.id; return id === newPeriods[0].id; @@ -15329,7 +15135,13 @@ function updatePeriods(oldPeriods, newPeriods) { throw new media_error/* default */.Z("MANIFEST_UPDATE_ERROR", "Cannot perform partial update: incoherent data"); } // The first updated Period can only be a partial part - updatePeriodInPlace(oldPeriods[indexOfNewFirstPeriod], newPeriods[0], MANIFEST_UPDATE_TYPE.Partial); + var updateRes = updatePeriodInPlace(oldPeriods[indexOfNewFirstPeriod], newPeriods[0], MANIFEST_UPDATE_TYPE.Partial); + res.updatedPeriods.push({ + period: oldPeriods[indexOfNewFirstPeriod], + result: updateRes + }); + // Search each consecutive elements of `newPeriods` - after the initial one already + // processed - in `oldPeriods`, removing and adding unfound Periods in the process var prevIndexOfNewPeriod = indexOfNewFirstPeriod + 1; for (var i = 1; i < newPeriods.length; i++) { var newPeriod = newPeriods[i]; @@ -15342,26 +15154,51 @@ function updatePeriods(oldPeriods, newPeriods) { } if (indexOfNewPeriod < 0) { - oldPeriods.splice.apply(oldPeriods, [prevIndexOfNewPeriod, oldPeriods.length - prevIndexOfNewPeriod].concat(newPeriods.slice(i, newPeriods.length))); - return; - } - if (indexOfNewPeriod > prevIndexOfNewPeriod) { - oldPeriods.splice(prevIndexOfNewPeriod, indexOfNewPeriod - prevIndexOfNewPeriod); - indexOfNewPeriod = prevIndexOfNewPeriod; + var _res$removedPeriods3; + // Next element of `newPeriods` not found: insert it + var toRemoveUntil = -1; + for (var _j = prevIndexOfNewPeriod; _j < oldPeriods.length; _j++) { + if (newPeriod.start < oldPeriods[_j].start) { + toRemoveUntil = _j; + break; // end the loop + } + } + + var nbElementsToRemove = toRemoveUntil - prevIndexOfNewPeriod; + var removed = oldPeriods.splice(prevIndexOfNewPeriod, nbElementsToRemove, newPeriod); + res.addedPeriods.push(newPeriod); + (_res$removedPeriods3 = res.removedPeriods).push.apply(_res$removedPeriods3, removed); + } else { + if (indexOfNewPeriod > prevIndexOfNewPeriod) { + var _res$removedPeriods4; + // Some old periods were not found: remove + log/* default.warn */.Z.warn("Manifest: old Periods not found in new when updating, removing"); + var _removed2 = oldPeriods.splice(prevIndexOfNewPeriod, indexOfNewPeriod - prevIndexOfNewPeriod); + (_res$removedPeriods4 = res.removedPeriods).push.apply(_res$removedPeriods4, _removed2); + indexOfNewPeriod = prevIndexOfNewPeriod; + } + // Later Periods can be fully replaced + var result = updatePeriodInPlace(oldPeriods[indexOfNewPeriod], newPeriod, MANIFEST_UPDATE_TYPE.Full); + res.updatedPeriods.push({ + period: oldPeriods[indexOfNewPeriod], + result: result + }); } - // Later Periods can be fully replaced - updatePeriodInPlace(oldPeriods[indexOfNewPeriod], newPeriod, MANIFEST_UPDATE_TYPE.Full); prevIndexOfNewPeriod++; } if (prevIndexOfNewPeriod < oldPeriods.length) { - oldPeriods.splice(prevIndexOfNewPeriod, oldPeriods.length - prevIndexOfNewPeriod); + var _res$removedPeriods5; + log/* default.warn */.Z.warn("Manifest: Ending Periods not found in new when updating, removing"); + var _removed3 = oldPeriods.splice(prevIndexOfNewPeriod, oldPeriods.length - prevIndexOfNewPeriod); + (_res$removedPeriods5 = res.removedPeriods).push.apply(_res$removedPeriods5, _removed3); } + return res; } ;// CONCATENATED MODULE: ./src/manifest/manifest.ts -function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } -function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } -function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } +function manifest_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = manifest_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function manifest_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return manifest_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return manifest_arrayLikeToArray(o, minLen); } +function manifest_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -15388,6 +15225,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len + var generateSupplementaryTrackID = (0,id_generator/* default */.Z)(); var generateNewManifestId = (0,id_generator/* default */.Z)(); /** @@ -15774,14 +15612,15 @@ var Manifest = /*#__PURE__*/function (_EventEmitter) { this.suggestedPresentationDelay = newManifest.suggestedPresentationDelay; this.transport = newManifest.transport; this.publishTime = newManifest.publishTime; + var updatedPeriodsResult; if (updateType === MANIFEST_UPDATE_TYPE.Full) { this._timeBounds = newManifest._timeBounds; this.uris = newManifest.uris; - replacePeriods(this.periods, newManifest.periods); + updatedPeriodsResult = replacePeriods(this.periods, newManifest.periods); } else { this._timeBounds.maximumTimeData = newManifest._timeBounds.maximumTimeData; this.updateUrl = newManifest.uris[0]; - updatePeriods(this.periods, newManifest.periods); + updatedPeriodsResult = updatePeriods(this.periods, newManifest.periods); // Partial updates do not remove old Periods. // This can become a memory problem when playing a content long enough. // Let's clean manually Periods behind the minimum possible position. @@ -15801,7 +15640,7 @@ var Manifest = /*#__PURE__*/function (_EventEmitter) { // Let's trigger events at the end, as those can trigger side-effects. // We do not want the current Manifest object to be incomplete when those // happen. - this.trigger("manifestUpdate", null); + this.trigger("manifestUpdate", updatedPeriodsResult); }; return Manifest; }(event_emitter/* default */.Z); @@ -15819,11 +15658,11 @@ var Manifest = /*#__PURE__*/function (_EventEmitter) { function updateDeciperability(manifest, isDecipherable) { var updates = []; - for (var _iterator = _createForOfIteratorHelperLoose(manifest.periods), _step; !(_step = _iterator()).done;) { + for (var _iterator = manifest_createForOfIteratorHelperLoose(manifest.periods), _step; !(_step = _iterator()).done;) { var period = _step.value; - for (var _iterator2 = _createForOfIteratorHelperLoose(period.getAdaptations()), _step2; !(_step2 = _iterator2()).done;) { + for (var _iterator2 = manifest_createForOfIteratorHelperLoose(period.getAdaptations()), _step2; !(_step2 = _iterator2()).done;) { var adaptation = _step2.value; - for (var _iterator3 = _createForOfIteratorHelperLoose(adaptation.representations), _step3; !(_step3 = _iterator3()).done;) { + for (var _iterator3 = manifest_createForOfIteratorHelperLoose(adaptation.representations), _step3; !(_step3 = _iterator3()).done;) { var representation = _step3.value; var result = isDecipherable(representation); if (result !== representation.decipherable) { @@ -15833,6 +15672,7 @@ function updateDeciperability(manifest, isDecipherable) { adaptation: adaptation, representation: representation }); + log/* default.debug */.Z.debug("Decipherability changed for \"" + representation.id + "\"", "(" + representation.bitrate + ")", String(representation.decipherable)); representation.decipherable = result; } } @@ -17125,6 +16965,7 @@ var BaseRepresentationIndex = /*#__PURE__*/function () { }, segmentUrlTemplate: segmentUrlTemplate, startNumber: index.startNumber, + endNumber: index.endNumber, timeline: (_c = index.timeline) !== null && _c !== void 0 ? _c : [], timescale: timescale }; @@ -17405,7 +17246,8 @@ function getSegmentsFromTimeline(index, from, durationWanted, isEMSGWhitelisted, var timeline = index.timeline, timescale = index.timescale, segmentUrlTemplate = index.segmentUrlTemplate, - startNumber = index.startNumber; + startNumber = index.startNumber, + endNumber = index.endNumber; var currentNumber = startNumber !== null && startNumber !== void 0 ? startNumber : 1; var segments = []; var timelineLength = timeline.length; @@ -17420,6 +17262,9 @@ function getSegmentsFromTimeline(index, from, durationWanted, isEMSGWhitelisted, var segmentTime = start + segmentNumberInCurrentRange * duration; while (segmentTime < scaledTo && segmentNumberInCurrentRange <= repeat) { var segmentNumber = currentNumber + segmentNumberInCurrentRange; + if (endNumber !== undefined && segmentNumber > endNumber) { + break; + } var detokenizedURL = segmentUrlTemplate === null ? null : (0,_tokens__WEBPACK_IMPORTED_MODULE_1__/* .createDashUrlDetokenizer */ .QB)(segmentTime, segmentNumber)(segmentUrlTemplate); var time = segmentTime - index.indexTimeOffset; var realDuration = duration; @@ -17453,6 +17298,9 @@ function getSegmentsFromTimeline(index, from, durationWanted, isEMSGWhitelisted, return segments; } currentNumber += repeat + 1; + if (endNumber !== undefined && currentNumber > endNumber) { + return segments; + } } return segments; } @@ -19110,7 +18958,7 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { * @param {Object} context */ function TimelineRepresentationIndex(index, context) { - var _a, _b, _c, _d; + var _a, _b, _c; if (!TimelineRepresentationIndex.isTimelineIndexArgument(index)) { throw new Error("The given index is not compatible with a " + "TimelineRepresentationIndex."); } @@ -19151,7 +18999,8 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { }, segmentUrlTemplate: segmentUrlTemplate, startNumber: index.startNumber, - timeline: (_d = index.timeline) !== null && _d !== void 0 ? _d : null, + endNumber: index.endNumber, + timeline: index.timeline === undefined ? null : updateTimelineFromEndNumber(index.timeline, index.startNumber, index.endNumber), timescale: timescale }; this._scaledPeriodStart = (0,index_helpers/* toIndexTime */.gT)(periodStart, this._index); @@ -19180,12 +19029,14 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { var _this$_index = this._index, segmentUrlTemplate = _this$_index.segmentUrlTemplate, startNumber = _this$_index.startNumber, + endNumber = _this$_index.endNumber, timeline = _this$_index.timeline, timescale = _this$_index.timescale, indexTimeOffset = _this$_index.indexTimeOffset; return (0,get_segments_from_timeline/* default */.Z)({ segmentUrlTemplate: segmentUrlTemplate, startNumber: startNumber, + endNumber: endNumber, timeline: timeline, timescale: timescale, indexTimeOffset: indexTimeOffset @@ -19365,6 +19216,7 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { if (hasReplaced) { this._index.startNumber = newIndex._index.startNumber; } + this._index.endNumber = newIndex._index.endNumber; this._isDynamic = newIndex._isDynamic; this._scaledPeriodStart = newIndex._scaledPeriodStart; this._scaledPeriodEnd = newIndex._scaledPeriodEnd; @@ -19434,6 +19286,8 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { var nbEltsRemoved = (0,clear_timeline_from_position/* default */.Z)(this._index.timeline, scaledFirstPosition); if (this._index.startNumber !== undefined) { this._index.startNumber += nbEltsRemoved; + } else if (this._index.endNumber !== undefined) { + this._index.startNumber = nbEltsRemoved + 1; } }; TimelineRepresentationIndex.getIndexEnd = function getIndexEnd(timeline, scaledPeriodEnd) { @@ -19478,7 +19332,7 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY = _config$getCurrent.MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY; if (this._unsafelyBaseOnPreviousIndex === null || newElements.length < MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY) { // Just completely parse the current timeline - return constructTimelineFromElements(newElements); + return updateTimelineFromEndNumber(constructTimelineFromElements(newElements), this._index.startNumber, this._index.endNumber); } // Construct previously parsed timeline if not already done var prevTimeline; @@ -19489,11 +19343,46 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { prevTimeline = this._unsafelyBaseOnPreviousIndex._index.timeline; } this._unsafelyBaseOnPreviousIndex = null; // Free memory - return constructTimelineFromPreviousTimeline(newElements, prevTimeline); + return updateTimelineFromEndNumber(constructTimelineFromPreviousTimeline(newElements, prevTimeline), this._index.startNumber, this._index.endNumber); }; return TimelineRepresentationIndex; }(); +/** + * Take the original SegmentTimeline's parsed timeline and, if an `endNumber` is + * specified, filter segments which possess a number superior to that number. + * + * This should only be useful in only rare and broken MPDs, but we aim to + * respect the specification even in those cases. + * + * @param {Array.} timeline + * @param {number|undefined} startNumber + * @param {Array.} endNumber + * @returns {number|undefined} + */ +function updateTimelineFromEndNumber(timeline, startNumber, endNumber) { + if (endNumber === undefined) { + return timeline; + } + var currNumber = startNumber !== null && startNumber !== void 0 ? startNumber : 1; + for (var idx = 0; idx < timeline.length; idx++) { + var seg = timeline[idx]; + currNumber += seg.repeatCount + 1; + if (currNumber > endNumber) { + if (currNumber === endNumber + 1) { + return timeline.slice(0, idx + 1); + } else { + var newTimeline = timeline.slice(0, idx); + var lastElt = Object.assign({}, seg); + var beginningNumber = currNumber - seg.repeatCount - 1; + lastElt.repeatCount = Math.max(0, endNumber - beginningNumber); + newTimeline.push(lastElt); + return newTimeline; + } + } + } + return timeline; +} ;// CONCATENATED MODULE: ./src/parsers/manifest/dash/common/indexes/timeline/index.ts /** * Copyright 2015 CANAL+ Group @@ -19533,6 +19422,7 @@ var TimelineRepresentationIndex = /*#__PURE__*/function () { + /** * IRepresentationIndex implementation for DASH' SegmentTemplate without a * SegmentTimeline. @@ -19577,7 +19467,8 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { }, url: segmentUrlTemplate, presentationTimeOffset: presentationTimeOffset, - startNumber: index.startNumber + startNumber: index.startNumber, + endNumber: index.endNumber }; this._isDynamic = isDynamic; this._periodStart = periodStart; @@ -19601,6 +19492,7 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { var index = this._index; var duration = index.duration, startNumber = index.startNumber, + endNumber = index.endNumber, timescale = index.timescale, url = index.url; var scaledStart = this._periodStart * timescale; @@ -19628,6 +19520,9 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { for (var timeFromPeriodStart = numberIndexedToZero * duration; timeFromPeriodStart <= lastWantedStartPosition; timeFromPeriodStart += duration) { // To obtain the real number, adds the real number from the Period's start var realNumber = numberIndexedToZero + numberOffset; + if (endNumber !== undefined && realNumber > endNumber) { + return segments; + } var realDuration = scaledEnd != null && timeFromPeriodStart + duration > scaledEnd ? scaledEnd - timeFromPeriodStart : duration; var realTime = timeFromPeriodStart + scaledStart; var manifestTime = timeFromPeriodStart + this._index.presentationTimeOffset; @@ -19670,15 +19565,15 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { * @returns {number|null} */; _proto.getLastAvailablePosition = function getLastAvailablePosition() { - var _a; var lastSegmentStart = this._getLastSegmentStart(); - if (lastSegmentStart == null) { + if ((0,is_null_or_undefined/* default */.Z)(lastSegmentStart)) { // In that case (null or undefined), getLastAvailablePosition should reflect // the result of getLastSegmentStart, as the meaning is the same for // the two functions. So, we return the result of the latter. return lastSegmentStart; } - var lastSegmentEnd = Math.min(lastSegmentStart + this._index.duration, (_a = this._scaledRelativePeriodEnd) !== null && _a !== void 0 ? _a : Infinity); + var scaledRelativeIndexEnd = this._estimateRelativeScaledEnd(); + var lastSegmentEnd = Math.min(lastSegmentStart + this._index.duration, scaledRelativeIndexEnd !== null && scaledRelativeIndexEnd !== void 0 ? scaledRelativeIndexEnd : Infinity); return lastSegmentEnd / this._index.timescale + this._periodStart; } /** @@ -19690,12 +19585,13 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { if (!this._isDynamic) { return this.getLastAvailablePosition(); } - if (this._scaledRelativePeriodEnd === undefined) { + var scaledRelativeIndexEnd = this._estimateRelativeScaledEnd(); + if (scaledRelativeIndexEnd === undefined) { return undefined; } var timescale = this._index.timescale; - var absoluteScaledPeriodEnd = this._scaledRelativePeriodEnd + this._periodStart * timescale; - return absoluteScaledPeriodEnd / this._index.timescale; + var absoluteScaledIndexEnd = scaledRelativeIndexEnd + this._periodStart * timescale; + return absoluteScaledIndexEnd / timescale; } /** * Returns: @@ -19718,12 +19614,12 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { var segmentTimeRounding = getSegmentTimeRoundingError(timescale); var scaledPeriodStart = this._periodStart * timescale; var scaledRelativeEnd = end * timescale - scaledPeriodStart; - if (this._scaledRelativePeriodEnd === undefined) { + var relativeScaledIndexEnd = this._estimateRelativeScaledEnd(); + if (relativeScaledIndexEnd === undefined) { return scaledRelativeEnd + segmentTimeRounding >= 0; } - var scaledRelativePeriodEnd = this._scaledRelativePeriodEnd; var scaledRelativeStart = start * timescale - scaledPeriodStart; - return scaledRelativeStart - segmentTimeRounding < scaledRelativePeriodEnd && scaledRelativeEnd + segmentTimeRounding >= 0; + return scaledRelativeStart - segmentTimeRounding < relativeScaledIndexEnd; } /** * Returns true if, based on the arguments, the index should be refreshed. @@ -19766,25 +19662,30 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { return false; } /** + * Returns `true` if the last segments in this index have already been + * generated so that we can freely go to the next period. + * Returns `false` if the index is still waiting on future segments to be + * generated. * @returns {Boolean} */; _proto.isFinished = function isFinished() { if (!this._isDynamic) { return true; } - if (this._scaledRelativePeriodEnd === undefined) { + var scaledRelativeIndexEnd = this._estimateRelativeScaledEnd(); + if (scaledRelativeIndexEnd === undefined) { return false; } var timescale = this._index.timescale; var lastSegmentStart = this._getLastSegmentStart(); // As last segment start is null if live time is before // current period, consider the index not to be finished. - if (lastSegmentStart == null) { + if ((0,is_null_or_undefined/* default */.Z)(lastSegmentStart)) { return false; } var lastSegmentEnd = lastSegmentStart + this._index.duration; var segmentTimeRounding = getSegmentTimeRoundingError(timescale); - return lastSegmentEnd + segmentTimeRounding >= this._scaledRelativePeriodEnd; + return lastSegmentEnd + segmentTimeRounding >= scaledRelativeIndexEnd; } /** * @returns {Boolean} @@ -19853,18 +19754,22 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { var _a; var _this$_index2 = this._index, duration = _this$_index2.duration, - timescale = _this$_index2.timescale; + timescale = _this$_index2.timescale, + endNumber = _this$_index2.endNumber, + _this$_index2$startNu = _this$_index2.startNumber, + startNumber = _this$_index2$startNu === void 0 ? 1 : _this$_index2$startNu; if (this._isDynamic) { var lastPos = this._manifestBoundsCalculator.estimateMaximumBound(); if (lastPos === undefined) { return undefined; } var agressiveModeOffset = this._aggressiveMode ? duration / timescale : 0; - if (this._scaledRelativePeriodEnd != null && this._scaledRelativePeriodEnd < (lastPos + agressiveModeOffset - this._periodStart) * this._index.timescale) { - if (this._scaledRelativePeriodEnd < duration) { - return null; + if (this._scaledRelativePeriodEnd !== undefined && this._scaledRelativePeriodEnd < (lastPos + agressiveModeOffset - this._periodStart) * this._index.timescale) { + var numberOfSegments = Math.ceil(this._scaledRelativePeriodEnd / duration); + if (endNumber !== undefined && endNumber - startNumber + 1 < numberOfSegments) { + numberOfSegments = endNumber - startNumber + 1; } - return (Math.floor(this._scaledRelativePeriodEnd / duration) - 1) * duration; + return (numberOfSegments - 1) * duration; } // /!\ The scaled last position augments continuously and might not // reflect exactly the real server-side value. As segments are @@ -19877,21 +19782,46 @@ var TemplateRepresentationIndex = /*#__PURE__*/function () { } var availabilityTimeOffset = ((this._availabilityTimeOffset !== undefined ? this._availabilityTimeOffset : 0) + agressiveModeOffset) * timescale; var numberOfSegmentsAvailable = Math.floor((scaledLastPosition + availabilityTimeOffset) / duration); + if (endNumber !== undefined && endNumber - startNumber + 1 < numberOfSegmentsAvailable) { + numberOfSegmentsAvailable = endNumber - startNumber + 1; + } return numberOfSegmentsAvailable <= 0 ? null : (numberOfSegmentsAvailable - 1) * duration; } else { var maximumTime = (_a = this._scaledRelativePeriodEnd) !== null && _a !== void 0 ? _a : 0; - var numberIndexedToZero = Math.ceil(maximumTime / duration) - 1; - var regularLastSegmentStart = numberIndexedToZero * duration; + var _numberOfSegments = Math.ceil(maximumTime / duration); + if (endNumber !== undefined && endNumber - startNumber + 1 < _numberOfSegments) { + _numberOfSegments = endNumber - startNumber + 1; + } + var regularLastSegmentStart = (_numberOfSegments - 1) * duration; // In some SegmentTemplate, we could think that there is one more // segment that there actually is due to a very little difference between // the period's duration and a multiple of a segment's duration. // Check that we're within a good margin var minimumDuration = config/* default.getCurrent */.Z.getCurrent().MINIMUM_SEGMENT_SIZE * timescale; - if (maximumTime - regularLastSegmentStart > minimumDuration || numberIndexedToZero === 0) { + if (endNumber !== undefined || maximumTime - regularLastSegmentStart > minimumDuration || _numberOfSegments < 2) { return regularLastSegmentStart; } - return (numberIndexedToZero - 1) * duration; + return (_numberOfSegments - 2) * duration; + } + } + /** + * Returns an estimate of the last available position in this + * `RepresentationIndex` based on attributes such as the Period's end and + * the `endNumber` attribute. + * If the estimate cannot be made (e.g. this Period's segments are still being + * generated and its end is yet unknown), returns `undefined`. + * @returns {number|undefined} + */; + _proto._estimateRelativeScaledEnd = function _estimateRelativeScaledEnd() { + var _a, _b; + if (this._index.endNumber !== undefined) { + var numberOfSegments = this._index.endNumber - ((_a = this._index.startNumber) !== null && _a !== void 0 ? _a : 1) + 1; + return Math.max(Math.min(numberOfSegments * this._index.duration, (_b = this._scaledRelativePeriodEnd) !== null && _b !== void 0 ? _b : Infinity), 0); } + if (this._scaledRelativePeriodEnd === undefined) { + return undefined; + } + return Math.max(this._scaledRelativePeriodEnd, 0); }; return TemplateRepresentationIndex; }(); @@ -20096,12 +20026,11 @@ function combineInbandEventStreams(representation, adaptation) { */ function getHDRInformation(_ref) { var adaptationProfiles = _ref.adaptationProfiles, + essentialProperties = _ref.essentialProperties, + supplementalProperties = _ref.supplementalProperties, manifestProfiles = _ref.manifestProfiles, codecs = _ref.codecs; var profiles = (adaptationProfiles !== null && adaptationProfiles !== void 0 ? adaptationProfiles : "") + (manifestProfiles !== null && manifestProfiles !== void 0 ? manifestProfiles : ""); - if (codecs === undefined) { - return undefined; - } if (profiles.indexOf("http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10") !== -1) { if (codecs === "hvc1.2.4.L153.B0" || codecs === "hev1.2.4.L153.B0") { return { @@ -20111,7 +20040,25 @@ function getHDRInformation(_ref) { }; } } - if (/^vp(08|09|10)/.exec(codecs)) { + var transferCharacteristicScheme = (0,array_find/* default */.Z)([].concat(essentialProperties !== null && essentialProperties !== void 0 ? essentialProperties : [], supplementalProperties !== null && supplementalProperties !== void 0 ? supplementalProperties : []), function (p) { + return p.schemeIdUri === "urn:mpeg:mpegB:cicp:TransferCharacteristics"; + }); + if (transferCharacteristicScheme !== undefined) { + switch (transferCharacteristicScheme.value) { + case "15": + return undefined; + // SDR + case "16": + return { + eotf: "pq" + }; + case "18": + return { + eotf: "hlg" + }; + } + } + if (codecs !== undefined && /^vp(08|09|10)/.exec(codecs)) { return getWEBMHDRInformation(codecs); } } @@ -20156,7 +20103,15 @@ function parseRepresentations(representationsIR, adaptation, context) { representationBitrate = representation.attributes.bitrate; } var representationBaseURLs = resolveBaseURLs(context.baseURLs, representation.children.baseURLs); - var cdnMetadata = representationBaseURLs.map(function (x) { + var cdnMetadata = representationBaseURLs.length === 0 ? + // No BaseURL seems to be associated to this Representation, nor to the MPD, + // but underlying segments might have one. To indicate that segments should + // still be available through a CDN without giving any root CDN URL here, + // we just communicate about an empty `baseUrl`, as documented. + [{ + baseUrl: "", + id: undefined + }] : representationBaseURLs.map(function (x) { return { baseUrl: x.url, id: x.serviceLocation @@ -20257,6 +20212,8 @@ function parseRepresentations(representationsIR, adaptation, context) { } parsedRepresentation.hdrInfo = getHDRInformation({ adaptationProfiles: adaptation.attributes.profiles, + supplementalProperties: adaptation.children.supplementalProperties, + essentialProperties: adaptation.children.essentialProperties, manifestProfiles: context.manifestProfiles, codecs: codecs }); @@ -20315,15 +20272,29 @@ function isVisuallyImpaired(accessibility) { /** * Detect if the accessibility given defines an adaptation for the hard of * hearing. - * Based on DVB Document A168 (DVB-DASH). - * @param {Object} accessibility + * Based on DVB Document A168 (DVB-DASH) and DASH specification. + * @param {Array.} accessibilities + * @param {Array.} roles * @returns {Boolean} */ -function isHardOfHearing(accessibility) { - if (accessibility === undefined) { - return false; +function isCaptionning(accessibilities, roles) { + if (accessibilities !== undefined) { + var hasDvbClosedCaptionSignaling = accessibilities.some(function (accessibility) { + return accessibility.schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && accessibility.value === "2"; + }); + if (hasDvbClosedCaptionSignaling) { + return true; + } + } + if (roles !== undefined) { + var hasDashCaptionSinaling = roles.some(function (role) { + return role.schemeIdUri === "urn:mpeg:dash:role:2011" && role.value === "caption"; + }); + if (hasDashCaptionSinaling) { + return true; + } } - return accessibility.schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && accessibility.value === "2"; + return false; } /** * Detect if the accessibility given defines an AdaptationSet containing a sign @@ -20341,8 +20312,6 @@ function hasSignLanguageInterpretation(accessibility) { /** * Contruct Adaptation ID from the information we have. * @param {Object} adaptation - * @param {Array.} representations - * @param {Array.} representations * @param {Object} infos * @returns {string} */ @@ -20351,6 +20320,7 @@ function getAdaptationID(adaptation, infos) { return adaptation.attributes.id; } var isClosedCaption = infos.isClosedCaption, + isForcedSubtitle = infos.isForcedSubtitle, isAudioDescription = infos.isAudioDescription, isSignInterpreted = infos.isSignInterpreted, isTrickModeTrack = infos.isTrickModeTrack, @@ -20362,6 +20332,9 @@ function getAdaptationID(adaptation, infos) { if (isClosedCaption === true) { idString += "-cc"; } + if (isForcedSubtitle === true) { + idString += "-cc"; + } if (isAudioDescription === true) { idString += "-ad"; } @@ -20416,7 +20389,7 @@ function getAdaptationSetSwitchingIDs(adaptation) { * @returns {Array.} */ function parseAdaptationSets(adaptationsIR, context) { - var _a, _b, _c, _d, _e, _f, _g, _h; + var _a, _b, _c, _d, _e, _f; var parsedAdaptations = { video: [], audio: [], @@ -20426,14 +20399,6 @@ function parseAdaptationSets(adaptationsIR, context) { var trickModeAdaptations = []; var adaptationSwitchingInfos = {}; var parsedAdaptationsIDs = []; - /** - * Index of the last parsed Video AdaptationSet with a Role set as "main" in - * `parsedAdaptations.video`. - * `-1` if not yet encountered. - * Used as we merge all main video AdaptationSet due to a comprehension of the - * DASH-IF IOP. - */ - var lastMainVideoAdapIdx = -1; for (var adaptationIdx = 0; adaptationIdx < adaptationsIR.length; adaptationIdx++) { var adaptation = adaptationsIR[adaptationIdx]; var adaptationChildren = adaptation.children; @@ -20456,7 +20421,6 @@ function parseAdaptationSets(adaptationsIR, context) { } var priority = (_c = adaptation.attributes.selectionPriority) !== null && _c !== void 0 ? _c : 1; var originalID = adaptation.attributes.id; - var newID = void 0; var adaptationSetSwitchingIDs = getAdaptationSetSwitchingIDs(adaptation); var parentSegmentTemplates = []; if (context.segmentTemplate !== undefined) { @@ -20486,123 +20450,118 @@ function parseAdaptationSets(adaptationsIR, context) { }) : undefined; var trickModeAttachedAdaptationIds = (_d = trickModeProperty === null || trickModeProperty === void 0 ? void 0 : trickModeProperty.value) === null || _d === void 0 ? void 0 : _d.split(" "); var isTrickModeTrack = trickModeAttachedAdaptationIds !== undefined; - if (type === "video" && isMainAdaptation && lastMainVideoAdapIdx >= 0 && parsedAdaptations.video.length > lastMainVideoAdapIdx && !isTrickModeTrack) { - var _videoMainAdaptation$; - var videoMainAdaptation = parsedAdaptations.video[lastMainVideoAdapIdx][0]; - reprCtxt.unsafelyBaseOnPreviousAdaptation = (_f = (_e = context.unsafelyBaseOnPreviousPeriod) === null || _e === void 0 ? void 0 : _e.getAdaptation(videoMainAdaptation.id)) !== null && _f !== void 0 ? _f : null; - var representations = parseRepresentations(representationsIR, adaptation, reprCtxt); - (_videoMainAdaptation$ = videoMainAdaptation.representations).push.apply(_videoMainAdaptation$, representations); - newID = videoMainAdaptation.id; + var _adaptationChildren = adaptationChildren, + accessibilities = _adaptationChildren.accessibilities; + var isDub = void 0; + if (roles !== undefined && roles.some(function (role) { + return role.value === "dub"; + })) { + isDub = true; + } + var isClosedCaption = void 0; + if (type !== "text") { + isClosedCaption = false; } else { - var _adaptationChildren = adaptationChildren, - accessibilities = _adaptationChildren.accessibilities; - var isDub = void 0; - if (roles !== undefined && roles.some(function (role) { - return role.value === "dub"; - })) { - isDub = true; - } - var isClosedCaption = void 0; - if (type !== "text") { - isClosedCaption = false; - } else if (accessibilities !== undefined) { - isClosedCaption = accessibilities.some(isHardOfHearing); - } - var isAudioDescription = void 0; - if (type !== "audio") { - isAudioDescription = false; - } else if (accessibilities !== undefined) { - isAudioDescription = accessibilities.some(isVisuallyImpaired); - } - var isSignInterpreted = void 0; - if (type !== "video") { - isSignInterpreted = false; - } else if (accessibilities !== undefined) { - isSignInterpreted = accessibilities.some(hasSignLanguageInterpretation); - } - var adaptationID = getAdaptationID(adaptation, { - isAudioDescription: isAudioDescription, - isClosedCaption: isClosedCaption, - isSignInterpreted: isSignInterpreted, - isTrickModeTrack: isTrickModeTrack, - type: type + isClosedCaption = isCaptionning(accessibilities, roles); + } + var isForcedSubtitle = void 0; + if (type === "text" && roles !== undefined && roles.some(function (role) { + return role.value === "forced-subtitle" || role.value === "forced_subtitle"; + })) { + isForcedSubtitle = true; + } + var isAudioDescription = void 0; + if (type !== "audio") { + isAudioDescription = false; + } else if (accessibilities !== undefined) { + isAudioDescription = accessibilities.some(isVisuallyImpaired); + } + var isSignInterpreted = void 0; + if (type !== "video") { + isSignInterpreted = false; + } else if (accessibilities !== undefined) { + isSignInterpreted = accessibilities.some(hasSignLanguageInterpretation); + } + var adaptationID = getAdaptationID(adaptation, { + isAudioDescription: isAudioDescription, + isForcedSubtitle: isForcedSubtitle, + isClosedCaption: isClosedCaption, + isSignInterpreted: isSignInterpreted, + isTrickModeTrack: isTrickModeTrack, + type: type + }); + // Avoid duplicate IDs + while ((0,array_includes/* default */.Z)(parsedAdaptationsIDs, adaptationID)) { + adaptationID += "-dup"; + } + var newID = adaptationID; + parsedAdaptationsIDs.push(adaptationID); + reprCtxt.unsafelyBaseOnPreviousAdaptation = (_f = (_e = context.unsafelyBaseOnPreviousPeriod) === null || _e === void 0 ? void 0 : _e.getAdaptation(adaptationID)) !== null && _f !== void 0 ? _f : null; + var representations = parseRepresentations(representationsIR, adaptation, reprCtxt); + var parsedAdaptationSet = { + id: adaptationID, + representations: representations, + type: type, + isTrickModeTrack: isTrickModeTrack + }; + if (adaptation.attributes.language != null) { + parsedAdaptationSet.language = adaptation.attributes.language; + } + if (isClosedCaption != null) { + parsedAdaptationSet.closedCaption = isClosedCaption; + } + if (isAudioDescription != null) { + parsedAdaptationSet.audioDescription = isAudioDescription; + } + if (isDub === true) { + parsedAdaptationSet.isDub = true; + } + if (isForcedSubtitle !== undefined) { + parsedAdaptationSet.forcedSubtitles = isForcedSubtitle; + } + if (isSignInterpreted === true) { + parsedAdaptationSet.isSignInterpreted = true; + } + if (label !== undefined) { + parsedAdaptationSet.label = label; + } + if (trickModeAttachedAdaptationIds !== undefined) { + trickModeAdaptations.push({ + adaptation: parsedAdaptationSet, + trickModeAttachedAdaptationIds: trickModeAttachedAdaptationIds }); - // Avoid duplicate IDs - while ((0,array_includes/* default */.Z)(parsedAdaptationsIDs, adaptationID)) { - adaptationID += "-dup"; - } - newID = adaptationID; - parsedAdaptationsIDs.push(adaptationID); - reprCtxt.unsafelyBaseOnPreviousAdaptation = (_h = (_g = context.unsafelyBaseOnPreviousPeriod) === null || _g === void 0 ? void 0 : _g.getAdaptation(adaptationID)) !== null && _h !== void 0 ? _h : null; - var _representations = parseRepresentations(representationsIR, adaptation, reprCtxt); - var parsedAdaptationSet = { - id: adaptationID, - representations: _representations, - type: type, - isTrickModeTrack: isTrickModeTrack - }; - if (adaptation.attributes.language != null) { - parsedAdaptationSet.language = adaptation.attributes.language; - } - if (isClosedCaption != null) { - parsedAdaptationSet.closedCaption = isClosedCaption; - } - if (isAudioDescription != null) { - parsedAdaptationSet.audioDescription = isAudioDescription; - } - if (isDub === true) { - parsedAdaptationSet.isDub = true; - } - if (isSignInterpreted === true) { - parsedAdaptationSet.isSignInterpreted = true; - } - if (label !== undefined) { - parsedAdaptationSet.label = label; - } - if (trickModeAttachedAdaptationIds !== undefined) { - trickModeAdaptations.push({ - adaptation: parsedAdaptationSet, - trickModeAttachedAdaptationIds: trickModeAttachedAdaptationIds - }); - } else { - // look if we have to merge this into another Adaptation - var mergedIntoIdx = -1; - var _loop = function _loop() { - var id = _step2.value; - var switchingInfos = adaptationSwitchingInfos[id]; - if (switchingInfos !== undefined && switchingInfos.newID !== newID && (0,array_includes/* default */.Z)(switchingInfos.adaptationSetSwitchingIDs, originalID)) { - mergedIntoIdx = (0,array_find_index/* default */.Z)(parsedAdaptations[type], function (a) { - return a[0].id === id; - }); - var mergedInto = parsedAdaptations[type][mergedIntoIdx]; - if (mergedInto !== undefined && mergedInto[0].audioDescription === parsedAdaptationSet.audioDescription && mergedInto[0].closedCaption === parsedAdaptationSet.closedCaption && mergedInto[0].language === parsedAdaptationSet.language) { - var _mergedInto$0$represe; - log/* default.info */.Z.info("DASH Parser: merging \"switchable\" AdaptationSets", originalID, id); - (_mergedInto$0$represe = mergedInto[0].representations).push.apply(_mergedInto$0$represe, parsedAdaptationSet.representations); - if (type === "video" && isMainAdaptation && !mergedInto[1].isMainAdaptation) { - lastMainVideoAdapIdx = Math.max(lastMainVideoAdapIdx, mergedIntoIdx); - } - mergedInto[1] = { - priority: Math.max(priority, mergedInto[1].priority), - isMainAdaptation: isMainAdaptation || mergedInto[1].isMainAdaptation, - indexInMpd: Math.min(adaptationIdx, mergedInto[1].indexInMpd) - }; - } - } - }; - for (var _iterator2 = parse_adaptation_sets_createForOfIteratorHelperLoose(adaptationSetSwitchingIDs), _step2; !(_step2 = _iterator2()).done;) { - _loop(); - } - if (mergedIntoIdx < 0) { - parsedAdaptations[type].push([parsedAdaptationSet, { - priority: priority, - isMainAdaptation: isMainAdaptation, - indexInMpd: adaptationIdx - }]); - if (type === "video" && isMainAdaptation) { - lastMainVideoAdapIdx = parsedAdaptations.video.length - 1; + } else { + // look if we have to merge this into another Adaptation + var mergedIntoIdx = -1; + var _loop = function _loop() { + var id = _step2.value; + var switchingInfos = adaptationSwitchingInfos[id]; + if (switchingInfos !== undefined && switchingInfos.newID !== newID && (0,array_includes/* default */.Z)(switchingInfos.adaptationSetSwitchingIDs, originalID)) { + mergedIntoIdx = (0,array_find_index/* default */.Z)(parsedAdaptations[type], function (a) { + return a[0].id === id; + }); + var mergedInto = parsedAdaptations[type][mergedIntoIdx]; + if (mergedInto !== undefined && mergedInto[0].audioDescription === parsedAdaptationSet.audioDescription && mergedInto[0].closedCaption === parsedAdaptationSet.closedCaption && mergedInto[0].language === parsedAdaptationSet.language) { + var _mergedInto$0$represe; + log/* default.info */.Z.info("DASH Parser: merging \"switchable\" AdaptationSets", originalID, id); + (_mergedInto$0$represe = mergedInto[0].representations).push.apply(_mergedInto$0$represe, parsedAdaptationSet.representations); + mergedInto[1] = { + priority: Math.max(priority, mergedInto[1].priority), + isMainAdaptation: isMainAdaptation || mergedInto[1].isMainAdaptation, + indexInMpd: Math.min(adaptationIdx, mergedInto[1].indexInMpd) + }; } } + }; + for (var _iterator2 = parse_adaptation_sets_createForOfIteratorHelperLoose(adaptationSetSwitchingIDs), _step2; !(_step2 = _iterator2()).done;) { + _loop(); + } + if (mergedIntoIdx < 0) { + parsedAdaptations[type].push([parsedAdaptationSet, { + priority: priority, + isMainAdaptation: isMainAdaptation, + indexInMpd: adaptationIdx + }]); } } if (originalID != null && adaptationSwitchingInfos[originalID] == null) { @@ -21827,6 +21786,13 @@ function parseSegmentBase(root) { dashName: "startNumber" }); break; + case "endNumber": + parseValue(attr.value, { + asKey: "endNumber", + parser: parseMPDInteger, + dashName: "endNumber" + }); + break; } } return [attributes, warnings]; @@ -25521,6 +25487,13 @@ function applyGeneralStyle(element, style) { */ function applyPStyle(element, style) { element.style.margin = "0px"; + // Set on it the default font-size, more specific font sizes may then be set + // on children elements. + // Doing this on the parent

    elements seems to fix some CSS issues we had + // with too large inner line breaks spacing when the text track element was + // too small, for some reasons. + addClassName(element, "proportional-style"); + element.setAttribute("data-proportional-font-size", "1"); // applies to body, div, p, region, span var paragraphBackgroundColor = style.backgroundColor; if ((0,is_non_empty_string/* default */.Z)(paragraphBackgroundColor)) { @@ -28044,7 +28017,7 @@ function generateManifestParser(options) { * Parse the MPD through the default JS-written parser (as opposed to the * WebAssembly one). * If it is not defined, throws. - * @returns {Observable} + * @returns {Object|Promise.} */ function runDefaultJsParser() { if (parsers.js === null) { @@ -28058,14 +28031,14 @@ function generateManifestParser(options) { * Process return of one of the MPD parser. * If it asks for a resource, load it then continue. * @param {Object} parserResponse - Response returned from a MPD parser. - * @returns {Observable} + * @returns {Object|Promise.} */ function processMpdParserResponse(parserResponse) { if (parserResponse.type === "done") { if (parserResponse.value.warnings.length > 0) { onWarnings(parserResponse.value.warnings); } - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return Promise.reject(cancelSignal.cancellationError); } var manifest = new src_manifest/* default */.ZP(parserResponse.value.parsed, options); @@ -28494,12 +28467,9 @@ var check_isobmff_integrity = __webpack_require__(4460); function addSegmentIntegrityChecks(segmentLoader) { return function (url, content, loaderOptions, initialCancelSignal, callbacks) { return new Promise(function (resolve, reject) { - var requestCanceller = new task_canceller/* default */.ZP({ - cancelOn: initialCancelSignal - }); - // Reject the `CancellationError` when `requestCanceller`'s signal emits - // `stopRejectingOnCancel` here is a function allowing to stop this mechanism - var stopRejectingOnCancel = requestCanceller.signal.register(reject); + var requestCanceller = new task_canceller/* default */.ZP(); + var unlinkCanceller = requestCanceller.linkToSignal(initialCancelSignal); + requestCanceller.signal.register(reject); segmentLoader(url, content, loaderOptions, requestCanceller.signal, Object.assign(Object.assign({}, callbacks), { onNewChunk: function onNewChunk(data) { try { @@ -28507,7 +28477,7 @@ function addSegmentIntegrityChecks(segmentLoader) { callbacks.onNewChunk(data); } catch (err) { // Do not reject with a `CancellationError` after cancelling the request - stopRejectingOnCancel(); + cleanUpCancellers(); // Cancel the request requestCanceller.cancel(); // Reject with thrown error @@ -28515,10 +28485,10 @@ function addSegmentIntegrityChecks(segmentLoader) { } } })).then(function (info) { - if (requestCanceller.isUsed) { + cleanUpCancellers(); + if (requestCanceller.isUsed()) { return; } - stopRejectingOnCancel(); if (info.resultType === "segment-loaded") { try { trowOnIntegrityError(info.resultData.responseData); @@ -28528,10 +28498,14 @@ function addSegmentIntegrityChecks(segmentLoader) { } } resolve(info); - }, function (error) { - stopRejectingOnCancel(); - reject(error); + }, function (err) { + cleanUpCancellers(); + reject(err); }); + function cleanUpCancellers() { + requestCanceller.signal.deregister(reject); + unlinkCanceller(); + } }); /** * If the data's seems to be corrupted, throws an `INTEGRITY_ERROR` error. @@ -28776,7 +28750,7 @@ function lowLatencySegmentLoader(url, content, options, callbacks, cancelSignal) partialChunk = res[1]; for (var i = 0; i < completeChunks.length; i++) { callbacks.onNewChunk(completeChunks[i]); - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } } @@ -28785,7 +28759,7 @@ function lowLatencySegmentLoader(url, content, options, callbacks, cancelSignal) size: info.size, totalSize: info.totalSize }); - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } } @@ -28877,7 +28851,7 @@ function generateSegmentLoader(_ref) { return checkMediaSegmentIntegrity !== true ? segmentLoader : addSegmentIntegrityChecks(segmentLoader); /** * @param {Object|null} wantedCdn - * @returns {Observable} + * @returns {Promise.} */ function segmentLoader(wantedCdn, content, options, cancelSignal, callbacks) { var url = constructSegmentUrl(wantedCdn, content.segment); @@ -28908,7 +28882,7 @@ function generateSegmentLoader(_ref) { * @param {Object} _args */ var resolve = function resolve(_args) { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -28928,7 +28902,7 @@ function generateSegmentLoader(_ref) { */ var reject = function reject(err) { var _a, _b, _c; - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -28940,7 +28914,7 @@ function generateSegmentLoader(_ref) { rej(emittedErr); }; var progress = function progress(_args) { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } callbacks.onProgress({ @@ -28954,7 +28928,7 @@ function generateSegmentLoader(_ref) { * the "regular" implementation */ var fallback = function fallback() { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -29843,7 +29817,7 @@ function getPlainTextTrackData(_ref2, textTrackData, isChunked) { * @param {boolean} __priv_patchLastSegmentInSidx - Enable ugly Canal+-specific * fix for an issue people on the content-packaging side could not fix. * For more information on that, look at the code using it. - * @returns {Observable.} + * @returns {Object} */ function parseISOBMFFEmbeddedTextTrack(data, isChunked, content, initTimescale, __priv_patchLastSegmentInSidx) { var period = content.period, @@ -29901,7 +29875,7 @@ function parseISOBMFFEmbeddedTextTrack(data, isChunked, content, initTimescale, * @param {Object} content - Object describing the context of the given * segment's data: of which segment, `Representation`, `Adaptation`, `Period`, * `Manifest` it is a part of etc. - * @returns {Observable.} + * @returns {Object} */ function parsePlainTextTrack(data, isChunked, content) { var period = content.period, @@ -29950,7 +29924,7 @@ function generateTextTrackParser(_ref) { * @param {Object} loadedSegment * @param {Object} content * @param {number|undefined} initTimescale - * @returns {Observable.} + * @returns {Object} */ return function textTrackParser(loadedSegment, content, initTimescale) { var _a; @@ -32807,7 +32781,7 @@ var generateSegmentLoader = function generateSegmentLoader(_ref) { * @param {Object} args */ var resolve = function resolve(_args) { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -32840,7 +32814,7 @@ var generateSegmentLoader = function generateSegmentLoader(_ref) { */ var reject = function reject(err) { var _a, _b, _c; - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -32852,7 +32826,7 @@ var generateSegmentLoader = function generateSegmentLoader(_ref) { rej(emittedErr); }; var progress = function progress(_args) { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } callbacks.onProgress({ @@ -32862,7 +32836,7 @@ var generateSegmentLoader = function generateSegmentLoader(_ref) { }); }; var fallback = function fallback() { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -33585,7 +33559,7 @@ function callCustomManifestLoader(customManifestLoader, fallbackManifestLoader) * @param {Object} args */ var resolve = function resolve(_args) { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -33607,7 +33581,7 @@ function callCustomManifestLoader(customManifestLoader, fallbackManifestLoader) */ var reject = function reject(err) { var _a, _b, _c; - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -33623,7 +33597,7 @@ function callCustomManifestLoader(customManifestLoader, fallbackManifestLoader) * the "regular" implementation */ var fallback = function fallback() { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -34475,6 +34449,7 @@ function toUint8Array(input) { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "Z": function() { return /* binding */ cancellableSleep; } /* harmony export */ }); +/* harmony import */ var _create_cancellable_promise__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7733); /** * Copyright 2015 CANAL+ Group * @@ -34490,6 +34465,7 @@ function toUint8Array(input) { * See the License for the specific language governing permissions and * limitations under the License. */ + /** * Wait the given `delay`, resolving the Promise when finished. * @@ -34504,296 +34480,76 @@ function toUint8Array(input) { * cancellation with the corresponding `CancellationError`. */ function cancellableSleep(delay, cancellationSignal) { - return new Promise(function (res, rej) { + return (0,_create_cancellable_promise__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)(cancellationSignal, function (res) { var timeout = setTimeout(function () { - unregisterCancelSignal(); - res(); + return res(); }, delay); - var unregisterCancelSignal = cancellationSignal.register(function onCancel(cancellationError) { - clearTimeout(timeout); - rej(cancellationError); - }); + return function () { + return clearTimeout(timeout); + }; }); } /***/ }), -/***/ 8117: +/***/ 7733: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1480); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3102); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2817); -/* harmony import */ var _is_null_or_undefined__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1946); -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "Z": function() { return /* binding */ createCancellablePromise; } +/* harmony export */ }); /** - * Try to cast the given value into an observable. - * StraightForward - test first for an Observable then for a Promise. - * @param {Observable|Function|*} - * @returns {Observable} - */ -function castToObservable(value) { - if (value instanceof rxjs__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y) { - return value; - } else if (value instanceof Promise || !(0,_is_null_or_undefined__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z)(value) && typeof value.then === "function") { - return (0,rxjs__WEBPACK_IMPORTED_MODULE_2__/* .from */ .D)(value); - } - return (0,rxjs__WEBPACK_IMPORTED_MODULE_3__.of)(value); -} -/* harmony default export */ __webpack_exports__["Z"] = (castToObservable); - -/***/ }), - -/***/ 8333: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "Z": function() { return /* binding */ deferSubscriptions; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/subscribeOn.js -var subscribeOn = __webpack_require__(8720); -// EXTERNAL MODULE: ./node_modules/rxjs/node_modules/tslib/tslib.es6.js -var tslib_es6 = __webpack_require__(5987); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/AsyncAction.js + 2 modules -var AsyncAction = __webpack_require__(8337); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/util/Immediate.js -var nextHandle = 1; -var resolved; -var activeHandles = {}; -function findAndClearHandle(handle) { - if (handle in activeHandles) { - delete activeHandles[handle]; - return true; - } - return false; -} -var Immediate = { - setImmediate: function (cb) { - var handle = nextHandle++; - activeHandles[handle] = true; - if (!resolved) { - resolved = Promise.resolve(); - } - resolved.then(function () { return findAndClearHandle(handle) && cb(); }); - return handle; - }, - clearImmediate: function (handle) { - findAndClearHandle(handle); - }, -}; -var TestTools = { - pending: function () { - return Object.keys(activeHandles).length; + * Returns a Promise linked to a `CancellationSignal`, which will reject the + * corresponding `CancellationError` if that signal emits before the wanted + * task finishes (either on success or on error). + * + * The given callback mimicks the Promise interface with the added possibility + * of returning a callback which will be called when and if the task is + * cancelled before being either resolved or rejected. + * In that case, that logic will be called just before the Promise is rejected + * with the corresponding `CancellationError`. + * The point of this callback is to implement aborting logic, such as for + * example aborting a request. + * + * @param {Object} cancellationSignal - The `CancellationSignal` the returned + * Promise will be linked to. + * @param {Function} cb - The function implementing the cancellable Promise. Its + * arguments follow Promise's semantics but it can also return a function which + * will be called when and if `cancellationSignal` emits before either arguments + * are called. + * @returns {Promise} - The created Promise, which will resolve when and if the + * first argument to `cb` is called first and reject either if the second + * argument to `cb` is called first or if the given `CancellationSignal` emits + * before either of the two previous conditions. + */ +function createCancellablePromise(cancellationSignal, cb) { + var abortingLogic; + return new Promise(function (res, rej) { + if (cancellationSignal.cancellationError !== null) { + // If the signal was already triggered before, do not even call `cb` + return rej(cancellationSignal.cancellationError); + } + var hasUnregistered = false; + abortingLogic = cb(function onCancellablePromiseSuccess(val) { + cancellationSignal.deregister(onCancellablePromiseCancellation); + hasUnregistered = true; + res(val); + }, function onCancellablePromiseFailure(err) { + cancellationSignal.deregister(onCancellablePromiseCancellation); + hasUnregistered = true; + rej(err); + }); + if (!hasUnregistered) { + cancellationSignal.register(onCancellablePromiseCancellation); } -}; -//# sourceMappingURL=Immediate.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/immediateProvider.js - - -var setImmediate = Immediate.setImmediate, clearImmediate = Immediate.clearImmediate; -var immediateProvider = { - setImmediate: function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - var delegate = immediateProvider.delegate; - return ((delegate === null || delegate === void 0 ? void 0 : delegate.setImmediate) || setImmediate).apply(void 0, (0,tslib_es6/* __spreadArray */.ev)([], (0,tslib_es6/* __read */.CR)(args))); - }, - clearImmediate: function (handle) { - var delegate = immediateProvider.delegate; - return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearImmediate) || clearImmediate)(handle); - }, - delegate: undefined, -}; -//# sourceMappingURL=immediateProvider.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/AsapAction.js - - - -var AsapAction = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(AsapAction, _super); - function AsapAction(scheduler, work) { - var _this = _super.call(this, scheduler, work) || this; - _this.scheduler = scheduler; - _this.work = work; - return _this; + function onCancellablePromiseCancellation(error) { + if (abortingLogic !== undefined) { + abortingLogic(); + } + rej(error); } - AsapAction.prototype.requestAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { delay = 0; } - if (delay !== null && delay > 0) { - return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); - } - scheduler.actions.push(this); - return scheduler._scheduled || (scheduler._scheduled = immediateProvider.setImmediate(scheduler.flush.bind(scheduler, undefined))); - }; - AsapAction.prototype.recycleAsyncId = function (scheduler, id, delay) { - var _a; - if (delay === void 0) { delay = 0; } - if (delay != null ? delay > 0 : this.delay > 0) { - return _super.prototype.recycleAsyncId.call(this, scheduler, id, delay); - } - var actions = scheduler.actions; - if (id != null && ((_a = actions[actions.length - 1]) === null || _a === void 0 ? void 0 : _a.id) !== id) { - immediateProvider.clearImmediate(id); - scheduler._scheduled = undefined; - } - return undefined; - }; - return AsapAction; -}(AsyncAction/* AsyncAction */.o)); - -//# sourceMappingURL=AsapAction.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/AsyncScheduler.js + 1 modules -var AsyncScheduler = __webpack_require__(9682); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/AsapScheduler.js - - -var AsapScheduler = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(AsapScheduler, _super); - function AsapScheduler() { - return _super !== null && _super.apply(this, arguments) || this; - } - AsapScheduler.prototype.flush = function (action) { - this._active = true; - var flushId = this._scheduled; - this._scheduled = undefined; - var actions = this.actions; - var error; - action = action || actions.shift(); - do { - if ((error = action.execute(action.state, action.delay))) { - break; - } - } while ((action = actions[0]) && action.id === flushId && actions.shift()); - this._active = false; - if (error) { - while ((action = actions[0]) && action.id === flushId && actions.shift()) { - action.unsubscribe(); - } - throw error; - } - }; - return AsapScheduler; -}(AsyncScheduler/* AsyncScheduler */.v)); - -//# sourceMappingURL=AsapScheduler.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/asap.js - - -var asapScheduler = new AsapScheduler(AsapAction); -var asap = (/* unused pure expression or super */ null && (asapScheduler)); -//# sourceMappingURL=asap.js.map -;// CONCATENATED MODULE: ./src/utils/defer_subscriptions.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * At subscription, instead of "running" the Observable right away, wait until - * the current task has finished executing before actually running this - * Observable. - * - * This can be important for example when you want in a given function to - * exploit the same shared Observable which may send synchronous events directly - * after subscription. - * - * Here, you might be left in a situation where the first element subscribing to - * that Observable will receive those synchronous events immediately on - * subscription. Further subscriptions on that Observable will miss out on those - * events - even if those subscriptions happen synchronously after the first - * one. - * - * Calling `deferSubscriptions` in those cases will make sure that all such - * subscriptions can be registered before the Observable start emitting events - * (as long as such Subscriptions are done synchronously). - * - * @example - * ```js - * const myObservable = rxjs.timer(100).pipe(mapTo("ASYNC MSG"), - * startWith("SYNCHRONOUS MSG"), - * share()); - * - * myObservable.subscribe(x => console.log("Sub1:", x)); - * myObservable.subscribe(x => console.log("Sub2:", x)); - * - * setTimeout(() => { - * myObservable.subscribe(x => console.log("Sub3:", x)); - * }, 50); - * - * // You will get: - * // Sub1: SYNCHRONOUS MSG - * // Sub1: ASYNC MSG - * // Sub2: ASYNC MSG - * // Sub3: ASYNC MSG - * - * // ------------------------------ - * - * const myObservableDeferred = rxjs.timer(100).pipe(mapTo("ASYNC MSG"), - * startWith("SYNCHRONOUS MSG"), - * deferSubscriptions(), - * // NOTE: the order is important here - * share()); - * - * myObservableDeferred.subscribe(x => console.log("Sub1:", x)); - * myObservableDeferred.subscribe(x => console.log("Sub2:", x)); - * - * setTimeout(() => { - * myObservableDeferred.subscribe(x => console.log("Sub3:", x)); - * }, 50); - * - * // You will get: - * // Sub1: SYNCHRONOUS MSG - * // Sub2: SYNCHRONOUS MSG - * // Sub1: ASYNC MSG - * // Sub2: ASYNC MSG - * // Sub3: ASYNC MSG - * ``` - * @returns {function} - */ -function deferSubscriptions() { - return function (source) { - // TODO asapScheduler seems to not push the subscription in the microtask - // queue as nextTick does but in a regular event loop queue. - // This means that the subscription will be run even later that we wish for. - // This is not dramatic but it could be better. - // Either this is a problem with RxJS or this was wanted, in which case we - // may need to add our own scheduler. - return source.pipe((0,subscribeOn/* subscribeOn */.R)(asapScheduler)); - }; + }); } /***/ }), @@ -34803,10 +34559,8 @@ function deferSubscriptions() { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "R": function() { return /* binding */ fromEvent; }, /* harmony export */ "Z": function() { return /* binding */ EventEmitter; } /* harmony export */ }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1480); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3887); /* harmony import */ var _is_null_or_undefined__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1946); /** @@ -34826,7 +34580,6 @@ function deferSubscriptions() { */ - /** * Simple but fully type-safe EventEmitter implementation. * @class EventEmitter @@ -34911,74 +34664,7 @@ var EventEmitter = /*#__PURE__*/function () { }; return EventEmitter; }(); -/** - * Simple redefinition of the fromEvent from rxjs to also work on our - * implementation of EventEmitter with type-checked strings - * @param {Object} target - * @param {string} eventName - * @returns {Observable} - */ - -function fromEvent(target, eventName) { - return new rxjs__WEBPACK_IMPORTED_MODULE_2__/* .Observable */ .y(function (obs) { - function handler(event) { - obs.next(event); - } - target.addEventListener(eventName, handler); - return function () { - target.removeEventListener(eventName, handler); - }; - }); -} - -/***/ }), - -/***/ 2793: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ filterMap; } -/* harmony export */ }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9917); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9127); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4975); -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Special kind of map which will ignore the result when the value emitted - * corresponds to a given token. - * - * This can also be performed through a `mergeMap` (by returning the `EMPTY` - * Observable when we want to ignore events) but using `filterMap` is both more - * straightforward and more performant. - * @param {function} callback - * @param {*} filteringToken - * @returns {function} - */ -function filterMap(callback, filteringToken) { - return function (source) { - return (0,rxjs__WEBPACK_IMPORTED_MODULE_0__/* .defer */ .P)(function () { - return source.pipe((0,rxjs__WEBPACK_IMPORTED_MODULE_1__/* .map */ .U)(callback), (0,rxjs__WEBPACK_IMPORTED_MODULE_2__/* .filter */ .h)(function (x) { - return x !== filteringToken; - })); - }); - }; -} /***/ }), @@ -36236,8 +35922,6 @@ function isTimeInTimeRanges(ranges, time) { /* harmony export */ "ZP": function() { return /* binding */ createSharedReference; }, /* harmony export */ "lR": function() { return /* binding */ createMappedReference; } /* harmony export */ }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1480); -/* harmony import */ var _task_canceller__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(288); function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } @@ -36256,17 +35940,21 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len * See the License for the specific language governing permissions and * limitations under the License. */ - - /** * Create an `ISharedReference` object encapsulating the mutable `initialValue` * value of type T. * * @see ISharedReference * @param {*} initialValue - * @returns {Observable} + * @param {Object|undefined} [cancelSignal] - If set, the created shared + * reference will be automatically "finished" once that signal emits. + * Finished references won't be able to update their value anymore, and will + * also automatically have their listeners (callbacks linked to value change) + * removed - as they cannot be triggered anymore, thus providing a security + * against memory leaks. + * @returns {Object} */ -function createSharedReference(initialValue) { +function createSharedReference(initialValue, cancelSignal) { /** Current value referenced by this `ISharedReference`. */ var value = initialValue; /** @@ -36278,8 +35966,8 @@ function createSharedReference(initialValue) { * once it changes * - `complete`: Allows to clean-up the listener, will be called once the * reference is finished. - * - `hasBeenCleared`: becomes `true` when the Observable becomes - * unsubscribed and thus when it is removed from the `cbs` array. + * - `hasBeenCleared`: becomes `true` when the reference is + * removed from the `cbs` array. * Adding this property allows to detect when a previously-added * listener has since been removed e.g. as a side-effect during a * function call. @@ -36287,6 +35975,9 @@ function createSharedReference(initialValue) { */ var cbs = []; var isFinished = false; + if (cancelSignal !== undefined) { + cancelSignal.register(finish); + } return { /** * Returns the current value of this shared reference. @@ -36313,7 +36004,7 @@ function createSharedReference(initialValue) { var cbObj = _step.value; try { if (!cbObj.hasBeenCleared) { - cbObj.trigger(newVal); + cbObj.trigger(newVal, cbObj.complete); } } catch (_) { /* nothing */ @@ -36329,47 +36020,12 @@ function createSharedReference(initialValue) { this.setValue(newVal); } }, - /** - * Returns an Observable which synchronously emits the current value (unless - * the `skipCurrentValue` argument has been set to `true`) and then each - * time a new value is set. - * @param {boolean} [skipCurrentValue] - * @returns {Observable} - */ - asObservable: function asObservable(skipCurrentValue) { - return new rxjs__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (obs) { - if (skipCurrentValue !== true) { - obs.next(value); - } - if (isFinished) { - obs.complete(); - return undefined; - } - var cbObj = { - trigger: obs.next.bind(obs), - complete: obs.complete.bind(obs), - hasBeenCleared: false - }; - cbs.push(cbObj); - return function () { - /** - * Code in here can still be running while this is happening. - * Set `hasBeenCleared` to `true` to avoid still using the - * `subscriber` from this object. - */ - cbObj.hasBeenCleared = true; - var indexOf = cbs.indexOf(cbObj); - if (indexOf >= 0) { - cbs.splice(indexOf, 1); - } - }; - }); - }, /** * Allows to register a callback to be called each time the value inside the * reference is updated. * @param {Function} cb - Callback to be called each time the reference is - * updated. Takes the new value im argument. + * updated. Takes as first argument its new value and in second argument a + * callback allowing to unregister the callback. * @param {Object|undefined} [options] * @param {Object|undefined} [options.clearSignal] - Allows to provide a * CancellationSignal which will unregister the callback when it emits. @@ -36377,28 +36033,30 @@ function createSharedReference(initialValue) { * callback will also be immediately called with the current value. */ onUpdate: function onUpdate(cb, options) { - if ((options === null || options === void 0 ? void 0 : options.emitCurrentValue) === true) { - cb(value); - } - if (isFinished) { - return; - } var cbObj = { trigger: cb, complete: unlisten, hasBeenCleared: false }; cbs.push(cbObj); + if ((options === null || options === void 0 ? void 0 : options.emitCurrentValue) === true) { + cb(value, unlisten); + } + if (isFinished || cbObj.hasBeenCleared) { + unlisten(); + return; + } if ((options === null || options === void 0 ? void 0 : options.clearSignal) === undefined) { return; } options.clearSignal.register(unlisten); function unlisten() { - /** - * Code in here can still be running while this is happening. - * Set `hasBeenCleared` to `true` to avoid still using the - * `subscriber` from this object. - */ + if ((options === null || options === void 0 ? void 0 : options.clearSignal) !== undefined) { + options.clearSignal.deregister(unlisten); + } + if (cbObj.hasBeenCleared) { + return; + } cbObj.hasBeenCleared = true; var indexOf = cbs.indexOf(cbObj); if (indexOf >= 0) { @@ -36406,52 +36064,56 @@ function createSharedReference(initialValue) { } } }, + /** + * Variant of `onUpdate` which will only call the callback once, once the + * value inside the reference is different from `undefined`. + * The callback is called synchronously if the value already isn't set to + * `undefined`. + * + * This method can be used as a lighter weight alternative to `onUpdate` + * when just waiting that the stored value becomes defined. + * @param {Function} cb - Callback to be called each time the reference is + * updated. Takes the new value in argument. + * @param {Object} [options] + * @param {Object} [options.clearSignal] - Allows to provide a + * CancellationSignal which will unregister the callback when it emits. + */ waitUntilDefined: function waitUntilDefined(cb, options) { - if (value !== undefined) { - cb(value); - return; - } - if (isFinished) { - return; - } - var childCanceller = new _task_canceller__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .ZP(); - if ((options === null || options === void 0 ? void 0 : options.clearSignal) !== undefined) { - options.clearSignal.register(function () { - return childCanceller.cancel(); - }); - } - this.onUpdate(function (val) { + this.onUpdate(function (val, stopListening) { if (val !== undefined) { - childCanceller.cancel(); + stopListening(); cb(value); - return; } }, { - clearSignal: childCanceller.signal + clearSignal: options === null || options === void 0 ? void 0 : options.clearSignal, + emitCurrentValue: true }); }, /** * Indicate that no new values will be emitted. - * Allows to automatically close all Observables generated from this shared - * reference. + * Allows to automatically free all listeners linked to this reference. */ - finish: function finish() { - isFinished = true; - var clonedCbs = cbs.slice(); - for (var _iterator2 = _createForOfIteratorHelperLoose(clonedCbs), _step2; !(_step2 = _iterator2()).done;) { - var cbObj = _step2.value; - try { - if (!cbObj.hasBeenCleared) { - cbObj.complete(); - } + finish: finish + }; + function finish() { + if (cancelSignal !== undefined) { + cancelSignal.deregister(finish); + } + isFinished = true; + var clonedCbs = cbs.slice(); + for (var _iterator2 = _createForOfIteratorHelperLoose(clonedCbs), _step2; !(_step2 = _iterator2()).done;) { + var cbObj = _step2.value; + try { + if (!cbObj.hasBeenCleared) { + cbObj.complete(); cbObj.hasBeenCleared = true; - } catch (_) { - /* nothing */ } + } catch (_) { + /* nothing */ } - cbs.length = 0; } - }; + cbs.length = 0; + } } /** * Create a new `ISharedReference` based on another one by mapping over its @@ -36460,12 +36122,12 @@ function createSharedReference(initialValue) { * over. * @param {Function} mappingFn - The mapping function which will receives * `originalRef`'s value and outputs this new reference's value. - * @param {Object | undefined} [cancellationSignal] - Optionally, a - * `CancellationSignal` which will finish that reference when it emits. + * @param {Object} cancellationSignal - Optionally, a `CancellationSignal` which + * will finish that reference when it emits. * @returns {Object} - The new, mapped, reference. */ function createMappedReference(originalRef, mappingFn, cancellationSignal) { - var newRef = createSharedReference(mappingFn(originalRef.getValue())); + var newRef = createSharedReference(mappingFn(originalRef.getValue()), cancellationSignal); originalRef.onUpdate(function mapOriginalReference(x) { newRef.setValue(mappingFn(x)); }, { @@ -36473,11 +36135,6 @@ function createMappedReference(originalRef, mappingFn, cancellationSignal) { }); // TODO nothing is done if `originalRef` is finished, though the returned // reference could also be finished in that case. To do? - if (cancellationSignal !== undefined) { - cancellationSignal.register(function () { - newRef.finish(); - }); - } return newRef; } @@ -36575,7 +36232,7 @@ function request(options) { } reject(err); }); - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } } @@ -36804,48 +36461,6 @@ function getFilenameIndexInUrl(url) { } -/***/ }), - -/***/ 5561: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ tryCatch; } -/* harmony export */ }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3610); -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @param {Function} func - A function you want to execute - * @param {*} argsForFunc - The function's argument - * @returns {*} - If it fails, returns a throwing Observable, else the - * function's result (which should be, in most cases, an Observable). - */ -function tryCatch(func, argsForFunc) { - try { - return func(argsForFunc); - } catch (e) { - return (0,rxjs__WEBPACK_IMPORTED_MODULE_0__/* .throwError */ ._)(function () { - return e; - }); - } -} - /***/ }), /***/ 9252: @@ -37316,14 +36931,15 @@ function takeFirstSet() { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "FU": function() { return /* binding */ CancellationError; }, +/* harmony export */ "XG": function() { return /* binding */ CancellationSignal; }, /* harmony export */ "ZP": function() { return /* binding */ TaskCanceller; } /* harmony export */ }); -/* unused harmony export CancellationSignal */ -/* harmony import */ var _babel_runtime_helpers_assertThisInitialized__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7326); -/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4578); -/* harmony import */ var _babel_runtime_helpers_wrapNativeSuper__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2146); -/* harmony import */ var _assert__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(811); -/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8894); +/* harmony import */ var _babel_runtime_helpers_assertThisInitialized__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7326); +/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4578); +/* harmony import */ var _babel_runtime_helpers_wrapNativeSuper__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2146); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3887); +/* harmony import */ var _assert__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(811); +/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8894); @@ -37344,6 +36960,7 @@ function takeFirstSet() { */ + /** * Class facilitating asynchronous task cancellation. * @@ -37446,22 +37063,44 @@ var TaskCanceller = /*#__PURE__*/function () { * Creates a new `TaskCanceller`, with its own `CancellationSignal` created * as its `signal` provide. * You can then pass this property to async task you wish to be cancellable. - * @param {Object|undefined} options */ - function TaskCanceller(options) { - var _this = this; + function TaskCanceller() { var _createCancellationFu = createCancellationFunctions(), trigger = _createCancellationFu[0], register = _createCancellationFu[1]; - this.isUsed = false; + this._isUsed = false; this._trigger = trigger; this.signal = new CancellationSignal(register); - if ((options === null || options === void 0 ? void 0 : options.cancelOn) !== undefined) { - var unregisterParent = options.cancelOn.register(function () { - _this.cancel(); - }); - this.signal.register(unregisterParent); - } + } + /** + * Returns `true` if this `TaskCanceller` has already been triggered. + * `false` otherwise. + */ + var _proto = TaskCanceller.prototype; + _proto.isUsed = function isUsed() { + return this._isUsed; + } + /** + * Bind this `TaskCanceller` to a `CancellationSignal`, so the former + * is automatically cancelled when the latter is triggered. + * + * Note that this call registers a callback on the given signal, until either + * the current `TaskCanceller` is cancelled or until this given + * `CancellationSignal` is triggered. + * To avoid leaking memory, the returned callback allow to undo this link. + * It should be called if/when that link is not needed anymore, such as when + * there is no need for this `TaskCanceller` anymore. + * + * @param {Object} signal + * @returns {Function} + */; + _proto.linkToSignal = function linkToSignal(signal) { + var _this = this; + var unregister = signal.register(function () { + _this.cancel(); + }); + this.signal.register(unregister); + return unregister; } /** * "Trigger" the `TaskCanceller`, notify through its associated @@ -37473,13 +37112,12 @@ var TaskCanceller = /*#__PURE__*/function () { * cancellation is actually triggered as a chain reaction from a previous * cancellation. * @param {Error} [srcError] - */ - var _proto = TaskCanceller.prototype; + */; _proto.cancel = function cancel(srcError) { - if (this.isUsed) { + if (this._isUsed) { return; } - this.isUsed = true; + this._isUsed = true; var cancellationError = srcError !== null && srcError !== void 0 ? srcError : new CancellationError(); this._trigger(cancellationError); } @@ -37509,18 +37147,31 @@ var CancellationSignal = /*#__PURE__*/function () { */ function CancellationSignal(registerToSource) { var _this2 = this; - this.isCancelled = false; + this._isCancelled = false; this.cancellationError = null; this._listeners = []; registerToSource(function (cancellationError) { _this2.cancellationError = cancellationError; - _this2.isCancelled = true; + _this2._isCancelled = true; while (_this2._listeners.length > 0) { - var listener = _this2._listeners.splice(_this2._listeners.length - 1, 1)[0]; - listener(cancellationError); + try { + var listener = _this2._listeners.pop(); + listener === null || listener === void 0 ? void 0 : listener(cancellationError); + } catch (err) { + _log__WEBPACK_IMPORTED_MODULE_0__/* ["default"].error */ .Z.error("Error while calling clean up listener", err instanceof Error ? err.toString() : "Unknown error"); + } } }); } + /** + * Returns `true` when the cancellation order was already triggered, meaning + * that the linked task needs to be aborted. + * @returns boolean + */ + var _proto2 = CancellationSignal.prototype; + _proto2.isCancelled = function isCancelled() { + return this._isCancelled; + } /** * Registers a function that will be called when/if the current task is * cancelled. @@ -37541,13 +37192,13 @@ var CancellationSignal = /*#__PURE__*/function () { * task succeeded or failed). * You don't need to call that function when cancellation has already been * performed. - */ - var _proto2 = CancellationSignal.prototype; + */; _proto2.register = function register(fn) { var _this3 = this; - if (this.isCancelled) { - (0,_assert__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)(this.cancellationError !== null); + if (this._isCancelled) { + (0,_assert__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z)(this.cancellationError !== null); fn(this.cancellationError); + return _noop__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .Z; } this._listeners.push(fn); return function () { @@ -37563,13 +37214,9 @@ var CancellationSignal = /*#__PURE__*/function () { * @param {Function} fn */; _proto2.deregister = function deregister(fn) { - if (this.isCancelled) { - return; - } - for (var i = 0; i < this._listeners.length; i++) { + for (var i = this._listeners.length - 1; i >= 0; i--) { if (this._listeners[i] === fn) { this._listeners.splice(i, 1); - return; } } }; @@ -37581,25 +37228,25 @@ var CancellationSignal = /*#__PURE__*/function () { * @extends Error */ var CancellationError = /*#__PURE__*/function (_Error) { - (0,_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .Z)(CancellationError, _Error); + (0,_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z)(CancellationError, _Error); function CancellationError() { var _this4; _this4 = _Error.call(this) || this; // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class - Object.setPrototypeOf((0,_babel_runtime_helpers_assertThisInitialized__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .Z)(_this4), CancellationError.prototype); + Object.setPrototypeOf((0,_babel_runtime_helpers_assertThisInitialized__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z)(_this4), CancellationError.prototype); _this4.name = "CancellationError"; _this4.message = "This task was cancelled."; return _this4; } return CancellationError; -}( /*#__PURE__*/(0,_babel_runtime_helpers_wrapNativeSuper__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z)(Error)); +}( /*#__PURE__*/(0,_babel_runtime_helpers_wrapNativeSuper__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .Z)(Error)); /** * Helper function allowing communication between a `TaskCanceller` and a * `CancellationSignal`. * @returns {Array.} */ function createCancellationFunctions() { - var listener = _noop__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z; + var listener = _noop__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .Z; return [function trigger(error) { listener(error); }, function register(newListener) { @@ -37733,5036 +37380,1685 @@ module.exports = (function () { /***/ }), -/***/ 1480: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +/***/ 7061: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { -"use strict"; +var _typeof = (__webpack_require__(8698)["default"]); -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "y": function() { return /* binding */ Observable; } -}); +function _regeneratorRuntime() { + "use strict"; + /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ + + module.exports = _regeneratorRuntime = function _regeneratorRuntime() { + return exports; + }, module.exports.__esModule = true, module.exports["default"] = module.exports; + var exports = {}, + Op = Object.prototype, + hasOwn = Op.hasOwnProperty, + $Symbol = "function" == typeof Symbol ? Symbol : {}, + iteratorSymbol = $Symbol.iterator || "@@iterator", + asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", + toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subscriber.js + 1 modules -var Subscriber = __webpack_require__(6267); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subscription.js + 1 modules -var Subscription = __webpack_require__(5720); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/symbol/observable.js -var observable = __webpack_require__(6766); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/identity.js -var identity = __webpack_require__(278); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/util/pipe.js + function define(obj, key, value) { + return Object.defineProperty(obj, key, { + value: value, + enumerable: !0, + configurable: !0, + writable: !0 + }), obj[key]; + } -function pipe() { - var fns = []; - for (var _i = 0; _i < arguments.length; _i++) { - fns[_i] = arguments[_i]; - } - return pipeFromArray(fns); -} -function pipeFromArray(fns) { - if (fns.length === 0) { - return identity/* identity */.y; - } - if (fns.length === 1) { - return fns[0]; - } - return function piped(input) { - return fns.reduce(function (prev, fn) { return fn(prev); }, input); + try { + define({}, ""); + } catch (err) { + define = function define(obj, key, value) { + return obj[key] = value; }; -} -//# sourceMappingURL=pipe.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/config.js -var config = __webpack_require__(3912); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isFunction.js -var isFunction = __webpack_require__(8474); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/errorContext.js -var errorContext = __webpack_require__(8846); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/Observable.js + } + + function wrap(innerFn, outerFn, self, tryLocsList) { + var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator, + generator = Object.create(protoGenerator.prototype), + context = new Context(tryLocsList || []); + return generator._invoke = function (innerFn, self, context) { + var state = "suspendedStart"; + return function (method, arg) { + if ("executing" === state) throw new Error("Generator is already running"); + if ("completed" === state) { + if ("throw" === method) throw arg; + return doneResult(); + } + for (context.method = method, context.arg = arg;;) { + var delegate = context.delegate; + if (delegate) { + var delegateResult = maybeInvokeDelegate(delegate, context); + if (delegateResult) { + if (delegateResult === ContinueSentinel) continue; + return delegateResult; + } + } + if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { + if ("suspendedStart" === state) throw state = "completed", context.arg; + context.dispatchException(context.arg); + } else "return" === context.method && context.abrupt("return", context.arg); + state = "executing"; + var record = tryCatch(innerFn, self, context); + if ("normal" === record.type) { + if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; + return { + value: record.arg, + done: context.done + }; + } -var Observable = (function () { - function Observable(subscribe) { - if (subscribe) { - this._subscribe = subscribe; + "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); } + }; + }(innerFn, self, context), generator; + } + + function tryCatch(fn, obj, arg) { + try { + return { + type: "normal", + arg: fn.call(obj, arg) + }; + } catch (err) { + return { + type: "throw", + arg: err + }; } - Observable.prototype.lift = function (operator) { - var observable = new Observable(); - observable.source = this; - observable.operator = operator; - return observable; - }; - Observable.prototype.subscribe = function (observerOrNext, error, complete) { - var _this = this; - var subscriber = isSubscriber(observerOrNext) ? observerOrNext : new Subscriber/* SafeSubscriber */.Hp(observerOrNext, error, complete); - (0,errorContext/* errorContext */.x)(function () { - var _a = _this, operator = _a.operator, source = _a.source; - subscriber.add(operator - ? - operator.call(subscriber, source) - : source - ? - _this._subscribe(subscriber) - : - _this._trySubscribe(subscriber)); - }); - return subscriber; - }; - Observable.prototype._trySubscribe = function (sink) { - try { - return this._subscribe(sink); - } - catch (err) { - sink.error(err); - } - }; - Observable.prototype.forEach = function (next, promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var subscriber = new Subscriber/* SafeSubscriber */.Hp({ - next: function (value) { - try { - next(value); - } - catch (err) { - reject(err); - subscriber.unsubscribe(); - } - }, - error: reject, - complete: resolve, - }); - _this.subscribe(subscriber); - }); - }; - Observable.prototype._subscribe = function (subscriber) { - var _a; - return (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber); - }; - Observable.prototype[observable/* observable */.L] = function () { - return this; - }; - Observable.prototype.pipe = function () { - var operations = []; - for (var _i = 0; _i < arguments.length; _i++) { - operations[_i] = arguments[_i]; - } - return pipeFromArray(operations)(this); - }; - Observable.prototype.toPromise = function (promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var value; - _this.subscribe(function (x) { return (value = x); }, function (err) { return reject(err); }, function () { return resolve(value); }); + } + + exports.wrap = wrap; + var ContinueSentinel = {}; + + function Generator() {} + + function GeneratorFunction() {} + + function GeneratorFunctionPrototype() {} + + var IteratorPrototype = {}; + define(IteratorPrototype, iteratorSymbol, function () { + return this; + }); + var getProto = Object.getPrototypeOf, + NativeIteratorPrototype = getProto && getProto(getProto(values([]))); + NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol) && (IteratorPrototype = NativeIteratorPrototype); + var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); + + function defineIteratorMethods(prototype) { + ["next", "throw", "return"].forEach(function (method) { + define(prototype, method, function (arg) { + return this._invoke(method, arg); + }); + }); + } + + function AsyncIterator(generator, PromiseImpl) { + function invoke(method, arg, resolve, reject) { + var record = tryCatch(generator[method], generator, arg); + + if ("throw" !== record.type) { + var result = record.arg, + value = result.value; + return value && "object" == _typeof(value) && hasOwn.call(value, "__await") ? PromiseImpl.resolve(value.__await).then(function (value) { + invoke("next", value, resolve, reject); + }, function (err) { + invoke("throw", err, resolve, reject); + }) : PromiseImpl.resolve(value).then(function (unwrapped) { + result.value = unwrapped, resolve(result); + }, function (error) { + return invoke("throw", error, resolve, reject); }); - }; - Observable.create = function (subscribe) { - return new Observable(subscribe); - }; - return Observable; -}()); + } -function getPromiseCtor(promiseCtor) { - var _a; - return (_a = promiseCtor !== null && promiseCtor !== void 0 ? promiseCtor : config/* config.Promise */.v.Promise) !== null && _a !== void 0 ? _a : Promise; -} -function isObserver(value) { - return value && (0,isFunction/* isFunction */.m)(value.next) && (0,isFunction/* isFunction */.m)(value.error) && (0,isFunction/* isFunction */.m)(value.complete); -} -function isSubscriber(value) { - return (value && value instanceof Subscriber/* Subscriber */.Lv) || (isObserver(value) && (0,Subscription/* isSubscription */.Nn)(value)); -} -//# sourceMappingURL=Observable.js.map + reject(record.arg); + } -/***/ }), + var previousPromise; -/***/ 3: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + this._invoke = function (method, arg) { + function callInvokeWithMethodAndArg() { + return new PromiseImpl(function (resolve, reject) { + invoke(method, arg, resolve, reject); + }); + } -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "t": function() { return /* binding */ ReplaySubject; } -/* harmony export */ }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5987); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6716); -/* harmony import */ var _scheduler_dateTimestampProvider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4318); - - - -var ReplaySubject = (function (_super) { - (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__extends */ .ZT)(ReplaySubject, _super); - function ReplaySubject(_bufferSize, _windowTime, _timestampProvider) { - if (_bufferSize === void 0) { _bufferSize = Infinity; } - if (_windowTime === void 0) { _windowTime = Infinity; } - if (_timestampProvider === void 0) { _timestampProvider = _scheduler_dateTimestampProvider__WEBPACK_IMPORTED_MODULE_1__/* .dateTimestampProvider */ .l; } - var _this = _super.call(this) || this; - _this._bufferSize = _bufferSize; - _this._windowTime = _windowTime; - _this._timestampProvider = _timestampProvider; - _this._buffer = []; - _this._infiniteTimeWindow = true; - _this._infiniteTimeWindow = _windowTime === Infinity; - _this._bufferSize = Math.max(1, _bufferSize); - _this._windowTime = Math.max(1, _windowTime); - return _this; - } - ReplaySubject.prototype.next = function (value) { - var _a = this, isStopped = _a.isStopped, _buffer = _a._buffer, _infiniteTimeWindow = _a._infiniteTimeWindow, _timestampProvider = _a._timestampProvider, _windowTime = _a._windowTime; - if (!isStopped) { - _buffer.push(value); - !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime); - } - this._trimBuffer(); - _super.prototype.next.call(this, value); - }; - ReplaySubject.prototype._subscribe = function (subscriber) { - this._throwIfClosed(); - this._trimBuffer(); - var subscription = this._innerSubscribe(subscriber); - var _a = this, _infiniteTimeWindow = _a._infiniteTimeWindow, _buffer = _a._buffer; - var copy = _buffer.slice(); - for (var i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) { - subscriber.next(copy[i]); - } - this._checkFinalizedStatuses(subscriber); - return subscription; - }; - ReplaySubject.prototype._trimBuffer = function () { - var _a = this, _bufferSize = _a._bufferSize, _timestampProvider = _a._timestampProvider, _buffer = _a._buffer, _infiniteTimeWindow = _a._infiniteTimeWindow; - var adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize; - _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize); - if (!_infiniteTimeWindow) { - var now = _timestampProvider.now(); - var last = 0; - for (var i = 1; i < _buffer.length && _buffer[i] <= now; i += 2) { - last = i; - } - last && _buffer.splice(0, last + 1); - } + return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); }; - return ReplaySubject; -}(_Subject__WEBPACK_IMPORTED_MODULE_2__/* .Subject */ .x)); - -//# sourceMappingURL=ReplaySubject.js.map + } -/***/ }), + function maybeInvokeDelegate(delegate, context) { + var method = delegate.iterator[context.method]; -/***/ 6716: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + if (undefined === method) { + if (context.delegate = null, "throw" === context.method) { + if (delegate.iterator["return"] && (context.method = "return", context.arg = undefined, maybeInvokeDelegate(delegate, context), "throw" === context.method)) return ContinueSentinel; + context.method = "throw", context.arg = new TypeError("The iterator does not provide a 'throw' method"); + } -"use strict"; + return ContinueSentinel; + } -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "x": function() { return /* binding */ Subject; } -}); + var record = tryCatch(method, delegate.iterator, context.arg); + if ("throw" === record.type) return context.method = "throw", context.arg = record.arg, context.delegate = null, ContinueSentinel; + var info = record.arg; + return info ? info.done ? (context[delegate.resultName] = info.value, context.next = delegate.nextLoc, "return" !== context.method && (context.method = "next", context.arg = undefined), context.delegate = null, ContinueSentinel) : info : (context.method = "throw", context.arg = new TypeError("iterator result is not an object"), context.delegate = null, ContinueSentinel); + } -// UNUSED EXPORTS: AnonymousSubject - -// EXTERNAL MODULE: ./node_modules/rxjs/node_modules/tslib/tslib.es6.js -var tslib_es6 = __webpack_require__(5987); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Observable.js + 1 modules -var Observable = __webpack_require__(1480); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subscription.js + 1 modules -var Subscription = __webpack_require__(5720); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/createErrorClass.js -var createErrorClass = __webpack_require__(1819); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/util/ObjectUnsubscribedError.js - -var ObjectUnsubscribedError = (0,createErrorClass/* createErrorClass */.d)(function (_super) { - return function ObjectUnsubscribedErrorImpl() { - _super(this); - this.name = 'ObjectUnsubscribedError'; - this.message = 'object unsubscribed'; + function pushTryEntry(locs) { + var entry = { + tryLoc: locs[0] }; -}); -//# sourceMappingURL=ObjectUnsubscribedError.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/arrRemove.js -var arrRemove = __webpack_require__(3699); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/errorContext.js -var errorContext = __webpack_require__(8846); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/Subject.js + 1 in locs && (entry.catchLoc = locs[1]), 2 in locs && (entry.finallyLoc = locs[2], entry.afterLoc = locs[3]), this.tryEntries.push(entry); + } + function resetTryEntry(entry) { + var record = entry.completion || {}; + record.type = "normal", delete record.arg, entry.completion = record; + } + function Context(tryLocsList) { + this.tryEntries = [{ + tryLoc: "root" + }], tryLocsList.forEach(pushTryEntry, this), this.reset(!0); + } + function values(iterable) { + if (iterable) { + var iteratorMethod = iterable[iteratorSymbol]; + if (iteratorMethod) return iteratorMethod.call(iterable); + if ("function" == typeof iterable.next) return iterable; + if (!isNaN(iterable.length)) { + var i = -1, + next = function next() { + for (; ++i < iterable.length;) { + if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; + } + return next.value = undefined, next.done = !0, next; + }; -var Subject = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(Subject, _super); - function Subject() { - var _this = _super.call(this) || this; - _this.closed = false; - _this.currentObservers = null; - _this.observers = []; - _this.isStopped = false; - _this.hasError = false; - _this.thrownError = null; - return _this; + return next.next = next; + } } - Subject.prototype.lift = function (operator) { - var subject = new AnonymousSubject(this, this); - subject.operator = operator; - return subject; - }; - Subject.prototype._throwIfClosed = function () { - if (this.closed) { - throw new ObjectUnsubscribedError(); - } - }; - Subject.prototype.next = function (value) { - var _this = this; - (0,errorContext/* errorContext */.x)(function () { - var e_1, _a; - _this._throwIfClosed(); - if (!_this.isStopped) { - if (!_this.currentObservers) { - _this.currentObservers = Array.from(_this.observers); - } - try { - for (var _b = (0,tslib_es6/* __values */.XA)(_this.currentObservers), _c = _b.next(); !_c.done; _c = _b.next()) { - var observer = _c.value; - observer.next(value); - } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { - try { - if (_c && !_c.done && (_a = _b.return)) _a.call(_b); - } - finally { if (e_1) throw e_1.error; } - } - } - }); - }; - Subject.prototype.error = function (err) { - var _this = this; - (0,errorContext/* errorContext */.x)(function () { - _this._throwIfClosed(); - if (!_this.isStopped) { - _this.hasError = _this.isStopped = true; - _this.thrownError = err; - var observers = _this.observers; - while (observers.length) { - observers.shift().error(err); - } - } - }); + + return { + next: doneResult }; - Subject.prototype.complete = function () { - var _this = this; - (0,errorContext/* errorContext */.x)(function () { - _this._throwIfClosed(); - if (!_this.isStopped) { - _this.isStopped = true; - var observers = _this.observers; - while (observers.length) { - observers.shift().complete(); - } - } - }); + } + + function doneResult() { + return { + value: undefined, + done: !0 }; - Subject.prototype.unsubscribe = function () { - this.isStopped = this.closed = true; - this.observers = this.currentObservers = null; + } + + return GeneratorFunction.prototype = GeneratorFunctionPrototype, define(Gp, "constructor", GeneratorFunctionPrototype), define(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { + var ctor = "function" == typeof genFun && genFun.constructor; + return !!ctor && (ctor === GeneratorFunction || "GeneratorFunction" === (ctor.displayName || ctor.name)); + }, exports.mark = function (genFun) { + return Object.setPrototypeOf ? Object.setPrototypeOf(genFun, GeneratorFunctionPrototype) : (genFun.__proto__ = GeneratorFunctionPrototype, define(genFun, toStringTagSymbol, "GeneratorFunction")), genFun.prototype = Object.create(Gp), genFun; + }, exports.awrap = function (arg) { + return { + __await: arg }; - Object.defineProperty(Subject.prototype, "observed", { - get: function () { - var _a; - return ((_a = this.observers) === null || _a === void 0 ? void 0 : _a.length) > 0; - }, - enumerable: false, - configurable: true + }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, asyncIteratorSymbol, function () { + return this; + }), exports.AsyncIterator = AsyncIterator, exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) { + void 0 === PromiseImpl && (PromiseImpl = Promise); + var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl); + return exports.isGeneratorFunction(outerFn) ? iter : iter.next().then(function (result) { + return result.done ? result.value : iter.next(); }); - Subject.prototype._trySubscribe = function (subscriber) { - this._throwIfClosed(); - return _super.prototype._trySubscribe.call(this, subscriber); - }; - Subject.prototype._subscribe = function (subscriber) { - this._throwIfClosed(); - this._checkFinalizedStatuses(subscriber); - return this._innerSubscribe(subscriber); - }; - Subject.prototype._innerSubscribe = function (subscriber) { - var _this = this; - var _a = this, hasError = _a.hasError, isStopped = _a.isStopped, observers = _a.observers; - if (hasError || isStopped) { - return Subscription/* EMPTY_SUBSCRIPTION */.Lc; - } - this.currentObservers = null; - observers.push(subscriber); - return new Subscription/* Subscription */.w0(function () { - _this.currentObservers = null; - (0,arrRemove/* arrRemove */.P)(observers, subscriber); - }); + }, defineIteratorMethods(Gp), define(Gp, toStringTagSymbol, "Generator"), define(Gp, iteratorSymbol, function () { + return this; + }), define(Gp, "toString", function () { + return "[object Generator]"; + }), exports.keys = function (object) { + var keys = []; + + for (var key in object) { + keys.push(key); + } + + return keys.reverse(), function next() { + for (; keys.length;) { + var key = keys.pop(); + if (key in object) return next.value = key, next.done = !1, next; + } + + return next.done = !0, next; }; - Subject.prototype._checkFinalizedStatuses = function (subscriber) { - var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, isStopped = _a.isStopped; - if (hasError) { - subscriber.error(thrownError); - } - else if (isStopped) { - subscriber.complete(); + }, exports.values = values, Context.prototype = { + constructor: Context, + reset: function reset(skipTempReset) { + if (this.prev = 0, this.next = 0, this.sent = this._sent = undefined, this.done = !1, this.delegate = null, this.method = "next", this.arg = undefined, this.tryEntries.forEach(resetTryEntry), !skipTempReset) for (var name in this) { + "t" === name.charAt(0) && hasOwn.call(this, name) && !isNaN(+name.slice(1)) && (this[name] = undefined); + } + }, + stop: function stop() { + this.done = !0; + var rootRecord = this.tryEntries[0].completion; + if ("throw" === rootRecord.type) throw rootRecord.arg; + return this.rval; + }, + dispatchException: function dispatchException(exception) { + if (this.done) throw exception; + var context = this; + + function handle(loc, caught) { + return record.type = "throw", record.arg = exception, context.next = loc, caught && (context.method = "next", context.arg = undefined), !!caught; + } + + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i], + record = entry.completion; + if ("root" === entry.tryLoc) return handle("end"); + + if (entry.tryLoc <= this.prev) { + var hasCatch = hasOwn.call(entry, "catchLoc"), + hasFinally = hasOwn.call(entry, "finallyLoc"); + + if (hasCatch && hasFinally) { + if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); + if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); + } else if (hasCatch) { + if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); + } else { + if (!hasFinally) throw new Error("try statement without catch or finally"); + if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); + } } - }; - Subject.prototype.asObservable = function () { - var observable = new Observable/* Observable */.y(); - observable.source = this; - return observable; - }; - Subject.create = function (destination, source) { - return new AnonymousSubject(destination, source); - }; - return Subject; -}(Observable/* Observable */.y)); - -var AnonymousSubject = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(AnonymousSubject, _super); - function AnonymousSubject(destination, source) { - var _this = _super.call(this) || this; - _this.destination = destination; - _this.source = source; - return _this; - } - AnonymousSubject.prototype.next = function (value) { - var _a, _b; - (_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.next) === null || _b === void 0 ? void 0 : _b.call(_a, value); - }; - AnonymousSubject.prototype.error = function (err) { - var _a, _b; - (_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.call(_a, err); - }; - AnonymousSubject.prototype.complete = function () { - var _a, _b; - (_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.complete) === null || _b === void 0 ? void 0 : _b.call(_a); - }; - AnonymousSubject.prototype._subscribe = function (subscriber) { - var _a, _b; - return (_b = (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber)) !== null && _b !== void 0 ? _b : Subscription/* EMPTY_SUBSCRIPTION */.Lc; - }; - return AnonymousSubject; -}(Subject)); + } + }, + abrupt: function abrupt(type, arg) { + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i]; -//# sourceMappingURL=Subject.js.map + if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { + var finallyEntry = entry; + break; + } + } -/***/ }), + finallyEntry && ("break" === type || "continue" === type) && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc && (finallyEntry = null); + var record = finallyEntry ? finallyEntry.completion : {}; + return record.type = type, record.arg = arg, finallyEntry ? (this.method = "next", this.next = finallyEntry.finallyLoc, ContinueSentinel) : this.complete(record); + }, + complete: function complete(record, afterLoc) { + if ("throw" === record.type) throw record.arg; + return "break" === record.type || "continue" === record.type ? this.next = record.arg : "return" === record.type ? (this.rval = this.arg = record.arg, this.method = "return", this.next = "end") : "normal" === record.type && afterLoc && (this.next = afterLoc), ContinueSentinel; + }, + finish: function finish(finallyLoc) { + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i]; + if (entry.finallyLoc === finallyLoc) return this.complete(entry.completion, entry.afterLoc), resetTryEntry(entry), ContinueSentinel; + } + }, + "catch": function _catch(tryLoc) { + for (var i = this.tryEntries.length - 1; i >= 0; --i) { + var entry = this.tryEntries[i]; -/***/ 6267: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + if (entry.tryLoc === tryLoc) { + var record = entry.completion; -"use strict"; + if ("throw" === record.type) { + var thrown = record.arg; + resetTryEntry(entry); + } -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "Hp": function() { return /* binding */ SafeSubscriber; }, - "Lv": function() { return /* binding */ Subscriber; } -}); + return thrown; + } + } -// UNUSED EXPORTS: EMPTY_OBSERVER - -// EXTERNAL MODULE: ./node_modules/rxjs/node_modules/tslib/tslib.es6.js -var tslib_es6 = __webpack_require__(5987); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isFunction.js -var isFunction = __webpack_require__(8474); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subscription.js + 1 modules -var Subscription = __webpack_require__(5720); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/config.js -var config = __webpack_require__(3912); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/reportUnhandledError.js -var reportUnhandledError = __webpack_require__(5); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/noop.js -var noop = __webpack_require__(2967); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/NotificationFactories.js -var COMPLETE_NOTIFICATION = (function () { return createNotification('C', undefined, undefined); })(); -function errorNotification(error) { - return createNotification('E', undefined, error); -} -function nextNotification(value) { - return createNotification('N', value, undefined); -} -function createNotification(kind, value, error) { - return { - kind: kind, - value: value, - error: error, - }; + throw new Error("illegal catch attempt"); + }, + delegateYield: function delegateYield(iterable, resultName, nextLoc) { + return this.delegate = { + iterator: values(iterable), + resultName: resultName, + nextLoc: nextLoc + }, "next" === this.method && (this.arg = undefined), ContinueSentinel; + } + }, exports; } -//# sourceMappingURL=NotificationFactories.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/timeoutProvider.js -var timeoutProvider = __webpack_require__(8380); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/errorContext.js -var errorContext = __webpack_require__(8846); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/Subscriber.js +module.exports = _regeneratorRuntime, module.exports.__esModule = true, module.exports["default"] = module.exports; +/***/ }), +/***/ 8698: +/***/ (function(module) { +function _typeof(obj) { + "@babel/helpers - typeof"; + return (module.exports = _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); +} +module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; +/***/ }), +/***/ 4687: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { -var Subscriber = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(Subscriber, _super); - function Subscriber(destination) { - var _this = _super.call(this) || this; - _this.isStopped = false; - if (destination) { - _this.destination = destination; - if ((0,Subscription/* isSubscription */.Nn)(destination)) { - destination.add(_this); - } - } - else { - _this.destination = EMPTY_OBSERVER; - } - return _this; - } - Subscriber.create = function (next, error, complete) { - return new SafeSubscriber(next, error, complete); - }; - Subscriber.prototype.next = function (value) { - if (this.isStopped) { - handleStoppedNotification(nextNotification(value), this); - } - else { - this._next(value); - } - }; - Subscriber.prototype.error = function (err) { - if (this.isStopped) { - handleStoppedNotification(errorNotification(err), this); - } - else { - this.isStopped = true; - this._error(err); - } - }; - Subscriber.prototype.complete = function () { - if (this.isStopped) { - handleStoppedNotification(COMPLETE_NOTIFICATION, this); - } - else { - this.isStopped = true; - this._complete(); - } - }; - Subscriber.prototype.unsubscribe = function () { - if (!this.closed) { - this.isStopped = true; - _super.prototype.unsubscribe.call(this); - this.destination = null; - } - }; - Subscriber.prototype._next = function (value) { - this.destination.next(value); - }; - Subscriber.prototype._error = function (err) { - try { - this.destination.error(err); - } - finally { - this.unsubscribe(); - } - }; - Subscriber.prototype._complete = function () { - try { - this.destination.complete(); - } - finally { - this.unsubscribe(); - } - }; - return Subscriber; -}(Subscription/* Subscription */.w0)); - -var _bind = Function.prototype.bind; -function bind(fn, thisArg) { - return _bind.call(fn, thisArg); -} -var ConsumerObserver = (function () { - function ConsumerObserver(partialObserver) { - this.partialObserver = partialObserver; - } - ConsumerObserver.prototype.next = function (value) { - var partialObserver = this.partialObserver; - if (partialObserver.next) { - try { - partialObserver.next(value); - } - catch (error) { - handleUnhandledError(error); - } - } - }; - ConsumerObserver.prototype.error = function (err) { - var partialObserver = this.partialObserver; - if (partialObserver.error) { - try { - partialObserver.error(err); - } - catch (error) { - handleUnhandledError(error); - } - } - else { - handleUnhandledError(err); - } - }; - ConsumerObserver.prototype.complete = function () { - var partialObserver = this.partialObserver; - if (partialObserver.complete) { - try { - partialObserver.complete(); - } - catch (error) { - handleUnhandledError(error); - } - } - }; - return ConsumerObserver; -}()); -var SafeSubscriber = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(SafeSubscriber, _super); - function SafeSubscriber(observerOrNext, error, complete) { - var _this = _super.call(this) || this; - var partialObserver; - if ((0,isFunction/* isFunction */.m)(observerOrNext) || !observerOrNext) { - partialObserver = { - next: (observerOrNext !== null && observerOrNext !== void 0 ? observerOrNext : undefined), - error: error !== null && error !== void 0 ? error : undefined, - complete: complete !== null && complete !== void 0 ? complete : undefined, - }; - } - else { - var context_1; - if (_this && config/* config.useDeprecatedNextContext */.v.useDeprecatedNextContext) { - context_1 = Object.create(observerOrNext); - context_1.unsubscribe = function () { return _this.unsubscribe(); }; - partialObserver = { - next: observerOrNext.next && bind(observerOrNext.next, context_1), - error: observerOrNext.error && bind(observerOrNext.error, context_1), - complete: observerOrNext.complete && bind(observerOrNext.complete, context_1), - }; - } - else { - partialObserver = observerOrNext; - } - } - _this.destination = new ConsumerObserver(partialObserver); - return _this; - } - return SafeSubscriber; -}(Subscriber)); +// TODO(Babel 8): Remove this file. -function handleUnhandledError(error) { - if (config/* config.useDeprecatedSynchronousErrorHandling */.v.useDeprecatedSynchronousErrorHandling) { - (0,errorContext/* captureError */.O)(error); - } - else { - (0,reportUnhandledError/* reportUnhandledError */.h)(error); - } -} -function defaultErrorHandler(err) { - throw err; -} -function handleStoppedNotification(notification, subscriber) { - var onStoppedNotification = config/* config.onStoppedNotification */.v.onStoppedNotification; - onStoppedNotification && timeoutProvider/* timeoutProvider.setTimeout */.z.setTimeout(function () { return onStoppedNotification(notification, subscriber); }); +var runtime = __webpack_require__(7061)(); +module.exports = runtime; + +// Copied from https://github.com/facebook/regenerator/blob/main/packages/runtime/runtime.js#L736= +try { + regeneratorRuntime = runtime; +} catch (accidentalStrictMode) { + if (typeof globalThis === "object") { + globalThis.regeneratorRuntime = runtime; + } else { + Function("r", "regeneratorRuntime = r")(runtime); + } } -var EMPTY_OBSERVER = { - closed: true, - next: noop/* noop */.Z, - error: defaultErrorHandler, - complete: noop/* noop */.Z, -}; -//# sourceMappingURL=Subscriber.js.map + /***/ }), -/***/ 5720: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +/***/ 7326: +/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "Z": function() { return /* binding */ _assertThisInitialized; } +/* harmony export */ }); +function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "Lc": function() { return /* binding */ EMPTY_SUBSCRIPTION; }, - "w0": function() { return /* binding */ Subscription; }, - "Nn": function() { return /* binding */ isSubscription; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/node_modules/tslib/tslib.es6.js -var tslib_es6 = __webpack_require__(5987); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isFunction.js -var isFunction = __webpack_require__(8474); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/createErrorClass.js -var createErrorClass = __webpack_require__(1819); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/util/UnsubscriptionError.js - -var UnsubscriptionError = (0,createErrorClass/* createErrorClass */.d)(function (_super) { - return function UnsubscriptionErrorImpl(errors) { - _super(this); - this.message = errors - ? errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ') - : ''; - this.name = 'UnsubscriptionError'; - this.errors = errors; - }; -}); -//# sourceMappingURL=UnsubscriptionError.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/arrRemove.js -var arrRemove = __webpack_require__(3699); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/Subscription.js - - - - -var Subscription = (function () { - function Subscription(initialTeardown) { - this.initialTeardown = initialTeardown; - this.closed = false; - this._parentage = null; - this._finalizers = null; - } - Subscription.prototype.unsubscribe = function () { - var e_1, _a, e_2, _b; - var errors; - if (!this.closed) { - this.closed = true; - var _parentage = this._parentage; - if (_parentage) { - this._parentage = null; - if (Array.isArray(_parentage)) { - try { - for (var _parentage_1 = (0,tslib_es6/* __values */.XA)(_parentage), _parentage_1_1 = _parentage_1.next(); !_parentage_1_1.done; _parentage_1_1 = _parentage_1.next()) { - var parent_1 = _parentage_1_1.value; - parent_1.remove(this); - } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { - try { - if (_parentage_1_1 && !_parentage_1_1.done && (_a = _parentage_1.return)) _a.call(_parentage_1); - } - finally { if (e_1) throw e_1.error; } - } - } - else { - _parentage.remove(this); - } - } - var initialFinalizer = this.initialTeardown; - if ((0,isFunction/* isFunction */.m)(initialFinalizer)) { - try { - initialFinalizer(); - } - catch (e) { - errors = e instanceof UnsubscriptionError ? e.errors : [e]; - } - } - var _finalizers = this._finalizers; - if (_finalizers) { - this._finalizers = null; - try { - for (var _finalizers_1 = (0,tslib_es6/* __values */.XA)(_finalizers), _finalizers_1_1 = _finalizers_1.next(); !_finalizers_1_1.done; _finalizers_1_1 = _finalizers_1.next()) { - var finalizer = _finalizers_1_1.value; - try { - execFinalizer(finalizer); - } - catch (err) { - errors = errors !== null && errors !== void 0 ? errors : []; - if (err instanceof UnsubscriptionError) { - errors = (0,tslib_es6/* __spreadArray */.ev)((0,tslib_es6/* __spreadArray */.ev)([], (0,tslib_es6/* __read */.CR)(errors)), (0,tslib_es6/* __read */.CR)(err.errors)); - } - else { - errors.push(err); - } - } - } - } - catch (e_2_1) { e_2 = { error: e_2_1 }; } - finally { - try { - if (_finalizers_1_1 && !_finalizers_1_1.done && (_b = _finalizers_1.return)) _b.call(_finalizers_1); - } - finally { if (e_2) throw e_2.error; } - } - } - if (errors) { - throw new UnsubscriptionError(errors); - } - } - }; - Subscription.prototype.add = function (teardown) { - var _a; - if (teardown && teardown !== this) { - if (this.closed) { - execFinalizer(teardown); - } - else { - if (teardown instanceof Subscription) { - if (teardown.closed || teardown._hasParent(this)) { - return; - } - teardown._addParent(this); - } - (this._finalizers = (_a = this._finalizers) !== null && _a !== void 0 ? _a : []).push(teardown); - } - } - }; - Subscription.prototype._hasParent = function (parent) { - var _parentage = this._parentage; - return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent)); - }; - Subscription.prototype._addParent = function (parent) { - var _parentage = this._parentage; - this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent; - }; - Subscription.prototype._removeParent = function (parent) { - var _parentage = this._parentage; - if (_parentage === parent) { - this._parentage = null; - } - else if (Array.isArray(_parentage)) { - (0,arrRemove/* arrRemove */.P)(_parentage, parent); - } - }; - Subscription.prototype.remove = function (teardown) { - var _finalizers = this._finalizers; - _finalizers && (0,arrRemove/* arrRemove */.P)(_finalizers, teardown); - if (teardown instanceof Subscription) { - teardown._removeParent(this); - } - }; - Subscription.EMPTY = (function () { - var empty = new Subscription(); - empty.closed = true; - return empty; - })(); - return Subscription; -}()); - -var EMPTY_SUBSCRIPTION = Subscription.EMPTY; -function isSubscription(value) { - return (value instanceof Subscription || - (value && 'closed' in value && (0,isFunction/* isFunction */.m)(value.remove) && (0,isFunction/* isFunction */.m)(value.add) && (0,isFunction/* isFunction */.m)(value.unsubscribe))); -} -function execFinalizer(finalizer) { - if ((0,isFunction/* isFunction */.m)(finalizer)) { - finalizer(); - } - else { - finalizer.unsubscribe(); - } + return self; } -//# sourceMappingURL=Subscription.js.map /***/ }), -/***/ 3912: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +/***/ 5861: +/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "v": function() { return /* binding */ config; } +/* harmony export */ "Z": function() { return /* binding */ _asyncToGenerator; } /* harmony export */ }); -var config = { - onUnhandledError: null, - onStoppedNotification: null, - Promise: undefined, - useDeprecatedSynchronousErrorHandling: false, - useDeprecatedNextContext: false, -}; -//# sourceMappingURL=config.js.map - -/***/ }), +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } -/***/ 2034: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} -"use strict"; +function _asyncToGenerator(fn) { + return function () { + var self = this, + args = arguments; + return new Promise(function (resolve, reject) { + var gen = fn.apply(self, args); -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "z": function() { return /* binding */ concat; } -}); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); + } -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/mergeAll.js -var mergeAll = __webpack_require__(4367); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/concatAll.js + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); + } -function concatAll() { - return (0,mergeAll/* mergeAll */.J)(1); + _next(undefined); + }); + }; } -//# sourceMappingURL=concatAll.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/args.js -var util_args = __webpack_require__(2457); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/from.js + 8 modules -var from = __webpack_require__(3102); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/concat.js +/***/ }), +/***/ 3144: +/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { -function concat() { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - return concatAll()((0,from/* from */.D)(args, (0,util_args/* popScheduler */.yG)(args))); +"use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "Z": function() { return /* binding */ _createClass; } +/* harmony export */ }); +function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} + +function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + Object.defineProperty(Constructor, "prototype", { + writable: false + }); + return Constructor; } -//# sourceMappingURL=concat.js.map /***/ }), -/***/ 9917: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +/***/ 4578: +/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "P": function() { return /* binding */ defer; } +/* harmony export */ "Z": function() { return /* binding */ _inheritsLoose; } /* harmony export */ }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1480); -/* harmony import */ var _innerFrom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7878); - +/* harmony import */ var _setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9611); -function defer(observableFactory) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (subscriber) { - (0,_innerFrom__WEBPACK_IMPORTED_MODULE_1__/* .innerFrom */ .Xf)(observableFactory()).subscribe(subscriber); - }); +function _inheritsLoose(subClass, superClass) { + subClass.prototype = Object.create(superClass.prototype); + subClass.prototype.constructor = subClass; + (0,_setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)(subClass, superClass); } -//# sourceMappingURL=defer.js.map /***/ }), -/***/ 1545: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +/***/ 9611: +/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "E": function() { return /* binding */ EMPTY; } +/* harmony export */ "Z": function() { return /* binding */ _setPrototypeOf; } /* harmony export */ }); -/* unused harmony export empty */ -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1480); - -var EMPTY = new _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (subscriber) { return subscriber.complete(); }); -function empty(scheduler) { - return scheduler ? emptyScheduled(scheduler) : EMPTY; -} -function emptyScheduled(scheduler) { - return new Observable(function (subscriber) { return scheduler.schedule(function () { return subscriber.complete(); }); }); +function _setPrototypeOf(o, p) { + _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + return _setPrototypeOf(o, p); } -//# sourceMappingURL=empty.js.map /***/ }), -/***/ 3102: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +/***/ 2146: +/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; // EXPORTS __webpack_require__.d(__webpack_exports__, { - "D": function() { return /* binding */ from; } + "Z": function() { return /* binding */ _wrapNativeSuper; } }); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/innerFrom.js -var innerFrom = __webpack_require__(7878); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/executeSchedule.js -var executeSchedule = __webpack_require__(7845); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/lift.js -var lift = __webpack_require__(6798); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/OperatorSubscriber.js -var OperatorSubscriber = __webpack_require__(2566); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/observeOn.js - - - -function observeOn(scheduler, delay) { - if (delay === void 0) { delay = 0; } - return (0,lift/* operate */.e)(function (source, subscriber) { - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { return (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { return subscriber.next(value); }, delay); }, function () { return (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { return subscriber.complete(); }, delay); }, function (err) { return (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { return subscriber.error(err); }, delay); })); - }); -} -//# sourceMappingURL=observeOn.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/subscribeOn.js -var subscribeOn = __webpack_require__(8720); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduled/scheduleObservable.js - - - -function scheduleObservable(input, scheduler) { - return (0,innerFrom/* innerFrom */.Xf)(input).pipe((0,subscribeOn/* subscribeOn */.R)(scheduler), observeOn(scheduler)); -} -//# sourceMappingURL=scheduleObservable.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduled/schedulePromise.js - - - -function schedulePromise(input, scheduler) { - return (0,innerFrom/* innerFrom */.Xf)(input).pipe((0,subscribeOn/* subscribeOn */.R)(scheduler), observeOn(scheduler)); +;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js +function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); } -//# sourceMappingURL=schedulePromise.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Observable.js + 1 modules -var Observable = __webpack_require__(1480); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduled/scheduleArray.js - -function scheduleArray(input, scheduler) { - return new Observable/* Observable */.y(function (subscriber) { - var i = 0; - return scheduler.schedule(function () { - if (i === input.length) { - subscriber.complete(); - } - else { - subscriber.next(input[i++]); - if (!subscriber.closed) { - this.schedule(); - } - } - }); - }); +// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js +var setPrototypeOf = __webpack_require__(9611); +;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/isNativeFunction.js +function _isNativeFunction(fn) { + return Function.toString.call(fn).indexOf("[native code]") !== -1; } -//# sourceMappingURL=scheduleArray.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/symbol/iterator.js -var symbol_iterator = __webpack_require__(9768); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isFunction.js -var isFunction = __webpack_require__(8474); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduled/scheduleIterable.js - - - +;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/isNativeReflectConstruct.js +function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; -function scheduleIterable(input, scheduler) { - return new Observable/* Observable */.y(function (subscriber) { - var iterator; - (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { - iterator = input[symbol_iterator/* iterator */.h](); - (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { - var _a; - var value; - var done; - try { - (_a = iterator.next(), value = _a.value, done = _a.done); - } - catch (err) { - subscriber.error(err); - return; - } - if (done) { - subscriber.complete(); - } - else { - subscriber.next(value); - } - }, 0, true); - }); - return function () { return (0,isFunction/* isFunction */.m)(iterator === null || iterator === void 0 ? void 0 : iterator.return) && iterator.return(); }; - }); + try { + Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); + return true; + } catch (e) { + return false; + } } -//# sourceMappingURL=scheduleIterable.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduled/scheduleAsyncIterable.js - +;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/construct.js -function scheduleAsyncIterable(input, scheduler) { - if (!input) { - throw new Error('Iterable cannot be null'); - } - return new Observable/* Observable */.y(function (subscriber) { - (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { - var iterator = input[Symbol.asyncIterator](); - (0,executeSchedule/* executeSchedule */.f)(subscriber, scheduler, function () { - iterator.next().then(function (result) { - if (result.done) { - subscriber.complete(); - } - else { - subscriber.next(result.value); - } - }); - }, 0, true); - }); - }); -} -//# sourceMappingURL=scheduleAsyncIterable.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isInteropObservable.js -var isInteropObservable = __webpack_require__(1764); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isPromise.js -var isPromise = __webpack_require__(3841); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isArrayLike.js -var isArrayLike = __webpack_require__(5685); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isIterable.js -var isIterable = __webpack_require__(1837); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isAsyncIterable.js -var isAsyncIterable = __webpack_require__(8430); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/throwUnobservableError.js -var throwUnobservableError = __webpack_require__(8729); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isReadableStreamLike.js -var isReadableStreamLike = __webpack_require__(8671); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduled/scheduleReadableStreamLike.js +function _construct(Parent, args, Class) { + if (_isNativeReflectConstruct()) { + _construct = Reflect.construct.bind(); + } else { + _construct = function _construct(Parent, args, Class) { + var a = [null]; + a.push.apply(a, args); + var Constructor = Function.bind.apply(Parent, a); + var instance = new Constructor(); + if (Class) (0,setPrototypeOf/* default */.Z)(instance, Class.prototype); + return instance; + }; + } -function scheduleReadableStreamLike(input, scheduler) { - return scheduleAsyncIterable((0,isReadableStreamLike/* readableStreamLikeToAsyncGenerator */.Q)(input), scheduler); + return _construct.apply(null, arguments); } -//# sourceMappingURL=scheduleReadableStreamLike.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduled/scheduled.js - - - - +;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/wrapNativeSuper.js +function _wrapNativeSuper(Class) { + var _cache = typeof Map === "function" ? new Map() : undefined; + _wrapNativeSuper = function _wrapNativeSuper(Class) { + if (Class === null || !_isNativeFunction(Class)) return Class; + if (typeof Class !== "function") { + throw new TypeError("Super expression must either be null or a function"); + } + if (typeof _cache !== "undefined") { + if (_cache.has(Class)) return _cache.get(Class); + _cache.set(Class, Wrapper); + } -function scheduled(input, scheduler) { - if (input != null) { - if ((0,isInteropObservable/* isInteropObservable */.c)(input)) { - return scheduleObservable(input, scheduler); - } - if ((0,isArrayLike/* isArrayLike */.z)(input)) { - return scheduleArray(input, scheduler); - } - if ((0,isPromise/* isPromise */.t)(input)) { - return schedulePromise(input, scheduler); - } - if ((0,isAsyncIterable/* isAsyncIterable */.D)(input)) { - return scheduleAsyncIterable(input, scheduler); - } - if ((0,isIterable/* isIterable */.T)(input)) { - return scheduleIterable(input, scheduler); - } - if ((0,isReadableStreamLike/* isReadableStreamLike */.L)(input)) { - return scheduleReadableStreamLike(input, scheduler); - } + function Wrapper() { + return _construct(Class, arguments, _getPrototypeOf(this).constructor); } - throw (0,throwUnobservableError/* createInvalidObservableTypeError */.z)(input); -} -//# sourceMappingURL=scheduled.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/from.js + Wrapper.prototype = Object.create(Class.prototype, { + constructor: { + value: Wrapper, + enumerable: false, + writable: true, + configurable: true + } + }); + return (0,setPrototypeOf/* default */.Z)(Wrapper, Class); + }; -function from(input, scheduler) { - return scheduler ? scheduled(input, scheduler) : (0,innerFrom/* innerFrom */.Xf)(input); + return _wrapNativeSuper(Class); } -//# sourceMappingURL=from.js.map -/***/ }), - -/***/ 2401: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +/***/ }) +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ !function() { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function() { return module['default']; } : +/******/ function() { return module; }; +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ !function() { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = function(exports, definition) { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ !function() { +/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } +/******/ }(); +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be in strict mode. +!function() { "use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "R": function() { return /* binding */ fromEvent; } -/* harmony export */ }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5987); -/* harmony import */ var _observable_innerFrom__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7878); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1480); -/* harmony import */ var _operators_mergeMap__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7877); -/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5685); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); -/* harmony import */ var _util_mapOneOrManyArgs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3211); - - - - - +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + "default": function() { return /* binding */ src; } +}); -var nodeEventEmitterMethods = ['addListener', 'removeListener']; -var eventTargetMethods = ['addEventListener', 'removeEventListener']; -var jqueryMethods = ['on', 'off']; -function fromEvent(target, eventName, options, resultSelector) { - if ((0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(options)) { - resultSelector = options; - options = undefined; - } - if (resultSelector) { - return fromEvent(target, eventName, options).pipe((0,_util_mapOneOrManyArgs__WEBPACK_IMPORTED_MODULE_1__/* .mapOneOrManyArgs */ .Z)(resultSelector)); - } - var _a = (0,tslib__WEBPACK_IMPORTED_MODULE_2__/* .__read */ .CR)(isEventTarget(target) - ? eventTargetMethods.map(function (methodName) { return function (handler) { return target[methodName](eventName, handler, options); }; }) - : - isNodeStyleEventEmitter(target) - ? nodeEventEmitterMethods.map(toCommonHandlerRegistry(target, eventName)) - : isJQueryStyleEventEmitter(target) - ? jqueryMethods.map(toCommonHandlerRegistry(target, eventName)) - : [], 2), add = _a[0], remove = _a[1]; - if (!add) { - if ((0,_util_isArrayLike__WEBPACK_IMPORTED_MODULE_3__/* .isArrayLike */ .z)(target)) { - return (0,_operators_mergeMap__WEBPACK_IMPORTED_MODULE_4__/* .mergeMap */ .z)(function (subTarget) { return fromEvent(subTarget, eventName, options); })((0,_observable_innerFrom__WEBPACK_IMPORTED_MODULE_5__/* .innerFrom */ .Xf)(target)); - } - } - if (!add) { - throw new TypeError('Invalid event target'); +// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/createClass.js +var createClass = __webpack_require__(3144); +// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js +var inheritsLoose = __webpack_require__(4578); +// EXTERNAL MODULE: ./src/compat/event_listeners.ts +var event_listeners = __webpack_require__(3038); +;// CONCATENATED MODULE: ./src/compat/get_start_date.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Calculating a live-offseted media position necessitate to obtain first an + * offset, and then adding that offset to the wanted position. + * + * That offset is in most case present inside the Manifest file, yet in cases + * without it or without a Manifest, such as the "directfile" mode, the RxPlayer + * won't know that offset. + * + * Thankfully Safari declares a `getStartDate` method allowing to obtain that + * offset when available. This logic is mainly useful when playing HLS contents + * in directfile mode on Safari. + * @param {HTMLMediaElement} mediaElement + * @returns {number|undefined} + */ +function getStartDate(mediaElement) { + var _mediaElement = mediaElement; + if (typeof _mediaElement.getStartDate === "function") { + var startDate = _mediaElement.getStartDate(); + if (typeof startDate === "object" && startDate !== null) { + var startDateNum = +startDate; + if (!isNaN(startDateNum)) { + return startDateNum / 1000; + } + } else if (typeof startDate === "number" && !isNaN(startDate)) { + return startDate; } - return new _Observable__WEBPACK_IMPORTED_MODULE_6__/* .Observable */ .y(function (subscriber) { - var handler = function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - return subscriber.next(1 < args.length ? args : args[0]); - }; - add(handler); - return function () { return remove(handler); }; - }); -} -function toCommonHandlerRegistry(target, eventName) { - return function (methodName) { return function (handler) { return target[methodName](eventName, handler); }; }; + } } -function isNodeStyleEventEmitter(target) { - return (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(target.addListener) && (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(target.removeListener); +;// CONCATENATED MODULE: ./src/compat/fullscreen.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Request fullScreen action on a given element. + * @param {HTMLElement} elt + */ +function requestFullscreen(element) { + if (!fullscreen_isFullscreen()) { + var elt = element; + /* eslint-disable @typescript-eslint/unbound-method */ + if (typeof elt.requestFullscreen === "function") { + /* eslint-enable @typescript-eslint/unbound-method */ + /* eslint-disable @typescript-eslint/no-floating-promises */ + elt.requestFullscreen(); + /* eslint-enable @typescript-eslint/no-floating-promises */ + } else if (typeof elt.msRequestFullscreen === "function") { + elt.msRequestFullscreen(); + } else if (typeof elt.mozRequestFullScreen === "function") { + elt.mozRequestFullScreen(); + } else if (typeof elt.webkitRequestFullscreen === "function") { + elt.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } + } } -function isJQueryStyleEventEmitter(target) { - return (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(target.on) && (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(target.off); +/** + * Exit fullscreen if an element is currently in fullscreen. + */ +function fullscreen_exitFullscreen() { + if (fullscreen_isFullscreen()) { + var doc = document; + /* eslint-disable @typescript-eslint/unbound-method */ + if (typeof doc.exitFullscreen === "function") { + /* eslint-enable @typescript-eslint/unbound-method */ + /* eslint-disable @typescript-eslint/no-floating-promises */ + doc.exitFullscreen(); + /* eslint-enable @typescript-eslint/no-floating-promises */ + } else if (typeof doc.msExitFullscreen === "function") { + doc.msExitFullscreen(); + } else if (typeof doc.mozCancelFullScreen === "function") { + doc.mozCancelFullScreen(); + } else if (typeof doc.webkitExitFullscreen === "function") { + doc.webkitExitFullscreen(); + } + } } -function isEventTarget(target) { - return (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(target.addEventListener) && (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(target.removeEventListener); +/** + * Returns true if an element in the document is being displayed in fullscreen + * mode; + * otherwise it's false. + * @returns {boolean} + */ +function fullscreen_isFullscreen() { + var doc = document; + return doc.fullscreenElement != null || doc.mozFullScreenElement != null || doc.webkitFullscreenElement != null || doc.msFullscreenElement != null; } -//# sourceMappingURL=fromEvent.js.map - -/***/ }), - -/***/ 7878: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Xf": function() { return /* binding */ innerFrom; } -/* harmony export */ }); -/* unused harmony exports fromInteropObservable, fromArrayLike, fromPromise, fromIterable, fromAsyncIterable, fromReadableStreamLike */ -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(5987); -/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5685); -/* harmony import */ var _util_isPromise__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3841); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1480); -/* harmony import */ var _util_isInteropObservable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1764); -/* harmony import */ var _util_isAsyncIterable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8430); -/* harmony import */ var _util_throwUnobservableError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8729); -/* harmony import */ var _util_isIterable__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(1837); -/* harmony import */ var _util_isReadableStreamLike__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8671); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(8474); -/* harmony import */ var _util_reportUnhandledError__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(5); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(6766); - +// EXTERNAL MODULE: ./src/compat/browser_detection.ts +var browser_detection = __webpack_require__(3666); +// EXTERNAL MODULE: ./src/log.ts + 1 modules +var log = __webpack_require__(3887); +;// CONCATENATED MODULE: ./src/compat/browser_version.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Returns either : + * - 'null' when the current browser is not Firefox. + * - '-1' when it is impossible to get the Firefox version + * - A number above 0 that is the Firefox version number + * @returns {number|null} + */ +function getFirefoxVersion() { + if (!browser_detection/* isFirefox */.vU) { + log/* default.warn */.Z.warn("Compat: Can't access Firefox version on no firefox browser."); + return null; + } + var userAgent = navigator.userAgent; + var match = /Firefox\/([0-9]+)\./.exec(userAgent); + if (match === null) { + return -1; + } + var result = parseInt(match[1], 10); + if (isNaN(result)) { + return -1; + } + return result; +} +;// CONCATENATED MODULE: ./src/compat/can_rely_on_video_visibility_and_size.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This functions tells if the RxPlayer can trust on any browser data + * about video element visibility and size. + * + * On Firefox (version >= 67) : + * - The PIP feature exists but can be disabled by default according + * to the OS and the channel used for updating / getting Firefox binaries. + * - There is no API to know if the Picture-in-picture (PIP) is enabled + * - There is no API to get the width of the PIP window + * + * The element clientWidth tells the width of the original video element, and + * no PIP window API exists to determine its presence or width. Thus, there are + * no way to determine the real width of the video window, as we can't know when + * the PIP feature or window is enabled, and we can't have access to the windo + * size information. + * + * Moreover, when the document is considered as hidden (e.g. in case of hidden + * tab), as there is no way to know if the PIP feature or window is enabled, + * we can't know if the video window is visible or not. + * @returns {boolean} + */ +function canRelyOnVideoVisibilityAndSize() { + if (!browser_detection/* isFirefox */.vU) { + return true; + } + var firefoxVersion = getFirefoxVersion(); + if (firefoxVersion === null || firefoxVersion < 67) { + return true; + } + var proto = HTMLVideoElement === null || HTMLVideoElement === void 0 ? void 0 : HTMLVideoElement.prototype; + return (proto === null || proto === void 0 ? void 0 : proto.requirePictureInPicture) !== undefined; +} +// EXTERNAL MODULE: ./src/config.ts + 2 modules +var config = __webpack_require__(6872); +// EXTERNAL MODULE: ./src/errors/format_error.ts +var format_error = __webpack_require__(8750); +// EXTERNAL MODULE: ./src/errors/media_error.ts +var media_error = __webpack_require__(3714); +// EXTERNAL MODULE: ./src/errors/error_codes.ts +var error_codes = __webpack_require__(5992); +// EXTERNAL MODULE: ./src/features/index.ts +var features = __webpack_require__(7874); +// EXTERNAL MODULE: ./src/utils/are_arrays_of_numbers_equal.ts +var are_arrays_of_numbers_equal = __webpack_require__(4791); +// EXTERNAL MODULE: ./src/utils/assert.ts +var assert = __webpack_require__(811); +// EXTERNAL MODULE: ./src/utils/event_emitter.ts +var event_emitter = __webpack_require__(1959); +// EXTERNAL MODULE: ./src/utils/id_generator.ts +var id_generator = __webpack_require__(908); +// EXTERNAL MODULE: ./src/utils/is_null_or_undefined.ts +var is_null_or_undefined = __webpack_require__(1946); +// EXTERNAL MODULE: ./src/utils/object_assign.ts +var object_assign = __webpack_require__(8026); +// EXTERNAL MODULE: ./src/utils/ranges.ts +var ranges = __webpack_require__(2829); +// EXTERNAL MODULE: ./src/utils/reference.ts +var reference = __webpack_require__(5095); +// EXTERNAL MODULE: ./src/utils/task_canceller.ts +var task_canceller = __webpack_require__(288); +// EXTERNAL MODULE: ./src/utils/warn_once.ts +var warn_once = __webpack_require__(8806); +// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js +var asyncToGenerator = __webpack_require__(5861); +// EXTERNAL MODULE: ./node_modules/@babel/runtime/regenerator/index.js +var regenerator = __webpack_require__(4687); +var regenerator_default = /*#__PURE__*/__webpack_require__.n(regenerator); +// EXTERNAL MODULE: ./src/compat/eme/custom_media_keys/index.ts + 7 modules +var custom_media_keys = __webpack_require__(6139); +// EXTERNAL MODULE: ./src/core/decrypt/utils/media_keys_infos_store.ts +var media_keys_infos_store = __webpack_require__(770); +;// CONCATENATED MODULE: ./src/core/decrypt/dispose_decryption_resources.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -function innerFrom(input) { - if (input instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y) { - return input; - } - if (input != null) { - if ((0,_util_isInteropObservable__WEBPACK_IMPORTED_MODULE_1__/* .isInteropObservable */ .c)(input)) { - return fromInteropObservable(input); - } - if ((0,_util_isArrayLike__WEBPACK_IMPORTED_MODULE_2__/* .isArrayLike */ .z)(input)) { - return fromArrayLike(input); - } - if ((0,_util_isPromise__WEBPACK_IMPORTED_MODULE_3__/* .isPromise */ .t)(input)) { - return fromPromise(input); - } - if ((0,_util_isAsyncIterable__WEBPACK_IMPORTED_MODULE_4__/* .isAsyncIterable */ .D)(input)) { - return fromAsyncIterable(input); - } - if ((0,_util_isIterable__WEBPACK_IMPORTED_MODULE_5__/* .isIterable */ .T)(input)) { - return fromIterable(input); - } - if ((0,_util_isReadableStreamLike__WEBPACK_IMPORTED_MODULE_6__/* .isReadableStreamLike */ .L)(input)) { - return fromReadableStreamLike(input); - } - } - throw (0,_util_throwUnobservableError__WEBPACK_IMPORTED_MODULE_7__/* .createInvalidObservableTypeError */ .z)(input); -} -function fromInteropObservable(obj) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (subscriber) { - var obs = obj[_symbol_observable__WEBPACK_IMPORTED_MODULE_8__/* .observable */ .L](); - if ((0,_util_isFunction__WEBPACK_IMPORTED_MODULE_9__/* .isFunction */ .m)(obs.subscribe)) { - return obs.subscribe(subscriber); - } - throw new TypeError('Provided object does not correctly implement Symbol.observable'); - }); -} -function fromArrayLike(array) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (subscriber) { - for (var i = 0; i < array.length && !subscriber.closed; i++) { - subscriber.next(array[i]); - } - subscriber.complete(); - }); -} -function fromPromise(promise) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (subscriber) { - promise - .then(function (value) { - if (!subscriber.closed) { - subscriber.next(value); - subscriber.complete(); - } - }, function (err) { return subscriber.error(err); }) - .then(null, _util_reportUnhandledError__WEBPACK_IMPORTED_MODULE_10__/* .reportUnhandledError */ .h); - }); +/** + * Free up all ressources taken by the content decryption logic. + * @param {HTMLMediaElement} mediaElement + * @returns {Promise} + */ +function disposeDecryptionResources(_x) { + return _disposeDecryptionResources.apply(this, arguments); } -function fromIterable(iterable) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (subscriber) { - var e_1, _a; - try { - for (var iterable_1 = (0,tslib__WEBPACK_IMPORTED_MODULE_11__/* .__values */ .XA)(iterable), iterable_1_1 = iterable_1.next(); !iterable_1_1.done; iterable_1_1 = iterable_1.next()) { - var value = iterable_1_1.value; - subscriber.next(value); - if (subscriber.closed) { - return; - } - } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { - try { - if (iterable_1_1 && !iterable_1_1.done && (_a = iterable_1.return)) _a.call(iterable_1); +function _disposeDecryptionResources() { + _disposeDecryptionResources = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(mediaElement) { + var currentState, loadedSessionsStore; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + currentState = media_keys_infos_store/* default.getState */.Z.getState(mediaElement); + if (!(currentState === null)) { + _context.next = 3; + break; } - finally { if (e_1) throw e_1.error; } + return _context.abrupt("return"); + case 3: + log/* default.info */.Z.info("DRM: Disposing of the current MediaKeys"); + loadedSessionsStore = currentState.loadedSessionsStore; + media_keys_infos_store/* default.clearState */.Z.clearState(mediaElement); + _context.next = 8; + return loadedSessionsStore.closeAllSessions(); + case 8: + (0,custom_media_keys/* setMediaKeys */.Y)(mediaElement, null); + case 9: + case "end": + return _context.stop(); } - subscriber.complete(); - }); + } + }, _callee); + })); + return _disposeDecryptionResources.apply(this, arguments); } -function fromAsyncIterable(asyncIterable) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__/* .Observable */ .y(function (subscriber) { - process(asyncIterable, subscriber).catch(function (err) { return subscriber.error(err); }); - }); +;// CONCATENATED MODULE: ./src/core/decrypt/get_key_system_configuration.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Returns the name of the current key system used as well as its configuration, + * as reported by the `MediaKeySystemAccess` itself. + * @param {HTMLMediaElement} mediaElement + * @returns {Array|null} + */ +function get_key_system_configuration_getKeySystemConfiguration(mediaElement) { + var currentState = media_keys_infos_store/* default.getState */.Z.getState(mediaElement); + if (currentState === null) { + return null; + } + return [currentState.mediaKeySystemAccess.keySystem, currentState.mediaKeySystemAccess.getConfiguration()]; } -function fromReadableStreamLike(readableStream) { - return fromAsyncIterable((0,_util_isReadableStreamLike__WEBPACK_IMPORTED_MODULE_6__/* .readableStreamLikeToAsyncGenerator */ .Q)(readableStream)); -} -function process(asyncIterable, subscriber) { - var asyncIterable_1, asyncIterable_1_1; - var e_2, _a; - return (0,tslib__WEBPACK_IMPORTED_MODULE_11__/* .__awaiter */ .mG)(this, void 0, void 0, function () { - var value, e_2_1; - return (0,tslib__WEBPACK_IMPORTED_MODULE_11__/* .__generator */ .Jh)(this, function (_b) { - switch (_b.label) { - case 0: - _b.trys.push([0, 5, 6, 11]); - asyncIterable_1 = (0,tslib__WEBPACK_IMPORTED_MODULE_11__/* .__asyncValues */ .KL)(asyncIterable); - _b.label = 1; - case 1: return [4, asyncIterable_1.next()]; - case 2: - if (!(asyncIterable_1_1 = _b.sent(), !asyncIterable_1_1.done)) return [3, 4]; - value = asyncIterable_1_1.value; - subscriber.next(value); - if (subscriber.closed) { - return [2]; - } - _b.label = 3; - case 3: return [3, 1]; - case 4: return [3, 11]; - case 5: - e_2_1 = _b.sent(); - e_2 = { error: e_2_1 }; - return [3, 11]; - case 6: - _b.trys.push([6, , 9, 10]); - if (!(asyncIterable_1_1 && !asyncIterable_1_1.done && (_a = asyncIterable_1.return))) return [3, 8]; - return [4, _a.call(asyncIterable_1)]; - case 7: - _b.sent(); - _b.label = 8; - case 8: return [3, 10]; - case 9: - if (e_2) throw e_2.error; - return [7]; - case 10: return [7]; - case 11: - subscriber.complete(); - return [2]; - } - }); - }); +/** + * Returns the name of the current key system used, as originally indicated by + * the user. + * @deprecated + * @param {HTMLMediaElement} mediaElement + * @returns {string|null} + */ +function get_key_system_configuration_getCurrentKeySystem(mediaElement) { + var currentState = media_keys_infos_store/* default.getState */.Z.getState(mediaElement); + return currentState == null ? null : currentState.keySystemOptions.type; } -//# sourceMappingURL=innerFrom.js.map - -/***/ }), - -/***/ 3071: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "T": function() { return /* binding */ merge; } -/* harmony export */ }); -/* harmony import */ var _operators_mergeAll__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4367); -/* harmony import */ var _innerFrom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7878); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1545); -/* harmony import */ var _util_args__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2457); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3102); - - - - +;// CONCATENATED MODULE: ./src/compat/should_unset_media_keys.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -function merge() { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - var scheduler = (0,_util_args__WEBPACK_IMPORTED_MODULE_0__/* .popScheduler */ .yG)(args); - var concurrent = (0,_util_args__WEBPACK_IMPORTED_MODULE_0__/* .popNumber */ ._6)(args, Infinity); - var sources = args; - return !sources.length - ? - _empty__WEBPACK_IMPORTED_MODULE_1__/* .EMPTY */ .E - : sources.length === 1 - ? - (0,_innerFrom__WEBPACK_IMPORTED_MODULE_2__/* .innerFrom */ .Xf)(sources[0]) - : - (0,_operators_mergeAll__WEBPACK_IMPORTED_MODULE_3__/* .mergeAll */ .J)(concurrent)((0,_from__WEBPACK_IMPORTED_MODULE_4__/* .from */ .D)(sources, scheduler)); +/** + * Returns true if the mediakeys associated to a media element should be + * unset once the content is stopped. + * Depends on the target. + * @returns {Boolean} + */ +function shouldUnsetMediaKeys() { + return browser_detection/* isIE11 */.fq; } -//# sourceMappingURL=merge.js.map - -/***/ }), +;// CONCATENATED MODULE: ./src/core/decrypt/clear_on_stop.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -/***/ 2817: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "of": function() { return /* binding */ of; } -/* harmony export */ }); -/* harmony import */ var _util_args__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2457); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3102); -function of() { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - var scheduler = (0,_util_args__WEBPACK_IMPORTED_MODULE_0__/* .popScheduler */ .yG)(args); - return (0,_from__WEBPACK_IMPORTED_MODULE_1__/* .from */ .D)(args, scheduler); +/** + * Clear DRM-related resources that should be cleared when the current content + * stops its playback. + * @param {HTMLMediaElement} mediaElement + * @returns {Promise} + */ +function clearOnStop(mediaElement) { + log/* default.info */.Z.info("DRM: Clearing-up DRM session."); + if (shouldUnsetMediaKeys()) { + log/* default.info */.Z.info("DRM: disposing current MediaKeys."); + return disposeDecryptionResources(mediaElement); + } + var currentState = media_keys_infos_store/* default.getState */.Z.getState(mediaElement); + if (currentState !== null && currentState.keySystemOptions.closeSessionsOnStop === true) { + log/* default.info */.Z.info("DRM: closing all current sessions."); + return currentState.loadedSessionsStore.closeAllSessions(); + } + log/* default.info */.Z.info("DRM: Nothing to clear. Returning right away. No state =", currentState === null); + return Promise.resolve(); } -//# sourceMappingURL=of.js.map - -/***/ }), - -/***/ 3610: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "_": function() { return /* binding */ throwError; } -/* harmony export */ }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1480); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); - - -function throwError(errorOrErrorFactory, scheduler) { - var errorFactory = (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(errorOrErrorFactory) ? errorOrErrorFactory : function () { return errorOrErrorFactory; }; - var init = function (subscriber) { return subscriber.error(errorFactory()); }; - return new _Observable__WEBPACK_IMPORTED_MODULE_1__/* .Observable */ .y(scheduler ? function (subscriber) { return scheduler.schedule(init, 0, subscriber); } : init); +;// CONCATENATED MODULE: ./src/compat/should_reload_media_source_on_decipherability_update.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Returns true if we have to reload the MediaSource due to an update in the + * decipherability status of some segments based on the current key sytem. + * + * We found that on all Widevine targets tested, a simple seek is sufficient. + * As widevine clients make a good chunk of users, we can make a difference + * between them and others as it is for the better. + * @param {string|undefined} currentKeySystem + * @returns {Boolean} + */ +function shouldReloadMediaSourceOnDecipherabilityUpdate(currentKeySystem) { + return currentKeySystem === undefined || currentKeySystem.indexOf("widevine") < 0; } -//# sourceMappingURL=throwError.js.map - -/***/ }), - -/***/ 6625: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "H": function() { return /* binding */ timer; } -/* harmony export */ }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1480); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7991); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4865); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1454); - - - - -function timer(dueTime, intervalOrScheduler, scheduler) { - if (dueTime === void 0) { dueTime = 0; } - if (scheduler === void 0) { scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__/* .async */ .P; } - var intervalDuration = -1; - if (intervalOrScheduler != null) { - if ((0,_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__/* .isScheduler */ .K)(intervalOrScheduler)) { - scheduler = intervalOrScheduler; - } - else { - intervalDuration = intervalOrScheduler; - } +// EXTERNAL MODULE: ./src/utils/create_cancellable_promise.ts +var create_cancellable_promise = __webpack_require__(7733); +// EXTERNAL MODULE: ./src/utils/noop.ts +var noop = __webpack_require__(8894); +// EXTERNAL MODULE: ./src/utils/take_first_set.ts +var take_first_set = __webpack_require__(5278); +// EXTERNAL MODULE: ./src/utils/array_find_index.ts +var array_find_index = __webpack_require__(5138); +;// CONCATENATED MODULE: ./src/core/adaptive/utils/get_buffer_levels.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Return "Buffer Levels" which are steps of available buffers from which we + * are normally able switch safely to the next available bitrate. + * (Following an algorithm close to BOLA) + * @param {Array.} bitrates - All available bitrates, __sorted__ in + * ascending order. + * @returns {Array.} + */ +function getBufferLevels(bitrates) { + var logs = bitrates.map(function (b) { + return Math.log(b / bitrates[0]); + }); + var utilities = logs.map(function (l) { + return l - logs[0] + 1; + }); // normalize + var gp = (utilities[utilities.length - 1] - 1) / (bitrates.length * 2 + 10); + var Vp = 1 / gp; + return bitrates.map(function (_, i) { + return minBufferLevelForBitrate(i); + }); + /** + * Get minimum buffer we should keep ahead to pick this bitrate. + * @param {number} index + * @returns {number} + */ + function minBufferLevelForBitrate(index) { + if (index === 0) { + return 0; } - return new _Observable__WEBPACK_IMPORTED_MODULE_2__/* .Observable */ .y(function (subscriber) { - var due = (0,_util_isDate__WEBPACK_IMPORTED_MODULE_3__/* .isValidDate */ .q)(dueTime) ? +dueTime - scheduler.now() : dueTime; - if (due < 0) { - due = 0; - } - var n = 0; - return scheduler.schedule(function () { - if (!subscriber.closed) { - subscriber.next(n++); - if (0 <= intervalDuration) { - this.schedule(undefined, intervalDuration); - } - else { - subscriber.complete(); - } - } - }, due); - }); -} -//# sourceMappingURL=timer.js.map - -/***/ }), - -/***/ 2566: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "x": function() { return /* binding */ createOperatorSubscriber; } -/* harmony export */ }); -/* unused harmony export OperatorSubscriber */ -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5987); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6267); - - -function createOperatorSubscriber(destination, onNext, onComplete, onError, onFinalize) { - return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize); -} -var OperatorSubscriber = (function (_super) { - (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__extends */ .ZT)(OperatorSubscriber, _super); - function OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize, shouldUnsubscribe) { - var _this = _super.call(this, destination) || this; - _this.onFinalize = onFinalize; - _this.shouldUnsubscribe = shouldUnsubscribe; - _this._next = onNext - ? function (value) { - try { - onNext(value); - } - catch (err) { - destination.error(err); - } - } - : _super.prototype._next; - _this._error = onError - ? function (err) { - try { - onError(err); - } - catch (err) { - destination.error(err); - } - finally { - this.unsubscribe(); - } - } - : _super.prototype._error; - _this._complete = onComplete - ? function () { - try { - onComplete(); - } - catch (err) { - destination.error(err); - } - finally { - this.unsubscribe(); - } - } - : _super.prototype._complete; - return _this; + var boundedIndex = Math.min(Math.max(1, index), bitrates.length - 1); + if (bitrates[boundedIndex] === bitrates[boundedIndex - 1]) { + return minBufferLevelForBitrate(index - 1); } - OperatorSubscriber.prototype.unsubscribe = function () { - var _a; - if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) { - var closed_1 = this.closed; - _super.prototype.unsubscribe.call(this); - !closed_1 && ((_a = this.onFinalize) === null || _a === void 0 ? void 0 : _a.call(this)); - } - }; - return OperatorSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__/* .Subscriber */ .Lv)); - -//# sourceMappingURL=OperatorSubscriber.js.map - -/***/ }), - -/***/ 9878: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "K": function() { return /* binding */ catchError; } -/* harmony export */ }); -/* harmony import */ var _observable_innerFrom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7878); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2566); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); - - - -function catchError(selector) { - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - var innerSub = null; - var syncUnsub = false; - var handledResult; - innerSub = source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__/* .createOperatorSubscriber */ .x)(subscriber, undefined, undefined, function (err) { - handledResult = (0,_observable_innerFrom__WEBPACK_IMPORTED_MODULE_2__/* .innerFrom */ .Xf)(selector(err, catchError(selector)(source))); - if (innerSub) { - innerSub.unsubscribe(); - innerSub = null; - handledResult.subscribe(subscriber); - } - else { - syncUnsub = true; - } - })); - if (syncUnsub) { - innerSub.unsubscribe(); - innerSub = null; - handledResult.subscribe(subscriber); - } - }); + return Vp * (gp + (bitrates[boundedIndex] * utilities[boundedIndex - 1] - bitrates[boundedIndex - 1] * utilities[boundedIndex]) / (bitrates[boundedIndex] - bitrates[boundedIndex - 1])) + 4; + } } -//# sourceMappingURL=catchError.js.map - -/***/ }), - -/***/ 4975: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { +;// CONCATENATED MODULE: ./src/core/adaptive/buffer_based_chooser.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "h": function() { return /* binding */ filter; } -/* harmony export */ }); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2566); -function filter(predicate, thisArg) { - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - var index = 0; - source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__/* .createOperatorSubscriber */ .x)(subscriber, function (value) { return predicate.call(thisArg, value, index++) && subscriber.next(value); })); +/** + * Choose a bitrate based on the currently available buffer. + * + * This algorithm is based on a deviation of the BOLA algorithm. + * It is a hybrid solution that also relies on a given bitrate's + * "maintainability". + * Each time a chunk is downloaded, from the ratio between the chunk duration + * and chunk's request time, we can assume that the representation is + * "maintanable" or not. + * If so, we may switch to a better quality, or conversely to a worse quality. + * + * @class BufferBasedChooser + */ +var BufferBasedChooser = /*#__PURE__*/function () { + /** + * @param {Array.} bitrates + */ + function BufferBasedChooser(bitrates) { + this._levelsMap = getBufferLevels(bitrates); + this._bitrates = bitrates; + log/* default.debug */.Z.debug("ABR: Steps for buffer based chooser.", this._levelsMap.map(function (l, i) { + return "bufferLevel: " + l + ", bitrate: " + bitrates[i]; + }).join(" ,")); + } + /** + * @param {Object} playbackObservation + * @returns {number|undefined} + */ + var _proto = BufferBasedChooser.prototype; + _proto.getEstimate = function getEstimate(playbackObservation) { + var bufferLevels = this._levelsMap; + var bitrates = this._bitrates; + var bufferGap = playbackObservation.bufferGap, + currentBitrate = playbackObservation.currentBitrate, + currentScore = playbackObservation.currentScore, + speed = playbackObservation.speed; + if (currentBitrate == null) { + return bitrates[0]; + } + var currentBitrateIndex = (0,array_find_index/* default */.Z)(bitrates, function (b) { + return b === currentBitrate; }); -} -//# sourceMappingURL=filter.js.map - -/***/ }), - -/***/ 3286: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "x": function() { return /* binding */ finalize; } -/* harmony export */ }); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); - -function finalize(callback) { - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - try { - source.subscribe(subscriber); + if (currentBitrateIndex < 0 || bitrates.length !== bufferLevels.length) { + log/* default.error */.Z.error("ABR: Current Bitrate not found in the calculated levels"); + return bitrates[0]; + } + var scaledScore; + if (currentScore != null) { + scaledScore = speed === 0 ? currentScore : currentScore / speed; + } + if (scaledScore != null && scaledScore > 1) { + var currentBufferLevel = bufferLevels[currentBitrateIndex]; + var nextIndex = function () { + for (var i = currentBitrateIndex + 1; i < bufferLevels.length; i++) { + if (bufferLevels[i] > currentBufferLevel) { + return i; + } } - finally { - subscriber.add(callback); + }(); + if (nextIndex != null) { + var nextBufferLevel = bufferLevels[nextIndex]; + if (bufferGap >= nextBufferLevel) { + return bitrates[nextIndex]; } - }); -} -//# sourceMappingURL=finalize.js.map - -/***/ }), - -/***/ 533: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "l": function() { return /* binding */ ignoreElements; } -/* harmony export */ }); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2566); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2967); - - - -function ignoreElements() { - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__/* .createOperatorSubscriber */ .x)(subscriber, _util_noop__WEBPACK_IMPORTED_MODULE_2__/* .noop */ .Z)); - }); -} -//# sourceMappingURL=ignoreElements.js.map - -/***/ }), - -/***/ 9127: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "U": function() { return /* binding */ map; } -/* harmony export */ }); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2566); - - -function map(project, thisArg) { - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - var index = 0; - source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__/* .createOperatorSubscriber */ .x)(subscriber, function (value) { - subscriber.next(project.call(thisArg, value, index++)); - })); - }); -} -//# sourceMappingURL=map.js.map - -/***/ }), - -/***/ 4367: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "J": function() { return /* binding */ mergeAll; } -/* harmony export */ }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7877); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); - - -function mergeAll(concurrent) { - if (concurrent === void 0) { concurrent = Infinity; } - return (0,_mergeMap__WEBPACK_IMPORTED_MODULE_0__/* .mergeMap */ .z)(_util_identity__WEBPACK_IMPORTED_MODULE_1__/* .identity */ .y, concurrent); -} -//# sourceMappingURL=mergeAll.js.map - -/***/ }), - -/***/ 7877: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "z": function() { return /* binding */ mergeMap; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/map.js -var map = __webpack_require__(9127); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/innerFrom.js -var innerFrom = __webpack_require__(7878); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/lift.js -var lift = __webpack_require__(6798); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/executeSchedule.js -var executeSchedule = __webpack_require__(7845); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/OperatorSubscriber.js -var OperatorSubscriber = __webpack_require__(2566); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/mergeInternals.js - - - -function mergeInternals(source, subscriber, project, concurrent, onBeforeNext, expand, innerSubScheduler, additionalFinalizer) { - var buffer = []; - var active = 0; - var index = 0; - var isComplete = false; - var checkComplete = function () { - if (isComplete && !buffer.length && !active) { - subscriber.complete(); - } - }; - var outerNext = function (value) { return (active < concurrent ? doInnerSub(value) : buffer.push(value)); }; - var doInnerSub = function (value) { - expand && subscriber.next(value); - active++; - var innerComplete = false; - (0,innerFrom/* innerFrom */.Xf)(project(value, index++)).subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (innerValue) { - onBeforeNext === null || onBeforeNext === void 0 ? void 0 : onBeforeNext(innerValue); - if (expand) { - outerNext(innerValue); - } - else { - subscriber.next(innerValue); - } - }, function () { - innerComplete = true; - }, undefined, function () { - if (innerComplete) { - try { - active--; - var _loop_1 = function () { - var bufferedValue = buffer.shift(); - if (innerSubScheduler) { - (0,executeSchedule/* executeSchedule */.f)(subscriber, innerSubScheduler, function () { return doInnerSub(bufferedValue); }); - } - else { - doInnerSub(bufferedValue); - } - }; - while (buffer.length && active < concurrent) { - _loop_1(); - } - checkComplete(); - } - catch (err) { - subscriber.error(err); - } - } - })); - }; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, outerNext, function () { - isComplete = true; - checkComplete(); - })); - return function () { - additionalFinalizer === null || additionalFinalizer === void 0 ? void 0 : additionalFinalizer(); - }; -} -//# sourceMappingURL=mergeInternals.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/isFunction.js -var isFunction = __webpack_require__(8474); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/mergeMap.js - - - - - -function mergeMap(project, resultSelector, concurrent) { - if (concurrent === void 0) { concurrent = Infinity; } - if ((0,isFunction/* isFunction */.m)(resultSelector)) { - return mergeMap(function (a, i) { return (0,map/* map */.U)(function (b, ii) { return resultSelector(a, b, i, ii); })((0,innerFrom/* innerFrom */.Xf)(project(a, i))); }, concurrent); + } } - else if (typeof resultSelector === 'number') { - concurrent = resultSelector; + if (scaledScore == null || scaledScore < 1.15) { + var _currentBufferLevel = bufferLevels[currentBitrateIndex]; + if (bufferGap < _currentBufferLevel) { + for (var i = currentBitrateIndex - 1; i >= 0; i--) { + if (bitrates[i] < currentBitrate) { + return bitrates[i]; + } + } + return currentBitrate; + } } - return (0,lift/* operate */.e)(function (source, subscriber) { return mergeInternals(source, subscriber, project, concurrent); }); -} -//# sourceMappingURL=mergeMap.js.map - -/***/ }), - -/***/ 3074: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "R": function() { return /* binding */ scan; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/lift.js -var lift = __webpack_require__(6798); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/OperatorSubscriber.js -var OperatorSubscriber = __webpack_require__(2566); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/scanInternals.js - -function scanInternals(accumulator, seed, hasSeed, emitOnNext, emitBeforeComplete) { - return function (source, subscriber) { - var hasState = hasSeed; - var state = seed; - var index = 0; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - var i = index++; - state = hasState - ? - accumulator(state, value, i) - : - ((hasState = true), value); - emitOnNext && subscriber.next(state); - }, emitBeforeComplete && - (function () { - hasState && subscriber.next(state); - subscriber.complete(); - }))); - }; -} -//# sourceMappingURL=scanInternals.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/scan.js - - -function scan(accumulator, seed) { - return (0,lift/* operate */.e)(scanInternals(accumulator, seed, arguments.length >= 2, true)); -} -//# sourceMappingURL=scan.js.map - -/***/ }), - -/***/ 5583: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + return currentBitrate; + }; + return BufferBasedChooser; +}(); -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "B": function() { return /* binding */ share; } -/* harmony export */ }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5987); -/* harmony import */ var _observable_innerFrom__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7878); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6716); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6267); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6798); +// EXTERNAL MODULE: ./src/utils/array_find.ts +var array_find = __webpack_require__(3274); +;// CONCATENATED MODULE: ./src/core/adaptive/utils/ewma.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Tweaked implementation of an exponential weighted Moving Average. + * @class EWMA + */ +var EWMA = /*#__PURE__*/function () { + /** + * @param {number} halfLife + */ + function EWMA(halfLife) { + // (half-life = log(1/2) / log(Decay Factor) + this._alpha = Math.exp(Math.log(0.5) / halfLife); + this._lastEstimate = 0; + this._totalWeight = 0; + } + /** + * @param {number} weight + * @param {number} value + */ + var _proto = EWMA.prototype; + _proto.addSample = function addSample(weight, value) { + var adjAlpha = Math.pow(this._alpha, weight); + var newEstimate = value * (1 - adjAlpha) + adjAlpha * this._lastEstimate; + if (!isNaN(newEstimate)) { + this._lastEstimate = newEstimate; + this._totalWeight += weight; + } + } + /** + * @returns {number} value + */; + _proto.getEstimate = function getEstimate() { + var zeroFactor = 1 - Math.pow(this._alpha, this._totalWeight); + return this._lastEstimate / zeroFactor; + }; + return EWMA; +}(); +;// CONCATENATED MODULE: ./src/core/adaptive/network_analyzer.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -function share(options) { - if (options === void 0) { options = {}; } - var _a = options.connector, connector = _a === void 0 ? function () { return new _Subject__WEBPACK_IMPORTED_MODULE_0__/* .Subject */ .x(); } : _a, _b = options.resetOnError, resetOnError = _b === void 0 ? true : _b, _c = options.resetOnComplete, resetOnComplete = _c === void 0 ? true : _c, _d = options.resetOnRefCountZero, resetOnRefCountZero = _d === void 0 ? true : _d; - return function (wrapperSource) { - var connection; - var resetConnection; - var subject; - var refCount = 0; - var hasCompleted = false; - var hasErrored = false; - var cancelReset = function () { - resetConnection === null || resetConnection === void 0 ? void 0 : resetConnection.unsubscribe(); - resetConnection = undefined; - }; - var reset = function () { - cancelReset(); - connection = subject = undefined; - hasCompleted = hasErrored = false; - }; - var resetAndUnsubscribe = function () { - var conn = connection; - reset(); - conn === null || conn === void 0 ? void 0 : conn.unsubscribe(); - }; - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_1__/* .operate */ .e)(function (source, subscriber) { - refCount++; - if (!hasErrored && !hasCompleted) { - cancelReset(); - } - var dest = (subject = subject !== null && subject !== void 0 ? subject : connector()); - subscriber.add(function () { - refCount--; - if (refCount === 0 && !hasErrored && !hasCompleted) { - resetConnection = handleReset(resetAndUnsubscribe, resetOnRefCountZero); - } - }); - dest.subscribe(subscriber); - if (!connection && - refCount > 0) { - connection = new _Subscriber__WEBPACK_IMPORTED_MODULE_2__/* .SafeSubscriber */ .Hp({ - next: function (value) { return dest.next(value); }, - error: function (err) { - hasErrored = true; - cancelReset(); - resetConnection = handleReset(reset, resetOnError, err); - dest.error(err); - }, - complete: function () { - hasCompleted = true; - cancelReset(); - resetConnection = handleReset(reset, resetOnComplete); - dest.complete(); - }, - }); - (0,_observable_innerFrom__WEBPACK_IMPORTED_MODULE_3__/* .innerFrom */ .Xf)(source).subscribe(connection); - } - })(wrapperSource); - }; -} -function handleReset(reset, on) { - var args = []; - for (var _i = 2; _i < arguments.length; _i++) { - args[_i - 2] = arguments[_i]; +/** + * Get pending segment request(s) starting with the asked segment position. + * @param {Object} requests - Every requests pending, in a chronological + * order in terms of segment time. + * @param {number} neededPosition + * @returns {Array.} + */ +function getConcernedRequests(requests, neededPosition) { + /** Index of the request for the next needed segment, in `requests`. */ + var nextSegmentIndex = -1; + for (var i = 0; i < requests.length; i++) { + var segment = requests[i].content.segment; + if (segment.duration <= 0) { + continue; } - if (on === true) { - reset(); - return; + var segmentEnd = segment.time + segment.duration; + if (!segment.complete) { + if (i === requests.length - 1 && neededPosition - segment.time > -1.2) { + nextSegmentIndex = i; + break; + } } - if (on === false) { - return; + if (segmentEnd > neededPosition && neededPosition - segment.time > -1.2) { + nextSegmentIndex = i; + break; } - var onSubscriber = new _Subscriber__WEBPACK_IMPORTED_MODULE_2__/* .SafeSubscriber */ .Hp({ - next: function () { - onSubscriber.unsubscribe(); - reset(); - }, - }); - return on.apply(void 0, (0,tslib__WEBPACK_IMPORTED_MODULE_4__/* .__spreadArray */ .ev)([], (0,tslib__WEBPACK_IMPORTED_MODULE_4__/* .__read */ .CR)(args))).subscribe(onSubscriber); -} -//# sourceMappingURL=share.js.map - -/***/ }), - -/***/ 8515: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "d": function() { return /* binding */ shareReplay; } -/* harmony export */ }); -/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3); -/* harmony import */ var _share__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5583); - - -function shareReplay(configOrBufferSize, windowTime, scheduler) { - var _a, _b, _c; - var bufferSize; - var refCount = false; - if (configOrBufferSize && typeof configOrBufferSize === 'object') { - (_a = configOrBufferSize.bufferSize, bufferSize = _a === void 0 ? Infinity : _a, _b = configOrBufferSize.windowTime, windowTime = _b === void 0 ? Infinity : _b, _c = configOrBufferSize.refCount, refCount = _c === void 0 ? false : _c, scheduler = configOrBufferSize.scheduler); - } - else { - bufferSize = (configOrBufferSize !== null && configOrBufferSize !== void 0 ? configOrBufferSize : Infinity); - } - return (0,_share__WEBPACK_IMPORTED_MODULE_0__/* .share */ .B)({ - connector: function () { return new _ReplaySubject__WEBPACK_IMPORTED_MODULE_1__/* .ReplaySubject */ .t(bufferSize, windowTime, scheduler); }, - resetOnError: true, - resetOnComplete: false, - resetOnRefCountZero: refCount, - }); -} -//# sourceMappingURL=shareReplay.js.map - -/***/ }), - -/***/ 6108: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "O": function() { return /* binding */ startWith; } -/* harmony export */ }); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2034); -/* harmony import */ var _util_args__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2457); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6798); - - - -function startWith() { - var values = []; - for (var _i = 0; _i < arguments.length; _i++) { - values[_i] = arguments[_i]; + } + if (nextSegmentIndex < 0) { + // Not found + return []; + } + var nextRequest = requests[nextSegmentIndex]; + var segmentTime = nextRequest.content.segment.time; + var filteredRequests = [nextRequest]; + // Get the possibly multiple requests for that segment's position + for (var _i = nextSegmentIndex + 1; _i < requests.length; _i++) { + if (requests[_i].content.segment.time === segmentTime) { + filteredRequests.push(requests[_i]); + } else { + break; } - var scheduler = (0,_util_args__WEBPACK_IMPORTED_MODULE_0__/* .popScheduler */ .yG)(values); - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_1__/* .operate */ .e)(function (source, subscriber) { - (scheduler ? (0,_observable_concat__WEBPACK_IMPORTED_MODULE_2__/* .concat */ .z)(values, source, scheduler) : (0,_observable_concat__WEBPACK_IMPORTED_MODULE_2__/* .concat */ .z)(values, source)).subscribe(subscriber); - }); -} -//# sourceMappingURL=startWith.js.map - -/***/ }), - -/***/ 8720: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "R": function() { return /* binding */ subscribeOn; } -/* harmony export */ }); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); - -function subscribeOn(scheduler, delay) { - if (delay === void 0) { delay = 0; } - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - subscriber.add(scheduler.schedule(function () { return source.subscribe(subscriber); }, delay)); - }); + } + return filteredRequests; } -//# sourceMappingURL=subscribeOn.js.map - -/***/ }), - -/***/ 4978: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "w": function() { return /* binding */ switchMap; } -/* harmony export */ }); -/* harmony import */ var _observable_innerFrom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7878); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2566); - - - -function switchMap(project, resultSelector) { - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - var innerSubscriber = null; - var index = 0; - var isComplete = false; - var checkComplete = function () { return isComplete && !innerSubscriber && subscriber.complete(); }; - source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__/* .createOperatorSubscriber */ .x)(subscriber, function (value) { - innerSubscriber === null || innerSubscriber === void 0 ? void 0 : innerSubscriber.unsubscribe(); - var innerIndex = 0; - var outerIndex = index++; - (0,_observable_innerFrom__WEBPACK_IMPORTED_MODULE_2__/* .innerFrom */ .Xf)(project(value, outerIndex)).subscribe((innerSubscriber = (0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_1__/* .createOperatorSubscriber */ .x)(subscriber, function (innerValue) { return subscriber.next(resultSelector ? resultSelector(value, innerValue, outerIndex, innerIndex++) : innerValue); }, function () { - innerSubscriber = null; - checkComplete(); - }))); - }, function () { - isComplete = true; - checkComplete(); - })); - }); +/** + * Estimate the __VERY__ recent bandwidth based on a single unfinished request. + * Useful when the current bandwidth seemed to have fallen quickly. + * + * @param {Object} request + * @returns {number|undefined} + */ +function estimateRequestBandwidth(request) { + if (request.progress.length < 5) { + // threshold from which we can consider + // progress events reliably + return undefined; + } + // try to infer quickly the current bitrate based on the + // progress events + var ewma1 = new EWMA(2); + var progress = request.progress; + for (var i = 1; i < progress.length; i++) { + var bytesDownloaded = progress[i].size - progress[i - 1].size; + var timeElapsed = progress[i].timestamp - progress[i - 1].timestamp; + var reqBitrate = bytesDownloaded * 8 / (timeElapsed / 1000); + ewma1.addSample(timeElapsed / 1000, reqBitrate); + } + return ewma1.getEstimate(); } -//# sourceMappingURL=switchMap.js.map - -/***/ }), - -/***/ 4727: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "q": function() { return /* binding */ take; } -/* harmony export */ }); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1545); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2566); - - - -function take(count) { - return count <= 0 - ? - function () { return _observable_empty__WEBPACK_IMPORTED_MODULE_0__/* .EMPTY */ .E; } - : (0,_util_lift__WEBPACK_IMPORTED_MODULE_1__/* .operate */ .e)(function (source, subscriber) { - var seen = 0; - source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_2__/* .createOperatorSubscriber */ .x)(subscriber, function (value) { - if (++seen <= count) { - subscriber.next(value); - if (count <= seen) { - subscriber.complete(); - } - } - })); - }); +/** + * Estimate remaining time for a pending request from a progress event. + * @param {Object} lastProgressEvent + * @param {number} bandwidthEstimate + * @returns {number} + */ +function estimateRemainingTime(lastProgressEvent, bandwidthEstimate) { + var remainingData = (lastProgressEvent.totalSize - lastProgressEvent.size) * 8; + return Math.max(remainingData / bandwidthEstimate, 0); } -//# sourceMappingURL=take.js.map - -/***/ }), - -/***/ 3505: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "R": function() { return /* binding */ takeUntil; } -/* harmony export */ }); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2566); -/* harmony import */ var _observable_innerFrom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7878); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2967); - - - - -function takeUntil(notifier) { - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_0__/* .operate */ .e)(function (source, subscriber) { - (0,_observable_innerFrom__WEBPACK_IMPORTED_MODULE_1__/* .innerFrom */ .Xf)(notifier).subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_2__/* .createOperatorSubscriber */ .x)(subscriber, function () { return subscriber.complete(); }, _util_noop__WEBPACK_IMPORTED_MODULE_3__/* .noop */ .Z)); - !subscriber.closed && source.subscribe(subscriber); - }); +/** + * Check if the request for the most needed segment is too slow. + * If that's the case, re-calculate the bandwidth urgently based on + * this single request. + * @param {Object} pendingRequests - Every requests pending, in a chronological + * order in terms of segment time. + * @param {Object} playbackInfo - Information on the current playback. + * @param {Object|null} currentRepresentation - The Representation being + * presently being loaded. + * @param {boolean} lowLatencyMode - If `true`, we're playing the content as a + * low latency content - where requests might be pending when the segment is + * still encoded. + * @param {Number} lastEstimatedBitrate - Last bitrate estimate emitted. + * @returns {Number|undefined} + */ +function estimateStarvationModeBitrate(pendingRequests, playbackInfo, currentRepresentation, lowLatencyMode, lastEstimatedBitrate) { + if (lowLatencyMode) { + // TODO Skip only for newer segments? + return undefined; + } + var bufferGap = playbackInfo.bufferGap, + speed = playbackInfo.speed, + position = playbackInfo.position; + var realBufferGap = isFinite(bufferGap) ? bufferGap : 0; + var nextNeededPosition = position.last + realBufferGap; + var concernedRequests = getConcernedRequests(pendingRequests, nextNeededPosition); + if (concernedRequests.length !== 1) { + // 0 == no request + // 2+ == too complicated to calculate + return undefined; + } + var concernedRequest = concernedRequests[0]; + var now = performance.now(); + var lastProgressEvent = concernedRequest.progress.length > 0 ? concernedRequest.progress[concernedRequest.progress.length - 1] : undefined; + // first, try to do a quick estimate from progress events + var bandwidthEstimate = estimateRequestBandwidth(concernedRequest); + if (lastProgressEvent !== undefined && bandwidthEstimate !== undefined) { + var remainingTime = estimateRemainingTime(lastProgressEvent, bandwidthEstimate); + // if the remaining time does seem reliable + if ((now - lastProgressEvent.timestamp) / 1000 <= remainingTime) { + // Calculate estimated time spent rebuffering if we continue doing that request. + var expectedRebufferingTime = remainingTime - realBufferGap / speed; + if (expectedRebufferingTime > 2000) { + return bandwidthEstimate; + } + } + } + if (!concernedRequest.content.segment.complete) { + return undefined; + } + var chunkDuration = concernedRequest.content.segment.duration; + var requestElapsedTime = (now - concernedRequest.requestTimestamp) / 1000; + var reasonableElapsedTime = requestElapsedTime <= (chunkDuration * 1.5 + 2) / speed; + if (currentRepresentation == null || reasonableElapsedTime) { + return undefined; + } + // calculate a reduced bitrate from the current one + var factor = chunkDuration / requestElapsedTime; + var reducedBitrate = currentRepresentation.bitrate * Math.min(0.7, factor); + if (lastEstimatedBitrate === undefined || reducedBitrate < lastEstimatedBitrate) { + return reducedBitrate; + } } -//# sourceMappingURL=takeUntil.js.map - -/***/ }), - -/***/ 2006: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "b": function() { return /* binding */ tap; } -/* harmony export */ }); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2566); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); - - - - -function tap(observerOrNext, error, complete) { - var tapObserver = (0,_util_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(observerOrNext) || error || complete - ? - { next: observerOrNext, error: error, complete: complete } - : observerOrNext; - return tapObserver - ? (0,_util_lift__WEBPACK_IMPORTED_MODULE_1__/* .operate */ .e)(function (source, subscriber) { - var _a; - (_a = tapObserver.subscribe) === null || _a === void 0 ? void 0 : _a.call(tapObserver); - var isUnsub = true; - source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_2__/* .createOperatorSubscriber */ .x)(subscriber, function (value) { - var _a; - (_a = tapObserver.next) === null || _a === void 0 ? void 0 : _a.call(tapObserver, value); - subscriber.next(value); - }, function () { - var _a; - isUnsub = false; - (_a = tapObserver.complete) === null || _a === void 0 ? void 0 : _a.call(tapObserver); - subscriber.complete(); - }, function (err) { - var _a; - isUnsub = false; - (_a = tapObserver.error) === null || _a === void 0 ? void 0 : _a.call(tapObserver, err); - subscriber.error(err); - }, function () { - var _a, _b; - if (isUnsub) { - (_a = tapObserver.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(tapObserver); - } - (_b = tapObserver.finalize) === null || _b === void 0 ? void 0 : _b.call(tapObserver); - })); - }) - : - _util_identity__WEBPACK_IMPORTED_MODULE_3__/* .identity */ .y; +/** + * Returns true if, based on the current requests, it seems that the ABR should + * switch immediately if a lower bitrate is more adapted. + * Returns false if it estimates that you have time before switching to a lower + * bitrate. + * @param {Object} playbackInfo - Information on the current playback. + * @param {Object} requests - Every requests pending, in a chronological + * order in terms of segment time. + * @param {boolean} lowLatencyMode - If `true`, we're playing the content as a + * low latency content, as close to the live edge as possible. + * @returns {boolean} + */ +function shouldDirectlySwitchToLowBitrate(playbackInfo, requests, lowLatencyMode) { + if (lowLatencyMode) { + // TODO only when playing close to the live edge? + return true; + } + var realBufferGap = isFinite(playbackInfo.bufferGap) ? playbackInfo.bufferGap : 0; + var nextNeededPosition = playbackInfo.position.last + realBufferGap; + var nextRequest = (0,array_find/* default */.Z)(requests, function (_ref) { + var content = _ref.content; + return content.segment.duration > 0 && content.segment.time + content.segment.duration > nextNeededPosition; + }); + if (nextRequest === undefined) { + return true; + } + var now = performance.now(); + var lastProgressEvent = nextRequest.progress.length > 0 ? nextRequest.progress[nextRequest.progress.length - 1] : undefined; + // first, try to do a quick estimate from progress events + var bandwidthEstimate = estimateRequestBandwidth(nextRequest); + if (lastProgressEvent === undefined || bandwidthEstimate === undefined) { + return true; + } + var remainingTime = estimateRemainingTime(lastProgressEvent, bandwidthEstimate); + if ((now - lastProgressEvent.timestamp) / 1000 > remainingTime * 1.2) { + return true; + } + var expectedRebufferingTime = remainingTime - realBufferGap / playbackInfo.speed; + return expectedRebufferingTime > -1.5; } -//# sourceMappingURL=tap.js.map - -/***/ }), - -/***/ 3428: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "M": function() { return /* binding */ withLatestFrom; } -/* harmony export */ }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5987); -/* harmony import */ var _util_lift__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6798); -/* harmony import */ var _OperatorSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2566); -/* harmony import */ var _observable_innerFrom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7878); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(278); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2967); -/* harmony import */ var _util_args__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2457); - - - - - - - -function withLatestFrom() { - var inputs = []; - for (var _i = 0; _i < arguments.length; _i++) { - inputs[_i] = arguments[_i]; +/** + * Analyze the current network conditions and give a bandwidth estimate as well + * as a maximum bitrate a Representation should be. + * @class NetworkAnalyzer + */ +var NetworkAnalyzer = /*#__PURE__*/function () { + function NetworkAnalyzer(initialBitrate, lowLatencyMode) { + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + ABR_STARVATION_GAP = _config$getCurrent.ABR_STARVATION_GAP, + OUT_OF_STARVATION_GAP = _config$getCurrent.OUT_OF_STARVATION_GAP, + ABR_STARVATION_FACTOR = _config$getCurrent.ABR_STARVATION_FACTOR, + ABR_REGULAR_FACTOR = _config$getCurrent.ABR_REGULAR_FACTOR; + this._initialBitrate = initialBitrate; + this._inStarvationMode = false; + this._lowLatencyMode = lowLatencyMode; + if (lowLatencyMode) { + this._config = { + starvationGap: ABR_STARVATION_GAP.LOW_LATENCY, + outOfStarvationGap: OUT_OF_STARVATION_GAP.LOW_LATENCY, + starvationBitrateFactor: ABR_STARVATION_FACTOR.LOW_LATENCY, + regularBitrateFactor: ABR_REGULAR_FACTOR.LOW_LATENCY + }; + } else { + this._config = { + starvationGap: ABR_STARVATION_GAP.DEFAULT, + outOfStarvationGap: OUT_OF_STARVATION_GAP.DEFAULT, + starvationBitrateFactor: ABR_STARVATION_FACTOR.DEFAULT, + regularBitrateFactor: ABR_REGULAR_FACTOR.DEFAULT + }; } - var project = (0,_util_args__WEBPACK_IMPORTED_MODULE_0__/* .popResultSelector */ .jO)(inputs); - return (0,_util_lift__WEBPACK_IMPORTED_MODULE_1__/* .operate */ .e)(function (source, subscriber) { - var len = inputs.length; - var otherValues = new Array(len); - var hasValue = inputs.map(function () { return false; }); - var ready = false; - var _loop_1 = function (i) { - (0,_observable_innerFrom__WEBPACK_IMPORTED_MODULE_2__/* .innerFrom */ .Xf)(inputs[i]).subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_3__/* .createOperatorSubscriber */ .x)(subscriber, function (value) { - otherValues[i] = value; - if (!ready && !hasValue[i]) { - hasValue[i] = true; - (ready = hasValue.every(_util_identity__WEBPACK_IMPORTED_MODULE_4__/* .identity */ .y)) && (hasValue = null); - } - }, _util_noop__WEBPACK_IMPORTED_MODULE_5__/* .noop */ .Z)); - }; - for (var i = 0; i < len; i++) { - _loop_1(i); - } - source.subscribe((0,_OperatorSubscriber__WEBPACK_IMPORTED_MODULE_3__/* .createOperatorSubscriber */ .x)(subscriber, function (value) { - if (ready) { - var values = (0,tslib__WEBPACK_IMPORTED_MODULE_6__/* .__spreadArray */ .ev)([value], (0,tslib__WEBPACK_IMPORTED_MODULE_6__/* .__read */ .CR)(otherValues)); - subscriber.next(project ? project.apply(void 0, (0,tslib__WEBPACK_IMPORTED_MODULE_6__/* .__spreadArray */ .ev)([], (0,tslib__WEBPACK_IMPORTED_MODULE_6__/* .__read */ .CR)(values))) : values); - } - })); - }); -} -//# sourceMappingURL=withLatestFrom.js.map - -/***/ }), - -/***/ 8337: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "o": function() { return /* binding */ AsyncAction; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/node_modules/tslib/tslib.es6.js -var tslib_es6 = __webpack_require__(5987); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subscription.js + 1 modules -var Subscription = __webpack_require__(5720); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/Action.js - - -var Action = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(Action, _super); - function Action(scheduler, work) { - return _super.call(this) || this; - } - Action.prototype.schedule = function (state, delay) { - if (delay === void 0) { delay = 0; } - return this; - }; - return Action; -}(Subscription/* Subscription */.w0)); - -//# sourceMappingURL=Action.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/intervalProvider.js - -var intervalProvider = { - setInterval: function (handler, timeout) { - var args = []; - for (var _i = 2; _i < arguments.length; _i++) { - args[_i - 2] = arguments[_i]; - } - var delegate = intervalProvider.delegate; - if (delegate === null || delegate === void 0 ? void 0 : delegate.setInterval) { - return delegate.setInterval.apply(delegate, (0,tslib_es6/* __spreadArray */.ev)([handler, timeout], (0,tslib_es6/* __read */.CR)(args))); - } - return setInterval.apply(void 0, (0,tslib_es6/* __spreadArray */.ev)([handler, timeout], (0,tslib_es6/* __read */.CR)(args))); - }, - clearInterval: function (handle) { - var delegate = intervalProvider.delegate; - return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearInterval) || clearInterval)(handle); - }, - delegate: undefined, -}; -//# sourceMappingURL=intervalProvider.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/arrRemove.js -var arrRemove = __webpack_require__(3699); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/AsyncAction.js - - - - -var AsyncAction = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(AsyncAction, _super); - function AsyncAction(scheduler, work) { - var _this = _super.call(this, scheduler, work) || this; - _this.scheduler = scheduler; - _this.work = work; - _this.pending = false; - return _this; - } - AsyncAction.prototype.schedule = function (state, delay) { - var _a; - if (delay === void 0) { delay = 0; } - if (this.closed) { - return this; - } - this.state = state; - var id = this.id; - var scheduler = this.scheduler; - if (id != null) { - this.id = this.recycleAsyncId(scheduler, id, delay); - } - this.pending = true; - this.delay = delay; - this.id = (_a = this.id) !== null && _a !== void 0 ? _a : this.requestAsyncId(scheduler, this.id, delay); - return this; - }; - AsyncAction.prototype.requestAsyncId = function (scheduler, _id, delay) { - if (delay === void 0) { delay = 0; } - return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay); - }; - AsyncAction.prototype.recycleAsyncId = function (_scheduler, id, delay) { - if (delay === void 0) { delay = 0; } - if (delay != null && this.delay === delay && this.pending === false) { - return id; - } - if (id != null) { - intervalProvider.clearInterval(id); - } - return undefined; - }; - AsyncAction.prototype.execute = function (state, delay) { - if (this.closed) { - return new Error('executing a cancelled action'); - } - this.pending = false; - var error = this._execute(state, delay); - if (error) { - return error; - } - else if (this.pending === false && this.id != null) { - this.id = this.recycleAsyncId(this.scheduler, this.id, null); - } - }; - AsyncAction.prototype._execute = function (state, _delay) { - var errored = false; - var errorValue; - try { - this.work(state); - } - catch (e) { - errored = true; - errorValue = e ? e : new Error('Scheduled action threw falsy error'); - } - if (errored) { - this.unsubscribe(); - return errorValue; - } - }; - AsyncAction.prototype.unsubscribe = function () { - if (!this.closed) { - var _a = this, id = _a.id, scheduler = _a.scheduler; - var actions = scheduler.actions; - this.work = this.state = this.scheduler = null; - this.pending = false; - (0,arrRemove/* arrRemove */.P)(actions, this); - if (id != null) { - this.id = this.recycleAsyncId(scheduler, id, null); - } - this.delay = null; - _super.prototype.unsubscribe.call(this); - } - }; - return AsyncAction; -}(Action)); - -//# sourceMappingURL=AsyncAction.js.map - -/***/ }), - -/***/ 9682: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "v": function() { return /* binding */ AsyncScheduler; } -}); - -// EXTERNAL MODULE: ./node_modules/rxjs/node_modules/tslib/tslib.es6.js -var tslib_es6 = __webpack_require__(5987); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/dateTimestampProvider.js -var dateTimestampProvider = __webpack_require__(4318); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/Scheduler.js - -var Scheduler = (function () { - function Scheduler(schedulerActionCtor, now) { - if (now === void 0) { now = Scheduler.now; } - this.schedulerActionCtor = schedulerActionCtor; - this.now = now; - } - Scheduler.prototype.schedule = function (work, delay, state) { - if (delay === void 0) { delay = 0; } - return new this.schedulerActionCtor(this, work).schedule(state, delay); - }; - Scheduler.now = dateTimestampProvider/* dateTimestampProvider.now */.l.now; - return Scheduler; -}()); - -//# sourceMappingURL=Scheduler.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/AsyncScheduler.js - - -var AsyncScheduler = (function (_super) { - (0,tslib_es6/* __extends */.ZT)(AsyncScheduler, _super); - function AsyncScheduler(SchedulerAction, now) { - if (now === void 0) { now = Scheduler.now; } - var _this = _super.call(this, SchedulerAction, now) || this; - _this.actions = []; - _this._active = false; - return _this; - } - AsyncScheduler.prototype.flush = function (action) { - var actions = this.actions; - if (this._active) { - actions.push(action); - return; - } - var error; - this._active = true; - do { - if ((error = action.execute(action.state, action.delay))) { - break; - } - } while ((action = actions.shift())); - this._active = false; - if (error) { - while ((action = actions.shift())) { - action.unsubscribe(); - } - throw error; - } - }; - return AsyncScheduler; -}(Scheduler)); - -//# sourceMappingURL=AsyncScheduler.js.map - -/***/ }), - -/***/ 7991: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "P": function() { return /* binding */ async; }, -/* harmony export */ "z": function() { return /* binding */ asyncScheduler; } -/* harmony export */ }); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8337); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9682); - - -var asyncScheduler = new _AsyncScheduler__WEBPACK_IMPORTED_MODULE_0__/* .AsyncScheduler */ .v(_AsyncAction__WEBPACK_IMPORTED_MODULE_1__/* .AsyncAction */ .o); -var async = asyncScheduler; -//# sourceMappingURL=async.js.map - -/***/ }), - -/***/ 4318: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "l": function() { return /* binding */ dateTimestampProvider; } -/* harmony export */ }); -var dateTimestampProvider = { - now: function () { - return (dateTimestampProvider.delegate || Date).now(); - }, - delegate: undefined, -}; -//# sourceMappingURL=dateTimestampProvider.js.map - -/***/ }), - -/***/ 8380: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "z": function() { return /* binding */ timeoutProvider; } -/* harmony export */ }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5987); - -var timeoutProvider = { - setTimeout: function (handler, timeout) { - var args = []; - for (var _i = 2; _i < arguments.length; _i++) { - args[_i - 2] = arguments[_i]; - } - var delegate = timeoutProvider.delegate; - if (delegate === null || delegate === void 0 ? void 0 : delegate.setTimeout) { - return delegate.setTimeout.apply(delegate, (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__spreadArray */ .ev)([handler, timeout], (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__read */ .CR)(args))); - } - return setTimeout.apply(void 0, (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__spreadArray */ .ev)([handler, timeout], (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__read */ .CR)(args))); - }, - clearTimeout: function (handle) { - var delegate = timeoutProvider.delegate; - return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearTimeout) || clearTimeout)(handle); - }, - delegate: undefined, -}; -//# sourceMappingURL=timeoutProvider.js.map - -/***/ }), - -/***/ 9768: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "h": function() { return /* binding */ iterator; } -/* harmony export */ }); -/* unused harmony export getSymbolIterator */ -function getSymbolIterator() { - if (typeof Symbol !== 'function' || !Symbol.iterator) { - return '@@iterator'; - } - return Symbol.iterator; -} -var iterator = getSymbolIterator(); -//# sourceMappingURL=iterator.js.map - -/***/ }), - -/***/ 6766: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "L": function() { return /* binding */ observable; } -/* harmony export */ }); -var observable = (function () { return (typeof Symbol === 'function' && Symbol.observable) || '@@observable'; })(); -//# sourceMappingURL=observable.js.map - -/***/ }), - -/***/ 2457: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "_6": function() { return /* binding */ popNumber; }, -/* harmony export */ "jO": function() { return /* binding */ popResultSelector; }, -/* harmony export */ "yG": function() { return /* binding */ popScheduler; } -/* harmony export */ }); -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); -/* harmony import */ var _isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4865); - - -function last(arr) { - return arr[arr.length - 1]; -} -function popResultSelector(args) { - return (0,_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(last(args)) ? args.pop() : undefined; -} -function popScheduler(args) { - return (0,_isScheduler__WEBPACK_IMPORTED_MODULE_1__/* .isScheduler */ .K)(last(args)) ? args.pop() : undefined; -} -function popNumber(args, defaultValue) { - return typeof last(args) === 'number' ? args.pop() : defaultValue; -} -//# sourceMappingURL=args.js.map - -/***/ }), - -/***/ 3699: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "P": function() { return /* binding */ arrRemove; } -/* harmony export */ }); -function arrRemove(arr, item) { - if (arr) { - var index = arr.indexOf(item); - 0 <= index && arr.splice(index, 1); - } -} -//# sourceMappingURL=arrRemove.js.map - -/***/ }), - -/***/ 1819: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "d": function() { return /* binding */ createErrorClass; } -/* harmony export */ }); -function createErrorClass(createImpl) { - var _super = function (instance) { - Error.call(instance); - instance.stack = new Error().stack; - }; - var ctorFunc = createImpl(_super); - ctorFunc.prototype = Object.create(Error.prototype); - ctorFunc.prototype.constructor = ctorFunc; - return ctorFunc; -} -//# sourceMappingURL=createErrorClass.js.map - -/***/ }), - -/***/ 8846: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "O": function() { return /* binding */ captureError; }, -/* harmony export */ "x": function() { return /* binding */ errorContext; } -/* harmony export */ }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3912); - -var context = null; -function errorContext(cb) { - if (_config__WEBPACK_IMPORTED_MODULE_0__/* .config.useDeprecatedSynchronousErrorHandling */ .v.useDeprecatedSynchronousErrorHandling) { - var isRoot = !context; - if (isRoot) { - context = { errorThrown: false, error: null }; - } - cb(); - if (isRoot) { - var _a = context, errorThrown = _a.errorThrown, error = _a.error; - context = null; - if (errorThrown) { - throw error; - } - } + } + /** + * Gives an estimate of the current bandwidth and of the bitrate that should + * be considered for chosing a `representation`. + * This estimate is only based on network metrics. + * @param {Object} playbackInfo - Gives current information about playback. + * @param {Object} bandwidthEstimator - `BandwidthEstimator` allowing to + * produce network bandwidth estimates. + * @param {Object|null} currentRepresentation - The Representation currently + * chosen. + * `null` if no Representation has been chosen yet. + * @param {Array.} currentRequests - All segment requests by segment's + * start chronological order + * @param {number|undefined} lastEstimatedBitrate - Bitrate emitted during the + * last estimate. + * @returns {Object} + */ + var _proto = NetworkAnalyzer.prototype; + _proto.getBandwidthEstimate = function getBandwidthEstimate(playbackInfo, bandwidthEstimator, currentRepresentation, currentRequests, lastEstimatedBitrate) { + var newBitrateCeil; // bitrate ceil for the chosen Representation + var bandwidthEstimate; + var localConf = this._config; + var bufferGap = playbackInfo.bufferGap, + position = playbackInfo.position, + duration = playbackInfo.duration; + var realBufferGap = isFinite(bufferGap) ? bufferGap : 0; + var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), + ABR_STARVATION_DURATION_DELTA = _config$getCurrent2.ABR_STARVATION_DURATION_DELTA; + // check if should get in/out of starvation mode + if (isNaN(duration) || realBufferGap + position.last < duration - ABR_STARVATION_DURATION_DELTA) { + if (!this._inStarvationMode && realBufferGap <= localConf.starvationGap) { + log/* default.info */.Z.info("ABR: enter starvation mode."); + this._inStarvationMode = true; + } else if (this._inStarvationMode && realBufferGap >= localConf.outOfStarvationGap) { + log/* default.info */.Z.info("ABR: exit starvation mode."); + this._inStarvationMode = false; + } + } else if (this._inStarvationMode) { + log/* default.info */.Z.info("ABR: exit starvation mode."); + this._inStarvationMode = false; } - else { - cb(); + // If in starvation mode, check if a quick new estimate can be done + // from the last requests. + // If so, cancel previous estimates and replace it by the new one + if (this._inStarvationMode) { + bandwidthEstimate = estimateStarvationModeBitrate(currentRequests, playbackInfo, currentRepresentation, this._lowLatencyMode, lastEstimatedBitrate); + if (bandwidthEstimate != null) { + log/* default.info */.Z.info("ABR: starvation mode emergency estimate:", bandwidthEstimate); + bandwidthEstimator.reset(); + newBitrateCeil = currentRepresentation == null ? bandwidthEstimate : Math.min(bandwidthEstimate, currentRepresentation.bitrate); + } } -} -function captureError(err) { - if (_config__WEBPACK_IMPORTED_MODULE_0__/* .config.useDeprecatedSynchronousErrorHandling */ .v.useDeprecatedSynchronousErrorHandling && context) { - context.errorThrown = true; - context.error = err; + // if newBitrateCeil is not yet defined, do the normal estimation + if (newBitrateCeil == null) { + bandwidthEstimate = bandwidthEstimator.getEstimate(); + if (bandwidthEstimate != null) { + newBitrateCeil = bandwidthEstimate * (this._inStarvationMode ? localConf.starvationBitrateFactor : localConf.regularBitrateFactor); + } else if (lastEstimatedBitrate != null) { + newBitrateCeil = lastEstimatedBitrate * (this._inStarvationMode ? localConf.starvationBitrateFactor : localConf.regularBitrateFactor); + } else { + newBitrateCeil = this._initialBitrate; + } } -} -//# sourceMappingURL=errorContext.js.map - -/***/ }), - -/***/ 7845: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "f": function() { return /* binding */ executeSchedule; } -/* harmony export */ }); -function executeSchedule(parentSubscription, scheduler, work, delay, repeat) { - if (delay === void 0) { delay = 0; } - if (repeat === void 0) { repeat = false; } - var scheduleSubscription = scheduler.schedule(function () { - work(); - if (repeat) { - parentSubscription.add(this.schedule(null, delay)); - } - else { - this.unsubscribe(); - } - }, delay); - parentSubscription.add(scheduleSubscription); - if (!repeat) { - return scheduleSubscription; + if (playbackInfo.speed > 1) { + newBitrateCeil /= playbackInfo.speed; } -} -//# sourceMappingURL=executeSchedule.js.map - -/***/ }), - -/***/ 278: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "y": function() { return /* binding */ identity; } -/* harmony export */ }); -function identity(x) { - return x; -} -//# sourceMappingURL=identity.js.map - -/***/ }), - -/***/ 5685: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "z": function() { return /* binding */ isArrayLike; } -/* harmony export */ }); -var isArrayLike = (function (x) { return x && typeof x.length === 'number' && typeof x !== 'function'; }); -//# sourceMappingURL=isArrayLike.js.map - -/***/ }), - -/***/ 8430: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "D": function() { return /* binding */ isAsyncIterable; } -/* harmony export */ }); -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); - -function isAsyncIterable(obj) { - return Symbol.asyncIterator && (0,_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(obj === null || obj === void 0 ? void 0 : obj[Symbol.asyncIterator]); -} -//# sourceMappingURL=isAsyncIterable.js.map - -/***/ }), - -/***/ 1454: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "q": function() { return /* binding */ isValidDate; } -/* harmony export */ }); -function isValidDate(value) { - return value instanceof Date && !isNaN(value); -} -//# sourceMappingURL=isDate.js.map - -/***/ }), - -/***/ 8474: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "m": function() { return /* binding */ isFunction; } -/* harmony export */ }); -function isFunction(value) { - return typeof value === 'function'; -} -//# sourceMappingURL=isFunction.js.map - -/***/ }), - -/***/ 1764: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "c": function() { return /* binding */ isInteropObservable; } -/* harmony export */ }); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6766); -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); - - -function isInteropObservable(input) { - return (0,_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(input[_symbol_observable__WEBPACK_IMPORTED_MODULE_1__/* .observable */ .L]); -} -//# sourceMappingURL=isInteropObservable.js.map - -/***/ }), - -/***/ 1837: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "T": function() { return /* binding */ isIterable; } -/* harmony export */ }); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9768); -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); - - -function isIterable(input) { - return (0,_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(input === null || input === void 0 ? void 0 : input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_1__/* .iterator */ .h]); -} -//# sourceMappingURL=isIterable.js.map - -/***/ }), - -/***/ 3841: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "t": function() { return /* binding */ isPromise; } -/* harmony export */ }); -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); - -function isPromise(value) { - return (0,_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(value === null || value === void 0 ? void 0 : value.then); -} -//# sourceMappingURL=isPromise.js.map - -/***/ }), - -/***/ 8671: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "L": function() { return /* binding */ isReadableStreamLike; }, -/* harmony export */ "Q": function() { return /* binding */ readableStreamLikeToAsyncGenerator; } -/* harmony export */ }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5987); -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8474); - - -function readableStreamLikeToAsyncGenerator(readableStream) { - return (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__asyncGenerator */ .FC)(this, arguments, function readableStreamLikeToAsyncGenerator_1() { - var reader, _a, value, done; - return (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__generator */ .Jh)(this, function (_b) { - switch (_b.label) { - case 0: - reader = readableStream.getReader(); - _b.label = 1; - case 1: - _b.trys.push([1, , 9, 10]); - _b.label = 2; - case 2: - if (false) {} - return [4, (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__await */ .qq)(reader.read())]; - case 3: - _a = _b.sent(), value = _a.value, done = _a.done; - if (!done) return [3, 5]; - return [4, (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__await */ .qq)(void 0)]; - case 4: return [2, _b.sent()]; - case 5: return [4, (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__await */ .qq)(value)]; - case 6: return [4, _b.sent()]; - case 7: - _b.sent(); - return [3, 2]; - case 8: return [3, 10]; - case 9: - reader.releaseLock(); - return [7]; - case 10: return [2]; - } - }); - }); -} -function isReadableStreamLike(obj) { - return (0,_isFunction__WEBPACK_IMPORTED_MODULE_1__/* .isFunction */ .m)(obj === null || obj === void 0 ? void 0 : obj.getReader); -} -//# sourceMappingURL=isReadableStreamLike.js.map - -/***/ }), - -/***/ 4865: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "K": function() { return /* binding */ isScheduler; } -/* harmony export */ }); -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); - -function isScheduler(value) { - return value && (0,_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(value.schedule); -} -//# sourceMappingURL=isScheduler.js.map - -/***/ }), - -/***/ 6798: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "e": function() { return /* binding */ operate; } -/* harmony export */ }); -/* unused harmony export hasLift */ -/* harmony import */ var _isFunction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8474); - -function hasLift(source) { - return (0,_isFunction__WEBPACK_IMPORTED_MODULE_0__/* .isFunction */ .m)(source === null || source === void 0 ? void 0 : source.lift); -} -function operate(init) { - return function (source) { - if (hasLift(source)) { - return source.lift(function (liftedSource) { - try { - return init(liftedSource, this); - } - catch (err) { - this.error(err); - } - }); - } - throw new TypeError('Unable to lift unknown Observable type'); - }; -} -//# sourceMappingURL=lift.js.map - -/***/ }), - -/***/ 3211: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ mapOneOrManyArgs; } -/* harmony export */ }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5987); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9127); - - -var isArray = Array.isArray; -function callOrApply(fn, args) { - return isArray(args) ? fn.apply(void 0, (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__spreadArray */ .ev)([], (0,tslib__WEBPACK_IMPORTED_MODULE_0__/* .__read */ .CR)(args))) : fn(args); -} -function mapOneOrManyArgs(fn) { - return (0,_operators_map__WEBPACK_IMPORTED_MODULE_1__/* .map */ .U)(function (args) { return callOrApply(fn, args); }); -} -//# sourceMappingURL=mapOneOrManyArgs.js.map - -/***/ }), - -/***/ 2967: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ noop; } -/* harmony export */ }); -function noop() { } -//# sourceMappingURL=noop.js.map - -/***/ }), - -/***/ 5: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "h": function() { return /* binding */ reportUnhandledError; } -/* harmony export */ }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3912); -/* harmony import */ var _scheduler_timeoutProvider__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8380); - - -function reportUnhandledError(err) { - _scheduler_timeoutProvider__WEBPACK_IMPORTED_MODULE_0__/* .timeoutProvider.setTimeout */ .z.setTimeout(function () { - var onUnhandledError = _config__WEBPACK_IMPORTED_MODULE_1__/* .config.onUnhandledError */ .v.onUnhandledError; - if (onUnhandledError) { - onUnhandledError(err); - } - else { - throw err; - } - }); -} -//# sourceMappingURL=reportUnhandledError.js.map - -/***/ }), - -/***/ 8729: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "z": function() { return /* binding */ createInvalidObservableTypeError; } -/* harmony export */ }); -function createInvalidObservableTypeError(input) { - return new TypeError("You provided " + (input !== null && typeof input === 'object' ? 'an invalid object' : "'" + input + "'") + " where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable."); -} -//# sourceMappingURL=throwUnobservableError.js.map - -/***/ }), - -/***/ 5987: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "CR": function() { return /* binding */ __read; }, -/* harmony export */ "FC": function() { return /* binding */ __asyncGenerator; }, -/* harmony export */ "Jh": function() { return /* binding */ __generator; }, -/* harmony export */ "KL": function() { return /* binding */ __asyncValues; }, -/* harmony export */ "XA": function() { return /* binding */ __values; }, -/* harmony export */ "ZT": function() { return /* binding */ __extends; }, -/* harmony export */ "ev": function() { return /* binding */ __spreadArray; }, -/* harmony export */ "mG": function() { return /* binding */ __awaiter; }, -/* harmony export */ "qq": function() { return /* binding */ __await; } -/* harmony export */ }); -/* unused harmony exports __assign, __rest, __decorate, __param, __metadata, __createBinding, __exportStar, __spread, __spreadArrays, __asyncDelegator, __makeTemplateObject, __importStar, __importDefault, __classPrivateFieldGet, __classPrivateFieldSet */ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -/* global Reflect, Promise */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - } - return __assign.apply(this, arguments); -} - -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -} - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -} - -function __param(paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -} - -function __metadata(metadataKey, metadataValue) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); -} - -function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -} - -var __createBinding = Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -}); - -function __exportStar(m, o) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p); -} - -function __values(o) { - var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; - if (m) return m.call(o); - if (o && typeof o.length === "number") return { - next: function () { - if (o && i >= o.length) o = void 0; - return { value: o && o[i++], done: !o }; - } - }; - throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); -} - -function __read(o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; -} - -/** @deprecated */ -function __spread() { - for (var ar = [], i = 0; i < arguments.length; i++) - ar = ar.concat(__read(arguments[i])); - return ar; -} - -/** @deprecated */ -function __spreadArrays() { - for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; - for (var r = Array(s), k = 0, i = 0; i < il; i++) - for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) - r[k] = a[j]; - return r; -} - -function __spreadArray(to, from) { - for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) - to[j] = from[i]; - return to; -} - -function __await(v) { - return this instanceof __await ? (this.v = v, this) : new __await(v); -} - -function __asyncGenerator(thisArg, _arguments, generator) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var g = generator.apply(thisArg, _arguments || []), i, q = []; - return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; - function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } - function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } - function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } - function fulfill(value) { resume("next", value); } - function reject(value) { resume("throw", value); } - function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } -} - -function __asyncDelegator(o) { - var i, p; - return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; - function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } -} - -function __asyncValues(o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -} - -function __makeTemplateObject(cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; - -var __setModuleDefault = Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}; - -function __importStar(mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -} - -function __importDefault(mod) { - return (mod && mod.__esModule) ? mod : { default: mod }; -} - -function __classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver); -} - -function __classPrivateFieldSet(receiver, privateMap, value) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to set private field on non-instance"); - } - privateMap.set(receiver, value); - return value; -} - - -/***/ }), - -/***/ 7061: -/***/ (function(module, __unused_webpack_exports, __webpack_require__) { - -var _typeof = (__webpack_require__(8698)["default"]); - -function _regeneratorRuntime() { - "use strict"; - /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ - - module.exports = _regeneratorRuntime = function _regeneratorRuntime() { - return exports; - }, module.exports.__esModule = true, module.exports["default"] = module.exports; - var exports = {}, - Op = Object.prototype, - hasOwn = Op.hasOwnProperty, - $Symbol = "function" == typeof Symbol ? Symbol : {}, - iteratorSymbol = $Symbol.iterator || "@@iterator", - asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", - toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; - - function define(obj, key, value) { - return Object.defineProperty(obj, key, { - value: value, - enumerable: !0, - configurable: !0, - writable: !0 - }), obj[key]; - } - - try { - define({}, ""); - } catch (err) { - define = function define(obj, key, value) { - return obj[key] = value; + return { + bandwidthEstimate: bandwidthEstimate, + bitrateChosen: newBitrateCeil }; } - - function wrap(innerFn, outerFn, self, tryLocsList) { - var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator, - generator = Object.create(protoGenerator.prototype), - context = new Context(tryLocsList || []); - return generator._invoke = function (innerFn, self, context) { - var state = "suspendedStart"; - return function (method, arg) { - if ("executing" === state) throw new Error("Generator is already running"); - - if ("completed" === state) { - if ("throw" === method) throw arg; - return doneResult(); - } - - for (context.method = method, context.arg = arg;;) { - var delegate = context.delegate; - - if (delegate) { - var delegateResult = maybeInvokeDelegate(delegate, context); - - if (delegateResult) { - if (delegateResult === ContinueSentinel) continue; - return delegateResult; - } - } - - if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { - if ("suspendedStart" === state) throw state = "completed", context.arg; - context.dispatchException(context.arg); - } else "return" === context.method && context.abrupt("return", context.arg); - state = "executing"; - var record = tryCatch(innerFn, self, context); - - if ("normal" === record.type) { - if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; - return { - value: record.arg, - done: context.done - }; - } - - "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); - } - }; - }(innerFn, self, context), generator; - } - - function tryCatch(fn, obj, arg) { - try { - return { - type: "normal", - arg: fn.call(obj, arg) - }; - } catch (err) { - return { - type: "throw", - arg: err - }; - } - } - - exports.wrap = wrap; - var ContinueSentinel = {}; - - function Generator() {} - - function GeneratorFunction() {} - - function GeneratorFunctionPrototype() {} - - var IteratorPrototype = {}; - define(IteratorPrototype, iteratorSymbol, function () { - return this; - }); - var getProto = Object.getPrototypeOf, - NativeIteratorPrototype = getProto && getProto(getProto(values([]))); - NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol) && (IteratorPrototype = NativeIteratorPrototype); - var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); - - function defineIteratorMethods(prototype) { - ["next", "throw", "return"].forEach(function (method) { - define(prototype, method, function (arg) { - return this._invoke(method, arg); - }); - }); - } - - function AsyncIterator(generator, PromiseImpl) { - function invoke(method, arg, resolve, reject) { - var record = tryCatch(generator[method], generator, arg); - - if ("throw" !== record.type) { - var result = record.arg, - value = result.value; - return value && "object" == _typeof(value) && hasOwn.call(value, "__await") ? PromiseImpl.resolve(value.__await).then(function (value) { - invoke("next", value, resolve, reject); - }, function (err) { - invoke("throw", err, resolve, reject); - }) : PromiseImpl.resolve(value).then(function (unwrapped) { - result.value = unwrapped, resolve(result); - }, function (error) { - return invoke("throw", error, resolve, reject); - }); - } - - reject(record.arg); - } - - var previousPromise; - - this._invoke = function (method, arg) { - function callInvokeWithMethodAndArg() { - return new PromiseImpl(function (resolve, reject) { - invoke(method, arg, resolve, reject); - }); - } - - return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); - }; - } - - function maybeInvokeDelegate(delegate, context) { - var method = delegate.iterator[context.method]; - - if (undefined === method) { - if (context.delegate = null, "throw" === context.method) { - if (delegate.iterator["return"] && (context.method = "return", context.arg = undefined, maybeInvokeDelegate(delegate, context), "throw" === context.method)) return ContinueSentinel; - context.method = "throw", context.arg = new TypeError("The iterator does not provide a 'throw' method"); - } - - return ContinueSentinel; - } - - var record = tryCatch(method, delegate.iterator, context.arg); - if ("throw" === record.type) return context.method = "throw", context.arg = record.arg, context.delegate = null, ContinueSentinel; - var info = record.arg; - return info ? info.done ? (context[delegate.resultName] = info.value, context.next = delegate.nextLoc, "return" !== context.method && (context.method = "next", context.arg = undefined), context.delegate = null, ContinueSentinel) : info : (context.method = "throw", context.arg = new TypeError("iterator result is not an object"), context.delegate = null, ContinueSentinel); - } - - function pushTryEntry(locs) { - var entry = { - tryLoc: locs[0] - }; - 1 in locs && (entry.catchLoc = locs[1]), 2 in locs && (entry.finallyLoc = locs[2], entry.afterLoc = locs[3]), this.tryEntries.push(entry); - } - - function resetTryEntry(entry) { - var record = entry.completion || {}; - record.type = "normal", delete record.arg, entry.completion = record; - } - - function Context(tryLocsList) { - this.tryEntries = [{ - tryLoc: "root" - }], tryLocsList.forEach(pushTryEntry, this), this.reset(!0); - } - - function values(iterable) { - if (iterable) { - var iteratorMethod = iterable[iteratorSymbol]; - if (iteratorMethod) return iteratorMethod.call(iterable); - if ("function" == typeof iterable.next) return iterable; - - if (!isNaN(iterable.length)) { - var i = -1, - next = function next() { - for (; ++i < iterable.length;) { - if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; - } - - return next.value = undefined, next.done = !0, next; - }; - - return next.next = next; - } - } - - return { - next: doneResult - }; - } - - function doneResult() { - return { - value: undefined, - done: !0 - }; - } - - return GeneratorFunction.prototype = GeneratorFunctionPrototype, define(Gp, "constructor", GeneratorFunctionPrototype), define(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { - var ctor = "function" == typeof genFun && genFun.constructor; - return !!ctor && (ctor === GeneratorFunction || "GeneratorFunction" === (ctor.displayName || ctor.name)); - }, exports.mark = function (genFun) { - return Object.setPrototypeOf ? Object.setPrototypeOf(genFun, GeneratorFunctionPrototype) : (genFun.__proto__ = GeneratorFunctionPrototype, define(genFun, toStringTagSymbol, "GeneratorFunction")), genFun.prototype = Object.create(Gp), genFun; - }, exports.awrap = function (arg) { - return { - __await: arg - }; - }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, asyncIteratorSymbol, function () { - return this; - }), exports.AsyncIterator = AsyncIterator, exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) { - void 0 === PromiseImpl && (PromiseImpl = Promise); - var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl); - return exports.isGeneratorFunction(outerFn) ? iter : iter.next().then(function (result) { - return result.done ? result.value : iter.next(); - }); - }, defineIteratorMethods(Gp), define(Gp, toStringTagSymbol, "Generator"), define(Gp, iteratorSymbol, function () { - return this; - }), define(Gp, "toString", function () { - return "[object Generator]"; - }), exports.keys = function (object) { - var keys = []; - - for (var key in object) { - keys.push(key); - } - - return keys.reverse(), function next() { - for (; keys.length;) { - var key = keys.pop(); - if (key in object) return next.value = key, next.done = !1, next; - } - - return next.done = !0, next; - }; - }, exports.values = values, Context.prototype = { - constructor: Context, - reset: function reset(skipTempReset) { - if (this.prev = 0, this.next = 0, this.sent = this._sent = undefined, this.done = !1, this.delegate = null, this.method = "next", this.arg = undefined, this.tryEntries.forEach(resetTryEntry), !skipTempReset) for (var name in this) { - "t" === name.charAt(0) && hasOwn.call(this, name) && !isNaN(+name.slice(1)) && (this[name] = undefined); - } - }, - stop: function stop() { - this.done = !0; - var rootRecord = this.tryEntries[0].completion; - if ("throw" === rootRecord.type) throw rootRecord.arg; - return this.rval; - }, - dispatchException: function dispatchException(exception) { - if (this.done) throw exception; - var context = this; - - function handle(loc, caught) { - return record.type = "throw", record.arg = exception, context.next = loc, caught && (context.method = "next", context.arg = undefined), !!caught; - } - - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i], - record = entry.completion; - if ("root" === entry.tryLoc) return handle("end"); - - if (entry.tryLoc <= this.prev) { - var hasCatch = hasOwn.call(entry, "catchLoc"), - hasFinally = hasOwn.call(entry, "finallyLoc"); - - if (hasCatch && hasFinally) { - if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); - if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); - } else if (hasCatch) { - if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); - } else { - if (!hasFinally) throw new Error("try statement without catch or finally"); - if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); - } - } - } - }, - abrupt: function abrupt(type, arg) { - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i]; - - if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { - var finallyEntry = entry; - break; - } - } - - finallyEntry && ("break" === type || "continue" === type) && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc && (finallyEntry = null); - var record = finallyEntry ? finallyEntry.completion : {}; - return record.type = type, record.arg = arg, finallyEntry ? (this.method = "next", this.next = finallyEntry.finallyLoc, ContinueSentinel) : this.complete(record); - }, - complete: function complete(record, afterLoc) { - if ("throw" === record.type) throw record.arg; - return "break" === record.type || "continue" === record.type ? this.next = record.arg : "return" === record.type ? (this.rval = this.arg = record.arg, this.method = "return", this.next = "end") : "normal" === record.type && afterLoc && (this.next = afterLoc), ContinueSentinel; - }, - finish: function finish(finallyLoc) { - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i]; - if (entry.finallyLoc === finallyLoc) return this.complete(entry.completion, entry.afterLoc), resetTryEntry(entry), ContinueSentinel; - } - }, - "catch": function _catch(tryLoc) { - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i]; - - if (entry.tryLoc === tryLoc) { - var record = entry.completion; - - if ("throw" === record.type) { - var thrown = record.arg; - resetTryEntry(entry); - } - - return thrown; - } - } - - throw new Error("illegal catch attempt"); - }, - delegateYield: function delegateYield(iterable, resultName, nextLoc) { - return this.delegate = { - iterator: values(iterable), - resultName: resultName, - nextLoc: nextLoc - }, "next" === this.method && (this.arg = undefined), ContinueSentinel; + /** + * For a given wanted bitrate, tells if should switch urgently. + * @param {number} bitrate - The new estimated bitrate. + * @param {Object|null} currentRepresentation - The Representation being + * presently being loaded. + * @param {Array.} currentRequests - All segment requests by segment's + * start chronological order + * @param {Object} playbackInfo - Information on the current playback. + * @returns {boolean} + */; + _proto.isUrgent = function isUrgent(bitrate, currentRepresentation, currentRequests, playbackInfo) { + if (currentRepresentation === null) { + return true; + } else if (bitrate === currentRepresentation.bitrate) { + return false; + } else if (bitrate > currentRepresentation.bitrate) { + return !this._inStarvationMode; } - }, exports; -} - -module.exports = _regeneratorRuntime, module.exports.__esModule = true, module.exports["default"] = module.exports; - -/***/ }), - -/***/ 8698: -/***/ (function(module) { - -function _typeof(obj) { - "@babel/helpers - typeof"; - - return (module.exports = _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(obj); -} - -module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; - -/***/ }), - -/***/ 4687: -/***/ (function(module, __unused_webpack_exports, __webpack_require__) { - -// TODO(Babel 8): Remove this file. - -var runtime = __webpack_require__(7061)(); -module.exports = runtime; - -// Copied from https://github.com/facebook/regenerator/blob/main/packages/runtime/runtime.js#L736= -try { - regeneratorRuntime = runtime; -} catch (accidentalStrictMode) { - if (typeof globalThis === "object") { - globalThis.regeneratorRuntime = runtime; - } else { - Function("r", "regeneratorRuntime = r")(runtime); - } -} - - -/***/ }), - -/***/ 7326: -/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ _assertThisInitialized; } -/* harmony export */ }); -function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - - return self; -} - -/***/ }), - -/***/ 5861: -/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ _asyncToGenerator; } -/* harmony export */ }); -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - - if (info.done) { - resolve(value); - } else { - Promise.resolve(value).then(_next, _throw); - } -} - -function _asyncToGenerator(fn) { - return function () { - var self = this, - args = arguments; - return new Promise(function (resolve, reject) { - var gen = fn.apply(self, args); - - function _next(value) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); - } - - function _throw(err) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); - } - - _next(undefined); - }); - }; -} - -/***/ }), - -/***/ 3144: -/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ _createClass; } -/* harmony export */ }); -function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } -} - -function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - Object.defineProperty(Constructor, "prototype", { - writable: false - }); - return Constructor; -} - -/***/ }), - -/***/ 4578: -/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ _inheritsLoose; } -/* harmony export */ }); -/* harmony import */ var _setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9611); - -function _inheritsLoose(subClass, superClass) { - subClass.prototype = Object.create(superClass.prototype); - subClass.prototype.constructor = subClass; - (0,_setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .Z)(subClass, superClass); -} - -/***/ }), - -/***/ 9611: -/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Z": function() { return /* binding */ _setPrototypeOf; } -/* harmony export */ }); -function _setPrototypeOf(o, p) { - _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); -} - -/***/ }), - -/***/ 2146: -/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "Z": function() { return /* binding */ _wrapNativeSuper; } -}); - -;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js -function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); + return shouldDirectlySwitchToLowBitrate(playbackInfo, currentRequests, this._lowLatencyMode); }; - return _getPrototypeOf(o); -} -// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js -var setPrototypeOf = __webpack_require__(9611); -;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/isNativeFunction.js -function _isNativeFunction(fn) { - return Function.toString.call(fn).indexOf("[native code]") !== -1; -} -;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/isNativeReflectConstruct.js -function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - - try { - Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); - return true; - } catch (e) { - return false; - } -} -;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/construct.js - - -function _construct(Parent, args, Class) { - if (_isNativeReflectConstruct()) { - _construct = Reflect.construct.bind(); - } else { - _construct = function _construct(Parent, args, Class) { - var a = [null]; - a.push.apply(a, args); - var Constructor = Function.bind.apply(Parent, a); - var instance = new Constructor(); - if (Class) (0,setPrototypeOf/* default */.Z)(instance, Class.prototype); - return instance; - }; - } - - return _construct.apply(null, arguments); -} -;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/wrapNativeSuper.js - - - - -function _wrapNativeSuper(Class) { - var _cache = typeof Map === "function" ? new Map() : undefined; - - _wrapNativeSuper = function _wrapNativeSuper(Class) { - if (Class === null || !_isNativeFunction(Class)) return Class; - - if (typeof Class !== "function") { - throw new TypeError("Super expression must either be null or a function"); - } - - if (typeof _cache !== "undefined") { - if (_cache.has(Class)) return _cache.get(Class); - - _cache.set(Class, Wrapper); - } - - function Wrapper() { - return _construct(Class, arguments, _getPrototypeOf(this).constructor); - } - - Wrapper.prototype = Object.create(Class.prototype, { - constructor: { - value: Wrapper, - enumerable: false, - writable: true, - configurable: true - } - }); - return (0,setPrototypeOf/* default */.Z)(Wrapper, Class); - }; - - return _wrapNativeSuper(Class); -} - -/***/ }) - -/******/ }); -/************************************************************************/ -/******/ // The module cache -/******/ var __webpack_module_cache__ = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ // Check if module is in cache -/******/ var cachedModule = __webpack_module_cache__[moduleId]; -/******/ if (cachedModule !== undefined) { -/******/ return cachedModule.exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = __webpack_module_cache__[moduleId] = { -/******/ // no module.id needed -/******/ // no module.loaded needed -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/************************************************************************/ -/******/ /* webpack/runtime/compat get default export */ -/******/ !function() { -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function() { return module['default']; } : -/******/ function() { return module; }; -/******/ __webpack_require__.d(getter, { a: getter }); -/******/ return getter; -/******/ }; -/******/ }(); -/******/ -/******/ /* webpack/runtime/define property getters */ -/******/ !function() { -/******/ // define getter functions for harmony exports -/******/ __webpack_require__.d = function(exports, definition) { -/******/ for(var key in definition) { -/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { -/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); -/******/ } -/******/ } -/******/ }; -/******/ }(); -/******/ -/******/ /* webpack/runtime/hasOwnProperty shorthand */ -/******/ !function() { -/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } -/******/ }(); -/******/ -/************************************************************************/ -var __webpack_exports__ = {}; -// This entry need to be wrapped in an IIFE because it need to be in strict mode. -!function() { -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "default": function() { return /* binding */ src; } -}); - -// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/createClass.js -var createClass = __webpack_require__(3144); -// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js -var inheritsLoose = __webpack_require__(4578); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Subject.js + 1 modules -var Subject = __webpack_require__(6716); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/take.js -var take = __webpack_require__(4727); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/takeUntil.js -var takeUntil = __webpack_require__(3505); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/map.js -var map = __webpack_require__(9127); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/identity.js -var identity = __webpack_require__(278); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/lift.js -var lift = __webpack_require__(6798); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/OperatorSubscriber.js -var OperatorSubscriber = __webpack_require__(2566); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/distinctUntilChanged.js - - - -function distinctUntilChanged(comparator, keySelector) { - if (keySelector === void 0) { keySelector = identity/* identity */.y; } - comparator = comparator !== null && comparator !== void 0 ? comparator : defaultCompare; - return (0,lift/* operate */.e)(function (source, subscriber) { - var previousKey; - var first = true; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - var currentKey = keySelector(value); - if (first || !comparator(previousKey, currentKey)) { - first = false; - previousKey = currentKey; - subscriber.next(value); - } - })); - }); -} -function defaultCompare(a, b) { - return a === b; -} -//# sourceMappingURL=distinctUntilChanged.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/Observable.js + 1 modules -var Observable = __webpack_require__(1480); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/of.js -var of = __webpack_require__(2817); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/mergeMap.js + 1 modules -var mergeMap = __webpack_require__(7877); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/shareReplay.js -var shareReplay = __webpack_require__(8515); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/defer.js -var defer = __webpack_require__(9917); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/connectable.js - - - -var DEFAULT_CONFIG = { - connector: function () { return new Subject/* Subject */.x(); }, - resetOnDisconnect: true, -}; -function connectable(source, config) { - if (config === void 0) { config = DEFAULT_CONFIG; } - var connection = null; - var connector = config.connector, _a = config.resetOnDisconnect, resetOnDisconnect = _a === void 0 ? true : _a; - var subject = connector(); - var result = new Observable/* Observable */.y(function (subscriber) { - return subject.subscribe(subscriber); - }); - result.connect = function () { - if (!connection || connection.closed) { - connection = (0,defer/* defer */.P)(function () { return source; }).subscribe(subject); - if (resetOnDisconnect) { - connection.add(function () { return (subject = connector()); }); - } - } - return connection; - }; - return result; -} -//# sourceMappingURL=connectable.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/filter.js -var filter = __webpack_require__(4975); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/share.js -var share = __webpack_require__(5583); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/util/argsArgArrayOrObject.js -var isArray = Array.isArray; -var getPrototypeOf = Object.getPrototypeOf, objectProto = Object.prototype, getKeys = Object.keys; -function argsArgArrayOrObject(args) { - if (args.length === 1) { - var first_1 = args[0]; - if (isArray(first_1)) { - return { args: first_1, keys: null }; - } - if (isPOJO(first_1)) { - var keys = getKeys(first_1); - return { - args: keys.map(function (key) { return first_1[key]; }), - keys: keys, - }; - } - } - return { args: args, keys: null }; -} -function isPOJO(obj) { - return obj && typeof obj === 'object' && getPrototypeOf(obj) === objectProto; -} -//# sourceMappingURL=argsArgArrayOrObject.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/from.js + 8 modules -var from = __webpack_require__(3102); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/mapOneOrManyArgs.js -var mapOneOrManyArgs = __webpack_require__(3211); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/args.js -var util_args = __webpack_require__(2457); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/util/createObject.js -function createObject(keys, values) { - return keys.reduce(function (result, key, i) { return ((result[key] = values[i]), result); }, {}); -} -//# sourceMappingURL=createObject.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/util/executeSchedule.js -var executeSchedule = __webpack_require__(7845); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/combineLatest.js - - - - - - - - - -function combineLatest() { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - var scheduler = (0,util_args/* popScheduler */.yG)(args); - var resultSelector = (0,util_args/* popResultSelector */.jO)(args); - var _a = argsArgArrayOrObject(args), observables = _a.args, keys = _a.keys; - if (observables.length === 0) { - return (0,from/* from */.D)([], scheduler); - } - var result = new Observable/* Observable */.y(combineLatestInit(observables, scheduler, keys - ? - function (values) { return createObject(keys, values); } - : - identity/* identity */.y)); - return resultSelector ? result.pipe((0,mapOneOrManyArgs/* mapOneOrManyArgs */.Z)(resultSelector)) : result; -} -function combineLatestInit(observables, scheduler, valueTransform) { - if (valueTransform === void 0) { valueTransform = identity/* identity */.y; } - return function (subscriber) { - maybeSchedule(scheduler, function () { - var length = observables.length; - var values = new Array(length); - var active = length; - var remainingFirstValues = length; - var _loop_1 = function (i) { - maybeSchedule(scheduler, function () { - var source = (0,from/* from */.D)(observables[i], scheduler); - var hasFirstValue = false; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - values[i] = value; - if (!hasFirstValue) { - hasFirstValue = true; - remainingFirstValues--; - } - if (!remainingFirstValues) { - subscriber.next(valueTransform(values.slice())); - } - }, function () { - if (!--active) { - subscriber.complete(); - } - })); - }, subscriber); - }; - for (var i = 0; i < length; i++) { - _loop_1(i); - } - }, subscriber); - }; -} -function maybeSchedule(scheduler, execute, subscription) { - if (scheduler) { - (0,executeSchedule/* executeSchedule */.f)(subscription, scheduler, execute); - } - else { - execute(); - } -} -//# sourceMappingURL=combineLatest.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/startWith.js -var startWith = __webpack_require__(6108); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/concat.js + 1 modules -var concat = __webpack_require__(2034); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/switchMap.js -var switchMap = __webpack_require__(4978); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/merge.js -var merge = __webpack_require__(3071); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/empty.js -var empty = __webpack_require__(1545); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/skipWhile.js - - -function skipWhile(predicate) { - return (0,lift/* operate */.e)(function (source, subscriber) { - var taking = false; - var index = 0; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { return (taking || (taking = !predicate(value, index++))) && subscriber.next(value); })); - }); -} -//# sourceMappingURL=skipWhile.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/tap.js -var tap = __webpack_require__(2006); -// EXTERNAL MODULE: ./src/compat/event_listeners.ts + 1 modules -var event_listeners = __webpack_require__(1381); -;// CONCATENATED MODULE: ./src/compat/get_start_date.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Calculating a live-offseted media position necessitate to obtain first an - * offset, and then adding that offset to the wanted position. - * - * That offset is in most case present inside the Manifest file, yet in cases - * without it or without a Manifest, such as the "directfile" mode, the RxPlayer - * won't know that offset. - * - * Thankfully Safari declares a `getStartDate` method allowing to obtain that - * offset when available. This logic is mainly useful when playing HLS contents - * in directfile mode on Safari. - * @param {HTMLMediaElement} mediaElement - * @returns {number|undefined} - */ -function getStartDate(mediaElement) { - var _mediaElement = mediaElement; - if (typeof _mediaElement.getStartDate === "function") { - var startDate = _mediaElement.getStartDate(); - if (typeof startDate === "object" && startDate !== null) { - var startDateNum = +startDate; - if (!isNaN(startDateNum)) { - return startDateNum / 1000; - } - } else if (typeof startDate === "number" && !isNaN(startDate)) { - return startDate; - } - } -} -;// CONCATENATED MODULE: ./src/compat/fullscreen.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Request fullScreen action on a given element. - * @param {HTMLElement} elt - */ -function requestFullscreen(element) { - if (!fullscreen_isFullscreen()) { - var elt = element; - /* eslint-disable @typescript-eslint/unbound-method */ - if (typeof elt.requestFullscreen === "function") { - /* eslint-enable @typescript-eslint/unbound-method */ - /* eslint-disable @typescript-eslint/no-floating-promises */ - elt.requestFullscreen(); - /* eslint-enable @typescript-eslint/no-floating-promises */ - } else if (typeof elt.msRequestFullscreen === "function") { - elt.msRequestFullscreen(); - } else if (typeof elt.mozRequestFullScreen === "function") { - elt.mozRequestFullScreen(); - } else if (typeof elt.webkitRequestFullscreen === "function") { - elt.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); - } - } -} -/** - * Exit fullscreen if an element is currently in fullscreen. - */ -function fullscreen_exitFullscreen() { - if (fullscreen_isFullscreen()) { - var doc = document; - /* eslint-disable @typescript-eslint/unbound-method */ - if (typeof doc.exitFullscreen === "function") { - /* eslint-enable @typescript-eslint/unbound-method */ - /* eslint-disable @typescript-eslint/no-floating-promises */ - doc.exitFullscreen(); - /* eslint-enable @typescript-eslint/no-floating-promises */ - } else if (typeof doc.msExitFullscreen === "function") { - doc.msExitFullscreen(); - } else if (typeof doc.mozCancelFullScreen === "function") { - doc.mozCancelFullScreen(); - } else if (typeof doc.webkitExitFullscreen === "function") { - doc.webkitExitFullscreen(); - } - } -} -/** - * Returns true if an element in the document is being displayed in fullscreen - * mode; - * otherwise it's false. - * @returns {boolean} - */ -function fullscreen_isFullscreen() { - var doc = document; - return doc.fullscreenElement != null || doc.mozFullScreenElement != null || doc.webkitFullscreenElement != null || doc.msFullscreenElement != null; -} - -// EXTERNAL MODULE: ./src/compat/browser_detection.ts -var browser_detection = __webpack_require__(3666); -// EXTERNAL MODULE: ./src/log.ts + 1 modules -var log = __webpack_require__(3887); -;// CONCATENATED MODULE: ./src/compat/browser_version.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -/** - * Returns either : - * - 'null' when the current browser is not Firefox. - * - '-1' when it is impossible to get the Firefox version - * - A number above 0 that is the Firefox version number - * @returns {number|null} - */ -function getFirefoxVersion() { - if (!browser_detection/* isFirefox */.vU) { - log/* default.warn */.Z.warn("Compat: Can't access Firefox version on no firefox browser."); - return null; - } - var userAgent = navigator.userAgent; - var match = /Firefox\/([0-9]+)\./.exec(userAgent); - if (match === null) { - return -1; - } - var result = parseInt(match[1], 10); - if (isNaN(result)) { - return -1; - } - return result; -} - -;// CONCATENATED MODULE: ./src/compat/can_rely_on_video_visibility_and_size.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -/** - * This functions tells if the RxPlayer can trust on any browser data - * about video element visibility and size. - * - * On Firefox (version >= 67) : - * - The PIP feature exists but can be disabled by default according - * to the OS and the channel used for updating / getting Firefox binaries. - * - There is no API to know if the Picture-in-picture (PIP) is enabled - * - There is no API to get the width of the PIP window - * - * The element clientWidth tells the width of the original video element, and - * no PIP window API exists to determine its presence or width. Thus, there are - * no way to determine the real width of the video window, as we can't know when - * the PIP feature or window is enabled, and we can't have access to the windo - * size information. - * - * Moreover, when the document is considered as hidden (e.g. in case of hidden - * tab), as there is no way to know if the PIP feature or window is enabled, - * we can't know if the video window is visible or not. - * @returns {boolean} - */ -function canRelyOnVideoVisibilityAndSize() { - if (!browser_detection/* isFirefox */.vU) { - return true; - } - var firefoxVersion = getFirefoxVersion(); - if (firefoxVersion === null || firefoxVersion < 67) { - return true; - } - var proto = HTMLVideoElement === null || HTMLVideoElement === void 0 ? void 0 : HTMLVideoElement.prototype; - return (proto === null || proto === void 0 ? void 0 : proto.requirePictureInPicture) !== undefined; -} -// EXTERNAL MODULE: ./src/config.ts + 2 modules -var config = __webpack_require__(6872); -// EXTERNAL MODULE: ./src/errors/media_error.ts -var media_error = __webpack_require__(3714); -// EXTERNAL MODULE: ./src/errors/format_error.ts -var format_error = __webpack_require__(8750); -// EXTERNAL MODULE: ./src/errors/error_codes.ts -var error_codes = __webpack_require__(5992); -// EXTERNAL MODULE: ./src/features/index.ts -var features = __webpack_require__(7874); -// EXTERNAL MODULE: ./src/manifest/index.ts + 6 modules -var manifest = __webpack_require__(1989); -// EXTERNAL MODULE: ./src/utils/are_arrays_of_numbers_equal.ts -var are_arrays_of_numbers_equal = __webpack_require__(4791); -// EXTERNAL MODULE: ./src/utils/event_emitter.ts -var event_emitter = __webpack_require__(1959); -// EXTERNAL MODULE: ./src/utils/is_null_or_undefined.ts -var is_null_or_undefined = __webpack_require__(1946); -// EXTERNAL MODULE: ./src/utils/object_assign.ts -var object_assign = __webpack_require__(8026); -// EXTERNAL MODULE: ./src/utils/ranges.ts -var ranges = __webpack_require__(2829); -// EXTERNAL MODULE: ./src/utils/reference.ts -var reference = __webpack_require__(5095); -// EXTERNAL MODULE: ./src/utils/task_canceller.ts -var task_canceller = __webpack_require__(288); -// EXTERNAL MODULE: ./src/utils/warn_once.ts -var warn_once = __webpack_require__(8806); -// EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js -var asyncToGenerator = __webpack_require__(5861); -// EXTERNAL MODULE: ./node_modules/@babel/runtime/regenerator/index.js -var regenerator = __webpack_require__(4687); -var regenerator_default = /*#__PURE__*/__webpack_require__.n(regenerator); -// EXTERNAL MODULE: ./src/compat/eme/custom_media_keys/index.ts + 7 modules -var custom_media_keys = __webpack_require__(6139); -// EXTERNAL MODULE: ./src/core/decrypt/utils/media_keys_infos_store.ts -var media_keys_infos_store = __webpack_require__(770); -;// CONCATENATED MODULE: ./src/core/decrypt/dispose_decryption_resources.ts - - -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -/** - * Free up all ressources taken by the content decryption logic. - * @param {HTMLMediaElement} mediaElement - * @returns {Promise} - */ -function disposeDecryptionResources(_x) { - return _disposeDecryptionResources.apply(this, arguments); -} -function _disposeDecryptionResources() { - _disposeDecryptionResources = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(mediaElement) { - var currentState, loadedSessionsStore; - return regenerator_default().wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - currentState = media_keys_infos_store/* default.getState */.Z.getState(mediaElement); - if (!(currentState === null)) { - _context.next = 3; - break; - } - return _context.abrupt("return"); - case 3: - log/* default.info */.Z.info("DRM: Disposing of the current MediaKeys"); - loadedSessionsStore = currentState.loadedSessionsStore; - media_keys_infos_store/* default.clearState */.Z.clearState(mediaElement); - _context.next = 8; - return loadedSessionsStore.closeAllSessions(); - case 8: - (0,custom_media_keys/* setMediaKeys */.Y)(mediaElement, null); - case 9: - case "end": - return _context.stop(); - } - } - }, _callee); - })); - return _disposeDecryptionResources.apply(this, arguments); -} -;// CONCATENATED MODULE: ./src/core/decrypt/get_current_key_system.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Returns the name of the current key system used. - * @param {HTMLMediaElement} mediaElement - * @returns {string|null} - */ -function get_current_key_system_getCurrentKeySystem(mediaElement) { - var currentState = media_keys_infos_store/* default.getState */.Z.getState(mediaElement); - return currentState == null ? null : currentState.keySystemOptions.type; -} -;// CONCATENATED MODULE: ./src/compat/should_unset_media_keys.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Returns true if the mediakeys associated to a media element should be - * unset once the content is stopped. - * Depends on the target. - * @returns {Boolean} - */ -function shouldUnsetMediaKeys() { - return browser_detection/* isIE11 */.fq; -} -;// CONCATENATED MODULE: ./src/core/decrypt/clear_on_stop.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - -/** - * Clear DRM-related resources that should be cleared when the current content - * stops its playback. - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function clearOnStop(mediaElement) { - log/* default.info */.Z.info("DRM: Clearing-up DRM session."); - if (shouldUnsetMediaKeys()) { - log/* default.info */.Z.info("DRM: disposing current MediaKeys."); - return disposeDecryptionResources(mediaElement); - } - var currentState = media_keys_infos_store/* default.getState */.Z.getState(mediaElement); - if (currentState !== null && currentState.keySystemOptions.closeSessionsOnStop === true) { - log/* default.info */.Z.info("DRM: closing all current sessions."); - return currentState.loadedSessionsStore.closeAllSessions(); - } - log/* default.info */.Z.info("DRM: Nothing to clear. Returning right away. No state =", currentState === null); - return Promise.resolve(); -} -// EXTERNAL MODULE: ./src/utils/assert.ts -var assert = __webpack_require__(811); -// EXTERNAL MODULE: ./src/errors/request_error.ts -var request_error = __webpack_require__(9105); -// EXTERNAL MODULE: ./src/errors/network_error.ts -var network_error = __webpack_require__(9362); -;// CONCATENATED MODULE: ./src/core/fetchers/utils/error_selector.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Generate a new error from the infos given. - * @param {string} code - * @param {Error} error - * @returns {Error} - */ -function errorSelector(error) { - if (error instanceof request_error/* default */.Z) { - return new network_error/* default */.Z("PIPELINE_LOAD_ERROR", error); - } - return (0,format_error/* default */.Z)(error, { - defaultCode: "PIPELINE_LOAD_ERROR", - defaultReason: "Unknown error when fetching the Manifest" - }); -} -;// CONCATENATED MODULE: ./src/compat/is_offline.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Some browsers have a builtin API to know if it's connected at least to a - * LAN network, at most to the internet. - * - * /!\ This feature can be dangerous as you can both have false positives and - * false negatives. - * - * False positives: - * - you can still play local contents (on localhost) if isOffline == true - * - on some browsers isOffline might be true even if we're connected to a LAN - * or a router (it would mean we're just not able to connect to the - * Internet). So we can eventually play LAN contents if isOffline == true - * - * False negatives: - * - in some cases, we even might have isOffline at false when we do not have - * any connection: - * - in browsers that do not support the feature - * - in browsers running in some virtualization softwares where the - * network adapters are always connected. - * - * Use with these cases in mind. - * @returns {Boolean} - */ -function isOffline() { - /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ - return navigator.onLine === false; - /* eslint-enable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -} -// EXTERNAL MODULE: ./src/errors/custom_loader_error.ts -var custom_loader_error = __webpack_require__(7839); -// EXTERNAL MODULE: ./src/errors/is_known_error.ts -var is_known_error = __webpack_require__(9822); -// EXTERNAL MODULE: ./src/utils/cancellable_sleep.ts -var cancellable_sleep = __webpack_require__(7864); -// EXTERNAL MODULE: ./src/utils/get_fuzzed_delay.ts -var get_fuzzed_delay = __webpack_require__(2572); -// EXTERNAL MODULE: ./src/utils/noop.ts -var noop = __webpack_require__(8894); -;// CONCATENATED MODULE: ./src/core/fetchers/utils/schedule_request.ts - - -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - - - - -/** - * Called on a loader error. - * Returns whether the loader request should be retried. - * - * TODO the notion of retrying or not could be transport-specific (e.g. 412 are - * mainly used for Smooth contents) and thus as part of the transport code (e.g. - * by rejecting with an error always having a `canRetry` property?). - * Or not, to ponder. - * - * @param {Error} error - * @returns {Boolean} - If true, the request can be retried. - */ -function shouldRetry(error) { - if (error instanceof request_error/* default */.Z) { - if (error.type === error_codes/* NetworkErrorTypes.ERROR_HTTP_CODE */.br.ERROR_HTTP_CODE) { - return error.status >= 500 || error.status === 404 || error.status === 415 || - // some CDN seems to use that code when - // requesting low-latency segments too much - // in advance - error.status === 412; - } - return error.type === error_codes/* NetworkErrorTypes.TIMEOUT */.br.TIMEOUT || error.type === error_codes/* NetworkErrorTypes.ERROR_EVENT */.br.ERROR_EVENT; - } else if (error instanceof custom_loader_error/* default */.Z) { - if (typeof error.canRetry === "boolean") { - return error.canRetry; - } - if (error.xhr !== undefined) { - return error.xhr.status >= 500 || error.xhr.status === 404 || error.xhr.status === 415 || - // some CDN seems to use that code when - // requesting low-latency segments too much - // in advance - error.xhr.status === 412; - } - return false; - } - return (0,is_known_error/* default */.Z)(error) && error.code === "INTEGRITY_ERROR"; -} -/** - * Returns true if we're pretty sure that the current error is due to the - * user being offline. - * @param {Error} error - * @returns {Boolean} - */ -function isOfflineRequestError(error) { - if (error instanceof request_error/* default */.Z) { - return error.type === error_codes/* NetworkErrorTypes.ERROR_EVENT */.br.ERROR_EVENT && isOffline(); - } else if (error instanceof custom_loader_error/* default */.Z) { - return error.isOfflineError; - } - return false; // under doubt, return false -} -/** - * Guess the type of error obtained. - * @param {*} error - * @returns {number} - */ -function getRequestErrorType(error) { - return isOfflineRequestError(error) ? 2 /* REQUEST_ERROR_TYPES.Offline */ : 1 /* REQUEST_ERROR_TYPES.Regular */; -} -/** - * Specific algorithm used to perform segment and manifest requests. - * - * Here how it works: - * - * 1. You give it one or multiple of the CDN available for the resource you - * want to request (from the most important one to the least important), - * a callback doing the request with the chosen CDN in argument, and some - * options. - * - * 2. it tries to call the request callback with the most prioritized CDN - * first: - * - if it works as expected, it resolves the returned Promise with that - * request's response. - * - if it fails, it calls ther `onRetry` callback given with the - * corresponding error, un-prioritize that CDN and try with the new - * most prioritized CDN. - * - * Each CDN might be retried multiple times, depending on the nature of the - * error and the Configuration given. - * - * Multiple retries of the same CDN are done after a delay to avoid - * overwhelming it, this is what we call a "backoff". That delay raises - * exponentially as multiple consecutive errors are encountered on this - * CDN. - * - * @param {Array.|null} cdns - The different CDN on which the - * wanted resource is available. `scheduleRequestWithCdns` will call the - * `performRequest` callback with the right element from that array if different - * from `null`. - * - * Can be set to `null` when that resource is not reachable through a CDN, in - * which case the `performRequest` callback may be called with `null`. - * @param {Object|null} cdnPrioritizer - Interface allowing to give the priority - * between multiple CDNs. - * @param {Function} performRequest - Callback implementing the request in - * itself. Resolving when the resource request succeed and rejecting with the - * corresponding error when the request failed. - * @param {Object} options - Configuration allowing to tweak the number on which - * the algorithm behind `scheduleRequestWithCdns` bases itself. - * @param {Object} cancellationSignal - CancellationSignal allowing to cancel - * the logic of `scheduleRequestWithCdns`. - * To trigger if the resource is not needed anymore. - * @returns {Promise} - Promise resolving, with the corresponding - * `performRequest`'s data, when the resource request succeed and rejecting in - * the following scenarios: - * - `scheduleRequestWithCdns` has been cancelled due to `cancellationSignal` - * being triggered. In that case a `CancellationError` is thrown. - * - * - The resource request(s) failed and will not be retried anymore. - */ -function scheduleRequestWithCdns(_x, _x2, _x3, _x4, _x5) { - return _scheduleRequestWithCdns.apply(this, arguments); -} -/** - * Lightweight version of the request algorithm, this time with only a simple - * Promise given. - * @param {Function} performRequest - * @param {Object} options - * @returns {Promise} - */ -function _scheduleRequestWithCdns() { - _scheduleRequestWithCdns = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee3(cdns, cdnPrioritizer, performRequest, options, cancellationSignal) { - var baseDelay, maxDelay, maxRetryRegular, maxRetryOffline, onRetry, missedAttempts, initialCdnToRequest, getCdnToRequest, requestCdn, _requestCdn, retryWithNextCdn, _retryWithNextCdn, waitPotentialBackoffAndRequest, getPrioritaryRequestableCdnFromSortedList; - return regenerator_default().wrap(function _callee3$(_context3) { - while (1) { - switch (_context3.prev = _context3.next) { - case 0: - getPrioritaryRequestableCdnFromSortedList = function _getPrioritaryRequest(sortedCdns) { - var _a; - if (missedAttempts.size === 0) { - return sortedCdns[0]; - } - var now = performance.now(); - return (_a = sortedCdns.filter(function (c) { - var _a; - return ((_a = missedAttempts.get(c)) === null || _a === void 0 ? void 0 : _a.isBlacklisted) !== true; - }).reduce(function (acc, x) { - var _a; - var blockedUntil = (_a = missedAttempts.get(x)) === null || _a === void 0 ? void 0 : _a.blockedUntil; - if (blockedUntil !== undefined && blockedUntil <= now) { - blockedUntil = undefined; - } - if (acc === undefined) { - return [x, blockedUntil]; - } - if (blockedUntil === undefined) { - if (acc[1] === undefined) { - return acc; - } - return [x, undefined]; - } - return acc[1] === undefined ? acc : blockedUntil < acc[1] ? [x, blockedUntil] : acc; - }, undefined)) === null || _a === void 0 ? void 0 : _a[0]; - }; - waitPotentialBackoffAndRequest = function _waitPotentialBackoff(nextWantedCdn, prevRequestError) { - var nextCdnAttemptObj = missedAttempts.get(nextWantedCdn); - if (nextCdnAttemptObj === undefined || nextCdnAttemptObj.blockedUntil === undefined) { - return requestCdn(nextWantedCdn); - } - var now = performance.now(); - var blockedFor = nextCdnAttemptObj.blockedUntil - now; - if (blockedFor <= 0) { - return requestCdn(nextWantedCdn); - } - var canceller = new task_canceller/* default */.ZP({ - cancelOn: cancellationSignal - }); - return new Promise(function (res, rej) { - /* eslint-disable-next-line @typescript-eslint/no-misused-promises */ - cdnPrioritizer === null || cdnPrioritizer === void 0 ? void 0 : cdnPrioritizer.addEventListener("priorityChange", function () { - var updatedPrioritaryCdn = getCdnToRequest(); - if (cancellationSignal.isCancelled) { - throw cancellationSignal.cancellationError; - } - if (updatedPrioritaryCdn === undefined) { - return rej(prevRequestError); - } - if (updatedPrioritaryCdn !== nextWantedCdn) { - canceller.cancel(); - waitPotentialBackoffAndRequest(updatedPrioritaryCdn, prevRequestError).then(res, rej); - } - }, canceller.signal); - (0,cancellable_sleep/* default */.Z)(blockedFor, canceller.signal).then(function () { - return requestCdn(nextWantedCdn).then(res, rej); - }, noop/* default */.Z); - }); - }; - _retryWithNextCdn = function _retryWithNextCdn3() { - _retryWithNextCdn = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee2(prevRequestError) { - var nextCdn; - return regenerator_default().wrap(function _callee2$(_context2) { - while (1) { - switch (_context2.prev = _context2.next) { - case 0: - nextCdn = getCdnToRequest(); - if (!cancellationSignal.isCancelled) { - _context2.next = 3; - break; - } - throw cancellationSignal.cancellationError; - case 3: - if (!(nextCdn === undefined)) { - _context2.next = 5; - break; - } - throw prevRequestError; - case 5: - onRetry(prevRequestError); - if (!cancellationSignal.isCancelled) { - _context2.next = 8; - break; - } - throw cancellationSignal.cancellationError; - case 8: - return _context2.abrupt("return", waitPotentialBackoffAndRequest(nextCdn, prevRequestError)); - case 9: - case "end": - return _context2.stop(); - } - } - }, _callee2); - })); - return _retryWithNextCdn.apply(this, arguments); - }; - retryWithNextCdn = function _retryWithNextCdn2(_x7) { - return _retryWithNextCdn.apply(this, arguments); - }; - _requestCdn = function _requestCdn3() { - _requestCdn = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(cdn) { - var res, currentErrorType, missedAttemptsObj, maxRetry, errorCounter, delay, fuzzedDelay; - return regenerator_default().wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - _context.prev = 0; - _context.next = 3; - return performRequest(cdn, cancellationSignal); - case 3: - res = _context.sent; - return _context.abrupt("return", res); - case 7: - _context.prev = 7; - _context.t0 = _context["catch"](0); - if (!task_canceller/* default.isCancellationError */.ZP.isCancellationError(_context.t0)) { - _context.next = 11; - break; - } - throw _context.t0; - case 11: - if (cdn !== null && cdnPrioritizer !== null) { - // We failed requesting the resource on this CDN. - // Globally give priority to the next CDN through the CdnPrioritizer. - cdnPrioritizer.downgradeCdn(cdn); - } - currentErrorType = getRequestErrorType(_context.t0); - missedAttemptsObj = missedAttempts.get(cdn); - if (missedAttemptsObj === undefined) { - missedAttemptsObj = { - errorCounter: 1, - lastErrorType: currentErrorType, - blockedUntil: undefined, - isBlacklisted: false - }; - missedAttempts.set(cdn, missedAttemptsObj); - } else { - if (currentErrorType !== missedAttemptsObj.lastErrorType) { - missedAttemptsObj.errorCounter = 1; - missedAttemptsObj.lastErrorType = currentErrorType; - } else { - missedAttemptsObj.errorCounter++; - } - } - if (shouldRetry(_context.t0)) { - _context.next = 19; - break; - } - missedAttemptsObj.blockedUntil = undefined; - missedAttemptsObj.isBlacklisted = true; - return _context.abrupt("return", retryWithNextCdn(_context.t0)); - case 19: - maxRetry = currentErrorType === 2 /* REQUEST_ERROR_TYPES.Offline */ ? maxRetryOffline : maxRetryRegular; - if (missedAttemptsObj.errorCounter > maxRetry) { - missedAttemptsObj.blockedUntil = undefined; - missedAttemptsObj.isBlacklisted = true; - } else { - errorCounter = missedAttemptsObj.errorCounter; - delay = Math.min(baseDelay * Math.pow(2, errorCounter - 1), maxDelay); - fuzzedDelay = (0,get_fuzzed_delay/* default */.Z)(delay); - missedAttemptsObj.blockedUntil = performance.now() + fuzzedDelay; - } - return _context.abrupt("return", retryWithNextCdn(_context.t0)); - case 22: - case "end": - return _context.stop(); - } - } - }, _callee, null, [[0, 7]]); - })); - return _requestCdn.apply(this, arguments); - }; - requestCdn = function _requestCdn2(_x6) { - return _requestCdn.apply(this, arguments); - }; - getCdnToRequest = function _getCdnToRequest() { - if (cdns === null) { - var nullAttemptObject = missedAttempts.get(null); - if (nullAttemptObject !== undefined && nullAttemptObject.isBlacklisted) { - return undefined; - } - return null; - } else if (cdnPrioritizer === null) { - return getPrioritaryRequestableCdnFromSortedList(cdns); - } else { - var prioritized = cdnPrioritizer.getCdnPreferenceForResource(cdns); - return getPrioritaryRequestableCdnFromSortedList(prioritized); - } - }; - if (!(cancellationSignal.cancellationError !== null)) { - _context3.next = 9; - break; - } - return _context3.abrupt("return", Promise.reject(cancellationSignal.cancellationError)); - case 9: - baseDelay = options.baseDelay, maxDelay = options.maxDelay, maxRetryRegular = options.maxRetryRegular, maxRetryOffline = options.maxRetryOffline, onRetry = options.onRetry; - if (cdns !== null && cdns.length === 0) { - log/* default.warn */.Z.warn("Fetchers: no CDN given to `scheduleRequestWithCdns`."); - } - missedAttempts = new Map(); - initialCdnToRequest = getCdnToRequest(); - if (!(initialCdnToRequest === undefined)) { - _context3.next = 15; - break; - } - throw new Error("No CDN to request"); - case 15: - return _context3.abrupt("return", requestCdn(initialCdnToRequest)); - case 16: - case "end": - return _context3.stop(); - } - } - }, _callee3); - })); - return _scheduleRequestWithCdns.apply(this, arguments); -} -function scheduleRequestPromise(performRequest, options, cancellationSignal) { - // same than for a single unknown CDN - return scheduleRequestWithCdns(null, null, performRequest, options, cancellationSignal); -} -;// CONCATENATED MODULE: ./src/core/fetchers/manifest/manifest_fetcher.ts - + return NetworkAnalyzer; +}(); +;// CONCATENATED MODULE: ./src/core/adaptive/guess_based_chooser.ts function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } @@ -42784,337 +39080,224 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len - - - - - - /** - * Class allowing to facilitate the task of loading and parsing a Manifest. - * @class ManifestFetcher - * @example - * ```js - * const manifestFetcher = new ManifestFetcher(manifestUrl, pipelines, options); - * manifestFetcher.fetch().pipe( - * // Filter only responses (might also receive warning events) - * filter((evt) => evt.type === "response"); - * // Parse the Manifest - * mergeMap(res => res.parse({ externalClockOffset })) - * // (again) - * filter((evt) => evt.type === "parsed"); - * ).subscribe(({ value }) => { - * console.log("Manifest:", value.manifest); - * }); - * ``` + * Estimate which Representation should be played based on risky "guesses". + * + * Basically, this `GuessBasedChooser` will attempt switching to the superior + * quality when conditions allows this and then check if we're able to maintain + * this quality. If we're not, it will rollbacks to the previous, maintaninable, + * guess. + * + * The algorithm behind the `GuessBasedChooser` is very risky in terms of + * rebuffering chances. As such, it should only be used when other approach + * don't work (e.g. low-latency contents). + * @class GuessBasedChooser */ -var ManifestFetcher = /*#__PURE__*/function () { +var GuessBasedChooser = /*#__PURE__*/function () { /** - * Construct a new ManifestFetcher. - * @param {string | undefined} url - Default Manifest url, will be used when - * no URL is provided to the `fetch` function. - * `undefined` if unknown or if a Manifest should be retrieved through other - * means than an HTTP request. - * @param {Object} pipelines - Transport pipelines used to perform the - * Manifest loading and parsing operations. - * @param {Object} settings - Configure the `ManifestFetcher`. + * Create a new `GuessBasedChooser`. + * @param {Object} scoreCalculator + * @param {Object} prevEstimate */ - function ManifestFetcher(url, pipelines, settings) { - this._manifestUrl = url; - this._pipelines = pipelines.manifest; - this._settings = settings; + function GuessBasedChooser(scoreCalculator, prevEstimate) { + this._scoreCalculator = scoreCalculator; + this._lastAbrEstimate = prevEstimate; + this._consecutiveWrongGuesses = 0; + this._blockGuessesUntil = 0; + this._lastMaintanableBitrate = null; } /** - * (re-)Load the Manifest. - * This method does not yet parse it, parsing will then be available through - * a callback available on the response. - * - * You can set an `url` on which that Manifest will be requested. - * If not set, the regular Manifest url - defined on the `ManifestFetcher` - * instanciation - will be used instead. + * Perform a "guess", which basically indicates which Representation should be + * chosen according to the `GuessBasedChooser`. * - * @param {string} [url] - * @returns {Observable} + * @param {Array.} representations - Array of all Representation the + * GuessBasedChooser can choose from, sorted by bitrate ascending. + * /!\ It is very important that Representation in that Array are sorted by + * bitrate ascending for this method to work as intented. + * @param {Object} observation - Last playback observation performed. + * @param {Object} currentRepresentation - The Representation currently + * loading. + * @param {number} incomingBestBitrate - The bitrate of the Representation + * chosen by the more optimistic of the other ABR algorithms currently. + * @param {Array.} requests - Information on all pending requests. + * @returns {Object|null} - If a guess is made, return that guess, else + * returns `null` (in which case you should fallback to another ABR + * algorithm). */ - var _proto = ManifestFetcher.prototype; - _proto.fetch = function fetch(url) { - var _this = this; - return new Observable/* Observable */.y(function (obs) { - var settings = _this._settings; - var pipelines = _this._pipelines; - var requestUrl = url !== null && url !== void 0 ? url : _this._manifestUrl; - /** `true` if the loading pipeline is already completely executed. */ - var hasFinishedLoading = false; - /** Allows to cancel the loading operation. */ - var canceller = new task_canceller/* default */.ZP(); - var backoffSettings = _this._getBackoffSetting(function (err) { - obs.next({ - type: "warning", - value: errorSelector(err) - }); - }); - var loadingPromise = pipelines.resolveManifestUrl === undefined ? callLoaderWithRetries(requestUrl) : callResolverWithRetries(requestUrl).then(callLoaderWithRetries); - loadingPromise.then(function (response) { - hasFinishedLoading = true; - obs.next({ - type: "response", - parse: function parse(parserOptions) { - return _this._parseLoadedManifest(response, parserOptions); - } - }); - obs.complete(); - })["catch"](function (err) { - if (canceller.isUsed) { - // Cancellation has already been handled by RxJS - return; + var _proto = GuessBasedChooser.prototype; + _proto.getGuess = function getGuess(representations, observation, currentRepresentation, incomingBestBitrate, requests) { + var bufferGap = observation.bufferGap, + speed = observation.speed; + var lastChosenRep = this._lastAbrEstimate.representation; + if (lastChosenRep === null) { + return null; // There's nothing to base our guess on + } + + if (incomingBestBitrate > lastChosenRep.bitrate) { + // ABR estimates are already superior or equal to the guess + // we'll be doing here, so no need to guess + if (this._lastAbrEstimate.algorithmType === 2 /* ABRAlgorithmType.GuessBased */) { + if (this._lastAbrEstimate.representation !== null) { + this._lastMaintanableBitrate = this._lastAbrEstimate.representation.bitrate; } - hasFinishedLoading = true; - obs.error(errorSelector(err)); - }); - return function () { - if (!hasFinishedLoading) { - canceller.cancel(); + this._consecutiveWrongGuesses = 0; + } + return null; + } + var scoreData = this._scoreCalculator.getEstimate(currentRepresentation); + if (this._lastAbrEstimate.algorithmType !== 2 /* ABRAlgorithmType.GuessBased */) { + if (scoreData === undefined) { + return null; // not enough information to start guessing + } + + if (this._canGuessHigher(bufferGap, speed, scoreData)) { + var nextRepresentation = getNextRepresentation(representations, currentRepresentation); + if (nextRepresentation !== null) { + return nextRepresentation; } - }; - /** - * Call the resolver part of the pipeline, retrying if it fails according - * to the current settings. - * Returns the Promise of the last attempt. - * /!\ This pipeline should have a `resolveManifestUrl` function defined. - * @param {string | undefined} resolverUrl - * @returns {Promise} - */ - function callResolverWithRetries(resolverUrl) { - var resolveManifestUrl = pipelines.resolveManifestUrl; - (0,assert/* default */.Z)(resolveManifestUrl !== undefined); - var callResolver = function callResolver() { - return resolveManifestUrl(resolverUrl, canceller.signal); - }; - return scheduleRequestPromise(callResolver, backoffSettings, canceller.signal); } - /** - * Call the loader part of the pipeline, retrying if it fails according - * to the current settings. - * Returns the Promise of the last attempt. - * @param {string | undefined} manifestUrl - * @returns {Promise} - */ - function callLoaderWithRetries(manifestUrl) { - var loadManifest = pipelines.loadManifest; - var requestTimeout = (0,is_null_or_undefined/* default */.Z)(settings.requestTimeout) ? config/* default.getCurrent */.Z.getCurrent().DEFAULT_REQUEST_TIMEOUT : settings.requestTimeout; - if (requestTimeout < 0) { - requestTimeout = undefined; - } - var callLoader = function callLoader() { - return loadManifest(manifestUrl, { - timeout: requestTimeout - }, canceller.signal); - }; - return scheduleRequestPromise(callLoader, backoffSettings, canceller.signal); + return null; + } + // If we reached here, we're currently already in guessing mode + if (this._isLastGuessValidated(lastChosenRep, incomingBestBitrate, scoreData)) { + log/* default.debug */.Z.debug("ABR: Guessed Representation validated", lastChosenRep.bitrate); + this._lastMaintanableBitrate = lastChosenRep.bitrate; + this._consecutiveWrongGuesses = 0; + } + if (currentRepresentation.id !== lastChosenRep.id) { + return lastChosenRep; + } + var shouldStopGuess = this._shouldStopGuess(currentRepresentation, scoreData, bufferGap, requests); + if (shouldStopGuess) { + // Block guesses for a time + this._consecutiveWrongGuesses++; + this._blockGuessesUntil = performance.now() + Math.min(this._consecutiveWrongGuesses * 15000, 120000); + return getPreviousRepresentation(representations, currentRepresentation); + } else if (scoreData === undefined) { + return currentRepresentation; + } + if (this._canGuessHigher(bufferGap, speed, scoreData)) { + var _nextRepresentation = getNextRepresentation(representations, currentRepresentation); + if (_nextRepresentation !== null) { + return _nextRepresentation; } - }); + } + return currentRepresentation; } /** - * Parse an already loaded Manifest. - * - * This method should be reserved for Manifests for which no request has been - * done. - * In other cases, it's preferable to go through the `fetch` method, so - * information on the request can be used by the parsing process. - * @param {*} manifest - * @param {Object} parserOptions - * @returns {Observable} + * Returns `true` if we've enough confidence on the current situation to make + * a higher guess. + * @param {number} bufferGap + * @param {number} speed + * @param {Array} scoreData + * @returns {boolean} */; - _proto.parse = function parse(manifest, parserOptions) { - return this._parseLoadedManifest({ - responseData: manifest, - size: undefined, - requestDuration: undefined - }, parserOptions); + _proto._canGuessHigher = function _canGuessHigher(bufferGap, speed, _ref) { + var score = _ref[0], + scoreConfidenceLevel = _ref[1]; + return isFinite(bufferGap) && bufferGap >= 2.5 && performance.now() > this._blockGuessesUntil && scoreConfidenceLevel === 1 /* ScoreConfidenceLevel.HIGH */ && score / speed > 1.01; } /** - * Parse a Manifest. - * - * @param {Object} loaded - Information about the loaded Manifest as well as - * about the corresponding request. - * @param {Object} parserOptions - Options used when parsing the Manifest. - * @returns {Observable} + * Returns `true` if the pending guess of `lastGuess` seems to not + * be maintainable and as such should be stopped. + * @param {Object} lastGuess + * @param {Array} scoreData + * @param {number} bufferGap + * @param {Array.} requests + * @returns {boolean} */; - _proto._parseLoadedManifest = function _parseLoadedManifest(loaded, parserOptions) { - var _this2 = this; - return new Observable/* Observable */.y(function (obs) { - var parsingTimeStart = performance.now(); - var canceller = new task_canceller/* default */.ZP(); - var sendingTime = loaded.sendingTime, - receivedTime = loaded.receivedTime; - var backoffSettings = _this2._getBackoffSetting(function (err) { - obs.next({ - type: "warning", - value: errorSelector(err) - }); - }); - var opts = { - externalClockOffset: parserOptions.externalClockOffset, - unsafeMode: parserOptions.unsafeMode, - previousManifest: parserOptions.previousManifest, - originalUrl: _this2._manifestUrl - }; - try { - var res = _this2._pipelines.parseManifest(loaded, opts, onWarnings, canceller.signal, scheduleRequest); - if (!isPromise(res)) { - emitManifestAndComplete(res.manifest); - } else { - res.then(function (_ref) { - var manifest = _ref.manifest; - return emitManifestAndComplete(manifest); - })["catch"](function (err) { - if (canceller.isUsed) { - // Cancellation is already handled by RxJS - return; - } - emitError(err, true); - }); - } - } catch (err) { - if (canceller.isUsed) { - // Cancellation is already handled by RxJS - return undefined; - } - emitError(err, true); - } - return function () { - canceller.cancel(); - }; - /** - * Perform a request with the same retry mechanisms and error handling - * than for a Manifest loader. - * @param {Function} performRequest - * @returns {Function} - */ - function scheduleRequest(_x) { - return _scheduleRequest.apply(this, arguments); - } - /** - * Handle minor errors encountered by a Manifest parser. - * @param {Array.} warnings - */ - function _scheduleRequest() { - _scheduleRequest = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(performRequest) { - var data; - return regenerator_default().wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - _context.prev = 0; - _context.next = 3; - return scheduleRequestPromise(performRequest, backoffSettings, canceller.signal); - case 3: - data = _context.sent; - return _context.abrupt("return", data); - case 7: - _context.prev = 7; - _context.t0 = _context["catch"](0); - throw errorSelector(_context.t0); - case 10: - case "end": - return _context.stop(); - } - } - }, _callee, null, [[0, 7]]); - })); - return _scheduleRequest.apply(this, arguments); - } - function onWarnings(warnings) { - for (var _iterator = _createForOfIteratorHelperLoose(warnings), _step; !(_step = _iterator()).done;) { - var warning = _step.value; - if (canceller.isUsed) { - return; - } - emitError(warning, false); + _proto._shouldStopGuess = function _shouldStopGuess(lastGuess, scoreData, bufferGap, requests) { + if (scoreData !== undefined && scoreData[0] < 1.01) { + return true; + } else if ((scoreData === undefined || scoreData[0] < 1.2) && bufferGap < 0.6) { + return true; + } + var guessedRepresentationRequests = requests.filter(function (req) { + return req.content.representation.id === lastGuess.id; + }); + var now = performance.now(); + for (var _iterator = _createForOfIteratorHelperLoose(guessedRepresentationRequests), _step; !(_step = _iterator()).done;) { + var req = _step.value; + var requestElapsedTime = now - req.requestTimestamp; + if (req.content.segment.isInit) { + if (requestElapsedTime > 1000) { + return true; } - } - /** - * Emit a formatted "parsed" event through `obs`. - * To call once the Manifest has been parsed. - * @param {Object} manifest - */ - function emitManifestAndComplete(manifest) { - onWarnings(manifest.contentWarnings); - var parsingTime = performance.now() - parsingTimeStart; - log/* default.info */.Z.info("MF: Manifest parsed in " + parsingTime + "ms"); - obs.next({ - type: "parsed", - manifest: manifest, - sendingTime: sendingTime, - receivedTime: receivedTime, - parsingTime: parsingTime - }); - obs.complete(); - } - /** - * Format the given Error and emit it through `obs`. - * Either through a `"warning"` event, if `isFatal` is `false`, or through - * a fatal Observable error, if `isFatal` is set to `true`. - * @param {*} err - * @param {boolean} isFatal - */ - function emitError(err, isFatal) { - var formattedError = (0,format_error/* default */.Z)(err, { - defaultCode: "PIPELINE_PARSE_ERROR", - defaultReason: "Unknown error when parsing the Manifest" - }); - if (isFatal) { - obs.error(formattedError); - } else { - obs.next({ - type: "warning", - value: formattedError - }); + } else if (requestElapsedTime > req.content.segment.duration * 1000 + 200) { + return true; + } else { + var fastBw = estimateRequestBandwidth(req); + if (fastBw !== undefined && fastBw < lastGuess.bitrate * 0.8) { + return true; } } - }); - } - /** - * Construct "backoff settings" that can be used with a range of functions - * allowing to perform multiple request attempts - * @param {Function} onRetry - * @returns {Object} - */; - _proto._getBackoffSetting = function _getBackoffSetting(onRetry) { - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - DEFAULT_MAX_MANIFEST_REQUEST_RETRY = _config$getCurrent.DEFAULT_MAX_MANIFEST_REQUEST_RETRY, - DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE = _config$getCurrent.DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE, - INITIAL_BACKOFF_DELAY_BASE = _config$getCurrent.INITIAL_BACKOFF_DELAY_BASE, - MAX_BACKOFF_DELAY_BASE = _config$getCurrent.MAX_BACKOFF_DELAY_BASE; - var _this$_settings = this._settings, - lowLatencyMode = _this$_settings.lowLatencyMode, - ogRegular = _this$_settings.maxRetryRegular, - ogOffline = _this$_settings.maxRetryOffline; - var baseDelay = lowLatencyMode ? INITIAL_BACKOFF_DELAY_BASE.LOW_LATENCY : INITIAL_BACKOFF_DELAY_BASE.REGULAR; - var maxDelay = lowLatencyMode ? MAX_BACKOFF_DELAY_BASE.LOW_LATENCY : MAX_BACKOFF_DELAY_BASE.REGULAR; - var maxRetryRegular = ogRegular !== null && ogRegular !== void 0 ? ogRegular : DEFAULT_MAX_MANIFEST_REQUEST_RETRY; - var maxRetryOffline = ogOffline !== null && ogOffline !== void 0 ? ogOffline : DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE; - return { - onRetry: onRetry, - baseDelay: baseDelay, - maxDelay: maxDelay, - maxRetryRegular: maxRetryRegular, - maxRetryOffline: maxRetryOffline - }; + } + return false; }; - return ManifestFetcher; + _proto._isLastGuessValidated = function _isLastGuessValidated(lastGuess, incomingBestBitrate, scoreData) { + if (scoreData !== undefined && scoreData[1] === 1 /* ScoreConfidenceLevel.HIGH */ && scoreData[0] > 1.5) { + return true; + } + return incomingBestBitrate >= lastGuess.bitrate && (this._lastMaintanableBitrate === null || this._lastMaintanableBitrate < lastGuess.bitrate); + }; + return GuessBasedChooser; }(); /** - * Returns `true` when the returned value seems to be a Promise instance, as - * created by the RxPlayer. - * @param {*} val - * @returns {boolean} + * From the array of Representations given, returns the Representation with a + * bitrate immediately superior to the current one. + * Returns `null` if that "next" Representation is not found. + * + * /!\ The representations have to be already sorted by bitrate, in ascending + * order. + * @param {Array.} representations - Available representations to choose + * from, sorted by bitrate in ascending order. + * @param {Object} currentRepresentation - The Representation currently + * considered. + * @returns {Object|null} */ -function isPromise(val) { - return val instanceof Promise; +function getNextRepresentation(representations, currentRepresentation) { + var len = representations.length; + var index = (0,array_find_index/* default */.Z)(representations, function (_ref2) { + var id = _ref2.id; + return id === currentRepresentation.id; + }); + if (index < 0) { + log/* default.error */.Z.error("ABR: Current Representation not found."); + return null; + } + while (++index < len) { + if (representations[index].bitrate > currentRepresentation.bitrate) { + return representations[index]; + } + } + return null; } -;// CONCATENATED MODULE: ./src/core/fetchers/manifest/index.ts +/** + * From the array of Representations given, returns the Representation with a + * bitrate immediately inferior. + * Returns `null` if that "previous" Representation is not found. + * @param {Array.} representations + * @param {Object} currentRepresentation + * @returns {Object|null} + */ +function getPreviousRepresentation(representations, currentRepresentation) { + var index = (0,array_find_index/* default */.Z)(representations, function (_ref3) { + var id = _ref3.id; + return id === currentRepresentation.id; + }); + if (index < 0) { + log/* default.error */.Z.error("ABR: Current Representation not found."); + return null; + } + while (--index >= 0) { + if (representations[index].bitrate < currentRepresentation.bitrate) { + return representations[index]; + } + } + return null; +} +;// CONCATENATED MODULE: ./src/core/adaptive/utils/bandwidth_estimator.ts /** * Copyright 2015 CANAL+ Group * @@ -43131,12 +39314,68 @@ function isPromise(val) { * limitations under the License. */ -/* harmony default export */ var fetchers_manifest = (ManifestFetcher); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/finalize.js -var finalize = __webpack_require__(3286); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/ignoreElements.js -var ignoreElements = __webpack_require__(533); -;// CONCATENATED MODULE: ./src/compat/should_reload_media_source_on_decipherability_update.ts + +/** + * Calculate a mean bandwidth based on the bytes downloaded and the amount + * of time needed to do so. + * @class BandwidthEstimator + */ +var BandwidthEstimator = /*#__PURE__*/function () { + function BandwidthEstimator() { + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + ABR_FAST_EMA = _config$getCurrent.ABR_FAST_EMA, + ABR_SLOW_EMA = _config$getCurrent.ABR_SLOW_EMA; + this._fastEWMA = new EWMA(ABR_FAST_EMA); + this._slowEWMA = new EWMA(ABR_SLOW_EMA); + this._bytesSampled = 0; + } + /** + * Takes a bandwidth sample. + * @param {number} durationInMs - The amount of time, in milliseconds, for a + * particular request. + * @param {number} numberOfBytes - The total number of bytes transferred in + * that request. + */ + var _proto = BandwidthEstimator.prototype; + _proto.addSample = function addSample(durationInMs, numberOfBytes) { + var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), + ABR_MINIMUM_CHUNK_SIZE = _config$getCurrent2.ABR_MINIMUM_CHUNK_SIZE; + if (numberOfBytes < ABR_MINIMUM_CHUNK_SIZE) { + return; + } + var bandwidth = numberOfBytes * 8000 / durationInMs; + var weight = durationInMs / 1000; + this._bytesSampled += numberOfBytes; + this._fastEWMA.addSample(weight, bandwidth); + this._slowEWMA.addSample(weight, bandwidth); + } + /** + * Get estimate of the bandwidth, in bits per seconds. + * @returns {Number|undefined} + */; + _proto.getEstimate = function getEstimate() { + var _config$getCurrent3 = config/* default.getCurrent */.Z.getCurrent(), + ABR_MINIMUM_TOTAL_BYTES = _config$getCurrent3.ABR_MINIMUM_TOTAL_BYTES; + if (this._bytesSampled < ABR_MINIMUM_TOTAL_BYTES) { + return undefined; + } + // Take the minimum of these two estimates. + // This should have the effect of adapting down quickly, but up more slowly. + return Math.min(this._fastEWMA.getEstimate(), this._slowEWMA.getEstimate()); + } + /** Reset the bandwidth estimation. */; + _proto.reset = function reset() { + var _config$getCurrent4 = config/* default.getCurrent */.Z.getCurrent(), + ABR_FAST_EMA = _config$getCurrent4.ABR_FAST_EMA, + ABR_SLOW_EMA = _config$getCurrent4.ABR_SLOW_EMA; + this._fastEWMA = new EWMA(ABR_FAST_EMA); + this._slowEWMA = new EWMA(ABR_SLOW_EMA); + this._bytesSampled = 0; + }; + return BandwidthEstimator; +}(); + +;// CONCATENATED MODULE: ./src/core/adaptive/utils/filter_by_bitrate.ts /** * Copyright 2015 CANAL+ Group * @@ -43152,28 +39391,34 @@ var ignoreElements = __webpack_require__(533); * See the License for the specific language governing permissions and * limitations under the License. */ + /** - * Returns true if we have to reload the MediaSource due to an update in the - * decipherability status of some segments based on the current key sytem. - * - * We found that on all Widevine targets tested, a simple seek is sufficient. - * As widevine clients make a good chunk of users, we can make a difference - * between them and others as it is for the better. - * @param {string|null} currentKeySystem - * @returns {Boolean} + * Get only representations lower or equal to a given bitrate. + * If no representation is lower than the given bitrate, returns an array containing + * all Representation(s) with the lowest available bitrate. + * @param {Array.} representations - All Representations available + * @param {Number} bitrate + * @returns {Array.} */ -function shouldReloadMediaSourceOnDecipherabilityUpdate(currentKeySystem) { - return currentKeySystem === null || currentKeySystem.indexOf("widevine") < 0; +function filterByBitrate(representations, bitrate) { + if (representations.length === 0) { + return []; + } + representations.sort(function (ra, rb) { + return ra.bitrate - rb.bitrate; + }); + var minimumBitrate = representations[0].bitrate; + var bitrateCeil = Math.max(bitrate, minimumBitrate); + var firstSuperiorBitrateIndex = (0,array_find_index/* default */.Z)(representations, function (representation) { + return representation.bitrate > bitrateCeil; + }); + if (firstSuperiorBitrateIndex === -1) { + return representations; // All representations have lower bitrates. + } + + return representations.slice(0, firstSuperiorBitrateIndex); } -// EXTERNAL MODULE: ./src/utils/defer_subscriptions.ts + 5 modules -var defer_subscriptions = __webpack_require__(8333); -// EXTERNAL MODULE: ./src/utils/filter_map.ts -var filter_map = __webpack_require__(2793); -// EXTERNAL MODULE: ./src/utils/take_first_set.ts -var take_first_set = __webpack_require__(5278); -// EXTERNAL MODULE: ./src/utils/array_find_index.ts -var array_find_index = __webpack_require__(5138); -;// CONCATENATED MODULE: ./src/core/adaptive/utils/get_buffer_levels.ts +;// CONCATENATED MODULE: ./src/core/adaptive/utils/filter_by_width.ts /** * Copyright 2015 CANAL+ Group * @@ -43189,43 +39434,33 @@ var array_find_index = __webpack_require__(5138); * See the License for the specific language governing permissions and * limitations under the License. */ + + /** - * Return "Buffer Levels" which are steps of available buffers from which we - * are normally able switch safely to the next available bitrate. - * (Following an algorithm close to BOLA) - * @param {Array.} bitrates - All available bitrates, __sorted__ in - * ascending order. - * @returns {Array.} + * Filter representations based on their width: + * - the highest width considered will be the one linked to the first + * representation which has a superior width to the one given. + * @param {Array.} representations - The representations array + * @param {Number} width + * @returns {Array.} */ -function getBufferLevels(bitrates) { - var logs = bitrates.map(function (b) { - return Math.log(b / bitrates[0]); +function filterByWidth(representations, width) { + var sortedRepsByWidth = representations.slice() // clone + .sort(function (a, b) { + return (0,take_first_set/* default */.Z)(a.width, 0) - (0,take_first_set/* default */.Z)(b.width, 0); }); - var utilities = logs.map(function (l) { - return l - logs[0] + 1; - }); // normalize - var gp = (utilities[utilities.length - 1] - 1) / (bitrates.length * 2 + 10); - var Vp = 1 / gp; - return bitrates.map(function (_, i) { - return minBufferLevelForBitrate(i); + var repWithMaxWidth = (0,array_find/* default */.Z)(sortedRepsByWidth, function (representation) { + return typeof representation.width === "number" && representation.width >= width; }); - /** - * Get minimum buffer we should keep ahead to pick this bitrate. - * @param {number} index - * @returns {number} - */ - function minBufferLevelForBitrate(index) { - if (index === 0) { - return 0; - } - var boundedIndex = Math.min(Math.max(1, index), bitrates.length - 1); - if (bitrates[boundedIndex] === bitrates[boundedIndex - 1]) { - return minBufferLevelForBitrate(index - 1); - } - return Vp * (gp + (bitrates[boundedIndex] * utilities[boundedIndex - 1] - bitrates[boundedIndex - 1] * utilities[boundedIndex]) / (bitrates[boundedIndex] - bitrates[boundedIndex - 1])) + 4; + if (repWithMaxWidth === undefined) { + return representations; } + var maxWidth = typeof repWithMaxWidth.width === "number" ? repWithMaxWidth.width : 0; + return representations.filter(function (representation) { + return typeof representation.width === "number" ? representation.width <= maxWidth : true; + }); } -;// CONCATENATED MODULE: ./src/core/adaptive/buffer_based_chooser.ts +;// CONCATENATED MODULE: ./src/core/adaptive/utils/last_estimate_storage.ts /** * Copyright 2015 CANAL+ Group * @@ -43241,94 +39476,32 @@ function getBufferLevels(bitrates) { * See the License for the specific language governing permissions and * limitations under the License. */ - - - -/** - * Choose a bitrate based on the currently available buffer. - * - * This algorithm is based on a deviation of the BOLA algorithm. - * It is a hybrid solution that also relies on a given bitrate's - * "maintainability". - * Each time a chunk is downloaded, from the ratio between the chunk duration - * and chunk's request time, we can assume that the representation is - * "maintanable" or not. - * If so, we may switch to a better quality, or conversely to a worse quality. - * - * @class BufferBasedChooser - */ -var BufferBasedChooser = /*#__PURE__*/function () { - /** - * @param {Array.} bitrates - */ - function BufferBasedChooser(bitrates) { - this._levelsMap = getBufferLevels(bitrates); - this._bitrates = bitrates; - log/* default.debug */.Z.debug("ABR: Steps for buffer based chooser.", this._levelsMap.map(function (l, i) { - return "bufferLevel: " + l + ", bitrate: " + bitrates[i]; - }).join(" ,")); +/** Stores the last estimate made by the `RepresentationEstimator`. */ +var LastEstimateStorage = /*#__PURE__*/function () { + function LastEstimateStorage() { + this.bandwidth = undefined; + this.representation = null; + this.algorithmType = 3 /* ABRAlgorithmType.None */; } /** - * @param {Object} playbackObservation - * @returns {number|undefined} + * Update this `LastEstimateStorage` with new values. + * @param {Object} representation - Estimated Representation. + * @param {number|undefined} bandwidth - Estimated bandwidth. + * @param {number} algorithmType - The type of algorithm used to produce that + * estimate. */ - var _proto = BufferBasedChooser.prototype; - _proto.getEstimate = function getEstimate(playbackObservation) { - var bufferLevels = this._levelsMap; - var bitrates = this._bitrates; - var bufferGap = playbackObservation.bufferGap, - currentBitrate = playbackObservation.currentBitrate, - currentScore = playbackObservation.currentScore, - speed = playbackObservation.speed; - if (currentBitrate == null) { - return bitrates[0]; - } - var currentBitrateIndex = (0,array_find_index/* default */.Z)(bitrates, function (b) { - return b === currentBitrate; - }); - if (currentBitrateIndex < 0 || bitrates.length !== bufferLevels.length) { - log/* default.error */.Z.error("ABR: Current Bitrate not found in the calculated levels"); - return bitrates[0]; - } - var scaledScore; - if (currentScore != null) { - scaledScore = speed === 0 ? currentScore : currentScore / speed; - } - if (scaledScore != null && scaledScore > 1) { - var currentBufferLevel = bufferLevels[currentBitrateIndex]; - var nextIndex = function () { - for (var i = currentBitrateIndex + 1; i < bufferLevels.length; i++) { - if (bufferLevels[i] > currentBufferLevel) { - return i; - } - } - }(); - if (nextIndex != null) { - var nextBufferLevel = bufferLevels[nextIndex]; - if (bufferGap >= nextBufferLevel) { - return bitrates[nextIndex]; - } - } - } - if (scaledScore == null || scaledScore < 1.15) { - var _currentBufferLevel = bufferLevels[currentBitrateIndex]; - if (bufferGap < _currentBufferLevel) { - for (var i = currentBitrateIndex - 1; i >= 0; i--) { - if (bitrates[i] < currentBitrate) { - return bitrates[i]; - } - } - return currentBitrate; - } - } - return currentBitrate; + var _proto = LastEstimateStorage.prototype; + _proto.update = function update(representation, bandwidth, algorithmType) { + this.representation = representation; + this.bandwidth = bandwidth; + this.algorithmType = algorithmType; }; - return BufferBasedChooser; + return LastEstimateStorage; }(); -// EXTERNAL MODULE: ./src/utils/array_find.ts -var array_find = __webpack_require__(3274); -;// CONCATENATED MODULE: ./src/core/adaptive/utils/ewma.ts +// EXTERNAL MODULE: ./src/utils/object_values.ts +var object_values = __webpack_require__(1679); +;// CONCATENATED MODULE: ./src/core/adaptive/utils/pending_requests_store.ts /** * Copyright 2015 CANAL+ Group * @@ -43344,44 +39517,73 @@ var array_find = __webpack_require__(3274); * See the License for the specific language governing permissions and * limitations under the License. */ + + /** - * Tweaked implementation of an exponential weighted Moving Average. - * @class EWMA + * Store information about pending requests, like information about: + * - for which segments they are + * - how the request's progress goes + * @class PendingRequestsStore */ -var EWMA = /*#__PURE__*/function () { +var PendingRequestsStore = /*#__PURE__*/function () { + function PendingRequestsStore() { + this._currentRequests = {}; + } /** - * @param {number} halfLife + * Add information about a new pending request. + * @param {Object} payload */ - function EWMA(halfLife) { - // (half-life = log(1/2) / log(Decay Factor) - this._alpha = Math.exp(Math.log(0.5) / halfLife); - this._lastEstimate = 0; - this._totalWeight = 0; + var _proto = PendingRequestsStore.prototype; + _proto.add = function add(payload) { + var id = payload.id, + requestTimestamp = payload.requestTimestamp, + content = payload.content; + this._currentRequests[id] = { + requestTimestamp: requestTimestamp, + progress: [], + content: content + }; } /** - * @param {number} weight - * @param {number} value - */ - var _proto = EWMA.prototype; - _proto.addSample = function addSample(weight, value) { - var adjAlpha = Math.pow(this._alpha, weight); - var newEstimate = value * (1 - adjAlpha) + adjAlpha * this._lastEstimate; - if (!isNaN(newEstimate)) { - this._lastEstimate = newEstimate; - this._totalWeight += weight; + * Notify of the progress of a currently pending request. + * @param {Object} progress + */; + _proto.addProgress = function addProgress(progress) { + var request = this._currentRequests[progress.id]; + if (request == null) { + if (false) {} + log/* default.warn */.Z.warn("ABR: progress for a request not added"); + return; } + request.progress.push(progress); } /** - * @returns {number} value + * Remove a request previously set as pending. + * @param {string} id */; - _proto.getEstimate = function getEstimate() { - var zeroFactor = 1 - Math.pow(this._alpha, this._totalWeight); - return this._lastEstimate / zeroFactor; + _proto.remove = function remove(id) { + if (this._currentRequests[id] == null) { + if (false) {} + log/* default.warn */.Z.warn("ABR: can't remove unknown request"); + } + delete this._currentRequests[id]; + } + /** + * Returns information about all pending requests, in segment's chronological + * order. + * @returns {Array.} + */; + _proto.getRequests = function getRequests() { + return (0,object_values/* default */.Z)(this._currentRequests).filter(function (x) { + return x != null; + }).sort(function (reqA, reqB) { + return reqA.content.segment.time - reqB.content.segment.time; + }); }; - return EWMA; + return PendingRequestsStore; }(); -;// CONCATENATED MODULE: ./src/core/adaptive/network_analyzer.ts +;// CONCATENATED MODULE: ./src/core/adaptive/utils/representation_score_calculator.ts /** * Copyright 2015 CANAL+ Group * @@ -43399,318 +39601,151 @@ var EWMA = /*#__PURE__*/function () { */ - - -/** - * Get pending segment request(s) starting with the asked segment position. - * @param {Object} requests - Every requests pending, in a chronological - * order in terms of segment time. - * @param {number} neededPosition - * @returns {Array.} - */ -function getConcernedRequests(requests, neededPosition) { - /** Index of the request for the next needed segment, in `requests`. */ - var nextSegmentIndex = -1; - for (var i = 0; i < requests.length; i++) { - var segment = requests[i].content.segment; - if (segment.duration <= 0) { - continue; - } - var segmentEnd = segment.time + segment.duration; - if (!segment.complete) { - if (i === requests.length - 1 && neededPosition - segment.time > -1.2) { - nextSegmentIndex = i; - break; - } - } - if (segmentEnd > neededPosition && neededPosition - segment.time > -1.2) { - nextSegmentIndex = i; - break; - } - } - if (nextSegmentIndex < 0) { - // Not found - return []; - } - var nextRequest = requests[nextSegmentIndex]; - var segmentTime = nextRequest.content.segment.time; - var filteredRequests = [nextRequest]; - // Get the possibly multiple requests for that segment's position - for (var _i = nextSegmentIndex + 1; _i < requests.length; _i++) { - if (requests[_i].content.segment.time === segmentTime) { - filteredRequests.push(requests[_i]); - } else { - break; - } - } - return filteredRequests; -} /** - * Estimate the __VERY__ recent bandwidth based on a single unfinished request. - * Useful when the current bandwidth seemed to have fallen quickly. + * Calculate the "maintainability score" of a given Representation: + * - A score higher than 1 means that the Representation can theorically + * be downloaded faster than the duration of the media it represents. + * (e.g. a segment representing 4 seconds can be downloaded in less than 4 + * seconds). + * - A score lower or equal to 1 means that the Representation cannot be + * downloaded * - * @param {Object} request - * @returns {number|undefined} - */ -function estimateRequestBandwidth(request) { - if (request.progress.length < 5) { - // threshold from which we can consider - // progress events reliably - return undefined; - } - // try to infer quickly the current bitrate based on the - // progress events - var ewma1 = new EWMA(2); - var progress = request.progress; - for (var i = 1; i < progress.length; i++) { - var bytesDownloaded = progress[i].size - progress[i - 1].size; - var timeElapsed = progress[i].timestamp - progress[i - 1].timestamp; - var reqBitrate = bytesDownloaded * 8 / (timeElapsed / 1000); - ewma1.addSample(timeElapsed / 1000, reqBitrate); - } - return ewma1.getEstimate(); -} -/** - * Estimate remaining time for a pending request from a progress event. - * @param {Object} lastProgressEvent - * @param {number} bandwidthEstimate - * @returns {number} - */ -function estimateRemainingTime(lastProgressEvent, bandwidthEstimate) { - var remainingData = (lastProgressEvent.totalSize - lastProgressEvent.size) * 8; - return Math.max(remainingData / bandwidthEstimate, 0); -} -/** - * Check if the request for the most needed segment is too slow. - * If that's the case, re-calculate the bandwidth urgently based on - * this single request. - * @param {Object} pendingRequests - Every requests pending, in a chronological - * order in terms of segment time. - * @param {Object} playbackInfo - Information on the current playback. - * @param {Object|null} currentRepresentation - The Representation being - * presently being loaded. - * @param {boolean} lowLatencyMode - If `true`, we're playing the content as a - * low latency content - where requests might be pending when the segment is - * still encoded. - * @param {Number} lastEstimatedBitrate - Last bitrate estimate emitted. - * @returns {Number|undefined} - */ -function estimateStarvationModeBitrate(pendingRequests, playbackInfo, currentRepresentation, lowLatencyMode, lastEstimatedBitrate) { - if (lowLatencyMode) { - // TODO Skip only for newer segments? - return undefined; - } - var bufferGap = playbackInfo.bufferGap, - speed = playbackInfo.speed, - position = playbackInfo.position; - var realBufferGap = isFinite(bufferGap) ? bufferGap : 0; - var nextNeededPosition = position.last + realBufferGap; - var concernedRequests = getConcernedRequests(pendingRequests, nextNeededPosition); - if (concernedRequests.length !== 1) { - // 0 == no request - // 2+ == too complicated to calculate - return undefined; - } - var concernedRequest = concernedRequests[0]; - var now = performance.now(); - var lastProgressEvent = concernedRequest.progress.length > 0 ? concernedRequest.progress[concernedRequest.progress.length - 1] : undefined; - // first, try to do a quick estimate from progress events - var bandwidthEstimate = estimateRequestBandwidth(concernedRequest); - if (lastProgressEvent !== undefined && bandwidthEstimate !== undefined) { - var remainingTime = estimateRemainingTime(lastProgressEvent, bandwidthEstimate); - // if the remaining time does seem reliable - if ((now - lastProgressEvent.timestamp) / 1000 <= remainingTime) { - // Calculate estimated time spent rebuffering if we continue doing that request. - var expectedRebufferingTime = remainingTime - realBufferGap / speed; - if (expectedRebufferingTime > 2000) { - return bandwidthEstimate; - } - } - } - if (!concernedRequest.content.segment.complete) { - return undefined; - } - var chunkDuration = concernedRequest.content.segment.duration; - var requestElapsedTime = (now - concernedRequest.requestTimestamp) / 1000; - var reasonableElapsedTime = requestElapsedTime <= (chunkDuration * 1.5 + 2) / speed; - if (currentRepresentation == null || reasonableElapsedTime) { - return undefined; - } - // calculate a reduced bitrate from the current one - var factor = chunkDuration / requestElapsedTime; - var reducedBitrate = currentRepresentation.bitrate * Math.min(0.7, factor); - if (lastEstimatedBitrate === undefined || reducedBitrate < lastEstimatedBitrate) { - return reducedBitrate; - } -} -/** - * Returns true if, based on the current requests, it seems that the ABR should - * switch immediately if a lower bitrate is more adapted. - * Returns false if it estimates that you have time before switching to a lower - * bitrate. - * @param {Object} playbackInfo - Information on the current playback. - * @param {Object} requests - Every requests pending, in a chronological - * order in terms of segment time. - * @param {boolean} lowLatencyMode - If `true`, we're playing the content as a - * low latency content, as close to the live edge as possible. - * @returns {boolean} + * The score follows a simple linear relation to both variables it is based + * on: + * - if n seconds of content can be downloaded in 2*n seconds, the score will + * be `0.5`. + * - if n seconds of content can be downloaded in n seconds, the score will be + * `1`. + * - if n seconds of content can be downloaded in n/2 seconds, the score will + * be `2`. + * - ... + * + * The score is mainly here to tell you when your buffer-based guesses are + * actually higher than the quality you should normally reach. + * + * /!\ Please bear in mind that we don't consider the playback rate in those + * operations. + * Still, integrating the playback rate a posteriori should not be difficult + * (e.g. you can just divide the score by that rate). + * + * @class RepresentationScoreCalculator */ -function shouldDirectlySwitchToLowBitrate(playbackInfo, requests, lowLatencyMode) { - if (lowLatencyMode) { - // TODO only when playing close to the live edge? - return true; - } - var realBufferGap = isFinite(playbackInfo.bufferGap) ? playbackInfo.bufferGap : 0; - var nextNeededPosition = playbackInfo.position.last + realBufferGap; - var nextRequest = (0,array_find/* default */.Z)(requests, function (_ref) { - var content = _ref.content; - return content.segment.duration > 0 && content.segment.time + content.segment.duration > nextNeededPosition; - }); - if (nextRequest === undefined) { - return true; - } - var now = performance.now(); - var lastProgressEvent = nextRequest.progress.length > 0 ? nextRequest.progress[nextRequest.progress.length - 1] : undefined; - // first, try to do a quick estimate from progress events - var bandwidthEstimate = estimateRequestBandwidth(nextRequest); - if (lastProgressEvent === undefined || bandwidthEstimate === undefined) { - return true; - } - var remainingTime = estimateRemainingTime(lastProgressEvent, bandwidthEstimate); - if ((now - lastProgressEvent.timestamp) / 1000 > remainingTime * 1.2) { - return true; +var RepresentationScoreCalculator = /*#__PURE__*/function () { + function RepresentationScoreCalculator() { + this._currentRepresentationData = null; + this._lastRepresentationWithGoodScore = null; } - var expectedRebufferingTime = remainingTime - realBufferGap / playbackInfo.speed; - return expectedRebufferingTime > -1.5; -} -/** - * Analyze the current network conditions and give a bandwidth estimate as well - * as a maximum bitrate a Representation should be. - * @class NetworkAnalyzer - */ -var NetworkAnalyzer = /*#__PURE__*/function () { - function NetworkAnalyzer(initialBitrate, lowLatencyMode) { - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - ABR_STARVATION_GAP = _config$getCurrent.ABR_STARVATION_GAP, - OUT_OF_STARVATION_GAP = _config$getCurrent.OUT_OF_STARVATION_GAP, - ABR_STARVATION_FACTOR = _config$getCurrent.ABR_STARVATION_FACTOR, - ABR_REGULAR_FACTOR = _config$getCurrent.ABR_REGULAR_FACTOR; - this._initialBitrate = initialBitrate; - this._inStarvationMode = false; - this._lowLatencyMode = lowLatencyMode; - if (lowLatencyMode) { - this._config = { - starvationGap: ABR_STARVATION_GAP.LOW_LATENCY, - outOfStarvationGap: OUT_OF_STARVATION_GAP.LOW_LATENCY, - starvationBitrateFactor: ABR_STARVATION_FACTOR.LOW_LATENCY, - regularBitrateFactor: ABR_REGULAR_FACTOR.LOW_LATENCY - }; + /** + * Add new sample data. + * @param {Representation} representation + * @param {number} requestDuration - duration taken for doing the request for + * the whole segment. + * @param {number} segmentDuration - media duration of the whole segment, in + * seconds. + */ + var _proto = RepresentationScoreCalculator.prototype; + _proto.addSample = function addSample(representation, requestDuration, segmentDuration) { + var ratio = segmentDuration / requestDuration; + var currentRep = this._currentRepresentationData; + var currentEWMA; + if (currentRep !== null && currentRep.representation.id === representation.id) { + currentEWMA = currentRep.ewma; + currentRep.ewma.addSample(requestDuration, ratio); + currentRep.loadedDuration += segmentDuration; + currentRep.loadedSegments++; } else { - this._config = { - starvationGap: ABR_STARVATION_GAP.DEFAULT, - outOfStarvationGap: OUT_OF_STARVATION_GAP.DEFAULT, - starvationBitrateFactor: ABR_STARVATION_FACTOR.DEFAULT, - regularBitrateFactor: ABR_REGULAR_FACTOR.DEFAULT + currentEWMA = new EWMA(5); + currentEWMA.addSample(requestDuration, ratio); + this._currentRepresentationData = { + representation: representation, + ewma: currentEWMA, + loadedDuration: segmentDuration, + loadedSegments: 0 }; } + if (currentEWMA.getEstimate() > 1 && this._lastRepresentationWithGoodScore !== representation) { + log/* default.debug */.Z.debug("ABR: New last stable representation", representation.bitrate); + this._lastRepresentationWithGoodScore = representation; + } } /** - * Gives an estimate of the current bandwidth and of the bitrate that should - * be considered for chosing a `representation`. - * This estimate is only based on network metrics. - * @param {Object} playbackInfo - Gives current information about playback. - * @param {Object} bandwidthEstimator - `BandwidthEstimator` allowing to - * produce network bandwidth estimates. - * @param {Object|null} currentRepresentation - The Representation currently - * chosen. - * `null` if no Representation has been chosen yet. - * @param {Array.} currentRequests - All segment requests by segment's - * start chronological order - * @param {number|undefined} lastEstimatedBitrate - Bitrate emitted during the - * last estimate. - * @returns {Object} - */ - var _proto = NetworkAnalyzer.prototype; - _proto.getBandwidthEstimate = function getBandwidthEstimate(playbackInfo, bandwidthEstimator, currentRepresentation, currentRequests, lastEstimatedBitrate) { - var newBitrateCeil; // bitrate ceil for the chosen Representation - var bandwidthEstimate; - var localConf = this._config; - var bufferGap = playbackInfo.bufferGap, - position = playbackInfo.position, - duration = playbackInfo.duration; - var realBufferGap = isFinite(bufferGap) ? bufferGap : 0; - var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), - ABR_STARVATION_DURATION_DELTA = _config$getCurrent2.ABR_STARVATION_DURATION_DELTA; - // check if should get in/out of starvation mode - if (isNaN(duration) || realBufferGap + position.last < duration - ABR_STARVATION_DURATION_DELTA) { - if (!this._inStarvationMode && realBufferGap <= localConf.starvationGap) { - log/* default.info */.Z.info("ABR: enter starvation mode."); - this._inStarvationMode = true; - } else if (this._inStarvationMode && realBufferGap >= localConf.outOfStarvationGap) { - log/* default.info */.Z.info("ABR: exit starvation mode."); - this._inStarvationMode = false; - } - } else if (this._inStarvationMode) { - log/* default.info */.Z.info("ABR: exit starvation mode."); - this._inStarvationMode = false; - } - // If in starvation mode, check if a quick new estimate can be done - // from the last requests. - // If so, cancel previous estimates and replace it by the new one - if (this._inStarvationMode) { - bandwidthEstimate = estimateStarvationModeBitrate(currentRequests, playbackInfo, currentRepresentation, this._lowLatencyMode, lastEstimatedBitrate); - if (bandwidthEstimate != null) { - log/* default.info */.Z.info("ABR: starvation mode emergency estimate:", bandwidthEstimate); - bandwidthEstimator.reset(); - newBitrateCeil = currentRepresentation == null ? bandwidthEstimate : Math.min(bandwidthEstimate, currentRepresentation.bitrate); - } - } - // if newBitrateCeil is not yet defined, do the normal estimation - if (newBitrateCeil == null) { - bandwidthEstimate = bandwidthEstimator.getEstimate(); - if (bandwidthEstimate != null) { - newBitrateCeil = bandwidthEstimate * (this._inStarvationMode ? localConf.starvationBitrateFactor : localConf.regularBitrateFactor); - } else if (lastEstimatedBitrate != null) { - newBitrateCeil = lastEstimatedBitrate * (this._inStarvationMode ? localConf.starvationBitrateFactor : localConf.regularBitrateFactor); - } else { - newBitrateCeil = this._initialBitrate; - } - } - if (playbackInfo.speed > 1) { - newBitrateCeil /= playbackInfo.speed; + * Get score estimate for the given Representation. + * undefined if no estimate is available. + * @param {Representation} representation + * @returns {number|undefined} + */; + _proto.getEstimate = function getEstimate(representation) { + if (this._currentRepresentationData === null || this._currentRepresentationData.representation.id !== representation.id) { + return undefined; } - return { - bandwidthEstimate: bandwidthEstimate, - bitrateChosen: newBitrateCeil - }; + var _this$_currentReprese = this._currentRepresentationData, + ewma = _this$_currentReprese.ewma, + loadedSegments = _this$_currentReprese.loadedSegments, + loadedDuration = _this$_currentReprese.loadedDuration; + var estimate = ewma.getEstimate(); + var confidenceLevel = loadedSegments >= 5 && loadedDuration >= 10 ? 1 /* ScoreConfidenceLevel.HIGH */ : 0 /* ScoreConfidenceLevel.LOW */; + return [estimate, confidenceLevel]; } /** - * For a given wanted bitrate, tells if should switch urgently. - * @param {number} bitrate - The new estimated bitrate. - * @param {Object|null} currentRepresentation - The Representation being - * presently being loaded. - * @param {Array.} currentRequests - All segment requests by segment's - * start chronological order - * @param {Object} playbackInfo - Information on the current playback. - * @returns {boolean} + * Returns last Representation which had reached a score superior to 1. + * This Representation is the last known one which could be maintained. + * Useful to know if a current guess is higher than what you should + * normally be able to play. + * `null` if no Representation ever reach that score. + * @returns {Representation|null} */; - _proto.isUrgent = function isUrgent(bitrate, currentRepresentation, currentRequests, playbackInfo) { - if (currentRepresentation === null) { - return true; - } else if (bitrate === currentRepresentation.bitrate) { - return false; - } else if (bitrate > currentRepresentation.bitrate) { - return !this._inStarvationMode; - } - return shouldDirectlySwitchToLowBitrate(playbackInfo, currentRequests, this._lowLatencyMode); + _proto.getLastStableRepresentation = function getLastStableRepresentation() { + return this._lastRepresentationWithGoodScore; }; - return NetworkAnalyzer; + return RepresentationScoreCalculator; }(); -;// CONCATENATED MODULE: ./src/core/adaptive/guess_based_chooser.ts -function guess_based_chooser_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = guess_based_chooser_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } -function guess_based_chooser_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return guess_based_chooser_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return guess_based_chooser_arrayLikeToArray(o, minLen); } -function guess_based_chooser_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } +;// CONCATENATED MODULE: ./src/core/adaptive/utils/select_optimal_representation.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * From the given array of Representations (sorted by bitrate order ascending), + * returns the one corresponding to the given optimal, minimum and maximum + * bitrates. + * @param {Array.} representations - The representations array, + * sorted in bitrate ascending order. + * @param {Number} optimalBitrate - The optimal bitrate the Representation + * should have under the current condition. + * @param {Number} minBitrate - The minimum bitrate the chosen Representation + * should have. We will take the Representation with the maximum bitrate if none + * is found. + * @param {Number} maxBitrate - The maximum bitrate the chosen Representation + * should have. We will take the Representation with the minimum bitrate if none + * is found. + * @returns {Representation|undefined} + */ +function selectOptimalRepresentation(representations, optimalBitrate, minBitrate, maxBitrate) { + var wantedBitrate = optimalBitrate <= minBitrate ? minBitrate : optimalBitrate >= maxBitrate ? maxBitrate : optimalBitrate; + var firstIndexTooHigh = (0,array_find_index/* default */.Z)(representations, function (representation) { + return representation.bitrate > wantedBitrate; + }); + if (firstIndexTooHigh === -1) { + return representations[representations.length - 1]; + } else if (firstIndexTooHigh === 0) { + return representations[0]; + } + return representations[firstIndexTooHigh - 1]; +} +;// CONCATENATED MODULE: ./src/core/adaptive/adaptive_representation_selector.ts /** * Copyright 2015 CANAL+ Group * @@ -43729,387 +39764,442 @@ function guess_based_chooser_arrayLikeToArray(arr, len) { if (len == null || len + + + + + + + + + + + + + +// Create default shared references +var manualBitrateDefaultRef = (0,reference/* default */.ZP)(-1); +manualBitrateDefaultRef.finish(); +var minAutoBitrateDefaultRef = (0,reference/* default */.ZP)(0); +minAutoBitrateDefaultRef.finish(); +var maxAutoBitrateDefaultRef = (0,reference/* default */.ZP)(Infinity); +maxAutoBitrateDefaultRef.finish(); +var limitWidthDefaultRef = (0,reference/* default */.ZP)(undefined); +limitWidthDefaultRef.finish(); +var throttleBitrateDefaultRef = (0,reference/* default */.ZP)(Infinity); +throttleBitrateDefaultRef.finish(); /** - * Estimate which Representation should be played based on risky "guesses". - * - * Basically, this `GuessBasedChooser` will attempt switching to the superior - * quality when conditions allows this and then check if we're able to maintain - * this quality. If we're not, it will rollbacks to the previous, maintaninable, - * guess. + * Select the most adapted Representation according to the network and buffer + * metrics it receives. * - * The algorithm behind the `GuessBasedChooser` is very risky in terms of - * rebuffering chances. As such, it should only be used when other approach - * don't work (e.g. low-latency contents). - * @class GuessBasedChooser + * @param {Object} options - Initial configuration (see type definition) + * @returns {Object} - Interface allowing to select a Representation. + * @see IRepresentationEstimator */ -var GuessBasedChooser = /*#__PURE__*/function () { +function createAdaptiveRepresentationSelector(options) { /** - * Create a new `GuessBasedChooser`. - * @param {Object} scoreCalculator - * @param {Object} prevEstimate + * Allows to estimate the current network bandwidth. + * One per active media type. */ - function GuessBasedChooser(scoreCalculator, prevEstimate) { - this._scoreCalculator = scoreCalculator; - this._lastAbrEstimate = prevEstimate; - this._consecutiveWrongGuesses = 0; - this._blockGuessesUntil = 0; - this._lastMaintanableBitrate = null; - } + var bandwidthEstimators = {}; + var manualBitrates = options.manualBitrates, + minAutoBitrates = options.minAutoBitrates, + maxAutoBitrates = options.maxAutoBitrates, + initialBitrates = options.initialBitrates, + throttlers = options.throttlers, + lowLatencyMode = options.lowLatencyMode; /** - * Perform a "guess", which basically indicates which Representation should be - * chosen according to the `GuessBasedChooser`. + * Returns Object emitting Representation estimates as well as callbacks + * allowing to helping it produce them. * - * @param {Array.} representations - Array of all Representation the - * GuessBasedChooser can choose from, sorted by bitrate ascending. - * /!\ It is very important that Representation in that Array are sorted by - * bitrate ascending for this method to work as intented. - * @param {Object} observation - Last playback observation performed. - * @param {Object} currentRepresentation - The Representation currently - * loading. - * @param {number} incomingBestBitrate - The bitrate of the Representation - * chosen by the more optimistic of the other ABR algorithms currently. - * @param {Array.} requests - Information on all pending requests. - * @returns {Object|null} - If a guess is made, return that guess, else - * returns `null` (in which case you should fallback to another ABR - * algorithm). + * @see IRepresentationEstimator + * @param {Object} context + * @param {Object} currentRepresentation + * @param {Object} representations + * @param {Object} playbackObserver + * @param {Object} stopAllEstimates + * @returns {Array.} */ - var _proto = GuessBasedChooser.prototype; - _proto.getGuess = function getGuess(representations, observation, currentRepresentation, incomingBestBitrate, requests) { - var bufferGap = observation.bufferGap, - speed = observation.speed; - var lastChosenRep = this._lastAbrEstimate.representation; - if (lastChosenRep === null) { - return null; // There's nothing to base our guess on - } - - if (incomingBestBitrate > lastChosenRep.bitrate) { - // ABR estimates are already superior or equal to the guess - // we'll be doing here, so no need to guess - if (this._lastAbrEstimate.algorithmType === 2 /* ABRAlgorithmType.GuessBased */) { - if (this._lastAbrEstimate.representation !== null) { - this._lastMaintanableBitrate = this._lastAbrEstimate.representation.bitrate; - } - this._consecutiveWrongGuesses = 0; - } - return null; + return function getEstimates(context, currentRepresentation, representations, playbackObserver, stopAllEstimates) { + var type = context.adaptation.type; + var bandwidthEstimator = _getBandwidthEstimator(type); + var manualBitrate = (0,take_first_set/* default */.Z)(manualBitrates[type], manualBitrateDefaultRef); + var minAutoBitrate = (0,take_first_set/* default */.Z)(minAutoBitrates[type], minAutoBitrateDefaultRef); + var maxAutoBitrate = (0,take_first_set/* default */.Z)(maxAutoBitrates[type], maxAutoBitrateDefaultRef); + var initialBitrate = (0,take_first_set/* default */.Z)(initialBitrates[type], 0); + var filters = { + limitWidth: (0,take_first_set/* default */.Z)(throttlers.limitWidth[type], limitWidthDefaultRef), + throttleBitrate: (0,take_first_set/* default */.Z)(throttlers.throttleBitrate[type], throttlers.throttle[type], throttleBitrateDefaultRef) + }; + return getEstimateReference({ + bandwidthEstimator: bandwidthEstimator, + context: context, + currentRepresentation: currentRepresentation, + filters: filters, + initialBitrate: initialBitrate, + manualBitrate: manualBitrate, + minAutoBitrate: minAutoBitrate, + maxAutoBitrate: maxAutoBitrate, + playbackObserver: playbackObserver, + representations: representations, + lowLatencyMode: lowLatencyMode + }, stopAllEstimates); + }; + /** + * Returns interface allowing to estimate network throughtput for a given type. + * @param {string} bufferType + * @returns {Object} + */ + function _getBandwidthEstimator(bufferType) { + var originalBandwidthEstimator = bandwidthEstimators[bufferType]; + if (originalBandwidthEstimator == null) { + log/* default.debug */.Z.debug("ABR: Creating new BandwidthEstimator for ", bufferType); + var bandwidthEstimator = new BandwidthEstimator(); + bandwidthEstimators[bufferType] = bandwidthEstimator; + return bandwidthEstimator; } - var scoreData = this._scoreCalculator.getEstimate(currentRepresentation); - if (this._lastAbrEstimate.algorithmType !== 2 /* ABRAlgorithmType.GuessBased */) { - if (scoreData === undefined) { - return null; // not enough information to start guessing - } - - if (this._canGuessHigher(bufferGap, speed, scoreData)) { - var nextRepresentation = getNextRepresentation(representations, currentRepresentation); - if (nextRepresentation !== null) { - return nextRepresentation; - } - } - return null; + return originalBandwidthEstimator; + } +} +/** + * Estimate regularly the current network bandwidth and the best Representation + * that can be played according to the current network and playback conditions. + * + * `getEstimateReference` only does estimations for a given type (e.g. + * "audio", "video" etc.) and Period. + * + * If estimates for multiple types and/or Periods are needed, you should + * call `getEstimateReference` as many times. + * + * This function returns a tuple: + * - the first element being the object through which estimates will be produced + * - the second element being callbacks that have to be triggered at various + * events to help it doing those estimates. + * + * @param {Object} args + * @param {Object} stopAllEstimates + * @returns {Array.} + */ +function getEstimateReference(_ref, stopAllEstimates) { + var bandwidthEstimator = _ref.bandwidthEstimator, + context = _ref.context, + currentRepresentation = _ref.currentRepresentation, + filters = _ref.filters, + initialBitrate = _ref.initialBitrate, + lowLatencyMode = _ref.lowLatencyMode, + manualBitrate = _ref.manualBitrate, + maxAutoBitrate = _ref.maxAutoBitrate, + minAutoBitrate = _ref.minAutoBitrate, + playbackObserver = _ref.playbackObserver, + representationsRef = _ref.representations; + var scoreCalculator = new RepresentationScoreCalculator(); + var networkAnalyzer = new NetworkAnalyzer(initialBitrate !== null && initialBitrate !== void 0 ? initialBitrate : 0, lowLatencyMode); + var requestsStore = new PendingRequestsStore(); + var onAddedSegment = noop/* default */.Z; + var callbacks = { + metrics: onMetric, + requestBegin: onRequestBegin, + requestProgress: onRequestProgress, + requestEnd: onRequestEnd, + addedSegment: function addedSegment(val) { + onAddedSegment(val); } - // If we reached here, we're currently already in guessing mode - if (this._isLastGuessValidated(lastChosenRep, incomingBestBitrate, scoreData)) { - log/* default.debug */.Z.debug("ABR: Guessed Representation validated", lastChosenRep.bitrate); - this._lastMaintanableBitrate = lastChosenRep.bitrate; - this._consecutiveWrongGuesses = 0; + }; + /** + * `TaskCanceller` allowing to stop producing estimate. + * This TaskCanceller is used both for restarting estimates with a new + * configuration and to cancel them altogether. + */ + var currentEstimatesCanceller = new task_canceller/* default */.ZP(); + currentEstimatesCanceller.linkToSignal(stopAllEstimates); + // Create `ISharedReference` on which estimates will be emitted. + var estimateRef = createEstimateReference(manualBitrate.getValue(), representationsRef.getValue(), currentEstimatesCanceller.signal); + manualBitrate.onUpdate(restartEstimatesProductionFromCurrentConditions, { + clearSignal: stopAllEstimates + }); + representationsRef.onUpdate(restartEstimatesProductionFromCurrentConditions, { + clearSignal: stopAllEstimates + }); + return { + estimates: estimateRef, + callbacks: callbacks + }; + function createEstimateReference(manualBitrateVal, representations, innerCancellationSignal) { + if (representations.length === 0) { + // No Representation given, return `null` as documented + return (0,reference/* default */.ZP)({ + representation: null, + bitrate: undefined, + knownStableBitrate: undefined, + manual: false, + urgent: true + }); } - if (currentRepresentation.id !== lastChosenRep.id) { - return lastChosenRep; + if (manualBitrateVal >= 0) { + // A manual bitrate has been set. Just choose Representation according to it. + var manualRepresentation = selectOptimalRepresentation(representations, manualBitrateVal, 0, Infinity); + return (0,reference/* default */.ZP)({ + representation: manualRepresentation, + bitrate: undefined, + knownStableBitrate: undefined, + manual: true, + urgent: true // a manual bitrate switch should happen immediately + }); } - var shouldStopGuess = this._shouldStopGuess(currentRepresentation, scoreData, bufferGap, requests); - if (shouldStopGuess) { - // Block guesses for a time - this._consecutiveWrongGuesses++; - this._blockGuessesUntil = performance.now() + Math.min(this._consecutiveWrongGuesses * 15000, 120000); - return getPreviousRepresentation(representations, currentRepresentation); - } else if (scoreData === undefined) { - return currentRepresentation; + + if (representations.length === 1) { + // There's only a single Representation. Just choose it. + return (0,reference/* default */.ZP)({ + bitrate: undefined, + representation: representations[0], + manual: false, + urgent: true, + knownStableBitrate: undefined + }); } - if (this._canGuessHigher(bufferGap, speed, scoreData)) { - var _nextRepresentation = getNextRepresentation(representations, currentRepresentation); - if (_nextRepresentation !== null) { - return _nextRepresentation; + /** If true, Representation estimates based on the buffer health might be used. */ + var allowBufferBasedEstimates = false; + /** + * Current optimal Representation's bandwidth choosen by a buffer-based + * adaptive algorithm. + */ + var currentBufferBasedEstimate; + var bitrates = representations.map(function (r) { + return r.bitrate; + }); + /** + * Module calculating the optimal Representation based on the current + * buffer's health (i.e. whether enough data is buffered, history of + * buffer size etc.). + */ + var bufferBasedChooser = new BufferBasedChooser(bitrates); + /** Store the previous estimate made here. */ + var prevEstimate = new LastEstimateStorage(); + /** + * Module calculating the optimal Representation by "guessing it" with a + * step-by-step algorithm. + * Only used in very specific scenarios. + */ + var guessBasedChooser = new GuessBasedChooser(scoreCalculator, prevEstimate); + // get initial observation for initial estimate + var lastPlaybackObservation = playbackObserver.getReference().getValue(); + /** Reference through which estimates are emitted. */ + var innerEstimateRef = (0,reference/* default */.ZP)(getCurrentEstimate()); + // Listen to playback observations + playbackObserver.listen(function (obs) { + lastPlaybackObservation = obs; + updateEstimate(); + }, { + includeLastObservation: false, + clearSignal: innerCancellationSignal + }); + onAddedSegment = function onAddedSegment(val) { + if (lastPlaybackObservation === null) { + return; } - } - return currentRepresentation; - } - /** - * Returns `true` if we've enough confidence on the current situation to make - * a higher guess. - * @param {number} bufferGap - * @param {number} speed - * @param {Array} scoreData - * @returns {boolean} - */; - _proto._canGuessHigher = function _canGuessHigher(bufferGap, speed, _ref) { - var score = _ref[0], - scoreConfidenceLevel = _ref[1]; - return isFinite(bufferGap) && bufferGap >= 2.5 && performance.now() > this._blockGuessesUntil && scoreConfidenceLevel === 1 /* ScoreConfidenceLevel.HIGH */ && score / speed > 1.01; - } - /** - * Returns `true` if the pending guess of `lastGuess` seems to not - * be maintainable and as such should be stopped. - * @param {Object} lastGuess - * @param {Array} scoreData - * @param {number} bufferGap - * @param {Array.} requests - * @returns {boolean} - */; - _proto._shouldStopGuess = function _shouldStopGuess(lastGuess, scoreData, bufferGap, requests) { - if (scoreData !== undefined && scoreData[0] < 1.01) { - return true; - } else if ((scoreData === undefined || scoreData[0] < 1.2) && bufferGap < 0.6) { - return true; - } - var guessedRepresentationRequests = requests.filter(function (req) { - return req.content.representation.id === lastGuess.id; + var _lastPlaybackObservat = lastPlaybackObservation, + position = _lastPlaybackObservat.position, + speed = _lastPlaybackObservat.speed; + var timeRanges = val.buffered; + var bufferGap = (0,ranges/* getLeftSizeOfRange */.L7)(timeRanges, position.last); + var representation = val.content.representation; + var scoreData = scoreCalculator.getEstimate(representation); + var currentScore = scoreData === null || scoreData === void 0 ? void 0 : scoreData[0]; + var currentBitrate = representation.bitrate; + var observation = { + bufferGap: bufferGap, + currentBitrate: currentBitrate, + currentScore: currentScore, + speed: speed + }; + currentBufferBasedEstimate = bufferBasedChooser.getEstimate(observation); + updateEstimate(); + }; + minAutoBitrate.onUpdate(updateEstimate, { + clearSignal: innerCancellationSignal }); - var now = performance.now(); - for (var _iterator = guess_based_chooser_createForOfIteratorHelperLoose(guessedRepresentationRequests), _step; !(_step = _iterator()).done;) { - var req = _step.value; - var requestElapsedTime = now - req.requestTimestamp; - if (req.content.segment.isInit) { - if (requestElapsedTime > 1000) { - return true; - } - } else if (requestElapsedTime > req.content.segment.duration * 1000 + 200) { - return true; - } else { - var fastBw = estimateRequestBandwidth(req); - if (fastBw !== undefined && fastBw < lastGuess.bitrate * 0.8) { - return true; - } + maxAutoBitrate.onUpdate(updateEstimate, { + clearSignal: innerCancellationSignal + }); + filters.limitWidth.onUpdate(updateEstimate, { + clearSignal: innerCancellationSignal + }); + filters.limitWidth.onUpdate(updateEstimate, { + clearSignal: innerCancellationSignal + }); + return innerEstimateRef; + function updateEstimate() { + innerEstimateRef.setValue(getCurrentEstimate()); + } + /** Returns the actual estimate based on all methods and algorithm available. */ + function getCurrentEstimate() { + var _lastPlaybackObservat2 = lastPlaybackObservation, + bufferGap = _lastPlaybackObservat2.bufferGap, + position = _lastPlaybackObservat2.position, + maximumPosition = _lastPlaybackObservat2.maximumPosition; + var widthLimit = filters.limitWidth.getValue(); + var bitrateThrottle = filters.throttleBitrate.getValue(); + var currentRepresentationVal = currentRepresentation.getValue(); + var minAutoBitrateVal = minAutoBitrate.getValue(); + var maxAutoBitrateVal = maxAutoBitrate.getValue(); + var filteredReps = getFilteredRepresentations(representations, widthLimit, bitrateThrottle); + var requests = requestsStore.getRequests(); + var _networkAnalyzer$getB = networkAnalyzer.getBandwidthEstimate(lastPlaybackObservation, bandwidthEstimator, currentRepresentationVal, requests, prevEstimate.bandwidth), + bandwidthEstimate = _networkAnalyzer$getB.bandwidthEstimate, + bitrateChosen = _networkAnalyzer$getB.bitrateChosen; + var stableRepresentation = scoreCalculator.getLastStableRepresentation(); + var knownStableBitrate = stableRepresentation === null ? undefined : stableRepresentation.bitrate / (lastPlaybackObservation.speed > 0 ? lastPlaybackObservation.speed : 1); + if (allowBufferBasedEstimates && bufferGap <= 5) { + allowBufferBasedEstimates = false; + } else if (!allowBufferBasedEstimates && isFinite(bufferGap) && bufferGap > 10) { + allowBufferBasedEstimates = true; + } + /** + * Representation chosen when considering only [pessimist] bandwidth + * calculation. + * This is a safe enough choice but might be lower than what the user + * could actually profit from. + */ + var chosenRepFromBandwidth = selectOptimalRepresentation(filteredReps, bitrateChosen, minAutoBitrateVal, maxAutoBitrateVal); + var currentBestBitrate = chosenRepFromBandwidth.bitrate; + /** + * Representation chosen when considering the current buffer size. + * If defined, takes precedence over `chosenRepFromBandwidth`. + * + * This is a very safe choice, yet it is very slow and might not be + * adapted to cases where a buffer cannot be build, such as live contents. + * + * `null` if this buffer size mode is not enabled or if we don't have a + * choice from it yet. + */ + var chosenRepFromBufferSize = null; + if (allowBufferBasedEstimates && currentBufferBasedEstimate !== undefined && currentBufferBasedEstimate > currentBestBitrate) { + chosenRepFromBufferSize = selectOptimalRepresentation(filteredReps, currentBufferBasedEstimate, minAutoBitrateVal, maxAutoBitrateVal); + currentBestBitrate = chosenRepFromBufferSize.bitrate; + } + /** + * Representation chosen by the more adventurous `GuessBasedChooser`, + * which iterates through Representations one by one until finding one + * that cannot be "maintained". + * + * If defined, takes precedence over both `chosenRepFromBandwidth` and + * `chosenRepFromBufferSize`. + * + * This is the riskiest choice (in terms of rebuffering chances) but is + * only enabled when no other solution is adapted (for now, this just + * applies for low-latency contents when playing close to the live + * edge). + * + * `null` if not enabled or if there's currently no guess. + */ + var chosenRepFromGuessMode = null; + if (lowLatencyMode && currentRepresentationVal !== null && context.manifest.isDynamic && maximumPosition - position.last < 40) { + chosenRepFromGuessMode = guessBasedChooser.getGuess(representations, lastPlaybackObservation, currentRepresentationVal, currentBestBitrate, requests); + } + if (chosenRepFromGuessMode !== null && chosenRepFromGuessMode.bitrate > currentBestBitrate) { + log/* default.debug */.Z.debug("ABR: Choosing representation with guess-based estimation.", chosenRepFromGuessMode.bitrate, chosenRepFromGuessMode.id); + prevEstimate.update(chosenRepFromGuessMode, bandwidthEstimate, 2 /* ABRAlgorithmType.GuessBased */); + return { + bitrate: bandwidthEstimate, + representation: chosenRepFromGuessMode, + urgent: currentRepresentationVal === null || chosenRepFromGuessMode.bitrate < currentRepresentationVal.bitrate, + manual: false, + knownStableBitrate: knownStableBitrate + }; + } else if (chosenRepFromBufferSize !== null) { + log/* default.debug */.Z.debug("ABR: Choosing representation with buffer-based estimation.", chosenRepFromBufferSize.bitrate, chosenRepFromBufferSize.id); + prevEstimate.update(chosenRepFromBufferSize, bandwidthEstimate, 0 /* ABRAlgorithmType.BufferBased */); + return { + bitrate: bandwidthEstimate, + representation: chosenRepFromBufferSize, + urgent: networkAnalyzer.isUrgent(chosenRepFromBufferSize.bitrate, currentRepresentationVal, requests, lastPlaybackObservation), + manual: false, + knownStableBitrate: knownStableBitrate + }; + } else { + log/* default.debug */.Z.debug("ABR: Choosing representation with bandwidth estimation.", chosenRepFromBandwidth.bitrate, chosenRepFromBandwidth.id); + prevEstimate.update(chosenRepFromBandwidth, bandwidthEstimate, 1 /* ABRAlgorithmType.BandwidthBased */); + return { + bitrate: bandwidthEstimate, + representation: chosenRepFromBandwidth, + urgent: networkAnalyzer.isUrgent(chosenRepFromBandwidth.bitrate, currentRepresentationVal, requests, lastPlaybackObservation), + manual: false, + knownStableBitrate: knownStableBitrate + }; } } - return false; - }; - _proto._isLastGuessValidated = function _isLastGuessValidated(lastGuess, incomingBestBitrate, scoreData) { - if (scoreData !== undefined && scoreData[1] === 1 /* ScoreConfidenceLevel.HIGH */ && scoreData[0] > 1.5) { - return true; - } - return incomingBestBitrate >= lastGuess.bitrate && (this._lastMaintanableBitrate === null || this._lastMaintanableBitrate < lastGuess.bitrate); - }; - return GuessBasedChooser; -}(); -/** - * From the array of Representations given, returns the Representation with a - * bitrate immediately superior to the current one. - * Returns `null` if that "next" Representation is not found. - * - * /!\ The representations have to be already sorted by bitrate, in ascending - * order. - * @param {Array.} representations - Available representations to choose - * from, sorted by bitrate in ascending order. - * @param {Object} currentRepresentation - The Representation currently - * considered. - * @returns {Object|null} - */ - -function getNextRepresentation(representations, currentRepresentation) { - var len = representations.length; - var index = (0,array_find_index/* default */.Z)(representations, function (_ref2) { - var id = _ref2.id; - return id === currentRepresentation.id; - }); - if (index < 0) { - log/* default.error */.Z.error("ABR: Current Representation not found."); - return null; - } - while (++index < len) { - if (representations[index].bitrate > currentRepresentation.bitrate) { - return representations[index]; - } - } - return null; -} -/** - * From the array of Representations given, returns the Representation with a - * bitrate immediately inferior. - * Returns `null` if that "previous" Representation is not found. - * @param {Array.} representations - * @param {Object} currentRepresentation - * @returns {Object|null} - */ -function getPreviousRepresentation(representations, currentRepresentation) { - var index = (0,array_find_index/* default */.Z)(representations, function (_ref3) { - var id = _ref3.id; - return id === currentRepresentation.id; - }); - if (index < 0) { - log/* default.error */.Z.error("ABR: Current Representation not found."); - return null; - } - while (--index >= 0) { - if (representations[index].bitrate < currentRepresentation.bitrate) { - return representations[index]; - } - } - return null; -} -;// CONCATENATED MODULE: ./src/core/adaptive/utils/bandwidth_estimator.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -/** - * Calculate a mean bandwidth based on the bytes downloaded and the amount - * of time needed to do so. - * @class BandwidthEstimator - */ -var BandwidthEstimator = /*#__PURE__*/function () { - function BandwidthEstimator() { - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - ABR_FAST_EMA = _config$getCurrent.ABR_FAST_EMA, - ABR_SLOW_EMA = _config$getCurrent.ABR_SLOW_EMA; - this._fastEWMA = new EWMA(ABR_FAST_EMA); - this._slowEWMA = new EWMA(ABR_SLOW_EMA); - this._bytesSampled = 0; } /** - * Takes a bandwidth sample. - * @param {number} durationInMs - The amount of time, in milliseconds, for a - * particular request. - * @param {number} numberOfBytes - The total number of bytes transferred in - * that request. + * Stop previous estimate production (if one) and restart it considering new + * conditions (such as a manual bitrate and/or a new list of Representations). */ - var _proto = BandwidthEstimator.prototype; - _proto.addSample = function addSample(durationInMs, numberOfBytes) { - var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), - ABR_MINIMUM_CHUNK_SIZE = _config$getCurrent2.ABR_MINIMUM_CHUNK_SIZE; - if (numberOfBytes < ABR_MINIMUM_CHUNK_SIZE) { - return; - } - var bandwidth = numberOfBytes * 8000 / durationInMs; - var weight = durationInMs / 1000; - this._bytesSampled += numberOfBytes; - this._fastEWMA.addSample(weight, bandwidth); - this._slowEWMA.addSample(weight, bandwidth); + function restartEstimatesProductionFromCurrentConditions() { + var manualBitrateVal = manualBitrate.getValue(); + var representations = representationsRef.getValue(); + currentEstimatesCanceller.cancel(); + currentEstimatesCanceller = new task_canceller/* default */.ZP(); + currentEstimatesCanceller.linkToSignal(stopAllEstimates); + var newRef = createEstimateReference(manualBitrateVal, representations, currentEstimatesCanceller.signal); + newRef.onUpdate(function onNewEstimate(newEstimate) { + estimateRef.setValue(newEstimate); + }, { + clearSignal: currentEstimatesCanceller.signal, + emitCurrentValue: true + }); } /** - * Get estimate of the bandwidth, in bits per seconds. - * @returns {Number|undefined} - */; - _proto.getEstimate = function getEstimate() { - var _config$getCurrent3 = config/* default.getCurrent */.Z.getCurrent(), - ABR_MINIMUM_TOTAL_BYTES = _config$getCurrent3.ABR_MINIMUM_TOTAL_BYTES; - if (this._bytesSampled < ABR_MINIMUM_TOTAL_BYTES) { - return undefined; + * Callback to call when new metrics are available + * @param {Object} value + */ + function onMetric(value) { + var requestDuration = value.requestDuration, + segmentDuration = value.segmentDuration, + size = value.size, + content = value.content; + // calculate bandwidth + bandwidthEstimator.addSample(requestDuration, size); + if (!content.segment.isInit) { + // calculate "maintainability score" + var segment = content.segment, + representation = content.representation; + if (segmentDuration === undefined && !segment.complete) { + // We cannot know the real duration of the segment + return; + } + var segDur = segmentDuration !== null && segmentDuration !== void 0 ? segmentDuration : segment.duration; + scoreCalculator.addSample(representation, requestDuration / 1000, segDur); } - // Take the minimum of these two estimates. - // This should have the effect of adapting down quickly, but up more slowly. - return Math.min(this._fastEWMA.getEstimate(), this._slowEWMA.getEstimate()); } - /** Reset the bandwidth estimation. */; - _proto.reset = function reset() { - var _config$getCurrent4 = config/* default.getCurrent */.Z.getCurrent(), - ABR_FAST_EMA = _config$getCurrent4.ABR_FAST_EMA, - ABR_SLOW_EMA = _config$getCurrent4.ABR_SLOW_EMA; - this._fastEWMA = new EWMA(ABR_FAST_EMA); - this._slowEWMA = new EWMA(ABR_SLOW_EMA); - this._bytesSampled = 0; - }; - return BandwidthEstimator; -}(); - -;// CONCATENATED MODULE: ./src/core/adaptive/utils/filter_by_bitrate.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Get only representations lower or equal to a given bitrate. - * If no representation is lower than the given bitrate, returns an array containing - * all Representation(s) with the lowest available bitrate. - * @param {Array.} representations - All Representations available - * @param {Number} bitrate - * @returns {Array.} - */ -function filterByBitrate(representations, bitrate) { - if (representations.length === 0) { - return []; + /** Callback called when a new request begins. */ + function onRequestBegin(val) { + requestsStore.add(val); } - representations.sort(function (ra, rb) { - return ra.bitrate - rb.bitrate; - }); - var minimumBitrate = representations[0].bitrate; - var bitrateCeil = Math.max(bitrate, minimumBitrate); - var firstSuperiorBitrateIndex = (0,array_find_index/* default */.Z)(representations, function (representation) { - return representation.bitrate > bitrateCeil; - }); - if (firstSuperiorBitrateIndex === -1) { - return representations; // All representations have lower bitrates. + /** Callback called when progress information is known on a pending request. */ + function onRequestProgress(val) { + requestsStore.addProgress(val); + } + /** Callback called when a pending request ends. */ + function onRequestEnd(val) { + requestsStore.remove(val.id); } - - return representations.slice(0, firstSuperiorBitrateIndex); } -;// CONCATENATED MODULE: ./src/core/adaptive/utils/filter_by_width.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * Filter representations based on their width: - * - the highest width considered will be the one linked to the first - * representation which has a superior width to the one given. - * @param {Array.} representations - The representations array - * @param {Number} width - * @returns {Array.} + * Filter representations given through filters options. + * @param {Array.} representations + * @param {number | undefined} widthLimit - Filter Object. + * @returns {Array.} */ -function filterByWidth(representations, width) { - var sortedRepsByWidth = representations.slice() // clone - .sort(function (a, b) { - return (0,take_first_set/* default */.Z)(a.width, 0) - (0,take_first_set/* default */.Z)(b.width, 0); - }); - var repWithMaxWidth = (0,array_find/* default */.Z)(sortedRepsByWidth, function (representation) { - return typeof representation.width === "number" && representation.width >= width; - }); - if (repWithMaxWidth === undefined) { - return representations; +function getFilteredRepresentations(representations, widthLimit, bitrateThrottle) { + var filteredReps = representations; + if (bitrateThrottle < Infinity) { + filteredReps = filterByBitrate(filteredReps, bitrateThrottle); } - var maxWidth = typeof repWithMaxWidth.width === "number" ? repWithMaxWidth.width : 0; - return representations.filter(function (representation) { - return typeof representation.width === "number" ? representation.width <= maxWidth : true; - }); + if (widthLimit !== undefined) { + filteredReps = filterByWidth(filteredReps, widthLimit); + } + return filteredReps; } -;// CONCATENATED MODULE: ./src/core/adaptive/utils/last_estimate_storage.ts +;// CONCATENATED MODULE: ./src/core/adaptive/index.ts /** * Copyright 2015 CANAL+ Group * @@ -44125,32 +40215,15 @@ function filterByWidth(representations, width) { * See the License for the specific language governing permissions and * limitations under the License. */ -/** Stores the last estimate made by the `RepresentationEstimator`. */ -var LastEstimateStorage = /*#__PURE__*/function () { - function LastEstimateStorage() { - this.bandwidth = undefined; - this.representation = null; - this.algorithmType = 3 /* ABRAlgorithmType.None */; - } - /** - * Update this `LastEstimateStorage` with new values. - * @param {Object} representation - Estimated Representation. - * @param {number|undefined} bandwidth - Estimated bandwidth. - * @param {number} algorithmType - The type of algorithm used to produce that - * estimate. - */ - var _proto = LastEstimateStorage.prototype; - _proto.update = function update(representation, bandwidth, algorithmType) { - this.representation = representation; - this.bandwidth = bandwidth; - this.algorithmType = algorithmType; - }; - return LastEstimateStorage; -}(); -// EXTERNAL MODULE: ./src/utils/object_values.ts -var object_values = __webpack_require__(1679); -;// CONCATENATED MODULE: ./src/core/adaptive/utils/pending_requests_store.ts +/* harmony default export */ var adaptive = (createAdaptiveRepresentationSelector); +// EXTERNAL MODULE: ./src/manifest/index.ts + 6 modules +var manifest = __webpack_require__(1989); +// EXTERNAL MODULE: ./src/errors/request_error.ts +var request_error = __webpack_require__(9105); +// EXTERNAL MODULE: ./src/errors/network_error.ts +var network_error = __webpack_require__(9362); +;// CONCATENATED MODULE: ./src/core/fetchers/utils/error_selector.ts /** * Copyright 2015 CANAL+ Group * @@ -44167,72 +40240,22 @@ var object_values = __webpack_require__(1679); * limitations under the License. */ - /** - * Store information about pending requests, like information about: - * - for which segments they are - * - how the request's progress goes - * @class PendingRequestsStore + * Generate a new error from the infos given. + * @param {string} code + * @param {Error} error + * @returns {Error} */ -var PendingRequestsStore = /*#__PURE__*/function () { - function PendingRequestsStore() { - this._currentRequests = {}; - } - /** - * Add information about a new pending request. - * @param {Object} payload - */ - var _proto = PendingRequestsStore.prototype; - _proto.add = function add(payload) { - var id = payload.id, - requestTimestamp = payload.requestTimestamp, - content = payload.content; - this._currentRequests[id] = { - requestTimestamp: requestTimestamp, - progress: [], - content: content - }; - } - /** - * Notify of the progress of a currently pending request. - * @param {Object} progress - */; - _proto.addProgress = function addProgress(progress) { - var request = this._currentRequests[progress.id]; - if (request == null) { - if (false) {} - log/* default.warn */.Z.warn("ABR: progress for a request not added"); - return; - } - request.progress.push(progress); - } - /** - * Remove a request previously set as pending. - * @param {string} id - */; - _proto.remove = function remove(id) { - if (this._currentRequests[id] == null) { - if (false) {} - log/* default.warn */.Z.warn("ABR: can't remove unknown request"); - } - delete this._currentRequests[id]; +function errorSelector(error) { + if (error instanceof request_error/* default */.Z) { + return new network_error/* default */.Z("PIPELINE_LOAD_ERROR", error); } - /** - * Returns information about all pending requests, in segment's chronological - * order. - * @returns {Array.} - */; - _proto.getRequests = function getRequests() { - return (0,object_values/* default */.Z)(this._currentRequests).filter(function (x) { - return x != null; - }).sort(function (reqA, reqB) { - return reqA.content.segment.time - reqB.content.segment.time; - }); - }; - return PendingRequestsStore; -}(); - -;// CONCATENATED MODULE: ./src/core/adaptive/utils/representation_score_calculator.ts + return (0,format_error/* default */.Z)(error, { + defaultCode: "PIPELINE_LOAD_ERROR", + defaultReason: "Unknown error when fetching the Manifest" + }); +} +;// CONCATENATED MODULE: ./src/compat/is_offline.ts /** * Copyright 2015 CANAL+ Group * @@ -44248,108 +40271,45 @@ var PendingRequestsStore = /*#__PURE__*/function () { * See the License for the specific language governing permissions and * limitations under the License. */ - - /** - * Calculate the "maintainability score" of a given Representation: - * - A score higher than 1 means that the Representation can theorically - * be downloaded faster than the duration of the media it represents. - * (e.g. a segment representing 4 seconds can be downloaded in less than 4 - * seconds). - * - A score lower or equal to 1 means that the Representation cannot be - * downloaded + * Some browsers have a builtin API to know if it's connected at least to a + * LAN network, at most to the internet. * - * The score follows a simple linear relation to both variables it is based - * on: - * - if n seconds of content can be downloaded in 2*n seconds, the score will - * be `0.5`. - * - if n seconds of content can be downloaded in n seconds, the score will be - * `1`. - * - if n seconds of content can be downloaded in n/2 seconds, the score will - * be `2`. - * - ... + * /!\ This feature can be dangerous as you can both have false positives and + * false negatives. * - * The score is mainly here to tell you when your buffer-based guesses are - * actually higher than the quality you should normally reach. + * False positives: + * - you can still play local contents (on localhost) if isOffline == true + * - on some browsers isOffline might be true even if we're connected to a LAN + * or a router (it would mean we're just not able to connect to the + * Internet). So we can eventually play LAN contents if isOffline == true * - * /!\ Please bear in mind that we don't consider the playback rate in those - * operations. - * Still, integrating the playback rate a posteriori should not be difficult - * (e.g. you can just divide the score by that rate). + * False negatives: + * - in some cases, we even might have isOffline at false when we do not have + * any connection: + * - in browsers that do not support the feature + * - in browsers running in some virtualization softwares where the + * network adapters are always connected. * - * @class RepresentationScoreCalculator + * Use with these cases in mind. + * @returns {Boolean} */ -var RepresentationScoreCalculator = /*#__PURE__*/function () { - function RepresentationScoreCalculator() { - this._currentRepresentationData = null; - this._lastRepresentationWithGoodScore = null; - } - /** - * Add new sample data. - * @param {Representation} representation - * @param {number} requestDuration - duration taken for doing the request for - * the whole segment. - * @param {number} segmentDuration - media duration of the whole segment, in - * seconds. - */ - var _proto = RepresentationScoreCalculator.prototype; - _proto.addSample = function addSample(representation, requestDuration, segmentDuration) { - var ratio = segmentDuration / requestDuration; - var currentRep = this._currentRepresentationData; - var currentEWMA; - if (currentRep !== null && currentRep.representation.id === representation.id) { - currentEWMA = currentRep.ewma; - currentRep.ewma.addSample(requestDuration, ratio); - currentRep.loadedDuration += segmentDuration; - currentRep.loadedSegments++; - } else { - currentEWMA = new EWMA(5); - currentEWMA.addSample(requestDuration, ratio); - this._currentRepresentationData = { - representation: representation, - ewma: currentEWMA, - loadedDuration: segmentDuration, - loadedSegments: 0 - }; - } - if (currentEWMA.getEstimate() > 1 && this._lastRepresentationWithGoodScore !== representation) { - log/* default.debug */.Z.debug("ABR: New last stable representation", representation.bitrate); - this._lastRepresentationWithGoodScore = representation; - } - } - /** - * Get score estimate for the given Representation. - * undefined if no estimate is available. - * @param {Representation} representation - * @returns {number|undefined} - */; - _proto.getEstimate = function getEstimate(representation) { - if (this._currentRepresentationData === null || this._currentRepresentationData.representation.id !== representation.id) { - return undefined; - } - var _this$_currentReprese = this._currentRepresentationData, - ewma = _this$_currentReprese.ewma, - loadedSegments = _this$_currentReprese.loadedSegments, - loadedDuration = _this$_currentReprese.loadedDuration; - var estimate = ewma.getEstimate(); - var confidenceLevel = loadedSegments >= 5 && loadedDuration >= 10 ? 1 /* ScoreConfidenceLevel.HIGH */ : 0 /* ScoreConfidenceLevel.LOW */; - return [estimate, confidenceLevel]; - } - /** - * Returns last Representation which had reached a score superior to 1. - * This Representation is the last known one which could be maintained. - * Useful to know if a current guess is higher than what you should - * normally be able to play. - * `null` if no Representation ever reach that score. - * @returns {Representation|null} - */; - _proto.getLastStableRepresentation = function getLastStableRepresentation() { - return this._lastRepresentationWithGoodScore; - }; - return RepresentationScoreCalculator; -}(); +function isOffline() { + /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ + return navigator.onLine === false; + /* eslint-enable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +} +// EXTERNAL MODULE: ./src/errors/custom_loader_error.ts +var custom_loader_error = __webpack_require__(7839); +// EXTERNAL MODULE: ./src/errors/is_known_error.ts +var is_known_error = __webpack_require__(9822); +// EXTERNAL MODULE: ./src/utils/cancellable_sleep.ts +var cancellable_sleep = __webpack_require__(7864); +// EXTERNAL MODULE: ./src/utils/get_fuzzed_delay.ts +var get_fuzzed_delay = __webpack_require__(2572); +;// CONCATENATED MODULE: ./src/core/fetchers/utils/schedule_request.ts + -;// CONCATENATED MODULE: ./src/core/adaptive/utils/select_optimal_representation.ts /** * Copyright 2015 CANAL+ Group * @@ -44366,35 +40326,375 @@ var RepresentationScoreCalculator = /*#__PURE__*/function () { * limitations under the License. */ + + + + + + /** - * From the given array of Representations (sorted by bitrate order ascending), - * returns the one corresponding to the given optimal, minimum and maximum - * bitrates. - * @param {Array.} representations - The representations array, - * sorted in bitrate ascending order. - * @param {Number} optimalBitrate - The optimal bitrate the Representation - * should have under the current condition. - * @param {Number} minBitrate - The minimum bitrate the chosen Representation - * should have. We will take the Representation with the maximum bitrate if none - * is found. - * @param {Number} maxBitrate - The maximum bitrate the chosen Representation - * should have. We will take the Representation with the minimum bitrate if none - * is found. - * @returns {Representation|undefined} + * Called on a loader error. + * Returns whether the loader request should be retried. + * + * TODO the notion of retrying or not could be transport-specific (e.g. 412 are + * mainly used for Smooth contents) and thus as part of the transport code (e.g. + * by rejecting with an error always having a `canRetry` property?). + * Or not, to ponder. + * + * @param {Error} error + * @returns {Boolean} - If true, the request can be retried. */ -function selectOptimalRepresentation(representations, optimalBitrate, minBitrate, maxBitrate) { - var wantedBitrate = optimalBitrate <= minBitrate ? minBitrate : optimalBitrate >= maxBitrate ? maxBitrate : optimalBitrate; - var firstIndexTooHigh = (0,array_find_index/* default */.Z)(representations, function (representation) { - return representation.bitrate > wantedBitrate; - }); - if (firstIndexTooHigh === -1) { - return representations[representations.length - 1]; - } else if (firstIndexTooHigh === 0) { - return representations[0]; +function shouldRetry(error) { + if (error instanceof request_error/* default */.Z) { + if (error.type === error_codes/* NetworkErrorTypes.ERROR_HTTP_CODE */.br.ERROR_HTTP_CODE) { + return error.status >= 500 || error.status === 404 || error.status === 415 || + // some CDN seems to use that code when + // requesting low-latency segments too much + // in advance + error.status === 412; + } + return error.type === error_codes/* NetworkErrorTypes.TIMEOUT */.br.TIMEOUT || error.type === error_codes/* NetworkErrorTypes.ERROR_EVENT */.br.ERROR_EVENT; + } else if (error instanceof custom_loader_error/* default */.Z) { + if (typeof error.canRetry === "boolean") { + return error.canRetry; + } + if (error.xhr !== undefined) { + return error.xhr.status >= 500 || error.xhr.status === 404 || error.xhr.status === 415 || + // some CDN seems to use that code when + // requesting low-latency segments too much + // in advance + error.xhr.status === 412; + } + return false; } - return representations[firstIndexTooHigh - 1]; + return (0,is_known_error/* default */.Z)(error) && error.code === "INTEGRITY_ERROR"; } -;// CONCATENATED MODULE: ./src/core/adaptive/adaptive_representation_selector.ts +/** + * Returns true if we're pretty sure that the current error is due to the + * user being offline. + * @param {Error} error + * @returns {Boolean} + */ +function isOfflineRequestError(error) { + if (error instanceof request_error/* default */.Z) { + return error.type === error_codes/* NetworkErrorTypes.ERROR_EVENT */.br.ERROR_EVENT && isOffline(); + } else if (error instanceof custom_loader_error/* default */.Z) { + return error.isOfflineError; + } + return false; // under doubt, return false +} +/** + * Guess the type of error obtained. + * @param {*} error + * @returns {number} + */ +function getRequestErrorType(error) { + return isOfflineRequestError(error) ? 2 /* REQUEST_ERROR_TYPES.Offline */ : 1 /* REQUEST_ERROR_TYPES.Regular */; +} +/** + * Specific algorithm used to perform segment and manifest requests. + * + * Here how it works: + * + * 1. You give it one or multiple of the CDN available for the resource you + * want to request (from the most important one to the least important), + * a callback doing the request with the chosen CDN in argument, and some + * options. + * + * 2. it tries to call the request callback with the most prioritized CDN + * first: + * - if it works as expected, it resolves the returned Promise with that + * request's response. + * - if it fails, it calls ther `onRetry` callback given with the + * corresponding error, un-prioritize that CDN and try with the new + * most prioritized CDN. + * + * Each CDN might be retried multiple times, depending on the nature of the + * error and the Configuration given. + * + * Multiple retries of the same CDN are done after a delay to avoid + * overwhelming it, this is what we call a "backoff". That delay raises + * exponentially as multiple consecutive errors are encountered on this + * CDN. + * + * @param {Array.|null} cdns - The different CDN on which the + * wanted resource is available. `scheduleRequestWithCdns` will call the + * `performRequest` callback with the right element from that array if different + * from `null`. + * + * Can be set to `null` when that resource is not reachable through a CDN, in + * which case the `performRequest` callback may be called with `null`. + * @param {Object|null} cdnPrioritizer - Interface allowing to give the priority + * between multiple CDNs. + * @param {Function} performRequest - Callback implementing the request in + * itself. Resolving when the resource request succeed and rejecting with the + * corresponding error when the request failed. + * @param {Object} options - Configuration allowing to tweak the number on which + * the algorithm behind `scheduleRequestWithCdns` bases itself. + * @param {Object} cancellationSignal - CancellationSignal allowing to cancel + * the logic of `scheduleRequestWithCdns`. + * To trigger if the resource is not needed anymore. + * @returns {Promise} - Promise resolving, with the corresponding + * `performRequest`'s data, when the resource request succeed and rejecting in + * the following scenarios: + * - `scheduleRequestWithCdns` has been cancelled due to `cancellationSignal` + * being triggered. In that case a `CancellationError` is thrown. + * + * - The resource request(s) failed and will not be retried anymore. + */ +function scheduleRequestWithCdns(_x, _x2, _x3, _x4, _x5) { + return _scheduleRequestWithCdns.apply(this, arguments); +} +/** + * Lightweight version of the request algorithm, this time with only a simple + * Promise given. + * @param {Function} performRequest + * @param {Object} options + * @returns {Promise} + */ +function _scheduleRequestWithCdns() { + _scheduleRequestWithCdns = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee3(cdns, cdnPrioritizer, performRequest, options, cancellationSignal) { + var baseDelay, maxDelay, maxRetryRegular, maxRetryOffline, onRetry, missedAttempts, initialCdnToRequest, getCdnToRequest, requestCdn, _requestCdn, retryWithNextCdn, _retryWithNextCdn, waitPotentialBackoffAndRequest, getPrioritaryRequestableCdnFromSortedList; + return regenerator_default().wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + getPrioritaryRequestableCdnFromSortedList = function _getPrioritaryRequest(sortedCdns) { + var _a; + if (missedAttempts.size === 0) { + return sortedCdns[0]; + } + var now = performance.now(); + return (_a = sortedCdns.filter(function (c) { + var _a; + return ((_a = missedAttempts.get(c)) === null || _a === void 0 ? void 0 : _a.isBlacklisted) !== true; + }).reduce(function (acc, x) { + var _a; + var blockedUntil = (_a = missedAttempts.get(x)) === null || _a === void 0 ? void 0 : _a.blockedUntil; + if (blockedUntil !== undefined && blockedUntil <= now) { + blockedUntil = undefined; + } + if (acc === undefined) { + return [x, blockedUntil]; + } + if (blockedUntil === undefined) { + if (acc[1] === undefined) { + return acc; + } + return [x, undefined]; + } + return acc[1] === undefined ? acc : blockedUntil < acc[1] ? [x, blockedUntil] : acc; + }, undefined)) === null || _a === void 0 ? void 0 : _a[0]; + }; + waitPotentialBackoffAndRequest = function _waitPotentialBackoff(nextWantedCdn, prevRequestError) { + var nextCdnAttemptObj = missedAttempts.get(nextWantedCdn); + if (nextCdnAttemptObj === undefined || nextCdnAttemptObj.blockedUntil === undefined) { + return requestCdn(nextWantedCdn); + } + var now = performance.now(); + var blockedFor = nextCdnAttemptObj.blockedUntil - now; + if (blockedFor <= 0) { + return requestCdn(nextWantedCdn); + } + var canceller = new task_canceller/* default */.ZP(); + var unlinkCanceller = canceller.linkToSignal(cancellationSignal); + return new Promise(function (res, rej) { + /* eslint-disable-next-line @typescript-eslint/no-misused-promises */ + cdnPrioritizer === null || cdnPrioritizer === void 0 ? void 0 : cdnPrioritizer.addEventListener("priorityChange", function () { + var updatedPrioritaryCdn = getCdnToRequest(); + if (cancellationSignal.isCancelled()) { + throw cancellationSignal.cancellationError; + } + if (updatedPrioritaryCdn === undefined) { + return cleanAndReject(prevRequestError); + } + if (updatedPrioritaryCdn !== nextWantedCdn) { + canceller.cancel(); + waitPotentialBackoffAndRequest(updatedPrioritaryCdn, prevRequestError).then(cleanAndResolve, cleanAndReject); + } + }, canceller.signal); + (0,cancellable_sleep/* default */.Z)(blockedFor, canceller.signal).then(function () { + return requestCdn(nextWantedCdn).then(cleanAndResolve, cleanAndReject); + }, noop/* default */.Z); + function cleanAndResolve(response) { + unlinkCanceller(); + res(response); + } + function cleanAndReject(err) { + unlinkCanceller(); + rej(err); + } + }); + }; + _retryWithNextCdn = function _retryWithNextCdn3() { + _retryWithNextCdn = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee2(prevRequestError) { + var nextCdn; + return regenerator_default().wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + nextCdn = getCdnToRequest(); + if (!cancellationSignal.isCancelled()) { + _context2.next = 3; + break; + } + throw cancellationSignal.cancellationError; + case 3: + if (!(nextCdn === undefined)) { + _context2.next = 5; + break; + } + throw prevRequestError; + case 5: + onRetry(prevRequestError); + if (!cancellationSignal.isCancelled()) { + _context2.next = 8; + break; + } + throw cancellationSignal.cancellationError; + case 8: + return _context2.abrupt("return", waitPotentialBackoffAndRequest(nextCdn, prevRequestError)); + case 9: + case "end": + return _context2.stop(); + } + } + }, _callee2); + })); + return _retryWithNextCdn.apply(this, arguments); + }; + retryWithNextCdn = function _retryWithNextCdn2(_x7) { + return _retryWithNextCdn.apply(this, arguments); + }; + _requestCdn = function _requestCdn3() { + _requestCdn = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(cdn) { + var res, currentErrorType, missedAttemptsObj, maxRetry, errorCounter, delay, fuzzedDelay; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.prev = 0; + _context.next = 3; + return performRequest(cdn, cancellationSignal); + case 3: + res = _context.sent; + return _context.abrupt("return", res); + case 7: + _context.prev = 7; + _context.t0 = _context["catch"](0); + if (!task_canceller/* default.isCancellationError */.ZP.isCancellationError(_context.t0)) { + _context.next = 11; + break; + } + throw _context.t0; + case 11: + if (cdn !== null && cdnPrioritizer !== null) { + // We failed requesting the resource on this CDN. + // Globally give priority to the next CDN through the CdnPrioritizer. + cdnPrioritizer.downgradeCdn(cdn); + } + currentErrorType = getRequestErrorType(_context.t0); + missedAttemptsObj = missedAttempts.get(cdn); + if (missedAttemptsObj === undefined) { + missedAttemptsObj = { + errorCounter: 1, + lastErrorType: currentErrorType, + blockedUntil: undefined, + isBlacklisted: false + }; + missedAttempts.set(cdn, missedAttemptsObj); + } else { + if (currentErrorType !== missedAttemptsObj.lastErrorType) { + missedAttemptsObj.errorCounter = 1; + missedAttemptsObj.lastErrorType = currentErrorType; + } else { + missedAttemptsObj.errorCounter++; + } + } + if (shouldRetry(_context.t0)) { + _context.next = 19; + break; + } + missedAttemptsObj.blockedUntil = undefined; + missedAttemptsObj.isBlacklisted = true; + return _context.abrupt("return", retryWithNextCdn(_context.t0)); + case 19: + maxRetry = currentErrorType === 2 /* REQUEST_ERROR_TYPES.Offline */ ? maxRetryOffline : maxRetryRegular; + if (missedAttemptsObj.errorCounter > maxRetry) { + missedAttemptsObj.blockedUntil = undefined; + missedAttemptsObj.isBlacklisted = true; + } else { + errorCounter = missedAttemptsObj.errorCounter; + delay = Math.min(baseDelay * Math.pow(2, errorCounter - 1), maxDelay); + fuzzedDelay = (0,get_fuzzed_delay/* default */.Z)(delay); + missedAttemptsObj.blockedUntil = performance.now() + fuzzedDelay; + } + return _context.abrupt("return", retryWithNextCdn(_context.t0)); + case 22: + case "end": + return _context.stop(); + } + } + }, _callee, null, [[0, 7]]); + })); + return _requestCdn.apply(this, arguments); + }; + requestCdn = function _requestCdn2(_x6) { + return _requestCdn.apply(this, arguments); + }; + getCdnToRequest = function _getCdnToRequest() { + if (cdns === null) { + var nullAttemptObject = missedAttempts.get(null); + if (nullAttemptObject !== undefined && nullAttemptObject.isBlacklisted) { + return undefined; + } + return null; + } else if (cdnPrioritizer === null) { + return getPrioritaryRequestableCdnFromSortedList(cdns); + } else { + var prioritized = cdnPrioritizer.getCdnPreferenceForResource(cdns); + return getPrioritaryRequestableCdnFromSortedList(prioritized); + } + }; + if (!(cancellationSignal.cancellationError !== null)) { + _context3.next = 9; + break; + } + return _context3.abrupt("return", Promise.reject(cancellationSignal.cancellationError)); + case 9: + baseDelay = options.baseDelay, maxDelay = options.maxDelay, maxRetryRegular = options.maxRetryRegular, maxRetryOffline = options.maxRetryOffline, onRetry = options.onRetry; + if (cdns !== null && cdns.length === 0) { + log/* default.warn */.Z.warn("Fetchers: no CDN given to `scheduleRequestWithCdns`."); + } + missedAttempts = new Map(); + initialCdnToRequest = getCdnToRequest(); + if (!(initialCdnToRequest === undefined)) { + _context3.next = 15; + break; + } + throw new Error("No CDN to request"); + case 15: + return _context3.abrupt("return", requestCdn(initialCdnToRequest)); + case 16: + case "end": + return _context3.stop(); + } + } + }, _callee3); + })); + return _scheduleRequestWithCdns.apply(this, arguments); +} +function scheduleRequestPromise(performRequest, options, cancellationSignal) { + // same than for a single unknown CDN + return scheduleRequestWithCdns(null, null, performRequest, options, cancellationSignal); +} +;// CONCATENATED MODULE: ./src/core/fetchers/manifest/manifest_fetcher.ts + + +function manifest_fetcher_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = manifest_fetcher_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function manifest_fetcher_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return manifest_fetcher_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return manifest_fetcher_arrayLikeToArray(o, minLen); } +function manifest_fetcher_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + /** * Copyright 2015 CANAL+ Group * @@ -44421,415 +40721,590 @@ function selectOptimalRepresentation(representations, optimalBitrate, minBitrate - - - - - /** - * Select the most adapted Representation according to the network and buffer - * metrics it receives. - * - * @param {Object} options - Initial configuration (see type definition) - * @returns {Object} - Interface allowing to select a Representation. - * @see IRepresentationEstimator + * Class allowing to facilitate the task of loading and parsing a Manifest, as + * well as automatically refreshing it. + * @class ManifestFetcher */ -function createAdaptiveRepresentationSelector(options) { +var ManifestFetcher = /*#__PURE__*/function (_EventEmitter) { + (0,inheritsLoose/* default */.Z)(ManifestFetcher, _EventEmitter); /** - * Allows to estimate the current network bandwidth. - * One per active media type. + * Construct a new ManifestFetcher. + * @param {Array. | undefined} urls - Manifest URLs, will be used when + * no URL is provided to the `fetch` function. + * `undefined` if unknown or if a Manifest should be retrieved through other + * means than an HTTP request. + * @param {Object} pipelines - Transport pipelines used to perform the + * Manifest loading and parsing operations. + * @param {Object} settings - Configure the `ManifestFetcher`. */ - var bandwidthEstimators = {}; - var manualBitrates = options.manualBitrates, - minAutoBitrates = options.minAutoBitrates, - maxAutoBitrates = options.maxAutoBitrates, - initialBitrates = options.initialBitrates, - throttlers = options.throttlers, - lowLatencyMode = options.lowLatencyMode; + function ManifestFetcher(urls, pipelines, settings) { + var _this; + _this = _EventEmitter.call(this) || this; + _this.scheduleManualRefresh = noop/* default */.Z; + _this._manifestUrls = urls; + _this._pipelines = pipelines.manifest; + _this._settings = settings; + _this._canceller = new task_canceller/* default */.ZP(); + _this._isStarted = false; + _this._isRefreshPending = false; + _this._consecutiveUnsafeMode = 0; + _this._prioritizedContentUrl = null; + return _this; + } /** - * Returns Object emitting Representation estimates as well as callbacks - * allowing to helping it produce them. + * Free resources and stop refresh mechanism from happening. * - * @see IRepresentationEstimator - * @param {Object} context - * @param {Object} currentRepresentation - * @param {Object} representations - * @param {Object} playbackObserver - * @param {Object} stopAllEstimates - * @returns {Array.} + * Once `dispose` has been called. This `ManifestFetcher` cannot be relied on + * anymore. */ - return function getEstimates(context, currentRepresentation, representations, playbackObserver, stopAllEstimates) { - var type = context.adaptation.type; - var bandwidthEstimator = _getBandwidthEstimator(type); - var manualBitrate = (0,take_first_set/* default */.Z)(manualBitrates[type], (0,reference/* default */.ZP)(-1)); - var minAutoBitrate = (0,take_first_set/* default */.Z)(minAutoBitrates[type], (0,reference/* default */.ZP)(0)); - var maxAutoBitrate = (0,take_first_set/* default */.Z)(maxAutoBitrates[type], (0,reference/* default */.ZP)(Infinity)); - var initialBitrate = (0,take_first_set/* default */.Z)(initialBitrates[type], 0); - var filters = { - limitWidth: (0,take_first_set/* default */.Z)(throttlers.limitWidth[type], (0,reference/* default */.ZP)(undefined)), - throttleBitrate: (0,take_first_set/* default */.Z)(throttlers.throttleBitrate[type], throttlers.throttle[type], (0,reference/* default */.ZP)(Infinity)) - }; - return getEstimateReference({ - bandwidthEstimator: bandwidthEstimator, - context: context, - currentRepresentation: currentRepresentation, - filters: filters, - initialBitrate: initialBitrate, - manualBitrate: manualBitrate, - minAutoBitrate: minAutoBitrate, - maxAutoBitrate: maxAutoBitrate, - playbackObserver: playbackObserver, - representations: representations, - lowLatencyMode: lowLatencyMode - }, stopAllEstimates); - }; + var _proto = ManifestFetcher.prototype; + _proto.dispose = function dispose() { + this._canceller.cancel(); + this.removeEventListener(); + } /** - * Returns interface allowing to estimate network throughtput for a given type. - * @param {string} bufferType - * @returns {Object} - */ - function _getBandwidthEstimator(bufferType) { - var originalBandwidthEstimator = bandwidthEstimators[bufferType]; - if (originalBandwidthEstimator == null) { - log/* default.debug */.Z.debug("ABR: Creating new BandwidthEstimator for ", bufferType); - var bandwidthEstimator = new BandwidthEstimator(); - bandwidthEstimators[bufferType] = bandwidthEstimator; - return bandwidthEstimator; + * Start requesting the Manifest as well as the Manifest refreshing logic, if + * needed. + * + * Once `start` has been called, this mechanism can only be stopped by calling + * `dispose`. + */; + _proto.start = function start() { + var _this2 = this; + if (this._isStarted) { + return; } - return originalBandwidthEstimator; - } -} -/** - * Estimate regularly the current network bandwidth and the best Representation - * that can be played according to the current network and playback conditions. - * - * `getEstimateReference` only does estimations for a given type (e.g. - * "audio", "video" etc.) and Period. - * - * If estimates for multiple types and/or Periods are needed, you should - * call `getEstimateReference` as many times. - * - * This function returns a tuple: - * - the first element being the object through which estimates will be produced - * - the second element being callbacks that have to be triggered at various - * events to help it doing those estimates. - * - * @param {Object} args - * @param {Object} stopAllEstimates - * @returns {Array.} - */ -function getEstimateReference(_ref, stopAllEstimates) { - var bandwidthEstimator = _ref.bandwidthEstimator, - context = _ref.context, - currentRepresentation = _ref.currentRepresentation, - filters = _ref.filters, - initialBitrate = _ref.initialBitrate, - lowLatencyMode = _ref.lowLatencyMode, - manualBitrate = _ref.manualBitrate, - maxAutoBitrate = _ref.maxAutoBitrate, - minAutoBitrate = _ref.minAutoBitrate, - playbackObserver = _ref.playbackObserver, - representationsRef = _ref.representations; - var scoreCalculator = new RepresentationScoreCalculator(); - var networkAnalyzer = new NetworkAnalyzer(initialBitrate !== null && initialBitrate !== void 0 ? initialBitrate : 0, lowLatencyMode); - var requestsStore = new PendingRequestsStore(); - var onAddedSegment = noop/* default */.Z; - var callbacks = { - metrics: onMetric, - requestBegin: onRequestBegin, - requestProgress: onRequestProgress, - requestEnd: onRequestEnd, - addedSegment: function addedSegment(val) { - onAddedSegment(val); + this._isStarted = true; + var manifestProm; + var initialManifest = this._settings.initialManifest; + if (initialManifest instanceof manifest/* default */.ZP) { + manifestProm = Promise.resolve({ + manifest: initialManifest + }); + } else if (initialManifest !== undefined) { + manifestProm = this.parse(initialManifest, { + previousManifest: null, + unsafeMode: false + }, undefined); + } else { + manifestProm = this._fetchManifest(undefined).then(function (val) { + return val.parse({ + previousManifest: null, + unsafeMode: false + }); + }); } - }; + manifestProm.then(function (val) { + _this2.trigger("manifestReady", val.manifest); + if (!_this2._canceller.isUsed()) { + _this2._recursivelyRefreshManifest(val.manifest, val); + } + })["catch"](function (err) { + return _this2._onFatalError(err); + }); + } /** - * `TaskCanceller` allowing to stop producing estimate. - * This TaskCanceller is used both for restarting estimates with a new - * configuration and to cancel them altogether. - */ - var currentEstimatesCanceller = new task_canceller/* default */.ZP({ - cancelOn: stopAllEstimates - }); - // Create `ISharedReference` on which estimates will be emitted. - var estimateRef = createEstimateReference(manualBitrate.getValue(), representationsRef.getValue(), currentEstimatesCanceller.signal); - manualBitrate.onUpdate(restartEstimatesProductionFromCurrentConditions, { - clearSignal: stopAllEstimates - }); - representationsRef.onUpdate(restartEstimatesProductionFromCurrentConditions, { - clearSignal: stopAllEstimates - }); - return { - estimates: estimateRef, - callbacks: callbacks - }; - function createEstimateReference(manualBitrateVal, representations, innerCancellationSignal) { - if (manualBitrateVal >= 0) { - // A manual bitrate has been set. Just choose Representation according to it. - var manualRepresentation = selectOptimalRepresentation(representations, manualBitrateVal, 0, Infinity); - return (0,reference/* default */.ZP)({ - representation: manualRepresentation, - bitrate: undefined, - knownStableBitrate: undefined, - manual: true, - urgent: true // a manual bitrate switch should happen immediately + * Update URL of the fetched Manifest. + * @param {Array. | undefined} urls - New Manifest URLs by order of + * priority or `undefined` if there's now no URL. + * @param {boolean} refreshNow - If set to `true`, the next Manifest refresh + * will be triggered immediately. + */; + _proto.updateContentUrls = function updateContentUrls(urls, refreshNow) { + var _a; + this._prioritizedContentUrl = (_a = urls === null || urls === void 0 ? void 0 : urls[0]) !== null && _a !== void 0 ? _a : undefined; + if (refreshNow) { + this.scheduleManualRefresh({ + enablePartialRefresh: false, + delay: 0, + canUseUnsafeMode: false }); } - - if (representations.length === 1) { - // There's only a single Representation. Just choose it. - return (0,reference/* default */.ZP)({ - bitrate: undefined, - representation: representations[0], - manual: false, - urgent: true, - knownStableBitrate: undefined - }); + } + /** + * (re-)Load the Manifest. + * This method does not yet parse it, parsing will then be available through + * a callback available on the response. + * + * You can set an `url` on which that Manifest will be requested. + * If not set, the regular Manifest url - defined on the `ManifestFetcher` + * instanciation - will be used instead. + * + * @param {string | undefined} url + * @returns {Promise} + */; + _proto._fetchManifest = + /*#__PURE__*/ + function () { + var _fetchManifest2 = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(url) { + var _this3 = this; + var _a, cancelSignal, settings, pipelines, requestUrl, backoffSettings, loadingPromise, response, callResolverWithRetries, callLoaderWithRetries; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + callLoaderWithRetries = function _callLoaderWithRetrie(manifestUrl) { + var loadManifest = pipelines.loadManifest; + var requestTimeout = (0,is_null_or_undefined/* default */.Z)(settings.requestTimeout) ? config/* default.getCurrent */.Z.getCurrent().DEFAULT_REQUEST_TIMEOUT : settings.requestTimeout; + if (requestTimeout < 0) { + requestTimeout = undefined; + } + var callLoader = function callLoader() { + return loadManifest(manifestUrl, { + timeout: requestTimeout + }, cancelSignal); + }; + return scheduleRequestPromise(callLoader, backoffSettings, cancelSignal); + }; + callResolverWithRetries = function _callResolverWithRetr(resolverUrl) { + var resolveManifestUrl = pipelines.resolveManifestUrl; + (0,assert/* default */.Z)(resolveManifestUrl !== undefined); + var callResolver = function callResolver() { + return resolveManifestUrl(resolverUrl, cancelSignal); + }; + return scheduleRequestPromise(callResolver, backoffSettings, cancelSignal); + }; + cancelSignal = this._canceller.signal; + settings = this._settings; + pipelines = this._pipelines; // TODO Better handle multiple Manifest URLs + requestUrl = url !== null && url !== void 0 ? url : (_a = this._manifestUrls) === null || _a === void 0 ? void 0 : _a[0]; + backoffSettings = this._getBackoffSetting(function (err) { + _this3.trigger("warning", errorSelector(err)); + }); + loadingPromise = pipelines.resolveManifestUrl === undefined ? callLoaderWithRetries(requestUrl) : callResolverWithRetries(requestUrl).then(callLoaderWithRetries); + _context.prev = 8; + _context.next = 11; + return loadingPromise; + case 11: + response = _context.sent; + return _context.abrupt("return", { + parse: function parse(parserOptions) { + return _this3._parseLoadedManifest(response, parserOptions, requestUrl); + } + }); + case 15: + _context.prev = 15; + _context.t0 = _context["catch"](8); + throw errorSelector(_context.t0); + case 18: + case "end": + return _context.stop(); + } + } + }, _callee, this, [[8, 15]]); + })); + function _fetchManifest(_x) { + return _fetchManifest2.apply(this, arguments); } - /** If true, Representation estimates based on the buffer health might be used. */ - var allowBufferBasedEstimates = false; + return _fetchManifest; + }() + /** + * Parse an already loaded Manifest. + * + * This method should be reserved for Manifests for which no request has been + * done. + * In other cases, it's preferable to go through the `fetch` method, so + * information on the request can be used by the parsing process. + * @param {*} manifest + * @param {Object} parserOptions + * @param {string | undefined} originalUrl + * @returns {Promise} + */ + ; + _proto.parse = function parse(manifest, parserOptions, originalUrl) { + return this._parseLoadedManifest({ + responseData: manifest, + size: undefined, + requestDuration: undefined + }, parserOptions, originalUrl); + } + /** + * Parse a Manifest. + * + * @param {Object} loaded - Information about the loaded Manifest as well as + * about the corresponding request. + * @param {Object} parserOptions - Options used when parsing the Manifest. + * @param {string | undefined} requestUrl + * @returns {Promise} + */; + _proto._parseLoadedManifest = + /*#__PURE__*/ + function () { + var _parseLoadedManifest2 = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee3(loaded, parserOptions, requestUrl) { + var _this4 = this; + var _a, parsingTimeStart, cancelSignal, trigger, sendingTime, receivedTime, backoffSettings, originalUrl, opts, res, _yield$res, manifest, formattedError, scheduleRequest, _scheduleRequest, onWarnings, finish; + return regenerator_default().wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + finish = function _finish(manifest) { + onWarnings(manifest.contentWarnings); + var parsingTime = performance.now() - parsingTimeStart; + log/* default.info */.Z.info("MF: Manifest parsed in " + parsingTime + "ms"); + return { + manifest: manifest, + sendingTime: sendingTime, + receivedTime: receivedTime, + parsingTime: parsingTime + }; + }; + onWarnings = function _onWarnings(warnings) { + for (var _iterator = manifest_fetcher_createForOfIteratorHelperLoose(warnings), _step; !(_step = _iterator()).done;) { + var warning = _step.value; + if (cancelSignal.isCancelled()) { + return; + } + var _formattedError = (0,format_error/* default */.Z)(warning, { + defaultCode: "PIPELINE_PARSE_ERROR", + defaultReason: "Unknown error when parsing the Manifest" + }); + trigger("warning", _formattedError); + } + }; + _scheduleRequest = function _scheduleRequest3() { + _scheduleRequest = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee2(performRequest) { + var data; + return regenerator_default().wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.prev = 0; + _context2.next = 3; + return scheduleRequestPromise(performRequest, backoffSettings, cancelSignal); + case 3: + data = _context2.sent; + return _context2.abrupt("return", data); + case 7: + _context2.prev = 7; + _context2.t0 = _context2["catch"](0); + throw errorSelector(_context2.t0); + case 10: + case "end": + return _context2.stop(); + } + } + }, _callee2, null, [[0, 7]]); + })); + return _scheduleRequest.apply(this, arguments); + }; + scheduleRequest = function _scheduleRequest2(_x5) { + return _scheduleRequest.apply(this, arguments); + }; + parsingTimeStart = performance.now(); + cancelSignal = this._canceller.signal; + trigger = this.trigger.bind(this); + sendingTime = loaded.sendingTime, receivedTime = loaded.receivedTime; + backoffSettings = this._getBackoffSetting(function (err) { + _this4.trigger("warning", errorSelector(err)); + }); + originalUrl = requestUrl !== null && requestUrl !== void 0 ? requestUrl : (_a = this._manifestUrls) === null || _a === void 0 ? void 0 : _a[0]; + opts = { + externalClockOffset: parserOptions.externalClockOffset, + unsafeMode: parserOptions.unsafeMode, + previousManifest: parserOptions.previousManifest, + originalUrl: originalUrl + }; + _context3.prev = 11; + res = this._pipelines.parseManifest(loaded, opts, onWarnings, cancelSignal, scheduleRequest); + if (isPromise(res)) { + _context3.next = 17; + break; + } + return _context3.abrupt("return", finish(res.manifest)); + case 17: + _context3.next = 19; + return res; + case 19: + _yield$res = _context3.sent; + manifest = _yield$res.manifest; + return _context3.abrupt("return", finish(manifest)); + case 22: + _context3.next = 28; + break; + case 24: + _context3.prev = 24; + _context3.t0 = _context3["catch"](11); + formattedError = (0,format_error/* default */.Z)(_context3.t0, { + defaultCode: "PIPELINE_PARSE_ERROR", + defaultReason: "Unknown error when parsing the Manifest" + }); + throw formattedError; + case 28: + case "end": + return _context3.stop(); + } + } + }, _callee3, this, [[11, 24]]); + })); + function _parseLoadedManifest(_x2, _x3, _x4) { + return _parseLoadedManifest2.apply(this, arguments); + } + return _parseLoadedManifest; + }() + /** + * Construct "backoff settings" that can be used with a range of functions + * allowing to perform multiple request attempts + * @param {Function} onRetry + * @returns {Object} + */ + ; + _proto._getBackoffSetting = function _getBackoffSetting(onRetry) { + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + DEFAULT_MAX_MANIFEST_REQUEST_RETRY = _config$getCurrent.DEFAULT_MAX_MANIFEST_REQUEST_RETRY, + DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE = _config$getCurrent.DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE, + INITIAL_BACKOFF_DELAY_BASE = _config$getCurrent.INITIAL_BACKOFF_DELAY_BASE, + MAX_BACKOFF_DELAY_BASE = _config$getCurrent.MAX_BACKOFF_DELAY_BASE; + var _this$_settings = this._settings, + lowLatencyMode = _this$_settings.lowLatencyMode, + ogRegular = _this$_settings.maxRetryRegular, + ogOffline = _this$_settings.maxRetryOffline; + var baseDelay = lowLatencyMode ? INITIAL_BACKOFF_DELAY_BASE.LOW_LATENCY : INITIAL_BACKOFF_DELAY_BASE.REGULAR; + var maxDelay = lowLatencyMode ? MAX_BACKOFF_DELAY_BASE.LOW_LATENCY : MAX_BACKOFF_DELAY_BASE.REGULAR; + var maxRetryRegular = ogRegular !== null && ogRegular !== void 0 ? ogRegular : DEFAULT_MAX_MANIFEST_REQUEST_RETRY; + var maxRetryOffline = ogOffline !== null && ogOffline !== void 0 ? ogOffline : DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE; + return { + onRetry: onRetry, + baseDelay: baseDelay, + maxDelay: maxDelay, + maxRetryRegular: maxRetryRegular, + maxRetryOffline: maxRetryOffline + }; + } + /** + * Performs Manifest refresh (recursively) when it judges it is time to do so. + * @param {Object} manifest + * @param {Object} manifestRequestInfos - Various information linked to the + * last Manifest loading and parsing operations. + */; + _proto._recursivelyRefreshManifest = function _recursivelyRefreshManifest(manifest, _ref) { + var _this5 = this; + var sendingTime = _ref.sendingTime, + parsingTime = _ref.parsingTime, + updatingTime = _ref.updatingTime; + var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), + MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE = _config$getCurrent2.MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE, + MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE = _config$getCurrent2.MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE; /** - * Current optimal Representation's bandwidth choosen by a buffer-based - * adaptive algorithm. + * Total time taken to fully update the last Manifest, in milliseconds. + * Note: this time also includes possible requests done by the parsers. */ - var currentBufferBasedEstimate; - var bitrates = representations.map(function (r) { - return r.bitrate; - }); + var totalUpdateTime = parsingTime !== undefined ? parsingTime + (updatingTime !== null && updatingTime !== void 0 ? updatingTime : 0) : undefined; /** - * Module calculating the optimal Representation based on the current - * buffer's health (i.e. whether enough data is buffered, history of - * buffer size etc.). + * "unsafeMode" is a mode where we unlock advanced Manifest parsing + * optimizations with the added risk to lose some information. + * `unsafeModeEnabled` is set to `true` when the `unsafeMode` is enabled. + * + * Only perform parsing in `unsafeMode` when the last full parsing took a + * lot of time and do not go higher than the maximum consecutive time. */ - var bufferBasedChooser = new BufferBasedChooser(bitrates); - /** Store the previous estimate made here. */ - var prevEstimate = new LastEstimateStorage(); + var unsafeModeEnabled = this._consecutiveUnsafeMode > 0 ? this._consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE : totalUpdateTime !== undefined ? totalUpdateTime >= MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE : false; + /** Time elapsed since the beginning of the Manifest request, in milliseconds. */ + var timeSinceRequest = sendingTime === undefined ? 0 : performance.now() - sendingTime; + /** Minimum update delay we should not go below, in milliseconds. */ + var minInterval = Math.max(this._settings.minimumManifestUpdateInterval - timeSinceRequest, 0); /** - * Module calculating the optimal Representation by "guessing it" with a - * step-by-step algorithm. - * Only used in very specific scenarios. + * Multiple refresh trigger are scheduled here, but only the first one should + * be effectively considered. + * `nextRefreshCanceller` will allow to cancel every other when one is triggered. */ - var guessBasedChooser = new GuessBasedChooser(scoreCalculator, prevEstimate); - // get initial observation for initial estimate - var lastPlaybackObservation = playbackObserver.getReference().getValue(); - /** Reference through which estimates are emitted. */ - var innerEstimateRef = (0,reference/* default */.ZP)(getCurrentEstimate()); - // subscribe to subsequent playback observations - playbackObserver.listen(function (obs) { - lastPlaybackObservation = obs; - updateEstimate(); - }, { - includeLastObservation: false, - clearSignal: innerCancellationSignal - }); - onAddedSegment = function onAddedSegment(val) { - if (lastPlaybackObservation === null) { - return; - } - var _lastPlaybackObservat = lastPlaybackObservation, - position = _lastPlaybackObservat.position, - speed = _lastPlaybackObservat.speed; - var timeRanges = val.buffered; - var bufferGap = (0,ranges/* getLeftSizeOfRange */.L7)(timeRanges, position.last); - var representation = val.content.representation; - var scoreData = scoreCalculator.getEstimate(representation); - var currentScore = scoreData === null || scoreData === void 0 ? void 0 : scoreData[0]; - var currentBitrate = representation.bitrate; - var observation = { - bufferGap: bufferGap, - currentBitrate: currentBitrate, - currentScore: currentScore, - speed: speed - }; - currentBufferBasedEstimate = bufferBasedChooser.getEstimate(observation); - updateEstimate(); + var nextRefreshCanceller = new task_canceller/* default */.ZP(); + nextRefreshCanceller.linkToSignal(this._canceller.signal); + /* Function to manually schedule a Manifest refresh */ + this.scheduleManualRefresh = function (settings) { + var enablePartialRefresh = settings.enablePartialRefresh, + delay = settings.delay, + canUseUnsafeMode = settings.canUseUnsafeMode; + var unsafeMode = canUseUnsafeMode && unsafeModeEnabled; + // The value allows to set a delay relatively to the last Manifest refresh + // (to avoid asking for it too often). + var timeSinceLastRefresh = sendingTime === undefined ? 0 : performance.now() - sendingTime; + var _minInterval = Math.max(_this5._settings.minimumManifestUpdateInterval - timeSinceLastRefresh, 0); + var timeoutId = setTimeout(function () { + nextRefreshCanceller.cancel(); + _this5._triggerNextManifestRefresh(manifest, { + enablePartialRefresh: enablePartialRefresh, + unsafeMode: unsafeMode + }); + }, Math.max((delay !== null && delay !== void 0 ? delay : 0) - timeSinceLastRefresh, _minInterval)); + nextRefreshCanceller.signal.register(function () { + clearTimeout(timeoutId); + }); }; - minAutoBitrate.onUpdate(updateEstimate, { - clearSignal: innerCancellationSignal - }); - maxAutoBitrate.onUpdate(updateEstimate, { - clearSignal: innerCancellationSignal - }); - filters.limitWidth.onUpdate(updateEstimate, { - clearSignal: innerCancellationSignal - }); - filters.limitWidth.onUpdate(updateEstimate, { - clearSignal: innerCancellationSignal - }); - return innerEstimateRef; - function updateEstimate() { - innerEstimateRef.setValue(getCurrentEstimate()); + /* Handle Manifest expiration. */ + if (manifest.expired !== null) { + var timeoutId = setTimeout(function () { + var _a; + (_a = manifest.expired) === null || _a === void 0 ? void 0 : _a.then(function () { + nextRefreshCanceller.cancel(); + _this5._triggerNextManifestRefresh(manifest, { + enablePartialRefresh: false, + unsafeMode: unsafeModeEnabled + }); + }, noop/* default */.Z /* `expired` should not reject */); + }, minInterval); + nextRefreshCanceller.signal.register(function () { + clearTimeout(timeoutId); + }); } - /** Returns the actual estimate based on all methods and algorithm available. */ - function getCurrentEstimate() { - var _lastPlaybackObservat2 = lastPlaybackObservation, - bufferGap = _lastPlaybackObservat2.bufferGap, - position = _lastPlaybackObservat2.position, - maximumPosition = _lastPlaybackObservat2.maximumPosition; - var widthLimit = filters.limitWidth.getValue(); - var bitrateThrottle = filters.throttleBitrate.getValue(); - var currentRepresentationVal = currentRepresentation.getValue(); - var minAutoBitrateVal = minAutoBitrate.getValue(); - var maxAutoBitrateVal = maxAutoBitrate.getValue(); - var filteredReps = getFilteredRepresentations(representations, widthLimit, bitrateThrottle); - var requests = requestsStore.getRequests(); - var _networkAnalyzer$getB = networkAnalyzer.getBandwidthEstimate(lastPlaybackObservation, bandwidthEstimator, currentRepresentationVal, requests, prevEstimate.bandwidth), - bandwidthEstimate = _networkAnalyzer$getB.bandwidthEstimate, - bitrateChosen = _networkAnalyzer$getB.bitrateChosen; - var stableRepresentation = scoreCalculator.getLastStableRepresentation(); - var knownStableBitrate = stableRepresentation === null ? undefined : stableRepresentation.bitrate / (lastPlaybackObservation.speed > 0 ? lastPlaybackObservation.speed : 1); - if (allowBufferBasedEstimates && bufferGap <= 5) { - allowBufferBasedEstimates = false; - } else if (!allowBufferBasedEstimates && isFinite(bufferGap) && bufferGap > 10) { - allowBufferBasedEstimates = true; - } - /** - * Representation chosen when considering only [pessimist] bandwidth - * calculation. - * This is a safe enough choice but might be lower than what the user - * could actually profit from. - */ - var chosenRepFromBandwidth = selectOptimalRepresentation(filteredReps, bitrateChosen, minAutoBitrateVal, maxAutoBitrateVal); - var currentBestBitrate = chosenRepFromBandwidth.bitrate; - /** - * Representation chosen when considering the current buffer size. - * If defined, takes precedence over `chosenRepFromBandwidth`. - * - * This is a very safe choice, yet it is very slow and might not be - * adapted to cases where a buffer cannot be build, such as live contents. - * - * `null` if this buffer size mode is not enabled or if we don't have a - * choice from it yet. - */ - var chosenRepFromBufferSize = null; - if (allowBufferBasedEstimates && currentBufferBasedEstimate !== undefined && currentBufferBasedEstimate > currentBestBitrate) { - chosenRepFromBufferSize = selectOptimalRepresentation(filteredReps, currentBufferBasedEstimate, minAutoBitrateVal, maxAutoBitrateVal); - currentBestBitrate = chosenRepFromBufferSize.bitrate; - } - /** - * Representation chosen by the more adventurous `GuessBasedChooser`, - * which iterates through Representations one by one until finding one - * that cannot be "maintained". - * - * If defined, takes precedence over both `chosenRepFromBandwidth` and - * `chosenRepFromBufferSize`. - * - * This is the riskiest choice (in terms of rebuffering chances) but is - * only enabled when no other solution is adapted (for now, this just - * applies for low-latency contents when playing close to the live - * edge). - * - * `null` if not enabled or if there's currently no guess. - */ - var chosenRepFromGuessMode = null; - if (lowLatencyMode && currentRepresentationVal !== null && context.manifest.isDynamic && maximumPosition - position.last < 40) { - chosenRepFromGuessMode = guessBasedChooser.getGuess(representations, lastPlaybackObservation, currentRepresentationVal, currentBestBitrate, requests); - } - if (chosenRepFromGuessMode !== null && chosenRepFromGuessMode.bitrate > currentBestBitrate) { - log/* default.debug */.Z.debug("ABR: Choosing representation with guess-based estimation.", chosenRepFromGuessMode.bitrate, chosenRepFromGuessMode.id); - prevEstimate.update(chosenRepFromGuessMode, bandwidthEstimate, 2 /* ABRAlgorithmType.GuessBased */); - return { - bitrate: bandwidthEstimate, - representation: chosenRepFromGuessMode, - urgent: currentRepresentationVal === null || chosenRepFromGuessMode.bitrate < currentRepresentationVal.bitrate, - manual: false, - knownStableBitrate: knownStableBitrate - }; - } else if (chosenRepFromBufferSize !== null) { - log/* default.debug */.Z.debug("ABR: Choosing representation with buffer-based estimation.", chosenRepFromBufferSize.bitrate, chosenRepFromBufferSize.id); - prevEstimate.update(chosenRepFromBufferSize, bandwidthEstimate, 0 /* ABRAlgorithmType.BufferBased */); - return { - bitrate: bandwidthEstimate, - representation: chosenRepFromBufferSize, - urgent: networkAnalyzer.isUrgent(chosenRepFromBufferSize.bitrate, currentRepresentationVal, requests, lastPlaybackObservation), - manual: false, - knownStableBitrate: knownStableBitrate - }; + /* + * Trigger Manifest refresh when the Manifest needs to be refreshed + * according to the Manifest's internal properties (parsing time is also + * taken into account in this operation to avoid refreshing too often). + */ + if (manifest.lifetime !== undefined && manifest.lifetime >= 0) { + /** Regular refresh delay as asked by the Manifest. */ + var regularRefreshDelay = manifest.lifetime * 1000 - timeSinceRequest; + /** Actually choosen delay to refresh the Manifest. */ + var actualRefreshInterval; + if (totalUpdateTime === undefined) { + actualRefreshInterval = regularRefreshDelay; + } else if (manifest.lifetime < 3 && totalUpdateTime >= 100) { + // If Manifest update is very frequent and we take time to update it, + // postpone it. + actualRefreshInterval = Math.min(Math.max( + // Take 3 seconds as a default safe value for a base interval. + 3000 - timeSinceRequest, + // Add update time to the original interval. + Math.max(regularRefreshDelay, 0) + totalUpdateTime), + // Limit the postponment's higher bound to a very high value relative + // to `regularRefreshDelay`. + // This avoid perpetually postponing a Manifest update when + // performance seems to have been abysmal one time. + regularRefreshDelay * 6); + log/* default.info */.Z.info("MUS: Manifest update rythm is too frequent. Postponing next request.", regularRefreshDelay, actualRefreshInterval); + } else if (totalUpdateTime >= manifest.lifetime * 1000 / 10) { + // If Manifest updating time is very long relative to its lifetime, + // postpone it: + actualRefreshInterval = Math.min( + // Just add the update time to the original waiting time + Math.max(regularRefreshDelay, 0) + totalUpdateTime, + // Limit the postponment's higher bound to a very high value relative + // to `regularRefreshDelay`. + // This avoid perpetually postponing a Manifest update when + // performance seems to have been abysmal one time. + regularRefreshDelay * 6); + log/* default.info */.Z.info("MUS: Manifest took too long to parse. Postponing next request", actualRefreshInterval, actualRefreshInterval); } else { - log/* default.debug */.Z.debug("ABR: Choosing representation with bandwidth estimation.", chosenRepFromBandwidth.bitrate, chosenRepFromBandwidth.id); - prevEstimate.update(chosenRepFromBandwidth, bandwidthEstimate, 1 /* ABRAlgorithmType.BandwidthBased */); - return { - bitrate: bandwidthEstimate, - representation: chosenRepFromBandwidth, - urgent: networkAnalyzer.isUrgent(chosenRepFromBandwidth.bitrate, currentRepresentationVal, requests, lastPlaybackObservation), - manual: false, - knownStableBitrate: knownStableBitrate - }; + actualRefreshInterval = regularRefreshDelay; } + var _timeoutId = setTimeout(function () { + nextRefreshCanceller.cancel(); + _this5._triggerNextManifestRefresh(manifest, { + enablePartialRefresh: false, + unsafeMode: unsafeModeEnabled + }); + }, Math.max(actualRefreshInterval, minInterval)); + nextRefreshCanceller.signal.register(function () { + clearTimeout(_timeoutId); + }); } } /** - * Stop previous estimate production (if one) and restart it considering new - * conditions (such as a manual bitrate and/or a new list of Representations). - */ - function restartEstimatesProductionFromCurrentConditions() { - var manualBitrateVal = manualBitrate.getValue(); - var representations = representationsRef.getValue(); - currentEstimatesCanceller.cancel(); - currentEstimatesCanceller = new task_canceller/* default */.ZP({ - cancelOn: stopAllEstimates - }); - var newRef = createEstimateReference(manualBitrateVal, representations, currentEstimatesCanceller.signal); - newRef.onUpdate(function onNewEstimate(newEstimate) { - estimateRef.setValue(newEstimate); - }, { - clearSignal: currentEstimatesCanceller.signal, - emitCurrentValue: true - }); - } - /** - * Callback to call when new metrics are available - * @param {Object} value - */ - function onMetric(value) { - var requestDuration = value.requestDuration, - segmentDuration = value.segmentDuration, - size = value.size, - content = value.content; - // calculate bandwidth - bandwidthEstimator.addSample(requestDuration, size); - if (!content.segment.isInit) { - // calculate "maintainability score" - var segment = content.segment, - representation = content.representation; - if (segmentDuration === undefined && !segment.complete) { - // We cannot know the real duration of the segment - return; + * Refresh the Manifest, performing a full update if a partial update failed. + * Also re-call `recursivelyRefreshManifest` to schedule the next refresh + * trigger. + * @param {Object} manifest + * @param {Object} refreshInformation + */; + _proto._triggerNextManifestRefresh = function _triggerNextManifestRefresh(manifest, _ref2) { + var _this6 = this; + var enablePartialRefresh = _ref2.enablePartialRefresh, + unsafeMode = _ref2.unsafeMode; + var manifestUpdateUrl = manifest.updateUrl; + var fullRefresh; + var refreshURL; + if (this._prioritizedContentUrl !== null) { + fullRefresh = true; + refreshURL = this._prioritizedContentUrl; + this._prioritizedContentUrl = null; + } else { + fullRefresh = !enablePartialRefresh || manifestUpdateUrl === undefined; + refreshURL = fullRefresh ? manifest.getUrl() : manifestUpdateUrl; + } + var externalClockOffset = manifest.clockOffset; + if (unsafeMode) { + this._consecutiveUnsafeMode += 1; + log/* default.info */.Z.info("Init: Refreshing the Manifest in \"unsafeMode\" for the " + String(this._consecutiveUnsafeMode) + " consecutive time."); + } else if (this._consecutiveUnsafeMode > 0) { + log/* default.info */.Z.info("Init: Not parsing the Manifest in \"unsafeMode\" anymore after " + String(this._consecutiveUnsafeMode) + " consecutive times."); + this._consecutiveUnsafeMode = 0; + } + if (this._isRefreshPending) { + return; + } + this._isRefreshPending = true; + this._fetchManifest(refreshURL).then(function (res) { + return res.parse({ + externalClockOffset: externalClockOffset, + previousManifest: manifest, + unsafeMode: unsafeMode + }); + }).then(function (res) { + _this6._isRefreshPending = false; + var newManifest = res.manifest, + newSendingTime = res.sendingTime, + parsingTime = res.parsingTime; + var updateTimeStart = performance.now(); + if (fullRefresh) { + manifest.replace(newManifest); + } else { + try { + manifest.update(newManifest); + } catch (e) { + var message = e instanceof Error ? e.message : "unknown error"; + log/* default.warn */.Z.warn("MUS: Attempt to update Manifest failed: " + message, "Re-downloading the Manifest fully"); + var _config$getCurrent3 = config/* default.getCurrent */.Z.getCurrent(), + FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY = _config$getCurrent3.FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY; + // The value allows to set a delay relatively to the last Manifest refresh + // (to avoid asking for it too often). + var timeSinceLastRefresh = newSendingTime === undefined ? 0 : performance.now() - newSendingTime; + var _minInterval = Math.max(_this6._settings.minimumManifestUpdateInterval - timeSinceLastRefresh, 0); + var unregisterCanceller = noop/* default */.Z; + var timeoutId = setTimeout(function () { + unregisterCanceller(); + _this6._triggerNextManifestRefresh(manifest, { + enablePartialRefresh: false, + unsafeMode: false + }); + }, Math.max(FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY - timeSinceLastRefresh, _minInterval)); + unregisterCanceller = _this6._canceller.signal.register(function () { + clearTimeout(timeoutId); + }); + return; + } } - var segDur = segmentDuration !== null && segmentDuration !== void 0 ? segmentDuration : segment.duration; - scoreCalculator.addSample(representation, requestDuration / 1000, segDur); + var updatingTime = performance.now() - updateTimeStart; + _this6._recursivelyRefreshManifest(manifest, { + sendingTime: newSendingTime, + parsingTime: parsingTime, + updatingTime: updatingTime + }); + })["catch"](function (err) { + _this6._isRefreshPending = false; + _this6._onFatalError(err); + }); + }; + _proto._onFatalError = function _onFatalError(err) { + if (this._canceller.isUsed()) { + return; } - } - /** Callback called when a new request begins. */ - function onRequestBegin(val) { - requestsStore.add(val); - } - /** Callback called when progress information is known on a pending request. */ - function onRequestProgress(val) { - requestsStore.addProgress(val); - } - /** Callback called when a pending request ends. */ - function onRequestEnd(val) { - requestsStore.remove(val.id); - } -} + this.trigger("error", err); + this.dispose(); + }; + return ManifestFetcher; +}(event_emitter/* default */.Z); /** - * Filter representations given through filters options. - * @param {Array.} representations - * @param {number | undefined} widthLimit - Filter Object. - * @returns {Array.} + * Returns `true` when the returned value seems to be a Promise instance, as + * created by the RxPlayer. + * @param {*} val + * @returns {boolean} */ -function getFilteredRepresentations(representations, widthLimit, bitrateThrottle) { - var filteredReps = representations; - if (bitrateThrottle < Infinity) { - filteredReps = filterByBitrate(filteredReps, bitrateThrottle); - } - if (widthLimit !== undefined) { - filteredReps = filterByWidth(filteredReps, widthLimit); - } - return filteredReps; + +function isPromise(val) { + return val instanceof Promise; } -;// CONCATENATED MODULE: ./src/core/adaptive/index.ts +;// CONCATENATED MODULE: ./src/core/fetchers/manifest/index.ts /** * Copyright 2015 CANAL+ Group * @@ -44846,7 +41321,7 @@ function getFilteredRepresentations(representations, widthLimit, bitrateThrottle * limitations under the License. */ -/* harmony default export */ var adaptive = (createAdaptiveRepresentationSelector); +/* harmony default export */ var fetchers_manifest = (ManifestFetcher); ;// CONCATENATED MODULE: ./src/core/fetchers/cdn_prioritizer.ts function cdn_prioritizer_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = cdn_prioritizer_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } @@ -44871,13 +41346,17 @@ function cdn_prioritizer_arrayLikeToArray(arr, len) { if (len == null || len > a /** - * Class signaling the priority between multiple CDN available for any given - * resource. + * Class storing and signaling the priority between multiple CDN available for + * any given resource. * - * This class might perform requests and schedule timeouts by itself to keep its - * internal list of CDN priority up-to-date. - * When it is not needed anymore, you should call the `dispose` method to clear - * all resources. + * This class was first created to implement the complexities behind + * Content Steering features, though its handling hasn't been added yet as we + * wait for its specification to be both standardized and relied on in the wild. + * In the meantime, it acts as an abstraction for the simple concept of + * avoiding to request a CDN for any segment when an issue is encountered with + * one (e.g. HTTP 500 statuses) and several CDN exist for a given resource. It + * should be noted that this is also one of the planified features of the + * Content Steering specification. * * @class CdnPrioritizer */ @@ -45095,8 +41574,6 @@ function applyPrioritizerToSegmentFetcher(prioritizer, fetcher) { var utils = __webpack_require__(520); // EXTERNAL MODULE: ./src/utils/array_includes.ts var array_includes = __webpack_require__(7714); -// EXTERNAL MODULE: ./src/utils/id_generator.ts -var id_generator = __webpack_require__(908); ;// CONCATENATED MODULE: ./src/utils/initialization_segment_cache.ts /** * Copyright 2015 CANAL+ Group @@ -45190,10 +41667,22 @@ var generateRequestID = (0,id_generator/* default */.Z)(); * An `ISegmentFetcher` also implements a retry mechanism, based on the given * `options` argument, which may retry a segment request when it fails. * - * @param {string} bufferType - * @param {Object} pipeline - * @param {Object} lifecycleCallbacks - * @param {Object} options + * @param {string} bufferType - Type of buffer concerned (e.g. `"audio"`, + * `"video"`, `"text" etc.) + * @param {Object} pipeline - The transport-specific logic allowing to load + * segments of the given buffer type and transport protocol (e.g. DASH). + * @param {Object|null} cdnPrioritizer - Abstraction allowing to synchronize, + * update and keep track of the priorization of the CDN to use to load any given + * segment, in cases where multiple ones are available. + * + * Can be set to `null` in which case a minimal priorization logic will be used + * instead. + * @param {Object} lifecycleCallbacks - Callbacks that can be registered to be + * informed when new requests are made, ended, new metrics are available etc. + * This should be mainly useful to implement an adaptive logic relying on those + * metrics and events. + * @param {Object} options - Various tweaking options allowing to configure the + * behavior of the returned `ISegmentFetcher`. * @returns {Function} */ function segment_fetcher_createSegmentFetcher(bufferType, pipeline, cdnPrioritizer, lifecycleCallbacks, options) { @@ -45217,7 +41706,7 @@ function segment_fetcher_createSegmentFetcher(bufferType, pipeline, cdnPrioritiz */ return /*#__PURE__*/function () { var _fetchSegment = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(content, fetcherCallbacks, cancellationSignal) { - var _a, _b, segmentIdString, requestId, requestInfo, parsedChunks, segmentDurationAcc, metricsSent, loaderCallbacks, cached, res, loadedData, callLoaderWithUrl, generateParserFunction, onRetry, sendNetworkMetricsIfAvailable; + var _a, _b, _c, segmentIdString, requestId, requestInfo, parsedChunks, segmentDurationAcc, metricsSent, loaderCallbacks, cached, res, loadedData, onCancellation, callLoaderWithUrl, generateParserFunction, onRetry, sendNetworkMetricsIfAvailable; return regenerator_default().wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -45269,6 +41758,18 @@ function segment_fetcher_createSegmentFetcher(bufferType, pipeline, cdnPrioritiz callLoaderWithUrl = function _callLoaderWithUrl(cdnMetadata) { return loadSegment(cdnMetadata, content, requestOptions, cancellationSignal, loaderCallbacks); }; + onCancellation = function _onCancellation() { + var _a; + if (requestInfo !== undefined) { + return; // Request already terminated + } + + log/* default.debug */.Z.debug("SF: Segment request cancelled", segmentIdString); + requestInfo = null; + (_a = lifecycleCallbacks.onRequestEnd) === null || _a === void 0 ? void 0 : _a.call(lifecycleCallbacks, { + id: requestId + }); + }; // used by logs segmentIdString = (0,utils/* getLoggableSegmentId */.K)(content); requestId = generateRequestID(); @@ -45332,37 +41833,26 @@ function segment_fetcher_createSegmentFetcher(bufferType, pipeline, cdnPrioritiz }; // Retrieve from cache if it exists cached = cache !== undefined ? cache.get(content) : null; if (!(cached !== null)) { - _context.next = 15; + _context.next = 16; break; } log/* default.debug */.Z.debug("SF: Found wanted segment in cache", segmentIdString); fetcherCallbacks.onChunk(generateParserFunction(cached, false)); return _context.abrupt("return", Promise.resolve()); - case 15: + case 16: log/* default.debug */.Z.debug("SF: Beginning request", segmentIdString); (_a = lifecycleCallbacks.onRequestBegin) === null || _a === void 0 ? void 0 : _a.call(lifecycleCallbacks, { requestTimestamp: performance.now(), id: requestId, content: content }); - cancellationSignal.register(function () { - var _a; - if (requestInfo !== undefined) { - return; // Request already terminated - } - - log/* default.debug */.Z.debug("SF: Segment request cancelled", segmentIdString); - requestInfo = null; - (_a = lifecycleCallbacks.onRequestEnd) === null || _a === void 0 ? void 0 : _a.call(lifecycleCallbacks, { - id: requestId - }); - }); - _context.prev = 18; - _context.next = 21; + cancellationSignal.register(onCancellation); + _context.prev = 19; + _context.next = 22; return scheduleRequestWithCdns(content.representation.cdnMetadata, cdnPrioritizer, callLoaderWithUrl, (0,object_assign/* default */.Z)({ onRetry: onRetry }, options), cancellationSignal); - case 21: + case 22: res = _context.sent; if (res.resultType === "segment-loaded") { loadedData = res.resultData.responseData; @@ -45381,7 +41871,7 @@ function segment_fetcher_createSegmentFetcher(bufferType, pipeline, cdnPrioritiz } else { requestInfo = null; } - if (!cancellationSignal.isCancelled) { + if (!cancellationSignal.isCancelled()) { // The current task could have been canceled as a result of one // of the previous callbacks call. In that case, we don't want to send // a "requestEnd" again as it has already been sent on cancellation. @@ -45389,27 +41879,32 @@ function segment_fetcher_createSegmentFetcher(bufferType, pipeline, cdnPrioritiz id: requestId }); } - _context.next = 37; + cancellationSignal.deregister(onCancellation); + _context.next = 41; break; - case 29: - _context.prev = 29; - _context.t0 = _context["catch"](18); + case 31: + _context.prev = 31; + _context.t0 = _context["catch"](19); + cancellationSignal.deregister(onCancellation); requestInfo = null; if (!(_context.t0 instanceof task_canceller/* CancellationError */.FU)) { - _context.next = 35; + _context.next = 38; break; } log/* default.debug */.Z.debug("SF: Segment request aborted", segmentIdString); throw _context.t0; - case 35: + case 38: log/* default.debug */.Z.debug("SF: Segment request failed", segmentIdString); + (_c = lifecycleCallbacks.onRequestEnd) === null || _c === void 0 ? void 0 : _c.call(lifecycleCallbacks, { + id: requestId + }); throw errorSelector(_context.t0); - case 37: + case 41: case "end": return _context.stop(); } } - }, _callee, null, [[18, 29]]); + }, _callee, null, [[19, 31]]); })); function fetchSegment(_x, _x2, _x3) { return _fetchSegment.apply(this, arguments); @@ -45445,6 +41940,7 @@ function getSegmentFetcherOptions(bufferType, _ref) { + var TaskPrioritizer = /*#__PURE__*/function () { /** * @param {Options} prioritizerOptions @@ -45485,49 +41981,43 @@ var TaskPrioritizer = /*#__PURE__*/function () { _proto.create = function create(taskFn, priority, callbacks, cancelSignal) { var _this = this; var newTask; - return new Promise(function (resolve, reject) { + return (0,create_cancellable_promise/* default */.Z)(cancelSignal, function (resolve, reject) { /** Function allowing to start the underlying Promise. */ var trigger = function trigger() { if (newTask.hasEnded) { - unregisterCancelSignal(); return; } - var interrupter = new task_canceller/* default */.ZP({ - cancelOn: cancelSignal - }); + var finishTask = function finishTask() { + unlinkInterrupter(); + _this._endTask(newTask); + }; + var onResolve = function onResolve(value) { + callbacks.beforeEnded(); + finishTask(); + resolve(value); + }; + var onReject = function onReject(err) { + finishTask(); + reject(err); + }; + var interrupter = new task_canceller/* default */.ZP(); + var unlinkInterrupter = interrupter.linkToSignal(cancelSignal); newTask.interrupter = interrupter; interrupter.signal.register(function () { newTask.interrupter = null; - if (!cancelSignal.isCancelled) { + if (!cancelSignal.isCancelled()) { callbacks.beforeInterrupted(); } }); _this._minPendingPriority = _this._minPendingPriority === null ? newTask.priority : Math.min(_this._minPendingPriority, newTask.priority); _this._pendingTasks.push(newTask); newTask.taskFn(interrupter.signal).then(onResolve)["catch"](function (err) { - if (!cancelSignal.isCancelled && interrupter.isUsed && err instanceof task_canceller/* CancellationError */.FU) { + if (!cancelSignal.isCancelled() && interrupter.isUsed() && err instanceof task_canceller/* CancellationError */.FU) { return; } onReject(err); }); }; - var unregisterCancelSignal = cancelSignal.register(function (cancellationError) { - _this._endTask(newTask); - reject(cancellationError); - }); - var finishTask = function finishTask() { - unregisterCancelSignal(); - _this._endTask(newTask); - }; - var onResolve = function onResolve(value) { - callbacks.beforeEnded(); - finishTask(); - resolve(value); - }; - var onReject = function onReject(err) { - finishTask(); - reject(err); - }; newTask = { hasEnded: false, priority: priority, @@ -45551,6 +42041,9 @@ var TaskPrioritizer = /*#__PURE__*/function () { _this._interruptCancellableTasks(); } } + return function () { + return _this._endTask(newTask); + }; }); }; _proto._endTask = function _endTask(task) { @@ -45786,30 +42279,6 @@ function _findTaskIndex(taskFn, queue) { * priority. * * @class SegmentFetcherCreator - * - * @example - * ```js - * const creator = new SegmentFetcherCreator(transport, { - * lowLatencyMode: false, - * maxRetryRegular: Infinity, - * maxRetryOffline: Infinity, - * }); - * - * // 2 - create a new fetcher with its backoff options - * const fetcher = creator.createSegmentFetcher("audio", { - * // ... (lifecycle callbacks if wanted) - * }); - * - * // 3 - load a segment with a given priority - * fetcher.createRequest(myContent, 1) - * // 4 - parse it - * .pipe( - * filter(evt => evt.type === "chunk"), - * mergeMap(response => response.parse()); - * ) - * // 5 - use it - * .subscribe((res) => console.log("audio chunk downloaded:", res)); - * ``` */ var SegmentFetcherCreator = /*#__PURE__*/function () { /** @@ -45866,221 +42335,6 @@ var SegmentFetcherCreator = /*#__PURE__*/function () { */ /* harmony default export */ var segment = (SegmentFetcherCreator); -// EXTERNAL MODULE: ./src/compat/clear_element_src.ts -var clear_element_src = __webpack_require__(5767); -// EXTERNAL MODULE: ./src/compat/browser_compatibility_types.ts -var browser_compatibility_types = __webpack_require__(3774); -// EXTERNAL MODULE: ./src/utils/is_non_empty_string.ts -var is_non_empty_string = __webpack_require__(6923); -;// CONCATENATED MODULE: ./src/core/init/create_media_source.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - - -var onSourceOpen$ = event_listeners/* onSourceOpen$ */.ym; -/** - * Dispose of ressources taken by the MediaSource: - * - Clear the MediaSource' SourceBuffers - * - Clear the mediaElement's src (stop the mediaElement) - * - Revoke MediaSource' URL - * @param {HTMLMediaElement} mediaElement - * @param {MediaSource|null} mediaSource - * @param {string|null} mediaSourceURL - */ -function resetMediaSource(mediaElement, mediaSource, mediaSourceURL) { - if (mediaSource !== null && mediaSource.readyState !== "closed") { - var readyState = mediaSource.readyState, - sourceBuffers = mediaSource.sourceBuffers; - for (var i = sourceBuffers.length - 1; i >= 0; i--) { - var sourceBuffer = sourceBuffers[i]; - try { - if (readyState === "open") { - log/* default.info */.Z.info("Init: Removing SourceBuffer from mediaSource"); - sourceBuffer.abort(); - } - mediaSource.removeSourceBuffer(sourceBuffer); - } catch (e) { - log/* default.warn */.Z.warn("Init: Error while disposing SourceBuffer", e instanceof Error ? e : ""); - } - } - if (sourceBuffers.length > 0) { - log/* default.warn */.Z.warn("Init: Not all SourceBuffers could have been removed."); - } - } - (0,clear_element_src/* default */.Z)(mediaElement); - if (mediaSourceURL !== null) { - try { - log/* default.debug */.Z.debug("Init: Revoking previous URL"); - URL.revokeObjectURL(mediaSourceURL); - } catch (e) { - log/* default.warn */.Z.warn("Init: Error while revoking the media source URL", e instanceof Error ? e : ""); - } - } -} -/** - * Create, on subscription, a MediaSource instance and attach it to the given - * mediaElement element's src attribute. - * - * Returns an Observable which emits the MediaSource when created and attached - * to the mediaElement element. - * This Observable never completes. It can throw if MediaSource is not - * available in the current environment. - * - * On unsubscription, the mediaElement.src is cleaned, MediaSource SourceBuffers - * are aborted and some minor cleaning is done. - * - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function createMediaSource(mediaElement) { - return new Observable/* Observable */.y(function (observer) { - if (browser_compatibility_types/* MediaSource_ */.J == null) { - throw new media_error/* default */.Z("MEDIA_SOURCE_NOT_SUPPORTED", "No MediaSource Object was found in the current browser."); - } - // make sure the media has been correctly reset - var oldSrc = (0,is_non_empty_string/* default */.Z)(mediaElement.src) ? mediaElement.src : null; - resetMediaSource(mediaElement, null, oldSrc); - log/* default.info */.Z.info("Init: Creating MediaSource"); - var mediaSource = new browser_compatibility_types/* MediaSource_ */.J(); - var objectURL = URL.createObjectURL(mediaSource); - log/* default.info */.Z.info("Init: Attaching MediaSource URL to the media element", objectURL); - mediaElement.src = objectURL; - observer.next(mediaSource); - return function () { - resetMediaSource(mediaElement, mediaSource, objectURL); - }; - }); -} -/** - * Create and open a new MediaSource object on the given media element. - * Emit the MediaSource when done. - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function openMediaSource(mediaElement) { - return createMediaSource(mediaElement).pipe((0,mergeMap/* mergeMap */.z)(function (mediaSource) { - return onSourceOpen$(mediaSource).pipe((0,take/* take */.q)(1), (0,map/* map */.U)(function () { - return mediaSource; - })); - })); -} -// EXTERNAL MODULE: ./src/core/init/events_generators.ts -var events_generators = __webpack_require__(8343); -;// CONCATENATED MODULE: ./src/core/init/get_initial_time.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -/** - * Returns the calculated initial time for the content described by the given - * Manifest: - * 1. if a start time is defined by user, calculate starting time from the - * manifest information - * 2. else if the media is live, use the live edge and suggested delays from - * it - * 3. else returns the minimum time announced in the manifest - * @param {Manifest} manifest - * @param {boolean} lowLatencyMode - * @param {Object} startAt - * @returns {Number} - */ -function getInitialTime(manifest, lowLatencyMode, startAt) { - if (!(0,is_null_or_undefined/* default */.Z)(startAt)) { - var min = manifest.getMinimumSafePosition(); - var max; - if (manifest.isLive) { - max = manifest.getLivePosition(); - } - if (max === undefined) { - max = manifest.getMaximumSafePosition(); - } - if (!(0,is_null_or_undefined/* default */.Z)(startAt.position)) { - log/* default.debug */.Z.debug("Init: using startAt.minimumPosition"); - return Math.max(Math.min(startAt.position, max), min); - } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.wallClockTime)) { - log/* default.debug */.Z.debug("Init: using startAt.wallClockTime"); - var ast = manifest.availabilityStartTime === undefined ? 0 : manifest.availabilityStartTime; - var position = startAt.wallClockTime - ast; - return Math.max(Math.min(position, max), min); - } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.fromFirstPosition)) { - log/* default.debug */.Z.debug("Init: using startAt.fromFirstPosition"); - var fromFirstPosition = startAt.fromFirstPosition; - return fromFirstPosition <= 0 ? min : Math.min(max, min + fromFirstPosition); - } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.fromLastPosition)) { - log/* default.debug */.Z.debug("Init: using startAt.fromLastPosition"); - var fromLastPosition = startAt.fromLastPosition; - return fromLastPosition >= 0 ? max : Math.max(min, max + fromLastPosition); - } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.percentage)) { - log/* default.debug */.Z.debug("Init: using startAt.percentage"); - var percentage = startAt.percentage; - if (percentage > 100) { - return max; - } else if (percentage < 0) { - return min; - } - var ratio = +percentage / 100; - var extent = max - min; - return min + extent * ratio; - } - } - var minimumPosition = manifest.getMinimumSafePosition(); - if (manifest.isLive) { - var suggestedPresentationDelay = manifest.suggestedPresentationDelay, - clockOffset = manifest.clockOffset; - var maximumPosition = manifest.getMaximumSafePosition(); - var liveTime; - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - DEFAULT_LIVE_GAP = _config$getCurrent.DEFAULT_LIVE_GAP; - if (clockOffset === undefined) { - log/* default.info */.Z.info("Init: no clock offset found for a live content, " + "starting close to maximum available position"); - liveTime = maximumPosition; - } else { - log/* default.info */.Z.info("Init: clock offset found for a live content, " + "checking if we can start close to it"); - var _ast = manifest.availabilityStartTime === undefined ? 0 : manifest.availabilityStartTime; - var clockRelativeLiveTime = (performance.now() + clockOffset) / 1000 - _ast; - liveTime = Math.min(maximumPosition, clockRelativeLiveTime); - } - var diffFromLiveTime = suggestedPresentationDelay !== undefined ? suggestedPresentationDelay : lowLatencyMode ? DEFAULT_LIVE_GAP.LOW_LATENCY : DEFAULT_LIVE_GAP.DEFAULT; - log/* default.debug */.Z.debug("Init: " + liveTime + " defined as the live time, applying a live gap" + (" of " + diffFromLiveTime)); - return Math.max(liveTime - diffFromLiveTime, minimumPosition); - } - log/* default.info */.Z.info("Init: starting at the minimum available position:", minimumPosition); - return minimumPosition; -} -// EXTERNAL MODULE: ./src/core/init/link_drm_and_content.ts + 1 modules -var link_drm_and_content = __webpack_require__(9607); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/throwError.js -var throwError = __webpack_require__(3610); // EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js var assertThisInitialized = __webpack_require__(7326); ;// CONCATENATED MODULE: ./src/compat/change_source_buffer_type.ts @@ -46158,6 +42412,7 @@ var types = __webpack_require__(9612); + /** * Allows to push and remove new segments to a SourceBuffer in a FIFO queue (not * doing so can lead to browser Errors) while keeping an inventory of what has @@ -46353,11 +42608,6 @@ var AudioVideoSegmentBuffer = /*#__PURE__*/function (_SegmentBuffer) { } } /** - * When the returned observable is subscribed: - * 1. Add your operation to the queue. - * 2. Begin the queue if not pending. - * - * Cancel queued operation on unsubscription. * @private * @param {Object} operation * @param {Object} cancellationSignal @@ -46365,17 +42615,17 @@ var AudioVideoSegmentBuffer = /*#__PURE__*/function (_SegmentBuffer) { */; _proto._addToQueue = function _addToQueue(operation, cancellationSignal) { var _this2 = this; - return new Promise(function (resolve, reject) { - if (cancellationSignal.cancellationError !== null) { - return reject(cancellationSignal.cancellationError); - } + return (0,create_cancellable_promise/* default */.Z)(cancellationSignal, function (resolve, reject) { var shouldRestartQueue = _this2._queue.length === 0 && _this2._pendingTask === null; var queueItem = (0,object_assign/* default */.Z)({ resolve: resolve, reject: reject }, operation); _this2._queue.push(queueItem); - cancellationSignal.register(function (error) { + if (shouldRestartQueue) { + _this2._flush(); + } + return function () { // Remove the corresponding element from the AudioVideoSegmentBuffer's // queue. // If the operation was a pending task, it should still continue to not @@ -46386,11 +42636,7 @@ var AudioVideoSegmentBuffer = /*#__PURE__*/function (_SegmentBuffer) { } queueItem.resolve = noop/* default */.Z; queueItem.reject = noop/* default */.Z; - reject(error); - }); - if (shouldRestartQueue) { - _this2._flush(); - } + }; }); } /** @@ -46630,6 +42876,7 @@ function assertPushedDataIsBufferSource(pushedData) { + var POSSIBLE_BUFFER_TYPES = ["audio", "video", "text", "image"]; /** * Allows to easily create and dispose SegmentBuffers, which are interfaces to @@ -46754,20 +43001,23 @@ var SegmentBuffersStore = /*#__PURE__*/function () { if (this._areNativeBuffersUsable()) { return Promise.resolve(); } - return new Promise(function (res, rej) { - var onAddedOrDisabled = function onAddedOrDisabled() { + return (0,create_cancellable_promise/* default */.Z)(cancelWaitSignal, function (res) { + /* eslint-disable-next-line prefer-const */ + var onAddedOrDisabled; + var removeCallback = function removeCallback() { + var indexOf = _this._onNativeBufferAddedOrDisabled.indexOf(onAddedOrDisabled); + if (indexOf >= 0) { + _this._onNativeBufferAddedOrDisabled.splice(indexOf, 1); + } + }; + onAddedOrDisabled = function onAddedOrDisabled() { if (_this._areNativeBuffersUsable()) { + removeCallback(); res(); } }; _this._onNativeBufferAddedOrDisabled.push(onAddedOrDisabled); - cancelWaitSignal.register(function (error) { - var indexOf = _this._onNativeBufferAddedOrDisabled.indexOf(onAddedOrDisabled); - if (indexOf >= 0) { - _this._onNativeBufferAddedOrDisabled.splice(indexOf, 1); - } - rej(error); - }); + return removeCallback; }); } /** @@ -46942,147 +43192,9 @@ function shouldHaveNativeBuffer(bufferType) { /* harmony default export */ var segment_buffers = (SegmentBuffersStore); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/innerFrom.js -var innerFrom = __webpack_require__(7878); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/exhaustMap.js - - - - -function exhaustMap(project, resultSelector) { - if (resultSelector) { - return function (source) { - return source.pipe(exhaustMap(function (a, i) { return (0,innerFrom/* innerFrom */.Xf)(project(a, i)).pipe((0,map/* map */.U)(function (b, ii) { return resultSelector(a, b, i, ii); })); })); - }; - } - return (0,lift/* operate */.e)(function (source, subscriber) { - var index = 0; - var innerSub = null; - var isComplete = false; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (outerValue) { - if (!innerSub) { - innerSub = (0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, undefined, function () { - innerSub = null; - isComplete && subscriber.complete(); - }); - (0,innerFrom/* innerFrom */.Xf)(project(outerValue, index++)).subscribe(innerSub); - } - }, function () { - isComplete = true; - !innerSub && subscriber.complete(); - })); - }); -} -//# sourceMappingURL=exhaustMap.js.map -;// CONCATENATED MODULE: ./src/utils/rx-from_cancellable_promise.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Transform a Promise that can be cancelled (through the usage of a - * `TaskCanceller`) to an Observable, while keeping the cancellation logic - * between both in sync. - * - * @example - * ```js - * const canceller = new TaskCanceller(); - * fromCancellablePromise( - * canceller, - * () => doSomeCancellableTasks(canceller.signal) - * ).subscribe( - * (i) => console.log("Emitted: ", i); - * (e) => console.log("Error: ", e); - * () => console.log("Complete.") - * ); - * ``` - * @param {Object} canceller - * @param {Function} fn - * @returns {Observable} - */ -function fromCancellablePromise(canceller, fn) { - return new Observable/* Observable */.y(function (obs) { - var isUnsubscribedFrom = false; - var isComplete = false; - fn().then(function (i) { - if (isUnsubscribedFrom) { - return; - } - isComplete = true; - obs.next(i); - obs.complete(); - }, function (err) { - isComplete = true; - if (isUnsubscribedFrom) { - return; - } - obs.error(err); - }); - return function () { - if (!isComplete) { - isUnsubscribedFrom = true; - canceller.cancel(); - } - }; - }); -} // EXTERNAL MODULE: ./node_modules/next-tick/index.js var next_tick = __webpack_require__(7473); var next_tick_default = /*#__PURE__*/__webpack_require__.n(next_tick); -;// CONCATENATED MODULE: ./src/utils/rx-next-tick.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -/** - * Create Observable that emits and complete on the next micro-task. - * - * This Observable can be useful to prevent race conditions based on - * synchronous task being performed in the wrong order. - * By awaiting nextTickObs before performing a task, you ensure that all other - * tasks that might have run synchronously either before or after it all already - * ran. - * @returns {Observable} - */ -function nextTickObs() { - return new Observable/* Observable */.y(function (obs) { - var isFinished = false; - next_tick_default()(function () { - if (!isFinished) { - obs.next(); - obs.complete(); - } - }); - return function () { - isFinished = true; - }; - }); -} ;// CONCATENATED MODULE: ./src/utils/sorted_list.ts /** * Copyright 2015 CANAL+ Group @@ -47188,6 +43300,9 @@ var SortedList = /*#__PURE__*/function () { throw new Error("Invalid index."); } return this._array[index]; + }; + _proto.toArray = function toArray() { + return this._array.slice(); } /** * Find the first element corresponding to the given predicate. @@ -47379,7 +43494,6 @@ var WeakMapMemory = /*#__PURE__*/function () { * * @param {Object} opt * @param {Object} cancellationSignal - * @returns {Observable} */ function BufferGarbageCollector(_ref, cancellationSignal) { var segmentBuffer = _ref.segmentBuffer, @@ -47528,89 +43642,8 @@ function _clearBuffer() { })); return _clearBuffer.apply(this, arguments); } -// EXTERNAL MODULE: ./src/core/stream/events_generators.ts -var stream_events_generators = __webpack_require__(8567); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/ReplaySubject.js -var ReplaySubject = __webpack_require__(3); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/catchError.js -var catchError = __webpack_require__(9878); -;// CONCATENATED MODULE: ./src/core/stream/reload_after_switch.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/downloading_queue.ts -/** - * Regularly ask to reload the MediaSource on each playback observation - * performed by the playback observer. - * - * If and only if the Period currently played corresponds to `Period`, applies - * an offset to the reloaded position corresponding to `deltaPos`. - * This can be useful for example when switching the audio/video track, where - * you might want to give back some context if that was the currently played - * track. - * - * @param {Object} period - The Period linked to the Adaptation or - * Representation that you want to switch to. - * @param {Observable} playbackObserver - emit playback conditions. - * Has to emit last playback conditions immediately on subscribe. - * @param {number} deltaPos - If the concerned Period is playing at the time - * this function is called, we will add this value, in seconds, to the current - * position to indicate the position we should reload at. - * This value allows to give back context (by replaying some media data) after - * a switch. - * @returns {Observable} - */ -function reloadAfterSwitch(period, bufferType, playbackObserver, deltaPos) { - // We begin by scheduling a micro-task to reduce the possibility of race - // conditions where `reloadAfterSwitch` would be called synchronously before - // the next observation (which may reflect very different playback conditions) - // is actually received. - // It can happen when `reloadAfterSwitch` is called as a side-effect of the - // same event that triggers the playback observation to be emitted. - return nextTickObs().pipe((0,mergeMap/* mergeMap */.z)(function () { - return playbackObserver.getReference().asObservable(); - }), (0,map/* map */.U)(function (observation) { - var _a, _b; - var currentTime = playbackObserver.getCurrentTime(); - var pos = currentTime + deltaPos; - // Bind to Period start and end - var reloadAt = Math.min(Math.max(period.start, pos), (_a = period.end) !== null && _a !== void 0 ? _a : Infinity); - var autoPlay = !((_b = observation.paused.pending) !== null && _b !== void 0 ? _b : playbackObserver.getIsPaused()); - return stream_events_generators/* default.waitingMediaSourceReload */.Z.waitingMediaSourceReload(bufferType, period, reloadAt, autoPlay); - })); -} -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/withLatestFrom.js -var withLatestFrom = __webpack_require__(3428); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/takeWhile.js - - -function takeWhile(predicate, inclusive) { - if (inclusive === void 0) { inclusive = false; } - return (0,lift/* operate */.e)(function (source, subscriber) { - var index = 0; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - var result = predicate(value, index++); - (result || inclusive) && subscriber.next(value); - !result && subscriber.complete(); - })); - }); -} -//# sourceMappingURL=takeWhile.js.map -;// CONCATENATED MODULE: ./src/core/stream/representation/downloading_queue.ts /** * Copyright 2015 CANAL+ Group * @@ -47632,11 +43665,17 @@ function takeWhile(predicate, inclusive) { + /** * Class scheduling segment downloads for a single Representation. + * + * TODO The request scheduling abstractions might be simplified by integrating + * the `DownloadingQueue` in the segment fetchers code, instead of having it as + * an utilis of the `RepresentationStream` like here. * @class DownloadingQueue */ -var DownloadingQueue = /*#__PURE__*/function () { +var DownloadingQueue = /*#__PURE__*/function (_EventEmitter) { + (0,inheritsLoose/* default */.Z)(DownloadingQueue, _EventEmitter); /** * Create a new `DownloadingQueue`. * @@ -47659,17 +43698,20 @@ var DownloadingQueue = /*#__PURE__*/function () { * media segment. */ function DownloadingQueue(content, downloadQueue, segmentFetcher, hasInitSegment) { - this._content = content; - this._currentObs$ = null; - this._downloadQueue = downloadQueue; - this._initSegmentRequest = null; - this._mediaSegmentRequest = null; - this._segmentFetcher = segmentFetcher; - this._initSegmentInfoRef = (0,reference/* default */.ZP)(undefined); - this._mediaSegmentsAwaitingInitMetadata = new Set(); + var _this; + _this = _EventEmitter.call(this) || this; + _this._content = content; + _this._currentCanceller = null; + _this._downloadQueue = downloadQueue; + _this._initSegmentRequest = null; + _this._mediaSegmentRequest = null; + _this._segmentFetcher = segmentFetcher; + _this._initSegmentInfoRef = (0,reference/* default */.ZP)(undefined); + _this._mediaSegmentAwaitingInitMetadata = null; if (!hasInitSegment) { - this._initSegmentInfoRef.setValue(null); + _this._initSegmentInfoRef.setValue(null); } + return _this; } /** * Returns the initialization segment currently being requested. @@ -47691,344 +43733,318 @@ var DownloadingQueue = /*#__PURE__*/function () { /** * Start the current downloading queue, emitting events as it loads and parses * initialization and media segments. - * - * If it was already started, returns the same - shared - Observable. - * @returns {Observable} */; _proto.start = function start() { - var _this = this; - if (this._currentObs$ !== null) { - return this._currentObs$; - } - var obs = (0,defer/* defer */.P)(function () { - var mediaQueue$ = _this._downloadQueue.asObservable().pipe((0,filter/* filter */.h)(function (_ref) { - var segmentQueue = _ref.segmentQueue; - // First, the first elements of the segmentQueue might be already - // loaded but awaiting the initialization segment to be parsed. - // Filter those out. - var nextSegmentToLoadIdx = 0; - for (; nextSegmentToLoadIdx < segmentQueue.length; nextSegmentToLoadIdx++) { - var nextSegment = segmentQueue[nextSegmentToLoadIdx].segment; - if (!_this._mediaSegmentsAwaitingInitMetadata.has(nextSegment.id)) { - break; - } - } - var currentSegmentRequest = _this._mediaSegmentRequest; - if (nextSegmentToLoadIdx >= segmentQueue.length) { - return currentSegmentRequest !== null; - } else if (currentSegmentRequest === null) { - return true; + var _this2 = this; + if (this._currentCanceller !== null) { + return; + } + this._currentCanceller = new task_canceller/* default */.ZP(); + // Listen for asked media segments + this._downloadQueue.onUpdate(function (queue) { + var segmentQueue = queue.segmentQueue; + if (segmentQueue.length > 0 && segmentQueue[0].segment.id === _this2._mediaSegmentAwaitingInitMetadata) { + // The most needed segment is still the same one, and there's no need to + // update its priority as the request already ended, just quit. + return; + } + var currentSegmentRequest = _this2._mediaSegmentRequest; + if (segmentQueue.length === 0) { + if (currentSegmentRequest === null) { + // There's nothing to load but there's already no request pending. + return; } - var nextItem = segmentQueue[nextSegmentToLoadIdx]; + log/* default.debug */.Z.debug("Stream: no more media segment to request. Cancelling queue.", _this2._content.adaptation.type); + _this2._restartMediaSegmentDownloadingQueue(); + return; + } else if (currentSegmentRequest === null) { + // There's no request although there are needed segments: start requests + log/* default.debug */.Z.debug("Stream: Media segments now need to be requested. Starting queue.", _this2._content.adaptation.type, segmentQueue.length); + _this2._restartMediaSegmentDownloadingQueue(); + return; + } else { + var nextItem = segmentQueue[0]; if (currentSegmentRequest.segment.id !== nextItem.segment.id) { - return true; + // The most important request if for another segment, request it + log/* default.debug */.Z.debug("Stream: Next media segment changed, cancelling previous", _this2._content.adaptation.type); + _this2._restartMediaSegmentDownloadingQueue(); + return; } if (currentSegmentRequest.priority !== nextItem.priority) { - _this._segmentFetcher.updatePriority(currentSegmentRequest.request, nextItem.priority); - } - return false; - }), (0,switchMap/* switchMap */.w)(function (_ref2) { - var segmentQueue = _ref2.segmentQueue; - return segmentQueue.length > 0 ? _this._requestMediaSegments() : empty/* EMPTY */.E; - })); - var initSegmentPush$ = _this._downloadQueue.asObservable().pipe((0,filter/* filter */.h)(function (next) { - var initSegmentRequest = _this._initSegmentRequest; - if (next.initSegment !== null && initSegmentRequest !== null) { - if (next.initSegment.priority !== initSegmentRequest.priority) { - _this._segmentFetcher.updatePriority(initSegmentRequest.request, next.initSegment.priority); - } - return false; - } else { - return next.initSegment === null || initSegmentRequest === null; + // The priority of the most important request has changed, update it + log/* default.debug */.Z.debug("Stream: Priority of next media segment changed, updating", _this2._content.adaptation.type, currentSegmentRequest.priority, nextItem.priority); + _this2._segmentFetcher.updatePriority(currentSegmentRequest.request, nextItem.priority); } - }), (0,switchMap/* switchMap */.w)(function (nextQueue) { - if (nextQueue.initSegment === null) { - return empty/* EMPTY */.E; + return; + } + }, { + emitCurrentValue: true, + clearSignal: this._currentCanceller.signal + }); + // Listen for asked init segment + this._downloadQueue.onUpdate(function (next) { + var _a; + var initSegmentRequest = _this2._initSegmentRequest; + if (next.initSegment !== null && initSegmentRequest !== null) { + if (next.initSegment.priority !== initSegmentRequest.priority) { + _this2._segmentFetcher.updatePriority(initSegmentRequest.request, next.initSegment.priority); } - return _this._requestInitSegment(nextQueue.initSegment); - })); - return (0,merge/* merge */.T)(initSegmentPush$, mediaQueue$); - }).pipe((0,share/* share */.B)()); - this._currentObs$ = obs; - return obs; + return; + } else if (((_a = next.initSegment) === null || _a === void 0 ? void 0 : _a.segment.id) === (initSegmentRequest === null || initSegmentRequest === void 0 ? void 0 : initSegmentRequest.segment.id)) { + return; + } + if (next.initSegment === null) { + log/* default.debug */.Z.debug("Stream: no more init segment to request. Cancelling queue.", _this2._content.adaptation.type); + } + _this2._restartInitSegmentDownloadingQueue(next.initSegment); + }, { + emitCurrentValue: true, + clearSignal: this._currentCanceller.signal + }); + }; + _proto.stop = function stop() { + var _a; + (_a = this._currentCanceller) === null || _a === void 0 ? void 0 : _a.cancel(); + this._currentCanceller = null; } /** * Internal logic performing media segment requests. - * @returns {Observable} */; - _proto._requestMediaSegments = function _requestMediaSegments() { - var _this2 = this; + _proto._restartMediaSegmentDownloadingQueue = function _restartMediaSegmentDownloadingQueue() { + var _this3 = this; + if (this._mediaSegmentRequest !== null) { + this._mediaSegmentRequest.canceller.cancel(); + } var _this$_downloadQueue$ = this._downloadQueue.getValue(), segmentQueue = _this$_downloadQueue$.segmentQueue; var currentNeededSegment = segmentQueue[0]; - /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - var self = this; - return (0,defer/* defer */.P)(function () { - return recursivelyRequestSegments(currentNeededSegment); - }).pipe((0,finalize/* finalize */.x)(function () { - _this2._mediaSegmentRequest = null; - })); - function recursivelyRequestSegments(startingSegment) { + var recursivelyRequestSegments = function recursivelyRequestSegments(startingSegment) { + if (_this3._currentCanceller !== null && _this3._currentCanceller.isUsed()) { + _this3._mediaSegmentRequest = null; + return; + } if (startingSegment === undefined) { - return (0,of.of)({ - type: "end-of-queue", - value: null - }); + _this3._mediaSegmentRequest = null; + _this3.trigger("emptyQueue", null); + return; } + var canceller = new task_canceller/* default */.ZP(); + var unlinkCanceller = _this3._currentCanceller === null ? noop/* default */.Z : canceller.linkToSignal(_this3._currentCanceller.signal); var segment = startingSegment.segment, priority = startingSegment.priority; var context = (0,object_assign/* default */.Z)({ segment: segment - }, self._content); - return new Observable/* Observable */.y(function (obs) { - /** TaskCanceller linked to this Observable's lifecycle. */ - var canceller = new task_canceller/* default */.ZP(); - /** - * If `true` , the Observable has either errored, completed, or was - * unsubscribed from. - * This only conserves the Observable for the current segment's request, - * not the other recursively-created future ones. - */ - var isComplete = false; - /** - * Subscription to request the following segment (as this function is - * recursive). - * `undefined` if no following segment has been requested. - */ - var nextSegmentSubscription; - /** - * If true, we're currently waiting for the initialization segment to be - * parsed before parsing a received chunk. - * - * In that case, the `DownloadingQueue` has to remain careful to only - * send further events and complete the Observable only once the - * initialization segment has been parsed AND the chunk parsing has been - * done (this can be done very simply by listening to the same - * `ISharedReference`, as its callbacks are called in the same order - * than the one in which they are added. - */ - var isWaitingOnInitSegment = false; - /** Scheduled actual segment request. */ - var request = self._segmentFetcher.createRequest(context, priority, { - /** - * Callback called when the request has to be retried. - * @param {Error} error - */ - onRetry: function onRetry(error) { - obs.next({ - type: "retry", - value: { - segment: segment, - error: error - } - }); - }, - /** - * Callback called when the request has to be interrupted and - * restarted later. - */ - beforeInterrupted: function beforeInterrupted() { - log/* default.info */.Z.info("Stream: segment request interrupted temporarly.", segment.id, segment.time); - }, - /** - * Callback called when a decodable chunk of the segment is available. - * @param {Function} parse - Function allowing to parse the segment. - */ - onChunk: function onChunk(parse) { - var initTimescale = self._initSegmentInfoRef.getValue(); - if (initTimescale !== undefined) { - emitChunk(parse(initTimescale !== null && initTimescale !== void 0 ? initTimescale : undefined)); - } else { - isWaitingOnInitSegment = true; - // We could also technically call `waitUntilDefined` in both cases, - // but I found it globally clearer to segregate the two cases, - // especially to always have a meaningful `isWaitingOnInitSegment` - // boolean which is a very important variable. - self._initSegmentInfoRef.waitUntilDefined(function (actualTimescale) { - emitChunk(parse(actualTimescale !== null && actualTimescale !== void 0 ? actualTimescale : undefined)); - }, { - clearSignal: canceller.signal - }); - } - }, - /** Callback called after all chunks have been sent. */onAllChunksReceived: function onAllChunksReceived() { - if (!isWaitingOnInitSegment) { - obs.next({ - type: "end-of-segment", - value: { - segment: segment - } - }); - } else { - self._mediaSegmentsAwaitingInitMetadata.add(segment.id); - self._initSegmentInfoRef.waitUntilDefined(function () { - obs.next({ - type: "end-of-segment", - value: { - segment: segment - } - }); - self._mediaSegmentsAwaitingInitMetadata["delete"](segment.id); - isWaitingOnInitSegment = false; - }, { - clearSignal: canceller.signal - }); - } - }, - /** - * Callback called right after the request ended but before the next - * requests are scheduled. It is used to schedule the next segment. - */ - beforeEnded: function beforeEnded() { - self._mediaSegmentRequest = null; - if (isWaitingOnInitSegment) { - self._initSegmentInfoRef.waitUntilDefined(continueToNextSegment, { - clearSignal: canceller.signal - }); - } else { - continueToNextSegment(); - } - } - }, canceller.signal); - request["catch"](function (error) { - if (!isComplete) { - isComplete = true; - obs.error(error); - } - }); - self._mediaSegmentRequest = { - segment: segment, - priority: priority, - request: request - }; - return function () { - self._mediaSegmentsAwaitingInitMetadata["delete"](segment.id); - if (nextSegmentSubscription !== undefined) { - nextSegmentSubscription.unsubscribe(); - } - if (isComplete) { - return; - } - isComplete = true; - isWaitingOnInitSegment = false; - canceller.cancel(); - }; - function emitChunk(parsed) { - (0,assert/* default */.Z)(parsed.segmentType === "media", "Should have loaded a media segment."); - obs.next((0,object_assign/* default */.Z)({}, parsed, { - type: "parsed-media", - segment: segment - })); - } - function continueToNextSegment() { - var lastQueue = self._downloadQueue.getValue().segmentQueue; - if (lastQueue.length === 0) { - obs.next({ - type: "end-of-queue", - value: null - }); - isComplete = true; - obs.complete(); - return; - } else if (lastQueue[0].segment.id === segment.id) { - lastQueue.shift(); - } - isComplete = true; - nextSegmentSubscription = recursivelyRequestSegments(lastQueue[0]).subscribe(obs); - } - }); - } - } - /** - * Internal logic performing initialization segment requests. - * @param {Object} queuedInitSegment - * @returns {Observable} - */; - _proto._requestInitSegment = function _requestInitSegment(queuedInitSegment) { - var _this3 = this; - if (queuedInitSegment === null) { - this._initSegmentRequest = null; - return empty/* EMPTY */.E; - } - /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - var self = this; - return new Observable/* Observable */.y(function (obs) { - /** TaskCanceller linked to this Observable's lifecycle. */ - var canceller = new task_canceller/* default */.ZP(); - var segment = queuedInitSegment.segment, - priority = queuedInitSegment.priority; - var context = (0,object_assign/* default */.Z)({ - segment: segment }, _this3._content); /** - * If `true` , the Observable has either errored, completed, or was - * unsubscribed from. + * If `true` , the current task has either errored, finished, or was + * cancelled. */ var isComplete = false; + /** + * If true, we're currently waiting for the initialization segment to be + * parsed before parsing a received chunk. + */ + var isWaitingOnInitSegment = false; + canceller.signal.register(function () { + _this3._mediaSegmentRequest = null; + if (isComplete) { + return; + } + if (_this3._mediaSegmentAwaitingInitMetadata === segment.id) { + _this3._mediaSegmentAwaitingInitMetadata = null; + } + isComplete = true; + isWaitingOnInitSegment = false; + }); + var emitChunk = function emitChunk(parsed) { + (0,assert/* default */.Z)(parsed.segmentType === "media", "Should have loaded a media segment."); + _this3.trigger("parsedMediaSegment", (0,object_assign/* default */.Z)({}, parsed, { + segment: segment + })); + }; + var continueToNextSegment = function continueToNextSegment() { + var lastQueue = _this3._downloadQueue.getValue().segmentQueue; + if (lastQueue.length === 0) { + isComplete = true; + _this3.trigger("emptyQueue", null); + return; + } else if (lastQueue[0].segment.id === segment.id) { + lastQueue.shift(); + } + isComplete = true; + recursivelyRequestSegments(lastQueue[0]); + }; + /** Scheduled actual segment request. */ var request = _this3._segmentFetcher.createRequest(context, priority, { - onRetry: function onRetry(err) { - obs.next({ - type: "retry", - value: { - segment: segment, - error: err - } + /** + * Callback called when the request has to be retried. + * @param {Error} error + */ + onRetry: function onRetry(error) { + _this3.trigger("requestRetry", { + segment: segment, + error: error }); }, + /** + * Callback called when the request has to be interrupted and + * restarted later. + */ beforeInterrupted: function beforeInterrupted() { - log/* default.info */.Z.info("Stream: init segment request interrupted temporarly.", segment.id); - }, - beforeEnded: function beforeEnded() { - self._initSegmentRequest = null; - isComplete = true; - obs.complete(); + log/* default.info */.Z.info("Stream: segment request interrupted temporarly.", segment.id, segment.time); }, + /** + * Callback called when a decodable chunk of the segment is available. + * @param {Function} parse - Function allowing to parse the segment. + */ onChunk: function onChunk(parse) { - var _a; - var parsed = parse(undefined); - (0,assert/* default */.Z)(parsed.segmentType === "init", "Should have loaded an init segment."); - obs.next((0,object_assign/* default */.Z)({}, parsed, { - type: "parsed-init", - segment: segment - })); - if (parsed.segmentType === "init") { - self._initSegmentInfoRef.setValue((_a = parsed.initTimescale) !== null && _a !== void 0 ? _a : null); + var initTimescale = _this3._initSegmentInfoRef.getValue(); + if (initTimescale !== undefined) { + emitChunk(parse(initTimescale !== null && initTimescale !== void 0 ? initTimescale : undefined)); + } else { + isWaitingOnInitSegment = true; + // We could also technically call `waitUntilDefined` in both cases, + // but I found it globally clearer to segregate the two cases, + // especially to always have a meaningful `isWaitingOnInitSegment` + // boolean which is a very important variable. + _this3._initSegmentInfoRef.waitUntilDefined(function (actualTimescale) { + emitChunk(parse(actualTimescale !== null && actualTimescale !== void 0 ? actualTimescale : undefined)); + }, { + clearSignal: canceller.signal + }); } }, + /** Callback called after all chunks have been sent. */ onAllChunksReceived: function onAllChunksReceived() { - obs.next({ - type: "end-of-segment", - value: { - segment: segment - } - }); + if (!isWaitingOnInitSegment) { + _this3.trigger("fullyLoadedSegment", segment); + } else { + _this3._mediaSegmentAwaitingInitMetadata = segment.id; + _this3._initSegmentInfoRef.waitUntilDefined(function () { + _this3._mediaSegmentAwaitingInitMetadata = null; + isWaitingOnInitSegment = false; + _this3.trigger("fullyLoadedSegment", segment); + }, { + clearSignal: canceller.signal + }); + } + }, + /** + * Callback called right after the request ended but before the next + * requests are scheduled. It is used to schedule the next segment. + */ + beforeEnded: function beforeEnded() { + unlinkCanceller(); + _this3._mediaSegmentRequest = null; + if (isWaitingOnInitSegment) { + _this3._initSegmentInfoRef.waitUntilDefined(continueToNextSegment, { + clearSignal: canceller.signal + }); + } else { + continueToNextSegment(); + } } }, canceller.signal); request["catch"](function (error) { + unlinkCanceller(); if (!isComplete) { isComplete = true; - obs.error(error); + _this3.stop(); + _this3.trigger("error", error); } }); - _this3._initSegmentRequest = { + _this3._mediaSegmentRequest = { segment: segment, priority: priority, - request: request + request: request, + canceller: canceller }; - return function () { - _this3._initSegmentRequest = null; - if (isComplete) { - return; + }; + recursivelyRequestSegments(currentNeededSegment); + } + /** + * Internal logic performing initialization segment requests. + * @param {Object} queuedInitSegment + */; + _proto._restartInitSegmentDownloadingQueue = function _restartInitSegmentDownloadingQueue(queuedInitSegment) { + var _this4 = this; + if (this._currentCanceller !== null && this._currentCanceller.isUsed()) { + return; + } + if (this._initSegmentRequest !== null) { + this._initSegmentRequest.canceller.cancel(); + } + if (queuedInitSegment === null) { + return; + } + var canceller = new task_canceller/* default */.ZP(); + var unlinkCanceller = this._currentCanceller === null ? noop/* default */.Z : canceller.linkToSignal(this._currentCanceller.signal); + var segment = queuedInitSegment.segment, + priority = queuedInitSegment.priority; + var context = (0,object_assign/* default */.Z)({ + segment: segment + }, this._content); + /** + * If `true` , the current task has either errored, finished, or was + * cancelled. + */ + var isComplete = false; + var request = this._segmentFetcher.createRequest(context, priority, { + onRetry: function onRetry(err) { + _this4.trigger("requestRetry", { + segment: segment, + error: err + }); + }, + beforeInterrupted: function beforeInterrupted() { + log/* default.info */.Z.info("Stream: init segment request interrupted temporarly.", segment.id); + }, + beforeEnded: function beforeEnded() { + unlinkCanceller(); + _this4._initSegmentRequest = null; + isComplete = true; + }, + onChunk: function onChunk(parse) { + var _a; + var parsed = parse(undefined); + (0,assert/* default */.Z)(parsed.segmentType === "init", "Should have loaded an init segment."); + _this4.trigger("parsedInitSegment", (0,object_assign/* default */.Z)({}, parsed, { + segment: segment + })); + if (parsed.segmentType === "init") { + _this4._initSegmentInfoRef.setValue((_a = parsed.initTimescale) !== null && _a !== void 0 ? _a : null); } + }, + onAllChunksReceived: function onAllChunksReceived() { + _this4.trigger("fullyLoadedSegment", segment); + } + }, canceller.signal); + request["catch"](function (error) { + unlinkCanceller(); + if (!isComplete) { isComplete = true; - canceller.cancel(); - }; + _this4.stop(); + _this4.trigger("error", error); + } }); + canceller.signal.register(function () { + _this4._initSegmentRequest = null; + if (isComplete) { + return; + } + isComplete = true; + }); + this._initSegmentRequest = { + segment: segment, + priority: priority, + request: request, + canceller: canceller + }; }; return DownloadingQueue; -}(); +}(event_emitter/* default */.Z); -;// CONCATENATED MODULE: ./src/core/stream/representation/check_for_discontinuity.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/check_for_discontinuity.ts /** * Copyright 2015 CANAL+ Group * @@ -48270,7 +44286,7 @@ function getIndexOfLastChunkInPeriod(bufferedChunks, periodEnd) { } return null; } -;// CONCATENATED MODULE: ./src/core/stream/representation/get_needed_segments.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/get_needed_segments.ts /** * Copyright 2015 CANAL+ Group * @@ -48286,7 +44302,6 @@ function getIndexOfLastChunkInPeriod(bufferedChunks, periodEnd) { * See the License for the specific language governing permissions and * limitations under the License. */ -// eslint-disable-next-line max-len @@ -48698,7 +44713,7 @@ function shouldReloadSegmentGCedAtTheEnd(segmentEntries, currentBufferedEnd) { // issue. In that case, don't try to reload. return Math.abs(prevBufferedEnd - lastBufferedEnd) > 0.01; } -;// CONCATENATED MODULE: ./src/core/stream/representation/get_segment_priority.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/get_segment_priority.ts /** * Copyright 2015 CANAL+ Group * @@ -48738,7 +44753,7 @@ function getSegmentPriority(segmentTime, wantedStartTimestamp) { } return SEGMENT_PRIORITIES_STEPS.length; } -;// CONCATENATED MODULE: ./src/core/stream/representation/get_buffer_status.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/get_buffer_status.ts /** * Copyright 2015 CANAL+ Group * @@ -48943,7 +44958,7 @@ function getPlayableBufferedSegments(neededRange, segmentInventory) { } return overlappingChunks; } -;// CONCATENATED MODULE: ./src/core/stream/representation/force_garbage_collection.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/force_garbage_collection.ts function force_garbage_collection_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = force_garbage_collection_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } @@ -49079,7 +45094,7 @@ function selectGCedRanges(position, buffered, gcGap) { } return cleanedupRanges; } -;// CONCATENATED MODULE: ./src/core/stream/representation/append_segment_to_buffer.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/append_segment_to_buffer.ts /** @@ -49102,11 +45117,12 @@ function selectGCedRanges(position, buffered, gcGap) { */ + /** * Append a segment to the given segmentBuffer. * If it leads to a QuotaExceededError, try to run our custom range * _garbage collector_ then retry. - * @param {Observable} playbackObserver + * @param {Object} playbackObserver * @param {Object} segmentBuffer * @param {Object} dataInfos * @param {Object} cancellationSignal @@ -49126,44 +45142,52 @@ function _appendSegmentToBuffer() { _context.next = 3; return segmentBuffer.pushChunk(dataInfos, cancellationSignal); case 3: - _context.next = 23; + _context.next = 27; break; case 5: _context.prev = 5; _context.t0 = _context["catch"](0); + if (!(cancellationSignal.isCancelled() && _context.t0 instanceof task_canceller/* CancellationError */.FU)) { + _context.next = 11; + break; + } + throw _context.t0; + case 11: if (!(!(_context.t0 instanceof Error) || _context.t0.name !== "QuotaExceededError")) { - _context.next = 10; + _context.next = 14; break; } reason = _context.t0 instanceof Error ? _context.t0.toString() : "An unknown error happened when pushing content"; throw new media_error/* default */.Z("BUFFER_APPEND_ERROR", reason); - case 10: + case 14: _playbackObserver$get = playbackObserver.getReference().getValue(), position = _playbackObserver$get.position; currentPos = (_a = position.pending) !== null && _a !== void 0 ? _a : position.last; - _context.prev = 12; - _context.next = 15; + _context.prev = 16; + _context.next = 19; return forceGarbageCollection(currentPos, segmentBuffer, cancellationSignal); - case 15: - _context.next = 17; + case 19: + _context.next = 21; return segmentBuffer.pushChunk(dataInfos, cancellationSignal); - case 17: - _context.next = 23; + case 21: + _context.next = 27; break; - case 19: - _context.prev = 19; - _context.t1 = _context["catch"](12); + case 23: + _context.prev = 23; + _context.t1 = _context["catch"](16); _reason = _context.t1 instanceof Error ? _context.t1.toString() : "Could not clean the buffer"; throw new media_error/* default */.Z("BUFFER_FULL_ERROR", _reason); - case 23: + case 27: case "end": return _context.stop(); } } - }, _callee, null, [[0, 5], [12, 19]]); + }, _callee, null, [[0, 5], [16, 23]]); })); return _appendSegmentToBuffer.apply(this, arguments); } -;// CONCATENATED MODULE: ./src/core/stream/representation/push_init_segment.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/push_init_segment.ts + + /** * Copyright 2015 CANAL+ Group * @@ -49180,49 +45204,68 @@ function _appendSegmentToBuffer() { * limitations under the License. */ - - - - /** * Push the initialization segment to the SegmentBuffer. - * The Observable returned: - * - emit an event once the segment has been pushed. - * - throws on Error. * @param {Object} args - * @returns {Observable} + * @param {Object} cancelSignal + * @returns {Promise} */ -function pushInitSegment(_ref) { - var playbackObserver = _ref.playbackObserver, - content = _ref.content, - segment = _ref.segment, - segmentData = _ref.segmentData, - segmentBuffer = _ref.segmentBuffer; - return (0,defer/* defer */.P)(function () { - if (segmentData === null) { - return empty/* EMPTY */.E; - } - var codec = content.representation.getMimeTypeString(); - var data = { - initSegment: segmentData, - chunk: null, - timestampOffset: 0, - appendWindow: [undefined, undefined], - codec: codec - }; - var canceller = new task_canceller/* default */.ZP(); - return fromCancellablePromise(canceller, function () { - return appendSegmentToBuffer(playbackObserver, segmentBuffer, { - data: data, - inventoryInfos: null - }, canceller.signal); - }).pipe((0,map/* map */.U)(function () { - var buffered = segmentBuffer.getBufferedRanges(); - return stream_events_generators/* default.addedSegment */.Z.addedSegment(content, segment, buffered, segmentData); - })); - }); +function pushInitSegment(_x, _x2) { + return _pushInitSegment.apply(this, arguments); +} +function _pushInitSegment() { + _pushInitSegment = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(_ref, cancelSignal) { + var playbackObserver, content, segment, segmentData, segmentBuffer, codec, data, buffered; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + playbackObserver = _ref.playbackObserver, content = _ref.content, segment = _ref.segment, segmentData = _ref.segmentData, segmentBuffer = _ref.segmentBuffer; + if (!(segmentData === null)) { + _context.next = 3; + break; + } + return _context.abrupt("return", null); + case 3: + if (!(cancelSignal.cancellationError !== null)) { + _context.next = 5; + break; + } + throw cancelSignal.cancellationError; + case 5: + codec = content.representation.getMimeTypeString(); + data = { + initSegment: segmentData, + chunk: null, + timestampOffset: 0, + appendWindow: [undefined, undefined], + codec: codec + }; + _context.next = 9; + return appendSegmentToBuffer(playbackObserver, segmentBuffer, { + data: data, + inventoryInfos: null + }, cancelSignal); + case 9: + buffered = segmentBuffer.getBufferedRanges(); + return _context.abrupt("return", { + content: content, + segment: segment, + buffered: buffered, + segmentData: segmentData + }); + case 11: + case "end": + return _context.stop(); + } + } + }, _callee); + })); + return _pushInitSegment.apply(this, arguments); } -;// CONCATENATED MODULE: ./src/core/stream/representation/push_media_segment.ts +;// CONCATENATED MODULE: ./src/core/stream/representation/utils/push_media_segment.ts + + /** * Copyright 2015 CANAL+ Group * @@ -49241,75 +45284,84 @@ function pushInitSegment(_ref) { - - - - /** * Push a given media segment (non-init segment) to a SegmentBuffer. - * The Observable returned: - * - emit an event once the segment has been pushed. - * - throws on Error. * @param {Object} args - * @returns {Observable} + * @param {Object} cancelSignal + * @returns {Promise} */ -function pushMediaSegment(_ref) { - var playbackObserver = _ref.playbackObserver, - content = _ref.content, - initSegmentData = _ref.initSegmentData, - parsedSegment = _ref.parsedSegment, - segment = _ref.segment, - segmentBuffer = _ref.segmentBuffer; - return (0,defer/* defer */.P)(function () { - var _a, _b; - if (parsedSegment.chunkData === null) { - return empty/* EMPTY */.E; - } - var chunkData = parsedSegment.chunkData, - chunkInfos = parsedSegment.chunkInfos, - chunkOffset = parsedSegment.chunkOffset, - chunkSize = parsedSegment.chunkSize, - appendWindow = parsedSegment.appendWindow; - var codec = content.representation.getMimeTypeString(); - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - APPEND_WINDOW_SECURITIES = _config$getCurrent.APPEND_WINDOW_SECURITIES; - // Cutting exactly at the start or end of the appendWindow can lead to - // cases of infinite rebuffering due to how browser handle such windows. - // To work-around that, we add a small offset before and after those. - var safeAppendWindow = [appendWindow[0] !== undefined ? Math.max(0, appendWindow[0] - APPEND_WINDOW_SECURITIES.START) : undefined, appendWindow[1] !== undefined ? appendWindow[1] + APPEND_WINDOW_SECURITIES.END : undefined]; - var data = { - initSegment: initSegmentData, - chunk: chunkData, - timestampOffset: chunkOffset, - appendWindow: safeAppendWindow, - codec: codec - }; - var estimatedStart = (_a = chunkInfos === null || chunkInfos === void 0 ? void 0 : chunkInfos.time) !== null && _a !== void 0 ? _a : segment.time; - var estimatedDuration = (_b = chunkInfos === null || chunkInfos === void 0 ? void 0 : chunkInfos.duration) !== null && _b !== void 0 ? _b : segment.duration; - var estimatedEnd = estimatedStart + estimatedDuration; - if (safeAppendWindow[0] !== undefined) { - estimatedStart = Math.max(estimatedStart, safeAppendWindow[0]); - } - if (safeAppendWindow[1] !== undefined) { - estimatedEnd = Math.min(estimatedEnd, safeAppendWindow[1]); - } - var inventoryInfos = (0,object_assign/* default */.Z)({ - segment: segment, - chunkSize: chunkSize, - start: estimatedStart, - end: estimatedEnd - }, content); - var canceller = new task_canceller/* default */.ZP(); - return fromCancellablePromise(canceller, function () { - return appendSegmentToBuffer(playbackObserver, segmentBuffer, { - data: data, - inventoryInfos: inventoryInfos - }, canceller.signal); - }).pipe((0,map/* map */.U)(function () { - var buffered = segmentBuffer.getBufferedRanges(); - return stream_events_generators/* default.addedSegment */.Z.addedSegment(content, segment, buffered, chunkData); - })); - }); +function pushMediaSegment(_x, _x2) { + return _pushMediaSegment.apply(this, arguments); +} +function _pushMediaSegment() { + _pushMediaSegment = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(_ref, cancelSignal) { + var playbackObserver, content, initSegmentData, parsedSegment, segment, segmentBuffer, _a, _b, chunkData, chunkInfos, chunkOffset, chunkSize, appendWindow, codec, _config$getCurrent, APPEND_WINDOW_SECURITIES, safeAppendWindow, data, estimatedStart, estimatedDuration, estimatedEnd, inventoryInfos, buffered; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + playbackObserver = _ref.playbackObserver, content = _ref.content, initSegmentData = _ref.initSegmentData, parsedSegment = _ref.parsedSegment, segment = _ref.segment, segmentBuffer = _ref.segmentBuffer; + if (!(parsedSegment.chunkData === null)) { + _context.next = 3; + break; + } + return _context.abrupt("return", null); + case 3: + if (!(cancelSignal.cancellationError !== null)) { + _context.next = 5; + break; + } + throw cancelSignal.cancellationError; + case 5: + chunkData = parsedSegment.chunkData, chunkInfos = parsedSegment.chunkInfos, chunkOffset = parsedSegment.chunkOffset, chunkSize = parsedSegment.chunkSize, appendWindow = parsedSegment.appendWindow; + codec = content.representation.getMimeTypeString(); + _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), APPEND_WINDOW_SECURITIES = _config$getCurrent.APPEND_WINDOW_SECURITIES; // Cutting exactly at the start or end of the appendWindow can lead to + // cases of infinite rebuffering due to how browser handle such windows. + // To work-around that, we add a small offset before and after those. + safeAppendWindow = [appendWindow[0] !== undefined ? Math.max(0, appendWindow[0] - APPEND_WINDOW_SECURITIES.START) : undefined, appendWindow[1] !== undefined ? appendWindow[1] + APPEND_WINDOW_SECURITIES.END : undefined]; + data = { + initSegment: initSegmentData, + chunk: chunkData, + timestampOffset: chunkOffset, + appendWindow: safeAppendWindow, + codec: codec + }; + estimatedStart = (_a = chunkInfos === null || chunkInfos === void 0 ? void 0 : chunkInfos.time) !== null && _a !== void 0 ? _a : segment.time; + estimatedDuration = (_b = chunkInfos === null || chunkInfos === void 0 ? void 0 : chunkInfos.duration) !== null && _b !== void 0 ? _b : segment.duration; + estimatedEnd = estimatedStart + estimatedDuration; + if (safeAppendWindow[0] !== undefined) { + estimatedStart = Math.max(estimatedStart, safeAppendWindow[0]); + } + if (safeAppendWindow[1] !== undefined) { + estimatedEnd = Math.min(estimatedEnd, safeAppendWindow[1]); + } + inventoryInfos = (0,object_assign/* default */.Z)({ + segment: segment, + chunkSize: chunkSize, + start: estimatedStart, + end: estimatedEnd + }, content); + _context.next = 18; + return appendSegmentToBuffer(playbackObserver, segmentBuffer, { + data: data, + inventoryInfos: inventoryInfos + }, cancelSignal); + case 18: + buffered = segmentBuffer.getBufferedRanges(); + return _context.abrupt("return", { + content: content, + segment: segment, + buffered: buffered, + segmentData: chunkData + }); + case 20: + case "end": + return _context.stop(); + } + } + }, _callee); + })); + return _pushMediaSegment.apply(this, arguments); } ;// CONCATENATED MODULE: ./src/core/stream/representation/representation_stream.ts /** @@ -49345,54 +45397,79 @@ function pushMediaSegment(_ref) { - - - - - /** - * Build up buffer for a single Representation. + * Perform the logic to load the right segments for the given Representation and + * push them to the given `SegmentBuffer`. * - * Download and push segments linked to the given Representation according - * to what is already in the SegmentBuffer and where the playback currently is. + * In essence, this is the entry point of the core streaming logic of the + * RxPlayer, the one actually responsible for finding which are the current + * right segments to load, loading them, and pushing them so they can be decoded. * - * Multiple RepresentationStream observables can run on the same SegmentBuffer. + * Multiple RepresentationStream can run on the same SegmentBuffer. * This allows for example smooth transitions between multiple periods. * - * @param {Object} args - * @returns {Observable} - */ -function RepresentationStream(_ref) { + * @param {Object} args - Various arguments allowing to know which segments to + * load, loading them and pushing them. + * You can check the corresponding type for more information. + * @param {Object} callbacks - The `RepresentationStream` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. + * + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `RepresentationStream` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `RepresentationStream` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, until the `terminating` callback has been triggered AND all loaded + * segments have been pushed, or until the `error` callback is called, whichever + * comes first. + * @param {Object} parentCancelSignal - `CancellationSignal` allowing, when + * triggered, to immediately stop all operations the `RepresentationStream` is + * doing. + */ +function RepresentationStream(_ref, callbacks, parentCancelSignal) { var content = _ref.content, options = _ref.options, playbackObserver = _ref.playbackObserver, segmentBuffer = _ref.segmentBuffer, segmentFetcher = _ref.segmentFetcher, - terminate$ = _ref.terminate$; + terminate = _ref.terminate; var period = content.period, adaptation = content.adaptation, representation = content.representation; - var bufferGoal$ = options.bufferGoal$, - maxBufferSize$ = options.maxBufferSize$, + var bufferGoal = options.bufferGoal, + maxBufferSize = options.maxBufferSize, drmSystemId = options.drmSystemId, - fastSwitchThreshold$ = options.fastSwitchThreshold$; + fastSwitchThreshold = options.fastSwitchThreshold; var bufferType = adaptation.type; + /** `TaskCanceller` stopping ALL operations performed by the `RepresentationStream` */ + var globalCanceller = new task_canceller/* default */.ZP(); + globalCanceller.linkToSignal(parentCancelSignal); + /** + * `TaskCanceller` allowing to only stop segment loading and checking operations. + * This allows to stop only tasks linked to network resource usage, which is + * often a limited resource, while still letting buffer operations to finish. + */ + var segmentsLoadingCanceller = new task_canceller/* default */.ZP(); + segmentsLoadingCanceller.linkToSignal(globalCanceller.signal); /** Saved initialization segment state for this representation. */ var initSegmentState = { segment: representation.index.getInitSegment(), segmentData: null, isLoaded: false }; - /** Allows to manually re-check which segments are needed. */ - var reCheckNeededSegments$ = new Subject/* Subject */.x(); /** Emit the last scheduled downloading queue for segments. */ var lastSegmentQueue = (0,reference/* createSharedReference */.$l)({ initSegment: null, segmentQueue: [] - }); + }, segmentsLoadingCanceller.signal); + /** If `true`, the current Representation has a linked initialization segment. */ var hasInitSegment = initSegmentState.segment !== null; - /** Will load every segments in `lastSegmentQueue` */ - var downloadingQueue = new DownloadingQueue(content, lastSegmentQueue, segmentFetcher, hasInitSegment); if (!hasInitSegment) { initSegmentState.segmentData = null; initSegmentState.isLoaded = true; @@ -49403,7 +45480,6 @@ function RepresentationStream(_ref) { * Allows to avoid sending multiple times protection events. */ var hasSentEncryptionData = false; - var encryptionEvent$ = empty/* EMPTY */.E; // If the DRM system id is already known, and if we already have encryption data // for it, we may not need to wait until the initialization segment is loaded to // signal required protection data, thus performing License negotiations sooner @@ -49414,25 +45490,86 @@ function RepresentationStream(_ref) { if (encryptionData.length > 0 && encryptionData.every(function (e) { return e.keyIds !== undefined; })) { - encryptionEvent$ = of.of.apply(void 0, encryptionData.map(function (d) { - return stream_events_generators/* default.encryptionDataEncountered */.Z.encryptionDataEncountered(d, content); - })); hasSentEncryptionData = true; + callbacks.encryptionDataEncountered(encryptionData.map(function (d) { + return (0,object_assign/* default */.Z)({ + content: content + }, d); + })); + if (globalCanceller.isUsed()) { + return; // previous callback has stopped everything by side-effect + } } } - /** Observable loading and pushing segments scheduled through `lastSegmentQueue`. */ - var queue$ = downloadingQueue.start().pipe((0,mergeMap/* mergeMap */.z)(onQueueEvent)); - /** Observable emitting the stream "status" and filling `lastSegmentQueue`. */ - var status$ = combineLatest([playbackObserver.getReference().asObservable(), bufferGoal$, maxBufferSize$, terminate$.pipe((0,take/* take */.q)(1), (0,startWith/* startWith */.O)(null)), reCheckNeededSegments$.pipe((0,startWith/* startWith */.O)(undefined))]).pipe((0,withLatestFrom/* withLatestFrom */.M)(fastSwitchThreshold$), (0,mergeMap/* mergeMap */.z)(function (_ref2) { - var _ref2$ = _ref2[0], - observation = _ref2$[0], - bufferGoal = _ref2$[1], - maxBufferSize = _ref2$[2], - terminate = _ref2$[3], - fastSwitchThreshold = _ref2[1]; + /** Will load every segments in `lastSegmentQueue` */ + var downloadingQueue = new DownloadingQueue(content, lastSegmentQueue, segmentFetcher, hasInitSegment); + downloadingQueue.addEventListener("error", function (err) { + if (segmentsLoadingCanceller.signal.isCancelled()) { + return; // ignore post requests-cancellation loading-related errors, + } + + globalCanceller.cancel(); // Stop every operations + callbacks.error(err); + }); + downloadingQueue.addEventListener("parsedInitSegment", onParsedChunk); + downloadingQueue.addEventListener("parsedMediaSegment", onParsedChunk); + downloadingQueue.addEventListener("emptyQueue", checkStatus); + downloadingQueue.addEventListener("requestRetry", function (payload) { + callbacks.warning(payload.error); + if (segmentsLoadingCanceller.signal.isCancelled()) { + return; // If the previous callback led to loading operations being stopped, skip + } + + var retriedSegment = payload.segment; + var index = representation.index; + if (index.isSegmentStillAvailable(retriedSegment) === false) { + checkStatus(); + } else if (index.canBeOutOfSyncError(payload.error, retriedSegment)) { + callbacks.manifestMightBeOufOfSync(); + } + }); + downloadingQueue.addEventListener("fullyLoadedSegment", function (segment) { + segmentBuffer.endOfSegment((0,object_assign/* default */.Z)({ + segment: segment + }, content), globalCanceller.signal)["catch"](onFatalBufferError); + }); + downloadingQueue.start(); + segmentsLoadingCanceller.signal.register(function () { + downloadingQueue.removeEventListener(); + downloadingQueue.stop(); + }); + playbackObserver.listen(checkStatus, { + includeLastObservation: false, + clearSignal: segmentsLoadingCanceller.signal + }); + bufferGoal.onUpdate(checkStatus, { + emitCurrentValue: false, + clearSignal: segmentsLoadingCanceller.signal + }); + maxBufferSize.onUpdate(checkStatus, { + emitCurrentValue: false, + clearSignal: segmentsLoadingCanceller.signal + }); + terminate.onUpdate(checkStatus, { + emitCurrentValue: false, + clearSignal: segmentsLoadingCanceller.signal + }); + checkStatus(); + return; + /** + * Produce a buffer status update synchronously on call, update the list + * of current segments to update and check various buffer and manifest related + * issues at the current time, calling the right callbacks if necessary. + */ + function checkStatus() { var _a, _b; + if (segmentsLoadingCanceller.isUsed()) { + return; // Stop all buffer status checking if load operations are stopped + } + + var observation = playbackObserver.getReference().getValue(); var initialWantedTime = (_a = observation.position.pending) !== null && _a !== void 0 ? _a : observation.position.last; - var status = getBufferStatus(content, initialWantedTime, playbackObserver, fastSwitchThreshold, bufferGoal, maxBufferSize, segmentBuffer); + var status = getBufferStatus(content, initialWantedTime, playbackObserver, fastSwitchThreshold.getValue(), bufferGoal.getValue(), maxBufferSize.getValue(), segmentBuffer); var neededSegments = status.neededSegments; var neededInitSegment = null; // Add initialization segment if required @@ -49455,19 +45592,22 @@ function RepresentationStream(_ref) { priority: initSegmentPriority }; } - if (terminate === null) { + var terminateVal = terminate.getValue(); + if (terminateVal === null) { lastSegmentQueue.setValue({ initSegment: neededInitSegment, segmentQueue: neededSegments }); - } else if (terminate.urgent) { + } else if (terminateVal.urgent) { log/* default.debug */.Z.debug("Stream: Urgent switch, terminate now.", bufferType); lastSegmentQueue.setValue({ initSegment: null, segmentQueue: [] }); lastSegmentQueue.finish(); - return (0,of.of)(stream_events_generators/* default.streamTerminating */.Z.streamTerminating()); + segmentsLoadingCanceller.cancel(); + callbacks.terminating(); + return; } else { // Non-urgent termination wanted: // End the download of the current media segment if pending and @@ -49485,139 +45625,141 @@ function RepresentationStream(_ref) { if (nextQueue.length === 0 && nextInit === null) { log/* default.debug */.Z.debug("Stream: No request left, terminate", bufferType); lastSegmentQueue.finish(); - return (0,of.of)(stream_events_generators/* default.streamTerminating */.Z.streamTerminating()); + segmentsLoadingCanceller.cancel(); + callbacks.terminating(); + return; } } - var bufferStatusEvt = (0,of.of)({ - type: "stream-status", - value: { - period: period, - position: observation.position.last, - bufferType: bufferType, - imminentDiscontinuity: status.imminentDiscontinuity, - hasFinishedLoading: status.hasFinishedLoading, - neededSegments: status.neededSegments - } + callbacks.streamStatusUpdate({ + period: period, + position: observation.position.last, + bufferType: bufferType, + imminentDiscontinuity: status.imminentDiscontinuity, + isEmptyStream: false, + hasFinishedLoading: status.hasFinishedLoading, + neededSegments: status.neededSegments }); - var bufferRemoval = empty/* EMPTY */.E; + if (segmentsLoadingCanceller.signal.isCancelled()) { + return; // previous callback has stopped loading operations by side-effect + } + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), UPTO_CURRENT_POSITION_CLEANUP = _config$getCurrent.UPTO_CURRENT_POSITION_CLEANUP; if (status.isBufferFull) { var gcedPosition = Math.max(0, initialWantedTime - UPTO_CURRENT_POSITION_CLEANUP); if (gcedPosition > 0) { - var removalCanceller = new task_canceller/* default */.ZP(); - bufferRemoval = fromCancellablePromise(removalCanceller, function () { - return segmentBuffer.removeBuffer(0, gcedPosition, removalCanceller.signal); - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - ).pipe((0,ignoreElements/* ignoreElements */.l)()); + segmentBuffer.removeBuffer(0, gcedPosition, globalCanceller.signal)["catch"](onFatalBufferError); } } - return status.shouldRefreshManifest ? (0,concat/* concat */.z)((0,of.of)(stream_events_generators/* default.needsManifestRefresh */.Z.needsManifestRefresh()), bufferStatusEvt, bufferRemoval) : (0,concat/* concat */.z)(bufferStatusEvt, bufferRemoval); - }), takeWhile(function (e) { - return e.type !== "stream-terminating"; - }, true)); - return (0,merge/* merge */.T)(status$, queue$, encryptionEvent$).pipe((0,share/* share */.B)()); - /** - * React to event from the `DownloadingQueue`. - * @param {Object} evt - * @returns {Observable} - */ - function onQueueEvent(evt) { - switch (evt.type) { - case "retry": - return (0,concat/* concat */.z)((0,of.of)({ - type: "warning", - value: evt.value.error - }), (0,defer/* defer */.P)(function () { - var retriedSegment = evt.value.segment; - var index = representation.index; - if (index.isSegmentStillAvailable(retriedSegment) === false) { - reCheckNeededSegments$.next(); - } else if (index.canBeOutOfSyncError(evt.value.error, retriedSegment)) { - return (0,of.of)(stream_events_generators/* default.manifestMightBeOufOfSync */.Z.manifestMightBeOufOfSync()); - } - return empty/* EMPTY */.E; // else, ignore. - })); - - case "parsed-init": - case "parsed-media": - return onParsedChunk(evt); - case "end-of-segment": - { - var segment = evt.value.segment; - var endOfSegmentCanceller = new task_canceller/* default */.ZP(); - return fromCancellablePromise(endOfSegmentCanceller, function () { - return segmentBuffer.endOfSegment((0,object_assign/* default */.Z)({ - segment: segment - }, content), endOfSegmentCanceller.signal); - }) - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */.pipe((0,ignoreElements/* ignoreElements */.l)()); - } - case "end-of-queue": - reCheckNeededSegments$.next(); - return empty/* EMPTY */.E; - default: - (0,assert_unreachable/* default */.Z)(evt); + if (status.shouldRefreshManifest) { + callbacks.needsManifestRefresh(); } } /** * Process a chunk that has just been parsed by pushing it to the * SegmentBuffer and emitting the right events. * @param {Object} evt - * @returns {Observable} */ function onParsedChunk(evt) { + if (globalCanceller.isUsed()) { + // We should not do anything with segments if the `RepresentationStream` + // is not running anymore. + return; + } if (evt.segmentType === "init") { - next_tick_default()(function () { - reCheckNeededSegments$.next(); - }); initSegmentState.segmentData = evt.initializationData; initSegmentState.isLoaded = true; // Now that the initialization segment has been parsed - which may have // included encryption information - take care of the encryption event // if not already done. - var allEncryptionData = representation.getAllEncryptionData(); - var initEncEvt$ = !hasSentEncryptionData && allEncryptionData.length > 0 ? of.of.apply(void 0, allEncryptionData.map(function (p) { - return stream_events_generators/* default.encryptionDataEncountered */.Z.encryptionDataEncountered(p, content); - })) : empty/* EMPTY */.E; - var pushEvent$ = pushInitSegment({ + if (!hasSentEncryptionData) { + var allEncryptionData = representation.getAllEncryptionData(); + if (allEncryptionData.length > 0) { + callbacks.encryptionDataEncountered(allEncryptionData.map(function (p) { + return (0,object_assign/* default */.Z)({ + content: content + }, p); + })); + } + } + pushInitSegment({ playbackObserver: playbackObserver, content: content, segment: evt.segment, segmentData: evt.initializationData, segmentBuffer: segmentBuffer - }); - return (0,merge/* merge */.T)(initEncEvt$, pushEvent$); + }, globalCanceller.signal).then(function (result) { + if (result !== null) { + callbacks.addedSegment(result); + } + })["catch"](onFatalBufferError); + // Sometimes the segment list is only known once the initialization segment + // is parsed. Thus we immediately re-check if there's new segments to load. + checkStatus(); } else { var inbandEvents = evt.inbandEvents, needsManifestRefresh = evt.needsManifestRefresh, protectionDataUpdate = evt.protectionDataUpdate; // TODO better handle use cases like key rotation by not always grouping // every protection data together? To check. - var segmentEncryptionEvent$ = protectionDataUpdate && !hasSentEncryptionData ? of.of.apply(void 0, representation.getAllEncryptionData().map(function (p) { - return stream_events_generators/* default.encryptionDataEncountered */.Z.encryptionDataEncountered(p, content); - })) : empty/* EMPTY */.E; - var manifestRefresh$ = needsManifestRefresh === true ? (0,of.of)(stream_events_generators/* default.needsManifestRefresh */.Z.needsManifestRefresh()) : empty/* EMPTY */.E; - var inbandEvents$ = inbandEvents !== undefined && inbandEvents.length > 0 ? (0,of.of)({ - type: "inband-events", - value: inbandEvents - }) : empty/* EMPTY */.E; + if (!hasSentEncryptionData && protectionDataUpdate) { + var _allEncryptionData = representation.getAllEncryptionData(); + if (_allEncryptionData.length > 0) { + callbacks.encryptionDataEncountered(_allEncryptionData.map(function (p) { + return (0,object_assign/* default */.Z)({ + content: content + }, p); + })); + if (globalCanceller.isUsed()) { + return; // previous callback has stopped everything by side-effect + } + } + } + + if (needsManifestRefresh === true) { + callbacks.needsManifestRefresh(); + if (globalCanceller.isUsed()) { + return; // previous callback has stopped everything by side-effect + } + } + + if (inbandEvents !== undefined && inbandEvents.length > 0) { + callbacks.inbandEvent(inbandEvents); + if (globalCanceller.isUsed()) { + return; // previous callback has stopped everything by side-effect + } + } + var initSegmentData = initSegmentState.segmentData; - var pushMediaSegment$ = pushMediaSegment({ + pushMediaSegment({ playbackObserver: playbackObserver, content: content, initSegmentData: initSegmentData, parsedSegment: evt, segment: evt.segment, segmentBuffer: segmentBuffer - }); - return (0,concat/* concat */.z)(segmentEncryptionEvent$, manifestRefresh$, inbandEvents$, pushMediaSegment$); + }, globalCanceller.signal).then(function (result) { + if (result !== null) { + callbacks.addedSegment(result); + } + })["catch"](onFatalBufferError); + } + } + /** + * Handle Buffer-related fatal errors by cancelling everything the + * `RepresentationStream` is doing and calling the error callback with the + * corresponding error. + * @param {*} err + */ + function onFatalBufferError(err) { + if (globalCanceller.isUsed() && err instanceof task_canceller/* CancellationError */.FU) { + // The error is linked to cancellation AND we explicitely cancelled buffer + // operations. + // We can thus ignore it, it is very unlikely to lead to true buffer issues. + return; } + globalCanceller.cancel(); + callbacks.error(err); } } ;// CONCATENATED MODULE: ./src/core/stream/representation/index.ts @@ -49637,8 +45779,9 @@ function RepresentationStream(_ref) { * limitations under the License. */ + /* harmony default export */ var stream_representation = (RepresentationStream); -;// CONCATENATED MODULE: ./src/core/stream/adaptation/create_representation_estimator.ts +;// CONCATENATED MODULE: ./src/core/stream/adaptation/utils/create_representation_estimator.ts /** * Copyright 2015 CANAL+ Group * @@ -49677,7 +45820,7 @@ function RepresentationStream(_ref) { function getRepresentationEstimate(content, representationEstimator, currentRepresentation, playbackObserver, onFatalError, cancellationSignal) { var manifest = content.manifest, adaptation = content.adaptation; - var representations = (0,reference/* default */.ZP)([]); + var representations = (0,reference/* default */.ZP)([], cancellationSignal); updateRepresentationsReference(); manifest.addEventListener("decipherabilityUpdate", updateRepresentationsReference); var unregisterCleanUp = cancellationSignal.register(cleanUp); @@ -49710,7 +45853,6 @@ function getRepresentationEstimate(content, representationEstimator, currentRepr /** Clean-up all resources taken here. */ function cleanUp() { manifest.removeEventListener("decipherabilityUpdate", updateRepresentationsReference); - representations.finish(); // check to protect against the case where it is not yet defined. if (typeof unregisterCleanUp !== "undefined") { unregisterCleanUp(); @@ -49733,16 +45875,6 @@ function getRepresentationEstimate(content, representationEstimator, currentRepr * See the License for the specific language governing permissions and * limitations under the License. */ -/** - * This file allows to create `AdaptationStream`s. - * - * An `AdaptationStream` downloads and push segment for a single Adaptation - * (e.g. a single audio, video or text track). - * It chooses which Representation to download mainly thanks to the - * IRepresentationEstimator, and orchestrates a RepresentationStream, - * which will download and push segments corresponding to a chosen - * Representation. - */ @@ -49755,20 +45887,36 @@ function getRepresentationEstimate(content, representationEstimator, currentRepr /** - * Create new AdaptationStream Observable, which task will be to download the - * media data for a given Adaptation (i.e. "track"). + * Create new `AdaptationStream` whose task will be to download the media data + * for a given Adaptation (i.e. "track"). * * It will rely on the IRepresentationEstimator to choose at any time the * best Representation for this Adaptation and then run the logic to download * and push the corresponding segments in the SegmentBuffer. * - * After being subscribed to, it will start running and will emit various events - * to report its current status. - * - * @param {Object} args - * @returns {Observable} - */ -function AdaptationStream(_ref) { + * @param {Object} args - Various arguments allowing the `AdaptationStream` to + * determine which Representation to choose and which segments to load from it. + * You can check the corresponding type for more information. + * @param {Object} callbacks - The `AdaptationStream` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. + * + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `AdaptationStream` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `AdaptationStream` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, or until the `error` callback is called, whichever comes first. + * @param {Object} parentCancelSignal - `CancellationSignal` allowing, when + * triggered, to immediately stop all operations the `AdaptationStream` is + * doing. + */ +function AdaptationStream(_ref, callbacks, parentCancelSignal) { var playbackObserver = _ref.playbackObserver, content = _ref.content, options = _ref.options, @@ -49781,6 +45929,9 @@ function AdaptationStream(_ref) { var manifest = content.manifest, period = content.period, adaptation = content.adaptation; + /** Allows to cancel everything the `AdaptationStream` is doing. */ + var adapStreamCanceller = new task_canceller/* default */.ZP(); + adapStreamCanceller.linkToSignal(parentCancelSignal); /** * The buffer goal ratio base itself on the value given by `wantedBufferAhead` * to determine a more dynamic buffer goal for a given Representation. @@ -49789,14 +45940,16 @@ function AdaptationStream(_ref) { * buffering and tells us that we should try to bufferize less data : * https://developers.google.com/web/updates/2017/10/quotaexceedederror */ - var bufferGoalRatioMap = {}; - var currentRepresentation = (0,reference/* createSharedReference */.$l)(null); - /** Errors when the adaptive logic fails with an error. */ - var abrErrorSubject = new Subject/* Subject */.x(); - var adaptiveCanceller = new task_canceller/* default */.ZP(); + var bufferGoalRatioMap = new Map(); + /** + * Emit the currently chosen `Representation`. + * `null` if no Representation is chosen for now. + */ + var currentRepresentation = (0,reference/* createSharedReference */.$l)(null, adapStreamCanceller.signal); var _createRepresentation = getRepresentationEstimate(content, representationEstimator, currentRepresentation, playbackObserver, function (err) { - abrErrorSubject.error(err); - }, adaptiveCanceller.signal), + adapStreamCanceller.cancel(); + callbacks.error(err); + }, adapStreamCanceller.signal), estimateRef = _createRepresentation.estimateRef, abrCallbacks = _createRepresentation.abrCallbacks; /** Allows the `RepresentationStream` to easily fetch media segments. */ @@ -49808,85 +45961,111 @@ function AdaptationStream(_ref) { onMetrics: abrCallbacks.metrics }); /* eslint-enable @typescript-eslint/unbound-method */ - /** - * Stores the last estimate emitted through the `abrEstimate$` Observable, - * starting with `null`. - * This allows to easily rely on that value in inner Observables which might also - * need the last already-considered value. - */ - var lastEstimate = (0,reference/* createSharedReference */.$l)(null); - /** Emits abr estimates on Subscription. */ - var abrEstimate$ = estimateRef.asObservable().pipe((0,tap/* tap */.b)(function (estimate) { - lastEstimate.setValue(estimate); - }), (0,defer_subscriptions/* default */.Z)(), (0,share/* share */.B)()); + /** Stores the last emitted bitrate. */ + var previousBitrate; /** Emit at each bitrate estimate done by the IRepresentationEstimator. */ - var bitrateEstimate$ = abrEstimate$.pipe((0,filter/* filter */.h)(function (_ref2) { + estimateRef.onUpdate(function (_ref2) { var bitrate = _ref2.bitrate; - return bitrate != null; - }), distinctUntilChanged(function (old, current) { - return old.bitrate === current.bitrate; - }), (0,map/* map */.U)(function (_ref3) { - var bitrate = _ref3.bitrate; + if (bitrate === undefined) { + return; + } + if (bitrate === previousBitrate) { + return; + } + previousBitrate = bitrate; log/* default.debug */.Z.debug("Stream: new " + adaptation.type + " bitrate estimate", bitrate); - return stream_events_generators/* default.bitrateEstimationChange */.Z.bitrateEstimationChange(adaptation.type, bitrate); - })); - /** Recursively create `RepresentationStream`s according to the last estimate. */ - var representationStreams$ = abrEstimate$.pipe(exhaustMap(function (estimate, i) { - return recursivelyCreateRepresentationStreams(estimate, i === 0); - })); - return (0,merge/* merge */.T)(abrErrorSubject, representationStreams$, bitrateEstimate$, - // Cancel adaptive logic on unsubscription - new Observable/* Observable */.y(function () { - return function () { - return adaptiveCanceller.cancel(); - }; - })); + callbacks.bitrateEstimationChange({ + type: adaptation.type, + bitrate: bitrate + }); + }, { + emitCurrentValue: true, + clearSignal: adapStreamCanceller.signal + }); + recursivelyCreateRepresentationStreams(true); /** - * Create `RepresentationStream`s starting with the Representation indicated in - * `fromEstimate` argument. + * Create `RepresentationStream`s starting with the Representation of the last + * estimate performed. * Each time a new estimate is made, this function will create a new * `RepresentationStream` corresponding to that new estimate. - * @param {Object} fromEstimate - The first estimate we should start with * @param {boolean} isFirstEstimate - Whether this is the first time we're - * creating a RepresentationStream in the corresponding `AdaptationStream`. + * creating a `RepresentationStream` in the corresponding `AdaptationStream`. * This is important because manual quality switches might need a full reload * of the MediaSource _except_ if we are talking about the first quality chosen. - * @returns {Observable} */ - function recursivelyCreateRepresentationStreams(fromEstimate, isFirstEstimate) { - var representation = fromEstimate.representation; + function recursivelyCreateRepresentationStreams(isFirstEstimate) { + /** + * `TaskCanceller` triggered when the current `RepresentationStream` is + * terminating and as such the next one might be immediately created + * recursively. + */ + var repStreamTerminatingCanceller = new task_canceller/* default */.ZP(); + repStreamTerminatingCanceller.linkToSignal(adapStreamCanceller.signal); + var _estimateRef$getValue = estimateRef.getValue(), + representation = _estimateRef$getValue.representation, + manual = _estimateRef$getValue.manual; + if (representation === null) { + return; + } // A manual bitrate switch might need an immediate feedback. // To do that properly, we need to reload the MediaSource - if (directManualBitrateSwitching && fromEstimate.manual && !isFirstEstimate) { + if (directManualBitrateSwitching && manual && !isFirstEstimate) { var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), DELTA_POSITION_AFTER_RELOAD = _config$getCurrent.DELTA_POSITION_AFTER_RELOAD; - return reloadAfterSwitch(period, adaptation.type, playbackObserver, DELTA_POSITION_AFTER_RELOAD.bitrateSwitch); + // We begin by scheduling a micro-task to reduce the possibility of race + // conditions where the inner logic would be called synchronously before + // the next observation (which may reflect very different playback conditions) + // is actually received. + return next_tick_default()(function () { + playbackObserver.listen(function (observation) { + var _a, _b; + var _estimateRef$getValue2 = estimateRef.getValue(), + newManual = _estimateRef$getValue2.manual; + if (!newManual) { + return; + } + var currentTime = playbackObserver.getCurrentTime(); + var pos = currentTime + DELTA_POSITION_AFTER_RELOAD.bitrateSwitch; + // Bind to Period start and end + var position = Math.min(Math.max(period.start, pos), (_a = period.end) !== null && _a !== void 0 ? _a : Infinity); + var autoPlay = !((_b = observation.paused.pending) !== null && _b !== void 0 ? _b : playbackObserver.getIsPaused()); + return callbacks.waitingMediaSourceReload({ + bufferType: adaptation.type, + period: period, + position: position, + autoPlay: autoPlay + }); + }, { + includeLastObservation: true, + clearSignal: repStreamTerminatingCanceller.signal + }); + }); } /** * Emit when the current RepresentationStream should be terminated to make * place for a new one (e.g. when switching quality). */ - var terminateCurrentStream$ = lastEstimate.asObservable().pipe((0,filter/* filter */.h)(function (newEstimate) { - return newEstimate === null || newEstimate.representation.id !== representation.id || newEstimate.manual && !fromEstimate.manual; - }), (0,take/* take */.q)(1), (0,map/* map */.U)(function (newEstimate) { - if (newEstimate === null) { - log/* default.info */.Z.info("Stream: urgent Representation termination", adaptation.type); - return { - urgent: true - }; + var terminateCurrentStream = (0,reference/* createSharedReference */.$l)(null, repStreamTerminatingCanceller.signal); + /** Allows to stop listening to estimateRef on the following line. */ + estimateRef.onUpdate(function (estimate) { + if (estimate.representation === null || estimate.representation.id === representation.id) { + return; } - if (newEstimate.urgent) { + if (estimate.urgent) { log/* default.info */.Z.info("Stream: urgent Representation switch", adaptation.type); - return { + return terminateCurrentStream.setValue({ urgent: true - }; + }); } else { log/* default.info */.Z.info("Stream: slow Representation switch", adaptation.type); - return { + return terminateCurrentStream.setValue({ urgent: false - }; + }); } - })); + }, { + clearSignal: repStreamTerminatingCanceller.signal, + emitCurrentValue: true + }); /** * "Fast-switching" is a behavior allowing to replace low-quality segments * (i.e. with a low bitrate) with higher-quality segments (higher bitrate) in @@ -49898,81 +46077,139 @@ function AdaptationStream(_ref) { * Set to `undefined` to indicate that there's no threshold (anything can be * replaced by higher-quality segments). */ - var fastSwitchThreshold$ = !options.enableFastSwitching ? (0,of.of)(0) : - // Do not fast-switch anything - lastEstimate.asObservable().pipe((0,map/* map */.U)(function (estimate) { - return estimate === null ? undefined : estimate.knownStableBitrate; - }), distinctUntilChanged()); - var representationChange$ = (0,of.of)(stream_events_generators/* default.representationChange */.Z.representationChange(adaptation.type, period, representation)); - return (0,concat/* concat */.z)(representationChange$, createRepresentationStream(representation, terminateCurrentStream$, fastSwitchThreshold$)).pipe((0,tap/* tap */.b)(function (evt) { - if (evt.type === "added-segment") { - abrCallbacks.addedSegment(evt.value); - } - if (evt.type === "representationChange") { - currentRepresentation.setValue(evt.value.representation); - } - }), (0,mergeMap/* mergeMap */.z)(function (evt) { - if (evt.type === "stream-terminating") { - var estimate = lastEstimate.getValue(); - if (estimate === null) { - return empty/* EMPTY */.E; - } - return recursivelyCreateRepresentationStreams(estimate, false); - } - return (0,of.of)(evt); - })); + var fastSwitchThreshold = (0,reference/* createSharedReference */.$l)(0); + if (options.enableFastSwitching) { + estimateRef.onUpdate(function (estimate) { + fastSwitchThreshold.setValueIfChanged(estimate === null || estimate === void 0 ? void 0 : estimate.knownStableBitrate); + }, { + clearSignal: repStreamTerminatingCanceller.signal, + emitCurrentValue: true + }); + } + var repInfo = { + type: adaptation.type, + period: period, + representation: representation + }; + currentRepresentation.setValue(representation); + if (adapStreamCanceller.isUsed()) { + return; // previous callback has stopped everything by side-effect + } + + callbacks.representationChange(repInfo); + if (adapStreamCanceller.isUsed()) { + return; // previous callback has stopped everything by side-effect + } + + var representationStreamCallbacks = { + streamStatusUpdate: callbacks.streamStatusUpdate, + encryptionDataEncountered: callbacks.encryptionDataEncountered, + manifestMightBeOufOfSync: callbacks.manifestMightBeOufOfSync, + needsManifestRefresh: callbacks.needsManifestRefresh, + inbandEvent: callbacks.inbandEvent, + warning: callbacks.warning, + error: function error(err) { + adapStreamCanceller.cancel(); + callbacks.error(err); + }, + addedSegment: function addedSegment(segmentInfo) { + abrCallbacks.addedSegment(segmentInfo); + if (adapStreamCanceller.isUsed()) { + return; + } + callbacks.addedSegment(segmentInfo); + }, + terminating: function terminating() { + if (repStreamTerminatingCanceller.isUsed()) { + return; // Already handled + } + + repStreamTerminatingCanceller.cancel(); + return recursivelyCreateRepresentationStreams(false); + } + }; + createRepresentationStream(representation, terminateCurrentStream, fastSwitchThreshold, representationStreamCallbacks); } /** - * Create and returns a new RepresentationStream Observable, linked to the + * Create and returns a new `RepresentationStream`, linked to the * given Representation. - * @param {Representation} representation - * @returns {Observable} - */ - function createRepresentationStream(representation, terminateCurrentStream$, fastSwitchThreshold$) { - return (0,defer/* defer */.P)(function () { - var oldBufferGoalRatio = bufferGoalRatioMap[representation.id]; - var bufferGoalRatio = oldBufferGoalRatio != null ? oldBufferGoalRatio : 1; - bufferGoalRatioMap[representation.id] = bufferGoalRatio; - var bufferGoal$ = wantedBufferAhead.asObservable().pipe((0,map/* map */.U)(function (wba) { - return wba * bufferGoalRatio; - })); - // eslint-disable-next-line max-len - var maxBufferSize$ = adaptation.type === "video" ? maxVideoBufferSize.asObservable() : (0,of.of)(Infinity); - log/* default.info */.Z.info("Stream: changing representation", adaptation.type, representation.id, representation.bitrate); - return stream_representation({ - playbackObserver: playbackObserver, - content: { - representation: representation, - adaptation: adaptation, - period: period, - manifest: manifest - }, - segmentBuffer: segmentBuffer, - segmentFetcher: segmentFetcher, - terminate$: terminateCurrentStream$, - options: { - bufferGoal$: bufferGoal$, - maxBufferSize$: maxBufferSize$, - drmSystemId: options.drmSystemId, - fastSwitchThreshold$: fastSwitchThreshold$ - } - }).pipe((0,catchError/* catchError */.K)(function (err) { + * @param {Object} representation + * @param {Object} terminateCurrentStream + * @param {Object} fastSwitchThreshold + * @param {Object} representationStreamCallbacks + */ + function createRepresentationStream(representation, terminateCurrentStream, fastSwitchThreshold, representationStreamCallbacks) { + /** + * `TaskCanceller` triggered when the `RepresentationStream` calls its + * `terminating` callback. + */ + var terminatingRepStreamCanceller = new task_canceller/* default */.ZP(); + terminatingRepStreamCanceller.linkToSignal(adapStreamCanceller.signal); + var bufferGoal = (0,reference/* createMappedReference */.lR)(wantedBufferAhead, function (prev) { + return prev * getBufferGoalRatio(representation); + }, terminatingRepStreamCanceller.signal); + var maxBufferSize = adaptation.type === "video" ? maxVideoBufferSize : (0,reference/* createSharedReference */.$l)(Infinity); + log/* default.info */.Z.info("Stream: changing representation", adaptation.type, representation.id, representation.bitrate); + var updatedCallbacks = (0,object_assign/* default */.Z)({}, representationStreamCallbacks, { + error: function error(err) { + var _a; var formattedError = (0,format_error/* default */.Z)(err, { defaultCode: "NONE", defaultReason: "Unknown `RepresentationStream` error" }); - if (formattedError.code === "BUFFER_FULL_ERROR") { + if (formattedError.code !== "BUFFER_FULL_ERROR") { + representationStreamCallbacks.error(err); + } else { var wba = wantedBufferAhead.getValue(); - var lastBufferGoalRatio = bufferGoalRatio; - if (lastBufferGoalRatio <= 0.25 || wba * lastBufferGoalRatio <= 2) { + var lastBufferGoalRatio = (_a = bufferGoalRatioMap.get(representation.id)) !== null && _a !== void 0 ? _a : 1; + // 70%, 49%, 34.3%, 24%, 16.81%, 11.76%, 8.24% and 5.76% + var newBufferGoalRatio = lastBufferGoalRatio * 0.7; + if (newBufferGoalRatio <= 0.05 || wba * newBufferGoalRatio <= 2) { throw formattedError; } - bufferGoalRatioMap[representation.id] = lastBufferGoalRatio - 0.25; - return createRepresentationStream(representation, terminateCurrentStream$, fastSwitchThreshold$); + bufferGoalRatioMap.set(representation.id, newBufferGoalRatio); + // We wait 4 seconds to let the situation evolve by itself before + // retrying loading segments with a lower buffer goal + (0,cancellable_sleep/* default */.Z)(4000, adapStreamCanceller.signal).then(function () { + return createRepresentationStream(representation, terminateCurrentStream, fastSwitchThreshold, representationStreamCallbacks); + })["catch"](noop/* default */.Z); } - throw formattedError; - })); + }, + terminating: function terminating() { + terminatingRepStreamCanceller.cancel(); + representationStreamCallbacks.terminating(); + } }); + stream_representation({ + playbackObserver: playbackObserver, + content: { + representation: representation, + adaptation: adaptation, + period: period, + manifest: manifest + }, + segmentBuffer: segmentBuffer, + segmentFetcher: segmentFetcher, + terminate: terminateCurrentStream, + options: { + bufferGoal: bufferGoal, + maxBufferSize: maxBufferSize, + drmSystemId: options.drmSystemId, + fastSwitchThreshold: fastSwitchThreshold + } + }, updatedCallbacks, adapStreamCanceller.signal); + } + /** + * @param {Object} representation + * @returns {number} + */ + function getBufferGoalRatio(representation) { + var oldBufferGoalRatio = bufferGoalRatioMap.get(representation.id); + var bufferGoalRatio = oldBufferGoalRatio !== undefined ? oldBufferGoalRatio : 1; + if (oldBufferGoalRatio === undefined) { + bufferGoalRatioMap.set(representation.id, bufferGoalRatio); + } + return bufferGoalRatio; } } ;// CONCATENATED MODULE: ./src/core/stream/adaptation/index.ts @@ -49992,63 +46229,8 @@ function AdaptationStream(_ref) { * limitations under the License. */ -/* harmony default export */ var stream_adaptation = (AdaptationStream); -;// CONCATENATED MODULE: ./src/core/stream/period/create_empty_adaptation_stream.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Create empty AdaptationStream Observable, linked to a Period. - * - * This observable will never download any segment and just emit a "full" - * event when reaching the end. - * @param {Observable} playbackObserver - * @param {Object} wantedBufferAhead - * @param {string} bufferType - * @param {Object} content - * @returns {Observable} - */ -function createEmptyAdaptationStream(playbackObserver, wantedBufferAhead, bufferType, content) { - var period = content.period; - var hasFinishedLoading = false; - var wantedBufferAhead$ = wantedBufferAhead.asObservable(); - var observation$ = playbackObserver.getReference().asObservable(); - return combineLatest([observation$, wantedBufferAhead$]).pipe((0,mergeMap/* mergeMap */.z)(function (_ref) { - var observation = _ref[0], - wba = _ref[1]; - var position = observation.position.last; - if (period.end !== undefined && position + wba >= period.end) { - log/* default.debug */.Z.debug("Stream: full \"empty\" AdaptationStream", bufferType); - hasFinishedLoading = true; - } - return (0,of.of)({ - type: "stream-status", - value: { - period: period, - bufferType: bufferType, - position: position, - imminentDiscontinuity: null, - hasFinishedLoading: hasFinishedLoading, - neededSegments: [], - shouldRefreshManifest: false - } - }); - })); -} +/* harmony default export */ var stream_adaptation = (AdaptationStream); // EXTERNAL MODULE: ./src/utils/starts_with.ts var starts_with = __webpack_require__(9252); ;// CONCATENATED MODULE: ./src/utils/are_codecs_compatible.ts @@ -50104,7 +46286,7 @@ function areCodecsCompatible(a, b) { return true; } /* harmony default export */ var are_codecs_compatible = (areCodecsCompatible); -;// CONCATENATED MODULE: ./src/core/stream/period/get_adaptation_switch_strategy.ts +;// CONCATENATED MODULE: ./src/core/stream/period/utils/get_adaptation_switch_strategy.ts /** * Copyright 2015 CANAL+ Group * @@ -50341,6 +46523,11 @@ function getFirstSegmentAfterPeriod(inventory, period) { return null; } ;// CONCATENATED MODULE: ./src/core/stream/period/period_stream.ts + + +function period_stream_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = period_stream_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function period_stream_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return period_stream_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return period_stream_arrayLikeToArray(o, minLen); } +function period_stream_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -50367,21 +46554,38 @@ function getFirstSegmentAfterPeriod(inventory, period) { - - - - /** - * Create single PeriodStream Observable: + * Create a single PeriodStream: * - Lazily create (or reuse) a SegmentBuffer for the given type. * - Create a Stream linked to an Adaptation each time it changes, to * download and append the corresponding segments to the SegmentBuffer. * - Announce when the Stream is full or is awaiting new Segments through * events - * @param {Object} args - * @returns {Observable} - */ -function PeriodStream(_ref) { + * + * @param {Object} args - Various arguments allowing the `PeriodStream` to + * determine which Adaptation and which Representation to choose, as well as + * which segments to load from it. + * You can check the corresponding type for more information. + * @param {Object} callbacks - The `PeriodStream` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. + * + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `AdaptationStream` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `AdaptationStream` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, or until the `error` callback is called, whichever comes first. + * @param {Object} parentCancelSignal - `CancellationSignal` allowing, when + * triggered, to immediately stop all operations the `PeriodStream` is + * doing. + */ +function PeriodStream(_ref, callbacks, parentCancelSignal) { var bufferType = _ref.bufferType, content = _ref.content, garbageCollectors = _ref.garbageCollectors, @@ -50393,96 +46597,217 @@ function PeriodStream(_ref) { wantedBufferAhead = _ref.wantedBufferAhead, maxVideoBufferSize = _ref.maxVideoBufferSize; var period = content.period; - // Emits the chosen Adaptation for the current type. - // `null` when no Adaptation is chosen (e.g. no subtitles) - var adaptation$ = new ReplaySubject/* ReplaySubject */.t(1); - return adaptation$.pipe((0,switchMap/* switchMap */.w)(function (adaptation, switchNb) { - /** - * If this is not the first Adaptation choice, we might want to apply a - * delta to the current position so we can re-play back some media in the - * new Adaptation to give some context back. - * This value contains this relative position, in seconds. - * @see reloadAfterSwitch - */ - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - DELTA_POSITION_AFTER_RELOAD = _config$getCurrent.DELTA_POSITION_AFTER_RELOAD; - var relativePosAfterSwitch = switchNb === 0 ? 0 : bufferType === "audio" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.audio : bufferType === "video" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.video : DELTA_POSITION_AFTER_RELOAD.trackSwitch.other; - if (adaptation === null) { - // Current type is disabled for that Period - log/* default.info */.Z.info("Stream: Set no " + bufferType + " Adaptation. P:", period.start); - var segmentBufferStatus = segmentBuffersStore.getStatus(bufferType); - var cleanBuffer$; - if (segmentBufferStatus.type === "initialized") { - log/* default.info */.Z.info("Stream: Clearing previous " + bufferType + " SegmentBuffer"); - if (segment_buffers.isNative(bufferType)) { - return reloadAfterSwitch(period, bufferType, playbackObserver, 0); - } - var canceller = new task_canceller/* default */.ZP(); - cleanBuffer$ = fromCancellablePromise(canceller, function () { - if (period.end === undefined) { - return segmentBufferStatus.value.removeBuffer(period.start, Infinity, canceller.signal); - } else if (period.end <= period.start) { - return Promise.resolve(); - } else { - return segmentBufferStatus.value.removeBuffer(period.start, period.end, canceller.signal); + /** + * Emits the chosen Adaptation for the current type. + * `null` when no Adaptation is chosen (e.g. no subtitles) + * `undefined` at the beginning (it can be ignored.). + */ + var adaptationRef = (0,reference/* default */.ZP)(undefined, parentCancelSignal); + callbacks.periodStreamReady({ + type: bufferType, + period: period, + adaptationRef: adaptationRef + }); + if (parentCancelSignal.isCancelled()) { + return; + } + var currentStreamCanceller; + var isFirstAdaptationSwitch = true; + adaptationRef.onUpdate(function (adaptation) { + // As an IIFE to profit from async/await while respecting onUpdate's signature + (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee() { + var _a, streamCanceller, segmentBufferStatus, periodEnd, _config$getCurrent, DELTA_POSITION_AFTER_RELOAD, relativePosAfterSwitch, readyState, segmentBuffer, playbackInfos, strategy, _iterator, _step, _step$value, start, end; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (!(adaptation === undefined)) { + _context.next = 2; + break; + } + return _context.abrupt("return"); + case 2: + streamCanceller = new task_canceller/* default */.ZP(); + streamCanceller.linkToSignal(parentCancelSignal); + currentStreamCanceller === null || currentStreamCanceller === void 0 ? void 0 : currentStreamCanceller.cancel(); // Cancel oreviously created stream if one + currentStreamCanceller = streamCanceller; + if (!(adaptation === null)) { + _context.next = 34; + break; + } + // Current type is disabled for that Period + log/* default.info */.Z.info("Stream: Set no " + bufferType + " Adaptation. P:", period.start); + segmentBufferStatus = segmentBuffersStore.getStatus(bufferType); + if (!(segmentBufferStatus.type === "initialized")) { + _context.next = 26; + break; + } + log/* default.info */.Z.info("Stream: Clearing previous " + bufferType + " SegmentBuffer"); + if (!segment_buffers.isNative(bufferType)) { + _context.next = 15; + break; + } + return _context.abrupt("return", askForMediaSourceReload(0, streamCanceller.signal)); + case 15: + periodEnd = (_a = period.end) !== null && _a !== void 0 ? _a : Infinity; + if (!(period.start > periodEnd)) { + _context.next = 20; + break; + } + log/* default.warn */.Z.warn("Stream: Can't free buffer: period's start is after its end"); + _context.next = 24; + break; + case 20: + _context.next = 22; + return segmentBufferStatus.value.removeBuffer(period.start, periodEnd, streamCanceller.signal); + case 22: + if (!streamCanceller.isUsed()) { + _context.next = 24; + break; + } + return _context.abrupt("return"); + case 24: + _context.next = 30; + break; + case 26: + if (!(segmentBufferStatus.type === "uninitialized")) { + _context.next = 30; + break; + } + segmentBuffersStore.disableSegmentBuffer(bufferType); + if (!streamCanceller.isUsed()) { + _context.next = 30; + break; + } + return _context.abrupt("return"); + case 30: + callbacks.adaptationChange({ + type: bufferType, + adaptation: null, + period: period + }); + if (!streamCanceller.isUsed()) { + _context.next = 33; + break; + } + return _context.abrupt("return"); + case 33: + return _context.abrupt("return", createEmptyAdaptationStream(playbackObserver, wantedBufferAhead, bufferType, { + period: period + }, callbacks, streamCanceller.signal)); + case 34: + /** + * If this is not the first Adaptation choice, we might want to apply a + * delta to the current position so we can re-play back some media in the + * new Adaptation to give some context back. + * This value contains this relative position, in seconds. + * @see askForMediaSourceReload + */ + _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), DELTA_POSITION_AFTER_RELOAD = _config$getCurrent.DELTA_POSITION_AFTER_RELOAD; + relativePosAfterSwitch = isFirstAdaptationSwitch ? 0 : bufferType === "audio" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.audio : bufferType === "video" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.video : DELTA_POSITION_AFTER_RELOAD.trackSwitch.other; + isFirstAdaptationSwitch = false; + if (!(segment_buffers.isNative(bufferType) && segmentBuffersStore.getStatus(bufferType).type === "disabled")) { + _context.next = 39; + break; + } + return _context.abrupt("return", askForMediaSourceReload(relativePosAfterSwitch, streamCanceller.signal)); + case 39: + log/* default.info */.Z.info("Stream: Updating " + bufferType + " adaptation", "A: " + adaptation.id, "P: " + period.start); + callbacks.adaptationChange({ + type: bufferType, + adaptation: adaptation, + period: period + }); + if (!streamCanceller.isUsed()) { + _context.next = 43; + break; + } + return _context.abrupt("return"); + case 43: + readyState = playbackObserver.getReadyState(); + segmentBuffer = createOrReuseSegmentBuffer(segmentBuffersStore, bufferType, adaptation, options); + playbackInfos = { + currentTime: playbackObserver.getCurrentTime(), + readyState: readyState + }; + strategy = getAdaptationSwitchStrategy(segmentBuffer, period, adaptation, playbackInfos, options); + if (!(strategy.type === "needs-reload")) { + _context.next = 49; + break; + } + return _context.abrupt("return", askForMediaSourceReload(relativePosAfterSwitch, streamCanceller.signal)); + case 49: + _context.next = 51; + return segmentBuffersStore.waitForUsableBuffers(streamCanceller.signal); + case 51: + if (!streamCanceller.isUsed()) { + _context.next = 53; + break; + } + return _context.abrupt("return"); + case 53: + if (!(strategy.type === "flush-buffer" || strategy.type === "clean-buffer")) { + _context.next = 67; + break; + } + _iterator = period_stream_createForOfIteratorHelperLoose(strategy.value); + case 55: + if ((_step = _iterator()).done) { + _context.next = 63; + break; + } + _step$value = _step.value, start = _step$value.start, end = _step$value.end; + _context.next = 59; + return segmentBuffer.removeBuffer(start, end, streamCanceller.signal); + case 59: + if (!streamCanceller.isUsed()) { + _context.next = 61; + break; + } + return _context.abrupt("return"); + case 61: + _context.next = 55; + break; + case 63: + if (!(strategy.type === "flush-buffer")) { + _context.next = 67; + break; + } + callbacks.needsBufferFlush(); + if (!streamCanceller.isUsed()) { + _context.next = 67; + break; + } + return _context.abrupt("return"); + case 67: + garbageCollectors.get(segmentBuffer)(streamCanceller.signal); + createAdaptationStream(adaptation, segmentBuffer, streamCanceller.signal); + case 69: + case "end": + return _context.stop(); } - }); - } else { - if (segmentBufferStatus.type === "uninitialized") { - segmentBuffersStore.disableSegmentBuffer(bufferType); } - cleanBuffer$ = (0,of.of)(null); + }, _callee); + }))()["catch"](function (err) { + if (err instanceof task_canceller/* CancellationError */.FU) { + return; } - return (0,concat/* concat */.z)(cleanBuffer$.pipe((0,map/* map */.U)(function () { - return stream_events_generators/* default.adaptationChange */.Z.adaptationChange(bufferType, null, period); - })), createEmptyAdaptationStream(playbackObserver, wantedBufferAhead, bufferType, { - period: period - })); - } - if (segment_buffers.isNative(bufferType) && segmentBuffersStore.getStatus(bufferType).type === "disabled") { - return reloadAfterSwitch(period, bufferType, playbackObserver, relativePosAfterSwitch); - } - log/* default.info */.Z.info("Stream: Updating " + bufferType + " adaptation", "A: " + adaptation.id, "P: " + period.start); - var newStream$ = (0,defer/* defer */.P)(function () { - var readyState = playbackObserver.getReadyState(); - var segmentBuffer = createOrReuseSegmentBuffer(segmentBuffersStore, bufferType, adaptation, options); - var playbackInfos = { - currentTime: playbackObserver.getCurrentTime(), - readyState: readyState - }; - var strategy = getAdaptationSwitchStrategy(segmentBuffer, period, adaptation, playbackInfos, options); - if (strategy.type === "needs-reload") { - return reloadAfterSwitch(period, bufferType, playbackObserver, relativePosAfterSwitch); - } - var needsBufferFlush$ = strategy.type === "flush-buffer" ? (0,of.of)(stream_events_generators/* default.needsBufferFlush */.Z.needsBufferFlush()) : empty/* EMPTY */.E; - var cleanBuffer$ = strategy.type === "clean-buffer" || strategy.type === "flush-buffer" ? concat/* concat.apply */.z.apply(void 0, strategy.value.map(function (_ref2) { - var start = _ref2.start, - end = _ref2.end; - var canceller = new task_canceller/* default */.ZP(); - return fromCancellablePromise(canceller, function () { - return segmentBuffer.removeBuffer(start, end, canceller.signal); - }); - })).pipe((0,ignoreElements/* ignoreElements */.l)()) : empty/* EMPTY */.E; - var bufferGarbageCollector$ = garbageCollectors.get(segmentBuffer); - var adaptationStream$ = createAdaptationStream(adaptation, segmentBuffer); - var cancelWait = new task_canceller/* default */.ZP(); - return fromCancellablePromise(cancelWait, function () { - return segmentBuffersStore.waitForUsableBuffers(cancelWait.signal); - }).pipe((0,mergeMap/* mergeMap */.z)(function () { - return (0,concat/* concat */.z)(cleanBuffer$, needsBufferFlush$, (0,merge/* merge */.T)(adaptationStream$, bufferGarbageCollector$)); - })); + currentStreamCanceller === null || currentStreamCanceller === void 0 ? void 0 : currentStreamCanceller.cancel(); + callbacks.error(err); }); - return (0,concat/* concat */.z)((0,of.of)(stream_events_generators/* default.adaptationChange */.Z.adaptationChange(bufferType, adaptation, period)), newStream$); - }), (0,startWith/* startWith */.O)(stream_events_generators/* default.periodStreamReady */.Z.periodStreamReady(bufferType, period, adaptation$))); + }, { + clearSignal: parentCancelSignal, + emitCurrentValue: true + }); /** * @param {Object} adaptation * @param {Object} segmentBuffer - * @returns {Observable} + * @param {Object} cancelSignal */ - function createAdaptationStream(adaptation, segmentBuffer) { + function createAdaptationStream(adaptation, segmentBuffer, cancelSignal) { var manifest = content.manifest; var adaptationPlaybackObserver = createAdaptationStreamPlaybackObserver(playbackObserver, segmentBuffer); - return stream_adaptation({ + stream_adaptation({ content: { manifest: manifest, period: period, @@ -50495,7 +46820,10 @@ function PeriodStream(_ref) { segmentFetcherCreator: segmentFetcherCreator, wantedBufferAhead: wantedBufferAhead, maxVideoBufferSize: maxVideoBufferSize - }).pipe((0,catchError/* catchError */.K)(function (error) { + }, Object.assign(Object.assign({}, callbacks), { + error: onAdaptationStreamError + }), cancelSignal); + function onAdaptationStreamError(error) { // Stream linked to a non-native media buffer should not impact the // stability of the player. ie: if a text buffer sends an error, we want // to continue playing without any subtitles @@ -50506,13 +46834,63 @@ function PeriodStream(_ref) { defaultCode: "NONE", defaultReason: "Unknown `AdaptationStream` error" }); - return (0,concat/* concat */.z)((0,of.of)(stream_events_generators/* default.warning */.Z.warning(formattedError)), createEmptyAdaptationStream(playbackObserver, wantedBufferAhead, bufferType, { + callbacks.warning(formattedError); + if (cancelSignal.isCancelled()) { + return; // Previous callback cancelled the Stream by side-effect + } + + return createEmptyAdaptationStream(playbackObserver, wantedBufferAhead, bufferType, { period: period - })); + }, callbacks, cancelSignal); } log/* default.error */.Z.error("Stream: " + bufferType + " Stream crashed. Stopping playback.", error instanceof Error ? error : ""); - throw error; - })); + callbacks.error(error); + } + } + /** + * Regularly ask to reload the MediaSource on each playback observation + * performed by the playback observer. + * + * If and only if the Period currently played corresponds to the concerned + * Period, applies an offset to the reloaded position corresponding to + * `deltaPos`. + * This can be useful for example when switching the audio/video tracks, where + * you might want to give back some context if that was the currently played + * track. + * + * @param {number} deltaPos - If the concerned Period is playing at the time + * this function is called, we will add this value, in seconds, to the current + * position to indicate the position we should reload at. + * This value allows to give back context (by replaying some media data) after + * a switch. + * @param {Object} cancelSignal + */ + function askForMediaSourceReload(deltaPos, cancelSignal) { + // We begin by scheduling a micro-task to reduce the possibility of race + // conditions where `askForMediaSourceReload` would be called synchronously before + // the next observation (which may reflect very different playback conditions) + // is actually received. + // It can happen when `askForMediaSourceReload` is called as a side-effect of + // the same event that triggers the playback observation to be emitted. + next_tick_default()(function () { + playbackObserver.listen(function (observation) { + var _a, _b; + var currentTime = playbackObserver.getCurrentTime(); + var pos = currentTime + deltaPos; + // Bind to Period start and end + var position = Math.min(Math.max(period.start, pos), (_a = period.end) !== null && _a !== void 0 ? _a : Infinity); + var autoPlay = !((_b = observation.paused.pending) !== null && _b !== void 0 ? _b : playbackObserver.getIsPaused()); + callbacks.waitingMediaSourceReload({ + bufferType: bufferType, + period: period, + position: position, + autoPlay: autoPlay + }); + }, { + includeLastObservation: true, + clearSignal: cancelSignal + }); + }); } } /** @@ -50554,14 +46932,11 @@ function getFirstDeclaredMimeType(adaptation) { */ function createAdaptationStreamPlaybackObserver(initialPlaybackObserver, segmentBuffer) { return initialPlaybackObserver.deriveReadOnlyObserver(function transform(observationRef, cancellationSignal) { - var newRef = (0,reference/* default */.ZP)(constructAdaptationStreamPlaybackObservation()); + var newRef = (0,reference/* default */.ZP)(constructAdaptationStreamPlaybackObservation(), cancellationSignal); observationRef.onUpdate(emitAdaptationStreamPlaybackObservation, { clearSignal: cancellationSignal, emitCurrentValue: false }); - cancellationSignal.register(function () { - newRef.finish(); - }); return newRef; function constructAdaptationStreamPlaybackObservation() { var baseObservation = observationRef.getValue(); @@ -50576,155 +46951,49 @@ function createAdaptationStreamPlaybackObserver(initialPlaybackObserver, segment } }); } -;// CONCATENATED MODULE: ./src/core/stream/period/index.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* harmony default export */ var period = (PeriodStream); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/scan.js + 1 modules -var scan = __webpack_require__(3074); -;// CONCATENATED MODULE: ./src/core/stream/orchestrator/active_period_emitter.ts /** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Create empty AdaptationStream, linked to a Period. + * This AdaptationStream will never download any segment and just emit a "full" + * event when reaching the end. + * @param {Object} playbackObserver + * @param {Object} wantedBufferAhead + * @param {string} bufferType + * @param {Object} content + * @param {Object} callbacks + * @param {Object} cancelSignal */ - -/** - * Emit the active Period each times it changes. - * - * The active Period is the first Period (in chronological order) which has - * a RepresentationStream associated for every defined BUFFER_TYPES. - * - * Emit null if no Period can be considered active currently. - * - * @example - * For 4 BUFFER_TYPES: "AUDIO", "VIDEO", "TEXT" and "IMAGE": - * ``` - * +-------------+ - * Period 1 | Period 2 | Period 3 - * AUDIO |=========| | |=== | | - * VIDEO | |===== | | - * TEXT |(NO TEXT)| | |(NO TEXT)| | |==== | - * IMAGE |=========| | |= | | - * +-------------+ - * - * The active Period here is Period 2 as Period 1 has no video - * RepresentationStream. - * - * If we are missing a or multiple PeriodStreams in the first chronological - * Period, like that is the case here, it generally means that we are - * currently switching between Periods. - * - * For here we are surely switching from Period 1 to Period 2 beginning by the - * video PeriodStream. As every PeriodStream is ready for Period 2, we can - * already inform that it is the current Period. - * ``` - * - * @param {Array.} buffers$ - * @returns {Observable} - */ -function ActivePeriodEmitter(buffers$) { - var numberOfStreams = buffers$.length; - return merge/* merge.apply */.T.apply(void 0, buffers$).pipe( - // not needed to filter, this is an optim - (0,filter/* filter */.h)(function (_ref) { - var type = _ref.type; - return type === "periodStreamCleared" || type === "adaptationChange" || type === "representationChange"; - }), (0,scan/* scan */.R)(function (acc, evt) { - switch (evt.type) { - case "periodStreamCleared": - { - var _evt$value = evt.value, - period = _evt$value.period, - type = _evt$value.type; - var currentInfos = acc[period.id]; - if (currentInfos !== undefined && currentInfos.buffers.has(type)) { - currentInfos.buffers["delete"](type); - if (currentInfos.buffers.size === 0) { - delete acc[period.id]; - } - } - } - break; - case "adaptationChange": - { - // For Adaptations that are not null, we will receive a - // `representationChange` event. We can thus skip this event and only - // listen to the latter. - if (evt.value.adaptation !== null) { - return acc; - } - } - // /!\ fallthrough done on purpose - // Note that we fall-through only when the Adaptation sent through the - // `adaptationChange` event is `null`. This is because in those cases, - // we won't receive any "representationChange" event. We however still - // need to register that Period as active for the current type. - // eslint-disable-next-line no-fallthrough - case "representationChange": - { - var _evt$value2 = evt.value, - _period = _evt$value2.period, - _type = _evt$value2.type; - var _currentInfos = acc[_period.id]; - if (_currentInfos === undefined) { - var bufferSet = new Set(); - bufferSet.add(_type); - acc[_period.id] = { - period: _period, - buffers: bufferSet - }; - } else if (!_currentInfos.buffers.has(_type)) { - _currentInfos.buffers.add(_type); - } - } - break; - } - return acc; - }, {}), (0,map/* map */.U)(function (list) { - var activePeriodIDs = Object.keys(list); - var completePeriods = []; - for (var i = 0; i < activePeriodIDs.length; i++) { - var periodInfos = list[activePeriodIDs[i]]; - if (periodInfos !== undefined && periodInfos.buffers.size === numberOfStreams) { - completePeriods.push(periodInfos.period); - } +function createEmptyAdaptationStream(playbackObserver, wantedBufferAhead, bufferType, content, callbacks, cancelSignal) { + var period = content.period; + var hasFinishedLoading = false; + wantedBufferAhead.onUpdate(sendStatus, { + emitCurrentValue: false, + clearSignal: cancelSignal + }); + playbackObserver.listen(sendStatus, { + includeLastObservation: false, + clearSignal: cancelSignal + }); + sendStatus(); + function sendStatus() { + var observation = playbackObserver.getReference().getValue(); + var wba = wantedBufferAhead.getValue(); + var position = observation.position.last; + if (period.end !== undefined && position + wba >= period.end) { + log/* default.debug */.Z.debug("Stream: full \"empty\" AdaptationStream", bufferType); + hasFinishedLoading = true; } - return completePeriods.reduce(function (acc, period) { - if (acc === null) { - return period; - } - return period.start < acc.start ? period : acc; - }, null); - }), distinctUntilChanged(function (a, b) { - return a === null && b === null || a !== null && b !== null && a.id === b.id; - })); + callbacks.streamStatusUpdate({ + period: period, + bufferType: bufferType, + position: position, + imminentDiscontinuity: null, + isEmptyStream: true, + hasFinishedLoading: hasFinishedLoading, + neededSegments: [] + }); + } } -;// CONCATENATED MODULE: ./src/core/stream/orchestrator/are_streams_complete.ts +;// CONCATENATED MODULE: ./src/core/stream/period/index.ts /** * Copyright 2015 CANAL+ Group * @@ -50741,46 +47010,8 @@ function ActivePeriodEmitter(buffers$) { * limitations under the License. */ -/** - * Returns an Observable which emits ``true`` when all PeriodStreams given are - * _complete_. - * Returns false otherwise. - * - * A PeriodStream for a given type is considered _complete_ when both of these - * conditions are true: - * - it is the last PeriodStream in the content for the given type - * - it has finished downloading segments (it is _full_) - * - * Simply put a _complete_ PeriodStream for a given type means that every - * segments needed for this Stream have been downloaded. - * - * When the Observable returned here emits, every Stream are finished. - * @param {...Observable} streams - * @returns {Observable} - */ -function areStreamsComplete() { - for (var _len = arguments.length, streams = new Array(_len), _key = 0; _key < _len; _key++) { - streams[_key] = arguments[_key]; - } - /** - * Array of Observables linked to the Array of Streams which emit: - * - true when the corresponding Stream is considered _complete_. - * - false when the corresponding Stream is considered _active_. - * @type {Array.} - */ - var isCompleteArray = streams.map(function (stream) { - return stream.pipe((0,filter/* filter */.h)(function (evt) { - return evt.type === "complete-stream" || evt.type === "stream-status" && !evt.value.hasFinishedLoading; - }), (0,map/* map */.U)(function (evt) { - return evt.type === "complete-stream"; - }), (0,startWith/* startWith */.O)(false), distinctUntilChanged()); - }); - return combineLatest(isCompleteArray).pipe((0,map/* map */.U)(function (areComplete) { - return areComplete.every(function (isComplete) { - return isComplete; - }); - }), distinctUntilChanged()); -} + +/* harmony default export */ var period = (PeriodStream); ;// CONCATENATED MODULE: ./src/core/stream/orchestrator/get_time_ranges_for_content.ts /** * Copyright 2015 CANAL+ Group @@ -50854,6 +47085,11 @@ function getTimeRangesForContent(segmentBuffer, contents) { return accumulator; } ;// CONCATENATED MODULE: ./src/core/stream/orchestrator/stream_orchestrator.ts + + +function stream_orchestrator_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = stream_orchestrator_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function stream_orchestrator_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return stream_orchestrator_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return stream_orchestrator_arrayLikeToArray(o, minLen); } +function stream_orchestrator_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -50880,16 +47116,8 @@ function getTimeRangesForContent(segmentBuffer, contents) { - - - - - - - - /** - * Create and manage the various Stream Observables needed for the content to + * Create and manage the various "Streams" needed for the content to * play: * * - Create or dispose SegmentBuffers depending on the chosen Adaptations. @@ -50901,19 +47129,36 @@ function getTimeRangesForContent(segmentBuffer, contents) { * - Concatenate Streams for adaptation from separate Periods at the right * time, to allow smooth transitions between periods. * - * - Emit various events to notify of its health and issues + * - Call various callbacks to notify of its health and issues * * @param {Object} content - * @param {Observable} playbackObserver - Emit position information + * @param {Object} playbackObserver - Emit position information * @param {Object} representationEstimator - Emit bitrate estimates and best * Representation to play. * @param {Object} segmentBuffersStore - Will be used to lazily create * SegmentBuffer instances associated with the current content. * @param {Object} segmentFetcherCreator - Allow to download segments. * @param {Object} options - * @returns {Observable} - */ -function StreamOrchestrator(content, playbackObserver, representationEstimator, segmentBuffersStore, segmentFetcherCreator, options) { + * @param {Object} callbacks - The `StreamOrchestrator` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. + * + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `StreamOrchestrator` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `StreamOrchestrator` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, or until the `error` callback is called, whichever comes first. + * @param {Object} orchestratorCancelSignal - `CancellationSignal` allowing, + * when triggered, to immediately stop all operations the `PeriodStream` is + * doing. + */ +function StreamOrchestrator(content, playbackObserver, representationEstimator, segmentBuffersStore, segmentFetcherCreator, options, callbacks, orchestratorCancelSignal) { var manifest = content.manifest, initialPeriod = content.initialPeriod; var maxBufferAhead = options.maxBufferAhead, @@ -50929,47 +47174,24 @@ function StreamOrchestrator(content, playbackObserver, representationEstimator, var bufferType = segmentBuffer.bufferType; var defaultMaxBehind = MAXIMUM_MAX_BUFFER_BEHIND[bufferType] != null ? MAXIMUM_MAX_BUFFER_BEHIND[bufferType] : Infinity; var defaultMaxAhead = MAXIMUM_MAX_BUFFER_AHEAD[bufferType] != null ? MAXIMUM_MAX_BUFFER_AHEAD[bufferType] : Infinity; - return new Observable/* Observable */.y(function () { - var canceller = new task_canceller/* default */.ZP(); + return function (gcCancelSignal) { BufferGarbageCollector({ segmentBuffer: segmentBuffer, playbackObserver: playbackObserver, maxBufferBehind: (0,reference/* createMappedReference */.lR)(maxBufferBehind, function (val) { return Math.min(val, defaultMaxBehind); - }, canceller.signal), + }, gcCancelSignal), maxBufferAhead: (0,reference/* createMappedReference */.lR)(maxBufferAhead, function (val) { return Math.min(val, defaultMaxAhead); - }, canceller.signal) - }, canceller.signal); - return function () { - canceller.cancel(); - }; - }); - }); - // Every PeriodStreams for every possible types - var streamsArray = segmentBuffersStore.getBufferTypes().map(function (bufferType) { - return manageEveryStreams(bufferType, initialPeriod).pipe((0,defer_subscriptions/* default */.Z)(), (0,share/* share */.B)()); + }, gcCancelSignal) + }, gcCancelSignal); + }; }); - // Emits the activePeriodChanged events every time the active Period changes. - var activePeriodChanged$ = ActivePeriodEmitter(streamsArray).pipe((0,filter/* filter */.h)(function (period) { - return period !== null; - }), (0,map/* map */.U)(function (period) { - log/* default.info */.Z.info("Stream: New active period", period.start); - return stream_events_generators/* default.activePeriodChanged */.Z.activePeriodChanged(period); - })); - var isLastPeriodKnown$ = (0,event_emitter/* fromEvent */.R)(manifest, "manifestUpdate").pipe((0,map/* map */.U)(function () { - return manifest.isLastPeriodKnown; - }), (0,startWith/* startWith */.O)(manifest.isLastPeriodKnown), distinctUntilChanged()); - // Emits an "end-of-stream" event once every PeriodStream are complete. - // Emits a 'resume-stream" when it's not - var endOfStream$ = combineLatest([areStreamsComplete.apply(void 0, streamsArray), isLastPeriodKnown$]).pipe((0,map/* map */.U)(function (_ref) { - var areComplete = _ref[0], - isLastPeriodKnown = _ref[1]; - return areComplete && isLastPeriodKnown; - }), distinctUntilChanged(), (0,map/* map */.U)(function (emitEndOfStream) { - return emitEndOfStream ? stream_events_generators/* default.endOfStream */.Z.endOfStream() : stream_events_generators/* default.resumeStream */.Z.resumeStream(); - })); - return merge/* merge.apply */.T.apply(void 0, streamsArray.concat([activePeriodChanged$, endOfStream$])); + // Create automatically the right `PeriodStream` for every possible types + for (var _iterator = stream_orchestrator_createForOfIteratorHelperLoose(segmentBuffersStore.getBufferTypes()), _step; !(_step = _iterator()).done;) { + var bufferType = _step.value; + manageEveryStreams(bufferType, initialPeriod); + } /** * Manage creation and removal of Streams for every Periods for a given type. * @@ -50978,49 +47200,100 @@ function StreamOrchestrator(content, playbackObserver, representationEstimator, * current position goes out of the bounds of these Streams. * @param {string} bufferType - e.g. "audio" or "video" * @param {Period} basePeriod - Initial Period downloaded. - * @returns {Observable} */ function manageEveryStreams(bufferType, basePeriod) { - // Each Period for which there is currently a Stream, chronologically + /** Each Period for which there is currently a Stream, chronologically */ var periodList = new SortedList(function (a, b) { return a.start - b.start; }); - var destroyStreams$ = new Subject/* Subject */.x(); - // When set to `true`, all the currently active PeriodStream will be destroyed - // and re-created from the new current position if we detect it to be out of - // their bounds. - // This is set to false when we're in the process of creating the first - // PeriodStream, to avoid interferences while no PeriodStream is available. + /** + * When set to `true`, all the currently active PeriodStream will be destroyed + * and re-created from the new current position if we detect it to be out of + * their bounds. + * This is set to false when we're in the process of creating the first + * PeriodStream, to avoid interferences while no PeriodStream is available. + */ var enableOutOfBoundsCheck = false; + /** Cancels currently created `PeriodStream`s. */ + var currentCanceller = new task_canceller/* default */.ZP(); + currentCanceller.linkToSignal(orchestratorCancelSignal); + // Restart the current Stream when the wanted time is in another period + // than the ones already considered + playbackObserver.listen(function (_ref) { + var position = _ref.position; + var _a, _b; + var time = (_a = position.pending) !== null && _a !== void 0 ? _a : position.last; + if (!enableOutOfBoundsCheck || !isOutOfPeriodList(time)) { + return; + } + log/* default.info */.Z.info("Stream: Destroying all PeriodStreams due to out of bounds situation", bufferType, time); + enableOutOfBoundsCheck = false; + while (periodList.length() > 0) { + var period = periodList.get(periodList.length() - 1); + periodList.removeElement(period); + callbacks.periodStreamCleared({ + type: bufferType, + period: period + }); + } + currentCanceller.cancel(); + currentCanceller = new task_canceller/* default */.ZP(); + currentCanceller.linkToSignal(orchestratorCancelSignal); + var nextPeriod = (_b = manifest.getPeriodForTime(time)) !== null && _b !== void 0 ? _b : manifest.getNextPeriod(time); + if (nextPeriod === undefined) { + log/* default.warn */.Z.warn("Stream: The wanted position is not found in the Manifest."); + return; + } + launchConsecutiveStreamsForPeriod(nextPeriod); + }, { + clearSignal: orchestratorCancelSignal, + includeLastObservation: true + }); + manifest.addEventListener("decipherabilityUpdate", function (evt) { + onDecipherabilityUpdates(evt)["catch"](function (err) { + currentCanceller.cancel(); + callbacks.error(err); + }); + }, orchestratorCancelSignal); + return launchConsecutiveStreamsForPeriod(basePeriod); /** * @param {Object} period - * @returns {Observable} */ function launchConsecutiveStreamsForPeriod(period) { - return manageConsecutivePeriodStreams(bufferType, period, destroyStreams$).pipe((0,map/* map */.U)(function (message) { - switch (message.type) { - case "waiting-media-source-reload": - // Only reload the MediaSource when the more immediately required - // Period is the one asking for it - var firstPeriod = periodList.head(); - if (firstPeriod === undefined || firstPeriod.id !== message.value.period.id) { - return stream_events_generators/* default.lockedStream */.Z.lockedStream(message.value.bufferType, message.value.period); - } else { - var _message$value = message.value, - position = _message$value.position, - autoPlay = _message$value.autoPlay; - return stream_events_generators/* default.needsMediaSourceReload */.Z.needsMediaSourceReload(position, autoPlay); - } - case "periodStreamReady": - enableOutOfBoundsCheck = true; - periodList.add(message.value.period); - break; - case "periodStreamCleared": - periodList.removeElement(message.value.period); - break; + var consecutivePeriodStreamCb = Object.assign(Object.assign({}, callbacks), { + waitingMediaSourceReload: function waitingMediaSourceReload(payload) { + // Only reload the MediaSource when the more immediately required + // Period is the one asking for it + var firstPeriod = periodList.head(); + if (firstPeriod === undefined || firstPeriod.id !== payload.period.id) { + callbacks.lockedStream({ + bufferType: payload.bufferType, + period: payload.period + }); + } else { + var position = payload.position, + autoPlay = payload.autoPlay; + callbacks.needsMediaSourceReload({ + position: position, + autoPlay: autoPlay + }); + } + }, + periodStreamReady: function periodStreamReady(payload) { + enableOutOfBoundsCheck = true; + periodList.add(payload.period); + callbacks.periodStreamReady(payload); + }, + periodStreamCleared: function periodStreamCleared(payload) { + periodList.removeElement(payload.period); + callbacks.periodStreamCleared(payload); + }, + error: function error(err) { + currentCanceller.cancel(); + callbacks.error(err); } - return message; - }), (0,share/* share */.B)()); + }); + manageConsecutivePeriodStreams(bufferType, period, consecutivePeriodStreamCb, currentCanceller.signal); } /** * Returns true if the given time is either: @@ -51038,162 +47311,194 @@ function StreamOrchestrator(content, playbackObserver, representationEstimator, } return head.start > time || (last.end == null ? Infinity : last.end) < time; } - // Restart the current Stream when the wanted time is in another period - // than the ones already considered - var observation$ = playbackObserver.getReference().asObservable(); - var restartStreamsWhenOutOfBounds$ = observation$.pipe((0,filter_map/* default */.Z)(function (_ref2) { - var position = _ref2.position; - var _a, _b; - var time = (_a = position.pending) !== null && _a !== void 0 ? _a : position.last; - if (!enableOutOfBoundsCheck || !isOutOfPeriodList(time)) { - return null; - } - var nextPeriod = (_b = manifest.getPeriodForTime(time)) !== null && _b !== void 0 ? _b : manifest.getNextPeriod(time); - if (nextPeriod === undefined) { - return null; - } - log/* default.info */.Z.info("SO: Current position out of the bounds of the active periods," + "re-creating Streams.", bufferType, time); - enableOutOfBoundsCheck = false; - destroyStreams$.next(); - return nextPeriod; - }, null), (0,mergeMap/* mergeMap */.z)(function (newInitialPeriod) { - if (newInitialPeriod == null) { - throw new media_error/* default */.Z("MEDIA_TIME_NOT_FOUND", "The wanted position is not found in the Manifest."); - } - return launchConsecutiveStreamsForPeriod(newInitialPeriod); - })); - var handleDecipherabilityUpdate$ = (0,event_emitter/* fromEvent */.R)(manifest, "decipherabilityUpdate").pipe((0,mergeMap/* mergeMap */.z)(onDecipherabilityUpdates)); - return (0,merge/* merge */.T)(restartStreamsWhenOutOfBounds$, handleDecipherabilityUpdate$, launchConsecutiveStreamsForPeriod(basePeriod)); - function onDecipherabilityUpdates(updates) { - var segmentBufferStatus = segmentBuffersStore.getStatus(bufferType); - var ofCurrentType = updates.filter(function (update) { - return update.adaptation.type === bufferType; - }); - if (ofCurrentType.length === 0 || segmentBufferStatus.type !== "initialized") { - return empty/* EMPTY */.E; // no need to stop the current Streams. - } - - var segmentBuffer = segmentBufferStatus.value; - var resettedContent = ofCurrentType.filter(function (update) { - return update.representation.decipherable === undefined; - }); - var undecipherableContent = ofCurrentType.filter(function (update) { - return update.representation.decipherable === false; - }); - /** - * Time ranges now containing undecipherable content. - * Those should first be removed and, depending on the platform, may - * need Supplementary actions as playback issues may remain even after - * removal. - */ - var undecipherableRanges = getTimeRangesForContent(segmentBuffer, undecipherableContent); - /** - * Time ranges now containing content whose decipherability status came - * back to being unknown. - * To simplify its handling, those are just removed from the buffer. - * Less considerations have to be taken than for the `undecipherableRanges`. - */ - var rangesToRemove = getTimeRangesForContent(segmentBuffer, resettedContent); - // First close all Stream currently active so they don't continue to - // load and push segments. - enableOutOfBoundsCheck = false; - destroyStreams$.next(); - /** Remove from the `SegmentBuffer` all the concerned time ranges. */ - var cleanOperations = [].concat(undecipherableRanges, rangesToRemove).map(function (_ref3) { - var start = _ref3.start, - end = _ref3.end; - if (start >= end) { - return empty/* EMPTY */.E; - } - var canceller = new task_canceller/* default */.ZP(); - return fromCancellablePromise(canceller, function () { - return segmentBuffer.removeBuffer(start, end, canceller.signal); - }).pipe((0,ignoreElements/* ignoreElements */.l)()); - }); - return concat/* concat.apply */.z.apply(void 0, cleanOperations.concat([ - // Schedule micro task before checking the last playback observation - // to reduce the risk of race conditions where the next observation - // was going to be emitted synchronously. - nextTickObs().pipe((0,ignoreElements/* ignoreElements */.l)()), playbackObserver.getReference().asObservable().pipe((0,take/* take */.q)(1), (0,mergeMap/* mergeMap */.z)(function (observation) { - var _a; - var restartStream$ = (0,defer/* defer */.P)(function () { - var _a; - var lastPosition = (_a = observation.position.pending) !== null && _a !== void 0 ? _a : observation.position.last; - var newInitialPeriod = manifest.getPeriodForTime(lastPosition); - if (newInitialPeriod == null) { - throw new media_error/* default */.Z("MEDIA_TIME_NOT_FOUND", "The wanted position is not found in the Manifest."); + /** + * React to a Manifest's decipherability updates. + * @param {Array.} + * @returns {Promise} + */ + function onDecipherabilityUpdates(_x) { + return _onDecipherabilityUpdates.apply(this, arguments); + } + function _onDecipherabilityUpdates() { + _onDecipherabilityUpdates = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(updates) { + var segmentBufferStatus, ofCurrentType, segmentBuffer, resettedContent, undecipherableContent, undecipherableRanges, rangesToRemove, period, _i, _arr, _arr$_i, start, end; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + segmentBufferStatus = segmentBuffersStore.getStatus(bufferType); + ofCurrentType = updates.filter(function (update) { + return update.adaptation.type === bufferType; + }); + if (!( + // No update concerns the current type of data + ofCurrentType.length === 0 || segmentBufferStatus.type !== "initialized" || + // The update only notifies of now-decipherable streams + ofCurrentType.every(function (x) { + return x.representation.decipherable === true; + }))) { + _context.next = 4; + break; + } + return _context.abrupt("return"); + case 4: + segmentBuffer = segmentBufferStatus.value; + resettedContent = ofCurrentType.filter(function (update) { + return update.representation.decipherable === undefined; + }); + undecipherableContent = ofCurrentType.filter(function (update) { + return update.representation.decipherable === false; + }); + /** + * Time ranges now containing undecipherable content. + * Those should first be removed and, depending on the platform, may + * need Supplementary actions as playback issues may remain even after + * removal. + */ + undecipherableRanges = getTimeRangesForContent(segmentBuffer, undecipherableContent); + /** + * Time ranges now containing content whose decipherability status came + * back to being unknown. + * To simplify its handling, those are just removed from the buffer. + * Less considerations have to be taken than for the `undecipherableRanges`. + */ + rangesToRemove = getTimeRangesForContent(segmentBuffer, resettedContent); // First close all Stream currently active so they don't continue to + // load and push segments. + enableOutOfBoundsCheck = false; + log/* default.info */.Z.info("Stream: Destroying all PeriodStreams for decipherability matters", bufferType); + while (periodList.length() > 0) { + period = periodList.get(periodList.length() - 1); + periodList.removeElement(period); + callbacks.periodStreamCleared({ + type: bufferType, + period: period + }); + } + currentCanceller.cancel(); + currentCanceller = new task_canceller/* default */.ZP(); + currentCanceller.linkToSignal(orchestratorCancelSignal); + /** Remove from the `SegmentBuffer` all the concerned time ranges. */ + _i = 0, _arr = [].concat(undecipherableRanges, rangesToRemove); + case 16: + if (!(_i < _arr.length)) { + _context.next = 24; + break; + } + _arr$_i = _arr[_i], start = _arr$_i.start, end = _arr$_i.end; + if (!(start < end)) { + _context.next = 21; + break; + } + _context.next = 21; + return segmentBuffer.removeBuffer(start, end, orchestratorCancelSignal); + case 21: + _i++; + _context.next = 16; + break; + case 24: + // Schedule micro task before checking the last playback observation + // to reduce the risk of race conditions where the next observation + // was going to be emitted synchronously. + next_tick_default()(function () { + var _a, _b; + if (orchestratorCancelSignal.isCancelled()) { + return; + } + var observation = playbackObserver.getReference().getValue(); + if (needsFlushingAfterClean(observation, undecipherableRanges)) { + var shouldAutoPlay = !((_a = observation.paused.pending) !== null && _a !== void 0 ? _a : playbackObserver.getIsPaused()); + callbacks.needsDecipherabilityFlush({ + position: observation.position.last, + autoPlay: shouldAutoPlay, + duration: observation.duration + }); + if (orchestratorCancelSignal.isCancelled()) { + return; + } + } else if (needsFlushingAfterClean(observation, rangesToRemove)) { + callbacks.needsBufferFlush(); + if (orchestratorCancelSignal.isCancelled()) { + return; + } + } + var lastPosition = (_b = observation.position.pending) !== null && _b !== void 0 ? _b : observation.position.last; + var newInitialPeriod = manifest.getPeriodForTime(lastPosition); + if (newInitialPeriod == null) { + callbacks.error(new media_error/* default */.Z("MEDIA_TIME_NOT_FOUND", "The wanted position is not found in the Manifest.")); + return; + } + launchConsecutiveStreamsForPeriod(newInitialPeriod); + }); + case 25: + case "end": + return _context.stop(); + } } - return launchConsecutiveStreamsForPeriod(newInitialPeriod); - }); - if (needsFlushingAfterClean(observation, undecipherableRanges)) { - var shouldAutoPlay = !((_a = observation.paused.pending) !== null && _a !== void 0 ? _a : playbackObserver.getIsPaused()); - return (0,concat/* concat */.z)((0,of.of)(stream_events_generators/* default.needsDecipherabilityFlush */.Z.needsDecipherabilityFlush(observation.position.last, shouldAutoPlay, observation.duration)), restartStream$); - } else if (needsFlushingAfterClean(observation, rangesToRemove)) { - return (0,concat/* concat */.z)((0,of.of)(stream_events_generators/* default.needsBufferFlush */.Z.needsBufferFlush()), restartStream$); - } - return restartStream$; - }))])); + }, _callee); + })); + return _onDecipherabilityUpdates.apply(this, arguments); } } /** * Create lazily consecutive PeriodStreams: * - * It first creates the PeriodStream for `basePeriod` and - once it becomes + * It first creates the `PeriodStream` for `basePeriod` and - once it becomes * full - automatically creates the next chronological one. - * This process repeats until the PeriodStream linked to the last Period is + * This process repeats until the `PeriodStream` linked to the last Period is * full. * - * If an "old" PeriodStream becomes active again, it destroys all PeriodStream - * coming after it (from the last chronological one to the first). + * If an "old" `PeriodStream` becomes active again, it destroys all + * `PeriodStream` coming after it (from the last chronological one to the + * first). * * To clean-up PeriodStreams, each one of them are also automatically * destroyed once the current position is superior or equal to the end of * the concerned Period. * - * A "periodStreamReady" event is sent each times a new PeriodStream is - * created. The first one (for `basePeriod`) should be sent synchronously on - * subscription. + * The "periodStreamReady" callback is alled each times a new `PeriodStream` + * is created. * - * A "periodStreamCleared" event is sent each times a PeriodStream is - * destroyed. + * The "periodStreamCleared" callback is called each times a PeriodStream is + * destroyed (this callback is though not called if it was destroyed due to + * the given `cancelSignal` emitting or due to a fatal error). * @param {string} bufferType - e.g. "audio" or "video" * @param {Period} basePeriod - Initial Period downloaded. - * @param {Observable} destroy$ - Emit when/if all created Streams from this - * point should be destroyed. - * @returns {Observable} - */ - function manageConsecutivePeriodStreams(bufferType, basePeriod, destroy$) { - log/* default.info */.Z.info("SO: Creating new Stream for", bufferType, basePeriod.start); - // Emits the Period of the next Period Stream when it can be created. - var createNextPeriodStream$ = new Subject/* Subject */.x(); - // Emits when the Streams for the next Periods should be destroyed, if - // created. - var destroyNextStreams$ = new Subject/* Subject */.x(); - // Emits when the current position goes over the end of the current Stream. - var endOfCurrentStream$ = playbackObserver.getReference().asObservable().pipe((0,filter/* filter */.h)(function (_ref4) { - var position = _ref4.position; - var _a; - return basePeriod.end != null && ((_a = position.pending) !== null && _a !== void 0 ? _a : position.last) >= basePeriod.end; - })); - // Create Period Stream for the next Period. - var nextPeriodStream$ = createNextPeriodStream$.pipe(exhaustMap(function (nextPeriod) { - return manageConsecutivePeriodStreams(bufferType, nextPeriod, destroyNextStreams$); - })); - // Allows to destroy each created Stream, from the newest to the oldest, - // once destroy$ emits. - var destroyAll$ = destroy$.pipe((0,take/* take */.q)(1), (0,tap/* tap */.b)(function () { - // first complete createNextStream$ to allow completion of the - // nextPeriodStream$ observable once every further Streams have been - // cleared. - createNextPeriodStream$.complete(); - // emit destruction signal to the next Stream first - destroyNextStreams$.next(); - destroyNextStreams$.complete(); // we do not need it anymore - }), (0,share/* share */.B)() // share side-effects - ); - // Will emit when the current Stream should be destroyed. - var killCurrentStream$ = (0,merge/* merge */.T)(endOfCurrentStream$, destroyAll$); - var periodStream$ = period({ + * @param {Object} consecutivePeriodStreamCb - Callbacks called on various + * events. See type for more information. + * @param {Object} cancelSignal - `CancellationSignal` allowing to stop + * everything that this function was doing. Callbacks in + * `consecutivePeriodStreamCb` might still be sent as a consequence of this + * signal emitting. + */ + function manageConsecutivePeriodStreams(bufferType, basePeriod, consecutivePeriodStreamCb, cancelSignal) { + log/* default.info */.Z.info("Stream: Creating new Stream for", bufferType, basePeriod.start); + /** + * Contains properties linnked to the next chronological `PeriodStream` that + * may be created here. + */ + var nextStreamInfo = null; + /** Emits when the `PeriodStream` linked to `basePeriod` should be destroyed. */ + var currentStreamCanceller = new task_canceller/* default */.ZP(); + currentStreamCanceller.linkToSignal(cancelSignal); + // Stop current PeriodStream when the current position goes over the end of + // that Period. + playbackObserver.listen(function (_ref2, stopListeningObservations) { + var position = _ref2.position; + var _a, _b; + if (basePeriod.end !== undefined && ((_a = position.pending) !== null && _a !== void 0 ? _a : position.last) >= basePeriod.end) { + log/* default.info */.Z.info("Stream: Destroying PeriodStream as the current playhead moved above it", bufferType, basePeriod.start, (_b = position.pending) !== null && _b !== void 0 ? _b : position.last, basePeriod.end); + stopListeningObservations(); + consecutivePeriodStreamCb.periodStreamCleared({ + type: bufferType, + period: basePeriod + }); + currentStreamCanceller.cancel(); + } + }, { + clearSignal: cancelSignal, + includeLastObservation: true + }); + var periodStreamArgs = { bufferType: bufferType, content: { manifest: manifest, @@ -51207,27 +47512,58 @@ function StreamOrchestrator(content, playbackObserver, representationEstimator, playbackObserver: playbackObserver, representationEstimator: representationEstimator, wantedBufferAhead: wantedBufferAhead - }).pipe((0,mergeMap/* mergeMap */.z)(function (evt) { - if (evt.type === "stream-status") { - if (evt.value.hasFinishedLoading) { + }; + var periodStreamCallbacks = Object.assign(Object.assign({}, consecutivePeriodStreamCb), { + streamStatusUpdate: function streamStatusUpdate(value) { + if (value.hasFinishedLoading) { var nextPeriod = manifest.getPeriodAfter(basePeriod); - if (nextPeriod === null) { - return (0,concat/* concat */.z)((0,of.of)(evt), (0,of.of)(stream_events_generators/* default.streamComplete */.Z.streamComplete(bufferType))); + if (nextPeriod !== null) { + // current Stream is full, create the next one if not + createNextPeriodStream(nextPeriod); } - // current Stream is full, create the next one if not - createNextPeriodStream$.next(nextPeriod); - } else { + } else if (nextStreamInfo !== null) { // current Stream is active, destroy next Stream if created - destroyNextStreams$.next(); + log/* default.info */.Z.info("Stream: Destroying next PeriodStream due to current one being active", bufferType, nextStreamInfo.period.start); + consecutivePeriodStreamCb.periodStreamCleared({ + type: bufferType, + period: nextStreamInfo.period + }); + nextStreamInfo.canceller.cancel(); + nextStreamInfo = null; } + consecutivePeriodStreamCb.streamStatusUpdate(value); + }, + error: function error(err) { + if (nextStreamInfo !== null) { + nextStreamInfo.canceller.cancel(); + nextStreamInfo = null; + } + currentStreamCanceller.cancel(); + consecutivePeriodStreamCb.error(err); + } + }); + period(periodStreamArgs, periodStreamCallbacks, currentStreamCanceller.signal); + /** + * Create `PeriodStream` for the next Period, specified under `nextPeriod`. + * @param {Object} nextPeriod + */ + function createNextPeriodStream(nextPeriod) { + if (nextStreamInfo !== null) { + log/* default.warn */.Z.warn("Stream: Creating next `PeriodStream` while it was already created."); + consecutivePeriodStreamCb.periodStreamCleared({ + type: bufferType, + period: nextStreamInfo.period + }); + nextStreamInfo.canceller.cancel(); } - return (0,of.of)(evt); - }), (0,share/* share */.B)()); - // Stream for the current Period. - var currentStream$ = (0,concat/* concat */.z)(periodStream$.pipe((0,takeUntil/* takeUntil */.R)(killCurrentStream$)), (0,of.of)(stream_events_generators/* default.periodStreamCleared */.Z.periodStreamCleared(bufferType, basePeriod)).pipe((0,tap/* tap */.b)(function () { - log/* default.info */.Z.info("SO: Destroying Stream for", bufferType, basePeriod.start); - }))); - return (0,merge/* merge */.T)(currentStream$, nextPeriodStream$, destroyAll$.pipe((0,ignoreElements/* ignoreElements */.l)())); + var nextStreamCanceller = new task_canceller/* default */.ZP(); + nextStreamCanceller.linkToSignal(cancelSignal); + nextStreamInfo = { + canceller: nextStreamCanceller, + period: nextPeriod + }; + manageConsecutivePeriodStreams(bufferType, nextPeriod, consecutivePeriodStreamCb, nextStreamInfo.canceller.signal); + } } } /** @@ -51284,9 +47620,14 @@ function needsFlushingAfterClean(observation, cleanedRanges) { * limitations under the License. */ - /* harmony default export */ var stream = (orchestrator); -;// CONCATENATED MODULE: ./src/core/init/content_time_boundaries_observer.ts +// EXTERNAL MODULE: ./src/core/init/types.ts +var init_types = __webpack_require__(379); +;// CONCATENATED MODULE: ./src/core/init/utils/content_time_boundaries_observer.ts + +function content_time_boundaries_observer_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = content_time_boundaries_observer_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function content_time_boundaries_observer_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return content_time_boundaries_observer_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return content_time_boundaries_observer_arrayLikeToArray(o, minLen); } +function content_time_boundaries_observer_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -51307,88 +47648,267 @@ function needsFlushingAfterClean(observation, cleanedRanges) { - - -// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default -// first type parameter as `any` instead of the perfectly fine `unknown`, -// leading to linter issues, as it forbids the usage of `any`. -// This is why we're disabling the eslint rule. -/* eslint-disable @typescript-eslint/no-unsafe-argument */ /** - * Observes the position and Adaptations being played and deduce various events - * related to the available time boundaries: - * - Emit when the theoretical duration of the content becomes known or when it - * changes. - * - Emit warnings when the duration goes out of what is currently - * theoretically playable. - * - * @param {Object} manifest - * @param {Object} lastAdaptationChange - * @param {Object} playbackObserver - * @returns {Observable} + * Observes what's being played and take care of media events relating to time + * boundaries: + * - Emits a `durationUpdate` when the duration of the current content is + * known and every time it changes. + * - Emits `endOfStream` API once segments have been pushed until the end and + * `resumeStream` if downloads starts back. + * - Emits a `periodChange` event when the currently-playing Period seemed to + * have changed. + * - emit "warning" events when what is being played is outside of the + * Manifest range. + * @class ContentTimeBoundariesObserver */ -function ContentTimeBoundariesObserver(manifest, lastAdaptationChange, playbackObserver) { +var ContentTimeBoundariesObserver = /*#__PURE__*/function (_EventEmitter) { + (0,inheritsLoose/* default */.Z)(ContentTimeBoundariesObserver, _EventEmitter); /** - * Allows to calculate the minimum and maximum playable position on the - * whole content. + * @param {Object} manifest + * @param {Object} playbackObserver */ - var maximumPositionCalculator = new MaximumPositionCalculator(manifest); - // trigger warnings when the wanted time is before or after the manifest's - // segments - var outOfManifest$ = playbackObserver.getReference().asObservable().pipe((0,filter_map/* default */.Z)(function (_ref) { - var position = _ref.position; - var _a; - var wantedPosition = (_a = position.pending) !== null && _a !== void 0 ? _a : position.last; - if (wantedPosition < manifest.getMinimumSafePosition()) { - var warning = new media_error/* default */.Z("MEDIA_TIME_BEFORE_MANIFEST", "The current position is behind the " + "earliest time announced in the Manifest."); - return events_generators/* default.warning */.Z.warning(warning); - } else if (wantedPosition > maximumPositionCalculator.getMaximumAvailablePosition()) { - var _warning = new media_error/* default */.Z("MEDIA_TIME_AFTER_MANIFEST", "The current position is after the latest " + "time announced in the Manifest."); - return events_generators/* default.warning */.Z.warning(_warning); + function ContentTimeBoundariesObserver(manifest, playbackObserver, bufferTypes) { + var _this; + _this = _EventEmitter.call(this) || this; + _this._canceller = new task_canceller/* default */.ZP(); + _this._manifest = manifest; + _this._activeStreams = new Map(); + _this._allBufferTypes = bufferTypes; + _this._lastCurrentPeriodId = null; + /** + * Allows to calculate the minimum and maximum playable position on the + * whole content. + */ + var maximumPositionCalculator = new MaximumPositionCalculator(manifest); + _this._maximumPositionCalculator = maximumPositionCalculator; + var cancelSignal = _this._canceller.signal; + playbackObserver.listen(function (_ref) { + var position = _ref.position; + var _a; + var wantedPosition = (_a = position.pending) !== null && _a !== void 0 ? _a : position.last; + if (wantedPosition < manifest.getMinimumSafePosition()) { + var warning = new media_error/* default */.Z("MEDIA_TIME_BEFORE_MANIFEST", "The current position is behind the " + "earliest time announced in the Manifest."); + _this.trigger("warning", warning); + } else if (wantedPosition > maximumPositionCalculator.getMaximumAvailablePosition()) { + var _warning = new media_error/* default */.Z("MEDIA_TIME_AFTER_MANIFEST", "The current position is after the latest " + "time announced in the Manifest."); + _this.trigger("warning", _warning); + } + }, { + includeLastObservation: true, + clearSignal: cancelSignal + }); + manifest.addEventListener("manifestUpdate", function () { + _this.trigger("durationUpdate", getManifestDuration()); + if (cancelSignal.isCancelled()) { + return; + } + _this._checkEndOfStream(); + }, cancelSignal); + function getManifestDuration() { + return manifest.isDynamic ? maximumPositionCalculator.getMaximumAvailablePosition() : maximumPositionCalculator.getEndingPosition(); } - return null; - }, null)); - /** - * Contains the content duration according to the last audio and video - * Adaptation chosen for the last Period. - * `undefined` if unknown yet. - */ - var contentDuration = (0,reference/* default */.ZP)(undefined); - var updateDurationOnManifestUpdate$ = (0,event_emitter/* fromEvent */.R)(manifest, "manifestUpdate").pipe((0,startWith/* startWith */.O)(null), (0,tap/* tap */.b)(function () { - var duration = manifest.isDynamic ? maximumPositionCalculator.getEndingPosition() : maximumPositionCalculator.getMaximumAvailablePosition(); - contentDuration.setValue(duration); - }), (0,ignoreElements/* ignoreElements */.l)()); - var updateDurationAndTimeBoundsOnTrackChange$ = lastAdaptationChange.asObservable().pipe((0,tap/* tap */.b)(function (message) { - if (message === null || !manifest.isLastPeriodKnown) { + return _this; + } + /** + * Method to call any time an Adaptation has been selected. + * + * That Adaptation switch will be considered as active until the + * `onPeriodCleared` method has been called for the same `bufferType` and + * `Period`, or until `dispose` is called. + * @param {string} bufferType - The type of buffer concerned by the Adaptation + * switch + * @param {Object} period - The Period concerned by the Adaptation switch + * @param {Object|null} adaptation - The Adaptation selected. `null` if the + * absence of `Adaptation` has been explicitely selected for this Period and + * buffer type (e.g. no video). + */ + var _proto = ContentTimeBoundariesObserver.prototype; + _proto.onAdaptationChange = function onAdaptationChange(bufferType, period, adaptation) { + if (this._manifest.isLastPeriodKnown) { + var lastPeriod = this._manifest.periods[this._manifest.periods.length - 1]; + if (period.id === (lastPeriod === null || lastPeriod === void 0 ? void 0 : lastPeriod.id)) { + if (bufferType === "audio" || bufferType === "video") { + if (bufferType === "audio") { + this._maximumPositionCalculator.updateLastAudioAdaptation(adaptation); + } else { + this._maximumPositionCalculator.updateLastVideoAdaptation(adaptation); + } + var newDuration = this._manifest.isDynamic ? this._maximumPositionCalculator.getMaximumAvailablePosition() : this._maximumPositionCalculator.getEndingPosition(); + this.trigger("durationUpdate", newDuration); + } + } + } + if (this._canceller.isUsed()) { return; } - var lastPeriod = manifest.periods[manifest.periods.length - 1]; - if (message.value.period.id === (lastPeriod === null || lastPeriod === void 0 ? void 0 : lastPeriod.id)) { - if (message.value.type === "audio" || message.value.type === "video") { - if (message.value.type === "audio") { - maximumPositionCalculator.updateLastAudioAdaptation(message.value.adaptation); - } else { - maximumPositionCalculator.updateLastVideoAdaptation(message.value.adaptation); + if (adaptation === null) { + this._addActivelyLoadedPeriod(period, bufferType); + } + } + /** + * Method to call any time a Representation has been selected. + * + * That Representation switch will be considered as active until the + * `onPeriodCleared` method has been called for the same `bufferType` and + * `Period`, or until `dispose` is called. + * @param {string} bufferType - The type of buffer concerned by the + * Representation switch + * @param {Object} period - The Period concerned by the Representation switch + */; + _proto.onRepresentationChange = function onRepresentationChange(bufferType, period) { + this._addActivelyLoadedPeriod(period, bufferType); + } + /** + * Method to call any time a Period and type combination is not considered + * anymore. + * + * Calling this method allows to signal that a previous Adaptation and/or + * Representation change respectively indicated by an `onAdaptationChange` and + * an `onRepresentationChange` call, are not active anymore. + * @param {string} bufferType - The type of buffer concerned + * @param {Object} period - The Period concerned + */; + _proto.onPeriodCleared = function onPeriodCleared(bufferType, period) { + this._removeActivelyLoadedPeriod(period, bufferType); + } + /** + * Method to call when the last chronological segment for a given buffer type + * is known to have been loaded and is either pushed or in the process of + * being pushed to the corresponding MSE `SourceBuffer` or equivalent. + * + * This method can even be called multiple times in a row as long as the + * aforementioned condition is true, if it simplify your code's management. + * @param {string} bufferType + */; + _proto.onLastSegmentFinishedLoading = function onLastSegmentFinishedLoading(bufferType) { + var streamInfo = this._lazilyCreateActiveStreamInfo(bufferType); + if (!streamInfo.hasFinishedLoadingLastPeriod) { + streamInfo.hasFinishedLoadingLastPeriod = true; + this._checkEndOfStream(); + } + } + /** + * Method to call to "cancel" a previous call to + * `onLastSegmentFinishedLoading`. + * + * That is, calling this method indicates that the last chronological segment + * of a given buffer type is now either not loaded or it is not known. + * + * This method can even be called multiple times in a row as long as the + * aforementioned condition is true, if it simplify your code's management. + * @param {string} bufferType + */; + _proto.onLastSegmentLoadingResume = function onLastSegmentLoadingResume(bufferType) { + var streamInfo = this._lazilyCreateActiveStreamInfo(bufferType); + if (streamInfo.hasFinishedLoadingLastPeriod) { + streamInfo.hasFinishedLoadingLastPeriod = false; + this._checkEndOfStream(); + } + } + /** + * Free all resources used by the `ContentTimeBoundariesObserver` and cancels + * all recurring processes it performs. + */; + _proto.dispose = function dispose() { + this.removeEventListener(); + this._canceller.cancel(); + }; + _proto._addActivelyLoadedPeriod = function _addActivelyLoadedPeriod(period, bufferType) { + var streamInfo = this._lazilyCreateActiveStreamInfo(bufferType); + if (!streamInfo.activePeriods.has(period)) { + streamInfo.activePeriods.add(period); + this._checkCurrentPeriod(); + } + }; + _proto._removeActivelyLoadedPeriod = function _removeActivelyLoadedPeriod(period, bufferType) { + var streamInfo = this._activeStreams.get(bufferType); + if (streamInfo === undefined) { + return; + } + if (streamInfo.activePeriods.has(period)) { + streamInfo.activePeriods.removeElement(period); + this._checkCurrentPeriod(); + } + }; + _proto._checkCurrentPeriod = function _checkCurrentPeriod() { + var _this2 = this; + if (this._allBufferTypes.length === 0) { + return; + } + var streamInfo = this._activeStreams.get(this._allBufferTypes[0]); + if (streamInfo === undefined) { + return; + } + var _loop = function _loop() { + var period = _step.value; + var wasFoundInAllTypes = true; + for (var i = 1; i < _this2._allBufferTypes.length; i++) { + var streamInfo2 = _this2._activeStreams.get(_this2._allBufferTypes[i]); + if (streamInfo2 === undefined) { + return { + v: void 0 + }; + } + var activePeriods = streamInfo2.activePeriods.toArray(); + var hasPeriod = activePeriods.some(function (p) { + return p.id === period.id; + }); + if (!hasPeriod) { + wasFoundInAllTypes = false; + break; } - var newDuration = manifest.isDynamic ? maximumPositionCalculator.getMaximumAvailablePosition() : maximumPositionCalculator.getEndingPosition(); - contentDuration.setValue(newDuration); } - } - }), (0,ignoreElements/* ignoreElements */.l)()); - return (0,merge/* merge */.T)(updateDurationOnManifestUpdate$, updateDurationAndTimeBoundsOnTrackChange$, outOfManifest$, contentDuration.asObservable().pipe(skipWhile(function (val) { - return val === undefined; - }), distinctUntilChanged(), (0,map/* map */.U)(function (value) { - return { - type: "contentDurationUpdate", - value: value + if (wasFoundInAllTypes) { + if (_this2._lastCurrentPeriodId !== period.id) { + _this2._lastCurrentPeriodId = period.id; + _this2.trigger("periodChange", period); + } + return { + v: void 0 + }; + } }; - }))); -} + for (var _iterator = content_time_boundaries_observer_createForOfIteratorHelperLoose(streamInfo.activePeriods.toArray()), _step; !(_step = _iterator()).done;) { + var _ret = _loop(); + if (typeof _ret === "object") return _ret.v; + } + }; + _proto._lazilyCreateActiveStreamInfo = function _lazilyCreateActiveStreamInfo(bufferType) { + var streamInfo = this._activeStreams.get(bufferType); + if (streamInfo === undefined) { + streamInfo = { + activePeriods: new SortedList(function (a, b) { + return a.start - b.start; + }), + hasFinishedLoadingLastPeriod: false + }; + this._activeStreams.set(bufferType, streamInfo); + } + return streamInfo; + }; + _proto._checkEndOfStream = function _checkEndOfStream() { + var _this3 = this; + if (!this._manifest.isLastPeriodKnown) { + return; + } + var everyBufferTypeLoaded = this._allBufferTypes.every(function (bt) { + var streamInfo = _this3._activeStreams.get(bt); + return streamInfo !== undefined && streamInfo.hasFinishedLoadingLastPeriod; + }); + if (everyBufferTypeLoaded) { + this.trigger("endOfStream", null); + } else { + this.trigger("resumeStream", null); + } + }; + return ContentTimeBoundariesObserver; +}(event_emitter/* default */.Z); /** * Calculate the last position from the last chosen audio and video Adaptations * for the last Period (or a default one, if no Adaptations has been chosen). * @class MaximumPositionCalculator */ + var MaximumPositionCalculator = /*#__PURE__*/function () { /** * @param {Object} manifest @@ -51406,8 +47926,8 @@ var MaximumPositionCalculator = /*#__PURE__*/function () { * `getMaximumAvailablePosition` and `getEndingPosition`. * @param {Object|null} adaptation */ - var _proto = MaximumPositionCalculator.prototype; - _proto.updateLastAudioAdaptation = function updateLastAudioAdaptation(adaptation) { + var _proto2 = MaximumPositionCalculator.prototype; + _proto2.updateLastAudioAdaptation = function updateLastAudioAdaptation(adaptation) { this._lastAudioAdaptation = adaptation; } /** @@ -51418,7 +47938,7 @@ var MaximumPositionCalculator = /*#__PURE__*/function () { * `getMaximumAvailablePosition` and `getEndingPosition`. * @param {Object|null} adaptation */; - _proto.updateLastVideoAdaptation = function updateLastVideoAdaptation(adaptation) { + _proto2.updateLastVideoAdaptation = function updateLastVideoAdaptation(adaptation) { this._lastVideoAdaptation = adaptation; } /** @@ -51426,7 +47946,7 @@ var MaximumPositionCalculator = /*#__PURE__*/function () { * segments are available) under the current circumstances. * @returns {number} */; - _proto.getMaximumAvailablePosition = function getMaximumAvailablePosition() { + _proto2.getMaximumAvailablePosition = function getMaximumAvailablePosition() { var _a; if (this._manifest.isDynamic) { return (_a = this._manifest.getLivePosition()) !== null && _a !== void 0 ? _a : this._manifest.getMaximumSafePosition(); @@ -51465,7 +47985,7 @@ var MaximumPositionCalculator = /*#__PURE__*/function () { * Returns `undefined` if that could not be determined, for various reasons. * @returns {number|undefined} */; - _proto.getEndingPosition = function getEndingPosition() { + _proto2.getEndingPosition = function getEndingPosition() { var _a, _b; if (!this._manifest.isDynamic) { return this.getMaximumAvailablePosition(); @@ -51565,7 +48085,121 @@ function getEndingPositionFromAdaptation(adaptation) { } return min; } -;// CONCATENATED MODULE: ./src/core/init/create_stream_playback_observer.ts +// EXTERNAL MODULE: ./src/compat/clear_element_src.ts +var clear_element_src = __webpack_require__(5767); +// EXTERNAL MODULE: ./src/compat/browser_compatibility_types.ts +var browser_compatibility_types = __webpack_require__(3774); +// EXTERNAL MODULE: ./src/utils/is_non_empty_string.ts +var is_non_empty_string = __webpack_require__(6923); +;// CONCATENATED MODULE: ./src/core/init/utils/create_media_source.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + + + +/** + * Dispose of ressources taken by the MediaSource: + * - Clear the MediaSource' SourceBuffers + * - Clear the mediaElement's src (stop the mediaElement) + * - Revoke MediaSource' URL + * @param {HTMLMediaElement} mediaElement + * @param {MediaSource|null} mediaSource + * @param {string|null} mediaSourceURL + */ +function resetMediaSource(mediaElement, mediaSource, mediaSourceURL) { + if (mediaSource !== null && mediaSource.readyState !== "closed") { + var readyState = mediaSource.readyState, + sourceBuffers = mediaSource.sourceBuffers; + for (var i = sourceBuffers.length - 1; i >= 0; i--) { + var sourceBuffer = sourceBuffers[i]; + try { + if (readyState === "open") { + log/* default.info */.Z.info("Init: Removing SourceBuffer from mediaSource"); + sourceBuffer.abort(); + } + mediaSource.removeSourceBuffer(sourceBuffer); + } catch (e) { + log/* default.warn */.Z.warn("Init: Error while disposing SourceBuffer", e instanceof Error ? e : ""); + } + } + if (sourceBuffers.length > 0) { + log/* default.warn */.Z.warn("Init: Not all SourceBuffers could have been removed."); + } + } + (0,clear_element_src/* default */.Z)(mediaElement); + if (mediaSourceURL !== null) { + try { + log/* default.debug */.Z.debug("Init: Revoking previous URL"); + URL.revokeObjectURL(mediaSourceURL); + } catch (e) { + log/* default.warn */.Z.warn("Init: Error while revoking the media source URL", e instanceof Error ? e : ""); + } + } +} +/** + * Create a MediaSource instance and attach it to the given mediaElement element's + * src attribute. + * + * Returns a Promise which resolves with the MediaSource when created and attached + * to the `mediaElement` element. + * + * When the given `unlinkSignal` emits, mediaElement.src is cleaned, MediaSource + * SourceBuffers are aborted and some minor cleaning is done. + * @param {HTMLMediaElement} mediaElement + * @param {Object} unlinkSignal + * @returns {MediaSource} + */ +function createMediaSource(mediaElement, unlinkSignal) { + if (browser_compatibility_types/* MediaSource_ */.J == null) { + throw new media_error/* default */.Z("MEDIA_SOURCE_NOT_SUPPORTED", "No MediaSource Object was found in the current browser."); + } + // make sure the media has been correctly reset + var oldSrc = (0,is_non_empty_string/* default */.Z)(mediaElement.src) ? mediaElement.src : null; + resetMediaSource(mediaElement, null, oldSrc); + log/* default.info */.Z.info("Init: Creating MediaSource"); + var mediaSource = new browser_compatibility_types/* MediaSource_ */.J(); + var objectURL = URL.createObjectURL(mediaSource); + log/* default.info */.Z.info("Init: Attaching MediaSource URL to the media element", objectURL); + mediaElement.src = objectURL; + unlinkSignal.register(function () { + resetMediaSource(mediaElement, mediaSource, objectURL); + }); + return mediaSource; +} +/** + * Create and open a new MediaSource object on the given media element. + * Resolves with the MediaSource when done. + * + * When the given `unlinkSignal` emits, mediaElement.src is cleaned, MediaSource + * SourceBuffers are aborted and some minor cleaning is done. + * @param {HTMLMediaElement} mediaElement + * @param {Object} unlinkSignal + * @returns {Promise} + */ +function openMediaSource(mediaElement, unlinkSignal) { + return (0,create_cancellable_promise/* default */.Z)(unlinkSignal, function (resolve) { + var mediaSource = createMediaSource(mediaElement, unlinkSignal); + event_listeners/* onSourceOpen */.u_(mediaSource, function () { + resolve(mediaSource); + }, unlinkSignal); + }); +} +;// CONCATENATED MODULE: ./src/core/init/utils/create_stream_playback_observer.ts /** * Copyright 2015 CANAL+ Group * @@ -51596,7 +48230,7 @@ function createStreamPlaybackObserver(manifest, playbackObserver, _ref) { speed = _ref.speed, startTime = _ref.startTime; return playbackObserver.deriveReadOnlyObserver(function transform(observationRef, cancellationSignal) { - var newRef = (0,reference/* default */.ZP)(constructStreamPlaybackObservation()); + var newRef = (0,reference/* default */.ZP)(constructStreamPlaybackObservation(), cancellationSignal); speed.onUpdate(emitStreamPlaybackObservation, { clearSignal: cancellationSignal, emitCurrentValue: false @@ -51605,9 +48239,6 @@ function createStreamPlaybackObserver(manifest, playbackObserver, _ref) { clearSignal: cancellationSignal, emitCurrentValue: false }); - cancellationSignal.register(function () { - newRef.finish(); - }); return newRef; function constructStreamPlaybackObservation() { var observation = observationRef.getValue(); @@ -51648,85 +48279,10 @@ function createStreamPlaybackObserver(manifest, playbackObserver, _ref) { } }); } -// EXTERNAL MODULE: ./src/core/init/emit_loaded_event.ts + 1 modules -var emit_loaded_event = __webpack_require__(5039); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/util/argsOrArgArray.js -var argsOrArgArray_isArray = Array.isArray; -function argsOrArgArray(args) { - return args.length === 1 && argsOrArgArray_isArray(args[0]) ? args[0] : args; -} -//# sourceMappingURL=argsOrArgArray.js.map -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/race.js - - - - -function race() { - var sources = []; - for (var _i = 0; _i < arguments.length; _i++) { - sources[_i] = arguments[_i]; - } - sources = argsOrArgArray(sources); - return sources.length === 1 ? (0,innerFrom/* innerFrom */.Xf)(sources[0]) : new Observable/* Observable */.y(raceInit(sources)); -} -function raceInit(sources) { - return function (subscriber) { - var subscriptions = []; - var _loop_1 = function (i) { - subscriptions.push((0,innerFrom/* innerFrom */.Xf)(sources[i]).subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - if (subscriptions) { - for (var s = 0; s < subscriptions.length; s++) { - s !== i && subscriptions[s].unsubscribe(); - } - subscriptions = null; - } - subscriber.next(value); - }))); - }; - for (var i = 0; subscriptions && !subscriber.closed && i < sources.length; i++) { - _loop_1(i); - } - }; -} -//# sourceMappingURL=race.js.map -// EXTERNAL MODULE: ./node_modules/rxjs/node_modules/tslib/tslib.es6.js -var tslib_es6 = __webpack_require__(5987); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/takeLast.js - - - - -function takeLast(count) { - return count <= 0 - ? function () { return empty/* EMPTY */.E; } - : (0,lift/* operate */.e)(function (source, subscriber) { - var buffer = []; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - buffer.push(value); - count < buffer.length && buffer.shift(); - }, function () { - var e_1, _a; - try { - for (var buffer_1 = (0,tslib_es6/* __values */.XA)(buffer), buffer_1_1 = buffer_1.next(); !buffer_1_1.done; buffer_1_1 = buffer_1.next()) { - var value = buffer_1_1.value; - subscriber.next(value); - } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { - try { - if (buffer_1_1 && !buffer_1_1.done && (_a = buffer_1.return)) _a.call(buffer_1); - } - finally { if (e_1) throw e_1.error; } - } - subscriber.complete(); - }, undefined, function () { - buffer = null; - })); - }); -} -//# sourceMappingURL=takeLast.js.map -;// CONCATENATED MODULE: ./src/core/init/end_of_stream.ts +;// CONCATENATED MODULE: ./src/core/init/utils/end_of_stream.ts +function end_of_stream_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = end_of_stream_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function end_of_stream_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return end_of_stream_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return end_of_stream_arrayLikeToArray(o, minLen); } +function end_of_stream_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -51745,9 +48301,9 @@ function takeLast(count) { -var onRemoveSourceBuffers$ = event_listeners/* onRemoveSourceBuffers$ */.gg, - end_of_stream_onSourceOpen$ = event_listeners/* onSourceOpen$ */.ym, - onUpdate$ = event_listeners/* onUpdate$ */._E; +var onRemoveSourceBuffers = event_listeners/* onRemoveSourceBuffers */.x6, + onSourceOpen = event_listeners/* onSourceOpen */.u_, + onSourceBufferUpdate = event_listeners/* onSourceBufferUpdate */.y4; /** * Get "updating" SourceBuffers from a SourceBufferList. * @param {SourceBufferList} sourceBuffers @@ -51770,63 +48326,155 @@ function getUpdatingSourceBuffers(sourceBuffers) { * If SourceBuffers are updating, wait for them to be updated before closing * it. * @param {MediaSource} mediaSource - * @returns {Observable} - */ -function triggerEndOfStream(mediaSource) { - return (0,defer/* defer */.P)(function () { - log/* default.debug */.Z.debug("Init: Trying to call endOfStream"); - if (mediaSource.readyState !== "open") { - log/* default.debug */.Z.debug("Init: MediaSource not open, cancel endOfStream"); - return (0,of.of)(null); - } - var sourceBuffers = mediaSource.sourceBuffers; - var updatingSourceBuffers = getUpdatingSourceBuffers(sourceBuffers); - if (updatingSourceBuffers.length === 0) { - log/* default.info */.Z.info("Init: Triggering end of stream"); - mediaSource.endOfStream(); - return (0,of.of)(null); - } - log/* default.debug */.Z.debug("Init: Waiting SourceBuffers to be updated before calling endOfStream."); - var updatedSourceBuffers$ = updatingSourceBuffers.map(function (sourceBuffer) { - return onUpdate$(sourceBuffer).pipe((0,take/* take */.q)(1)); - }); - return race(merge/* merge.apply */.T.apply(void 0, updatedSourceBuffers$).pipe(takeLast(1)), onRemoveSourceBuffers$(sourceBuffers).pipe((0,take/* take */.q)(1))).pipe((0,mergeMap/* mergeMap */.z)(function () { - return triggerEndOfStream(mediaSource); - })); - }); + * @param {Object} cancelSignal + */ +function triggerEndOfStream(mediaSource, cancelSignal) { + log/* default.debug */.Z.debug("Init: Trying to call endOfStream"); + if (mediaSource.readyState !== "open") { + log/* default.debug */.Z.debug("Init: MediaSource not open, cancel endOfStream"); + return; + } + var sourceBuffers = mediaSource.sourceBuffers; + var updatingSourceBuffers = getUpdatingSourceBuffers(sourceBuffers); + if (updatingSourceBuffers.length === 0) { + log/* default.info */.Z.info("Init: Triggering end of stream"); + mediaSource.endOfStream(); + return; + } + log/* default.debug */.Z.debug("Init: Waiting SourceBuffers to be updated before calling endOfStream."); + var innerCanceller = new task_canceller/* default */.ZP(); + innerCanceller.linkToSignal(cancelSignal); + for (var _iterator = end_of_stream_createForOfIteratorHelperLoose(updatingSourceBuffers), _step; !(_step = _iterator()).done;) { + var sourceBuffer = _step.value; + onSourceBufferUpdate(sourceBuffer, function () { + innerCanceller.cancel(); + triggerEndOfStream(mediaSource, cancelSignal); + }, innerCanceller.signal); + } + onRemoveSourceBuffers(sourceBuffers, function () { + innerCanceller.cancel(); + triggerEndOfStream(mediaSource, cancelSignal); + }, innerCanceller.signal); } /** * Trigger the `endOfStream` method of a MediaSource each times it opens. * @see triggerEndOfStream * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {Object} cancelSignal */ -function maintainEndOfStream(mediaSource) { - return end_of_stream_onSourceOpen$(mediaSource).pipe((0,startWith/* startWith */.O)(null), (0,switchMap/* switchMap */.w)(function () { - return triggerEndOfStream(mediaSource); - })); +function maintainEndOfStream(mediaSource, cancelSignal) { + var endOfStreamCanceller = new task_canceller/* default */.ZP(); + endOfStreamCanceller.linkToSignal(cancelSignal); + onSourceOpen(mediaSource, function () { + endOfStreamCanceller.cancel(); + endOfStreamCanceller = new task_canceller/* default */.ZP(); + endOfStreamCanceller.linkToSignal(cancelSignal); + triggerEndOfStream(mediaSource, endOfStreamCanceller.signal); + }, cancelSignal); + triggerEndOfStream(mediaSource, endOfStreamCanceller.signal); } -// EXTERNAL MODULE: ./src/core/init/initial_seek_and_play.ts + 2 modules -var initial_seek_and_play = __webpack_require__(7920); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/timer.js -var timer = __webpack_require__(6625); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/fromEvent.js -var fromEvent = __webpack_require__(2401); -// EXTERNAL MODULE: ./node_modules/rxjs/dist/esm5/internal/scheduler/async.js -var scheduler_async = __webpack_require__(7991); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/observable/interval.js +;// CONCATENATED MODULE: ./src/core/init/utils/get_initial_time.ts +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -function interval(period, scheduler) { - if (period === void 0) { period = 0; } - if (scheduler === void 0) { scheduler = scheduler_async/* asyncScheduler */.z; } - if (period < 0) { - period = 0; + +/** + * Returns the calculated initial time for the content described by the given + * Manifest: + * 1. if a start time is defined by user, calculate starting time from the + * manifest information + * 2. else if the media is live, use the live edge and suggested delays from + * it + * 3. else returns the minimum time announced in the manifest + * @param {Manifest} manifest + * @param {boolean} lowLatencyMode + * @param {Object} startAt + * @returns {Number} + */ +function getInitialTime(manifest, lowLatencyMode, startAt) { + if (!(0,is_null_or_undefined/* default */.Z)(startAt)) { + var min = manifest.getMinimumSafePosition(); + var max; + if (manifest.isLive) { + max = manifest.getLivePosition(); + } + if (max === undefined) { + max = manifest.getMaximumSafePosition(); + } + if (!(0,is_null_or_undefined/* default */.Z)(startAt.position)) { + log/* default.debug */.Z.debug("Init: using startAt.minimumPosition"); + return Math.max(Math.min(startAt.position, max), min); + } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.wallClockTime)) { + log/* default.debug */.Z.debug("Init: using startAt.wallClockTime"); + var ast = manifest.availabilityStartTime === undefined ? 0 : manifest.availabilityStartTime; + var position = startAt.wallClockTime - ast; + return Math.max(Math.min(position, max), min); + } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.fromFirstPosition)) { + log/* default.debug */.Z.debug("Init: using startAt.fromFirstPosition"); + var fromFirstPosition = startAt.fromFirstPosition; + return fromFirstPosition <= 0 ? min : Math.min(max, min + fromFirstPosition); + } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.fromLastPosition)) { + log/* default.debug */.Z.debug("Init: using startAt.fromLastPosition"); + var fromLastPosition = startAt.fromLastPosition; + return fromLastPosition >= 0 ? max : Math.max(min, max + fromLastPosition); + } else if (!(0,is_null_or_undefined/* default */.Z)(startAt.percentage)) { + log/* default.debug */.Z.debug("Init: using startAt.percentage"); + var percentage = startAt.percentage; + if (percentage > 100) { + return max; + } else if (percentage < 0) { + return min; + } + var ratio = +percentage / 100; + var extent = max - min; + return min + extent * ratio; + } + } + var minimumPosition = manifest.getMinimumSafePosition(); + if (manifest.isLive) { + var suggestedPresentationDelay = manifest.suggestedPresentationDelay, + clockOffset = manifest.clockOffset; + var maximumPosition = manifest.getMaximumSafePosition(); + var liveTime; + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + DEFAULT_LIVE_GAP = _config$getCurrent.DEFAULT_LIVE_GAP; + if (clockOffset === undefined) { + log/* default.info */.Z.info("Init: no clock offset found for a live content, " + "starting close to maximum available position"); + liveTime = maximumPosition; + } else { + log/* default.info */.Z.info("Init: clock offset found for a live content, " + "checking if we can start close to it"); + var _ast = manifest.availabilityStartTime === undefined ? 0 : manifest.availabilityStartTime; + var clockRelativeLiveTime = (performance.now() + clockOffset) / 1000 - _ast; + liveTime = Math.min(maximumPosition, clockRelativeLiveTime); } - return (0,timer/* timer */.H)(period, period, scheduler); + var diffFromLiveTime = suggestedPresentationDelay !== undefined ? suggestedPresentationDelay : lowLatencyMode ? DEFAULT_LIVE_GAP.LOW_LATENCY : DEFAULT_LIVE_GAP.DEFAULT; + log/* default.debug */.Z.debug("Init: " + liveTime + " defined as the live time, applying a live gap" + (" of " + diffFromLiveTime)); + return Math.max(liveTime - diffFromLiveTime, minimumPosition); + } + log/* default.info */.Z.info("Init: starting at the minimum available position:", minimumPosition); + return minimumPosition; } -//# sourceMappingURL=interval.js.map -;// CONCATENATED MODULE: ./src/core/init/media_duration_updater.ts +// EXTERNAL MODULE: ./src/core/init/utils/get_loaded_reference.ts + 1 modules +var get_loaded_reference = __webpack_require__(1757); +// EXTERNAL MODULE: ./src/core/init/utils/initial_seek_and_play.ts +var initial_seek_and_play = __webpack_require__(8833); +// EXTERNAL MODULE: ./src/core/init/utils/initialize_content_decryption.ts + 1 modules +var initialize_content_decryption = __webpack_require__(8799); +;// CONCATENATED MODULE: ./src/core/init/utils/media_duration_updater.ts /** * Copyright 2015 CANAL+ Group * @@ -51846,7 +48494,6 @@ function interval(period, scheduler) { - /** Number of seconds in a regular year. */ var YEAR_IN_SECONDS = 365 * 24 * 3600; /** @@ -51864,25 +48511,59 @@ var MediaDurationUpdater = /*#__PURE__*/function () { * pushed. */ function MediaDurationUpdater(manifest, mediaSource) { - var _this = this; - this._lastKnownDuration = (0,reference/* default */.ZP)(undefined); - this._subscription = isMediaSourceOpened$(mediaSource).pipe((0,switchMap/* switchMap */.w)(function (canUpdate) { - return canUpdate ? combineLatest([_this._lastKnownDuration.asObservable(), (0,event_emitter/* fromEvent */.R)(manifest, "manifestUpdate").pipe((0,startWith/* startWith */.O)(null))]) : empty/* EMPTY */.E; - }), (0,switchMap/* switchMap */.w)(function (_ref) { - var lastKnownDuration = _ref[0]; - return areSourceBuffersUpdating$(mediaSource.sourceBuffers).pipe((0,switchMap/* switchMap */.w)(function (areSBUpdating) { - return areSBUpdating ? empty/* EMPTY */.E : recursivelyTryUpdatingDuration(); - function recursivelyTryUpdatingDuration() { - var res = setMediaSourceDuration(mediaSource, manifest, lastKnownDuration); - if (res === "success" /* MediaSourceDurationUpdateStatus.Success */) { - return empty/* EMPTY */.E; - } - return (0,timer/* timer */.H)(2000).pipe((0,mergeMap/* mergeMap */.z)(function () { - return recursivelyTryUpdatingDuration(); - })); + var canceller = new task_canceller/* default */.ZP(); + var currentKnownDuration = (0,reference/* default */.ZP)(undefined, canceller.signal); + this._canceller = canceller; + this._currentKnownDuration = currentKnownDuration; + var isMediaSourceOpened = createMediaSourceOpenReference(mediaSource, this._canceller.signal); + /** TaskCanceller triggered each time the MediaSource open status changes. */ + var msUpdateCanceller = new task_canceller/* default */.ZP(); + msUpdateCanceller.linkToSignal(this._canceller.signal); + isMediaSourceOpened.onUpdate(onMediaSourceOpenedStatusChanged, { + emitCurrentValue: true, + clearSignal: this._canceller.signal + }); + function onMediaSourceOpenedStatusChanged() { + msUpdateCanceller.cancel(); + if (!isMediaSourceOpened.getValue()) { + return; + } + msUpdateCanceller = new task_canceller/* default */.ZP(); + msUpdateCanceller.linkToSignal(canceller.signal); + /** TaskCanceller triggered each time the content's duration may have changed */ + var durationChangeCanceller = new task_canceller/* default */.ZP(); + durationChangeCanceller.linkToSignal(msUpdateCanceller.signal); + var reSetDuration = function reSetDuration() { + durationChangeCanceller.cancel(); + durationChangeCanceller = new task_canceller/* default */.ZP(); + durationChangeCanceller.linkToSignal(msUpdateCanceller.signal); + onDurationMayHaveChanged(durationChangeCanceller.signal); + }; + currentKnownDuration.onUpdate(reSetDuration, { + emitCurrentValue: false, + clearSignal: msUpdateCanceller.signal + }); + manifest.addEventListener("manifestUpdate", reSetDuration, msUpdateCanceller.signal); + onDurationMayHaveChanged(durationChangeCanceller.signal); + } + function onDurationMayHaveChanged(cancelSignal) { + var areSourceBuffersUpdating = createSourceBuffersUpdatingReference(mediaSource.sourceBuffers, cancelSignal); + /** TaskCanceller triggered each time SourceBuffers' updating status changes */ + var sourceBuffersUpdatingCanceller = new task_canceller/* default */.ZP(); + sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal); + return areSourceBuffersUpdating.onUpdate(function (areUpdating) { + sourceBuffersUpdatingCanceller.cancel(); + sourceBuffersUpdatingCanceller = new task_canceller/* default */.ZP(); + sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal); + if (areUpdating) { + return; } - })); - })).subscribe(); + recursivelyForceDurationUpdate(mediaSource, manifest, currentKnownDuration.getValue(), cancelSignal); + }, { + clearSignal: cancelSignal, + emitCurrentValue: true + }); + } } /** * By default, the `MediaDurationUpdater` only set a safe estimate for the @@ -51895,7 +48576,7 @@ var MediaDurationUpdater = /*#__PURE__*/function () { */ var _proto = MediaDurationUpdater.prototype; _proto.updateKnownDuration = function updateKnownDuration(newDuration) { - this._lastKnownDuration.setValue(newDuration); + this._currentKnownDuration.setValueIfChanged(newDuration); } /** * Stop the `MediaDurationUpdater` from updating and free its resources. @@ -51903,7 +48584,7 @@ var MediaDurationUpdater = /*#__PURE__*/function () { * `MediaDurationUpdater`. */; _proto.stop = function stop() { - this._subscription.unsubscribe(); + this._canceller.cancel(); }; return MediaDurationUpdater; }(); @@ -51925,18 +48606,20 @@ function setMediaSourceDuration(mediaSource, manifest, knownDuration) { var newDuration = knownDuration; if (newDuration === undefined) { if (manifest.isDynamic) { - var maxPotentialPos = (_a = manifest.getLivePosition()) !== null && _a !== void 0 ? _a : manifest.getMaximumSafePosition(); - // Some targets poorly support setting a very high number for durations. - // Yet, in dynamic contents, we would prefer setting a value as high as possible - // to still be able to seek anywhere we want to (even ahead of the Manifest if - // we want to). As such, we put it at a safe default value of 2^32 excepted - // when the maximum position is already relatively close to that value, where - // we authorize exceptionally going over it. - newDuration = Math.max(Math.pow(2, 32), maxPotentialPos + YEAR_IN_SECONDS); + newDuration = (_a = manifest.getLivePosition()) !== null && _a !== void 0 ? _a : manifest.getMaximumSafePosition(); } else { newDuration = manifest.getMaximumSafePosition(); } } + if (manifest.isDynamic) { + // Some targets poorly support setting a very high number for durations. + // Yet, in dynamic contents, we would prefer setting a value as high as possible + // to still be able to seek anywhere we want to (even ahead of the Manifest if + // we want to). As such, we put it at a safe default value of 2^32 excepted + // when the maximum position is already relatively close to that value, where + // we authorize exceptionally going over it. + newDuration = Math.max(Math.pow(2, 32), newDuration + YEAR_IN_SECONDS); + } var maxBufferedEnd = 0; for (var i = 0; i < mediaSource.sourceBuffers.length; i++) { var sourceBuffer = mediaSource.sourceBuffers[i]; @@ -51953,7 +48636,7 @@ function setMediaSourceDuration(mediaSource, manifest, knownDuration) { if (maxBufferedEnd < mediaSource.duration) { try { log/* default.info */.Z.info("Init: Updating duration to what is currently buffered", maxBufferedEnd); - mediaSource.duration = newDuration; + mediaSource.duration = maxBufferedEnd; } catch (err) { log/* default.warn */.Z.warn("Duration Updater: Can't update duration on the MediaSource.", err instanceof Error ? err : ""); return "failed" /* MediaSourceDurationUpdateStatus.Failed */; @@ -51981,68 +48664,91 @@ function setMediaSourceDuration(mediaSource, manifest, knownDuration) { } } /** - * Returns an Observable which will emit only when all the SourceBuffers ended - * all pending updates. + * Returns an `ISharedReference` wrapping a boolean that tells if all the + * SourceBuffers ended all pending updates. * @param {SourceBufferList} sourceBuffers - * @returns {Observable} + * @param {Object} cancelSignal + * @returns {Object} */ -function areSourceBuffersUpdating$(sourceBuffers) { +function createSourceBuffersUpdatingReference(sourceBuffers, cancelSignal) { if (sourceBuffers.length === 0) { - return (0,of.of)(false); + var notOpenedRef = (0,reference/* default */.ZP)(false); + notOpenedRef.finish(); + return notOpenedRef; } - var sourceBufferUpdatingStatuses = []; + var areUpdatingRef = (0,reference/* default */.ZP)(false, cancelSignal); + reCheck(); var _loop = function _loop(i) { var sourceBuffer = sourceBuffers[i]; - sourceBufferUpdatingStatuses.push((0,merge/* merge */.T)((0,fromEvent/* fromEvent */.R)(sourceBuffer, "updatestart").pipe((0,map/* map */.U)(function () { - return true; - })), (0,fromEvent/* fromEvent */.R)(sourceBuffer, "update").pipe((0,map/* map */.U)(function () { - return false; - })), interval(500).pipe((0,map/* map */.U)(function () { - return sourceBuffer.updating; - }))).pipe((0,startWith/* startWith */.O)(sourceBuffer.updating), distinctUntilChanged())); + sourceBuffer.addEventListener("updatestart", reCheck); + sourceBuffer.addEventListener("update", reCheck); + cancelSignal.register(function () { + sourceBuffer.removeEventListener("updatestart", reCheck); + sourceBuffer.removeEventListener("update", reCheck); + }); }; for (var i = 0; i < sourceBuffers.length; i++) { _loop(i); } - return combineLatest(sourceBufferUpdatingStatuses).pipe((0,map/* map */.U)(function (areUpdating) { - return areUpdating.some(function (isUpdating) { - return isUpdating; - }); - }), distinctUntilChanged()); + return areUpdatingRef; + function reCheck() { + for (var _i = 0; _i < sourceBuffers.length; _i++) { + var sourceBuffer = sourceBuffers[_i]; + if (sourceBuffer.updating) { + areUpdatingRef.setValueIfChanged(true); + return; + } + } + areUpdatingRef.setValueIfChanged(false); + } } /** - * Emit a boolean that tells if the media source is opened or not. + * Returns an `ISharedReference` wrapping a boolean that tells if the media + * source is opened or not. * @param {MediaSource} mediaSource + * @param {Object} cancelSignal * @returns {Object} */ -function isMediaSourceOpened$(mediaSource) { - return (0,merge/* merge */.T)((0,event_listeners/* onSourceOpen$ */.ym)(mediaSource).pipe((0,map/* map */.U)(function () { - return true; - })), (0,event_listeners/* onSourceEnded$ */.ep)(mediaSource).pipe((0,map/* map */.U)(function () { - return false; - })), (0,event_listeners/* onSourceClose$ */.UG)(mediaSource).pipe((0,map/* map */.U)(function () { - return false; - }))).pipe((0,startWith/* startWith */.O)(mediaSource.readyState === "open"), distinctUntilChanged()); -} -// EXTERNAL MODULE: ./src/core/init/rebuffering_controller.ts + 1 modules -var rebuffering_controller = __webpack_require__(342); -;// CONCATENATED MODULE: ./node_modules/rxjs/dist/esm5/internal/operators/pairwise.js - - -function pairwise() { - return (0,lift/* operate */.e)(function (source, subscriber) { - var prev; - var hasPrev = false; - source.subscribe((0,OperatorSubscriber/* createOperatorSubscriber */.x)(subscriber, function (value) { - var p = prev; - prev = value; - hasPrev && subscriber.next([p, value]); - hasPrev = true; - })); - }); +function createMediaSourceOpenReference(mediaSource, cancelSignal) { + var isMediaSourceOpen = (0,reference/* default */.ZP)(mediaSource.readyState === "open", cancelSignal); + (0,event_listeners/* onSourceOpen */.u_)(mediaSource, function () { + isMediaSourceOpen.setValueIfChanged(true); + }, cancelSignal); + (0,event_listeners/* onSourceEnded */.N8)(mediaSource, function () { + isMediaSourceOpen.setValueIfChanged(false); + }, cancelSignal); + (0,event_listeners/* onSourceClose */.k6)(mediaSource, function () { + isMediaSourceOpen.setValueIfChanged(false); + }, cancelSignal); + return isMediaSourceOpen; +} +/** + * Immediately tries to set the MediaSource's duration to the most appropriate + * one according to the Manifest and duration given. + * + * If it fails, wait 2 seconds and retries. + * + * @param {MediaSource} mediaSource + * @param {Object} manifest + * @param {number|undefined} duration + * @param {Object} cancelSignal + */ +function recursivelyForceDurationUpdate(mediaSource, manifest, duration, cancelSignal) { + var res = setMediaSourceDuration(mediaSource, manifest, duration); + if (res === "success" /* MediaSourceDurationUpdateStatus.Success */) { + return; + } + var timeoutId = setTimeout(function () { + unregisterClear(); + recursivelyForceDurationUpdate(mediaSource, manifest, duration, cancelSignal); + }, 2000); + var unregisterClear = cancelSignal.register(function () { + clearTimeout(timeoutId); + }); } -//# sourceMappingURL=pairwise.js.map -;// CONCATENATED MODULE: ./src/core/init/stream_events_emitter/are_same_stream_events.ts +// EXTERNAL MODULE: ./src/core/init/utils/rebuffering_controller.ts + 1 modules +var rebuffering_controller = __webpack_require__(6199); +;// CONCATENATED MODULE: ./src/core/init/utils/stream_events_emitter/are_same_stream_events.ts /** * Copyright 2015 CANAL+ Group * @@ -52076,7 +48782,7 @@ function areSameStreamEvents(evt1, evt2) { return evt1.id === evt2.id && evt1.start === evt2.start && evt1.end === evt2.end; } /* harmony default export */ var are_same_stream_events = (areSameStreamEvents); -;// CONCATENATED MODULE: ./src/core/init/stream_events_emitter/refresh_scheduled_events_list.ts +;// CONCATENATED MODULE: ./src/core/init/utils/stream_events_emitter/refresh_scheduled_events_list.ts /** * Copyright 2015 CANAL+ Group * @@ -52151,7 +48857,10 @@ function refreshScheduledEventsList(oldScheduledEvents, manifest) { return scheduledEvents; } /* harmony default export */ var refresh_scheduled_events_list = (refreshScheduledEventsList); -;// CONCATENATED MODULE: ./src/core/init/stream_events_emitter/stream_events_emitter.ts +;// CONCATENATED MODULE: ./src/core/init/utils/stream_events_emitter/stream_events_emitter.ts +function stream_events_emitter_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = stream_events_emitter_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function stream_events_emitter_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return stream_events_emitter_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return stream_events_emitter_arrayLikeToArray(o, minLen); } +function stream_events_emitter_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -52171,35 +48880,76 @@ function refreshScheduledEventsList(oldScheduledEvents, manifest) { -/** - * Tells if a stream event has a duration - * @param {Object} evt - * @returns {Boolean} - */ -function isFiniteStreamEvent(evt) { - return evt.end !== undefined; -} /** * Get events from manifest and emit each time an event has to be emitted * @param {Object} manifest * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {Object} playbackObserver + * @param {Function} onEvent + * @param {Function} onEventSkip + * @param {Object} cancelSignal + * @returns {Object} */ -function streamEventsEmitter(manifest, mediaElement, observation$) { +function streamEventsEmitter(manifest, mediaElement, playbackObserver, onEvent, onEventSkip, cancelSignal) { var eventsBeingPlayed = new WeakMap(); - var lastScheduledEvents = []; - var scheduledEvents$ = (0,event_emitter/* fromEvent */.R)(manifest, "manifestUpdate").pipe((0,startWith/* startWith */.O)(null), (0,scan/* scan */.R)(function (oldScheduledEvents) { - return refresh_scheduled_events_list(oldScheduledEvents, manifest); - }, [])); + var scheduledEventsRef = (0,reference/* default */.ZP)(refresh_scheduled_events_list([], manifest), cancelSignal); + manifest.addEventListener("manifestUpdate", function () { + var prev = scheduledEventsRef.getValue(); + scheduledEventsRef.setValue(refresh_scheduled_events_list(prev, manifest)); + }, cancelSignal); + var isPollingEvents = false; + var cancelCurrentPolling = new task_canceller/* default */.ZP(); + cancelCurrentPolling.linkToSignal(cancelSignal); + scheduledEventsRef.onUpdate(function (_ref) { + var scheduledEventsLength = _ref.length; + if (scheduledEventsLength === 0) { + if (isPollingEvents) { + cancelCurrentPolling.cancel(); + cancelCurrentPolling = new task_canceller/* default */.ZP(); + cancelCurrentPolling.linkToSignal(cancelSignal); + isPollingEvents = false; + } + return; + } else if (isPollingEvents) { + return; + } + isPollingEvents = true; + var oldObservation = constructObservation(); + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + STREAM_EVENT_EMITTER_POLL_INTERVAL = _config$getCurrent.STREAM_EVENT_EMITTER_POLL_INTERVAL; + var intervalId = setInterval(checkStreamEvents, STREAM_EVENT_EMITTER_POLL_INTERVAL); + playbackObserver.listen(checkStreamEvents, { + includeLastObservation: false, + clearSignal: cancelCurrentPolling.signal + }); + cancelCurrentPolling.signal.register(function () { + clearInterval(intervalId); + }); + function checkStreamEvents() { + var newObservation = constructObservation(); + emitStreamEvents(scheduledEventsRef.getValue(), oldObservation, newObservation, cancelCurrentPolling.signal); + oldObservation = newObservation; + } + function constructObservation() { + var isSeeking = playbackObserver.getReference().getValue().seeking; + return { + currentTime: mediaElement.currentTime, + isSeeking: isSeeking + }; + } + }, { + emitCurrentValue: true, + clearSignal: cancelSignal + }); /** * Examine playback situation from playback observations to emit stream events and * prepare set onExit callbacks if needed. * @param {Array.} scheduledEvents * @param {Object} oldObservation * @param {Object} newObservation - * @returns {Observable} + * @param {Object} stopSignal */ - function emitStreamEvents$(scheduledEvents, oldObservation, newObservation) { + function emitStreamEvents(scheduledEvents, oldObservation, newObservation, stopSignal) { var previousTime = oldObservation.currentTime; var isSeeking = newObservation.isSeeking, currentTime = newObservation.currentTime; @@ -52230,283 +48980,51 @@ function streamEventsEmitter(manifest, mediaElement, observation$) { value: event.publicEvent }); } else { - eventsToSend.push({ - type: "stream-event", - value: event.publicEvent - }); - if (isFiniteStreamEvent(event)) { - eventsToExit.push(event.publicEvent); - } - } - } - } - return (0,concat/* concat */.z)(eventsToSend.length > 0 ? of.of.apply(void 0, eventsToSend) : empty/* EMPTY */.E, eventsToExit.length > 0 ? of.of.apply(void 0, eventsToExit).pipe((0,tap/* tap */.b)(function (evt) { - if (typeof evt.onExit === "function") { - evt.onExit(); - } - }), - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - (0,ignoreElements/* ignoreElements */.l)()) : empty/* EMPTY */.E); - } - /** - * This pipe allows to control wether the polling should occur, if there - * are scheduledEvents, or not. - */ - return scheduledEvents$.pipe((0,tap/* tap */.b)(function (scheduledEvents) { - return lastScheduledEvents = scheduledEvents; - }), (0,map/* map */.U)(function (evt) { - return evt.length > 0; - }), distinctUntilChanged(), (0,switchMap/* switchMap */.w)(function (hasEvents) { - if (!hasEvents) { - return empty/* EMPTY */.E; - } - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - STREAM_EVENT_EMITTER_POLL_INTERVAL = _config$getCurrent.STREAM_EVENT_EMITTER_POLL_INTERVAL; - return combineLatest([interval(STREAM_EVENT_EMITTER_POLL_INTERVAL).pipe((0,startWith/* startWith */.O)(null)), observation$]).pipe((0,map/* map */.U)(function (_ref) { - var _ = _ref[0], - observation = _ref[1]; - var seeking = observation.seeking; - return { - isSeeking: seeking, - currentTime: mediaElement.currentTime - }; - }), pairwise(), (0,mergeMap/* mergeMap */.z)(function (_ref2) { - var oldObservation = _ref2[0], - newObservation = _ref2[1]; - return emitStreamEvents$(lastScheduledEvents, oldObservation, newObservation); - })); - })); -} -/* harmony default export */ var stream_events_emitter = (streamEventsEmitter); -;// CONCATENATED MODULE: ./src/core/init/stream_events_emitter/index.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* harmony default export */ var init_stream_events_emitter = (stream_events_emitter); -;// CONCATENATED MODULE: ./src/core/init/load_on_media_source.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - - - - - - - - - - - -/** - * Returns a function allowing to load or reload the content in arguments into - * a single or multiple MediaSources. - * @param {Object} args - * @returns {Function} - */ -function createMediaSourceLoader(_ref) { - var mediaElement = _ref.mediaElement, - manifest = _ref.manifest, - speed = _ref.speed, - bufferOptions = _ref.bufferOptions, - representationEstimator = _ref.representationEstimator, - playbackObserver = _ref.playbackObserver, - segmentFetcherCreator = _ref.segmentFetcherCreator; - /** - * Load the content on the given MediaSource. - * @param {MediaSource} mediaSource - * @param {number} initialTime - * @param {boolean} autoPlay - */ - return function loadContentOnMediaSource(mediaSource, initialTime, autoPlay) { - var _a; - /** Maintains the MediaSource's duration up-to-date with the Manifest */ - var mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource); - var initialPeriod = (_a = manifest.getPeriodForTime(initialTime)) !== null && _a !== void 0 ? _a : manifest.getNextPeriod(initialTime); - if (initialPeriod === undefined) { - var error = new media_error/* default */.Z("MEDIA_STARTING_TIME_NOT_FOUND", "Wanted starting time not found in the Manifest."); - return (0,throwError/* throwError */._)(function () { - return error; - }); - } - /** Interface to create media buffers. */ - var segmentBuffersStore = new segment_buffers(mediaElement, mediaSource); - var _initialSeekAndPlay = (0,initial_seek_and_play/* default */.Z)({ - mediaElement: mediaElement, - playbackObserver: playbackObserver, - startTime: initialTime, - mustAutoPlay: autoPlay - }), - seekAndPlay$ = _initialSeekAndPlay.seekAndPlay$, - initialPlayPerformed = _initialSeekAndPlay.initialPlayPerformed, - initialSeekPerformed = _initialSeekAndPlay.initialSeekPerformed; - var observation$ = playbackObserver.getReference().asObservable(); - var streamEvents$ = initialPlayPerformed.asObservable().pipe((0,filter/* filter */.h)(function (hasPlayed) { - return hasPlayed; - }), (0,mergeMap/* mergeMap */.z)(function () { - return init_stream_events_emitter(manifest, mediaElement, observation$); - })); - var streamObserver = createStreamPlaybackObserver(manifest, playbackObserver, { - autoPlay: autoPlay, - initialPlayPerformed: initialPlayPerformed, - initialSeekPerformed: initialSeekPerformed, - speed: speed, - startTime: initialTime - }); - /** Cancel endOfStream calls when streams become active again. */ - var cancelEndOfStream$ = new Subject/* Subject */.x(); - /** Emits discontinuities detected by the StreamOrchestrator. */ - var discontinuityUpdate$ = new Subject/* Subject */.x(); - /** Emits event when streams are "locked", meaning they cannot load segments. */ - var lockedStream$ = new Subject/* Subject */.x(); - /** Emit each time a new Adaptation is considered by the `StreamOrchestrator`. */ - var lastAdaptationChange = (0,reference/* default */.ZP)(null); - // Creates Observable which will manage every Stream for the given Content. - var streams$ = stream({ - manifest: manifest, - initialPeriod: initialPeriod - }, streamObserver, representationEstimator, segmentBuffersStore, segmentFetcherCreator, bufferOptions).pipe((0,mergeMap/* mergeMap */.z)(function (evt) { - switch (evt.type) { - case "end-of-stream": - log/* default.debug */.Z.debug("Init: end-of-stream order received."); - return maintainEndOfStream(mediaSource).pipe((0,ignoreElements/* ignoreElements */.l)(), (0,takeUntil/* takeUntil */.R)(cancelEndOfStream$)); - case "resume-stream": - log/* default.debug */.Z.debug("Init: resume-stream order received."); - cancelEndOfStream$.next(null); - return empty/* EMPTY */.E; - case "stream-status": - var _evt$value = evt.value, - period = _evt$value.period, - bufferType = _evt$value.bufferType, - imminentDiscontinuity = _evt$value.imminentDiscontinuity, - position = _evt$value.position; - discontinuityUpdate$.next({ - period: period, - bufferType: bufferType, - discontinuity: imminentDiscontinuity, - position: position + eventsToSend.push({ + type: "stream-event", + value: event.publicEvent }); - return empty/* EMPTY */.E; - case "locked-stream": - lockedStream$.next(evt.value); - return empty/* EMPTY */.E; - case "adaptationChange": - lastAdaptationChange.setValue(evt); - return (0,of.of)(evt); - default: - return (0,of.of)(evt); + if (isFiniteStreamEvent(event)) { + eventsToExit.push(event.publicEvent); + } + } } - })); - var contentTimeObserver = ContentTimeBoundariesObserver(manifest, lastAdaptationChange, streamObserver).pipe((0,mergeMap/* mergeMap */.z)(function (evt) { - if (evt.type === "contentDurationUpdate") { - log/* default.debug */.Z.debug("Init: Duration has to be updated.", evt.value); - mediaDurationUpdater.updateKnownDuration(evt.value); - return empty/* EMPTY */.E; + } + if (eventsToSend.length > 0) { + for (var _iterator = stream_events_emitter_createForOfIteratorHelperLoose(eventsToSend), _step; !(_step = _iterator()).done;) { + var _event = _step.value; + if (_event.type === "stream-event") { + onEvent(_event.value); + } else { + onEventSkip(_event.value); + } + if (stopSignal.isCancelled()) { + return; + } } - return (0,of.of)(evt); - })); - /** - * Observable trying to avoid various stalling situations, emitting "stalled" - * events when it cannot, as well as "unstalled" events when it get out of one. - */ - var rebuffer$ = (0,rebuffering_controller/* default */.Z)(playbackObserver, manifest, speed, lockedStream$, discontinuityUpdate$); - /** - * Emit a "loaded" events once the initial play has been performed and the - * media can begin playback. - * Also emits warning events if issues arise when doing so. - */ - var loadingEvts$ = seekAndPlay$.pipe((0,switchMap/* switchMap */.w)(function (evt) { - return evt.type === "warning" ? (0,of.of)(evt) : (0,emit_loaded_event/* default */.Z)(observation$, mediaElement, segmentBuffersStore, false); - })); - return (0,merge/* merge */.T)(loadingEvts$, rebuffer$, streams$, contentTimeObserver, streamEvents$).pipe((0,finalize/* finalize */.x)(function () { - mediaDurationUpdater.stop(); - // clean-up every created SegmentBuffers - segmentBuffersStore.disposeAll(); - })); - }; + } + if (eventsToExit.length > 0) { + for (var _iterator2 = stream_events_emitter_createForOfIteratorHelperLoose(eventsToExit), _step2; !(_step2 = _iterator2()).done;) { + var _event2 = _step2.value; + if (typeof _event2.onExit === "function") { + _event2.onExit(); + } + if (stopSignal.isCancelled()) { + return; + } + } + } + } } -;// CONCATENATED MODULE: ./src/utils/rx-throttle.ts /** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Tells if a stream event has a duration + * @param {Object} evt + * @returns {Boolean} */ - -function throttle(func) { - var isPending = false; - return function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - return new Observable/* Observable */.y(function (obs) { - if (isPending) { - obs.complete(); - return undefined; - } - isPending = true; - var funcSubscription = func.apply(void 0, args).subscribe({ - next: function next(i) { - obs.next(i); - }, - error: function error(e) { - isPending = false; - obs.error(e); - }, - complete: function complete() { - isPending = false; - obs.complete(); - } - }); - return function () { - funcSubscription.unsubscribe(); - isPending = false; - }; - }); - }; +function isFiniteStreamEvent(evt) { + return evt.end !== undefined; } -;// CONCATENATED MODULE: ./src/core/init/manifest_update_scheduler.ts +;// CONCATENATED MODULE: ./src/core/init/utils/stream_events_emitter/index.ts /** * Copyright 2015 CANAL+ Group * @@ -52523,250 +49041,16 @@ function throttle(func) { * limitations under the License. */ +/* harmony default export */ var stream_events_emitter = (streamEventsEmitter); +// EXTERNAL MODULE: ./src/core/init/utils/throw_on_media_error.ts +var throw_on_media_error = __webpack_require__(4576); +;// CONCATENATED MODULE: ./src/core/init/media_source_content_initializer.ts +function media_source_content_initializer_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = media_source_content_initializer_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function media_source_content_initializer_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return media_source_content_initializer_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return media_source_content_initializer_arrayLikeToArray(o, minLen); } +function media_source_content_initializer_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } -/** - * Refresh the Manifest at the right time. - * @param {Object} manifestUpdateSchedulerArguments - * @returns {Observable} - */ -function manifestUpdateScheduler(_ref) { - var initialManifest = _ref.initialManifest, - manifestFetcher = _ref.manifestFetcher, - minimumManifestUpdateInterval = _ref.minimumManifestUpdateInterval, - scheduleRefresh$ = _ref.scheduleRefresh$; - /** - * Fetch and parse the manifest from the URL given. - * Throttled to avoid doing multiple simultaneous requests. - */ - var fetchManifest = throttle(function (manifestURL, options) { - return manifestFetcher.fetch(manifestURL).pipe((0,mergeMap/* mergeMap */.z)(function (response) { - return response.type === "warning" ? (0,of.of)(response) : - // bubble-up warnings - response.parse(options); - }), (0,share/* share */.B)()); - }); - // The Manifest always keeps the same reference - var manifest = initialManifest.manifest; - /** Number of consecutive times the parsing has been done in `unsafeMode`. */ - var consecutiveUnsafeMode = 0; - return (0,defer/* defer */.P)(function () { - return handleManifestRefresh$(initialManifest); - }); - /** - * Performs Manifest refresh (recursively) when it judges it is time to do so. - * @param {Object} manifestRequestInfos - Various information linked to the - * Manifest loading and parsing operations. - * @returns {Observable} - Observable which will automatically refresh the - * Manifest on subscription. Can also emit warnings when minor errors are - * encountered. - */ - function handleManifestRefresh$(_ref2) { - var sendingTime = _ref2.sendingTime, - parsingTime = _ref2.parsingTime, - updatingTime = _ref2.updatingTime; - /** - * Total time taken to fully update the last Manifest, in milliseconds. - * Note: this time also includes possible requests done by the parsers. - */ - var totalUpdateTime = parsingTime !== undefined ? parsingTime + (updatingTime !== null && updatingTime !== void 0 ? updatingTime : 0) : undefined; - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE = _config$getCurrent.MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE, - MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE = _config$getCurrent.MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE; - /** - * "unsafeMode" is a mode where we unlock advanced Manifest parsing - * optimizations with the added risk to lose some information. - * `unsafeModeEnabled` is set to `true` when the `unsafeMode` is enabled. - * - * Only perform parsing in `unsafeMode` when the last full parsing took a - * lot of time and do not go higher than the maximum consecutive time. - */ - var unsafeModeEnabled = consecutiveUnsafeMode > 0 ? consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE : totalUpdateTime !== undefined ? totalUpdateTime >= MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE : false; - /** Time elapsed since the beginning of the Manifest request, in milliseconds. */ - var timeSinceRequest = sendingTime === undefined ? 0 : performance.now() - sendingTime; - /** Minimum update delay we should not go below, in milliseconds. */ - var minInterval = Math.max(minimumManifestUpdateInterval - timeSinceRequest, 0); - /** Emit when the RxPlayer determined that a refresh should be done. */ - var internalRefresh$ = scheduleRefresh$.pipe((0,mergeMap/* mergeMap */.z)(function (_ref3) { - var completeRefresh = _ref3.completeRefresh, - delay = _ref3.delay, - canUseUnsafeMode = _ref3.canUseUnsafeMode; - var unsafeMode = canUseUnsafeMode && unsafeModeEnabled; - return startManualRefreshTimer(delay !== null && delay !== void 0 ? delay : 0, minimumManifestUpdateInterval, sendingTime).pipe((0,map/* map */.U)(function () { - return { - completeRefresh: completeRefresh, - unsafeMode: unsafeMode - }; - })); - })); - /** Emit when the Manifest tells us that it has "expired". */ - var expired$ = manifest.expired === null ? empty/* EMPTY */.E : (0,timer/* timer */.H)(minInterval).pipe((0,mergeMap/* mergeMap */.z)(function () { - return manifest.expired === null ? empty/* EMPTY */.E : (0,from/* from */.D)(manifest.expired); - }), (0,map/* map */.U)(function () { - return { - completeRefresh: true, - unsafeMode: unsafeModeEnabled - }; - })); - /** Emit when the Manifest should normally be refreshed. */ - var autoRefresh$ = createAutoRefreshObservable(); - return (0,merge/* merge */.T)(autoRefresh$, internalRefresh$, expired$).pipe((0,take/* take */.q)(1), (0,mergeMap/* mergeMap */.z)(function (_ref4) { - var completeRefresh = _ref4.completeRefresh, - unsafeMode = _ref4.unsafeMode; - return refreshManifest({ - completeRefresh: completeRefresh, - unsafeMode: unsafeMode - }); - }), (0,mergeMap/* mergeMap */.z)(function (evt) { - if (evt.type === "warning") { - return (0,of.of)(evt); - } - return handleManifestRefresh$(evt); - })); - /** - * Create an Observable that will emit when the Manifest needs to be - * refreshed according to the Manifest's internal properties (parsing - * time is also taken into account in this operation to avoid refreshing too - * often). - * @returns {Observable} - */ - function createAutoRefreshObservable() { - if (manifest.lifetime === undefined || manifest.lifetime < 0) { - return empty/* EMPTY */.E; - } - /** Regular refresh delay as asked by the Manifest. */ - var regularRefreshDelay = manifest.lifetime * 1000 - timeSinceRequest; - /** Actually choosen delay to refresh the Manifest. */ - var actualRefreshInterval; - if (totalUpdateTime === undefined) { - actualRefreshInterval = regularRefreshDelay; - } else if (manifest.lifetime < 3 && totalUpdateTime >= 100) { - // If Manifest update is very frequent and we take time to update it, - // postpone it. - actualRefreshInterval = Math.min(Math.max( - // Take 3 seconds as a default safe value for a base interval. - 3000 - timeSinceRequest, - // Add update time to the original interval. - Math.max(regularRefreshDelay, 0) + totalUpdateTime), - // Limit the postponment's higher bound to a very high value relative - // to `regularRefreshDelay`. - // This avoid perpetually postponing a Manifest update when - // performance seems to have been abysmal one time. - regularRefreshDelay * 6); - log/* default.info */.Z.info("MUS: Manifest update rythm is too frequent. Postponing next request.", regularRefreshDelay, actualRefreshInterval); - } else if (totalUpdateTime >= manifest.lifetime * 1000 / 10) { - // If Manifest updating time is very long relative to its lifetime, - // postpone it: - actualRefreshInterval = Math.min( - // Just add the update time to the original waiting time - Math.max(regularRefreshDelay, 0) + totalUpdateTime, - // Limit the postponment's higher bound to a very high value relative - // to `regularRefreshDelay`. - // This avoid perpetually postponing a Manifest update when - // performance seems to have been abysmal one time. - regularRefreshDelay * 6); - log/* default.info */.Z.info("MUS: Manifest took too long to parse. Postponing next request", actualRefreshInterval, actualRefreshInterval); - } else { - actualRefreshInterval = regularRefreshDelay; - } - return (0,timer/* timer */.H)(Math.max(actualRefreshInterval, minInterval)).pipe((0,map/* map */.U)(function () { - return { - completeRefresh: false, - unsafeMode: unsafeModeEnabled - }; - })); - } - } - /** - * Refresh the Manifest. - * Perform a full update if a partial update failed. - * @param {boolean} completeRefresh - * @returns {Observable} - */ - function refreshManifest(_ref5) { - var completeRefresh = _ref5.completeRefresh, - unsafeMode = _ref5.unsafeMode; - var manifestUpdateUrl = manifest.updateUrl; - var fullRefresh = completeRefresh || manifestUpdateUrl === undefined; - var refreshURL = fullRefresh ? manifest.getUrl() : manifestUpdateUrl; - var externalClockOffset = manifest.clockOffset; - if (unsafeMode) { - consecutiveUnsafeMode += 1; - log/* default.info */.Z.info("Init: Refreshing the Manifest in \"unsafeMode\" for the " + String(consecutiveUnsafeMode) + " consecutive time."); - } else if (consecutiveUnsafeMode > 0) { - log/* default.info */.Z.info("Init: Not parsing the Manifest in \"unsafeMode\" anymore after " + String(consecutiveUnsafeMode) + " consecutive times."); - consecutiveUnsafeMode = 0; - } - return fetchManifest(refreshURL, { - externalClockOffset: externalClockOffset, - previousManifest: manifest, - unsafeMode: unsafeMode - }).pipe((0,mergeMap/* mergeMap */.z)(function (value) { - if (value.type === "warning") { - return (0,of.of)(value); - } - var newManifest = value.manifest, - newSendingTime = value.sendingTime, - receivedTime = value.receivedTime, - parsingTime = value.parsingTime; - var updateTimeStart = performance.now(); - if (fullRefresh) { - manifest.replace(newManifest); - } else { - try { - manifest.update(newManifest); - } catch (e) { - var message = e instanceof Error ? e.message : "unknown error"; - log/* default.warn */.Z.warn("MUS: Attempt to update Manifest failed: " + message, "Re-downloading the Manifest fully"); - var _config$getCurrent2 = config/* default.getCurrent */.Z.getCurrent(), - FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY = _config$getCurrent2.FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY; - return startManualRefreshTimer(FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY, minimumManifestUpdateInterval, newSendingTime).pipe((0,mergeMap/* mergeMap */.z)(function () { - return refreshManifest({ - completeRefresh: true, - unsafeMode: false - }); - })); - } - } - return (0,of.of)({ - type: "parsed", - manifest: manifest, - sendingTime: newSendingTime, - receivedTime: receivedTime, - parsingTime: parsingTime, - updatingTime: performance.now() - updateTimeStart - }); - })); - } -} -/** - * Launch a timer Observable which will emit when it is time to refresh the - * Manifest. - * The timer's delay is calculated from: - * - a target delay (`wantedDelay`), which is the minimum time we want to wait - * in the best scenario - * - the minimum set possible interval between manifest updates - * (`minimumManifestUpdateInterval`) - * - the time at which was done the last Manifest refresh - * (`lastManifestRequestTime`) - * @param {number} wantedDelay - * @param {number} minimumManifestUpdateInterval - * @param {number|undefined} lastManifestRequestTime - * @returns {Observable} - */ -function startManualRefreshTimer(wantedDelay, minimumManifestUpdateInterval, lastManifestRequestTime) { - return (0,defer/* defer */.P)(function () { - // The value allows to set a delay relatively to the last Manifest refresh - // (to avoid asking for it too often). - var timeSinceLastRefresh = lastManifestRequestTime === undefined ? 0 : performance.now() - lastManifestRequestTime; - var _minInterval = Math.max(minimumManifestUpdateInterval - timeSinceLastRefresh, 0); - return (0,timer/* timer */.H)(Math.max(wantedDelay - timeSinceLastRefresh, _minInterval)); - }); -} -// EXTERNAL MODULE: ./src/core/init/throw_on_media_error.ts -var throw_on_media_error = __webpack_require__(2447); -;// CONCATENATED MODULE: ./src/core/init/initialize_media_source.ts /** * Copyright 2015 CANAL+ Group * @@ -52794,6 +49078,14 @@ var throw_on_media_error = __webpack_require__(2447); + + + + + + + + @@ -52802,227 +49094,583 @@ var throw_on_media_error = __webpack_require__(2447); /** - * Begin content playback. - * - * Returns an Observable emitting notifications about the content lifecycle. - * On subscription, it will perform every necessary tasks so the content can - * play. Among them: - * - * - Creates a MediaSource on the given `mediaElement` and attach to it the - * necessary SourceBuffer instances. - * - * - download the content's Manifest and handle its refresh logic - * - * - Perform decryption if needed + * Allows to load a new content thanks to the MediaSource Extensions (a.k.a. MSE) + * Web APIs. * - * - ask for the choice of the wanted Adaptation through events (e.g. to - * choose a language) + * Through this `ContentInitializer`, a Manifest will be fetched (and depending + * on the situation, refreshed), a `MediaSource` instance will be linked to the + * wanted `HTMLMediaElement` and chunks of media data, called segments, will be + * pushed on buffers associated to this `MediaSource` instance. * - * - requests and push the right segments (according to the Adaptation choice, - * the current position, the network conditions etc.) - * - * This Observable will throw in the case where a fatal error (i.e. which has - * stopped content playback) is encountered, with the corresponding error as a - * payload. - * - * This Observable will never complete, it will always run until it is - * unsubscribed from. - * Unsubscription will stop playback and reset the corresponding state. - * - * @param {Object} args - * @returns {Observable} + * @class MediaSourceContentInitializer */ -function InitializeOnMediaSource(_ref) { - var adaptiveOptions = _ref.adaptiveOptions, - autoPlay = _ref.autoPlay, - bufferOptions = _ref.bufferOptions, - keySystems = _ref.keySystems, - lowLatencyMode = _ref.lowLatencyMode, - manifest$ = _ref.manifest$, - manifestFetcher = _ref.manifestFetcher, - mediaElement = _ref.mediaElement, - minimumManifestUpdateInterval = _ref.minimumManifestUpdateInterval, - playbackObserver = _ref.playbackObserver, - segmentRequestOptions = _ref.segmentRequestOptions, - speed = _ref.speed, - startAt = _ref.startAt, - transport = _ref.transport, - textTrackOptions = _ref.textTrackOptions; - /** Choose the right "Representation" for a given "Adaptation". */ - var representationEstimator = adaptive(adaptiveOptions); - var playbackCanceller = new task_canceller/* default */.ZP(); - /** - * Create and open a new MediaSource object on the given media element on - * subscription. - * Multiple concurrent subscriptions on this Observable will obtain the same - * created MediaSource. - * The MediaSource will be closed when subscriptions are down to 0. - */ - var openMediaSource$ = openMediaSource(mediaElement).pipe((0,shareReplay/* shareReplay */.d)({ - refCount: true - })); - /** Send content protection initialization data. */ - var protectedSegments$ = new Subject/* Subject */.x(); - /** Initialize decryption capabilities and MediaSource. */ - var drmEvents$ = (0,link_drm_and_content/* default */.Z)(mediaElement, keySystems, protectedSegments$, openMediaSource$).pipe( - // Because multiple Observables here depend on this Observable as a source, - // we prefer deferring Subscription until those Observables are themselves - // all subscribed to. - // This is needed because `drmEvents$` might send events synchronously - // on subscription. In that case, it might communicate those events directly - // after the first Subscription is done, making the next subscription miss - // out on those events, even if that second subscription is done - // synchronously after the first one. - // By calling `deferSubscriptions`, we ensure that subscription to - // `drmEvents$` effectively starts after a very short delay, thus - // ensuring that no such race condition can occur. - (0,defer_subscriptions/* default */.Z)(), (0,share/* share */.B)()); - /** - * Translate errors coming from the media element into RxPlayer errors - * through a throwing Observable. - */ - var mediaError$ = (0,throw_on_media_error/* default */.Z)(mediaElement); - var mediaSourceReady$ = drmEvents$.pipe((0,filter/* filter */.h)(function (evt) { - return evt.type === "decryption-ready" || evt.type === "decryption-disabled"; - }), (0,map/* map */.U)(function (e) { - return e.value; - }), (0,take/* take */.q)(1)); - /** Load and play the content asked. */ - var loadContent$ = combineLatest([manifest$, mediaSourceReady$]).pipe((0,mergeMap/* mergeMap */.z)(function (_ref2) { - var manifestEvt = _ref2[0], - _ref2$ = _ref2[1], - drmSystemId = _ref2$.drmSystemId, - initialMediaSource = _ref2$.mediaSource; - if (manifestEvt.type === "warning") { - return (0,of.of)(manifestEvt); - } - var manifest = manifestEvt.manifest; - log/* default.debug */.Z.debug("Init: Calculating initial time"); - var initialTime = getInitialTime(manifest, lowLatencyMode, startAt); - log/* default.debug */.Z.debug("Init: Initial time calculated:", initialTime); - var requestOptions = { - lowLatencyMode: lowLatencyMode, - requestTimeout: segmentRequestOptions.requestTimeout, - maxRetryRegular: segmentRequestOptions.regularError, - maxRetryOffline: segmentRequestOptions.offlineError - }; - var segmentFetcherCreator = new segment(transport, requestOptions, playbackCanceller.signal); - var mediaSourceLoader = createMediaSourceLoader({ - bufferOptions: (0,object_assign/* default */.Z)({ - textTrackOptions: textTrackOptions, - drmSystemId: drmSystemId - }, bufferOptions), - manifest: manifest, - mediaElement: mediaElement, - playbackObserver: playbackObserver, - representationEstimator: representationEstimator, - segmentFetcherCreator: segmentFetcherCreator, - speed: speed +var MediaSourceContentInitializer = /*#__PURE__*/function (_ContentInitializer) { + (0,inheritsLoose/* default */.Z)(MediaSourceContentInitializer, _ContentInitializer); + /** + * Create a new `MediaSourceContentInitializer`, associated to the given + * settings. + * @param {Object} settings + */ + function MediaSourceContentInitializer(settings) { + var _this; + _this = _ContentInitializer.call(this) || this; + _this._settings = settings; + _this._initCanceller = new task_canceller/* default */.ZP(); + _this._initialManifestProm = null; + var urls = settings.url === undefined ? undefined : [settings.url]; + _this._manifestFetcher = new fetchers_manifest(urls, settings.transport, settings.manifestRequestSettings); + return _this; + } + /** + * Perform non-destructive preparation steps, to prepare a future content. + * For now, this mainly mean loading the Manifest document. + */ + var _proto = MediaSourceContentInitializer.prototype; + _proto.prepare = function prepare() { + var _this2 = this; + if (this._initialManifestProm !== null) { + return; + } + this._initialManifestProm = (0,create_cancellable_promise/* default */.Z)(this._initCanceller.signal, function (res, rej) { + _this2._manifestFetcher.addEventListener("warning", function (err) { + return _this2.trigger("warning", err); + }); + _this2._manifestFetcher.addEventListener("error", function (err) { + _this2.trigger("error", err); + rej(err); + }); + _this2._manifestFetcher.addEventListener("manifestReady", function (manifest) { + res(manifest); + }); + }); + this._manifestFetcher.start(); + this._initCanceller.signal.register(function () { + _this2._manifestFetcher.dispose(); + }); + } + /** + * @param {HTMLMediaElement} mediaElement + * @param {Object} playbackObserver + */; + _proto.start = function start(mediaElement, playbackObserver) { + var _this3 = this; + this.prepare(); // Load Manifest if not already done + /** Translate errors coming from the media element into RxPlayer errors. */ + (0,throw_on_media_error/* default */.Z)(mediaElement, function (error) { + return _this3._onFatalError(error); + }, this._initCanceller.signal); + /** Send content protection initialization data to the decryption logic. */ + var protectionRef = (0,reference/* default */.ZP)(null, this._initCanceller.signal); + this._initializeMediaSourceAndDecryption(mediaElement, protectionRef).then(function (initResult) { + return _this3._onInitialMediaSourceReady(mediaElement, initResult.mediaSource, playbackObserver, initResult.drmSystemId, protectionRef, initResult.unlinkMediaSource); + })["catch"](function (err) { + _this3._onFatalError(err); }); - // handle initial load and reloads - var recursiveLoad$ = recursivelyLoadOnMediaSource(initialMediaSource, initialTime, autoPlay); - // Emit when we want to manually update the manifest. - var scheduleRefresh$ = new Subject/* Subject */.x(); - var manifestUpdate$ = manifestUpdateScheduler({ - initialManifest: manifestEvt, - manifestFetcher: manifestFetcher, - minimumManifestUpdateInterval: minimumManifestUpdateInterval, - scheduleRefresh$: scheduleRefresh$ + } + /** + * Update URL of the Manifest. + * @param {Array.|undefined} urls - URLs to reach that Manifest from + * the most prioritized URL to the least prioritized URL. + * @param {boolean} refreshNow - If `true` the resource in question (e.g. + * DASH's MPD) will be refreshed immediately. + */; + _proto.updateContentUrls = function updateContentUrls(urls, refreshNow) { + this._manifestFetcher.updateContentUrls(urls, refreshNow); + }; + _proto.dispose = function dispose() { + this._initCanceller.cancel(); + }; + _proto._onFatalError = function _onFatalError(err) { + if (this._initCanceller.isUsed()) { + return; + } + this._initCanceller.cancel(); + this.trigger("error", err); + }; + _proto._initializeMediaSourceAndDecryption = function _initializeMediaSourceAndDecryption(mediaElement, protectionRef) { + var _this4 = this; + var initCanceller = this._initCanceller; + return (0,create_cancellable_promise/* default */.Z)(initCanceller.signal, function (resolve) { + var keySystems = _this4._settings.keySystems; + /** Initialize decryption capabilities. */ + var drmInitRef = (0,initialize_content_decryption/* default */.Z)(mediaElement, keySystems, protectionRef, { + onWarning: function onWarning(err) { + return _this4.trigger("warning", err); + }, + onError: function onError(err) { + return _this4._onFatalError(err); + } + }, initCanceller.signal); + drmInitRef.onUpdate(function (drmStatus, stopListeningToDrmUpdates) { + if (drmStatus.initializationState.type === "uninitialized") { + return; + } + stopListeningToDrmUpdates(); + var mediaSourceCanceller = new task_canceller/* default */.ZP(); + mediaSourceCanceller.linkToSignal(initCanceller.signal); + openMediaSource(mediaElement, mediaSourceCanceller.signal).then(function (mediaSource) { + var lastDrmStatus = drmInitRef.getValue(); + if (lastDrmStatus.initializationState.type === "awaiting-media-link") { + lastDrmStatus.initializationState.value.isMediaLinked.setValue(true); + drmInitRef.onUpdate(function (newDrmStatus, stopListeningToDrmUpdatesAgain) { + if (newDrmStatus.initializationState.type === "initialized") { + stopListeningToDrmUpdatesAgain(); + resolve({ + mediaSource: mediaSource, + drmSystemId: newDrmStatus.drmSystemId, + unlinkMediaSource: mediaSourceCanceller + }); + return; + } + }, { + emitCurrentValue: true, + clearSignal: initCanceller.signal + }); + } else if (drmStatus.initializationState.type === "initialized") { + resolve({ + mediaSource: mediaSource, + drmSystemId: drmStatus.drmSystemId, + unlinkMediaSource: mediaSourceCanceller + }); + return; + } + })["catch"](function (err) { + if (mediaSourceCanceller.isUsed()) { + return; + } + _this4._onFatalError(err); + }); + }, { + emitCurrentValue: true, + clearSignal: initCanceller.signal + }); }); - var manifestEvents$ = (0,merge/* merge */.T)((0,event_emitter/* fromEvent */.R)(manifest, "manifestUpdate").pipe((0,map/* map */.U)(function () { - return events_generators/* default.manifestUpdate */.Z.manifestUpdate(); - })), (0,event_emitter/* fromEvent */.R)(manifest, "decipherabilityUpdate").pipe((0,map/* map */.U)(events_generators/* default.decipherabilityUpdate */.Z.decipherabilityUpdate))); - return (0,merge/* merge */.T)(manifestEvents$, manifestUpdate$, recursiveLoad$).pipe((0,startWith/* startWith */.O)(events_generators/* default.manifestReady */.Z.manifestReady(manifest)), (0,finalize/* finalize */.x)(function () { - scheduleRefresh$.complete(); + }; + _proto._onInitialMediaSourceReady = /*#__PURE__*/function () { + var _onInitialMediaSourceReady2 = (0,asyncToGenerator/* default */.Z)( /*#__PURE__*/regenerator_default().mark(function _callee(mediaElement, initialMediaSource, playbackObserver, drmSystemId, protectionRef, initialMediaSourceCanceller) { + var _this5 = this; + var _this$_settings, adaptiveOptions, autoPlay, bufferOptions, lowLatencyMode, segmentRequestOptions, speed, startAt, textTrackOptions, transport, initCanceller, manifestProm, manifest, initialTime, representationEstimator, subBufferOptions, segmentFetcherCreator, bufferOnMediaSource, triggerEvent, onFatalError, recursivelyLoadOnMediaSource; + return regenerator_default().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + recursivelyLoadOnMediaSource = function _recursivelyLoadOnMed(mediaSource, startingPos, shouldPlay, currentCanceller) { + var opts = { + mediaElement: mediaElement, + playbackObserver: playbackObserver, + mediaSource: mediaSource, + initialTime: startingPos, + autoPlay: shouldPlay, + manifest: manifest, + representationEstimator: representationEstimator, + segmentFetcherCreator: segmentFetcherCreator, + speed: speed, + protectionRef: protectionRef, + bufferOptions: subBufferOptions + }; + bufferOnMediaSource(opts, onReloadMediaSource, currentCanceller.signal); + function onReloadMediaSource(reloadOrder) { + currentCanceller.cancel(); + if (initCanceller.isUsed()) { + return; + } + triggerEvent("reloadingMediaSource", null); + if (initCanceller.isUsed()) { + return; + } + var newCanceller = new task_canceller/* default */.ZP(); + newCanceller.linkToSignal(initCanceller.signal); + openMediaSource(mediaElement, newCanceller.signal).then(function (newMediaSource) { + recursivelyLoadOnMediaSource(newMediaSource, reloadOrder.position, reloadOrder.autoPlay, newCanceller); + })["catch"](function (err) { + if (newCanceller.isUsed()) { + return; + } + onFatalError(err); + }); + } + }; + _this$_settings = this._settings, adaptiveOptions = _this$_settings.adaptiveOptions, autoPlay = _this$_settings.autoPlay, bufferOptions = _this$_settings.bufferOptions, lowLatencyMode = _this$_settings.lowLatencyMode, segmentRequestOptions = _this$_settings.segmentRequestOptions, speed = _this$_settings.speed, startAt = _this$_settings.startAt, textTrackOptions = _this$_settings.textTrackOptions, transport = _this$_settings.transport; + initCanceller = this._initCanceller; + (0,assert/* default */.Z)(this._initialManifestProm !== null); + manifestProm = this._initialManifestProm; + _context.prev = 5; + _context.next = 8; + return manifestProm; + case 8: + manifest = _context.sent; + _context.next = 14; + break; + case 11: + _context.prev = 11; + _context.t0 = _context["catch"](5); + return _context.abrupt("return"); + case 14: + manifest.addEventListener("manifestUpdate", function () { + _this5.trigger("manifestUpdate", null); + }, initCanceller.signal); + manifest.addEventListener("decipherabilityUpdate", function (args) { + _this5.trigger("decipherabilityUpdate", args); + }, initCanceller.signal); + log/* default.debug */.Z.debug("Init: Calculating initial time"); + initialTime = getInitialTime(manifest, lowLatencyMode, startAt); + log/* default.debug */.Z.debug("Init: Initial time calculated:", initialTime); + /** Choose the right "Representation" for a given "Adaptation". */ + representationEstimator = adaptive(adaptiveOptions); + subBufferOptions = (0,object_assign/* default */.Z)({ + textTrackOptions: textTrackOptions, + drmSystemId: drmSystemId + }, bufferOptions); + segmentFetcherCreator = new segment(transport, segmentRequestOptions, initCanceller.signal); + this.trigger("manifestReady", manifest); + if (!initCanceller.isUsed()) { + _context.next = 25; + break; + } + return _context.abrupt("return"); + case 25: + bufferOnMediaSource = this._startBufferingOnMediaSource.bind(this); + triggerEvent = this.trigger.bind(this); + onFatalError = this._onFatalError.bind(this); // handle initial load and reloads + recursivelyLoadOnMediaSource(initialMediaSource, initialTime, autoPlay, initialMediaSourceCanceller); + /** + * Load the content defined by the Manifest in the mediaSource given at the + * given position and playing status. + * This function recursively re-call itself when a MediaSource reload is + * wanted. + * @param {MediaSource} mediaSource + * @param {number} startingPos + * @param {Object} currentCanceller + * @param {boolean} shouldPlay + */ + case 29: + case "end": + return _context.stop(); + } + } + }, _callee, this, [[5, 11]]); })); + function _onInitialMediaSourceReady(_x, _x2, _x3, _x4, _x5, _x6) { + return _onInitialMediaSourceReady2.apply(this, arguments); + } + return _onInitialMediaSourceReady; + }() + /** + * Buffer the content on the given MediaSource. + * @param {Object} args + * @param {function} onReloadOrder + * @param {Object} cancelSignal + */ + ; + _proto._startBufferingOnMediaSource = function _startBufferingOnMediaSource(args, onReloadOrder, cancelSignal) { + var _this6 = this; + var _a; + var autoPlay = args.autoPlay, + bufferOptions = args.bufferOptions, + initialTime = args.initialTime, + manifest = args.manifest, + mediaElement = args.mediaElement, + mediaSource = args.mediaSource, + playbackObserver = args.playbackObserver, + protectionRef = args.protectionRef, + representationEstimator = args.representationEstimator, + segmentFetcherCreator = args.segmentFetcherCreator, + speed = args.speed; + var initialPeriod = (_a = manifest.getPeriodForTime(initialTime)) !== null && _a !== void 0 ? _a : manifest.getNextPeriod(initialTime); + if (initialPeriod === undefined) { + var error = new media_error/* default */.Z("MEDIA_STARTING_TIME_NOT_FOUND", "Wanted starting time not found in the Manifest."); + return this._onFatalError(error); + } + /** Interface to create media buffers. */ + var segmentBuffersStore = new segment_buffers(mediaElement, mediaSource); + cancelSignal.register(function () { + segmentBuffersStore.disposeAll(); + }); + var _performInitialSeekAn = (0,initial_seek_and_play/* default */.Z)(mediaElement, playbackObserver, initialTime, autoPlay, function (err) { + return _this6.trigger("warning", err); + }, cancelSignal), + autoPlayResult = _performInitialSeekAn.autoPlayResult, + initialPlayPerformed = _performInitialSeekAn.initialPlayPerformed, + initialSeekPerformed = _performInitialSeekAn.initialSeekPerformed; + if (cancelSignal.isCancelled()) { + return; + } + initialPlayPerformed.onUpdate(function (isPerformed, stopListening) { + if (isPerformed) { + stopListening(); + stream_events_emitter(manifest, mediaElement, playbackObserver, function (evt) { + return _this6.trigger("streamEvent", evt); + }, function (evt) { + return _this6.trigger("streamEventSkip", evt); + }, cancelSignal); + } + }, { + clearSignal: cancelSignal, + emitCurrentValue: true + }); + var streamObserver = createStreamPlaybackObserver(manifest, playbackObserver, { + autoPlay: autoPlay, + initialPlayPerformed: initialPlayPerformed, + initialSeekPerformed: initialSeekPerformed, + speed: speed, + startTime: initialTime + }); + var rebufferingController = this._createRebufferingController(playbackObserver, manifest, speed, cancelSignal); + var contentTimeBoundariesObserver = this._createContentTimeBoundariesObserver(manifest, mediaSource, streamObserver, segmentBuffersStore, cancelSignal); + /** + * Emit a "loaded" events once the initial play has been performed and the + * media can begin playback. + * Also emits warning events if issues arise when doing so. + */ + autoPlayResult.then(function () { + (0,get_loaded_reference/* default */.Z)(playbackObserver, mediaElement, false, cancelSignal).onUpdate(function (isLoaded, stopListening) { + if (isLoaded) { + stopListening(); + _this6.trigger("loaded", { + segmentBuffersStore: segmentBuffersStore + }); + } + }, { + emitCurrentValue: true, + clearSignal: cancelSignal + }); + })["catch"](function (err) { + if (cancelSignal.isCancelled()) { + return; // Current loading cancelled, no need to trigger the error + } + + _this6._onFatalError(err); + }); + /* eslint-disable-next-line @typescript-eslint/no-this-alias */ + var self = this; + stream({ + manifest: manifest, + initialPeriod: initialPeriod + }, streamObserver, representationEstimator, segmentBuffersStore, segmentFetcherCreator, bufferOptions, handleStreamOrchestratorCallbacks(), cancelSignal); /** - * Load the content defined by the Manifest in the mediaSource given at the - * given position and playing status. - * This function recursively re-call itself when a MediaSource reload is - * wanted. - * @param {MediaSource} mediaSource - * @param {number} startingPos - * @param {boolean} shouldPlay - * @returns {Observable} + * Returns Object handling the callbacks from a `StreamOrchestrator`, which + * are basically how it communicates about events. + * @returns {Object} */ - function recursivelyLoadOnMediaSource(mediaSource, startingPos, shouldPlay) { - var reloadMediaSource$ = new Subject/* Subject */.x(); - var mediaSourceLoader$ = mediaSourceLoader(mediaSource, startingPos, shouldPlay).pipe((0,filter_map/* default */.Z)(function (evt) { - switch (evt.type) { - case "needs-manifest-refresh": - scheduleRefresh$.next({ - completeRefresh: false, - canUseUnsafeMode: true - }); - return null; - case "manifest-might-be-out-of-sync": - var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), - OUT_OF_SYNC_MANIFEST_REFRESH_DELAY = _config$getCurrent.OUT_OF_SYNC_MANIFEST_REFRESH_DELAY; - scheduleRefresh$.next({ - completeRefresh: true, - canUseUnsafeMode: false, - delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY - }); - return null; - case "needs-media-source-reload": - reloadMediaSource$.next(evt.value); - return null; - case "needs-decipherability-flush": - var keySystem = get_current_key_system_getCurrentKeySystem(mediaElement); - if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem)) { - reloadMediaSource$.next(evt.value); - return null; + function handleStreamOrchestratorCallbacks() { + return { + needsBufferFlush: function needsBufferFlush() { + return playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001); + }, + streamStatusUpdate: function streamStatusUpdate(value) { + // Announce discontinuities if found + var period = value.period, + bufferType = value.bufferType, + imminentDiscontinuity = value.imminentDiscontinuity, + position = value.position; + rebufferingController.updateDiscontinuityInfo({ + period: period, + bufferType: bufferType, + discontinuity: imminentDiscontinuity, + position: position + }); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + // If the status for the last Period indicates that segments are all loaded + // or on the contrary that the loading resumed, announce it to the + // ContentTimeBoundariesObserver. + if (manifest.isLastPeriodKnown && value.period.id === manifest.periods[manifest.periods.length - 1].id) { + var hasFinishedLoadingLastPeriod = value.hasFinishedLoading || value.isEmptyStream; + if (hasFinishedLoadingLastPeriod) { + contentTimeBoundariesObserver.onLastSegmentFinishedLoading(value.bufferType); + } else { + contentTimeBoundariesObserver.onLastSegmentLoadingResume(value.bufferType); } + } + }, + needsManifestRefresh: function needsManifestRefresh() { + return self._manifestFetcher.scheduleManualRefresh({ + enablePartialRefresh: true, + canUseUnsafeMode: true + }); + }, + manifestMightBeOufOfSync: function manifestMightBeOufOfSync() { + var _config$getCurrent = config/* default.getCurrent */.Z.getCurrent(), + OUT_OF_SYNC_MANIFEST_REFRESH_DELAY = _config$getCurrent.OUT_OF_SYNC_MANIFEST_REFRESH_DELAY; + self._manifestFetcher.scheduleManualRefresh({ + enablePartialRefresh: false, + canUseUnsafeMode: false, + delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY + }); + }, + lockedStream: function lockedStream(value) { + return rebufferingController.onLockedStream(value.bufferType, value.period); + }, + adaptationChange: function adaptationChange(value) { + self.trigger("adaptationChange", value); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + + contentTimeBoundariesObserver.onAdaptationChange(value.type, value.period, value.adaptation); + }, + representationChange: function representationChange(value) { + self.trigger("representationChange", value); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + + contentTimeBoundariesObserver.onRepresentationChange(value.type, value.period); + }, + inbandEvent: function inbandEvent(value) { + return self.trigger("inbandEvents", value); + }, + warning: function warning(value) { + return self.trigger("warning", value); + }, + periodStreamReady: function periodStreamReady(value) { + return self.trigger("periodStreamReady", value); + }, + periodStreamCleared: function periodStreamCleared(value) { + contentTimeBoundariesObserver.onPeriodCleared(value.type, value.period); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + + self.trigger("periodStreamCleared", value); + }, + bitrateEstimationChange: function bitrateEstimationChange(value) { + return self.trigger("bitrateEstimationChange", value); + }, + addedSegment: function addedSegment(value) { + return self.trigger("addedSegment", value); + }, + needsMediaSourceReload: function needsMediaSourceReload(value) { + return onReloadOrder(value); + }, + needsDecipherabilityFlush: function needsDecipherabilityFlush(value) { + var keySystem = get_key_system_configuration_getKeySystemConfiguration(mediaElement); + if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem === null || keySystem === void 0 ? void 0 : keySystem[0])) { + onReloadOrder(value); + } else { // simple seek close to the current position // to flush the buffers - var position = evt.value.position; - if (position + 0.001 < evt.value.duration) { + if (value.position + 0.001 < value.duration) { playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001); } else { - playbackObserver.setCurrentTime(position); + playbackObserver.setCurrentTime(value.position); } - return null; - case "encryption-data-encountered": - protectedSegments$.next(evt.value); - return null; - case "needs-buffer-flush": - playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001); - return null; + } + }, + encryptionDataEncountered: function encryptionDataEncountered(value) { + for (var _iterator = media_source_content_initializer_createForOfIteratorHelperLoose(value), _step; !(_step = _iterator()).done;) { + var protectionData = _step.value; + protectionRef.setValue(protectionData); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + } + }, + + error: function error(err) { + return self._onFatalError(err); } - return evt; - }, null)); - var currentLoad$ = mediaSourceLoader$.pipe((0,takeUntil/* takeUntil */.R)(reloadMediaSource$)); - var handleReloads$ = reloadMediaSource$.pipe((0,switchMap/* switchMap */.w)(function (reloadOrder) { - return openMediaSource(mediaElement).pipe((0,mergeMap/* mergeMap */.z)(function (newMS) { - return recursivelyLoadOnMediaSource(newMS, reloadOrder.position, reloadOrder.autoPlay); - }), (0,startWith/* startWith */.O)(events_generators/* default.reloadingMediaSource */.Z.reloadingMediaSource())); - })); - return (0,merge/* merge */.T)(handleReloads$, currentLoad$); + }; } - })); - return (0,merge/* merge */.T)(loadContent$, mediaError$, drmEvents$.pipe((0,ignoreElements/* ignoreElements */.l)())).pipe((0,finalize/* finalize */.x)(function () { - playbackCanceller.cancel(); - })); -} -;// CONCATENATED MODULE: ./src/core/init/index.ts -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - + } + /** + * Creates a `ContentTimeBoundariesObserver`, a class indicating various + * events related to media time (such as duration updates, period changes, + * warnings about being out of the Manifest time boundaries or "endOfStream" + * management), handle those events and returns the class. + * + * Various methods from that class need then to be called at various events + * (see `ContentTimeBoundariesObserver`). + * @param {Object} manifest + * @param {MediaSource} mediaSource + * @param {Object} streamObserver + * @param {Object} segmentBuffersStore + * @param {Object} cancelSignal + * @returns {Object} + */; + _proto._createContentTimeBoundariesObserver = function _createContentTimeBoundariesObserver(manifest, mediaSource, streamObserver, segmentBuffersStore, cancelSignal) { + var _this7 = this; + /** Maintains the MediaSource's duration up-to-date with the Manifest */ + var mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource); + cancelSignal.register(function () { + mediaDurationUpdater.stop(); + }); + /** Allows to cancel a pending `end-of-stream` operation. */ + var endOfStreamCanceller = null; + var contentTimeBoundariesObserver = new ContentTimeBoundariesObserver(manifest, streamObserver, segmentBuffersStore.getBufferTypes()); + cancelSignal.register(function () { + contentTimeBoundariesObserver.dispose(); + }); + contentTimeBoundariesObserver.addEventListener("warning", function (err) { + return _this7.trigger("warning", err); + }); + contentTimeBoundariesObserver.addEventListener("periodChange", function (period) { + _this7.trigger("activePeriodChanged", { + period: period + }); + }); + contentTimeBoundariesObserver.addEventListener("durationUpdate", function (newDuration) { + log/* default.debug */.Z.debug("Init: Duration has to be updated.", newDuration); + mediaDurationUpdater.updateKnownDuration(newDuration); + }); + contentTimeBoundariesObserver.addEventListener("endOfStream", function () { + if (endOfStreamCanceller === null) { + endOfStreamCanceller = new task_canceller/* default */.ZP(); + endOfStreamCanceller.linkToSignal(cancelSignal); + log/* default.debug */.Z.debug("Init: end-of-stream order received."); + maintainEndOfStream(mediaSource, endOfStreamCanceller.signal); + } + }); + contentTimeBoundariesObserver.addEventListener("resumeStream", function () { + if (endOfStreamCanceller !== null) { + log/* default.debug */.Z.debug("Init: resume-stream order received."); + endOfStreamCanceller.cancel(); + endOfStreamCanceller = null; + } + }); + return contentTimeBoundariesObserver; + } + /** + * Creates a `RebufferingController`, a class trying to avoid various stalling + * situations (such as rebuffering periods), and returns it. + * + * Various methods from that class need then to be called at various events + * (see `RebufferingController` definition). + * + * This function also handles the `RebufferingController`'s events: + * - emit "stalled" events when stalling situations cannot be prevented, + * - emit "unstalled" events when we could get out of one, + * - emit "warning" on various rebuffering-related minor issues + * like discontinuity skipping. + * @param {Object} playbackObserver + * @param {Object} manifest + * @param {Object} speed + * @param {Object} cancelSignal + * @returns {Object} + */; + _proto._createRebufferingController = function _createRebufferingController(playbackObserver, manifest, speed, cancelSignal) { + var _this8 = this; + var rebufferingController = new rebuffering_controller/* default */.Z(playbackObserver, manifest, speed); + // Bubble-up events + rebufferingController.addEventListener("stalled", function (evt) { + return _this8.trigger("stalled", evt); + }); + rebufferingController.addEventListener("unstalled", function () { + return _this8.trigger("unstalled", null); + }); + rebufferingController.addEventListener("warning", function (err) { + return _this8.trigger("warning", err); + }); + cancelSignal.register(function () { + return rebufferingController.destroy(); + }); + rebufferingController.start(); + return rebufferingController; + }; + return MediaSourceContentInitializer; +}(init_types/* ContentInitializer */.K); -/* harmony default export */ var init = (InitializeOnMediaSource); // EXTERNAL MODULE: ./src/utils/languages/normalize.ts + 2 modules var normalize = __webpack_require__(5553); ;// CONCATENATED MODULE: ./src/core/api/option_utils.ts @@ -53298,7 +49946,7 @@ function checkReloadOptions(options) { * @returns {Object} */ function parseLoadVideoOptions(options) { - var _a, _b, _c, _d, _e, _f, _g; + var _a, _b, _c, _d, _e, _f, _g, _h; var url; var transport; var keySystems; @@ -53326,6 +49974,9 @@ function parseLoadVideoOptions(options) { } else { transport = String(options.transport); } + if (!(0,is_null_or_undefined/* default */.Z)((_c = options.transportOptions) === null || _c === void 0 ? void 0 : _c.aggressiveMode)) { + (0,warn_once/* default */.Z)("`transportOptions.aggressiveMode` is deprecated and won't " + "be present in the next major version. " + "Please open an issue if you still need this."); + } var autoPlay = (0,is_null_or_undefined/* default */.Z)(options.autoPlay) ? DEFAULT_AUTO_PLAY : !!options.autoPlay; if ((0,is_null_or_undefined/* default */.Z)(options.keySystems)) { keySystems = []; @@ -53336,12 +49987,18 @@ function parseLoadVideoOptions(options) { if (typeof keySystem.type !== "string" || typeof keySystem.getLicense !== "function") { throw new Error("Invalid key system given: Missing type string or " + "getLicense callback"); } + if (!(0,is_null_or_undefined/* default */.Z)(keySystem.onKeyStatusesChange)) { + (0,warn_once/* default */.Z)("`keySystems[].onKeyStatusesChange` is deprecated and won't " + "be present in the next major version. " + "Please open an issue if you still need this."); + } + if (!(0,is_null_or_undefined/* default */.Z)(keySystem.throwOnLicenseExpiration)) { + (0,warn_once/* default */.Z)("`keySystems[].throwOnLicenseExpiration` is deprecated and won't " + "be present in the next major version. " + "Please open an issue if you still need this."); + } } } var lowLatencyMode = options.lowLatencyMode === undefined ? false : !!options.lowLatencyMode; var transportOptsArg = typeof options.transportOptions === "object" && options.transportOptions !== null ? options.transportOptions : {}; - var initialManifest = (_c = options.transportOptions) === null || _c === void 0 ? void 0 : _c.initialManifest; - var minimumManifestUpdateInterval = (_e = (_d = options.transportOptions) === null || _d === void 0 ? void 0 : _d.minimumManifestUpdateInterval) !== null && _e !== void 0 ? _e : 0; + var initialManifest = (_d = options.transportOptions) === null || _d === void 0 ? void 0 : _d.initialManifest; + var minimumManifestUpdateInterval = (_f = (_e = options.transportOptions) === null || _e === void 0 ? void 0 : _e.minimumManifestUpdateInterval) !== null && _f !== void 0 ? _f : 0; var audioTrackSwitchingMode = (0,is_null_or_undefined/* default */.Z)(options.audioTrackSwitchingMode) ? DEFAULT_AUDIO_TRACK_SWITCHING_MODE : options.audioTrackSwitchingMode; if (!(0,array_includes/* default */.Z)(["seamless", "direct", "reload"], audioTrackSwitchingMode)) { log/* default.warn */.Z.warn("The `audioTrackSwitchingMode` loadVideo option must match one of " + "the following strategy name:\n" + "- `seamless`\n" + "- `direct`\n" + "- `reload`\n" + "If badly set, " + DEFAULT_AUDIO_TRACK_SWITCHING_MODE + " strategy will be used as default"); @@ -53405,7 +50062,7 @@ function parseLoadVideoOptions(options) { (0,warn_once/* default */.Z)("The `hideNativeSubtitle` loadVideo option is deprecated"); hideNativeSubtitle = !!options.hideNativeSubtitle; } - var manualBitrateSwitchingMode = (_f = options.manualBitrateSwitchingMode) !== null && _f !== void 0 ? _f : DEFAULT_MANUAL_BITRATE_SWITCHING_MODE; + var manualBitrateSwitchingMode = (_g = options.manualBitrateSwitchingMode) !== null && _g !== void 0 ? _g : DEFAULT_MANUAL_BITRATE_SWITCHING_MODE; var enableFastSwitching = (0,is_null_or_undefined/* default */.Z)(options.enableFastSwitching) ? DEFAULT_ENABLE_FAST_SWITCHING : options.enableFastSwitching; if (textTrackMode === "html") { // TODO Better way to express that in TypeScript? @@ -53430,7 +50087,7 @@ function parseLoadVideoOptions(options) { startAt = options.startAt; } } - var networkConfig = (_g = options.networkConfig) !== null && _g !== void 0 ? _g : {}; + var networkConfig = (_h = options.networkConfig) !== null && _h !== void 0 ? _h : {}; // TODO without cast /* eslint-disable @typescript-eslint/consistent-type-assertions */ return { @@ -53495,8 +50152,8 @@ var SCANNED_MEDIA_ELEMENTS_EVENTS = ["canplay", "ended", "play", "pause", "seeki * `PlaybackObserver` to know the current state of the media being played. * * You can use the PlaybackObserver to either get the last observation - * performed, get the current media state or subscribe to an Observable emitting - * regularly media conditions. + * performed, get the current media state or listen to media observation sent + * at a regular interval. * * @class {PlaybackObserver} */ @@ -53521,8 +50178,7 @@ var PlaybackObserver = /*#__PURE__*/function () { } /** * Stop the `PlaybackObserver` from emitting playback observations and free all - * resources reserved to emitting them such as event listeners, intervals and - * subscribing callbacks. + * resources reserved to emitting them such as event listeners and intervals. * * Once `stop` is called, no new playback observation will ever be emitted. * @@ -53591,7 +50247,7 @@ var PlaybackObserver = /*#__PURE__*/function () { * produced by the `PlaybackObserver` and updated each time a new one is * produced. * - * This value can then be for example subscribed to to be notified of future + * This value can then be for example listened to to be notified of future * playback observations. * * @returns {Object} @@ -53610,7 +50266,7 @@ var PlaybackObserver = /*#__PURE__*/function () { */; _proto.listen = function listen(cb, options) { var _a; - if (this._canceller.isUsed || ((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled) === true) { + if (this._canceller.isUsed() || ((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled()) === true) { return noop/* default */.Z; } this._observationRef.onUpdate(cb, { @@ -53638,7 +50294,7 @@ var PlaybackObserver = /*#__PURE__*/function () { /** * Creates the `IReadOnlySharedReference` that will generate playback * observations. - * @returns {Observable} + * @returns {Object} */; _proto._createSharedReference = function _createSharedReference() { var _this = this; @@ -53682,7 +50338,7 @@ var PlaybackObserver = /*#__PURE__*/function () { } return timings; }; - var returnedSharedReference = (0,reference/* default */.ZP)(getCurrentObservation("init")); + var returnedSharedReference = (0,reference/* default */.ZP)(getCurrentObservation("init"), this._canceller.signal); var generateObservationForEvent = function generateObservationForEvent(event) { var newObservation = getCurrentObservation(event); if (log/* default.hasLevel */.Z.hasLevel("DEBUG")) { @@ -54027,7 +50683,7 @@ function generateReadOnlyObserver(src, transform, cancellationSignal) { }, listen: function listen(cb, options) { var _a; - if (cancellationSignal.isCancelled || ((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled) === true) { + if (cancellationSignal.isCancelled() || ((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled()) === true) { return; } mappedRef.onUpdate(cb, { @@ -54058,6 +50714,10 @@ var languages = __webpack_require__(7829); * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * This file is used to abstract the notion of text, audio and video tracks + * switching for an easier API management. + */ @@ -54090,6 +50750,7 @@ function normalizeTextTracks(tracks) { return tracks.map(function (t) { return t === null ? t : { normalized: (0,languages/* default */.ZP)(t.language), + forced: t.forced, closedCaption: t.closedCaption }; }); @@ -54153,13 +50814,12 @@ var TrackChoiceManager = /*#__PURE__*/function () { } } /** - * Add Subject to choose Adaptation for new "audio" or "text" Period. + * Add shared reference to choose Adaptation for new "audio" or "text" Period. * @param {string} bufferType - The concerned buffer type * @param {Period} period - The concerned Period. - * @param {Subject.} adaptation$ - A subject through which the - * choice will be given + * @param {Object} adaptationRef */; - _proto.addPeriod = function addPeriod(bufferType, period, adaptation$) { + _proto.addPeriod = function addPeriod(bufferType, period, adaptationRef) { var periodItem = getPeriodItem(this._periods, period); var adaptations = period.getSupportedAdaptations(bufferType); if (periodItem !== undefined) { @@ -54169,7 +50829,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { } else { periodItem[bufferType] = { adaptations: adaptations, - adaptation$: adaptation$ + adaptationRef: adaptationRef }; } } else { @@ -54178,13 +50838,13 @@ var TrackChoiceManager = /*#__PURE__*/function () { period: period }, _this$_periods$add[bufferType] = { adaptations: adaptations, - adaptation$: adaptation$ + adaptationRef: adaptationRef }, _this$_periods$add)); } } /** - * Remove Subject to choose an "audio", "video" or "text" Adaptation for a - * Period. + * Remove shared reference to choose an "audio", "video" or "text" Adaptation + * for a Period. * @param {string} bufferType - The concerned buffer type * @param {Period} period - The concerned Period. */; @@ -54220,7 +50880,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { this._resetChosenVideoTracks(); } /** - * Emit initial audio Adaptation through the given Subject based on: + * Emit initial audio Adaptation through the given shared reference based on: * - the preferred audio tracks * - the last choice for this period, if one * @param {Period} period - The concerned Period. @@ -54235,20 +50895,20 @@ var TrackChoiceManager = /*#__PURE__*/function () { var chosenAudioAdaptation = this._audioChoiceMemory.get(period); if (chosenAudioAdaptation === null) { // If the Period was previously without audio, keep it that way - audioInfos.adaptation$.next(null); + audioInfos.adaptationRef.setValue(null); } else if (chosenAudioAdaptation === undefined || !(0,array_includes/* default */.Z)(audioAdaptations, chosenAudioAdaptation)) { // Find the optimal audio Adaptation var preferredAudioTracks = this._preferredAudioTracks; var normalizedPref = normalizeAudioTracks(preferredAudioTracks); var optimalAdaptation = findFirstOptimalAudioAdaptation(audioAdaptations, normalizedPref); this._audioChoiceMemory.set(period, optimalAdaptation); - audioInfos.adaptation$.next(optimalAdaptation); + audioInfos.adaptationRef.setValue(optimalAdaptation); } else { - audioInfos.adaptation$.next(chosenAudioAdaptation); // set last one + audioInfos.adaptationRef.setValue(chosenAudioAdaptation); // set last one } } /** - * Emit initial text Adaptation through the given Subject based on: + * Emit initial text Adaptation through the given shared reference based on: * - the preferred text tracks * - the last choice for this period, if one * @param {Period} period - The concerned Period. @@ -54263,20 +50923,20 @@ var TrackChoiceManager = /*#__PURE__*/function () { var chosenTextAdaptation = this._textChoiceMemory.get(period); if (chosenTextAdaptation === null) { // If the Period was previously without text, keep it that way - textInfos.adaptation$.next(null); + textInfos.adaptationRef.setValue(null); } else if (chosenTextAdaptation === undefined || !(0,array_includes/* default */.Z)(textAdaptations, chosenTextAdaptation)) { // Find the optimal text Adaptation var preferredTextTracks = this._preferredTextTracks; var normalizedPref = normalizeTextTracks(preferredTextTracks); - var optimalAdaptation = findFirstOptimalTextAdaptation(textAdaptations, normalizedPref); + var optimalAdaptation = findFirstOptimalTextAdaptation(textAdaptations, normalizedPref, this._audioChoiceMemory.get(period)); this._textChoiceMemory.set(period, optimalAdaptation); - textInfos.adaptation$.next(optimalAdaptation); + textInfos.adaptationRef.setValue(optimalAdaptation); } else { - textInfos.adaptation$.next(chosenTextAdaptation); // set last one + textInfos.adaptationRef.setValue(chosenTextAdaptation); // set last one } } /** - * Emit initial video Adaptation through the given Subject based on: + * Emit initial video Adaptation through the given shared reference based on: * - the preferred video tracks * - the last choice for this period, if one * @param {Period} period - The concerned Period. @@ -54303,7 +50963,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { } if (newBaseAdaptation === null) { this._videoChoiceMemory.set(period, null); - videoInfos.adaptation$.next(null); + videoInfos.adaptationRef.setValue(null); return; } var newVideoAdaptation = getRightVideoTrack(newBaseAdaptation, this.trickModeTrackEnabled); @@ -54311,7 +50971,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { baseAdaptation: newBaseAdaptation, adaptation: newVideoAdaptation }); - videoInfos.adaptation$.next(newVideoAdaptation); + videoInfos.adaptationRef.setValue(newVideoAdaptation); } /** * Set audio track based on the ID of its adaptation for a given added Period. @@ -54336,7 +50996,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { return; } this._audioChoiceMemory.set(period, wantedAdaptation); - audioInfos.adaptation$.next(wantedAdaptation); + audioInfos.adaptationRef.setValue(wantedAdaptation); } /** * Set text track based on the ID of its adaptation for a given added Period. @@ -54361,7 +51021,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { return; } this._textChoiceMemory.set(period, wantedAdaptation); - textInfos.adaptation$.next(wantedAdaptation); + textInfos.adaptationRef.setValue(wantedAdaptation); } /** * Set video track based on the ID of its adaptation for a given added Period. @@ -54390,7 +51050,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { baseAdaptation: wantedBaseAdaptation, adaptation: newVideoAdaptation }); - videoInfos.adaptation$.next(newVideoAdaptation); + videoInfos.adaptationRef.setValue(newVideoAdaptation); } /** * Disable the current text track for a given period. @@ -54410,7 +51070,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { return; } this._textChoiceMemory.set(period, null); - textInfos.adaptation$.next(null); + textInfos.adaptationRef.setValue(null); } /** * Disable the current video track for a given period. @@ -54428,7 +51088,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { return; } this._videoChoiceMemory.set(period, null); - videoInfos.adaptation$.next(null); + videoInfos.adaptationRef.setValue(null); }; _proto.disableVideoTrickModeTracks = function disableVideoTrickModeTracks() { this.trickModeTrackEnabled = false; @@ -54497,13 +51157,17 @@ var TrackChoiceManager = /*#__PURE__*/function () { if ((0,is_null_or_undefined/* default */.Z)(chosenTextAdaptation)) { return null; } - return { + var formatted = { language: (0,take_first_set/* default */.Z)(chosenTextAdaptation.language, ""), normalized: (0,take_first_set/* default */.Z)(chosenTextAdaptation.normalizedLanguage, ""), closedCaption: chosenTextAdaptation.isClosedCaption === true, id: chosenTextAdaptation.id, label: chosenTextAdaptation.label }; + if (chosenTextAdaptation.isForcedSubtitles !== undefined) { + formatted.forced = chosenTextAdaptation.isForcedSubtitles; + } + return formatted; } /** * Returns an object describing the chosen video track for the given video @@ -54600,7 +51264,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { var chosenTextAdaptation = this._textChoiceMemory.get(period); var currentId = !(0,is_null_or_undefined/* default */.Z)(chosenTextAdaptation) ? chosenTextAdaptation.id : null; return textInfos.adaptations.map(function (adaptation) { - return { + var formatted = { language: (0,take_first_set/* default */.Z)(adaptation.language, ""), normalized: (0,take_first_set/* default */.Z)(adaptation.normalizedLanguage, ""), closedCaption: adaptation.isClosedCaption === true, @@ -54608,6 +51272,10 @@ var TrackChoiceManager = /*#__PURE__*/function () { active: currentId === null ? false : currentId === adaptation.id, label: adaptation.label }; + if (adaptation.isForcedSubtitles !== undefined) { + formatted.forced = adaptation.isForcedSubtitles; + } + return formatted; }); } /** @@ -54713,7 +51381,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { } var optimalAdaptation = findFirstOptimalAudioAdaptation(audioAdaptations, normalizedPref); _this._audioChoiceMemory.set(period, optimalAdaptation); - audioItem.adaptation$.next(optimalAdaptation); + audioItem.adaptationRef.setValue(optimalAdaptation); // previous "next" call could have changed everything, start over recursiveUpdateAudioTrack(0); }; @@ -54751,9 +51419,9 @@ var TrackChoiceManager = /*#__PURE__*/function () { recursiveUpdateTextTrack(index + 1); return; } - var optimalAdaptation = findFirstOptimalTextAdaptation(textAdaptations, normalizedPref); + var optimalAdaptation = findFirstOptimalTextAdaptation(textAdaptations, normalizedPref, _this2._audioChoiceMemory.get(period)); _this2._textChoiceMemory.set(period, optimalAdaptation); - textItem.adaptation$.next(optimalAdaptation); + textItem.adaptationRef.setValue(optimalAdaptation); // previous "next" call could have changed everything, start over recursiveUpdateTextTrack(0); }; @@ -54803,7 +51471,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { baseAdaptation: chosenVideoAdaptation.baseAdaptation, adaptation: wantedVideoAdaptation }); - videoItem.adaptation$.next(wantedVideoAdaptation); + videoItem.adaptationRef.setValue(wantedVideoAdaptation); // previous "next" call could have changed everything, start over return recursiveUpdateVideoTrack(0); } @@ -54811,7 +51479,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { var optimalAdaptation = findFirstOptimalVideoAdaptation(videoAdaptations, preferredVideoTracks); if (optimalAdaptation === null) { _this3._videoChoiceMemory.set(period, null); - videoItem.adaptation$.next(null); + videoItem.adaptationRef.setValue(null); // previous "next" call could have changed everything, start over return recursiveUpdateVideoTrack(0); } @@ -54820,7 +51488,7 @@ var TrackChoiceManager = /*#__PURE__*/function () { baseAdaptation: optimalAdaptation, adaptation: newVideoAdaptation }); - videoItem.adaptation$.next(newVideoAdaptation); + videoItem.adaptationRef.setValue(newVideoAdaptation); // previous "next" call could have changed everything, start over return recursiveUpdateVideoTrack(0); }; @@ -54925,7 +51593,7 @@ function createTextPreferenceMatcher(preferredTextTrack) { * @returns {boolean} */ return function matchTextPreference(textAdaptation) { - return (0,take_first_set/* default */.Z)(textAdaptation.normalizedLanguage, "") === preferredTextTrack.normalized && (preferredTextTrack.closedCaption ? textAdaptation.isClosedCaption === true : textAdaptation.isClosedCaption !== true); + return (0,take_first_set/* default */.Z)(textAdaptation.normalizedLanguage, "") === preferredTextTrack.normalized && (preferredTextTrack.closedCaption ? textAdaptation.isClosedCaption === true : textAdaptation.isClosedCaption !== true) && (preferredTextTrack.forced === true ? textAdaptation.isForcedSubtitles === true : textAdaptation.isForcedSubtitles !== true); }; } /** @@ -54935,9 +51603,11 @@ function createTextPreferenceMatcher(preferredTextTrack) { * `null` if the most optimal text adaptation is no text adaptation. * @param {Array.} textAdaptations * @param {Array.} preferredTextTracks + * @param {Object|null|undefined} chosenAudioAdaptation * @returns {Adaptation|null} */ -function findFirstOptimalTextAdaptation(textAdaptations, preferredTextTracks) { +function findFirstOptimalTextAdaptation(textAdaptations, preferredTextTracks, chosenAudioAdaptation) { + var _a; if (textAdaptations.length === 0) { return null; } @@ -54952,6 +51622,22 @@ function findFirstOptimalTextAdaptation(textAdaptations, preferredTextTracks) { return foundAdaptation; } } + var forcedSubtitles = textAdaptations.filter(function (ad) { + return ad.isForcedSubtitles === true; + }); + if (forcedSubtitles.length > 0) { + if (chosenAudioAdaptation !== null && chosenAudioAdaptation !== undefined) { + var sameLanguage = (0,array_find/* default */.Z)(forcedSubtitles, function (f) { + return f.normalizedLanguage === chosenAudioAdaptation.normalizedLanguage; + }); + if (sameLanguage !== undefined) { + return sameLanguage; + } + } + return (_a = (0,array_find/* default */.Z)(forcedSubtitles, function (f) { + return f.normalizedLanguage === undefined; + })) !== null && _a !== void 0 ? _a : null; + } // no optimal adaptation return null; } @@ -55113,37 +51799,91 @@ function getRightVideoTrack(adaptation, isTrickModeEnabled) { */ + /** - * Returns Observable which will emit: - * - `"seeking"` when we are seeking in the given mediaElement - * - `"seeked"` when a seek is considered as finished by the given observation$ - * Observable. * @param {HTMLMediaElement} mediaElement - * @param {Observable} observation$ - * @returns {Observable} - */ -function emitSeekEvents(mediaElement, observation$) { - return (0,defer/* defer */.P)(function () { - if (mediaElement === null) { - return empty/* EMPTY */.E; - } - var isSeeking$ = observation$.pipe((0,filter/* filter */.h)(function (observation) { - return observation.event === "seeking"; - }), (0,map/* map */.U)(function () { - return "seeking"; - })); - if (mediaElement.seeking) { - isSeeking$ = isSeeking$.pipe((0,startWith/* startWith */.O)("seeking")); - } - var hasSeeked$ = isSeeking$.pipe((0,switchMap/* switchMap */.w)(function () { - return observation$.pipe((0,filter/* filter */.h)(function (observation) { - return observation.event === "seeked"; - }), (0,map/* map */.U)(function () { - return "seeked"; - }), (0,take/* take */.q)(1)); - })); - return (0,merge/* merge */.T)(isSeeking$, hasSeeked$); + * @param {Object} playbackObserver - Observes playback conditions on + * `mediaElement`. + * @param {function} onSeeking - Callback called when a seeking operation starts + * on `mediaElement`. + * @param {function} onSeeked - Callback called when a seeking operation ends + * on `mediaElement`. + * @param {Object} cancelSignal - When triggered, stop calling callbacks and + * remove all listeners this function has registered. + */ +function emitSeekEvents(mediaElement, playbackObserver, onSeeking, onSeeked, cancelSignal) { + if (cancelSignal.isCancelled() || mediaElement === null) { + return; + } + var wasSeeking = playbackObserver.getReference().getValue().seeking; + if (wasSeeking) { + onSeeking(); + if (cancelSignal.isCancelled()) { + return; + } + } + playbackObserver.listen(function (obs) { + if (obs.event === "seeking") { + wasSeeking = true; + onSeeking(); + } else if (wasSeeking && obs.event === "seeked") { + wasSeeking = false; + onSeeked(); + } + }, { + includeLastObservation: true, + clearSignal: cancelSignal + }); +} +function constructPlayerStateReference(initializer, mediaElement, playbackObserver, cancelSignal) { + var playerStateRef = (0,reference/* default */.ZP)("LOADING" /* PLAYER_STATES.LOADING */, cancelSignal); + initializer.addEventListener("loaded", function () { + if (playerStateRef.getValue() === "LOADING" /* PLAYER_STATES.LOADING */) { + playerStateRef.setValue("LOADED" /* PLAYER_STATES.LOADED */); + if (!cancelSignal.isCancelled()) { + var newState = getLoadedContentState(mediaElement, null); + if (newState !== "PAUSED" /* PLAYER_STATES.PAUSED */) { + playerStateRef.setValue(newState); + } + } + } else { + playerStateRef.setValueIfChanged(getLoadedContentState(mediaElement, null)); + } + }, cancelSignal); + initializer.addEventListener("reloadingMediaSource", function () { + if (isLoadedState(playerStateRef.getValue())) { + playerStateRef.setValueIfChanged("RELOADING" /* PLAYER_STATES.RELOADING */); + } + }, cancelSignal); + /** + * Keep track of the last known stalling situation. + * `null` if playback is not stalled. + */ + var prevStallReason = null; + initializer.addEventListener("stalled", function (s) { + if (s !== prevStallReason) { + if (isLoadedState(playerStateRef.getValue())) { + playerStateRef.setValueIfChanged(getLoadedContentState(mediaElement, s)); + } + prevStallReason = s; + } + }, cancelSignal); + initializer.addEventListener("unstalled", function () { + if (prevStallReason !== null) { + if (isLoadedState(playerStateRef.getValue())) { + playerStateRef.setValueIfChanged(getLoadedContentState(mediaElement, null)); + } + prevStallReason = null; + } + }, cancelSignal); + playbackObserver.listen(function (observation) { + if (isLoadedState(playerStateRef.getValue()) && (0,array_includes/* default */.Z)(["seeking", "ended", "play", "pause"], observation.event)) { + playerStateRef.setValueIfChanged(getLoadedContentState(mediaElement, prevStallReason)); + } + }, { + clearSignal: cancelSignal }); + return playerStateRef; } /** * Get state string for a _loaded_ content. @@ -55175,9 +51915,16 @@ function getLoadedContentState(mediaElement, stalledStatus) { return mediaElement.paused ? "PAUSED" /* PLAYER_STATES.PAUSED */ : "PLAYING" /* PLAYER_STATES.PLAYING */; } + +function isLoadedState(state) { + return state !== "LOADING" /* PLAYER_STATES.LOADING */ && state !== "RELOADING" /* PLAYER_STATES.RELOADING */ && state !== "STOPPED" /* PLAYER_STATES.STOPPED */; +} ;// CONCATENATED MODULE: ./src/core/api/public_api.ts +function public_api_createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = public_api_unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function public_api_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return public_api_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return public_api_arrayLikeToArray(o, minLen); } +function public_api_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Copyright 2015 CANAL+ Group * @@ -55198,7 +51945,6 @@ function getLoadedContentState(mediaElement, stalledStatus) { * It also starts the different sub-parts of the player on various API calls. */ - /* eslint-disable-next-line max-len */ @@ -55222,12 +51968,14 @@ function getLoadedContentState(mediaElement, stalledStatus) { /* eslint-disable @typescript-eslint/naming-convention */ +var generateContentId = (0,id_generator/* default */.Z)(); var getPageActivityRef = event_listeners/* getPageActivityRef */.XR, getPictureOnPictureStateRef = event_listeners/* getPictureOnPictureStateRef */.w0, getVideoVisibilityRef = event_listeners/* getVideoVisibilityRef */.it, getVideoWidthRef = event_listeners/* getVideoWidthRef */.O0, - onFullscreenChange$ = event_listeners/* onFullscreenChange$ */.Q1, - onTextTrackChanges$ = event_listeners/* onTextTrackChanges$ */.UA; + onFullscreenChange = event_listeners/* onFullscreenChange */.zU, + onTextTrackAdded = event_listeners/* onTextTrackAdded */.kJ, + onTextTrackRemoved = event_listeners/* onTextTrackRemoved */.Q4; /** * @class Player * @extends EventEmitter @@ -55243,6 +51991,7 @@ var Player = /*#__PURE__*/function (_EventEmitter) { if (options === void 0) { options = {}; } + var _a, _b; _this = _EventEmitter.call(this) || this; var _parseConstructorOpti = parseConstructorOptions(options), initialAudioBitrate = _parseConstructorOpti.initialAudioBitrate, @@ -55268,57 +52017,65 @@ var Player = /*#__PURE__*/function (_EventEmitter) { // Workaround to support Firefox autoplay on FF 42. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624 videoElement.preload = "auto"; - _this.version = /* PLAYER_VERSION */"3.29.0"; + _this.version = /* PLAYER_VERSION */"3.30.0"; _this.log = log/* default */.Z; _this.state = "STOPPED"; _this.videoElement = videoElement; var destroyCanceller = new task_canceller/* default */.ZP(); - _this._priv_destroy$ = new Subject/* Subject */.x(); // TODO Remove the need for this Subject - _this._priv_destroy$.pipe((0,take/* take */.q)(1)).subscribe(function () { - destroyCanceller.cancel(); - }); + _this._destroyCanceller = destroyCanceller; _this._priv_pictureInPictureRef = getPictureOnPictureStateRef(videoElement, destroyCanceller.signal); /** @deprecated */ - onFullscreenChange$(videoElement).pipe((0,takeUntil/* takeUntil */.R)(_this._priv_destroy$)) - /* eslint-disable import/no-deprecated */.subscribe(function () { - return _this.trigger("fullscreenChange", _this.isFullscreen()); - }); - /* eslint-enable import/no-deprecated */ - /** @deprecated */ - onTextTrackChanges$(videoElement.textTracks).pipe((0,takeUntil/* takeUntil */.R)(_this._priv_destroy$), (0,map/* map */.U)(function (evt) { + onFullscreenChange(videoElement, function () { + /* eslint-disable import/no-deprecated */ + _this.trigger("fullscreenChange", _this.isFullscreen()); + /* eslint-enable import/no-deprecated */ + }, destroyCanceller.signal); + /** Store last known TextTrack array linked to the media element. */ + var prevTextTracks = []; + for (var i = 0; i < ((_a = videoElement.textTracks) === null || _a === void 0 ? void 0 : _a.length); i++) { + var textTrack = (_b = videoElement.textTracks) === null || _b === void 0 ? void 0 : _b[i]; + if (!(0,is_null_or_undefined/* default */.Z)(textTrack)) { + prevTextTracks.push(textTrack); + } + } + /** Callback called when a TextTrack element is added or removed. */ + var onTextTrackChanges = function onTextTrackChanges(_evt) { + var evt = _evt; var target = evt.target; - var arr = []; - for (var i = 0; i < target.length; i++) { - var textTrack = target[i]; - arr.push(textTrack); - } - return arr; - }), - // We can have two consecutive textTrackChanges with the exact same - // payload when we perform multiple texttrack operations before the event - // loop is freed. - // In that case we only want to fire one time the observable. - distinctUntilChanged(function (textTracksA, textTracksB) { - if (textTracksA.length !== textTracksB.length) { - return false; + var textTrackArr = []; + for (var _i = 0; _i < target.length; _i++) { + var _textTrack = target[_i]; + textTrackArr.push(_textTrack); + } + var oldTextTracks = prevTextTracks; + prevTextTracks = textTrackArr; + // We can have two consecutive textTrackChanges with the exact same + // payload when we perform multiple texttrack operations before the event + // loop is freed. + if (oldTextTracks.length !== textTrackArr.length) { + _this._priv_onNativeTextTracksNext(textTrackArr); + return; } - for (var i = 0; i < textTracksA.length; i++) { - if (textTracksA[i] !== textTracksB[i]) { - return false; + for (var _i2 = 0; _i2 < oldTextTracks.length; _i2++) { + if (oldTextTracks[_i2] !== textTrackArr[_i2]) { + _this._priv_onNativeTextTracksNext(textTrackArr); + return; } } - return true; - })).subscribe(function (x) { - return _this._priv_onNativeTextTracksNext(x); - }); - _this._priv_speed = (0,reference/* default */.ZP)(videoElement.playbackRate); + return; + }; + if (!(0,is_null_or_undefined/* default */.Z)(videoElement.textTracks)) { + onTextTrackAdded(videoElement.textTracks, onTextTrackChanges, destroyCanceller.signal); + onTextTrackRemoved(videoElement.textTracks, onTextTrackChanges, destroyCanceller.signal); + } + _this._priv_speed = (0,reference/* default */.ZP)(videoElement.playbackRate, _this._destroyCanceller.signal); _this._priv_preferTrickModeTracks = false; - _this._priv_contentLock = (0,reference/* default */.ZP)(false); + _this._priv_contentLock = (0,reference/* default */.ZP)(false, _this._destroyCanceller.signal); _this._priv_bufferOptions = { - wantedBufferAhead: (0,reference/* default */.ZP)(wantedBufferAhead), - maxBufferAhead: (0,reference/* default */.ZP)(maxBufferAhead), - maxBufferBehind: (0,reference/* default */.ZP)(maxBufferBehind), - maxVideoBufferSize: (0,reference/* default */.ZP)(maxVideoBufferSize) + wantedBufferAhead: (0,reference/* default */.ZP)(wantedBufferAhead, _this._destroyCanceller.signal), + maxBufferAhead: (0,reference/* default */.ZP)(maxBufferAhead, _this._destroyCanceller.signal), + maxBufferBehind: (0,reference/* default */.ZP)(maxBufferBehind, _this._destroyCanceller.signal), + maxVideoBufferSize: (0,reference/* default */.ZP)(maxVideoBufferSize, _this._destroyCanceller.signal) }; _this._priv_bitrateInfos = { lastBitrates: { @@ -55326,24 +52083,22 @@ var Player = /*#__PURE__*/function (_EventEmitter) { video: initialVideoBitrate }, minAutoBitrates: { - audio: (0,reference/* default */.ZP)(minAudioBitrate), - video: (0,reference/* default */.ZP)(minVideoBitrate) + audio: (0,reference/* default */.ZP)(minAudioBitrate, _this._destroyCanceller.signal), + video: (0,reference/* default */.ZP)(minVideoBitrate, _this._destroyCanceller.signal) }, maxAutoBitrates: { - audio: (0,reference/* default */.ZP)(maxAudioBitrate), - video: (0,reference/* default */.ZP)(maxVideoBitrate) + audio: (0,reference/* default */.ZP)(maxAudioBitrate, _this._destroyCanceller.signal), + video: (0,reference/* default */.ZP)(maxVideoBitrate, _this._destroyCanceller.signal) }, manualBitrates: { - audio: (0,reference/* default */.ZP)(-1), - video: (0,reference/* default */.ZP)(-1) + audio: (0,reference/* default */.ZP)(-1, _this._destroyCanceller.signal), + video: (0,reference/* default */.ZP)(-1, _this._destroyCanceller.signal) } }; _this._priv_throttleWhenHidden = throttleWhenHidden; _this._priv_throttleVideoBitrateWhenHidden = throttleVideoBitrateWhenHidden; _this._priv_limitVideoWidth = limitVideoWidth; _this._priv_mutedMemory = DEFAULT_UNMUTED_VOLUME; - _this._priv_trackChoiceManager = null; - _this._priv_mediaElementTrackChoiceManager = null; _this._priv_currentError = null; _this._priv_contentInfos = null; _this._priv_contentEventsMemory = {}; @@ -55397,22 +52152,8 @@ var Player = /*#__PURE__*/function (_EventEmitter) { log/* default.error */.Z.error("API: Could not dispose decryption resources: " + message); }); } - // free Observables linked to the Player instance - this._priv_destroy$.next(); - this._priv_destroy$.complete(); - // Complete all subjects and references - this._priv_speed.finish(); - this._priv_contentLock.finish(); - this._priv_bufferOptions.wantedBufferAhead.finish(); - this._priv_bufferOptions.maxVideoBufferSize.finish(); - this._priv_bufferOptions.maxBufferAhead.finish(); - this._priv_bufferOptions.maxBufferBehind.finish(); - this._priv_bitrateInfos.manualBitrates.video.finish(); - this._priv_bitrateInfos.manualBitrates.audio.finish(); - this._priv_bitrateInfos.minAutoBitrates.video.finish(); - this._priv_bitrateInfos.minAutoBitrates.audio.finish(); - this._priv_bitrateInfos.maxAutoBitrates.video.finish(); - this._priv_bitrateInfos.maxAutoBitrates.audio.finish(); + // free resources linked to the Player instance + this._destroyCanceller.cancel(); this._priv_reloadingMetadata = {}; // un-attach video element this.videoElement = null; @@ -55478,6 +52219,18 @@ var Player = /*#__PURE__*/function (_EventEmitter) { newOptions.autoPlay = autoPlay; } this._priv_initializeContentPlayback(newOptions); + }; + _proto.createDebugElement = function createDebugElement(element) { + if (features/* default.createDebugElement */.Z.createDebugElement === null) { + throw new Error("Feature `DEBUG_ELEMENT` not added to the RxPlayer"); + } + var canceller = new task_canceller/* default */.ZP(); + features/* default.createDebugElement */.Z.createDebugElement(element, this, canceller.signal); + return { + dispose: function dispose() { + canceller.cancel(); + } + }; } /** * From given options, initialize content playback. @@ -55485,7 +52238,6 @@ var Player = /*#__PURE__*/function (_EventEmitter) { */; _proto._priv_initializeContentPlayback = function _priv_initializeContentPlayback(options) { var _this2 = this; - var _a, _b, _c; var autoPlay = options.autoPlay, audioTrackSwitchingMode = options.audioTrackSwitchingMode, defaultAudioTrack = options.defaultAudioTrack, @@ -55507,42 +52259,11 @@ var Player = /*#__PURE__*/function (_EventEmitter) { throw new Error("the attached video element is disposed"); } var isDirectFile = transport === "directfile"; - /** Subject which will emit to stop the current content. */ + /** Emit to stop the current content. */ var currentContentCanceller = new task_canceller/* default */.ZP(); - // Some logic needs the equivalent of the `currentContentCanceller` under - // an Observable form - // TODO remove the need for `stoppedContent$` - var stoppedContent$ = new Observable/* Observable */.y(function (obs) { - currentContentCanceller.signal.register(function () { - obs.next(); - obs.complete(); - }); - }); - /** Future `this._priv_contentInfos` related to this content. */ - var contentInfos = { - url: url, - currentContentCanceller: currentContentCanceller, - isDirectFile: isDirectFile, - segmentBuffersStore: null, - thumbnails: null, - manifest: null, - currentPeriod: null, - activeAdaptations: null, - activeRepresentations: null, - initialAudioTrack: defaultAudioTrack, - initialTextTrack: defaultTextTrack - }; var videoElement = this.videoElement; - /** Global "playback observer" which will emit playback conditions */ - var playbackObserver = new PlaybackObserver(videoElement, { - withMediaSource: !isDirectFile, - lowLatencyMode: lowLatencyMode - }); - currentContentCanceller.signal.register(function () { - playbackObserver.stop(); - }); - /** Emit playback events. */ - var playback$; + var initializer; + var mediaElementTrackChoiceManager = null; if (!isDirectFile) { var transportFn = features/* default.transports */.Z.transports[transport]; if (typeof transportFn !== "function") { @@ -55558,44 +52279,14 @@ var Player = /*#__PURE__*/function (_EventEmitter) { manifestRequestTimeout = networkConfig.manifestRequestTimeout, segmentRequestTimeout = networkConfig.segmentRequestTimeout; /** Interface used to load and refresh the Manifest. */ - var manifestFetcher = new fetchers_manifest(url, transportPipelines, { + var manifestRequestSettings = { lowLatencyMode: lowLatencyMode, maxRetryRegular: manifestRetry, maxRetryOffline: offlineRetry, - requestTimeout: manifestRequestTimeout - }); - /** Observable emitting the initial Manifest */ - var manifest$; - if (initialManifest instanceof manifest/* default */.ZP) { - manifest$ = (0,of.of)({ - type: "parsed", - manifest: initialManifest - }); - } else if (initialManifest !== undefined) { - manifest$ = manifestFetcher.parse(initialManifest, { - previousManifest: null, - unsafeMode: false - }); - } else { - manifest$ = manifestFetcher.fetch(url).pipe((0,mergeMap/* mergeMap */.z)(function (response) { - return response.type === "warning" ? (0,of.of)(response) : - // bubble-up warnings - response.parse({ - previousManifest: null, - unsafeMode: false - }); - })); - } - // Load the Manifest right now and share it with every subscriber until - // the content is stopped - manifest$ = manifest$.pipe((0,takeUntil/* takeUntil */.R)(stoppedContent$), (0,shareReplay/* shareReplay */.d)()); - manifest$.subscribe(); - // now that the Manifest is loading, stop previous content and reset state - // This is done after fetching the Manifest as `stop` could technically - // take time. - this.stop(); - this._priv_currentError = null; - this._priv_contentInfos = contentInfos; + requestTimeout: manifestRequestTimeout, + minimumManifestUpdateInterval: minimumManifestUpdateInterval, + initialManifest: initialManifest + }; var relyOnVideoVisibilityAndSize = canRelyOnVideoVisibilityAndSize(); var throttlers = { throttle: {}, @@ -55657,143 +52348,173 @@ var Player = /*#__PURE__*/function (_EventEmitter) { onCodecSwitch: onCodecSwitch }, this._priv_bufferOptions); var segmentRequestOptions = { - regularError: segmentRetry, + lowLatencyMode: lowLatencyMode, + maxRetryRegular: segmentRetry, requestTimeout: segmentRequestTimeout, - offlineError: offlineRetry + maxRetryOffline: offlineRetry }; - // We've every options set up. Start everything now - var init$ = init({ + initializer = new MediaSourceContentInitializer({ adaptiveOptions: adaptiveOptions, autoPlay: autoPlay, bufferOptions: bufferOptions, - playbackObserver: playbackObserver, keySystems: keySystems, lowLatencyMode: lowLatencyMode, - manifest$: manifest$, - manifestFetcher: manifestFetcher, - mediaElement: videoElement, - minimumManifestUpdateInterval: minimumManifestUpdateInterval, + manifestRequestSettings: manifestRequestSettings, + transport: transportPipelines, segmentRequestOptions: segmentRequestOptions, speed: this._priv_speed, startAt: startAt, - transport: transportPipelines, - textTrackOptions: textTrackOptions - }).pipe((0,takeUntil/* takeUntil */.R)(stoppedContent$)); - playback$ = connectable(init$, { - connector: function connector() { - return new Subject/* Subject */.x(); - }, - resetOnDisconnect: false + textTrackOptions: textTrackOptions, + url: url }); } else { - // Stop previous content and reset its state - this.stop(); - this._priv_currentError = null; if (features/* default.directfile */.Z.directfile === null) { + this.stop(); + this._priv_currentError = null; throw new Error("DirectFile feature not activated in your build."); } - this._priv_contentInfos = contentInfos; - this._priv_mediaElementTrackChoiceManager = new features/* default.directfile.mediaElementTrackChoiceManager */.Z.directfile.mediaElementTrackChoiceManager(this.videoElement); - var preferredAudioTracks = defaultAudioTrack === undefined ? this._priv_preferredAudioTracks : [defaultAudioTrack]; - this._priv_mediaElementTrackChoiceManager.setPreferredAudioTracks(preferredAudioTracks, true); - var preferredTextTracks = defaultTextTrack === undefined ? this._priv_preferredTextTracks : [defaultTextTrack]; - this._priv_mediaElementTrackChoiceManager.setPreferredTextTracks(preferredTextTracks, true); - this._priv_mediaElementTrackChoiceManager.setPreferredVideoTracks(this._priv_preferredVideoTracks, true); - this.trigger("availableAudioTracksChange", this._priv_mediaElementTrackChoiceManager.getAvailableAudioTracks()); - this.trigger("availableVideoTracksChange", this._priv_mediaElementTrackChoiceManager.getAvailableVideoTracks()); - this.trigger("availableTextTracksChange", this._priv_mediaElementTrackChoiceManager.getAvailableTextTracks()); - this.trigger("audioTrackChange", (_a = this._priv_mediaElementTrackChoiceManager.getChosenAudioTrack()) !== null && _a !== void 0 ? _a : null); - this.trigger("textTrackChange", (_b = this._priv_mediaElementTrackChoiceManager.getChosenTextTrack()) !== null && _b !== void 0 ? _b : null); - this.trigger("videoTrackChange", (_c = this._priv_mediaElementTrackChoiceManager.getChosenVideoTrack()) !== null && _c !== void 0 ? _c : null); - this._priv_mediaElementTrackChoiceManager.addEventListener("availableVideoTracksChange", function (val) { - return _this2.trigger("availableVideoTracksChange", val); - }); - this._priv_mediaElementTrackChoiceManager.addEventListener("availableAudioTracksChange", function (val) { - return _this2.trigger("availableAudioTracksChange", val); - }); - this._priv_mediaElementTrackChoiceManager.addEventListener("availableTextTracksChange", function (val) { - return _this2.trigger("availableTextTracksChange", val); - }); - this._priv_mediaElementTrackChoiceManager.addEventListener("audioTrackChange", function (val) { - return _this2.trigger("audioTrackChange", val); - }); - this._priv_mediaElementTrackChoiceManager.addEventListener("videoTrackChange", function (val) { - return _this2.trigger("videoTrackChange", val); - }); - this._priv_mediaElementTrackChoiceManager.addEventListener("textTrackChange", function (val) { - return _this2.trigger("textTrackChange", val); - }); - var directfileInit$ = features/* default.directfile.initDirectFile */.Z.directfile.initDirectFile({ + mediaElementTrackChoiceManager = this._priv_initializeMediaElementTrackChoiceManager(defaultAudioTrack, defaultTextTrack, currentContentCanceller.signal); + if (currentContentCanceller.isUsed()) { + return; + } + initializer = new features/* default.directfile.initDirectFile */.Z.directfile.initDirectFile({ autoPlay: autoPlay, keySystems: keySystems, - mediaElement: videoElement, speed: this._priv_speed, - playbackObserver: playbackObserver, startAt: startAt, url: url - }).pipe((0,takeUntil/* takeUntil */.R)(stoppedContent$)); - playback$ = connectable(directfileInit$, { - connector: function connector() { - return new Subject/* Subject */.x(); - }, - resetOnDisconnect: false }); } - /** Emit an object when the player "stalls" and null when it un-stalls */ - var stalled$ = playback$.pipe((0,filter/* filter */.h)(function (evt) { - return evt.type === "stalled" || evt.type === "unstalled"; - }), (0,map/* map */.U)(function (x) { - return x.value; - }), distinctUntilChanged(function (prevStallReason, currStallReason) { - return prevStallReason === null && currStallReason === null || prevStallReason !== null && currStallReason !== null && prevStallReason === currStallReason; - })); - /** Emit when the content is considered "loaded". */ - var loaded$ = playback$.pipe((0,filter/* filter */.h)(function (evt) { - return evt.type === "loaded"; - }), (0,share/* share */.B)()); - /** Emit when we will "reload" the MediaSource. */ - var reloading$ = playback$.pipe((0,filter/* filter */.h)(function (evt) { - return evt.type === "reloading-media-source"; - }), (0,share/* share */.B)()); - /** Emit when the media element emits a "seeking" event. */ - var observation$ = playbackObserver.getReference().asObservable(); - var stateChangingEvent$ = observation$.pipe((0,filter/* filter */.h)(function (o) { - return o.event === "seeking" || o.event === "ended" || o.event === "play" || o.event === "pause"; - })); - /** Emit state updates once the content is considered "loaded". */ - var loadedStateUpdates$ = combineLatest([stalled$.pipe((0,startWith/* startWith */.O)(null)), stateChangingEvent$.pipe((0,startWith/* startWith */.O)(null))]).pipe((0,takeUntil/* takeUntil */.R)(stoppedContent$), (0,map/* map */.U)(function (_ref) { - var stalledStatus = _ref[0]; - return getLoadedContentState(videoElement, stalledStatus); - })); - /** Emit all player "state" updates. */ - var playerState$ = (0,concat/* concat */.z)((0,of.of)("LOADING" /* PLAYER_STATES.LOADING */), - // Begin with LOADING - loaded$.pipe((0,switchMap/* switchMap */.w)(function (_, i) { - var isFirstLoad = i === 0; - return (0,merge/* merge */.T)( - // Purposely subscribed first so a RELOADING triggered synchronously - // after a LOADED state is catched. - reloading$.pipe((0,map/* map */.U)(function () { - return "RELOADING"; - } /* PLAYER_STATES.RELOADING */)), - // Only switch to LOADED state for the first (i.e. non-RELOADING) load - isFirstLoad ? (0,of.of)("LOADED" /* PLAYER_STATES.LOADED */) : empty/* EMPTY */.E, - // Purposely put last so any other state change happens after we've - // already switched to LOADED - loadedStateUpdates$.pipe((0,takeUntil/* takeUntil */.R)(reloading$), - // For the first load, we prefer staying at the LOADED state over - // PAUSED when autoPlay is disabled. - // For consecutive loads however, there's no LOADED state. - skipWhile(function (state) { - return isFirstLoad && state === "PAUSED"; - } /* PLAYER_STATES.PAUSED */))); - }))).pipe(distinctUntilChanged()); - var playbackSubscription; - stoppedContent$.subscribe(function () { - if (playbackSubscription !== undefined) { - playbackSubscription.unsubscribe(); + /** Future `this._priv_contentInfos` related to this content. */ + var contentInfos = { + contentId: generateContentId(), + originalUrl: url, + currentContentCanceller: currentContentCanceller, + initializer: initializer, + isDirectFile: isDirectFile, + segmentBuffersStore: null, + thumbnails: null, + manifest: null, + currentPeriod: null, + activeAdaptations: null, + activeRepresentations: null, + initialAudioTrack: defaultAudioTrack, + initialTextTrack: defaultTextTrack, + trackChoiceManager: null, + mediaElementTrackChoiceManager: mediaElementTrackChoiceManager + }; + // Bind events + initializer.addEventListener("error", function (error) { + var formattedError = (0,format_error/* default */.Z)(error, { + defaultCode: "NONE", + defaultReason: "An unknown error stopped content playback." + }); + formattedError.fatal = true; + contentInfos.currentContentCanceller.cancel(); + _this2._priv_cleanUpCurrentContentState(); + _this2._priv_currentError = formattedError; + log/* default.error */.Z.error("API: The player stopped because of an error", error instanceof Error ? error : ""); + _this2._priv_setPlayerState("STOPPED" /* PLAYER_STATES.STOPPED */); + // TODO This condition is here because the eventual callback called when the + // player state is updated can launch a new content, thus the error will not + // be here anymore, in which case triggering the "error" event is unwanted. + // This is very ugly though, and we should probable have a better solution + if (_this2._priv_currentError === formattedError) { + _this2.trigger("error", formattedError); + } + }); + initializer.addEventListener("warning", function (error) { + var formattedError = (0,format_error/* default */.Z)(error, { + defaultCode: "NONE", + defaultReason: "An unknown error happened." + }); + log/* default.warn */.Z.warn("API: Sending warning:", formattedError); + _this2.trigger("warning", formattedError); + }); + initializer.addEventListener("reloadingMediaSource", function () { + contentInfos.segmentBuffersStore = null; + if (contentInfos.trackChoiceManager !== null) { + contentInfos.trackChoiceManager.resetPeriods(); + } + }); + initializer.addEventListener("inbandEvents", function (inbandEvents) { + return _this2.trigger("inbandEvents", inbandEvents); + }); + initializer.addEventListener("streamEvent", function (streamEvent) { + return _this2.trigger("streamEvent", streamEvent); + }); + initializer.addEventListener("streamEventSkip", function (streamEventSkip) { + return _this2.trigger("streamEventSkip", streamEventSkip); + }); + initializer.addEventListener("decipherabilityUpdate", function (decipherabilityUpdate) { + return _this2.trigger("decipherabilityUpdate", decipherabilityUpdate); + }); + initializer.addEventListener("activePeriodChanged", function (periodInfo) { + return _this2._priv_onActivePeriodChanged(contentInfos, periodInfo); + }); + initializer.addEventListener("periodStreamReady", function (periodReadyInfo) { + return _this2._priv_onPeriodStreamReady(contentInfos, periodReadyInfo); + }); + initializer.addEventListener("periodStreamCleared", function (periodClearedInfo) { + return _this2._priv_onPeriodStreamCleared(contentInfos, periodClearedInfo); + }); + initializer.addEventListener("representationChange", function (representationInfo) { + return _this2._priv_onRepresentationChange(contentInfos, representationInfo); + }); + initializer.addEventListener("adaptationChange", function (adaptationInfo) { + return _this2._priv_onAdaptationChange(contentInfos, adaptationInfo); + }); + initializer.addEventListener("bitrateEstimationChange", function (bitrateEstimationInfo) { + return _this2._priv_onBitrateEstimationChange(bitrateEstimationInfo); + }); + initializer.addEventListener("manifestReady", function (manifest) { + return _this2._priv_onManifestReady(contentInfos, manifest); + }); + initializer.addEventListener("loaded", function (evt) { + contentInfos.segmentBuffersStore = evt.segmentBuffersStore; + }); + initializer.addEventListener("addedSegment", function (evt) { + // Manage image tracks + // @deprecated + var content = evt.content, + segmentData = evt.segmentData; + if (content.adaptation.type === "image") { + if (!(0,is_null_or_undefined/* default */.Z)(segmentData) && segmentData.type === "bif") { + var imageData = segmentData.data; + /* eslint-disable import/no-deprecated */ + contentInfos.thumbnails = imageData; + _this2.trigger("imageTrackUpdate", { + data: contentInfos.thumbnails + }); + /* eslint-enable import/no-deprecated */ + } } }); + // Now, that most events are linked, prepare the next content. + initializer.prepare(); + // Now that the content is prepared, stop previous content and reset state + // This is done after content preparation as `stop` could technically have + // a long and synchronous blocking time. + // Note that this call is done **synchronously** after all events linking. + // This is **VERY** important so: + // - the `STOPPED` state is switched to synchronously after loading a new + // content. + // - we can avoid involontarily catching events linked to the previous + // content. + this.stop(); + /** Global "playback observer" which will emit playback conditions */ + var playbackObserver = new PlaybackObserver(videoElement, { + withMediaSource: !isDirectFile, + lowLatencyMode: lowLatencyMode + }); + currentContentCanceller.signal.register(function () { + playbackObserver.stop(); + }); + // Update the RxPlayer's state at the right events + var playerStateRef = constructPlayerStateReference(initializer, videoElement, playbackObserver, currentContentCanceller.signal); + currentContentCanceller.signal.register(function () { + initializer.dispose(); + }); /** * Function updating `this._priv_reloadingMetadata` in function of the * current state and playback conditions. @@ -55818,52 +52539,64 @@ var Player = /*#__PURE__*/function (_EventEmitter) { break; } }; - playerState$.pipe((0,tap/* tap */.b)(function (newState) { + /** + * `TaskCanceller` allowing to stop emitting `"seeking"` and `"seeked"` + * events. + * `null` when such events are not emitted currently. + */ + var seekEventsCanceller = null; + // React to player state change + playerStateRef.onUpdate(function (newState) { updateReloadingMetadata(newState); _this2._priv_setPlayerState(newState); + if (currentContentCanceller.isUsed()) { + return; + } + if (seekEventsCanceller !== null) { + if (!isLoadedState(_this2.state)) { + seekEventsCanceller.cancel(); + seekEventsCanceller = null; + } + } else if (isLoadedState(_this2.state)) { + seekEventsCanceller = new task_canceller/* default */.ZP(); + seekEventsCanceller.linkToSignal(currentContentCanceller.signal); + emitSeekEvents(videoElement, playbackObserver, function () { + return _this2.trigger("seeking", null); + }, function () { + return _this2.trigger("seeked", null); + }, seekEventsCanceller.signal); + } // Previous call could have performed all kind of side-effects, thus, // we re-check the current state associated to the RxPlayer - if (_this2.state === "ENDED" && _this2._priv_stopAtEnd) { - currentContentCanceller.cancel(); - } - }), (0,map/* map */.U)(function (state) { - return state !== "RELOADING" && state !== "STOPPED"; - }), distinctUntilChanged(), (0,switchMap/* switchMap */.w)(function (canSendObservation) { - return canSendObservation ? observation$ : empty/* EMPTY */.E; - }), (0,takeUntil/* takeUntil */.R)(stoppedContent$)).subscribe(function (o) { - updateReloadingMetadata(_this2.state); - _this2._priv_triggerPositionUpdate(o); + if (_this2.state === "ENDED" /* PLAYER_STATES.ENDED */ && _this2._priv_stopAtEnd) { + _this2.stop(); + } + }, { + emitCurrentValue: true, + clearSignal: currentContentCanceller.signal }); - // Link "seeking" and "seeked" events (once the content is loaded) - loaded$.pipe((0,switchMap/* switchMap */.w)(function () { - return emitSeekEvents(_this2.videoElement, observation$); - }), (0,takeUntil/* takeUntil */.R)(stoppedContent$)).subscribe(function (evt) { - log/* default.info */.Z.info("API: Triggering \"" + evt + "\" event"); - _this2.trigger(evt, null); + // React to playback conditions change + playbackObserver.listen(function (observation) { + updateReloadingMetadata(_this2.state); + _this2._priv_triggerPositionUpdate(contentInfos, observation); + }, { + clearSignal: currentContentCanceller.signal }); - // Link playback events to the corresponding callbacks - playback$.subscribe({ - next: function next(x) { - return _this2._priv_onPlaybackEvent(x); - }, - error: function error(err) { - return _this2._priv_onPlaybackError(err); - }, - complete: function complete() { - if (!contentInfos.currentContentCanceller.isUsed) { - log/* default.info */.Z.info("API: Previous playback finished. Stopping and cleaning-up..."); - contentInfos.currentContentCanceller.cancel(); - _this2._priv_cleanUpCurrentContentState(); - _this2._priv_setPlayerState("STOPPED" /* PLAYER_STATES.STOPPED */); - } - } + this._priv_currentError = null; + this._priv_contentInfos = contentInfos; + currentContentCanceller.signal.register(function () { + initializer.removeEventListener(); }); // initialize the content only when the lock is inactive - this._priv_contentLock.asObservable().pipe((0,filter/* filter */.h)(function (isLocked) { - return !isLocked; - }), (0,take/* take */.q)(1), (0,takeUntil/* takeUntil */.R)(stoppedContent$)).subscribe(function () { - // start playback! - playbackSubscription = playback$.connect(); + this._priv_contentLock.onUpdate(function (isLocked, stopListeningToLock) { + if (!isLocked) { + stopListeningToLock(); + // start playback! + initializer.start(videoElement, playbackObserver); + } + }, { + emitCurrentValue: true, + clearSignal: currentContentCanceller.signal }); } /** @@ -55981,7 +52714,8 @@ var Player = /*#__PURE__*/function (_EventEmitter) { return this._priv_preferTrickModeTracks; } /** - * Returns the url of the content's manifest + * Returns the url of the currently considered Manifest, or of the content for + * directfile content. * @returns {string|undefined} - Current URL. `undefined` if not known or no * URL yet. */; @@ -55992,15 +52726,30 @@ var Player = /*#__PURE__*/function (_EventEmitter) { var _this$_priv_contentIn3 = this._priv_contentInfos, isDirectFile = _this$_priv_contentIn3.isDirectFile, manifest = _this$_priv_contentIn3.manifest, - url = _this$_priv_contentIn3.url; + originalUrl = _this$_priv_contentIn3.originalUrl; if (isDirectFile) { - return url; + return originalUrl; } if (manifest !== null) { return manifest.getUrl(); } return undefined; } + /** + * Update URL of the content currently being played (e.g. DASH's MPD). + * @param {Array.|undefined} urls - URLs to reach that content / + * Manifest from the most prioritized URL to the least prioritized URL. + * @param {Object|undefined} [params] + * @param {boolean} params.refresh - If `true` the resource in question + * (e.g. DASH's MPD) will be refreshed immediately. + */; + _proto.updateContentUrls = function updateContentUrls(urls, params) { + if (this._priv_contentInfos === null) { + throw new Error("No content loaded"); + } + var refreshNow = (params === null || params === void 0 ? void 0 : params.refresh) === true; + this._priv_contentInfos.initializer.updateContentUrls(urls, refreshNow); + } /** * Returns the video duration, in seconds. * NaN if no video is playing. @@ -56032,6 +52781,7 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * @returns {Number} */; _proto.getVideoLoadedTime = function getVideoLoadedTime() { + (0,warn_once/* default */.Z)("`getVideoLoadedTime` is deprecated and won't be present in the " + "next major version"); if (this.videoElement === null) { throw new Error("Disposed player"); } @@ -56045,6 +52795,7 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * @returns {Number} */; _proto.getVideoPlayedTime = function getVideoPlayedTime() { + (0,warn_once/* default */.Z)("`getVideoPlayedTime` is deprecated and won't be present in the " + "next major version"); if (this.videoElement === null) { throw new Error("Disposed player"); } @@ -56167,6 +52918,7 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * @param {Object} opts */; _proto.setPlaybackRate = function setPlaybackRate(rate, opts) { + var _a; if (rate !== this._priv_speed.getValue()) { this._priv_speed.setValue(rate); } @@ -56175,11 +52927,12 @@ var Player = /*#__PURE__*/function (_EventEmitter) { return; } this._priv_preferTrickModeTracks = preferTrickModeTracks; - if (this._priv_trackChoiceManager !== null) { - if (preferTrickModeTracks && !this._priv_trackChoiceManager.isTrickModeEnabled()) { - this._priv_trackChoiceManager.enableVideoTrickModeTracks(); - } else if (!preferTrickModeTracks && this._priv_trackChoiceManager.isTrickModeEnabled()) { - this._priv_trackChoiceManager.disableVideoTrickModeTracks(); + var trackChoiceManager = (_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.trackChoiceManager; + if (!(0,is_null_or_undefined/* default */.Z)(trackChoiceManager)) { + if (preferTrickModeTracks && !trackChoiceManager.isTrickModeEnabled()) { + trackChoiceManager.enableVideoTrickModeTracks(); + } else if (!preferTrickModeTracks && trackChoiceManager.isTrickModeEnabled()) { + trackChoiceManager.disableVideoTrickModeTracks(); } } } @@ -56580,73 +53333,102 @@ var Player = /*#__PURE__*/function (_EventEmitter) { /** * Returns type of current keysystem (e.g. playready, widevine) if the content * is encrypted. null otherwise. + * @deprecated * @returns {string|null} */; _proto.getCurrentKeySystem = function getCurrentKeySystem() { + (0,warn_once/* default */.Z)("`getCurrentKeySystem` is deprecated." + "Please use the `getKeySystemConfiguration` method instead."); if (this.videoElement === null) { throw new Error("Disposed player"); } - return get_current_key_system_getCurrentKeySystem(this.videoElement); + return get_key_system_configuration_getCurrentKeySystem(this.videoElement); + } + /** + * Returns both the name of the key system (e.g. `"com.widevine.alpha"`) and + * the `MediaKeySystemConfiguration` currently associated to the + * HTMLMediaElement linked to the RxPlayer. + * + * Returns `null` if no such capabilities is associated or if unknown. + * @returns {Object|null} + */; + _proto.getKeySystemConfiguration = function getKeySystemConfiguration() { + if (this.videoElement === null) { + throw new Error("Disposed player"); + } + var values = get_key_system_configuration_getKeySystemConfiguration(this.videoElement); + if (values === null) { + return null; + } + return { + keySystem: values[0], + configuration: values[1] + }; } /** * Returns every available audio tracks for the current Period. * @returns {Array.|null} */; _proto.getAvailableAudioTracks = function getAvailableAudioTracks() { - var _a, _b; + var _a; if (this._priv_contentInfos === null) { return []; } var _this$_priv_contentIn8 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn8.currentPeriod, - isDirectFile = _this$_priv_contentIn8.isDirectFile; + isDirectFile = _this$_priv_contentIn8.isDirectFile, + trackChoiceManager = _this$_priv_contentIn8.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn8.mediaElementTrackChoiceManager; if (isDirectFile) { - return (_b = (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.getAvailableAudioTracks()) !== null && _b !== void 0 ? _b : []; + return (_a = mediaElementTrackChoiceManager === null || mediaElementTrackChoiceManager === void 0 ? void 0 : mediaElementTrackChoiceManager.getAvailableAudioTracks()) !== null && _a !== void 0 ? _a : []; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return []; } - return this._priv_trackChoiceManager.getAvailableAudioTracks(currentPeriod); + return trackChoiceManager.getAvailableAudioTracks(currentPeriod); } /** * Returns every available text tracks for the current Period. * @returns {Array.|null} */; _proto.getAvailableTextTracks = function getAvailableTextTracks() { - var _a, _b; + var _a; if (this._priv_contentInfos === null) { return []; } var _this$_priv_contentIn9 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn9.currentPeriod, - isDirectFile = _this$_priv_contentIn9.isDirectFile; + isDirectFile = _this$_priv_contentIn9.isDirectFile, + trackChoiceManager = _this$_priv_contentIn9.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn9.mediaElementTrackChoiceManager; if (isDirectFile) { - return (_b = (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.getAvailableTextTracks()) !== null && _b !== void 0 ? _b : []; + return (_a = mediaElementTrackChoiceManager === null || mediaElementTrackChoiceManager === void 0 ? void 0 : mediaElementTrackChoiceManager.getAvailableTextTracks()) !== null && _a !== void 0 ? _a : []; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return []; } - return this._priv_trackChoiceManager.getAvailableTextTracks(currentPeriod); + return trackChoiceManager.getAvailableTextTracks(currentPeriod); } /** * Returns every available video tracks for the current Period. * @returns {Array.|null} */; _proto.getAvailableVideoTracks = function getAvailableVideoTracks() { - var _a, _b; + var _a; if (this._priv_contentInfos === null) { return []; } var _this$_priv_contentIn10 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn10.currentPeriod, - isDirectFile = _this$_priv_contentIn10.isDirectFile; + isDirectFile = _this$_priv_contentIn10.isDirectFile, + trackChoiceManager = _this$_priv_contentIn10.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn10.mediaElementTrackChoiceManager; if (isDirectFile) { - return (_b = (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.getAvailableVideoTracks()) !== null && _b !== void 0 ? _b : []; + return (_a = mediaElementTrackChoiceManager === null || mediaElementTrackChoiceManager === void 0 ? void 0 : mediaElementTrackChoiceManager.getAvailableVideoTracks()) !== null && _a !== void 0 ? _a : []; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return []; } - return this._priv_trackChoiceManager.getAvailableVideoTracks(currentPeriod); + return trackChoiceManager.getAvailableVideoTracks(currentPeriod); } /** * Returns currently chosen audio language for the current Period. @@ -56658,17 +53440,19 @@ var Player = /*#__PURE__*/function (_EventEmitter) { } var _this$_priv_contentIn11 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn11.currentPeriod, - isDirectFile = _this$_priv_contentIn11.isDirectFile; + isDirectFile = _this$_priv_contentIn11.isDirectFile, + trackChoiceManager = _this$_priv_contentIn11.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn11.mediaElementTrackChoiceManager; if (isDirectFile) { - if (this._priv_mediaElementTrackChoiceManager === null) { + if (mediaElementTrackChoiceManager === null) { return undefined; } - return this._priv_mediaElementTrackChoiceManager.getChosenAudioTrack(); + return mediaElementTrackChoiceManager.getChosenAudioTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return undefined; } - return this._priv_trackChoiceManager.getChosenAudioTrack(currentPeriod); + return trackChoiceManager.getChosenAudioTrack(currentPeriod); } /** * Returns currently chosen subtitle for the current Period. @@ -56680,17 +53464,19 @@ var Player = /*#__PURE__*/function (_EventEmitter) { } var _this$_priv_contentIn12 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn12.currentPeriod, - isDirectFile = _this$_priv_contentIn12.isDirectFile; + isDirectFile = _this$_priv_contentIn12.isDirectFile, + trackChoiceManager = _this$_priv_contentIn12.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn12.mediaElementTrackChoiceManager; if (isDirectFile) { - if (this._priv_mediaElementTrackChoiceManager === null) { + if (mediaElementTrackChoiceManager === null) { return undefined; } - return this._priv_mediaElementTrackChoiceManager.getChosenTextTrack(); + return mediaElementTrackChoiceManager.getChosenTextTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return undefined; } - return this._priv_trackChoiceManager.getChosenTextTrack(currentPeriod); + return trackChoiceManager.getChosenTextTrack(currentPeriod); } /** * Returns currently chosen video track for the current Period. @@ -56702,17 +53488,19 @@ var Player = /*#__PURE__*/function (_EventEmitter) { } var _this$_priv_contentIn13 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn13.currentPeriod, - isDirectFile = _this$_priv_contentIn13.isDirectFile; + isDirectFile = _this$_priv_contentIn13.isDirectFile, + trackChoiceManager = _this$_priv_contentIn13.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn13.mediaElementTrackChoiceManager; if (isDirectFile) { - if (this._priv_mediaElementTrackChoiceManager === null) { + if (mediaElementTrackChoiceManager === null) { return undefined; } - return this._priv_mediaElementTrackChoiceManager.getChosenVideoTrack(); + return mediaElementTrackChoiceManager.getChosenVideoTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return undefined; } - return this._priv_trackChoiceManager.getChosenVideoTrack(currentPeriod); + return trackChoiceManager.getChosenVideoTrack(currentPeriod); } /** * Update the audio language for the current Period. @@ -56721,26 +53509,27 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * @throws Error - the given id is linked to no audio track. */; _proto.setAudioTrack = function setAudioTrack(audioId) { - var _a; if (this._priv_contentInfos === null) { throw new Error("No content loaded"); } var _this$_priv_contentIn14 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn14.currentPeriod, - isDirectFile = _this$_priv_contentIn14.isDirectFile; + isDirectFile = _this$_priv_contentIn14.isDirectFile, + trackChoiceManager = _this$_priv_contentIn14.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn14.mediaElementTrackChoiceManager; if (isDirectFile) { try { - (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.setAudioTrackById(audioId); + mediaElementTrackChoiceManager === null || mediaElementTrackChoiceManager === void 0 ? void 0 : mediaElementTrackChoiceManager.setAudioTrackById(audioId); return; } catch (e) { throw new Error("player: unknown audio track"); } } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { throw new Error("No compatible content launched."); } try { - this._priv_trackChoiceManager.setAudioTrackByID(currentPeriod, audioId); + trackChoiceManager.setAudioTrackByID(currentPeriod, audioId); } catch (e) { throw new Error("player: unknown audio track"); } @@ -56752,26 +53541,27 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * @throws Error - the given id is linked to no text track. */; _proto.setTextTrack = function setTextTrack(textId) { - var _a; if (this._priv_contentInfos === null) { throw new Error("No content loaded"); } var _this$_priv_contentIn15 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn15.currentPeriod, - isDirectFile = _this$_priv_contentIn15.isDirectFile; + isDirectFile = _this$_priv_contentIn15.isDirectFile, + trackChoiceManager = _this$_priv_contentIn15.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn15.mediaElementTrackChoiceManager; if (isDirectFile) { try { - (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.setTextTrackById(textId); + mediaElementTrackChoiceManager === null || mediaElementTrackChoiceManager === void 0 ? void 0 : mediaElementTrackChoiceManager.setTextTrackById(textId); return; } catch (e) { throw new Error("player: unknown text track"); } } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { throw new Error("No compatible content launched."); } try { - this._priv_trackChoiceManager.setTextTrackByID(currentPeriod, textId); + trackChoiceManager.setTextTrackByID(currentPeriod, textId); } catch (e) { throw new Error("player: unknown text track"); } @@ -56780,21 +53570,22 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * Disable subtitles for the current content. */; _proto.disableTextTrack = function disableTextTrack() { - var _a; if (this._priv_contentInfos === null) { return; } var _this$_priv_contentIn16 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn16.currentPeriod, - isDirectFile = _this$_priv_contentIn16.isDirectFile; + isDirectFile = _this$_priv_contentIn16.isDirectFile, + trackChoiceManager = _this$_priv_contentIn16.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn16.mediaElementTrackChoiceManager; if (isDirectFile) { - (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.disableTextTrack(); + mediaElementTrackChoiceManager === null || mediaElementTrackChoiceManager === void 0 ? void 0 : mediaElementTrackChoiceManager.disableTextTrack(); return; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return; } - return this._priv_trackChoiceManager.disableTextTrack(currentPeriod); + return trackChoiceManager.disableTextTrack(currentPeriod); } /** * Update the video track for the current Period. @@ -56803,26 +53594,27 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * @throws Error - the given id is linked to no video track. */; _proto.setVideoTrack = function setVideoTrack(videoId) { - var _a; if (this._priv_contentInfos === null) { throw new Error("No content loaded"); } var _this$_priv_contentIn17 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn17.currentPeriod, - isDirectFile = _this$_priv_contentIn17.isDirectFile; + isDirectFile = _this$_priv_contentIn17.isDirectFile, + trackChoiceManager = _this$_priv_contentIn17.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn17.mediaElementTrackChoiceManager; if (isDirectFile) { try { - (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.setVideoTrackById(videoId); + mediaElementTrackChoiceManager === null || mediaElementTrackChoiceManager === void 0 ? void 0 : mediaElementTrackChoiceManager.setVideoTrackById(videoId); return; } catch (e) { throw new Error("player: unknown video track"); } } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { throw new Error("No compatible content launched."); } try { - this._priv_trackChoiceManager.setVideoTrackByID(currentPeriod, videoId); + trackChoiceManager.setVideoTrackByID(currentPeriod, videoId); } catch (e) { throw new Error("player: unknown video track"); } @@ -56836,14 +53628,16 @@ var Player = /*#__PURE__*/function (_EventEmitter) { } var _this$_priv_contentIn18 = this._priv_contentInfos, currentPeriod = _this$_priv_contentIn18.currentPeriod, - isDirectFile = _this$_priv_contentIn18.isDirectFile; - if (isDirectFile && this._priv_mediaElementTrackChoiceManager !== null) { - return this._priv_mediaElementTrackChoiceManager.disableVideoTrack(); + isDirectFile = _this$_priv_contentIn18.isDirectFile, + trackChoiceManager = _this$_priv_contentIn18.trackChoiceManager, + mediaElementTrackChoiceManager = _this$_priv_contentIn18.mediaElementTrackChoiceManager; + if (isDirectFile && mediaElementTrackChoiceManager !== null) { + return mediaElementTrackChoiceManager.disableVideoTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return; } - return this._priv_trackChoiceManager.disableVideoTrack(currentPeriod); + return trackChoiceManager.disableVideoTrack(currentPeriod); } /** * Returns the current list of preferred audio tracks, in preference order. @@ -56881,10 +53675,11 @@ var Player = /*#__PURE__*/function (_EventEmitter) { throw new Error("Invalid `setPreferredAudioTracks` argument. " + "Should have been an Array."); } this._priv_preferredAudioTracks = tracks; - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.setPreferredAudioTracks(tracks, shouldApply); - } else if (this._priv_mediaElementTrackChoiceManager !== null) { - this._priv_mediaElementTrackChoiceManager.setPreferredAudioTracks(tracks, shouldApply); + var contentInfos = this._priv_contentInfos; + if (!(0,is_null_or_undefined/* default */.Z)(contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.trackChoiceManager)) { + contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.trackChoiceManager.setPreferredAudioTracks(tracks, shouldApply); + } else if (!(0,is_null_or_undefined/* default */.Z)(contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.mediaElementTrackChoiceManager)) { + contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.mediaElementTrackChoiceManager.setPreferredAudioTracks(tracks, shouldApply); } } /** @@ -56902,10 +53697,11 @@ var Player = /*#__PURE__*/function (_EventEmitter) { throw new Error("Invalid `setPreferredTextTracks` argument. " + "Should have been an Array."); } this._priv_preferredTextTracks = tracks; - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.setPreferredTextTracks(tracks, shouldApply); - } else if (this._priv_mediaElementTrackChoiceManager !== null) { - this._priv_mediaElementTrackChoiceManager.setPreferredTextTracks(tracks, shouldApply); + var contentInfos = this._priv_contentInfos; + if (!(0,is_null_or_undefined/* default */.Z)(contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.trackChoiceManager)) { + contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.trackChoiceManager.setPreferredTextTracks(tracks, shouldApply); + } else if (!(0,is_null_or_undefined/* default */.Z)(contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.mediaElementTrackChoiceManager)) { + contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.mediaElementTrackChoiceManager.setPreferredTextTracks(tracks, shouldApply); } } /** @@ -56923,10 +53719,11 @@ var Player = /*#__PURE__*/function (_EventEmitter) { throw new Error("Invalid `setPreferredVideoTracks` argument. " + "Should have been an Array."); } this._priv_preferredVideoTracks = tracks; - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.setPreferredVideoTracks(tracks, shouldApply); - } else if (this._priv_mediaElementTrackChoiceManager !== null) { - this._priv_mediaElementTrackChoiceManager.setPreferredVideoTracks(tracks, shouldApply); + var contentInfos = this._priv_contentInfos; + if (!(0,is_null_or_undefined/* default */.Z)(contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.trackChoiceManager)) { + contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.trackChoiceManager.setPreferredVideoTracks(tracks, shouldApply); + } else if (!(0,is_null_or_undefined/* default */.Z)(contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.mediaElementTrackChoiceManager)) { + contentInfos === null || contentInfos === void 0 ? void 0 : contentInfos.mediaElementTrackChoiceManager.setPreferredVideoTracks(tracks, shouldApply); } } /** @@ -57004,14 +53801,12 @@ var Player = /*#__PURE__*/function (_EventEmitter) { */; _proto._priv_cleanUpCurrentContentState = function _priv_cleanUpCurrentContentState() { var _this4 = this; - var _a; + var _a, _b; log/* default.debug */.Z.debug("Locking `contentLock` to clean-up the current content."); // lock playback of new contents while cleaning up is pending this._priv_contentLock.setValue(true); + (_b = (_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.mediaElementTrackChoiceManager) === null || _b === void 0 ? void 0 : _b.dispose(); this._priv_contentInfos = null; - this._priv_trackChoiceManager = null; - (_a = this._priv_mediaElementTrackChoiceManager) === null || _a === void 0 ? void 0 : _a.dispose(); - this._priv_mediaElementTrackChoiceManager = null; this._priv_contentEventsMemory = {}; // DRM-related clean-up var freeUpContentLock = function freeUpContentLock() { @@ -57033,153 +53828,57 @@ var Player = /*#__PURE__*/function (_EventEmitter) { freeUpContentLock(); } } - /** - * Triggered each time the playback Observable emits. - * - * React to various events. - * - * @param {Object} event - payload emitted - */; - _proto._priv_onPlaybackEvent = function _priv_onPlaybackEvent(event) { - switch (event.type) { - case "inband-events": - var inbandEvents = event.value; - this.trigger("inbandEvents", inbandEvents); - return; - case "stream-event": - this.trigger("streamEvent", event.value); - break; - case "stream-event-skip": - this.trigger("streamEventSkip", event.value); - break; - case "activePeriodChanged": - this._priv_onActivePeriodChanged(event.value); - break; - case "periodStreamReady": - this._priv_onPeriodStreamReady(event.value); - break; - case "periodStreamCleared": - this._priv_onPeriodStreamCleared(event.value); - break; - case "reloading-media-source": - this._priv_onReloadingMediaSource(); - break; - case "representationChange": - this._priv_onRepresentationChange(event.value); - break; - case "adaptationChange": - this._priv_onAdaptationChange(event.value); - break; - case "bitrateEstimationChange": - this._priv_onBitrateEstimationChange(event.value); - break; - case "manifestReady": - this._priv_onManifestReady(event.value); - break; - case "warning": - this._priv_onPlaybackWarning(event.value); - break; - case "loaded": - if (this._priv_contentInfos === null) { - log/* default.error */.Z.error("API: Loaded event while no content is loaded"); - return; - } - this._priv_contentInfos.segmentBuffersStore = event.value.segmentBuffersStore; - break; - case "decipherabilityUpdate": - this.trigger("decipherabilityUpdate", event.value); - break; - case "added-segment": - if (this._priv_contentInfos === null) { - log/* default.error */.Z.error("API: Added segment while no content is loaded"); - return; - } - // Manage image tracks - // @deprecated - var _event$value = event.value, - content = _event$value.content, - segmentData = _event$value.segmentData; - if (content.adaptation.type === "image") { - if (!(0,is_null_or_undefined/* default */.Z)(segmentData) && segmentData.type === "bif") { - var imageData = segmentData.data; - /* eslint-disable import/no-deprecated */ - this._priv_contentInfos.thumbnails = imageData; - this.trigger("imageTrackUpdate", { - data: this._priv_contentInfos.thumbnails - }); - /* eslint-enable import/no-deprecated */ - } - } - } - } - /** - * Triggered when we received a fatal error. - * Clean-up ressources and signal that the content has stopped on error. - * @param {Error} error - */; - _proto._priv_onPlaybackError = function _priv_onPlaybackError(error) { - var formattedError = (0,format_error/* default */.Z)(error, { - defaultCode: "NONE", - defaultReason: "An unknown error stopped content playback." - }); - formattedError.fatal = true; - if (this._priv_contentInfos !== null) { - this._priv_contentInfos.currentContentCanceller.cancel(); - } - this._priv_cleanUpCurrentContentState(); - this._priv_currentError = formattedError; - log/* default.error */.Z.error("API: The player stopped because of an error", error instanceof Error ? error : ""); - this._priv_setPlayerState("STOPPED" /* PLAYER_STATES.STOPPED */); - // TODO This condition is here because the eventual callback called when the - // player state is updated can launch a new content, thus the error will not - // be here anymore, in which case triggering the "error" event is unwanted. - // This is very ugly though, and we should probable have a better solution - if (this._priv_currentError === formattedError) { - this.trigger("error", formattedError); - } - } - /** - * Triggered when we received a warning event during playback. - * Trigger the right API event. - * @param {Error} error - */; - _proto._priv_onPlaybackWarning = function _priv_onPlaybackWarning(error) { - var formattedError = (0,format_error/* default */.Z)(error, { - defaultCode: "NONE", - defaultReason: "An unknown error happened." - }); - log/* default.warn */.Z.warn("API: Sending warning:", formattedError); - this.trigger("warning", formattedError); - } /** * Triggered when the Manifest has been loaded for the current content. * Initialize various private properties and emit initial event. - * @param {Object} value + * @param {Object} contentInfos + * @param {Object} manifest */; - _proto._priv_onManifestReady = function _priv_onManifestReady(_ref2) { + _proto._priv_onManifestReady = function _priv_onManifestReady(contentInfos, manifest) { var _this5 = this; - var manifest = _ref2.manifest; - var contentInfos = this._priv_contentInfos; - if (contentInfos === null) { - log/* default.error */.Z.error("API: The manifest is loaded but no content is."); - return; + var _a; + if (contentInfos.contentId !== ((_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.contentId)) { + return; // Event for another content } + contentInfos.manifest = manifest; + var cancelSignal = contentInfos.currentContentCanceller.signal; this._priv_reloadingMetadata.manifest = manifest; var initialAudioTrack = contentInfos.initialAudioTrack, initialTextTrack = contentInfos.initialTextTrack; - this._priv_trackChoiceManager = new TrackChoiceManager({ + contentInfos.trackChoiceManager = new TrackChoiceManager({ preferTrickModeTracks: this._priv_preferTrickModeTracks }); var preferredAudioTracks = initialAudioTrack === undefined ? this._priv_preferredAudioTracks : [initialAudioTrack]; - this._priv_trackChoiceManager.setPreferredAudioTracks(preferredAudioTracks, true); + contentInfos.trackChoiceManager.setPreferredAudioTracks(preferredAudioTracks, true); var preferredTextTracks = initialTextTrack === undefined ? this._priv_preferredTextTracks : [initialTextTrack]; - this._priv_trackChoiceManager.setPreferredTextTracks(preferredTextTracks, true); - this._priv_trackChoiceManager.setPreferredVideoTracks(this._priv_preferredVideoTracks, true); - manifest.addEventListener("manifestUpdate", function () { + contentInfos.trackChoiceManager.setPreferredTextTracks(preferredTextTracks, true); + contentInfos.trackChoiceManager.setPreferredVideoTracks(this._priv_preferredVideoTracks, true); + manifest.addEventListener("manifestUpdate", function (updates) { + var _a, _b, _c; // Update the tracks chosen if it changed - if (_this5._priv_trackChoiceManager !== null) { - _this5._priv_trackChoiceManager.update(); + if (contentInfos.trackChoiceManager !== null) { + contentInfos.trackChoiceManager.update(); + } + var currentPeriod = (_b = (_a = _this5._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.currentPeriod) !== null && _b !== void 0 ? _b : undefined; + var trackChoiceManager = (_c = _this5._priv_contentInfos) === null || _c === void 0 ? void 0 : _c.trackChoiceManager; + if (currentPeriod === undefined || (0,is_null_or_undefined/* default */.Z)(trackChoiceManager)) { + return; + } + for (var _iterator = public_api_createForOfIteratorHelperLoose(updates.updatedPeriods), _step; !(_step = _iterator()).done;) { + var update = _step.value; + if (update.period.id === currentPeriod.id) { + if (update.result.addedAdaptations.length > 0 || update.result.removedAdaptations.length > 0) { + // We might have new (or less) tracks, send events just to be sure + var audioTracks = trackChoiceManager.getAvailableAudioTracks(currentPeriod); + _this5._priv_triggerEventIfNotStopped("availableAudioTracksChange", audioTracks !== null && audioTracks !== void 0 ? audioTracks : [], cancelSignal); + var textTracks = trackChoiceManager.getAvailableTextTracks(currentPeriod); + _this5._priv_triggerEventIfNotStopped("availableTextTracksChange", textTracks !== null && textTracks !== void 0 ? textTracks : [], cancelSignal); + var videoTracks = trackChoiceManager.getAvailableVideoTracks(currentPeriod); + _this5._priv_triggerEventIfNotStopped("availableVideoTracksChange", videoTracks !== null && videoTracks !== void 0 ? videoTracks : [], cancelSignal); + } + } + return; } }, contentInfos.currentContentCanceller.signal); } @@ -57187,114 +53886,136 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * Triggered each times the current Period Changed. * Store and emit initial state for the Period. * - * @param {Object} value + * @param {Object} contentInfos + * @param {Object} periodInfo */; - _proto._priv_onActivePeriodChanged = function _priv_onActivePeriodChanged(_ref3) { - var period = _ref3.period; - var _a, _b, _c, _d, _e, _f; - if (this._priv_contentInfos === null) { - log/* default.error */.Z.error("API: The active period changed but no content is loaded"); - return; + _proto._priv_onActivePeriodChanged = function _priv_onActivePeriodChanged(contentInfos, _ref) { + var period = _ref.period; + var _a, _b, _c, _d, _e, _f, _g, _h; + if (contentInfos.contentId !== ((_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.contentId)) { + return; // Event for another content } - this._priv_contentInfos.currentPeriod = period; + + contentInfos.currentPeriod = period; + var cancelSignal = contentInfos.currentContentCanceller.signal; if (this._priv_contentEventsMemory.periodChange !== period) { this._priv_contentEventsMemory.periodChange = period; - this.trigger("periodChange", period); + this._priv_triggerEventIfNotStopped("periodChange", period, cancelSignal); } - this.trigger("availableAudioTracksChange", this.getAvailableAudioTracks()); - this.trigger("availableTextTracksChange", this.getAvailableTextTracks()); - this.trigger("availableVideoTracksChange", this.getAvailableVideoTracks()); + this._priv_triggerEventIfNotStopped("availableAudioTracksChange", this.getAvailableAudioTracks(), cancelSignal); + this._priv_triggerEventIfNotStopped("availableTextTracksChange", this.getAvailableTextTracks(), cancelSignal); + this._priv_triggerEventIfNotStopped("availableVideoTracksChange", this.getAvailableVideoTracks(), cancelSignal); + var trackChoiceManager = (_b = this._priv_contentInfos) === null || _b === void 0 ? void 0 : _b.trackChoiceManager; // Emit intial events for the Period - if (this._priv_trackChoiceManager !== null) { - var audioTrack = this._priv_trackChoiceManager.getChosenAudioTrack(period); - var textTrack = this._priv_trackChoiceManager.getChosenTextTrack(period); - var videoTrack = this._priv_trackChoiceManager.getChosenVideoTrack(period); - this.trigger("audioTrackChange", audioTrack); - this.trigger("textTrackChange", textTrack); - this.trigger("videoTrackChange", videoTrack); + if (!(0,is_null_or_undefined/* default */.Z)(trackChoiceManager)) { + var audioTrack = trackChoiceManager.getChosenAudioTrack(period); + this._priv_triggerEventIfNotStopped("audioTrackChange", audioTrack, cancelSignal); + var textTrack = trackChoiceManager.getChosenTextTrack(period); + this._priv_triggerEventIfNotStopped("textTrackChange", textTrack, cancelSignal); + var videoTrack = trackChoiceManager.getChosenVideoTrack(period); + this._priv_triggerEventIfNotStopped("videoTrackChange", videoTrack, cancelSignal); } else { - this.trigger("audioTrackChange", null); - this.trigger("textTrackChange", null); - this.trigger("videoTrackChange", null); + this._priv_triggerEventIfNotStopped("audioTrackChange", null, cancelSignal); + this._priv_triggerEventIfNotStopped("textTrackChange", null, cancelSignal); + this._priv_triggerEventIfNotStopped("videoTrackChange", null, cancelSignal); + } + this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange", this.getAvailableAudioBitrates(), cancelSignal); + if (contentInfos.currentContentCanceller.isUsed()) { + return; + } + this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange", this.getAvailableVideoBitrates(), cancelSignal); + if (contentInfos.currentContentCanceller.isUsed()) { + return; + } + var audioBitrate = (_e = (_d = (_c = this._priv_getCurrentRepresentations()) === null || _c === void 0 ? void 0 : _c.audio) === null || _d === void 0 ? void 0 : _d.bitrate) !== null && _e !== void 0 ? _e : -1; + this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", audioBitrate, cancelSignal); + if (contentInfos.currentContentCanceller.isUsed()) { + return; } - this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange", this.getAvailableAudioBitrates()); - this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange", this.getAvailableVideoBitrates()); - var audioBitrate = (_c = (_b = (_a = this._priv_getCurrentRepresentations()) === null || _a === void 0 ? void 0 : _a.audio) === null || _b === void 0 ? void 0 : _b.bitrate) !== null && _c !== void 0 ? _c : -1; - this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", audioBitrate); - var videoBitrate = (_f = (_e = (_d = this._priv_getCurrentRepresentations()) === null || _d === void 0 ? void 0 : _d.video) === null || _e === void 0 ? void 0 : _e.bitrate) !== null && _f !== void 0 ? _f : -1; - this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", videoBitrate); + var videoBitrate = (_h = (_g = (_f = this._priv_getCurrentRepresentations()) === null || _f === void 0 ? void 0 : _f.video) === null || _g === void 0 ? void 0 : _g.bitrate) !== null && _h !== void 0 ? _h : -1; + this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", videoBitrate, cancelSignal); } /** * Triggered each times a new "PeriodStream" is ready. * Choose the right Adaptation for the Period and emit it. + * @param {Object} contentInfos * @param {Object} value */; - _proto._priv_onPeriodStreamReady = function _priv_onPeriodStreamReady(value) { + _proto._priv_onPeriodStreamReady = function _priv_onPeriodStreamReady(contentInfos, value) { + var _a; + if (contentInfos.contentId !== ((_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.contentId)) { + return; // Event for another content + } + var type = value.type, period = value.period, - adaptation$ = value.adaptation$; + adaptationRef = value.adaptationRef; + var trackChoiceManager = contentInfos.trackChoiceManager; switch (type) { case "video": - if (this._priv_trackChoiceManager === null) { + if ((0,is_null_or_undefined/* default */.Z)(trackChoiceManager)) { log/* default.error */.Z.error("API: TrackChoiceManager not instanciated for a new video period"); - adaptation$.next(null); + adaptationRef.setValue(null); } else { - this._priv_trackChoiceManager.addPeriod(type, period, adaptation$); - this._priv_trackChoiceManager.setInitialVideoTrack(period); + trackChoiceManager.addPeriod(type, period, adaptationRef); + trackChoiceManager.setInitialVideoTrack(period); } break; case "audio": - if (this._priv_trackChoiceManager === null) { + if ((0,is_null_or_undefined/* default */.Z)(trackChoiceManager)) { log/* default.error */.Z.error("API: TrackChoiceManager not instanciated for a new " + type + " period"); - adaptation$.next(null); + adaptationRef.setValue(null); } else { - this._priv_trackChoiceManager.addPeriod(type, period, adaptation$); - this._priv_trackChoiceManager.setInitialAudioTrack(period); + trackChoiceManager.addPeriod(type, period, adaptationRef); + trackChoiceManager.setInitialAudioTrack(period); } break; case "text": - if (this._priv_trackChoiceManager === null) { + if ((0,is_null_or_undefined/* default */.Z)(trackChoiceManager)) { log/* default.error */.Z.error("API: TrackChoiceManager not instanciated for a new " + type + " period"); - adaptation$.next(null); + adaptationRef.setValue(null); } else { - this._priv_trackChoiceManager.addPeriod(type, period, adaptation$); - this._priv_trackChoiceManager.setInitialTextTrack(period); + trackChoiceManager.addPeriod(type, period, adaptationRef); + trackChoiceManager.setInitialTextTrack(period); } break; default: var adaptations = period.adaptations[type]; if (!(0,is_null_or_undefined/* default */.Z)(adaptations) && adaptations.length > 0) { - adaptation$.next(adaptations[0]); + adaptationRef.setValue(adaptations[0]); } else { - adaptation$.next(null); + adaptationRef.setValue(null); } break; } } /** * Triggered each times we "remove" a PeriodStream. + * @param {Object} contentInfos * @param {Object} value */; - _proto._priv_onPeriodStreamCleared = function _priv_onPeriodStreamCleared(value) { + _proto._priv_onPeriodStreamCleared = function _priv_onPeriodStreamCleared(contentInfos, value) { + var _a; + if (contentInfos.contentId !== ((_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.contentId)) { + return; // Event for another content + } + var type = value.type, period = value.period; + var trackChoiceManager = contentInfos.trackChoiceManager; // Clean-up track choice from TrackChoiceManager switch (type) { case "audio": case "text": case "video": - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.removePeriod(type, period); + if (!(0,is_null_or_undefined/* default */.Z)(trackChoiceManager)) { + trackChoiceManager.removePeriod(type, period); } break; } // Clean-up stored Representation and Adaptation information - if (this._priv_contentInfos === null) { - return; - } - var _this$_priv_contentIn20 = this._priv_contentInfos, - activeAdaptations = _this$_priv_contentIn20.activeAdaptations, - activeRepresentations = _this$_priv_contentIn20.activeRepresentations; + var activeAdaptations = contentInfos.activeAdaptations, + activeRepresentations = contentInfos.activeRepresentations; if (!(0,is_null_or_undefined/* default */.Z)(activeAdaptations) && !(0,is_null_or_undefined/* default */.Z)(activeAdaptations[period.id])) { var activePeriodAdaptations = activeAdaptations[period.id]; delete activePeriodAdaptations[type]; @@ -57310,38 +54031,27 @@ var Player = /*#__PURE__*/function (_EventEmitter) { } } } - /** - * Triggered each time the content is re-loaded on the MediaSource. - */; - _proto._priv_onReloadingMediaSource = function _priv_onReloadingMediaSource() { - if (this._priv_contentInfos !== null) { - this._priv_contentInfos.segmentBuffersStore = null; - } - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.resetPeriods(); - } - } /** * Triggered each times a new Adaptation is considered for the current * content. * Store given Adaptation and emit it if from the current Period. + * @param {Object} contentInfos * @param {Object} value */; - _proto._priv_onAdaptationChange = function _priv_onAdaptationChange(_ref4) { - var type = _ref4.type, - adaptation = _ref4.adaptation, - period = _ref4.period; - if (this._priv_contentInfos === null) { - log/* default.error */.Z.error("API: The adaptations changed but no content is loaded"); - return; + _proto._priv_onAdaptationChange = function _priv_onAdaptationChange(contentInfos, _ref2) { + var type = _ref2.type, + adaptation = _ref2.adaptation, + period = _ref2.period; + var _a; + if (contentInfos.contentId !== ((_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.contentId)) { + return; // Event for another content } - // lazily create this._priv_contentInfos.activeAdaptations - if (this._priv_contentInfos.activeAdaptations === null) { - this._priv_contentInfos.activeAdaptations = {}; + // lazily create contentInfos.activeAdaptations + if (contentInfos.activeAdaptations === null) { + contentInfos.activeAdaptations = {}; } - var _this$_priv_contentIn21 = this._priv_contentInfos, - activeAdaptations = _this$_priv_contentIn21.activeAdaptations, - currentPeriod = _this$_priv_contentIn21.currentPeriod; + var activeAdaptations = contentInfos.activeAdaptations, + currentPeriod = contentInfos.currentPeriod; var activePeriodAdaptations = activeAdaptations[period.id]; if ((0,is_null_or_undefined/* default */.Z)(activePeriodAdaptations)) { var _activeAdaptations$pe; @@ -57349,23 +54059,25 @@ var Player = /*#__PURE__*/function (_EventEmitter) { } else { activePeriodAdaptations[type] = adaptation; } - if (this._priv_trackChoiceManager !== null && currentPeriod !== null && !(0,is_null_or_undefined/* default */.Z)(period) && period.id === currentPeriod.id) { + var trackChoiceManager = contentInfos.trackChoiceManager; + var cancelSignal = contentInfos.currentContentCanceller.signal; + if (trackChoiceManager !== null && currentPeriod !== null && !(0,is_null_or_undefined/* default */.Z)(period) && period.id === currentPeriod.id) { switch (type) { case "audio": - var audioTrack = this._priv_trackChoiceManager.getChosenAudioTrack(currentPeriod); - this.trigger("audioTrackChange", audioTrack); + var audioTrack = trackChoiceManager.getChosenAudioTrack(currentPeriod); + this._priv_triggerEventIfNotStopped("audioTrackChange", audioTrack, cancelSignal); var availableAudioBitrates = this.getAvailableAudioBitrates(); - this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange", availableAudioBitrates); + this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange", availableAudioBitrates, cancelSignal); break; case "text": - var textTrack = this._priv_trackChoiceManager.getChosenTextTrack(currentPeriod); - this.trigger("textTrackChange", textTrack); + var textTrack = trackChoiceManager.getChosenTextTrack(currentPeriod); + this._priv_triggerEventIfNotStopped("textTrackChange", textTrack, cancelSignal); break; case "video": - var videoTrack = this._priv_trackChoiceManager.getChosenVideoTrack(currentPeriod); - this.trigger("videoTrackChange", videoTrack); + var videoTrack = trackChoiceManager.getChosenVideoTrack(currentPeriod); + this._priv_triggerEventIfNotStopped("videoTrackChange", videoTrack, cancelSignal); var availableVideoBitrates = this.getAvailableVideoBitrates(); - this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange", availableVideoBitrates); + this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange", availableVideoBitrates, cancelSignal); break; } } @@ -57375,24 +54087,23 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * * Store given Representation and emit it if from the current Period. * + * @param {Object} contentInfos * @param {Object} obj */; - _proto._priv_onRepresentationChange = function _priv_onRepresentationChange(_ref5) { - var type = _ref5.type, - period = _ref5.period, - representation = _ref5.representation; - var _a; - if (this._priv_contentInfos === null) { - log/* default.error */.Z.error("API: The representations changed but no content is loaded"); - return; + _proto._priv_onRepresentationChange = function _priv_onRepresentationChange(contentInfos, _ref3) { + var type = _ref3.type, + period = _ref3.period, + representation = _ref3.representation; + var _a, _b; + if (contentInfos.contentId !== ((_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.contentId)) { + return; // Event for another content } - // lazily create this._priv_contentInfos.activeRepresentations - if (this._priv_contentInfos.activeRepresentations === null) { - this._priv_contentInfos.activeRepresentations = {}; + // lazily create contentInfos.activeRepresentations + if (contentInfos.activeRepresentations === null) { + contentInfos.activeRepresentations = {}; } - var _this$_priv_contentIn22 = this._priv_contentInfos, - activeRepresentations = _this$_priv_contentIn22.activeRepresentations, - currentPeriod = _this$_priv_contentIn22.currentPeriod; + var activeRepresentations = contentInfos.activeRepresentations, + currentPeriod = contentInfos.currentPeriod; var activePeriodRepresentations = activeRepresentations[period.id]; if ((0,is_null_or_undefined/* default */.Z)(activePeriodRepresentations)) { var _activeRepresentation; @@ -57400,12 +54111,13 @@ var Player = /*#__PURE__*/function (_EventEmitter) { } else { activePeriodRepresentations[type] = representation; } - var bitrate = (_a = representation === null || representation === void 0 ? void 0 : representation.bitrate) !== null && _a !== void 0 ? _a : -1; + var bitrate = (_b = representation === null || representation === void 0 ? void 0 : representation.bitrate) !== null && _b !== void 0 ? _b : -1; if (!(0,is_null_or_undefined/* default */.Z)(period) && currentPeriod !== null && currentPeriod.id === period.id) { + var cancelSignal = this._priv_contentInfos.currentContentCanceller.signal; if (type === "video") { - this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", bitrate); + this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", bitrate, cancelSignal); } else if (type === "audio") { - this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", bitrate); + this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", bitrate, cancelSignal); } } } @@ -57416,9 +54128,9 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * * @param {Object} value */; - _proto._priv_onBitrateEstimationChange = function _priv_onBitrateEstimationChange(_ref6) { - var type = _ref6.type, - bitrate = _ref6.bitrate; + _proto._priv_onBitrateEstimationChange = function _priv_onBitrateEstimationChange(_ref4) { + var type = _ref4.type, + bitrate = _ref4.bitrate; if (bitrate !== undefined) { this._priv_bitrateInfos.lastBitrates[type] = bitrate; } @@ -57456,17 +54168,17 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * * Trigger the right Player Event * + * @param {Object} contentInfos * @param {Object} observation */; - _proto._priv_triggerPositionUpdate = function _priv_triggerPositionUpdate(observation) { - var _a; - if (this._priv_contentInfos === null) { - log/* default.warn */.Z.warn("API: Cannot perform time update: no content loaded."); - return; + _proto._priv_triggerPositionUpdate = function _priv_triggerPositionUpdate(contentInfos, observation) { + var _a, _b; + if (contentInfos.contentId !== ((_a = this._priv_contentInfos) === null || _a === void 0 ? void 0 : _a.contentId)) { + return; // Event for another content } - var _this$_priv_contentIn23 = this._priv_contentInfos, - isDirectFile = _this$_priv_contentIn23.isDirectFile, - manifest = _this$_priv_contentIn23.manifest; + + var isDirectFile = contentInfos.isDirectFile, + manifest = contentInfos.manifest; if (!isDirectFile && manifest === null || (0,is_null_or_undefined/* default */.Z)(observation)) { return; } @@ -57480,7 +54192,7 @@ var Player = /*#__PURE__*/function (_EventEmitter) { bufferGap: observation.bufferGap === undefined || !isFinite(observation.bufferGap) ? 0 : observation.bufferGap }; if (manifest !== null && manifest.isLive && observation.position > 0) { - var ast = (_a = manifest.availabilityStartTime) !== null && _a !== void 0 ? _a : 0; + var ast = (_b = manifest.availabilityStartTime) !== null && _b !== void 0 ? _b : 0; positionData.wallClockTime = observation.position + ast; var livePosition = manifest.getLivePosition(); if (livePosition !== undefined) { @@ -57499,10 +54211,11 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * the previously stored value. * @param {string} event * @param {Array.} newVal + * @param {Object} currentContentCancelSignal */; - _proto._priv_triggerAvailableBitratesChangeEvent = function _priv_triggerAvailableBitratesChangeEvent(event, newVal) { + _proto._priv_triggerAvailableBitratesChangeEvent = function _priv_triggerAvailableBitratesChangeEvent(event, newVal, currentContentCancelSignal) { var prevVal = this._priv_contentEventsMemory[event]; - if (prevVal === undefined || !(0,are_arrays_of_numbers_equal/* default */.Z)(newVal, prevVal)) { + if (!currentContentCancelSignal.isCancelled() && (prevVal === undefined || !(0,are_arrays_of_numbers_equal/* default */.Z)(newVal, prevVal))) { this._priv_contentEventsMemory[event] = newVal; this.trigger(event, newVal); } @@ -57512,9 +54225,10 @@ var Player = /*#__PURE__*/function (_EventEmitter) { * previously stored value. * @param {string} event * @param {number} newVal + * @param {Object} currentContentCancelSignal */; - _proto._priv_triggerCurrentBitrateChangeEvent = function _priv_triggerCurrentBitrateChangeEvent(event, newVal) { - if (newVal !== this._priv_contentEventsMemory[event]) { + _proto._priv_triggerCurrentBitrateChangeEvent = function _priv_triggerCurrentBitrateChangeEvent(event, newVal, currentContentCancelSignal) { + if (!currentContentCancelSignal.isCancelled() && newVal !== this._priv_contentEventsMemory[event]) { this._priv_contentEventsMemory[event] = newVal; this.trigger(event, newVal); } @@ -57523,13 +54237,66 @@ var Player = /*#__PURE__*/function (_EventEmitter) { if (this._priv_contentInfos === null) { return null; } - var _this$_priv_contentIn24 = this._priv_contentInfos, - currentPeriod = _this$_priv_contentIn24.currentPeriod, - activeRepresentations = _this$_priv_contentIn24.activeRepresentations; + var _this$_priv_contentIn20 = this._priv_contentInfos, + currentPeriod = _this$_priv_contentIn20.currentPeriod, + activeRepresentations = _this$_priv_contentIn20.activeRepresentations; if (currentPeriod === null || activeRepresentations === null || (0,is_null_or_undefined/* default */.Z)(activeRepresentations[currentPeriod.id])) { return null; } return activeRepresentations[currentPeriod.id]; + } + /** + * @param {string} evt + * @param {*} arg + * @param {Object} currentContentCancelSignal + */; + _proto._priv_triggerEventIfNotStopped = function _priv_triggerEventIfNotStopped(evt, arg, currentContentCancelSignal) { + if (!currentContentCancelSignal.isCancelled()) { + this.trigger(evt, arg); + } + } + /** + * @param {Object} defaultAudioTrack + * @param {Object} defaultTextTrack + * @param {Object} cancelSignal + * @returns {Object} + */; + _proto._priv_initializeMediaElementTrackChoiceManager = function _priv_initializeMediaElementTrackChoiceManager(defaultAudioTrack, defaultTextTrack, cancelSignal) { + var _this6 = this; + var _a, _b, _c; + (0,assert/* default */.Z)(features/* default.directfile */.Z.directfile !== null, "Initializing `MediaElementTrackChoiceManager` without Directfile feature"); + (0,assert/* default */.Z)(this.videoElement !== null, "Initializing `MediaElementTrackChoiceManager` on a disposed RxPlayer"); + var mediaElementTrackChoiceManager = new features/* default.directfile.mediaElementTrackChoiceManager */.Z.directfile.mediaElementTrackChoiceManager(this.videoElement); + var preferredAudioTracks = defaultAudioTrack === undefined ? this._priv_preferredAudioTracks : [defaultAudioTrack]; + mediaElementTrackChoiceManager.setPreferredAudioTracks(preferredAudioTracks, true); + var preferredTextTracks = defaultTextTrack === undefined ? this._priv_preferredTextTracks : [defaultTextTrack]; + mediaElementTrackChoiceManager.setPreferredTextTracks(preferredTextTracks, true); + mediaElementTrackChoiceManager.setPreferredVideoTracks(this._priv_preferredVideoTracks, true); + this._priv_triggerEventIfNotStopped("availableAudioTracksChange", mediaElementTrackChoiceManager.getAvailableAudioTracks(), cancelSignal); + this._priv_triggerEventIfNotStopped("availableVideoTracksChange", mediaElementTrackChoiceManager.getAvailableVideoTracks(), cancelSignal); + this._priv_triggerEventIfNotStopped("availableTextTracksChange", mediaElementTrackChoiceManager.getAvailableTextTracks(), cancelSignal); + this._priv_triggerEventIfNotStopped("audioTrackChange", (_a = mediaElementTrackChoiceManager.getChosenAudioTrack()) !== null && _a !== void 0 ? _a : null, cancelSignal); + this._priv_triggerEventIfNotStopped("textTrackChange", (_b = mediaElementTrackChoiceManager.getChosenTextTrack()) !== null && _b !== void 0 ? _b : null, cancelSignal); + this._priv_triggerEventIfNotStopped("videoTrackChange", (_c = mediaElementTrackChoiceManager.getChosenVideoTrack()) !== null && _c !== void 0 ? _c : null, cancelSignal); + mediaElementTrackChoiceManager.addEventListener("availableVideoTracksChange", function (val) { + return _this6.trigger("availableVideoTracksChange", val); + }); + mediaElementTrackChoiceManager.addEventListener("availableAudioTracksChange", function (val) { + return _this6.trigger("availableAudioTracksChange", val); + }); + mediaElementTrackChoiceManager.addEventListener("availableTextTracksChange", function (val) { + return _this6.trigger("availableTextTracksChange", val); + }); + mediaElementTrackChoiceManager.addEventListener("audioTrackChange", function (val) { + return _this6.trigger("audioTrackChange", val); + }); + mediaElementTrackChoiceManager.addEventListener("videoTrackChange", function (val) { + return _this6.trigger("videoTrackChange", val); + }); + mediaElementTrackChoiceManager.addEventListener("textTrackChange", function (val) { + return _this6.trigger("textTrackChange", val); + }); + return mediaElementTrackChoiceManager; }; (0,createClass/* default */.Z)(Player, null, [{ key: "ErrorTypes", @@ -57565,7 +54332,7 @@ var Player = /*#__PURE__*/function (_EventEmitter) { }]); return Player; }(event_emitter/* default */.Z); -Player.version = /* PLAYER_VERSION */"3.29.0"; +Player.version = /* PLAYER_VERSION */"3.30.0"; /* harmony default export */ var public_api = (Player); ;// CONCATENATED MODULE: ./src/core/api/index.ts /** @@ -57623,7 +54390,7 @@ function initializeFeaturesObject() { features_object/* default.imageParser */.Z.imageParser = (__webpack_require__(3203)/* ["default"] */ .Z); } // Feature switching the Native TextTrack implementation - var HAS_NATIVE_MODE = 1 || 0 || 0 || 0; + var HAS_NATIVE_MODE = true || 0; if (true) { features_object/* default.transports.smooth */.Z.transports.smooth = (__webpack_require__(2339)/* ["default"] */ .Z); } @@ -57633,7 +54400,8 @@ function initializeFeaturesObject() { } if (false) {} if (false) {} - if (HAS_NATIVE_MODE === 1) { + if (false) {} + if (HAS_NATIVE_MODE) { features_object/* default.nativeTextTracksBuffer */.Z.nativeTextTracksBuffer = (__webpack_require__(9059)/* ["default"] */ .Z); if (true) { features_object/* default.nativeTextTracksParsers.vtt */.Z.nativeTextTracksParsers.vtt = (__webpack_require__(9405)/* ["default"] */ .Z); @@ -57649,8 +54417,8 @@ function initializeFeaturesObject() { } } // Feature switching the HTML TextTrack implementation - var HAS_HTML_MODE = 1 || 0 || 0 || 0; - if (HAS_HTML_MODE === 1) { + var HAS_HTML_MODE = true || 0; + if (HAS_HTML_MODE) { features_object/* default.htmlTextTracksBuffer */.Z.htmlTextTracksBuffer = (__webpack_require__(5192)/* ["default"] */ .Z); if (true) { features_object/* default.htmlTextTracksParsers.sami */.Z.htmlTextTracksParsers.sami = (__webpack_require__(5734)/* ["default"] */ .Z); @@ -57666,7 +54434,7 @@ function initializeFeaturesObject() { } } if (true) { - var initDirectFile = (__webpack_require__(8969)/* ["default"] */ .Z); + var initDirectFile = (__webpack_require__(9372)/* ["default"] */ .Z); var mediaElementTrackChoiceManager = (__webpack_require__(6796)/* ["default"] */ .Z); features_object/* default.directfile */.Z.directfile = { initDirectFile: initDirectFile, diff --git a/dist/rx-player.min.js b/dist/rx-player.min.js index ae25ceec7f..14745795d9 100644 --- a/dist/rx-player.min.js +++ b/dist/rx-player.min.js @@ -1,2 +1,2 @@ /*! For license information please see rx-player.min.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.RxPlayer=t():e.RxPlayer=t()}(self,(function(){return function(){var e={3774:function(e,t,n){"use strict";n.d(t,{J:function(){return a},c:function(){return o}});var r=n(1946),i=n(2203).Z?void 0:window,a=void 0===i?void 0:(0,r.Z)(i.MediaSource)?(0,r.Z)(i.MozMediaSource)?(0,r.Z)(i.WebKitMediaSource)?i.MSMediaSource:i.WebKitMediaSource:i.MozMediaSource:i.MediaSource,o={HAVE_NOTHING:0,HAVE_METADATA:1,HAVE_CURRENT_DATA:2,HAVE_FUTURE_DATA:3,HAVE_ENOUGH_DATA:4}},3666:function(e,t,n){"use strict";n.d(t,{SB:function(){return m},YM:function(){return s},fq:function(){return o},gM:function(){return v},kD:function(){return u},op:function(){return c},uz:function(){return p},vS:function(){return h},vU:function(){return l},yS:function(){return d}});var r,i,a=n(2203),o=!a.Z&&void 0!==window.MSInputMethodContext&&void 0!==document.documentMode,s=!a.Z&&("Microsoft Internet Explorer"===navigator.appName||"Netscape"===navigator.appName&&/(Trident|Edge)\//.test(navigator.userAgent)),u=!a.Z&&-1!==navigator.userAgent.toLowerCase().indexOf("edg/"),l=!a.Z&&-1!==navigator.userAgent.toLowerCase().indexOf("firefox"),c=!a.Z&&/SamsungBrowser/.test(navigator.userAgent),d=!a.Z&&/Tizen/.test(navigator.userAgent),f=!a.Z&&navigator.userAgent.indexOf("Web0S")>=0,v=f&&(/[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent)||/[Cc]hr[o0]me\/79/.test(navigator.userAgent)),p=f&&(/[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent)||/[Cc]hr[o0]me\/87/.test(navigator.userAgent)),h=!a.Z&&(Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>=0||"[object SafariRemoteNotification]"===(null===(i=null===(r=window.safari)||void 0===r?void 0:r.pushNotification)||void 0===i?void 0:i.toString())),m=!a.Z&&"string"==typeof navigator.platform&&/iPad|iPhone|iPod/.test(navigator.platform)},5767:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(3887),i=n(1946);function a(e){var t=e.textTracks;if(!(0,i.Z)(t)){for(var n=0;n=0;o--)if("track"===a[o].nodeName)try{e.removeChild(a[o])}catch(e){r.Z.warn("Compat: Could not remove text track child from element.")}}e.src="",e.removeAttribute("src")}},6139:function(e,t,n){"use strict";n.d(t,{N:function(){return P},Y:function(){return D}});var r,i=n(3714),a=n(811),o=n(3666),s=n(2203),u=n(5059),l=n(3144),c=function(){function e(e,t,n){this._keyType=e,this._mediaKeys=t,this._configuration=n}var t=e.prototype;return t.createMediaKeys=function(){var e=this;return new Promise((function(t){return t(e._mediaKeys)}))},t.getConfiguration=function(){return this._configuration},(0,l.Z)(e,[{key:"keySystem",get:function(){return this._keyType}}]),e}();if(!s.Z){var d=window.MSMediaKeys;void 0!==d&&void 0!==d.prototype&&"function"==typeof d.isTypeSupported&&"function"==typeof d.prototype.createSession&&(r=d)}var f,v=n(4578),p=n(6716),h=n(3071),m=n(3505),g=n(1959),y=n(1381),_=function(e){function t(t){var n;return(n=e.call(this)||this).expiration=NaN,n.keyStatuses=new Map,n._mk=t,n._closeSession$=new p.x,n.closed=new Promise((function(e){n._closeSession$.subscribe(e)})),n.update=function(e){return new Promise((function(t,r){if(void 0===n._ss)return r("MediaKeySession not set.");try{t(n._ss.update(e,""))}catch(e){r(e)}}))},n}(0,v.Z)(t,e);var n=t.prototype;return n.generateRequest=function(e,t){var n=this;return new Promise((function(e){var r=t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer);n._ss=n._mk.createSession("video/mp4",r),(0,h.T)(y.GJ(n._ss),y.GV(n._ss),y.Xe(n._ss)).pipe((0,m.R)(n._closeSession$)).subscribe((function(e){return n.trigger(e.type,e)})),e()}))},n.close=function(){var e=this;return new Promise((function(t){null!=e._ss&&(e._ss.close(),e._ss=void 0),e._closeSession$.next(),e._closeSession$.complete(),t()}))},n.load=function(){return Promise.resolve(!1)},n.remove=function(){return Promise.resolve()},(0,l.Z)(t,[{key:"sessionId",get:function(){var e,t;return null!==(t=null===(e=this._ss)||void 0===e?void 0:e.sessionId)&&void 0!==t?t:""}}]),t}(g.Z),b=function(){function e(e){if(void 0===r)throw new Error("No MSMediaKeys API.");this._mediaKeys=new r(e)}var t=e.prototype;return t._setVideo=function(e){if(this._videoElement=e,void 0!==this._videoElement.msSetMediaKeys)return this._videoElement.msSetMediaKeys(this._mediaKeys)},t.createSession=function(){if(void 0===this._videoElement||void 0===this._mediaKeys)throw new Error("Video not attached to the MediaKeys");return new _(this._mediaKeys)},t.setServerCertificate=function(){throw new Error("Server certificate is not implemented in your browser")},e}();if(!s.Z){var T=window.MozMediaKeys;void 0!==T&&void 0!==T.prototype&&"function"==typeof T.isTypeSupported&&"function"==typeof T.prototype.createSession&&(f=T)}var E=n(9689),S=n(8894),w=n(3635);function k(e){return"function"==typeof e.webkitGenerateKeyRequest}var A=function(e){function t(t,n){var r;(r=e.call(this)||this)._vid=t,r._key=n,r.sessionId="",r._closeSession=S.Z,r.keyStatuses=new Map,r.expiration=NaN;var i=function(e){r.trigger(e.type,e)};return r.closed=new Promise((function(e){r._closeSession=function(){["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(e){t.removeEventListener(e,i),t.removeEventListener("webkit"+e,i)})),e()}})),["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(e){t.addEventListener(e,i),t.addEventListener("webkit"+e,i)})),r}(0,v.Z)(t,e);var n=t.prototype;return n.update=function(e){var t=this;return new Promise((function(n,r){try{if(t._key.indexOf("clearkey")>=0){var i=e instanceof ArrayBuffer?new Uint8Array(e):e,a=JSON.parse((0,w.uR)(i)),o=(0,E.K)(a.keys[0].k),s=(0,E.K)(a.keys[0].kid);n(t._vid.webkitAddKey(t._key,o,s,""))}else n(t._vid.webkitAddKey(t._key,e,null,""))}catch(e){r(e)}}))},n.generateRequest=function(e,t){var n=this;return new Promise((function(e){n._vid.webkitGenerateKeyRequest(n._key,t),e()}))},n.close=function(){var e=this;return new Promise((function(t){e._closeSession(),t()}))},n.load=function(){return Promise.resolve(!1)},n.remove=function(){return Promise.resolve()},t}(g.Z),x=function(){function e(e){this._keySystem=e}var t=e.prototype;return t._setVideo=function(e){if(!k(e))throw new Error("Video not attached to the MediaKeys");this._videoElement=e},t.createSession=function(){if(null==this._videoElement)throw new Error("Video not attached to the MediaKeys");return new A(this._videoElement,this._keySystem)},t.setServerCertificate=function(){throw new Error("Server certificate is not implemented in your browser")},e}();var I=n(6968);var Z=n(158);function R(e,t){var n=e;if(void 0===n.webkitSetMediaKeys)throw new Error("No webKitMediaKeys API.");return n.webkitSetMediaKeys(t)}var M=function(e){function t(t,n,r){var i;return(i=e.call(this)||this)._serverCertificate=r,i._videoElement=t,i._keyType=n,i._unbindSession=S.Z,i._closeSession=S.Z,i.closed=new Promise((function(e){i._closeSession=e})),i.keyStatuses=new Map,i.expiration=NaN,i}(0,v.Z)(t,e);var n=t.prototype;return n.update=function(e){var t=this;return new Promise((function(n,r){if(void 0===t._nativeSession||void 0===t._nativeSession.update||"function"!=typeof t._nativeSession.update)return r("Unavailable WebKit key session.");try{var i;i=e instanceof ArrayBuffer?new Uint8Array(e):e instanceof Uint8Array?e:new Uint8Array(e.buffer),n(t._nativeSession.update(i))}catch(e){r(e)}}))},n.generateRequest=function(e,t){var n=this;return new Promise((function(e){var r,i,a,o=n._videoElement;if(void 0===(null===(r=o.webkitKeys)||void 0===r?void 0:r.createSession))throw new Error("No WebKitMediaKeys API.");if("com.apple.fps.1_0"===(a=n._keyType)||"com.apple.fps.2_0"===a){if(void 0===n._serverCertificate)throw new Error("A server certificate is needed for creating fairplay session.");i=function(e,t){var n=e instanceof Uint8Array?e:new Uint8Array(e),r=t instanceof Uint8Array?t:new Uint8Array(t);if((0,I.dN)(n,0)+4!==n.length)throw new Error("Unsupported WebKit initData.");var i=(0,w.wV)(n),a=i.indexOf("skd://"),o=a>-1?i.substring(a+6):i,s=(0,w.TZ)(o),u=0,l=new Uint8Array(n.byteLength+4+s.byteLength+4+r.byteLength);return l.set(n),u+=n.length,l.set((0,I.O_)(s.byteLength),u),u+=4,l.set(s,u),u+=s.byteLength,l.set((0,I.O_)(r.byteLength),u),u+=4,l.set(r,u),l}(t,n._serverCertificate)}else i=t;var s=o.webkitKeys.createSession("video/mp4",i);if(null==s)throw new Error("Impossible to get the key sessions");n._listenEvent(s),n._nativeSession=s,e()}))},n.close=function(){var e=this;return new Promise((function(t,n){e._unbindSession(),e._closeSession(),void 0!==e._nativeSession?(e._nativeSession.close(),t()):n("No session to close.")}))},n.load=function(){return Promise.resolve(!1)},n.remove=function(){return Promise.resolve()},n._listenEvent=function(e){var t=this;this._unbindSession();var n=function(e){t.trigger(e.type,e)};["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(t){e.addEventListener(t,n),e.addEventListener("webkit"+t,n)})),this._unbindSession=function(){["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(t){e.removeEventListener(t,n),e.removeEventListener("webkit"+t,n)}))}},(0,l.Z)(t,[{key:"sessionId",get:function(){var e,t;return null!==(t=null===(e=this._nativeSession)||void 0===e?void 0:e.sessionId)&&void 0!==t?t:""}}]),t}(g.Z),C=function(){function e(e){if(void 0===Z.t)throw new Error("No WebKitMediaKeys API.");this._keyType=e,this._mediaKeys=new Z.t(e)}var t=e.prototype;return t._setVideo=function(e){if(this._videoElement=e,void 0===this._videoElement)throw new Error("Video not attached to the MediaKeys");return R(this._videoElement,this._mediaKeys)},t.createSession=function(){if(void 0===this._videoElement||void 0===this._mediaKeys)throw new Error("Video not attached to the MediaKeys");return new M(this._videoElement,this._keyType,this._serverCertificate)},t.setServerCertificate=function(e){return this._serverCertificate=e,Promise.resolve()},e}();var P=null,D=function(e,t){var n=e;return"function"==typeof n.setMediaKeys?n.setMediaKeys(t):"function"==typeof n.webkitSetMediaKeys?n.webkitSetMediaKeys(t):"function"==typeof n.mozSetMediaKeys?n.mozSetMediaKeys(t):"function"==typeof n.msSetMediaKeys&&null!==t?n.msSetMediaKeys(t):void 0};if(s.Z||null!=navigator.requestMediaKeySystemAccess&&!(0,u.Z)())P=function(){var e;return(e=navigator).requestMediaKeySystemAccess.apply(e,arguments)};else{var N,O;if(k(HTMLVideoElement.prototype)){var L={isTypeSupported:function(e){var t=document.querySelector("video");return null==t&&(t=document.createElement("video")),null!=t&&"function"==typeof t.canPlayType&&!!t.canPlayType("video/mp4",e)},createCustomMediaKeys:function(e){return new x(e)},setMediaKeys:function(e,t){if(null!==t){if(!(t instanceof x))throw new Error("Custom setMediaKeys is supposed to be called with old webkit custom MediaKeys.");return t._setVideo(e)}}};N=L.isTypeSupported,O=L.createCustomMediaKeys,D=L.setMediaKeys}else if(void 0!==Z.t){var B=function(){if(void 0===Z.t)throw new Error("No WebKitMediaKeys API.");return{isTypeSupported:Z.t.isTypeSupported,createCustomMediaKeys:function(e){return new C(e)},setMediaKeys:function(e,t){if(null===t)return R(e,t);if(!(t instanceof C))throw new Error("Custom setMediaKeys is supposed to be called with webkit custom MediaKeys.");return t._setVideo(e)}}}();N=B.isTypeSupported,O=B.createCustomMediaKeys,D=B.setMediaKeys}else if(o.fq&&void 0!==r){var U={isTypeSupported:function(e,t){if(void 0===r)throw new Error("No MSMediaKeys API.");return void 0!==t?r.isTypeSupported(e,t):r.isTypeSupported(e)},createCustomMediaKeys:function(e){return new b(e)},setMediaKeys:function(e,t){if(null!==t){if(!(t instanceof b))throw new Error("Custom setMediaKeys is supposed to be called with IE11 custom MediaKeys.");return t._setVideo(e)}}};N=U.isTypeSupported,O=U.createCustomMediaKeys,D=U.setMediaKeys}else if(void 0!==f){var F={isTypeSupported:function(e,t){if(void 0===f)throw new Error("No MozMediaKeys API.");return void 0!==t?f.isTypeSupported(e,t):f.isTypeSupported(e)},createCustomMediaKeys:function(e){if(void 0===f)throw new Error("No MozMediaKeys API.");return new f(e)},setMediaKeys:function(e,t){var n=e;if(void 0===n.mozSetMediaKeys||"function"!=typeof n.mozSetMediaKeys)throw new Error("Can't set video on MozMediaKeys.");return n.mozSetMediaKeys(t)}};N=F.isTypeSupported,O=F.createCustomMediaKeys,D=F.setMediaKeys}else{var z=window.MediaKeys,V=function(){if(void 0===z)throw new i.Z("MEDIA_KEYS_NOT_SUPPORTED","No `MediaKeys` implementation found in the current browser.");if(void 0===z.isTypeSupported){throw new Error("This browser seems to be unable to play encrypted contents currently. Note: Some browsers do not allow decryption in some situations, like when not using HTTPS.")}};N=function(e){return V(),(0,a.Z)("function"==typeof z.isTypeSupported),z.isTypeSupported(e)},O=function(e){return V(),new z(e)}}P=function(e,t){if(!N(e))return Promise.reject(new Error("Unsupported key type"));for(var n=0;n=t)return r.Z.warn("Compat: Invalid cue times: "+e+" - "+t),null;if((0,i.Z)(window.VTTCue)){if((0,i.Z)(window.TextTrackCue))throw new Error("VTT cues not supported in your target");return new TextTrackCue(e,t,n)}return new VTTCue(e,t,n)}},5059:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(3666),i=n(158);function a(){return(r.vS||r.SB)&&void 0!==i.t}},1669:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(3666);function i(){return r.op}},6872:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r={DEFAULT_UNMUTED_VOLUME:.1,DEFAULT_REQUEST_TIMEOUT:3e4,DEFAULT_TEXT_TRACK_MODE:"native",DEFAULT_MANUAL_BITRATE_SWITCHING_MODE:"seamless",DEFAULT_ENABLE_FAST_SWITCHING:!0,DEFAULT_AUDIO_TRACK_SWITCHING_MODE:"seamless",DELTA_POSITION_AFTER_RELOAD:{bitrateSwitch:-.1,trackSwitch:{audio:-.7,video:-.1,other:0}},DEFAULT_CODEC_SWITCHING_BEHAVIOR:"continue",DEFAULT_AUTO_PLAY:!1,DEFAULT_SHOW_NATIVE_SUBTITLE:!0,DEFAULT_STOP_AT_END:!0,DEFAULT_WANTED_BUFFER_AHEAD:30,DEFAULT_MAX_BUFFER_AHEAD:1/0,DEFAULT_MAX_BUFFER_BEHIND:1/0,DEFAULT_MAX_VIDEO_BUFFER_SIZE:1/0,MAXIMUM_MAX_BUFFER_AHEAD:{text:18e3},MAXIMUM_MAX_BUFFER_BEHIND:{text:18e3},DEFAULT_INITIAL_BITRATES:{audio:0,video:0,other:0},DEFAULT_MIN_BITRATES:{audio:0,video:0,other:0},DEFAULT_MAX_BITRATES:{audio:1/0,video:1/0,other:1/0},INACTIVITY_DELAY:6e4,DEFAULT_THROTTLE_WHEN_HIDDEN:!1,DEFAULT_THROTTLE_VIDEO_BITRATE_WHEN_HIDDEN:!1,DEFAULT_LIMIT_VIDEO_WIDTH:!1,DEFAULT_LIVE_GAP:{DEFAULT:10,LOW_LATENCY:3.5},BUFFER_DISCONTINUITY_THRESHOLD:.2,FORCE_DISCONTINUITY_SEEK_DELAY:5e3,BITRATE_REBUFFERING_RATIO:1.5,BUFFER_GC_GAPS:{CALM:240,BEEFY:30},DEFAULT_MAX_MANIFEST_REQUEST_RETRY:4,DEFAULT_CDN_DOWNGRADE_TIME:60,DEFAULT_MAX_REQUESTS_RETRY_ON_ERROR:4,DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE:1/0,INITIAL_BACKOFF_DELAY_BASE:{REGULAR:200,LOW_LATENCY:50},MAX_BACKOFF_DELAY_BASE:{REGULAR:3e3,LOW_LATENCY:1e3},SAMPLING_INTERVAL_MEDIASOURCE:1e3,SAMPLING_INTERVAL_LOW_LATENCY:250,SAMPLING_INTERVAL_NO_MEDIASOURCE:500,ABR_MINIMUM_TOTAL_BYTES:15e4,ABR_MINIMUM_CHUNK_SIZE:16e3,ABR_STARVATION_FACTOR:{DEFAULT:.72,LOW_LATENCY:.72},ABR_REGULAR_FACTOR:{DEFAULT:.8,LOW_LATENCY:.8},ABR_STARVATION_GAP:{DEFAULT:5,LOW_LATENCY:5},OUT_OF_STARVATION_GAP:{DEFAULT:7,LOW_LATENCY:7},ABR_STARVATION_DURATION_DELTA:.1,ABR_FAST_EMA:2,ABR_SLOW_EMA:10,RESUME_GAP_AFTER_SEEKING:{DEFAULT:1.5,LOW_LATENCY:.5},RESUME_GAP_AFTER_NOT_ENOUGH_DATA:{DEFAULT:.5,LOW_LATENCY:.5},RESUME_GAP_AFTER_BUFFERING:{DEFAULT:5,LOW_LATENCY:.5},REBUFFERING_GAP:{DEFAULT:.5,LOW_LATENCY:.2},MINIMUM_BUFFER_AMOUNT_BEFORE_FREEZING:2,UNFREEZING_SEEK_DELAY:6e3,FREEZING_STALLED_DELAY:600,UNFREEZING_DELTA_POSITION:.001,MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT:.15,MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE:.4,MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE:.3,MINIMUM_SEGMENT_SIZE:.005,APPEND_WINDOW_SECURITIES:{START:.2,END:.1},MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL:50,TEXT_TRACK_SIZE_CHECKS_INTERVAL:250,BUFFER_PADDING:{audio:1,video:3,other:1},SEGMENT_PRIORITIES_STEPS:[2,4,8,12,18,25],MAX_HIGH_PRIORITY_LEVEL:1,MIN_CANCELABLE_PRIORITY:3,EME_DEFAULT_WIDEVINE_ROBUSTNESSES:["HW_SECURE_ALL","HW_SECURE_DECODE","HW_SECURE_CRYPTO","SW_SECURE_DECODE","SW_SECURE_CRYPTO"],EME_KEY_SYSTEMS:{clearkey:["webkit-org.w3.clearkey","org.w3.clearkey"],widevine:["com.widevine.alpha"],playready:["com.microsoft.playready","com.chromecast.playready","com.youtube.playready"],fairplay:["com.apple.fps.1_0"]},MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE:10,MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE:200,MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY:300,OUT_OF_SYNC_MANIFEST_REFRESH_DELAY:3e3,FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY:3e3,DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0:3,EME_DEFAULT_MAX_SIMULTANEOUS_MEDIA_KEY_SESSIONS:15,EME_MAX_STORED_PERSISTENT_SESSION_INFORMATION:1e3,EME_WAITING_DELAY_LOADED_SESSION_EMPTY_KEYSTATUSES:100,FORCED_ENDED_THRESHOLD:8e-4,ADAPTATION_SWITCH_BUFFER_PADDINGS:{video:{before:5,after:5},audio:{before:2,after:2.5},text:{before:0,after:0},image:{before:0,after:0}},SOURCE_BUFFER_FLUSHING_INTERVAL:500,CONTENT_REPLACEMENT_PADDING:1.2,CACHE_LOAD_DURATION_THRESHOLDS:{video:50,audio:10},STREAM_EVENT_EMITTER_POLL_INTERVAL:250,DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR:.001,BUFFERED_HISTORY_RETENTION_TIME:6e4,BUFFERED_HISTORY_MAXIMUM_ENTRIES:200,MIN_BUFFER_AHEAD:5,UPTO_CURRENT_POSITION_CLEANUP:5},i=n(8026);function a(e){return null!=e&&!Array.isArray(e)&&"object"==typeof e}function o(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r=e.length||(e[t].enabled=!0)}(this._audioTracks.map((function(e){return e.nativeTrack})),e)},t}(a.Z);function f(e){for(var t=0;te.length)return u.Z.warn("Compat: Unrecognized initialization data. Use as is."),[{systemId:void 0,data:e}];var i=e.subarray(n,n+r),a={systemId:(0,l.Y)(i,8),data:i};p(t,a)?u.Z.warn("Compat: Duplicated PSSH found in initialization data, removing it."):t.push(a),n+=r}return n!==e.length?(u.Z.warn("Compat: Unrecognized initialization data. Use as is."),[{systemId:void 0,data:e}]):t}(new Uint8Array(t));return{type:n,values:r}}var m=n(6872),g=n(5157),y=n(5389),_=n(3274),b=n(7714),T=n(1959),E=n(1946),S=n(288),w=n(6139),k=n(770);function A(e){k.Z.setState(e,null),(0,w.Y)(e,null)}function x(){return(x=(0,r.Z)(o().mark((function e(t,n,r){var i,a,s,l,c,d;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return i=n.keySystemOptions,a=n.loadedSessionsStore,s=n.mediaKeySystemAccess,l=n.mediaKeys,c=k.Z.getState(t),d=null!==c&&c.loadedSessionsStore!==a?c.loadedSessionsStore.closeAllSessions():Promise.resolve(),e.next=5,d;case 5:if(!r.isCancelled){e.next=7;break}throw r.cancellationError;case 7:if(k.Z.setState(t,{keySystemOptions:i,mediaKeySystemAccess:s,mediaKeys:l,loadedSessionsStore:a}),t.mediaKeys!==l){e.next=10;break}return e.abrupt("return");case 10:u.Z.info("DRM: Attaching MediaKeys to the media element"),(0,w.Y)(t,l),u.Z.info("DRM: MediaKeys attached with success");case 13:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function I(e){if(""===e.sessionId)return!1;var t=e.keyStatuses,n=[];return t.forEach((function(e){n.push(e)})),n.length<=0?(u.Z.debug("DRM: isSessionUsable: MediaKeySession given has an empty keyStatuses",e.sessionId),!1):(0,b.Z)(n,"expired")?(u.Z.debug("DRM: isSessionUsable: MediaKeySession given has an expired key",e.sessionId),!1):(0,b.Z)(n,"internal-error")?(u.Z.debug("DRM: isSessionUsable: MediaKeySession given has a key with an internal-error",e.sessionId),!1):(u.Z.debug("DRM: isSessionUsable: MediaKeySession is usable",e.sessionId),!0)}function Z(e,t,n,r){var i=e.loadedSessionsStore,a=e.persistentSessionsStore;return"temporary"===n?R(i,t):null===a?(u.Z.warn("DRM: Cannot create persistent MediaKeySession, PersistentSessionsStore not created."),R(i,t)):function(e,t,n,r){return M.apply(this,arguments)}(i,a,t,r)}function R(e,t){u.Z.info("DRM: Creating a new temporary session");var n=e.createSession(t,"temporary");return Promise.resolve({type:"created-session",value:n})}function M(){return M=(0,r.Z)(o().mark((function e(t,n,i,a){var s,l,c,d,f,v;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(v=function(){return v=(0,r.Z)(o().mark((function e(){var r,l;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(null===a.cancellationError){e.next=2;break}throw a.cancellationError;case 2:return u.Z.info("DRM: Removing previous persistent session."),null!==(r=n.get(i))&&n.delete(r.sessionId),e.prev=5,e.next=8,t.closeSession(s.mediaKeySession);case 8:e.next=15;break;case 10:if(e.prev=10,e.t0=e.catch(5),""===s.mediaKeySession.sessionId){e.next=14;break}throw e.t0;case 14:t.removeSessionWithoutClosingIt(s.mediaKeySession);case 15:if(null===a.cancellationError){e.next=17;break}throw a.cancellationError;case 17:return l=t.createSession(i,"persistent-license"),e.abrupt("return",{type:"created-session",value:l});case 19:case"end":return e.stop()}}),e,null,[[5,10]])}))),v.apply(this,arguments)},f=function(){return v.apply(this,arguments)},null===a.cancellationError){e.next=4;break}throw a.cancellationError;case 4:if(u.Z.info("DRM: Creating persistent MediaKeySession"),s=t.createSession(i,"persistent-license"),null!==(l=n.getAndReuse(i))){e.next=9;break}return e.abrupt("return",{type:"created-session",value:s});case 9:return e.prev=9,e.next=12,t.loadPersistentSession(s.mediaKeySession,l.sessionId);case 12:if(c=e.sent){e.next=19;break}return u.Z.warn("DRM: No data stored for the loaded session"),n.delete(l.sessionId),t.removeSessionWithoutClosingIt(s.mediaKeySession),d=t.createSession(i,"persistent-license"),e.abrupt("return",{type:"created-session",value:d});case 19:if(!c||!I(s.mediaKeySession)){e.next=23;break}return n.add(i,i.keyIds,s.mediaKeySession),u.Z.info("DRM: Succeeded to load persistent session."),e.abrupt("return",{type:"loaded-persistent-session",value:s});case 23:return u.Z.warn("DRM: Previous persistent session not usable anymore."),e.abrupt("return",f());case 27:return e.prev=27,e.t0=e.catch(9),u.Z.warn("DRM: Unable to load persistent session: "+(e.t0 instanceof Error?e.t0.toString():"Unknown Error")),e.abrupt("return",f());case 31:case"end":return e.stop()}}),e,null,[[9,27]])}))),M.apply(this,arguments)}function C(e,t){return P.apply(this,arguments)}function P(){return(P=(0,r.Z)(o().mark((function e(t,n){var r,i,a,s,u;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!(n<0||n>=t.getLength())){e.next=2;break}return e.abrupt("return");case 2:for(r=[],i=t.getAll().slice(),a=i.length-n,s=0;s=s.length)){e.next=2;break}throw new g.Z("INCOMPATIBLE_KEYSYSTEMS","No key system compatible with your wanted configuration has been found in the current browser.");case 2:if(null!=w.N){e.next=4;break}throw new Error("requestMediaKeySystemAccess is not implemented in your browser.");case 4:return r=s[t],i=r.keyName,a=r.keyType,c=r.keySystemOptions,d=F(i,c),u.Z.debug("DRM: Request keysystem access "+a+","+(t+1)+" of "+s.length),e.prev=7,e.next=10,(0,w.N)(a,d);case 10:return f=e.sent,u.Z.info("DRM: Found compatible keysystem",a,t+1),e.abrupt("return",{type:"create-media-key-system-access",value:{options:c,mediaKeySystemAccess:f}});case 15:if(e.prev=15,e.t0=e.catch(7),u.Z.debug("DRM: Rejected access to keysystem",a,t+1),null===n.cancellationError){e.next=20;break}throw n.cancellationError;case 20:return e.abrupt("return",l(t+1));case 21:case"end":return e.stop()}}),e,null,[[7,15]])})))).apply(this,arguments)}}var V=n(2297);function K(e,t,n){var r;u.Z.debug("Compat: Calling generateRequest on the MediaKeySession");try{r=function(e){u.Z.info("Compat: Trying to move CENC PSSH from init data at the end of it.");for(var t=!1,n=new Uint8Array,r=new Uint8Array,i=0;ie.length)throw u.Z.warn("Compat: unrecognized initialization data. Cannot patch it."),new Error("Compat: unrecognized initialization data. Cannot patch it.");var o=e.subarray(i,i+a);if(16===e[i+12]&&119===e[i+13]&&239===e[i+14]&&236===e[i+15]&&192===e[i+16]&&178===e[i+17]&&77===e[i+18]&&2===e[i+19]&&172===e[i+20]&&227===e[i+21]&&60===e[i+22]&&30===e[i+23]&&82===e[i+24]&&226===e[i+25]&&251===e[i+26]&&75===e[i+27]){var s=(0,V.Xj)(o),l=null===s?void 0:o[s[1]];u.Z.info("Compat: CENC PSSH found with version",l),void 0===l?u.Z.warn("Compat: could not read version of CENC PSSH"):t===(1===l)?n=(0,d.zo)(n,o):1===l?(u.Z.warn("Compat: cenc version 1 encountered, removing every other cenc pssh box."),n=o,t=!0):u.Z.warn("Compat: filtering out cenc pssh box with wrong version",l)}else r=(0,d.zo)(r,o);i+=a}if(i!==e.length)throw u.Z.warn("Compat: unrecognized initialization data. Cannot patch it."),new Error("Compat: unrecognized initialization data. Cannot patch it.");return(0,d.zo)(r,n)}(n)}catch(e){r=n}var i=null!=t?t:"";return e.generateRequest(i,r).catch((function(t){if(""!==i||!(t instanceof TypeError))throw t;return u.Z.warn('Compat: error while calling `generateRequest` with an empty initialization data type. Retrying with a default "cenc" value.',t),e.generateRequest("cenc",r)}))}function G(e,t){return H.apply(this,arguments)}function H(){return H=(0,r.Z)(o().mark((function e(t,n){var r;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return u.Z.info("Compat/DRM: Load persisted session",n),e.next=3,t.load(n);case 3:if((r=e.sent)&&!(t.keyStatuses.size>0)){e.next=6;break}return e.abrupt("return",r);case 6:return e.abrupt("return",new Promise((function(e){t.addEventListener("keystatuseschange",i);var n=setTimeout(i,100);function i(){clearTimeout(n),t.removeEventListener("keystatuseschange",i),e(r)}})));case 7:case"end":return e.stop()}}),e)}))),H.apply(this,arguments)}var W=n(7864);function j(e){var t=new S.ZP;return Promise.race([e.close().then((function(){t.cancel()})),e.closed.then((function(){t.cancel()})),function(){return n.apply(this,arguments)}()]);function n(){return(n=(0,r.Z)(o().mark((function e(){var n;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,(0,W.Z)(1e3,t.signal);case 3:return e.next=5,i();case 5:e.next=13;break;case 7:if(e.prev=7,e.t0=e.catch(0),!(e.t0 instanceof S.FU)){e.next=11;break}return e.abrupt("return");case 11:n=e.t0 instanceof Error?e.t0.message:"Unknown error made it impossible to close the session",u.Z.error("DRM: "+n);case 13:case"end":return e.stop()}}),e,null,[[0,7]])})))).apply(this,arguments)}function i(){return a.apply(this,arguments)}function a(){return(a=(0,r.Z)(o().mark((function n(){return o().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.prev=0,n.next=3,e.update(new Uint8Array(1));case 3:n.next=13;break;case 5:if(n.prev=5,n.t0=n.catch(0),!t.isUsed){n.next=9;break}return n.abrupt("return");case 9:if(!(n.t0 instanceof Error&&"The session is already closed."===n.t0.message)){n.next=11;break}return n.abrupt("return");case 11:return n.next=13,(0,W.Z)(1e3,t.signal);case 13:if(!t.isUsed){n.next=15;break}return n.abrupt("return");case 15:throw new Error("Compat: Couldn't know if session is closed");case 16:case"end":return n.stop()}}),n,null,[[0,5]])})))).apply(this,arguments)}}var q=n(811);function Y(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return X(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return X(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function X(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function te(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){if(null!==this._keyIds&&J(t,this._keyIds))return!0;if(void 0!==this._initializationData.keyIds)return J(t,this._initializationData.keyIds)}return this._checkInitializationDataCompatibility(e)},t._checkInitializationDataCompatibility=function(e){return void 0!==e.keyIds&&e.keyIds.length>0&&void 0!==this._initializationData.keyIds?J(e.keyIds,this._initializationData.keyIds):this._initializationData.type===e.type&&this._initializationData.values.isCompatibleWith(e.values)},e}();function re(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return ie(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ie(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ie(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&n._storage[e].mediaKeySession===i&&n._storage.splice(e,1)})).catch((function(e){u.Z.warn("DRM-LSS: MediaKeySession.closed rejected: "+e)})),u.Z.debug("DRM-LSS: Add MediaKeySession",a.sessionType),this._storage.push(Object.assign({},a)),a},t.reuse=function(e){for(var t=this._storage.length-1;t>=0;t--){var n=this._storage[t];if(n.keySessionRecord.isCompatibleWith(e))return this._storage.splice(t,1),this._storage.push(n),Object.assign({},n)}return null},t.getEntryForSession=function(e){for(var t=this._storage.length-1;t>=0;t--){var n=this._storage[t];if(n.mediaKeySession===e)return Object.assign({},n)}return null},t.generateLicenseRequest=function(){var e=(0,r.Z)(o().mark((function e(t,n,r){var i,a,s,l;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:a=re(this._storage);case 1:if((s=a()).done){e.next=8;break}if((l=s.value).mediaKeySession!==t){e.next=6;break}return i=l,e.abrupt("break",8);case 6:e.next=1;break;case 8:if(void 0!==i){e.next=11;break}return u.Z.error("DRM-LSS: generateRequest error. No MediaKeySession found with the given initData and initDataType"),e.abrupt("return",K(t,n,r));case 11:if(i.isGeneratingRequest=!0,"none"===i.closingStatus.type){e.next=14;break}throw new Error("The `MediaKeySession` is being closed.");case 14:return e.prev=14,e.next=17,K(t,n,r);case 17:e.next=26;break;case 19:if(e.prev=19,e.t0=e.catch(14),void 0!==i){e.next=23;break}throw e.t0;case 23:throw i.isGeneratingRequest=!1,"awaiting"===i.closingStatus.type&&i.closingStatus.start(),e.t0;case 26:if(void 0!==i){e.next=28;break}return e.abrupt("return",void 0);case 28:i.isGeneratingRequest=!1,"awaiting"===i.closingStatus.type&&i.closingStatus.start();case 30:case"end":return e.stop()}}),e,this,[[14,19]])})));return function(t,n,r){return e.apply(this,arguments)}}(),t.loadPersistentSession=function(){var e=(0,r.Z)(o().mark((function e(t,n){var r,i,a,s,l;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:i=re(this._storage);case 1:if((a=i()).done){e.next=8;break}if((s=a.value).mediaKeySession!==t){e.next=6;break}return r=s,e.abrupt("break",8);case 6:e.next=1;break;case 8:if(void 0!==r){e.next=11;break}return u.Z.error("DRM-LSS: loadPersistentSession error. No MediaKeySession found with the given initData and initDataType"),e.abrupt("return",G(t,n));case 11:if(r.isLoadingPersistentSession=!0,"none"===r.closingStatus.type){e.next=14;break}throw new Error("The `MediaKeySession` is being closed.");case 14:return e.prev=14,e.next=17,G(t,n);case 17:l=e.sent,e.next=27;break;case 20:if(e.prev=20,e.t0=e.catch(14),void 0!==r){e.next=24;break}throw e.t0;case 24:throw r.isLoadingPersistentSession=!1,"awaiting"===r.closingStatus.type&&r.closingStatus.start(),e.t0;case 27:if(void 0!==r){e.next=29;break}return e.abrupt("return",l);case 29:return r.isLoadingPersistentSession=!1,"awaiting"===r.closingStatus.type&&r.closingStatus.start(),e.abrupt("return",l);case 32:case"end":return e.stop()}}),e,this,[[14,20]])})));return function(t,n){return e.apply(this,arguments)}}(),t.closeSession=function(){var e=(0,r.Z)(o().mark((function e(t){var n,r,i,a;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:r=re(this._storage);case 1:if((i=r()).done){e.next=8;break}if((a=i.value).mediaKeySession!==t){e.next=6;break}return n=a,e.abrupt("break",8);case 6:e.next=1;break;case 8:if(void 0!==n){e.next=11;break}return u.Z.warn("DRM-LSS: No MediaKeySession found with the given initData and initDataType"),e.abrupt("return",Promise.resolve(!1));case 11:return e.abrupt("return",this._closeEntry(n));case 12:case"end":return e.stop()}}),e,this)})));return function(t){return e.apply(this,arguments)}}(),t.getLength=function(){return this._storage.length},t.getAll=function(){return this._storage},t.closeAllSessions=function(){var e=(0,r.Z)(o().mark((function e(){var t,n,r=this;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t=this._storage,u.Z.debug("DRM-LSS: Closing all current MediaKeySessions",t.length),this._storage=[],n=t.map((function(e){return r._closeEntry(e)})),e.next=6,Promise.all(n);case 6:case"end":return e.stop()}}),e,this)})));return function(){return e.apply(this,arguments)}}(),t.removeSessionWithoutClosingIt=function(e){(0,q.Z)(""===e.sessionId,"Initialized `MediaKeySession`s should always be properly closed");for(var t=this._storage.length-1;t>=0;t--){if(this._storage[t].mediaKeySession===e)return this._storage.splice(t,1),!0}return!1},t.getIndex=function(e){for(var t=0;t=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function he(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0){var o=void 0===t?3:4,s=this._entries[a];if((null!==(r=s.version)&&void 0!==r?r:-1)>=o&&i===s.sessionId)return;u.Z.info("DRM-PSS: Updating session info.",i),this._entries.splice(a,1)}else u.Z.info("DRM-PSS: Add new session",i);var l=e.values.getFormattedValues().map((function(e){var t=e.systemId,n=e.data;return{systemId:t,hash:e.hash,data:new de(n)}}));void 0===t?this._entries.push({version:3,sessionId:i,values:l,initDataType:e.type}):this._entries.push({version:4,sessionId:i,keyIds:t.map((function(e){return new de(e)})),values:l,initDataType:e.type}),this._save()}else u.Z.warn("DRM-PSS: Invalid Persisten Session given.")},t.delete=function(e){for(var t=-1,n=0;n0&&(r=new g.Z("KEY_STATUS_CHANGE_ERROR","One or several problematic key statuses have been encountered",{keyStatuses:c})),{warning:r,blacklistedKeyIds:u,whitelistedKeyIds:l}}var lt=s.Xe,ct=s.GJ,dt=s.eX,ft=function(e){function t(n){var r;return r=e.call(this)||this,Object.setPrototypeOf((0,Ie.Z)(r),t.prototype),r.sessionError=n,r}return(0,i.Z)(t,e),t}((0,Ze.Z)(Error));function vt(e,t,n){u.Z.info("DRM: Binding session events",e.sessionId);var r,i,a=new Re.x,o=t.getLicenseConfig,s=void 0===o?{}:o,l=lt(e).pipe((0,Me.U)((function(e){throw new g.Z("KEY_ERROR",e.type)}))),c=dt(e).pipe((0,Ce.z)((function(r){return function(e,t,n,r){u.Z.info("DRM: keystatuseschange event received",e.sessionId);var i=(0,Pe.P)((function(){return(0,nt.Z)((function(){return"function"!=typeof t.onKeyStatusesChange?je.E:(0,Je.Z)(t.onKeyStatusesChange(r,e))}),void 0)})).pipe((0,Me.U)((function(t){return{type:"key-status-change-handled",value:{session:e,license:t}}})),(0,Ge.K)((function(e){var t=new g.Z("KEY_STATUS_CHANGE_ERROR","Unknown `onKeyStatusesChange` error");throw!(0,E.Z)(e)&&(0,ce.Z)(e.message)&&(t.message=e.message),t})));return(0,He.T)(pt(e,t,n),i)}(e,t,n,r)}))),d=ct(e).pipe((0,Ce.z)((function(n){var r=new Uint8Array(n.message),i=(0,ce.Z)(n.messageType)?n.messageType:"license-request";u.Z.info("DRM: Received message event, type "+i,e.sessionId);var o,l,c,d,f,v,p,h,m=(0,Pe.P)((function(){var e=t.getLicense(r,i),n=(0,E.Z)(s.timeout)?1e4:s.timeout;return(0,Je.Z)(e).pipe(n>=0?function(e,t){var n=(0,Ne.q)(e)?{first:e}:"number"==typeof e?{each:e}:e,r=n.first,i=n.each,a=n.with,o=void 0===a?Ve:a,s=n.scheduler,u=void 0===s?null!=t?t:De.z:s,l=n.meta,c=void 0===l?null:l;if(null==r&&null==i)throw new TypeError("No timeout provided.");return(0,Oe.e)((function(e,t){var n,a,s=null,l=0,d=function(e){a=(0,Fe.f)(t,u,(function(){try{n.unsubscribe(),(0,Le.Xf)(o({meta:c,lastValue:s,seen:l})).subscribe(t)}catch(e){t.error(e)}}),e)};n=e.subscribe((0,Ue.x)(t,(function(e){null==a||a.unsubscribe(),l++,t.next(s=e),i>0&&d(i)}),void 0,void 0,(function(){(null==a?void 0:a.closed)||null==a||a.unsubscribe(),s=null}))),!l&&d(null!=r?"number"==typeof r?r:+r-u.now():i)}))}(n):Ke.y)})),g=function(e,t){return{totalRetry:null!=t?t:2,baseDelay:200,maxDelay:3e3,shouldRetry:function(e){return e instanceof ze||(0,E.Z)(e)||!0!==e.noRetry},onRetry:function(t){return e.next({type:"warning",value:ht(t)})}}}(a,s.retry);return(o=m,l=g,c=l.baseDelay,d=l.maxDelay,f=l.totalRetry,v=l.shouldRetry,p=l.onRetry,h=0,o.pipe((0,Ge.K)((function(e,t){if(!(0,E.Z)(v)&&!v(e)||h++>=f)throw e;"function"==typeof p&&p(e,h);var n=Math.min(c*Math.pow(2,h-1),d),r=(0,tt.Z)(n);return(0,et.H)(r).pipe((0,Ce.z)((function(){return t})))})))).pipe((0,Me.U)((function(t){return{type:"key-message-handled",value:{session:e,license:t}}})),(0,Ge.K)((function(e){var t=ht(e);if(!(0,E.Z)(e)&&!0===e.fallbackOnLastTry)throw u.Z.warn("DRM: Last `getLicense` attempt failed. Blacklisting the current session."),new ft(t);throw t})))}))),f=(0,He.T)(d,c).pipe((r=function(t){switch(t.type){case"key-message-handled":case"key-status-change-handled":return(0,E.Z)(t.value.license)?(u.Z.info("DRM: No message given, skipping session.update"),je.E):function(e,t){return u.Z.info("DRM: Updating MediaKeySession with message"),(0,Je.Z)(e.update(t)).pipe((0,Ge.K)((function(e){var t=e instanceof Error?e.toString():"`session.update` failed";throw new g.Z("KEY_UPDATE_ERROR",t)})),(0,$e.b)((function(){u.Z.info("DRM: MediaKeySession update succeeded.")})),(0,Qe.l)())}(e,t.value.license);default:return(0,qe.of)(t)}},(0,We.m)(i)?(0,Ce.z)(r,i,1):(0,Ce.z)(r,1))),v=(0,He.T)(pt(e,t,n),f,l,a);return(0,E.Z)(e.closed)?v:v.pipe((0,Ye.R)((0,Je.Z)(e.closed)))}function pt(e,t,n){return(0,Pe.P)((function(){if(0===e.keyStatuses.size)return je.E;var r=ut(e,t,n),i=r.warning,a=r.blacklistedKeyIds,o=r.whitelistedKeyIds,s=(0,qe.of)({type:"keys-update",value:{whitelistedKeyIds:o,blacklistedKeyIds:a}});return void 0!==i?(0,Xe.z)((0,qe.of)({type:"warning",value:i}),s):s}))}function ht(e){if(e instanceof ze)return new g.Z("KEY_LOAD_TIMEOUT","The license server took too much time to respond.");var t=new g.Z("KEY_LOAD_ERROR","An error occured when calling `getLicense`.");return!(0,E.Z)(e)&&(0,ce.Z)(e.message)&&(t.message=e.message),t}var mt=n(9822);function gt(e,t){return yt.apply(this,arguments)}function yt(){return(yt=(0,r.Z)(o().mark((function e(t,n){var r,i;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,t.setServerCertificate(n);case 3:return r=e.sent,e.abrupt("return",r);case 7:throw e.prev=7,e.t0=e.catch(0),u.Z.warn("DRM: mediaKeys.setServerCertificate returned an error",e.t0 instanceof Error?e.t0:""),i=e.t0 instanceof Error?e.t0.toString():"`setServerCertificate` error",new g.Z("LICENSE_SERVER_CERTIFICATE_ERROR",i);case 12:case"end":return e.stop()}}),e,null,[[0,7]])})))).apply(this,arguments)}function _t(e,t){return bt.apply(this,arguments)}function bt(){return(bt=(0,r.Z)(o().mark((function e(t,n){var r,i;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!0!==be(t)){e.next=3;break}return u.Z.info("DRM: The MediaKeys already has a server certificate, skipping..."),e.abrupt("return",{type:"already-has-one"});case 3:if("function"==typeof t.setServerCertificate){e.next=6;break}return u.Z.warn("DRM: Could not set the server certificate. mediaKeys.setServerCertificate is not a function"),e.abrupt("return",{type:"method-not-implemented"});case 6:return u.Z.info("DRM: Setting server certificate on the MediaKeys"),ye(t),e.prev=8,e.next=11,gt(t,n);case 11:return r=e.sent,_e(t,n),e.abrupt("return",{type:"success",value:r});case 16:return e.prev=16,e.t0=e.catch(8),i=(0,mt.Z)(e.t0)?e.t0:new g.Z("LICENSE_SERVER_CERTIFICATE_ERROR","Unknown error when setting the server certificate."),e.abrupt("return",{type:"error",value:i});case 20:case"end":return e.stop()}}),e,null,[[8,16]])})))).apply(this,arguments)}function Tt(e,t){if(!(isNaN(t)||t<0||t>=e.getLength())){var n=e.getLength(),r=n-t;u.Z.info("DRM: Too many stored persistent sessions, removing some.",n,r),e.deleteOldSessions(r)}}var Et=n(9252);var St=function(){function e(e){this._innerValues=e,this._lazyFormattedValues=null}var t=e.prototype;return t.constructRequestData=function(){return d.zo.apply(void 0,this._innerValues.map((function(e){return e.data})))},t.isCompatibleWith=function(t){var n=t instanceof e?t.getFormattedValues():t;return fe(this.getFormattedValues(),n)},t.getFormattedValues=function(){return null===this._lazyFormattedValues&&(this._lazyFormattedValues=this._innerValues.slice().sort((function(e,t){return e.systemId===t.systemId?0:void 0===e.systemId?1:void 0===t.systemId||e.systemId=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function kt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&G._currentSessions.splice(r),void 0!==t.content&&Rt(t.content.manifest,[],[],N.record.getAssociatedKeyIds()),null===(n=i.persistentSessionsStore)||void 0===n||n.delete(L.sessionId),i.loadedSessionsStore.closeSession(L).catch((function(e){var t=e instanceof Error?e:"unknown error";u.Z.warn("DRM: failed to close expired session",t)})).then((function(){return G._unlockInitDataQueue()})).catch((function(e){return G._onFatalError(e)})),void(G._isStopped()||G.trigger("warning",e.reason))}if(e instanceof ft){if(N.blacklistedSessionError=e,void 0!==t.content){var a=t.content.manifest;u.Z.info("DRM: blacklisting Representations based on protection data."),Mt(a,t)}G._unlockInitDataQueue()}else G._onFatalError(e)}}),this._canceller.signal.register((function(){F.unsubscribe()})),void 0!==a.singleLicensePer&&"init-data"!==a.singleLicensePer||this._unlockInitDataQueue(),"created-session"!==P.type){e.next=68;break}return z=t.values.constructRequestData(),e.prev=55,e.next=58,i.loadedSessionsStore.generateLicenseRequest(L,t.type,z);case 58:e.next=68;break;case 60:if(e.prev=60,e.t0=e.catch(55),null!==(V=i.loadedSessionsStore.getEntryForSession(L))&&"none"===V.closingStatus.type){e.next=67;break}return(K=this._currentSessions.indexOf(N))>=0&&this._currentSessions.splice(K,1),e.abrupt("return",Promise.resolve());case 67:throw new g.Z("KEY_GENERATE_REQUEST_ERROR",e.t0 instanceof Error?e.t0.toString():"Unknown error");case 68:return e.abrupt("return",Promise.resolve());case 69:case"end":return e.stop()}}),e,this,[[55,60]])})));return function(t,n){return e.apply(this,arguments)}}(),n._tryToUseAlreadyCreatedSession=function(e,t){var n=t.stores,r=t.options,i=(0,_.Z)(this._currentSessions,(function(t){return t.record.isCompatibleWith(e)}));if(void 0===i)return!1;var a=i.blacklistedSessionError;if(!(0,E.Z)(a))return void 0===e.type||void 0===e.content?(u.Z.error("DRM: This initialization data has already been blacklisted but the current content is not known."),!0):(u.Z.info("DRM: This initialization data has already been blacklisted. Blacklisting the related content."),Mt(e.content.manifest,e),!0);if(void 0!==e.keyIds){var o;if(void 0===r.singleLicensePer||"init-data"===r.singleLicensePer){var s=i.keyStatuses.blacklisted;o=function(e,t){for(var n,r=function(){var e=n.value;if(t.some((function(t){return $(t,e)})))return{v:!0}},i=Y(e);!(n=i()).done;){var a=r();if("object"==typeof a)return a.v}return!1}(e.keyIds,s)}else{var l=i.keyStatuses.whitelisted;o=!J(e.keyIds,l)}if(o)return void 0===e.content?(u.Z.error("DRM: Cannot forbid key id, the content is unknown."),!0):(u.Z.info("DRM: Current initialization data is linked to blacklisted keys. Marking Representations as not decipherable"),Rt(e.content.manifest,[],e.keyIds,[]),!0)}if(null!==n.loadedSessionsStore.reuse(e))return u.Z.debug("DRM: Init data already processed. Skipping it."),!0;var c=this._currentSessions.indexOf(i);return-1===c?u.Z.error("DRM: Unable to remove processed init data: not found."):(u.Z.debug("DRM: A session from a processed init data is not available anymore. Re-processing it."),this._currentSessions.splice(c,1)),!1},n._onFatalError=function(e){if(!this._canceller.isUsed){var t=e instanceof Error?e:new y.Z("NONE","Unknown decryption error");this.error=t,this._initDataQueue.length=0,this._stateData={state:At.Error,isMediaKeysAttached:void 0,isInitDataQueueLocked:void 0,data:null},this._canceller.cancel(),this.trigger("error",t),this._stateData.state===At.Error&&this.trigger("stateChange",this._stateData.state)}},n._isStopped=function(){return this._stateData.state===At.Disposed||this._stateData.state===At.Error},n._processCurrentInitDataQueue=function(){for(;!1===this._stateData.isInitDataQueueLocked;){var e=this._initDataQueue.shift();if(void 0===e)return;this.onInitializationData(e)}},n._lockInitDataQueue=function(){!1===this._stateData.isInitDataQueueLocked&&(this._stateData.isInitDataQueueLocked=!0)},n._unlockInitDataQueue=function(){!0===this._stateData.isMediaKeysAttached?(this._stateData.isInitDataQueueLocked=!1,this._processCurrentInitDataQueue()):u.Z.error("DRM: Trying to unlock in the wrong state")},t}(T.Z);function Zt(e){var t=e.getConfiguration().sessionTypes;return void 0!==t&&(0,b.Z)(t,"persistent-license")}function Rt(e,t,n,r){e.updateRepresentationsDeciperability((function(e){if(void 0===e.contentProtections)return e.decipherable;var i=e.contentProtections.keyIds;if(void 0!==i)for(var a,o=wt(i);!(a=o()).done;){for(var s,u=a.value,l=wt(n);!(s=l()).done;){if($(s.value,u.keyId))return!1}for(var c,d=wt(t);!(c=d()).done;){if($(c.value,u.keyId))return!0}for(var f,v=wt(r);!(f=v()).done;){if($(f.value,u.keyId))return}}return e.decipherable}))}function Mt(e,t){e.updateRepresentationsDeciperability((function(e){var n,r;if(!1===e.decipherable)return!1;for(var i,a=function(){var e=i.value;if((void 0===t.type||e.type===t.type)&&t.values.getFormattedValues().every((function(t){return e.values.some((function(e){return(void 0===t.systemId||e.systemId===t.systemId)&&(0,c.Z)(e.data,t.data)}))})))return{v:!1}},o=wt(null!==(r=null===(n=e.contentProtections)||void 0===n?void 0:n.initData)&&void 0!==r?r:[]);!(i=o()).done;){var s=a();if("object"==typeof s)return s.v}return e.decipherable}))}function Ct(e,t,n,r,i,a){for(var o,s,l=[].concat(i,a),c=function(){var e=s.value;l.some((function(t){return $(t,e)}))||(u.Z.hasLevel("DEBUG")&&u.Z.debug("DRM: KeySessionRecord's key missing in the license, blacklisting it",(0,f.ci)(e)),l.push(e))},d=wt(t.getAssociatedKeyIds());!(s=d()).done;)c();if(void 0!==n&&"init-data"!==n){var v=e.keyIds,p=e.content;if(void 0!==v){var h=v.filter((function(e){return!l.some((function(t){return $(t,e)}))}));h.length>0&&(u.Z.hasLevel("DEBUG")&&u.Z.debug("DRM: init data keys missing in the license, blacklisting them",h.map((function(e){return(0,f.ci)(e)})).join(", ")),l.push.apply(l,h))}if(r&&void 0!==p)if("content"===n){for(var m,g=new Set,y=wt(p.manifest.periods);!(m=y()).done;){Dt(g,m.value)}Pt(g,l)}else if("periods"===n)for(var _,b=wt(p.manifest.periods);!(_=b()).done;){var T=_.value,E=new Set;if(Dt(E,T),(null===(o=e.content)||void 0===o?void 0:o.period.id)===T.id)Pt(E,l);else for(var S=Array.from(E),w=function(){var e=A[k];if(l.some((function(t){return $(t,e)})))return Pt(E,l),"break"},k=0,A=S;k=3&&null!==e.currentRange&&(!(0,a.Z)()||t.duration>0)?s.Z.loaded(n):null:t.duration>0?s.Z.loaded(n):null}),null),(0,r.q)(1))}},8343:function(e,t){"use strict";var n={loaded:function(e){return{type:"loaded",value:{segmentBuffersStore:e}}},decipherabilityUpdate:function(e){return{type:"decipherabilityUpdate",value:e}},manifestReady:function(e){return{type:"manifestReady",value:{manifest:e}}},manifestUpdate:function(){return{type:"manifestUpdate",value:null}},nullRepresentation:function(e,t){return{type:"representationChange",value:{type:e,representation:null,period:t}}},reloadingMediaSource:function(){return{type:"reloading-media-source",value:void 0}},stalled:function(e){return{type:"stalled",value:e}},unstalled:function(){return{type:"unstalled",value:null}},warning:function(e){return{type:"warning",value:e}}};t.Z=n},7920:function(e,t,n){"use strict";n.d(t,{Z:function(){return k}});var r=n(4975),i=n(4727),a=n(9127),o=n(9878),s=n(2817),u=n(2006),l=n(8515),c=n(7877),d=n(6108),f=n(2034),v=n(9917),p=n(8117),h=n(5561);var m=n(3774),g=n(1381);var y=n(1669),_=n(3714),b=n(3887),T=n(5095),E=n(8343);function S(e){return e.pipe((0,r.h)((function(e){var t=e.seeking,n=e.rebuffering,r=e.readyState;return!t&&null===n&&r>=1})),(0,i.q)(1),(0,a.U)((function(){})))}function w(e){return function(e){return(0,v.P)((function(){return(0,h.Z)((function(){return(0,p.Z)(e.play())}),void 0)}))}(e).pipe((0,a.U)((function(){return"autoplay"})),(0,o.K)((function(e){if(e instanceof Error&&"NotAllowedError"===e.name)return b.Z.warn("Init: Media element can't play. It may be due to browser auto-play policies."),(0,s.of)("autoplay-blocked");throw e})))}function k(e){var t=e.mediaElement,n=e.playbackObserver,r=e.startTime,a=e.mustAutoPlay,o=(0,T.$l)(!1),v=(0,T.$l)(!1),p=function(e){return e.readyState>=m.c.HAVE_METADATA?(0,s.of)(null):(0,g.K4)(e).pipe((0,i.q)(1))}(t).pipe((0,i.q)(1),(0,u.b)((function(){var e="function"==typeof r?r():r;b.Z.info("Init: Set initial time",e),n.setCurrentTime(e),o.setValue(!0),o.finish()})),(0,l.d)({refCount:!0}));return{seekAndPlay$:p.pipe((0,c.z)((function(){if(!(0,y.Z)()||t.duration>0)return S(n.getReference().asObservable());var e=new _.Z("MEDIA_ERR_NOT_LOADED_METADATA","Cannot load automatically: your browser falsely announced having loaded the content.");return S(n.getReference().asObservable()).pipe((0,d.O)(E.Z.warning(e)))})),(0,c.z)((function(e){return void 0!==e?(0,s.of)(e):(b.Z.info("Init: Can begin to play content"),a?w(t).pipe((0,c.z)((function(e){if(v.setValue(!0),v.finish(),"autoplay"===e)return(0,s.of)({type:"autoplay"});var t=new _.Z("MEDIA_ERR_BLOCKED_AUTOPLAY","Cannot trigger auto-play automatically: your browser does not allow it.");return(0,f.z)((0,s.of)(E.Z.warning(t)),(0,s.of)({type:"autoplay-blocked"}))}))):(t.autoplay&&b.Z.warn("Init: autoplay is enabled on HTML media element. Media will play as soon as possible."),v.setValue(!0),v.finish(),(0,s.of)({type:"skipped"})))})),(0,l.d)({refCount:!0})),initialPlayPerformed:v,initialSeekPerformed:o}}},8969:function(e,t,n){"use strict";n.d(t,{Z:function(){return T}});var r=n(1545),i=n(5583),a=n(4975),o=n(4727),s=n(7877),u=n(4978),l=n(2817),c=n(3071),d=n(533),f=n(5767),v=n(1480),p=n(3887);var h=n(8333),m=n(5039),g=n(7920),y=n(9607),_=n(342),b=n(2447);function T(e){var t=e.autoPlay,n=e.keySystems,T=e.mediaElement,E=e.playbackObserver,S=e.speed,w=e.startAt,k=e.url;if((0,f.Z)(T),null==k)throw new Error("No URL for a DirectFile content");var A=function(e,t){return new v.y((function(n){return p.Z.info("Setting URL to HTMLMediaElement",t),e.src=t,n.next(void 0),function(){(0,f.Z)(e)}}))}(T,k),x=(0,g.Z)({mediaElement:T,playbackObserver:E,startTime:function(){p.Z.debug("Init: Calculating initial time");var e=function(e,t){if(null==t)return 0;if(null!=t.position)return t.position;if(null!=t.wallClockTime)return t.wallClockTime;if(null!=t.fromFirstPosition)return t.fromFirstPosition;var n=e.duration;if(null==n||!isFinite(n))return p.Z.warn("startAt.fromLastPosition set but no known duration, beginning at 0."),0;if("number"==typeof t.fromLastPosition)return Math.max(0,n+t.fromLastPosition);if(null!=t.percentage){var r=t.percentage;return r>=100?n:r<=0?0:n*(+r/100)}return 0}(T,w);return p.Z.debug("Init: Initial time calculated:",e),e},mustAutoPlay:t}),I=x.seekAndPlay$,Z=(0,y.Z)(T,n,r.E,A).pipe((0,h.Z)(),(0,i.B)()),R=(0,b.Z)(T),M=E.getReference().asObservable(),C=(0,_.Z)(E,null,S,r.E,r.E),P=Z.pipe((0,a.h)((function(e){return"decryption-ready"===e.type||"decryption-disabled"===e.type})),(0,o.q)(1),(0,s.z)((function(){return I})),(0,u.w)((function(e){return"warning"===e.type?(0,l.of)(e):(0,m.Z)(M,T,null,!0)})));return(0,c.T)(P,Z.pipe((0,d.l)()),R,C)}},9607:function(e,t,n){"use strict";n.d(t,{Z:function(){return v}});var r=n(3071),i=n(9127),a=n(1480),o=n(1381),s=n(6139);var u=n(5157),l=n(7874),c=n(3887),d=n(7425),f=o.Oh;function v(e,t,n,o){var v=(0,r.T)(f(e),n);if(null==l.Z.ContentDecryptor)return(0,r.T)(v.pipe((0,i.U)((function(){throw c.Z.error("Init: Encrypted event but EME feature not activated"),new u.Z("MEDIA_IS_ENCRYPTED_ERROR","EME feature not activated.")}))),o.pipe((0,i.U)((function(e){return{type:"decryption-disabled",value:{drmSystemId:void 0,mediaSource:e}}}))));if(0===t.length)return(0,r.T)(v.pipe((0,i.U)((function(){throw c.Z.error("Init: Ciphered media and no keySystem passed"),new u.Z("MEDIA_IS_ENCRYPTED_ERROR","Media is encrypted and no `keySystems` given")}))),o.pipe((0,i.U)((function(e){return{type:"decryption-disabled",value:{drmSystemId:void 0,mediaSource:e}}}))));if("function"!=typeof s.N)return(0,r.T)(v.pipe((0,i.U)((function(){throw c.Z.error("Init: Encrypted event but no EME API available"),new u.Z("MEDIA_IS_ENCRYPTED_ERROR","Encryption APIs not found.")}))),o.pipe((0,i.U)((function(e){return{type:"decryption-disabled",value:{drmSystemId:void 0,mediaSource:e}}}))));c.Z.debug("Init: Creating ContentDecryptor");var p=l.Z.ContentDecryptor;return new a.y((function(r){var i,a=new p(e,t);a.addEventListener("stateChange",(function(e){e===d.u.WaitingForAttachment&&(a.removeEventListener("stateChange"),i=o.subscribe((function(e){a.addEventListener("stateChange",(function(t){t===d.u.ReadyForContent&&(r.next({type:"decryption-ready",value:{drmSystemId:a.systemId,mediaSource:e}}),a.removeEventListener("stateChange"))})),a.attach()})))})),a.addEventListener("error",(function(e){r.error(e)})),a.addEventListener("warning",(function(e){r.next({type:"warning",value:e})}));var s=n.subscribe((function(e){a.onInitializationData(e)}));return function(){s.unsubscribe(),null==i||i.unsubscribe(),a.dispose()}}))}},342:function(e,t,n){"use strict";n.d(t,{Z:function(){return y}});var r=n(3428),i=n(3074),a=n(2006),o=n(533),s=n(9127),u=n(3071),l=n(3286),c=n(3666).yS,d=n(6872),f=n(3714),v=n(3887),p=n(2829),h=n(288),m=n(8567),g=1/60;function y(e,t,n,f,h){var y,E=h.pipe((0,r.M)(e.getReference().asObservable()),(0,i.R)((function(e,t){return function(e,t,n){for(;e.length>0&&void 0!==e[0].period.end&&e[0].period.end+10r.start)return _(t)&&e.splice(a,0,t),e;_(t)&&e.push(t);return e}(e,t[0],t[1])}),[])),S=null,w=null,k=f.pipe((0,r.M)(e.getReference().asObservable()),(0,a.b)((function(t){var r,i=t[0],a=t[1];if(!(!a.rebuffering||a.paused||n.getValue()<=0||"audio"!==i.bufferType&&"video"!==i.bufferType)){var o=a.position,s=null!==(r=a.rebuffering.position)&&void 0!==r?r:o,u=i.period.start;ox&&(v.Z.warn("Init: trying to seek to un-freeze player"),e.setCurrentTime(e.getCurrentTime()+I),y={attemptTimestamp:R}),R-h.timestamp>k)return null===f||null!==w?A.stopRebuffering():A.startRebuffering(),{type:"stalled",value:"freezing"}}else y=null;if(null===f)return A.stopRebuffering(),1===l?{type:"stalled",value:a.seeking?null!==a.pendingInternalSeek?"internal-seek":"seeking":"not-ready"}:{type:"unstalled",value:null};var M="seeking"===f.reason&&null!==a.pendingInternalSeek?"internal-seek":f.reason;if(null!==w){var C=performance.now();if(C-w0){var D=function(e,t,n){if(0===e.length)return null;for(var r=null,i=0;in)return r;var o=void 0;if(void 0===a.end||a.end>n){var s=e[i],u=s.discontinuity,l=s.position,c=u.start,d=u.end;if(n>=(null!=c?c:l)-g)if(null===d){var f=t.getPeriodAfter(a);null!==f?o=f.start+g:v.Z.warn("Init: discontinuity at Period's end but no next Period")}else no?r:o)}}return r}(o,t,P);if(null!==D){var N=D+.001;if(!(N<=e.getCurrentTime()))return v.Z.warn("SA: skippable discontinuity found in the stream",u,N),e.setCurrentTime(N),m.Z.warning(b(P,N));v.Z.info("Init: position to seek already reached, no seeking",e.getCurrentTime(),N)}}var O=null!=P?P:u,L=(0,p.XS)(s,O);if(n.getValue()>0&&L=0;U--){var F=t.periods[U];if(void 0!==F.end&&F.end<=O){if(t.periods[U+1].start>O&&t.periods[U+1].start>e.getCurrentTime()){var z=t.periods[U+1];return e.setCurrentTime(z.start),m.Z.warning(b(O,z.start))}break}}return{type:"stalled",value:M}})));return(0,u.T)(k,x).pipe((0,l.x)((function(){A.dispose()})))}function _(e){return null!==e.discontinuity}function b(e,t){return new f.Z("DISCONTINUITY_ENCOUNTERED","A discontinuity has been encountered at position "+String(e)+", seeked at position "+String(t))}var T=function(){function e(e,t){this._speedUpdateCanceller=new h.ZP,this._isRebuffering=!1,this._playbackObserver=e,this._isDisposed=!1,this._speed=t,this._updateSpeed()}var t=e.prototype;return t.startRebuffering=function(){this._isRebuffering||this._isDisposed||(this._isRebuffering=!0,this._speedUpdateCanceller.cancel(),v.Z.info("Init: Pause playback to build buffer"),this._playbackObserver.setPlaybackRate(0))},t.stopRebuffering=function(){this._isRebuffering&&!this._isDisposed&&(this._isRebuffering=!1,this._speedUpdateCanceller=new h.ZP,this._updateSpeed())},t.dispose=function(){this._speedUpdateCanceller.cancel(),this._isDisposed=!0},t._updateSpeed=function(){var e=this;this._speed.onUpdate((function(t){v.Z.info("Init: Resume playback speed",t),e._playbackObserver.setPlaybackRate(t)}),{clearSignal:this._speedUpdateCanceller.signal,emitCurrentValue:!0})},e}()},2447:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(2401),i=n(7877),a=n(3714),o=n(1946);function s(e){return(0,r.R)(e,"error").pipe((0,i.z)((function(){var t,n,r=e.error;switch((0,o.Z)(r)||(t=r.code,n=r.message),t){case 1:throw n=null!=n?n:"The fetching of the associated resource was aborted by the user's request.",new a.Z("MEDIA_ERR_ABORTED",n);case 2:throw n=null!=n?n:"A network error occurred which prevented the media from being successfully fetched",new a.Z("MEDIA_ERR_NETWORK",n);case 3:throw n=null!=n?n:"An error occurred while trying to decode the media resource",new a.Z("MEDIA_ERR_DECODE",n);case 4:throw n=null!=n?n:"The media resource has been found to be unsuitable.",new a.Z("MEDIA_ERR_SRC_NOT_SUPPORTED",n);default:throw n=null!=n?n:"The HTMLMediaElement errored due to an unknown reason.",new a.Z("MEDIA_ERR_UNKNOWN",n)}})))}},7127:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(4578),i=n(3887),a=n(9612),o=n(4309),s=function(e){function t(){var t;return i.Z.debug("ISB: Creating ImageSegmentBuffer"),(t=e.call(this)||this).bufferType="image",t._buffered=new o.Z,t}(0,r.Z)(t,e);var n=t.prototype;return n.pushChunk=function(e){var t,n;if(i.Z.debug("ISB: appending new data."),null===e.data.chunk)return Promise.resolve();var r=e.data,a=r.appendWindow,o=r.chunk,s=o.start,u=o.end,l=o.timescale,c=null!==(t=a[0])&&void 0!==t?t:0,d=null!==(n=a[1])&&void 0!==n?n:1/0,f=s/l,v=u/l,p=Math.max(c,f),h=Math.min(d,v);try{this._buffered.insert(p,h),null!==e.inventoryInfos&&this._segmentInventory.insertChunk(e.inventoryInfos)}catch(e){return Promise.reject(e)}return Promise.resolve()},n.removeBuffer=function(e,t){return i.Z.info("ISB: ignored image data remove order",e,t),Promise.resolve()},n.endOfSegment=function(e){return this._segmentInventory.completeSegment(e,this._buffered),Promise.resolve()},n.getBufferedRanges=function(){return this._buffered},n.dispose=function(){i.Z.debug("ISB: disposing image SegmentBuffer"),this._buffered.remove(0,1/0)},t}(a.C)},5192:function(e,t,n){"use strict";n.d(t,{Z:function(){return w}});var r=n(4578),i=n(1381),a=n(3887),o=n(5095),s=n(2203).Z?void 0:window.ResizeObserver;var u=n(6872),l=n(288),c=n(9612),d=n(4309),f=n(7874);function v(e,t){return Math.abs(e-t)<=.2}function p(e,t){for(var n=e.length-1;n>=0;n--){if(e[n].startt)return e.slice(n,e.length)}return[]}function m(e,t,n){var r=Math.max(e.start,t),i=p(e.cues,t),a={start:e.start,end:r,cues:i},o=Math.min(n,e.end),s=h(e.cues,n);return[a,{start:o,end:e.end,cues:s}]}var g=function(){function e(){this._cuesBuffer=[]}var t=e.prototype;return t.get=function(e){for(var t=this._cuesBuffer,n=[],r=t.length-1;r>=0;r--){var i=t[r];if(e=i.start){for(var a=i.cues,o=0;o=a[o].start&&ee){var a=r[i];if(a.start>=n)return;if(a.end>=n){if(e<=a.start)a.cues=h(a.cues,n),a.start=n;else{var o=m(a,e,n),s=o[0],u=o[1];this._cuesBuffer[i]=s,r.splice(i+1,0,u)}return}a.start>=e?(r.splice(i,1),i--):(a.cues=p(a.cues,e),a.end=Math.max(e,a.start))}},t.insert=function(e,t,n){var r=this._cuesBuffer,i={start:t,end:n,cues:e};function a(e){var t=r[e];void 0===t||v(i.end,t.end)?r[e]=i:(t.start>=i.end||(t.cues=h(t.cues,i.end),t.start=i.end),r.splice(e,0,i))}for(var o=0;os.end);return void a(o)}if(ts.end);return void a(o)}if(v(s.end,n))return s.cues=p(s.cues,t),s.end=t,void r.splice(o+1,0,i);if(s.end>n){var u=m(s,t,n),l=u[0],c=u[1];return this._cuesBuffer[o]=l,r.splice(o+1,0,i),void r.splice(o+2,0,c)}s.cues=p(s.cues,t),s.end=t;var d=o+1;for(s=r[d];void 0!==s&&n>s.end;)r.splice(d,1),s=r[d];return void a(d)}}r.push(i)},e}();function y(e,t,n,r){for(var i=[t/n.columns,e/n.rows],a=r.getElementsByClassName("proportional-style"),o=0;o0}var _=i.M4,b=i.bQ,T=i.Q$;function E(e,t){try{e.removeChild(t)}catch(e){a.Z.warn("HTSB: Can't remove text track: not in the element.")}}function S(e){var t=e.getAttribute("data-resolution-rows"),n=e.getAttribute("data-resolution-columns");if(null===t||null===n)return null;var r=parseInt(t,10),i=parseInt(n,10);return null===r||null===i?null:{rows:r,columns:i}}var w=function(e){function t(t,n){var r;return a.Z.debug("HTSB: Creating HTMLTextSegmentBuffer"),(r=e.call(this)||this).bufferType="text",r._buffered=new d.Z,r._videoElement=t,r._textTrackElement=n,r._sizeUpdateCanceller=new l.ZP,r._canceller=new l.ZP,r._buffer=new g,r._currentCues=[],r.autoRefreshSubtitles(r._canceller.signal),r}(0,r.Z)(t,e);var n=t.prototype;return n.pushChunk=function(e){try{this.pushChunkSync(e)}catch(e){return Promise.reject(e)}return Promise.resolve()},n.removeBuffer=function(e,t){return this.removeBufferSync(e,t),Promise.resolve()},n.endOfSegment=function(e){return this._segmentInventory.completeSegment(e,this._buffered),Promise.resolve()},n.getBufferedRanges=function(){return this._buffered},n.dispose=function(){a.Z.debug("HTSB: Disposing HTMLTextSegmentBuffer"),this._disableCurrentCues(),this._buffer.remove(0,1/0),this._buffered.remove(0,1/0),this._canceller.cancel()},n.pushChunkSync=function(e){var t,n;a.Z.debug("HTSB: Appending new html text tracks");var r=e.data,i=r.timestampOffset,o=r.appendWindow,s=r.chunk;if(null!==s){var u,l,c=s.start,d=s.end,v=s.data,p=s.type,h=s.language,m=null!==(t=o[0])&&void 0!==t?t:0,g=null!==(n=o[1])&&void 0!==n?n:1/0,y=function(e,t,n,r){a.Z.debug("HTSB: Finding parser for html text tracks:",e);var i=f.Z.htmlTextTracksParsers[e];if("function"!=typeof i)throw new Error("no parser found for the given text track");a.Z.debug("HTSB: Parser found, parsing...");var o=i(t,n,r);return a.Z.debug("HTTB: Parsed successfully!",o.length),o}(p,v,i,h);if(0!==m&&g!==1/0){for(var _=0;_=0&&y[_].start>=g;)_--;for(y.splice(_,y.length),_=y.length-1;_>=0&&y[_].end>g;)y[_].end=g,_--}if(void 0!==c)u=Math.max(m,c);else{if(y.length<=0)return void a.Z.warn("HTSB: Current text tracks have no cues nor start time. Aborting");a.Z.warn("HTSB: No start time given. Guessing from cues."),u=y[0].start}if(void 0!==d)l=Math.min(g,d);else{if(y.length<=0)return void a.Z.warn("HTSB: Current text tracks have no cues nor end time. Aborting");a.Z.warn("HTSB: No end time given. Guessing from cues."),l=y[y.length-1].end}l<=u?a.Z.warn("HTSB: Invalid text track appended: ","the start time is inferior or equal to the end time."):(null!==e.inventoryInfos&&this._segmentInventory.insertChunk(e.inventoryInfos),this._buffer.insert(y,u,l),this._buffered.insert(u,l))}},n.removeBufferSync=function(e,t){a.Z.debug("HTSB: Removing html text track data",e,t),this._buffer.remove(e,t),this._buffered.remove(e,t)},n._disableCurrentCues=function(){if(this._sizeUpdateCanceller.cancel(),this._currentCues.length>0){for(var e=0;e0){this._sizeUpdateCanceller=new l.ZP({cancelOn:this._canceller.signal});var d=u.Z.getCurrent().TEXT_TRACK_SIZE_CHECKS_INTERVAL,f=function(e,t,n){var r=e.getBoundingClientRect(),i=r.height,u=r.width,l=(0,o.ZP)({height:i,width:u}),c=i,d=u;if(void 0!==s){var f=new s((function(e){if(0!==e.length){var t=e[0].contentRect,n=t.height,r=t.width;n===c&&r===d||(c=n,d=r,l.setValue({height:n,width:r}))}else a.Z.error("Compat: Resized but no observed element.")}));f.observe(e),n.register((function(){f.disconnect()}))}else{var v=setInterval((function(){var t=e.getBoundingClientRect(),n=t.height,r=t.width;n===c&&r===d||(c=n,d=r,l.setValue({height:n,width:r}))}),t);n.register((function(){clearInterval(v)}))}return l}(this._textTrackElement,d,this._sizeUpdateCanceller.signal);f.onUpdate((function(e){for(var t=e.height,n=e.width,r=0;r0?e.textTracks[u-1]:e.addTextTrack(s)).mode=t?null!==(n=a.HIDDEN)&&void 0!==n?n:"hidden":null!==(r=a.SHOWING)&&void 0!==r?r:"showing"}else o=document.createElement("track"),e.appendChild(o),a=o.track,o.kind=s,a.mode=t?"hidden":"showing";return{track:a,trackElement:o}}(t,n),s=o.track,l=o.trackElement;return r.bufferType="text",r._buffered=new u.Z,r._videoElement=t,r._track=s,r._trackElement=l,r}(0,r.Z)(t,e);var n=t.prototype;return n.pushChunk=function(e){var t,n;if(a.Z.debug("NTSB: Appending new native text tracks"),null===e.data.chunk)return Promise.resolve();var r=e.data,i=r.timestampOffset,o=r.appendWindow,s=r.chunk,u=s.start,c=s.end,d=s.data,f=s.type,v=s.language,p=null!==(t=o[0])&&void 0!==t?t:0,h=null!==(n=o[1])&&void 0!==n?n:1/0;try{var m,g,y=function(e,t,n,r){a.Z.debug("NTSB: Finding parser for native text tracks:",e);var i=l.Z.nativeTextTracksParsers[e];if("function"!=typeof i)throw new Error("no parser found for the given text track");a.Z.debug("NTSB: Parser found, parsing...");var o=i(t,n,r);return a.Z.debug("NTSB: Parsed successfully!",o.length),o}(f,d,i,v);if(0!==p&&h!==1/0){for(var _=0;_=0&&y[_].startTime>=h;)_--;for(y.splice(_,y.length),_=y.length-1;_>=0&&y[_].endTime>h;)y[_].endTime=h,_--}if(void 0!==u)m=Math.max(p,u);else{if(y.length<=0)return a.Z.warn("NTSB: Current text tracks have no cues nor start time. Aborting"),Promise.resolve();a.Z.warn("NTSB: No start time given. Guessing from cues."),m=y[0].startTime}if(void 0!==c)g=Math.min(h,c);else{if(y.length<=0)return a.Z.warn("NTSB: Current text tracks have no cues nor end time. Aborting"),Promise.resolve();a.Z.warn("NTSB: No end time given. Guessing from cues."),g=y[y.length-1].endTime}if(g<=m)return a.Z.warn("NTSB: Invalid text track appended: ","the start time is inferior or equal to the end time."),Promise.resolve();if(y.length>0){var b=y[0],T=this._track.cues;null!==T&&T.length>0&&b.startTime=0;i--){var s=r[i],u=s.startTime,l=s.endTime;u>=e&&u<=t&&l<=t&&o(n,s)}this._buffered.remove(e,t)},t}(s.C)},9612:function(e,t,n){"use strict";n.d(t,{C:function(){return _},f:function(){return g}});var r=n(6872),i=n(3887),a=n(520),o=n(5278);function s(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return u(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return u(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(this._history=this._history.splice(r)),this._history.length>this._maxHistoryLength){var a=this._history.length-this._maxHistoryLength;this._history=this._history.splice(a)}},e}();function c(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return d(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return d(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){var _=t[p+y-1];g={end:(0,o.Z)(_.bufferedEnd,_.end),precizeEnd:_.precizeEnd},i.Z.debug("SI: "+y+" segments GCed.",u);for(var b,T=c(t.splice(p,y));!(b=T()).done;){var E=b.value;void 0===E.bufferedStart&&void 0===E.bufferedEnd&&this._bufferedHistory.addBufferedSegment(E.infos,null)}n=p}if(void 0===a)return;if(v-(0,o.Z)(a.bufferedStart,a.start)>=s){if(h(a,f,g,u),n===t.length-1)return void m(a,v,u);a=t[++n];for(var S=(0,o.Z)(a.bufferedStart,a.start),w=(0,o.Z)(a.bufferedEnd,a.end),k=d=s&&(void 0===k||v-S>=w-k);){var A=t[n-1];void 0===A.bufferedEnd&&(A.bufferedEnd=a.precizeStart?a.start:A.end,i.Z.debug("SI: calculating buffered end of contiguous segment",u,A.bufferedEnd,A.end)),a.bufferedStart=A.bufferedEnd,void 0!==(a=t[++n])&&(S=(0,o.Z)(a.bufferedStart,a.start),w=(0,o.Z)(a.bufferedEnd,a.end))}}var x=t[n-1];void 0!==x&&m(x,v,u)}}if(null!=a){i.Z.debug("SI: last segments have been GCed",u,n,t.length);for(var I,Z=c(t.splice(n,t.length-n));!(I=Z()).done;){var R=I.value;void 0===R.bufferedStart&&void 0===R.bufferedEnd&&this._bufferedHistory.addBufferedSegment(R.infos,null)}}void 0!==u&&i.Z.hasLevel("DEBUG")&&i.Z.debug("SI: current "+u+" inventory timeline:\n"+function(e){var t=1/60,n={},r=[],i=null,a=null;function o(e){var t=String.fromCharCode(r.length+65);return r.push({letter:t,periodId:e.period.id,representationId:e.representation.id,bitrate:e.representation.bitrate}),t}for(var s="",u=0;u=u)i.Z.warn("SI: Invalid chunked inserted: starts before it ends",l,s,u);else{for(var c=this._inventory,d={partiallyPushed:!0,chunkSize:o,splitted:!1,start:s,end:u,precizeStart:!1,precizeEnd:!1,bufferedStart:void 0,bufferedEnd:void 0,infos:{segment:a,period:t,adaptation:n,representation:r}},f=c.length-1;f>=0;f--){var v=c[f];if(v.start<=s){if(v.end<=s){for(i.Z.debug("SI: Pushing segment strictly after previous one.",l,s,v.end),this._inventory.splice(f+1,0,d),f+=2;fd.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,d.end,c[f].start),c[f].start=d.end,c[f].bufferedStart=void 0,void(c[f].precizeStart=c[f].precizeStart&&d.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,c[f].start,c[f].end),c.splice(f,1)}return}if(v.start===s){if(v.end<=u){for(i.Z.debug("SI: Segment pushed replace another one",l,s,u,v.end),this._inventory.splice(f,1,d),f+=1;fd.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,d.end,c[f].start),c[f].start=d.end,c[f].bufferedStart=void 0,void(c[f].precizeStart=c[f].precizeStart&&d.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,c[f].start,c[f].end),c.splice(f,1)}return}return i.Z.debug("SI: Segment pushed ends before another with the same start",l,s,u,v.end),c.splice(f,0,d),v.start=d.end,v.bufferedStart=void 0,void(v.precizeStart=v.precizeStart&&d.precizeEnd)}if(v.end<=d.end){for(i.Z.debug("SI: Segment pushed updates end of previous one",l,s,u,v.start,v.end),this._inventory.splice(f+1,0,d),v.end=d.start,v.bufferedEnd=void 0,v.precizeEnd=v.precizeEnd&&d.precizeStart,f+=2;fd.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,d.end,c[f].start),c[f].start=d.end,c[f].bufferedStart=void 0,void(c[f].precizeStart=c[f].precizeStart&&d.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,c[f].start,c[f].end),c.splice(f,1)}return}i.Z.warn("SI: Segment pushed is contained in a previous one",l,s,u,v.start,v.end);var p={partiallyPushed:v.partiallyPushed,chunkSize:v.chunkSize,splitted:!0,start:d.end,end:v.end,precizeStart:v.precizeStart&&v.precizeEnd&&d.precizeEnd,precizeEnd:v.precizeEnd,bufferedStart:void 0,bufferedEnd:v.end,infos:v.infos};return v.end=d.start,v.splitted=!0,v.bufferedEnd=void 0,v.precizeEnd=v.precizeEnd&&d.precizeStart,c.splice(f+1,0,d),void c.splice(f+2,0,p)}}var h=this._inventory[0];if(void 0===h)return i.Z.debug("SI: first segment pushed",l,s,u),void this._inventory.push(d);if(!(h.start>=u)){if(h.end<=u){for(i.Z.debug("SI: Segment pushed starts before and completely recovers the previous first one",l,s,u,h.start,h.end),this._inventory.splice(0,1,d);c.length>1&&c[1].startd.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,d.end,c[1].start),c[1].start=d.end,c[1].bufferedStart=void 0,void(c[1].precizeStart=d.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,c[1].start,c[1].end),c.splice(1,1)}return}return i.Z.debug("SI: Segment pushed start of the next one",l,s,u,h.start,h.end),h.start=u,h.bufferedStart=void 0,h.precizeStart=d.precizeEnd,void this._inventory.splice(0,0,d)}i.Z.debug("SI: Segment pushed comes before all previous ones",l,s,u,h.start),this._inventory.splice(0,0,d)}}},t.completeSegment=function(e,t){if(!e.segment.isInit){for(var n=this._inventory,r=[],o=0;o0&&(s=!0,1===r.length&&(i.Z.warn("SI: Completed Segment is splitted.",e.segment.id,e.segment.time,e.segment.end),r[0].splitted=!0));var u=o,l=n[o].chunkSize;for(o+=1;o0&&(this._inventory.splice(u+1,v),o-=v),this._inventory[u].partiallyPushed=!1,this._inventory[u].chunkSize=l,this._inventory[u].end=p,this._inventory[u].bufferedEnd=h,this._inventory[u].splitted=s,r.push(this._inventory[u])}if(0===r.length)i.Z.warn("SI: Completed Segment not found",e.segment.id,e.segment.time);else{this.synchronizeBuffered(t);for(var m,g=c(r);!(m=g()).done;){var y=m.value;void 0!==y.bufferedStart&&void 0!==y.bufferedEnd?this._bufferedHistory.addBufferedSegment(y.infos,{start:y.bufferedStart,end:y.bufferedEnd}):i.Z.debug("SI: buffered range not known after sync. Skipping history.",y.start,y.end)}}}},t.getInventory=function(){return this._inventory},t.getHistoryFor=function(e){return this._bufferedHistory.getHistoryFor(e)},e}();function v(e){if(void 0===e.bufferedStart||e.partiallyPushed)return!1;var t=e.start,n=e.end-t,i=r.Z.getCurrent(),a=i.MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE,o=i.MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE;return Math.abs(t-e.bufferedStart)<=a&&(void 0===e.bufferedEnd||e.bufferedEnd>e.bufferedStart&&Math.abs(e.bufferedEnd-e.bufferedStart-n)<=Math.min(o,n/3))}function p(e){if(void 0===e.bufferedEnd||e.partiallyPushed)return!1;var t=e.start,n=e.end,i=n-t,a=r.Z.getCurrent(),o=a.MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE,s=a.MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE;return Math.abs(n-e.bufferedEnd)<=o&&null!=e.bufferedStart&&e.bufferedEnd>e.bufferedStart&&Math.abs(e.bufferedEnd-e.bufferedStart-i)<=Math.min(s,i/3)}function h(e,t,n,a){var o=r.Z.getCurrent().MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE;void 0!==e.bufferedStart?(e.bufferedStartt&&(n.precizeEnd||e.start-n.end<=o)?(i.Z.debug("SI: buffered start is end of previous segment",a,t,e.start,n.end),e.bufferedStart=n.end,v(e)&&(e.start=n.end,e.precizeStart=!0)):e.start-t<=o?(i.Z.debug("SI: found true buffered start",a,t,e.start),e.bufferedStart=t,v(e)&&(e.start=t,e.precizeStart=!0)):tt&&(i.Z.debug("SI: Segment partially GCed at the end",n,e.bufferedEnd,t),e.bufferedEnd=t),!e.precizeEnd&&t-e.end<=a&&p(e)&&(e.precizeEnd=!0,e.end=t)):e.precizeEnd?(i.Z.debug("SI: buffered end is precize end",n,e.end),e.bufferedEnd=e.end):t-e.end<=a?(i.Z.debug("SI: found true buffered end",n,t,e.end),e.bufferedEnd=t,p(e)&&(e.end=t,e.precizeEnd=!0)):t>e.end?(i.Z.debug("SI: range end too far from expected end",n,t,e.end),e.bufferedEnd=e.end):(i.Z.debug("SI: Segment appears immediately garbage collected at the end",n,e.bufferedEnd,t),e.bufferedEnd=t)}var g,y=f,_=function(){function e(){this._segmentInventory=new y}var t=e.prototype;return t.synchronizeInventory=function(){this._segmentInventory.synchronizeBuffered(this.getBufferedRanges())},t.getInventory=function(){return this._segmentInventory.getInventory()},t.getPendingOperations=function(){return[]},t.getSegmentHistory=function(e){return this._segmentInventory.getHistoryFor(e)},e}();!function(e){e[e.Push=0]="Push",e[e.Remove=1]="Remove",e[e.EndOfSegment=2]="EndOfSegment"}(g||(g={}))},4309:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(2829),i=function(){function e(){this._ranges=[],this.length=0}var t=e.prototype;return t.insert=function(e,t){(0,r.kR)(this._ranges,{start:e,end:t}),this.length=this._ranges.length},t.remove=function(e,t){var n=[];e>0&&n.push({start:0,end:e}),t<1/0&&n.push({start:t,end:1/0}),this._ranges=(0,r.tn)(this._ranges,n),this.length=this._ranges.length},t.start=function(e){if(e>=this._ranges.length)throw new Error("INDEX_SIZE_ERROR");return this._ranges[e].start},t.end=function(e){if(e>=this._ranges.length)throw new Error("INDEX_SIZE_ERROR");return this._ranges[e].end},e}()},8567:function(e,t,n){"use strict";var r=n(8026),i={activePeriodChanged:function(e){return{type:"activePeriodChanged",value:{period:e}}},adaptationChange:function(e,t,n){return{type:"adaptationChange",value:{type:e,adaptation:t,period:n}}},addedSegment:function(e,t,n,r){return{type:"added-segment",value:{content:e,segment:t,segmentData:r,buffered:n}}},bitrateEstimationChange:function(e,t){return{type:"bitrateEstimationChange",value:{type:e,bitrate:t}}},streamComplete:function(e){return{type:"complete-stream",value:{type:e}}},endOfStream:function(){return{type:"end-of-stream",value:void 0}},needsManifestRefresh:function(){return{type:"needs-manifest-refresh",value:void 0}},manifestMightBeOufOfSync:function(){return{type:"manifest-might-be-out-of-sync",value:void 0}},needsMediaSourceReload:function(e,t){return{type:"needs-media-source-reload",value:{position:e,autoPlay:t}}},lockedStream:function(e,t){return{type:"locked-stream",value:{bufferType:e,period:t}}},needsBufferFlush:function(){return{type:"needs-buffer-flush",value:void 0}},needsDecipherabilityFlush:function(e,t,n){return{type:"needs-decipherability-flush",value:{position:e,autoPlay:t,duration:n}}},periodStreamReady:function(e,t,n){return{type:"periodStreamReady",value:{type:e,period:t,adaptation$:n}}},periodStreamCleared:function(e,t){return{type:"periodStreamCleared",value:{type:e,period:t}}},encryptionDataEncountered:function(e,t){return{type:"encryption-data-encountered",value:(0,r.Z)({content:t},e)}},representationChange:function(e,t,n){return{type:"representationChange",value:{type:e,period:t,representation:n}}},streamTerminating:function(){return{type:"stream-terminating",value:void 0}},resumeStream:function(){return{type:"resume-stream",value:void 0}},warning:function(e){return{type:"warning",value:e}},waitingMediaSourceReload:function(e,t,n,r){return{type:"waiting-media-source-reload",value:{bufferType:e,period:t,position:n,autoPlay:r}}}};t.Z=i},7839:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(7326),i=n(4578),a=function(e){function t(n,i,a,o){var s;return s=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(s),t.prototype),s.name="CustomLoaderError",s.message=n,s.canRetry=i,s.isOfflineError=a,s.xhr=o,s}return(0,i.Z)(t,e),t}((0,n(2146).Z)(Error))},5157:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i,a){var u;return u=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(u),t.prototype),u.name="EncryptedMediaError",u.type=o.ZB.ENCRYPTED_MEDIA_ERROR,u.code=n,u.message=(0,s.Z)(u.name,u.code,i),u.fatal=!1,"string"==typeof(null==a?void 0:a.keyStatuses)&&(u.keyStatuses=a.keyStatuses),u}return(0,i.Z)(t,e),t}((0,a.Z)(Error))},5992:function(e,t,n){"use strict";n.d(t,{SM:function(){return a},ZB:function(){return r},br:function(){return i}});var r={NETWORK_ERROR:"NETWORK_ERROR",MEDIA_ERROR:"MEDIA_ERROR",ENCRYPTED_MEDIA_ERROR:"ENCRYPTED_MEDIA_ERROR",OTHER_ERROR:"OTHER_ERROR"},i={TIMEOUT:"TIMEOUT",ERROR_EVENT:"ERROR_EVENT",ERROR_HTTP_CODE:"ERROR_HTTP_CODE",PARSE_ERROR:"PARSE_ERROR"},a={PIPELINE_LOAD_ERROR:"PIPELINE_LOAD_ERROR",PIPELINE_PARSE_ERROR:"PIPELINE_PARSE_ERROR",INTEGRITY_ERROR:"INTEGRITY_ERROR",MANIFEST_PARSE_ERROR:"MANIFEST_PARSE_ERROR",MANIFEST_INCOMPATIBLE_CODECS_ERROR:"MANIFEST_INCOMPATIBLE_CODECS_ERROR",MANIFEST_UPDATE_ERROR:"MANIFEST_UPDATE_ERROR",MANIFEST_UNSUPPORTED_ADAPTATION_TYPE:"MANIFEST_UNSUPPORTED_ADAPTATION_TYPE",MEDIA_STARTING_TIME_NOT_FOUND:"MEDIA_STARTING_TIME_NOT_FOUND",MEDIA_TIME_BEFORE_MANIFEST:"MEDIA_TIME_BEFORE_MANIFEST",MEDIA_TIME_AFTER_MANIFEST:"MEDIA_TIME_AFTER_MANIFEST",MEDIA_TIME_NOT_FOUND:"MEDIA_TIME_NOT_FOUND",NO_PLAYABLE_REPRESENTATION:"NO_PLAYABLE_REPRESENTATION",MEDIA_IS_ENCRYPTED_ERROR:"MEDIA_IS_ENCRYPTED_ERROR",CREATE_MEDIA_KEYS_ERROR:"CREATE_MEDIA_KEYS_ERROR",KEY_ERROR:"KEY_ERROR",KEY_STATUS_CHANGE_ERROR:"KEY_STATUS_CHANGE_ERROR",KEY_UPDATE_ERROR:"KEY_UPDATE_ERROR",KEY_LOAD_ERROR:"KEY_LOAD_ERROR",KEY_LOAD_TIMEOUT:"KEY_LOAD_TIMEOUT",KEY_GENERATE_REQUEST_ERROR:"KEY_GENERATE_REQUEST_ERROR",INCOMPATIBLE_KEYSYSTEMS:"INCOMPATIBLE_KEYSYSTEMS",INVALID_ENCRYPTED_EVENT:"INVALID_ENCRYPTED_EVENT",INVALID_KEY_SYSTEM:"INVALID_KEY_SYSTEM",LICENSE_SERVER_CERTIFICATE_ERROR:"LICENSE_SERVER_CERTIFICATE_ERROR",MULTIPLE_SESSIONS_SAME_INIT_DATA:"MULTIPLE_SESSIONS_SAME_INIT_DATA",BUFFER_APPEND_ERROR:"BUFFER_APPEND_ERROR",BUFFER_FULL_ERROR:"BUFFER_FULL_ERROR",BUFFER_TYPE_UNKNOWN:"BUFFER_TYPE_UNKNOWN",MEDIA_ERR_BLOCKED_AUTOPLAY:"MEDIA_ERR_BLOCKED_AUTOPLAY",MEDIA_ERR_PLAY_NOT_ALLOWED:"MEDIA_ERR_PLAY_NOT_ALLOWED",MEDIA_ERR_NOT_LOADED_METADATA:"MEDIA_ERR_NOT_LOADED_METADATA",MEDIA_ERR_ABORTED:"MEDIA_ERR_ABORTED",MEDIA_ERR_NETWORK:"MEDIA_ERR_NETWORK",MEDIA_ERR_DECODE:"MEDIA_ERR_DECODE",MEDIA_ERR_SRC_NOT_SUPPORTED:"MEDIA_ERR_SRC_NOT_SUPPORTED",MEDIA_ERR_UNKNOWN:"MEDIA_ERR_UNKNOWN",MEDIA_SOURCE_NOT_SUPPORTED:"MEDIA_SOURCE_NOT_SUPPORTED",MEDIA_KEYS_NOT_SUPPORTED:"MEDIA_KEYS_NOT_SUPPORTED",DISCONTINUITY_ENCOUNTERED:"DISCONTINUITY_ENCOUNTERED",NONE:"NONE"}},7367:function(e,t,n){"use strict";function r(e,t,n){return e+" ("+t+") "+n}n.d(t,{Z:function(){return r}})},8750:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(9822),i=n(5389);function a(e,t){var n=t.defaultCode,a=t.defaultReason;if((0,r.Z)(e))return e;var o=e instanceof Error?e.toString():a;return new i.Z(n,o)}},9822:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(5157),i=n(5992),a=n(3714),o=n(9362),s=n(5389);function u(e){return(e instanceof r.Z||e instanceof a.Z||e instanceof s.Z||e instanceof o.Z)&&Object.keys(i.ZB).indexOf(e.type)>=0}},3714:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i){var a;return a=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(a),t.prototype),a.name="MediaError",a.type=o.ZB.MEDIA_ERROR,a.code=n,a.message=(0,s.Z)(a.name,a.code,i),a.fatal=!1,a}return(0,i.Z)(t,e),t}((0,a.Z)(Error))},9362:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i){var a;return a=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(a),t.prototype),a.name="NetworkError",a.type=o.ZB.NETWORK_ERROR,a.xhr=void 0===i.xhr?null:i.xhr,a.url=i.url,a.status=i.status,a.errorType=i.type,a.code=n,a.message=(0,s.Z)(a.name,a.code,i.message),a.fatal=!1,a}return(0,i.Z)(t,e),t.prototype.isHttpError=function(e){return this.errorType===o.br.ERROR_HTTP_CODE&&this.status===e},t}((0,a.Z)(Error))},5389:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i){var a;return a=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(a),t.prototype),a.name="OtherError",a.type=o.ZB.OTHER_ERROR,a.code=n,a.message=(0,s.Z)(a.name,a.code,i),a.fatal=!1,a}return(0,i.Z)(t,e),t}((0,a.Z)(Error))},9105:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(7326),i=n(4578),a=function(e){function t(n,i,a,o){var s;return s=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(s),t.prototype),s.name="RequestError",s.url=n,void 0!==o&&(s.xhr=o),s.status=i,s.type=a,s.message=a,s}return(0,i.Z)(t,e),t}((0,n(2146).Z)(Error))},7273:function(e,t){"use strict";t.Z={dashParsers:{wasm:null,js:null},directfile:null,ContentDecryptor:null,htmlTextTracksBuffer:null,htmlTextTracksParsers:{},imageBuffer:null,imageParser:null,nativeTextTracksBuffer:null,nativeTextTracksParsers:{},transports:{}}},7874:function(e,t,n){"use strict";var r=n(7273);t.Z=r.Z},3887:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(8894),i=new(function(){function e(){this.error=r.Z,this.warn=r.Z,this.info=r.Z,this.debug=r.Z,this._levels={NONE:0,ERROR:1,WARNING:2,INFO:3,DEBUG:4},this._currentLevel="NONE"}var t=e.prototype;return t.setLevel=function(e){var t,n=this._levels[e];"number"==typeof n?(t=n,this._currentLevel=e):(t=0,this._currentLevel="NONE"),this.error=t>=this._levels.ERROR?console.error.bind(console):r.Z,this.warn=t>=this._levels.WARNING?console.warn.bind(console):r.Z,this.info=t>=this._levels.INFO?console.info.bind(console):r.Z,this.debug=t>=this._levels.DEBUG?console.log.bind(console):r.Z},t.getLevel=function(){return this._currentLevel},t.hasLevel=function(e){return this._levels[e]>=this._levels[this._currentLevel]},e}())},8999:function(e,t,n){"use strict";n.d(t,{r:function(){return v},Z:function(){return p}});var r=n(3274),i=n(1946),a=n(7829);var o="undefined"!=typeof window&&"function"==typeof window.Set&&"function"==typeof Array.from?function(e){return Array.from(new Set(e))}:function(e){return e.filter((function(e,t,n){return n.indexOf(e)===t}))},s=n(3774);var u=n(3887),l=n(4791);function c(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return d(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return d(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(this.trickModeTracks=r.map((function(t){return new e(t)})));for(var l=t.representations,c=[],d=!1,v=0;v0&&!r.isSupported){var i=new a.Z("MANIFEST_INCOMPATIBLE_CODECS_ERROR","An Adaptation contains only incompatible codecs.");n.contentWarnings.push(i)}return r})).filter((function(e){return e.representations.length>0}));if(s.every((function(e){return!e.isSupported}))&&o.length>0&&("video"===i||"audio"===i))throw new a.Z("MANIFEST_PARSE_ERROR","No supported "+i+" adaptations");return s.length>0&&(r[i]=s),r}),{}),!Array.isArray(this.adaptations.video)&&!Array.isArray(this.adaptations.audio))throw new a.Z("MANIFEST_PARSE_ERROR","No supported audio and video tracks.");this.duration=e.duration,this.start=e.start,null!=this.duration&&null!=this.start&&(this.end=this.start+this.duration),this.streamEvents=void 0===e.streamEvents?[]:e.streamEvents}var t=e.prototype;return t.getAdaptations=function(){var e=this.adaptations;return(0,f.Z)(e).reduce((function(e,t){return null!=t?e.concat(t):e}),[])},t.getAdaptationsForType=function(e){var t=this.adaptations[e];return null==t?[]:t},t.getAdaptation=function(e){return(0,o.Z)(this.getAdaptations(),(function(t){var n=t.id;return e===n}))},t.getSupportedAdaptations=function(e){if(void 0===e)return this.getAdaptations().filter((function(e){return e.isSupported}));var t=this.adaptations[e];return void 0===t?[]:t.filter((function(e){return e.isSupported}))},t.containsTime=function(e){return e>=this.start&&(void 0===this.end||e=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&r._addSupplementaryImageAdaptations(u),o.length>0&&r._addSupplementaryTextAdaptations(o),r}(0,i.Z)(t,e);var n=t.prototype;return n.getPeriod=function(e){return(0,o.Z)(this.periods,(function(t){return e===t.id}))},n.getPeriodForTime=function(e){return(0,o.Z)(this.periods,(function(t){return e>=t.start&&(void 0===t.end||t.end>e)}))},n.getNextPeriod=function(e){return(0,o.Z)(this.periods,(function(t){return t.start>e}))},n.getPeriodAfter=function(e){var t=e.end;if(void 0===t)return null;var n=(0,o.Z)(this.periods,(function(e){return void 0===e.end||t0&&this.trigger("decipherabilityUpdate",t)},n.getAdaptations=function(){(0,c.Z)("manifest.getAdaptations() is deprecated. Please use manifest.period[].getAdaptations() instead");var e=this.periods[0];if(void 0===e)return[];var t=e.adaptations,n=[];for(var r in t)if(t.hasOwnProperty(r)){var i=t[r];n.push.apply(n,i)}return n},n.getAdaptationsForType=function(e){(0,c.Z)("manifest.getAdaptationsForType(type) is deprecated. Please use manifest.period[].getAdaptationsForType(type) instead");var t=this.periods[0];if(void 0===t)return[];var n=t.adaptations[e];return void 0===n?[]:n},n.getAdaptation=function(e){return(0,c.Z)("manifest.getAdaptation(id) is deprecated. Please use manifest.period[].getAdaptation(id) instead"),(0,o.Z)(this.getAdaptations(),(function(t){var n=t.id;return e===n}))},n._addSupplementaryImageAdaptations=function(e){var t=this,n=(Array.isArray(e)?e:[e]).map((function(e){var n=e.mimeType,r=e.url,i="gen-image-ada-"+b(),o="gen-image-rep-"+b(),s=(0,l.$)(r),u=r.substring(0,s),c=r.substring(s),f=new d.Z({id:i,type:"image",representations:[{bitrate:0,cdnMetadata:[{baseUrl:u}],id:o,mimeType:n,index:new h({media:c})}]},{isManuallyAdded:!0});if(f.representations.length>0&&!f.isSupported){var v=new a.Z("MANIFEST_INCOMPATIBLE_CODECS_ERROR","An Adaptation contains only incompatible codecs.");t.contentWarnings.push(v)}return f}));if(n.length>0&&this.periods.length>0){var r=this.periods[0].adaptations;r.image=null!=r.image?r.image.concat(n):n}},n._addSupplementaryTextAdaptations=function(e){var t=this,n=(Array.isArray(e)?e:[e]).reduce((function(e,n){var r=n.mimeType,i=n.codecs,o=n.url,s=n.language,u=n.languages,c=n.closedCaption,f=null!=s?[s]:null!=u?u:[],v=(0,l.$)(o),p=o.substring(0,v),m=o.substring(v);return e.concat(f.map((function(e){var n="gen-text-ada-"+b(),o="gen-text-rep-"+b(),s=new d.Z({id:n,type:"text",language:e,closedCaption:c,representations:[{bitrate:0,cdnMetadata:[{baseUrl:p}],id:o,mimeType:r,codecs:i,index:new h({media:m})}]},{isManuallyAdded:!0});if(s.representations.length>0&&!s.isSupported){var u=new a.Z("MANIFEST_INCOMPATIBLE_CODECS_ERROR","An Adaptation contains only incompatible codecs.");t.contentWarnings.push(u)}return s})))}),[]);if(n.length>0&&this.periods.length>0){var r=this.periods[0].adaptations;r.text=null!=r.text?r.text.concat(n):n}},n._performUpdate=function(e,t){if(this.availabilityStartTime=e.availabilityStartTime,this.expired=e.expired,this.isDynamic=e.isDynamic,this.isLive=e.isLive,this.isLastPeriodKnown=e.isLastPeriodKnown,this.lifetime=e.lifetime,this.contentWarnings=e.contentWarnings,this.suggestedPresentationDelay=e.suggestedPresentationDelay,this.transport=e.transport,this.publishTime=e.publishTime,t===r.Full)this._timeBounds=e._timeBounds,this.uris=e.uris,function(e,t){for(var n=0,i=0;ie.length)p.Z.error("Manifest: error when updating Periods");else{n0&&e.push.apply(e,c)}}(this.periods,e.periods);else{this._timeBounds.maximumTimeData=e._timeBounds.maximumTimeData,this.updateUrl=e.uris[0],function(e,t){if(0!==e.length){if(0!==t.length){var n=e[e.length-1];if(n.starto&&(e.splice(o,l-o),l=o),g(e[l],u,r.Full),o++}o0;){var i=this.periods[0];if(void 0===i.end||i.end>n)break;this.periods.shift()}}this.adaptations=void 0===this.periods[0]?{}:this.periods[0].adaptations,this.trigger("manifestUpdate",null)},t}(s.Z);var S=E},520:function(e,t,n){"use strict";n.d(t,{K:function(){return a},z:function(){return i}});var r=n(1946);function i(e,t){return e.segment.id===t.segment.id&&e.representation.id===t.representation.id&&e.adaptation.id===t.adaptation.id&&e.period.id===t.period.id}function a(e){if((0,r.Z)(e))return"";var t=e.period,n=e.adaptation,i=e.representation,a=e.segment;return n.type+" P: "+t.id+" A: "+n.id+" R: "+i.id+" S: "+(a.isInit?"init":a.complete?a.time+"-"+a.duration:""+a.time)}},2689:function(e,t,n){"use strict";n.d(t,{s:function(){return r}});var r=Math.pow(2,32)-1},2297:function(e,t,n){"use strict";n.d(t,{Qy:function(){return f},Xj:function(){return p},iz:function(){return d},lp:function(){return c},nR:function(){return v},t_:function(){return l},vA:function(){return u}});var r=n(3887),i=n(811),a=n(6968);function o(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return s(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return s(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ni)return null;s=(0,a.pV)(e,r),r+=8}if(s<0)throw new Error("ISOBMFF: Size out of range");if(n===t)return 1970628964===t&&(r+=16),[o,r,o+s];o+=s}return null}function v(e,t,n,r,i){for(var o,s=e.length,u=0;us)return;o=(0,a.pV)(e,l),l+=8}if(1970628964===c&&l+16<=s&&(0,a.pX)(e,l)===t&&(0,a.pX)(e,l+4)===n&&(0,a.pX)(e,l+8)===r&&(0,a.pX)(e,l+12)===i)return l+=16,e.subarray(l,u+o)}}function p(e){var t=e.length;if(t<8)return r.Z.warn("ISOBMFF: box inferior to 8 bytes, cannot find offsets"),null;var n=0,i=(0,a.pX)(e,n);n+=4;var o=(0,a.pX)(e,n);if(n+=4,0===i)i=t;else if(1===i){if(n+8>t)return r.Z.warn("ISOBMFF: box too short, cannot find offsets"),null;i=(0,a.pV)(e,n),n+=8}if(i<0)throw new Error("ISOBMFF: Size out of range");return 1970628964===o&&(n+=16),[0,n,i]}},6807:function(e,t,n){"use strict";n.d(t,{E3:function(){return u},Le:function(){return o},XA:function(){return i},fs:function(){return s},uq:function(){return a}});var r=n(2297);function i(e){var t=(0,r.t_)(e,1836019558);return null===t?null:(0,r.t_)(t,1953653094)}function a(e){return(0,r.lp)(e,1836019558).reduce((function(e,t){var n=(0,r.t_)(t,1953653094);return null!==n&&e.push(n),e}),[])}function o(e){return(0,r.t_)(e,1835295092)}function s(e){var t=(0,r.t_)(e,1836019574);if(null===t)return null;var n=(0,r.t_)(t,1953653099);return null===n?null:(0,r.t_)(n,1835297121)}function u(e,t){return void 0===t&&(t=0),(0,r.t_)(e.subarray(t),1701671783)}},6490:function(e,t,n){"use strict";n.d(t,{Z:function(){return s},Y:function(){return u}});var r=n(3887);var i="function"==typeof Uint8Array.prototype.slice?function(e,t,n){return e.slice(t,n)}:function(e,t,n){return new Uint8Array(Array.prototype.slice.call(e,t,n))},a=n(3635),o=n(2297);function s(e){var t=0,n=(0,o.t_)(e,1836019574);if(null===n)return[];for(var a=[];t1)r.Z.warn("ISOBMFF: un-handled PSSH version");else{var n=t+4;if(!(n+16>e.length)){var o=i(e,n,n+16);return(0,a.ci)(o)}}}},4644:function(e,t,n){"use strict";n.d(t,{J6:function(){return m},LD:function(){return h},MM:function(){return p},Qx:function(){return f},R0:function(){return y},Wf:function(){return d},s9:function(){return g}});var r=n(3887),i=n(6968),a=n(3635),o=n(2689),s=n(2297),u=n(6807);function l(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return c(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return c(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0;){var v=(0,i.pX)(e,o);o+=4;var p=2147483647&v;if(1===(2147483648&v)>>>31)throw new Error("sidx with reference_type `1` not yet implemented");var h=(0,i.pX)(e,o);o+=4,o+=4,d.push({time:l,duration:h,timescale:c,range:[r,r+p-1]}),l+=h,r+=p}return d}function f(e){var t=(0,u.XA)(e);if(null!==t){var n=(0,s.t_)(t,1952867444);if(null!==n){var r=n[0];return 1===r?(0,i.pV)(n,4):0===r?(0,i.pX)(n,4):void 0}}}function v(e){var t=(0,s.t_)(e,1952868452);if(null!==t){var n=1,r=(0,i.QI)(t,n);if(n+=3,(8&r)>0)return n+=4,(1&r)>0&&(n+=8),(2&r)>0&&(n+=4),(0,i.pX)(t,n)}}function p(e){var t=(0,u.uq)(e);if(0!==t.length){for(var n,r=0,a=l(t);!(n=a()).done;){var o=n.value,c=(0,s.t_)(o,1953658222);if(null===c)return;var d=0,f=c[d];if(d+=1,f>1)return;var p=(0,i.QI)(c,d);d+=3;var h=(256&p)>0,m=0;if(!h&&void 0===(m=v(o)))return;var g=(1&p)>0,y=(4&p)>0,_=(512&p)>0,b=(1024&p)>0,T=(2048&p)>0,E=(0,i.pX)(c,d);d+=4,g&&(d+=4),y&&(d+=4);for(var S=E,w=0;S-- >0;)h?(w+=(0,i.pX)(c,d),d+=4):w+=m,_&&(d+=4),b&&(d+=4),T&&(d+=4);r+=w}return r}}function h(e){var t=(0,u.fs)(e);if(null!==t){var n=(0,s.t_)(t,1835296868);if(null!==n){var r=0,a=n[r];return r+=4,1===a?(0,i.pX)(n,r+16):0===a?(0,i.pX)(n,r+8):void 0}}}function m(e){var t=e.length;if(t<4)throw new Error("Cannot update box length: box too short");var n=(0,i.pX)(e,0);if(0===n){if(t>o.s){var r=new Uint8Array(t+8);return r.set((0,i.kh)(1),0),r.set(e.subarray(4,8),4),r.set((0,i.el)(t+8),8),r.set(e.subarray(8,t),16),r}return e.set((0,i.kh)(t),0),e}if(1===n){if(t<16)throw new Error("Cannot update box length: box too short");return e.set((0,i.el)(t),8),e}if(t<=o.s)return e.set((0,i.kh)(t),0),e;var a=new Uint8Array(t+8);return a.set((0,i.kh)(1),0),a.set(e.subarray(4,8),4),a.set((0,i.el)(t+8),8),a.set(e.subarray(8,t),16),a}function g(e){for(var t=[],n=0;n0)throw new Error("Unhandled version: "+s);var d=(0,r.dN)(e,t);t+=4;var f=(0,r.dN)(e,t);t+=4;var v=(0,i.uR)(e.subarray(t,t+4));t+=4;var p=(0,r.qb)(e,t);t+=2;var h=(0,r.qb)(e,t),m=[e[t+=2],e[t+1]].join(":"),g=1===e[t+=2];t=64;var y=[];if(0===d)throw new Error("bif: no images to parse");for(var _=0,b=null;t0,this._isEMSGWhitelisted=d}var t=e.prototype;return t.getInitSegment=function(){return(0,a.Z)(this._index,this._isEMSGWhitelisted)},t.getSegments=function(e,t){return(0,o.Z)(this._index,e,t,this._isEMSGWhitelisted,this._scaledPeriodEnd)},t.shouldRefresh=function(){return!1},t.getFirstAvailablePosition=function(){var e=this._index;return 0===e.timeline.length?null:(0,i.zG)(Math.max(this._scaledPeriodStart,e.timeline[0].start),e)},t.getLastAvailablePosition=function(){var e,t=this._index.timeline;if(0===t.length)return null;var n=t[t.length-1],r=Math.min((0,i.jH)(n,null,this._scaledPeriodEnd),null!==(e=this._scaledPeriodEnd)&&void 0!==e?e:1/0);return(0,i.zG)(r,this._index)},t.getEnd=function(){return this.getLastAvailablePosition()},t.awaitSegmentBetween=function(){return!1},t.isSegmentStillAvailable=function(){return!0},t.checkDiscontinuity=function(){return null},t.initializeIndex=function(e){for(var t=0;t0?Math.floor(u/s):0),A=T+k*b;A=c)return m;h+=S+1}return m}},4784:function(e,t,n){"use strict";n.d(t,{QB:function(){return o},zA:function(){return a}});var r=n(6923);function i(e){return function(t,n,i){var a,o,s,u=(0,r.Z)(i)?parseInt(i,10):1;return a=String(e),o=u,(s=a.toString()).length>=o?s:(new Array(o+1).join("0")+s).slice(-o)}}function a(e,t,n){return function(e,t,n){return-1===e.indexOf("$")?e:e.replace(/\$\$/g,"$").replace(/\$RepresentationID\$/g,String(t)).replace(/\$Bandwidth(\%0(\d+)d)?\$/g,i(void 0===n?0:n))}(e,t,n)}function o(e,t){return function(n){return-1===n.indexOf("$")?n:n.replace(/\$\$/g,"$").replace(/\$Number(\%0(\d+)d)?\$/g,(function(e,n,r){if(void 0===t)throw new Error("Segment number not defined in a $Number$ scheme");return i(t)(e,n,r)})).replace(/\$Time(\%0(\d+)d)?\$/g,(function(t,n,r){if(void 0===e)throw new Error("Segment time not defined in a $Time$ scheme");return i(e)(t,n,r)}))}}},4541:function(e,t,n){"use strict";n.d(t,{Z:function(){return We}});var r=n(7904),i=n(1946),a=n(6872),o=n(3887),s=n(3274),u=n(9829);function l(e){var t=Date.parse(e)-performance.now();if(!isNaN(t))return t;o.Z.warn("DASH Parser: Invalid clock received: ",e)}function c(e){for(var t=e.representations,n=null,r=0;r=0;t--){var n=e[t].adaptations,r=void 0===n.audio?void 0:n.audio[0],i=void 0===n.video?void 0:n.video[0];if(void 0!==r||void 0!==i){var a=null,s=null;if(void 0!==r){var u=c(r);if(void 0===u)return{safe:void 0,unsafe:void 0};a=u}if(void 0!==i){var l=c(i);if(void 0===l)return{safe:void 0,unsafe:void 0};s=l}if(void 0!==r&&null===a||void 0!==i&&null===s)return o.Z.info("Parser utils: found Period with no segment. ","Going to previous one to calculate last position"),{safe:void 0,unsafe:void 0};if(null!==s)return null!==a?{safe:Math.min(a,s),unsafe:Math.max(a,s)}:{safe:s,unsafe:s};if(null!==a)return{safe:a,unsafe:a}}}return{safe:void 0,unsafe:void 0}}(e);return{minimumSafePosition:t,maximumSafePosition:n.safe,maximumUnsafePosition:n.unsafe}}var v=n(9592),p=n(908),h=n(1679),m=n(3635);var g=function(){function e(e){this._isDynamic=e.isDynamic,this._timeShiftBufferDepth=e.isDynamic&&void 0!==e.timeShiftBufferDepth?e.timeShiftBufferDepth:null}var t=e.prototype;return t.setLastPosition=function(e,t){this._lastPosition=e,this._positionTime=t},t.lastPositionIsKnown=function(){return this._isDynamic?null!=this._positionTime&&null!=this._lastPosition:null!=this._lastPosition},t.estimateMinimumBound=function(){if(!this._isDynamic||null===this._timeShiftBufferDepth)return 0;var e=this.estimateMaximumBound();return void 0!==e?e-this._timeShiftBufferDepth:void 0},t.estimateMaximumBound=function(){return this._isDynamic&&null!=this._positionTime&&null!=this._lastPosition?Math.max(this._lastPosition-this._positionTime+performance.now()/1e3,0):this._lastPosition},e}(),y=n(8999),_=n(5138),b=n(7714),T=n(6923);function E(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return S(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return S(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function S(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){var s=i-a.start;if(s%a.duration==0&&s/a.duration<=a.repeatCount)return{repeatNumberInPrevSegments:s/a.duration,prevSegmentsIdx:o,newElementsIdx:0,repeatNumberInNewElements:0}}if(++o>=e.length)return null;if((a=e[o]).start===i)return{prevSegmentsIdx:o,newElementsIdx:0,repeatNumberInPrevSegments:0,repeatNumberInNewElements:0};if(a.start>i)return null}else for(var u=0,l=t[0],c=i;;){var d=l.getAttribute("d"),f=null===d?null:parseInt(d,10);if(null===f||Number.isNaN(f))return null;var v=l.getAttribute("r"),p=null===v?null:parseInt(v,10);if(null!==p){if(Number.isNaN(p)||p<0)return null;if(p>0){var h=n-c;if(h%f==0&&h/f<=p)return{repeatNumberInPrevSegments:0,repeatNumberInNewElements:h/f,prevSegmentsIdx:0,newElementsIdx:u}}c+=f*(p+1)}else c+=f;if(++u>=t.length)return null;var m=(l=t[u]).getAttribute("t"),g=null===m?null:parseInt(m,10);if(null!==g){if(Number.isNaN(g))return null;c=g}if(c===n)return{newElementsIdx:u,prevSegmentsIdx:0,repeatNumberInPrevSegments:0,repeatNumberInNewElements:0};if(c>i)return null}}(t,e);if(null===r)return o.Z.warn('DASH: Cannot perform "based" update. Common segment not found.'),V(e);var i=r.prevSegmentsIdx,a=r.newElementsIdx,s=r.repeatNumberInPrevSegments,u=r.repeatNumberInNewElements,l=t.length-i+a-1;if(l>=e.length)return o.Z.info('DASH: Cannot perform "based" update. New timeline too short'),V(e);var c=t.slice(i);if(s>0){var d=c[0];d.start+=d.duration*s,c[0].repeatCount-=s}if(u>0&&0!==a)return o.Z.info('DASH: Cannot perform "based" update. The new timeline has a different form.'),V(e);var f=c[c.length-1],v=z(e[l]),p=(null!==(n=v.repeatCount)&&void 0!==n?n:0)-u;if(v.duration!==f.duration||f.repeatCount>p)return o.Z.info('DASH: Cannot perform "based" update. The new timeline has a different form at the beginning.'),V(e);void 0!==v.repeatCount&&v.repeatCount>f.repeatCount&&(f.repeatCount=v.repeatCount);for(var h=[],m=[],g=l+1;g0){var s=i[i.length-1];if((0,Z.jH)(s,null,this._scaledPeriodEnd)+a>=Math.min(o,null!==(n=this._scaledPeriodEnd)&&void 0!==n?n:1/0))return!1}return void 0===this._scaledPeriodEnd?o+a>this._scaledPeriodStart&&void 0:(0,Z.gT)(e,this._index)-athis._scaledPeriodStart},t.isSegmentStillAvailable=function(e){if(e.isInit)return!0;this._refreshTimeline(),null===this._index.timeline&&(this._index.timeline=this._getTimeline());var t=this._index,n=t.timeline,r=t.timescale,i=t.indexTimeOffset;return(0,O.Z)(e,n,r,i)},t.checkDiscontinuity=function(e){this._refreshTimeline();var t=this._index.timeline;return null===t&&(t=this._getTimeline(),this._index.timeline=t),(0,Z._j)({timeline:t,timescale:this._index.timescale,indexTimeOffset:this._index.indexTimeOffset},e,this._scaledPeriodEnd)},t.canBeOutOfSyncError=function(e){return!!this._isDynamic&&(e instanceof P.Z&&e.isHttpError(404))},t._replace=function(e){this._parseTimeline=e._parseTimeline,this._index=e._index,this._isDynamic=e._isDynamic,this._scaledPeriodStart=e._scaledPeriodStart,this._scaledPeriodEnd=e._scaledPeriodEnd,this._lastUpdate=e._lastUpdate,this._manifestBoundsCalculator=e._manifestBoundsCalculator,this._isLastPeriod=e._isLastPeriod},t._update=function(e){null===this._index.timeline&&(this._index.timeline=this._getTimeline()),null===e._index.timeline&&(e._index.timeline=e._getTimeline()),(0,L.Z)(this._index.timeline,e._index.timeline)&&(this._index.startNumber=e._index.startNumber),this._isDynamic=e._isDynamic,this._scaledPeriodStart=e._scaledPeriodStart,this._scaledPeriodEnd=e._scaledPeriodEnd,this._lastUpdate=e._lastUpdate,this._isLastPeriod=e._isLastPeriod},t.isFinished=function(){if(!this._isDynamic||!this._isLastPeriod)return!0;null===this._index.timeline&&(this._index.timeline=this._getTimeline());var e=this._index.timeline;if(void 0===this._scaledPeriodEnd||0===e.length)return!1;var t=e[e.length-1];return(0,Z.jH)(t,null,this._scaledPeriodEnd)+U(this._index.timescale)>=this._scaledPeriodEnd},t.isInitialized=function(){return!0},e.isTimelineIndexArgument=function(e){return"function"==typeof e.timelineParser||Array.isArray(e.timeline)},t._refreshTimeline=function(){if(null===this._index.timeline&&(this._index.timeline=this._getTimeline()),this._isDynamic){var e=this._manifestBoundsCalculator.estimateMinimumBound();if(null!=e){var t=(0,Z.gT)(e,this._index),n=(0,N.Z)(this._index.timeline,t);void 0!==this._index.startNumber&&(this._index.startNumber+=n)}}},e.getIndexEnd=function(e,t){return e.length<=0?null:Math.min((0,Z.jH)(e[e.length-1],null,t),null!=t?t:1/0)},t._getTimeline=function(){if(null===this._parseTimeline)return null!==this._index.timeline?this._index.timeline:(o.Z.error("DASH: Timeline already lazily parsed."),[]);var e=this._parseTimeline();this._parseTimeline=null;var t,n=a.Z.getCurrent().MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY;return null===this._unsafelyBaseOnPreviousIndex||e.lengthu?u-y:r,T=y+s,E=y+this._index.presentationTimeOffset,S=null===o?null:(0,M.QB)(E,_)(o),w={id:String(_),number:_,time:T/a,end:(T+b)/a,duration:b/a,timescale:1,isInit:!1,scaledDuration:b/a,url:S,timestampOffset:-n.indexTimeOffset/a,complete:!0,privateInfos:{isEMSGWhitelisted:this._isEMSGWhitelisted}};h.push(w),g++}return h},t.getFirstAvailablePosition=function(){var e=this._getFirstSegmentStart();return null==e?e:e/this._index.timescale+this._periodStart},t.getLastAvailablePosition=function(){var e,t=this._getLastSegmentStart();return null==t?t:Math.min(t+this._index.duration,null!==(e=this._scaledRelativePeriodEnd)&&void 0!==e?e:1/0)/this._index.timescale+this._periodStart},t.getEnd=function(){if(!this._isDynamic)return this.getLastAvailablePosition();if(void 0!==this._scaledRelativePeriodEnd){var e=this._index.timescale;return(this._scaledRelativePeriodEnd+this._periodStart*e)/this._index.timescale}},t.awaitSegmentBetween=function(e,t){if((0,D.Z)(e<=t),!this._isDynamic)return!1;var n=this._index.timescale,r=U(n),i=this._periodStart*n,a=t*n-i;return(void 0===this._scaledRelativePeriodEnd||e*n-i-r=0},t.shouldRefresh=function(){return!1},t.checkDiscontinuity=function(){return null},t.isSegmentStillAvailable=function(e){if(e.isInit)return!0;var t=this.getSegments(e.time,.1);return 0!==t.length&&(t[0].time===e.time&&t[0].end===e.end&&t[0].number===e.number)},t.canBeOutOfSyncError=function(){return!1},t.isFinished=function(){if(!this._isDynamic)return!0;if(void 0===this._scaledRelativePeriodEnd)return!1;var e=this._index.timescale,t=this._getLastSegmentStart();return null!=t&&t+this._index.duration+U(e)>=this._scaledRelativePeriodEnd},t.isInitialized=function(){return!0},t._replace=function(e){this._index=e._index,this._aggressiveMode=e._aggressiveMode,this._isDynamic=e._isDynamic,this._periodStart=e._periodStart,this._scaledRelativePeriodEnd=e._scaledRelativePeriodEnd,this._manifestBoundsCalculator=e._manifestBoundsCalculator},t._update=function(e){this._replace(e)},t._getFirstSegmentStart=function(){if(!this._isDynamic)return 0;if(0===this._scaledRelativePeriodEnd||void 0===this._scaledRelativePeriodEnd){var e=this._manifestBoundsCalculator.estimateMaximumBound();if(void 0!==e&&ethis._periodStart?(i-this._periodStart)*r:0;return Math.floor(a/n)*n}},t._getLastSegmentStart=function(){var e,t=this._index,n=t.duration,r=t.timescale;if(this._isDynamic){var i=this._manifestBoundsCalculator.estimateMaximumBound();if(void 0===i)return;var o=this._aggressiveMode?n/r:0;if(null!=this._scaledRelativePeriodEnd&&this._scaledRelativePeriodEnd<(i+o-this._periodStart)*this._index.timescale)return this._scaledRelativePeriodEnda.Z.getCurrent().MINIMUM_SEGMENT_SIZE*r||0===d?f:(d-1)*n},e}();function j(e,t){var n;if(0===t.length)return e;var r=t.map((function(e){return{url:e.value}}));if(0===e.length)return r;for(var i=[],a=0;a=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function Y(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){var y=t.parentSegmentTemplates.slice(),_=e.children.segmentTemplate;void 0!==_&&y.push(_);var b=x.Z.apply(void 0,[{}].concat(y));h.availabilityTimeComplete=null!==(n=b.availabilityTimeComplete)&&void 0!==n?n:t.availabilityTimeComplete,h.availabilityTimeOffset=(null!==(r=b.availabilityTimeOffset)&&void 0!==r?r:0)+t.availabilityTimeOffset,i=H.isTimelineIndexArgument(b)?new H(b,h):new W(b,h)}else{var T=t.adaptation.children;if(void 0!==T.segmentBase){var E=T.segmentBase;i=new I.Z(E,h)}else if(void 0!==T.segmentList){var S=T.segmentList;i=new C(S,h)}else i=new W({duration:Number.MAX_VALUE,timescale:1,startNumber:0,media:""},h)}return i}(e,(0,x.Z)({},n,{availabilityTimeOffset:h,availabilityTimeComplete:p,unsafelyBaseOnPreviousRepresentation:f,adaptation:t,inbandEventStreams:v})),g=void 0;null==e.attributes.bitrate?(o.Z.warn("DASH: No usable bitrate found in the Representation."),g=0):g=e.attributes.bitrate;var y={bitrate:g,cdnMetadata:j(n.baseURLs,e.children.baseURLs).map((function(e){return{baseUrl:e.url,id:e.serviceLocation}})),index:m,id:d},_=void 0;null!=e.attributes.codecs?_=e.attributes.codecs:null!=t.attributes.codecs&&(_=t.attributes.codecs),null!=_&&(_="mp4a.40.02"===_?"mp4a.40.2":_,y.codecs=_),null!=e.attributes.frameRate?y.frameRate=e.attributes.frameRate:null!=t.attributes.frameRate&&(y.frameRate=t.attributes.frameRate),null!=e.attributes.height?y.height=e.attributes.height:null!=t.attributes.height&&(y.height=t.attributes.height),null!=e.attributes.mimeType?y.mimeType=e.attributes.mimeType:null!=t.attributes.mimeType&&(y.mimeType=t.attributes.mimeType),null!=e.attributes.width?y.width=e.attributes.width:null!=t.attributes.width&&(y.width=t.attributes.width);var b=void 0!==t.children.contentProtections?t.children.contentProtections:[];if(void 0!==e.children.contentProtections&&b.push.apply(b,e.children.contentProtections),b.length>0){var T=b.reduce((function(e,t){var n;if(void 0!==t.attributes.schemeIdUri&&"urn:uuid:"===t.attributes.schemeIdUri.substring(0,9)&&(n=t.attributes.schemeIdUri.substring(9).replace(/-/g,"").toLowerCase()),void 0!==t.attributes.keyId&&t.attributes.keyId.length>0){var r={keyId:t.attributes.keyId,systemId:n};void 0===e.keyIds?e.keyIds=[r]:e.keyIds.push(r)}if(void 0!==n){for(var i,a=[],o=q(t.children.cencPssh);!(i=o()).done;){var u=i.value;a.push({systemId:n,data:u})}if(a.length>0){var l,c=(0,s.Z)(e.initData,(function(e){return"cenc"===e.type}));if(void 0===c)e.initData.push({type:"cenc",values:a});else(l=c.values).push.apply(l,a)}}return e}),{keyIds:void 0,initData:[]});(Object.keys(T.initData).length>0||void 0!==T.keyIds&&T.keyIds.length>0)&&(y.contentProtections=T)}y.hdrInfo=X({adaptationProfiles:t.attributes.profiles,manifestProfiles:n.manifestProfiles,codecs:_}),c.push(y)},f=q(e);!(l=f()).done;)d();return c}function Q(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return J(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return J(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function J(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function se(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&f.video.length>m&&!G){var H,W=f.video[m][0];z.unsafelyBaseOnPreviousAdaptation=null!==(l=null===(u=t.unsafelyBaseOnPreviousPeriod)||void 0===u?void 0:u.getAdaptation(W.id))&&void 0!==l?l:null;var q=$(R,E,z);(H=W.representations).push.apply(H,q),B=W.id}else{var Y=S.accessibilities,X=void 0;void 0!==x&&x.some((function(e){return"dub"===e.value}))&&(X=!0);var J=void 0;"text"!==N?J=!1:void 0!==Y&&(J=Y.some(te));var oe=void 0;"audio"!==N?oe=!1:void 0!==Y&&(oe=Y.some(ee));var se=void 0;"video"!==N?se=!1:void 0!==Y&&(se=Y.some(ne));for(var ue=re(E,{isAudioDescription:oe,isClosedCaption:J,isSignInterpreted:se,isTrickModeTrack:G,type:N});(0,b.Z)(h,ue);)ue+="-dup";B=ue,h.push(ue),z.unsafelyBaseOnPreviousAdaptation=null!==(d=null===(c=t.unsafelyBaseOnPreviousPeriod)||void 0===c?void 0:c.getAdaptation(ue))&&void 0!==d?d:null;var le={id:ue,representations:$(R,E,z),type:N,isTrickModeTrack:G};if(null!=E.attributes.language&&(le.language=E.attributes.language),null!=J&&(le.closedCaption=J),null!=oe&&(le.audioDescription=oe),!0===X&&(le.isDub=!0),!0===se&&(le.isSignInterpreted=!0),void 0!==I&&(le.label=I),void 0!==K)v.push({adaptation:le,trickModeAttachedAdaptationIds:K});else{for(var ce,de=-1,fe=function(){var e=ce.value,t=p[e];if(void 0!==t&&t.newID!==B&&(0,b.Z)(t.adaptationSetSwitchingIDs,L)){de=(0,_.Z)(f[N],(function(t){return t[0].id===e}));var n,r=f[N][de];void 0!==r&&r[0].audioDescription===le.audioDescription&&r[0].closedCaption===le.closedCaption&&r[0].language===le.language&&(o.Z.info('DASH Parser: merging "switchable" AdaptationSets',L,e),(n=r[0].representations).push.apply(n,le.representations),"video"===N&&Z&&!r[1].isMainAdaptation&&(m=Math.max(m,de)),r[1]={priority:Math.max(O,r[1].priority),isMainAdaptation:Z||r[1].isMainAdaptation,indexInMpd:Math.min(g,r[1].indexInMpd)})}},ve=Q(U);!(ce=ve()).done;)fe();de<0&&(f[N].push([le,{priority:O,isMainAdaptation:Z,indexInMpd:g}]),"video"===N&&Z&&(m=f.video.length-1))}}null!=L&&null==p[L]&&(p[L]={newID:B,adaptationSetSwitchingIDs:U})}}var pe=y.r.reduce((function(e,t){var n=f[t];return n.length>0&&(n.sort(ae),e[t]=n.map((function(e){return e[0]}))),e}),{});return f.video.sort(ae),w(pe,v),pe}(k.children.adaptations,z),K=(null!==(u=t.xmlNamespaces)&&void 0!==u?u:[]).concat(null!==(l=k.attributes.namespaces)&&void 0!==l?l:[]),G=function(e,t,n){for(var r,i,a,o=[],s=oe(e);!(a=s()).done;)for(var u,l=a.value,c=l.attributes,d=c.schemeIdUri,f=void 0===d?"":d,v=c.timescale,p=void 0===v?1:v,h=n.concat(null!==(r=l.attributes.namespaces)&&void 0!==r?r:[]),g=oe(l.children.events);!(u=g()).done;){var y=u.value;if(void 0!==y.eventStreamData){var _=(null!==(i=y.presentationTime)&&void 0!==i?i:0)/p+t,b=void 0===y.duration?void 0:_+y.duration/p,T=void 0;if(y.eventStreamData instanceof Element)T=y.eventStreamData;else{var E=h.reduce((function(e,t){return e+"xmlns:"+t.key+'="'+t.value+'" '}),"","application/xml").documentElement.childNodes[0]}o.push({start:_,end:b,id:y.id,data:{type:"dash-event-stream",value:{schemeIdUri:f,timescale:p,element:T}}})}}return o}(k.children.eventStreams,R,K),H={id:P,start:R,end:C,duration:M,adaptations:V,streamEvents:G};if(c.unshift(H),!E.lastPositionIsKnown()){var W=function(e){for(var t,n=null,r=!0,i=(0,h.Z)(e).filter((function(e){return null!=e})),a=oe((0,v.Z)(i,(function(e){return e})));!(t=a()).done;)for(var o,s=oe(t.value.representations);!(o=s()).done;){var u=o.value.index.getLastAvailablePosition();null!==u&&(r=!1,"number"==typeof u&&(n=null==n?u:Math.max(n,u)))}if(null!=n)return n;if(r)return null;return}(V);if(f)if("number"==typeof W){var q=performance.now()/1e3;E.setLastPosition(W,q)}else{var Y=ce(t,R);if(void 0!==Y){var X=Y[0],J=Y[1];E.setLastPosition(X,J)}}else"number"==typeof W&&E.setLastPosition(W)}},k=e.length-1;k>=0;k--)S(k);if(t.isDynamic&&!E.lastPositionIsKnown()){var x=ce(t,0);if(void 0!==x){var I=x[0],Z=x[1];E.setLastPosition(I,Z)}}return function(e){if(0===e.length)return[];for(var t=[e[0]],n=1;nr.start)&&(o.Z.warn("DASH: Updating overlapping Periods.",null==i?void 0:i.start,r.start),i.duration=r.start-i.start,i.end=r.start,!(i.duration>0));)t.pop(),i=t[t.length-1];t.push(r)}return t}(c)}function ce(e,t){if(null!=e.clockOffset){var n=e.clockOffset/1e3-e.availabilityStartTime,r=performance.now()/1e3,i=r+n;if(i>=t)return[i,r]}else{var a=Date.now()/1e3;if(a>=t)return o.Z.warn("DASH Parser: no clock synchronization mechanism found. Using the system clock instead."),[a-e.availabilityStartTime,performance.now()/1e3]}}function de(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return fe(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return fe(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function fe(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0?t[0].value:void 0}(t);if(null!=y&&y.length>0)return{type:"needs-clock",value:{url:y,continue:function(i){return i.success?(n.externalClockOffset=l(i.data),e(t,n,r,!0)):(r.push(i.error),o.Z.warn("DASH Parser: Error on fetching the clock ressource",i.error),e(t,n,r,!0))}}}}}for(var _=[],b=0;b=0&&(c=0===h.minimumUpdatePeriod?a.Z.getCurrent().DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0:h.minimumUpdatePeriod);var A=f(S),x=A.minimumSafePosition,I=A.maximumSafePosition,Z=A.maximumUnsafePosition,R=performance.now();if(m){var M,C;if(d=x,k=null!=_?_:null,void 0!==Z&&(C=Z),void 0!==I)M=I;else{var P=null!=y?y:0,D=t.externalClockOffset;if(void 0===D)o.Z.warn("DASH Parser: use system clock to define maximum position"),M=Date.now()/1e3-P;else M=(performance.now()+D)/1e3-P}void 0===C&&(C=M),v={isLinear:!0,maximumSafePosition:M,livePosition:C,time:R},null!==k&&void 0!==d&&M-d>k&&(k=M-d)}else{d=void 0!==x?x:void 0!==(null===(i=S[0])||void 0===i?void 0:i.start)?S[0].start:0;var N=null!=w?w:1/0;if(void 0!==S[S.length-1]){var O=S[S.length-1],L=null!==(s=O.end)&&void 0!==s?s:void 0!==O.duration?O.start+O.duration:void 0;void 0!==L&&L=0;o--){var s,u=_[o].index,l=a[o],f=l.parsed,v=l.warnings,p=l.receivedTime,h=l.sendingTime,m=l.url;v.length>0&&r.push.apply(r,v);for(var g,y=de(f);!(g=y()).done;){var b=g.value;c.set(b,{receivedTime:p,sendingTime:h,url:m})}(s=d.periods).splice.apply(s,[u,1].concat(f))}return e(t,n,r,i,c)}}}};function pe(e){var t=e.textContent,n=[];return null===t||0===t.length?[void 0,n]:[{value:t},n]}function he(e){for(var t={},n=0;n0){var s=Ze(a,"cenc:pssh"),u=s[0],l=s[1];null!==l&&(o.Z.warn(l.message),t.push(l)),null!==u&&n.push(u)}}}return[{cencPssh:n},t]}(e.childNodes),n=t[0],r=t[1];return[{children:n,attributes:function(e){for(var t={},n=0;n0&&(r=r.concat(d));break;case"SegmentList":var f=Le(a),v=f[0],p=f[1];r=r.concat(p),t.segmentList=v;break;case"SegmentTemplate":var h=Ue(a),m=h[0],g=h[1];r=r.concat(g),t.segmentTemplate=m;break;case"ContentProtection":var y=Pe(a),_=y[0],b=y[1];b.length>0&&(r=r.concat(b)),void 0!==_&&n.push(_)}}return n.length>0&&(t.contentProtections=n),[t,r]}(e.childNodes),n=t[0],r=t[1],i=function(e){for(var t={},n=[],r=Me(t,n),i=0;i0&&(r=r.concat(u));break;case"ContentComponent":t.contentComponent=he(a);break;case"EssentialProperty":null==t.essentialProperties?t.essentialProperties=[Re(a)]:t.essentialProperties.push(Re(a));break;case"InbandEventStream":void 0===t.inbandEventStreams&&(t.inbandEventStreams=[]),t.inbandEventStreams.push(Re(a));break;case"Label":var l=a.textContent;null!=l&&(t.label=l);break;case"Representation":var c=Fe(a),d=c[0],f=c[1];t.representations.push(d),f.length>0&&(r=r.concat(f));break;case"Role":null==t.roles?t.roles=[Re(a)]:t.roles.push(Re(a));break;case"SupplementalProperty":null==t.supplementalProperties?t.supplementalProperties=[Re(a)]:t.supplementalProperties.push(Re(a));break;case"SegmentBase":var v=Ne(a),p=v[0],h=v[1];t.segmentBase=p,h.length>0&&(r=r.concat(h));break;case"SegmentList":var m=Le(a),g=m[0],y=m[1];t.segmentList=g,y.length>0&&(r=r.concat(y));break;case"SegmentTemplate":var _=Ue(a),b=_[0],T=_[1];t.segmentTemplate=b,T.length>0&&(r=r.concat(T));break;case"ContentProtection":var E=Pe(a),S=E[0],w=E[1];w.length>0&&(r=r.concat(w)),void 0!==S&&n.push(S)}}return n.length>0&&(t.contentProtections=n),[t,r]}(e.childNodes),n=t[0],r=t[1],i=function(e){for(var t={},n=[],r=Me(t,n),i=0;i0&&(n=n.concat(c))}}return[t,n]}function Ke(e){for(var t={eventStreamData:e},n=[],r=Me(t,n),i=0;i0&&(i=i.concat(_))}}return[{baseURLs:n,adaptations:r,eventStreams:a,segmentTemplate:t},i]}(e.childNodes),n=t[0],r=t[1],i=function(e){for(var t={},n=[],r=Me(t,n),i=0;i",d=(new DOMParser).parseFromString(c,"text/xml");if(null==d||0===d.children.length)throw new Error("DASH parser: Invalid external ressources");for(var f=d.children[0].children,v=[],p=[],h=0;h0;){var r=e[0];if(r.start>=t)return n;if(-1===r.repeatCount)return n;if(0===r.repeatCount)e.shift(),n+=1;else{var i=e[1];if(void 0!==i&&i.start<=t)e.shift(),n+=1;else{if(r.duration<=0)return n;for(var a=r.start+r.duration,o=1;ar.repeatCount)){var s=r.repeatCount-o;return r.start=a,r.repeatCount=s,n+=o}e.shift(),n=r.repeatCount+1}}}return n}n.d(t,{Z:function(){return r}})},3911:function(e,t,n){"use strict";n.d(t,{KF:function(){return i},PZ:function(){return u},_j:function(){return l},gT:function(){return o},jH:function(){return a},zG:function(){return s}});var r=n(1946);function i(e,t,n){var i,a=e.repeatCount;return a>=0?a:(i=(0,r.Z)(t)?void 0!==n?n:Number.MAX_VALUE:t.start,Math.ceil((i-e.start)/e.duration)-1)}function a(e,t,n){var r=e.start,a=e.duration;return a<=0?r:r+(i(e,t,n)+1)*a}function o(e,t){var n;return e*t.timescale+(null!==(n=t.indexTimeOffset)&&void 0!==n?n:0)}function s(e,t){var n;return(e-(null!==(n=t.indexTimeOffset)&&void 0!==n?n:0))/t.timescale}function u(e,t,n){return[e*n,(e+t)*n]}function l(e,t,n){var r=e.timeline,i=o(t,e);if(i<0)return null;var u=function(e,t){for(var n=0,r=e.length;n>>1;e[i].start<=t?n=i+1:r=i}return n-1}(r,i);if(u<0||u>=r.length-1)return null;var l=r[u];if(l.duration<=0)return null;var c=r[u+1];if(void 0===c)return null;var d=c.start;return i>=a(l,c,n)&&ie.time)return!1;if(o===e.time)return void 0===a.range?void 0===e.range:null!=e.range&&a.range[0]===e.range[0]&&a.range[1]===e.range[1];if(a.repeatCount>=0&&void 0!==a.duration){var s=(o-a.start)/a.duration-1;return s%1==0&&s<=a.repeatCount}}return!1}n.d(t,{Z:function(){return r}})},5505:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(3714),i=n(3887),a=n(3911);function o(e,t){if(0===e.length)return e.push.apply(e,t),!0;if(0===t.length)return!1;var n=e.length,o=t[0].start,s=e[n-1];if((0,a.jH)(s,t[0])=0;u--){var l=e[u].start;if(l===o){var c=n-u;return e.splice.apply(e,[u,c].concat(t)),!1}if(lo)return i.Z.warn("RepresentationIndex: Manifest update removed all previous segments"),e.splice.apply(e,[0,n].concat(t)),!0;if(void 0===d.repeatCount||d.repeatCount<=0)return d.repeatCount<0&&(d.repeatCount=Math.floor((o-d.start)/d.duration)-1),e.splice.apply(e,[u+1,n-(u+1)].concat(t)),!1;if(d.start+d.duration*(d.repeatCount+1)<=o)return e.splice.apply(e,[u+1,n-(u+1)].concat(t)),!1;var f=(o-d.start)/d.duration-1;if(f%1==0&&d.duration===t[0].duration){var v=t[0].repeatCount<0?-1:t[0].repeatCount+f+1;return e.splice.apply(e,[u,n-u].concat(t)),e[u].start=d.start,e[u].repeatCount=v,!1}return i.Z.warn("RepresentationIndex: Manifest update removed previous segments"),e[u].repeatCount=Math.floor(f),e.splice.apply(e,[u+1,n-(u+1)].concat(t)),!1}}var p=e[e.length-1],h=t[t.length-1];return void 0!==p.repeatCount&&p.repeatCount<0?p.start>h.start?(i.Z.warn("RepresentationIndex: The new index is older than the previous one"),!1):(i.Z.warn('RepresentationIndex: The new index is "bigger" than the previous one'),e.splice.apply(e,[0,n].concat(t)),!0):p.start+p.duration*(p.repeatCount+1)>=h.start+h.duration*(h.repeatCount+1)?(i.Z.warn("RepresentationIndex: The new index is older than the previous one"),!1):(i.Z.warn('RepresentationIndex: The new index is "bigger" than the previous one'),e.splice.apply(e,[0,n].concat(t)),!0)}},5734:function(e,t,n){"use strict";var r=n(6923),i=/&#([0-9]+);/g,a=/
    /gi,o=/]*>([\s\S]*?)<\/style[^>]*>/i,s=/\s*

    ]+))?>(.*)/i,u=/]+?start="?([0-9]*)"?[^0-9]/i;function l(e,t){var n=new RegExp("\\s*"+t+":\\s*(\\S+);","i").exec(e);return Array.isArray(n)?n[1]:null}t.Z=function(e,t,n){var c,d,f=/]/gi,v=/]|<\/body>/gi,p=[],h=o.exec(e),m=Array.isArray(h)?h[1]:"";v.exec(e);var g,y=function(e){for(var t=/\.(\S+)\s*{([^}]*)}/gi,n={},r=t.exec(e);null!==r;){var i=r[1],a=l(r[2],"lang");null!=i&&null!=a&&(n[a]=i),r=t.exec(e)}return n}(m),_=function(e){var t=/p\s*{([^}]*)}/gi.exec(e);return null===t?"":t[1]}(m);if((0,r.Z)(n)&&void 0===(g=y[n]))throw new Error("sami: could not find lang "+n+" in CSS");for(;c=f.exec(e),d=v.exec(e),null!==c||null!==d;){if(null===c||null===d||c.index>=d.index)throw new Error("parse error");var b=e.slice(c.index,d.index),T=u.exec(b);if(!Array.isArray(T))throw new Error("parse error (sync time attribute)");var E=+T[1];if(isNaN(E))throw new Error("parse error (sync time attribute NaN)");S(b.split("\n"),E/1e3)}return p;function S(e,n){for(var o=e.length;--o>=0;){var u=s.exec(e[o]);if(Array.isArray(u)){var l=u[1],c=u[2];if(g===l)if(" "===c)p[p.length-1].end=n;else{var d=document.createElement("DIV");d.className="rxp-texttrack-region";var f=document.createElement("DIV");f.className="rxp-texttrack-div",f.style.position="absolute",f.style.bottom="0",f.style.width="100%",f.style.color="#fff",f.style.textShadow="-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000";var v=document.createElement("div");v.className="rxp-texttrack-p",(0,r.Z)(_)&&(v.style.cssText=_);for(var h=c.split(a),m=0;m/gi,s=/]*>([\s\S]*?)<\/style[^>]*>/i,u=/\s*

    ]+))?>(.*)/i,l=/]+?start="?([0-9]*)"?[^0-9]/i;function c(e,t){var n=new RegExp("\\s*"+t+":\\s*(\\S+);","i").exec(e);return Array.isArray(n)?n[1]:null}t.Z=function(e,t,n){var d,f,v=/]/gi,p=/]|<\/body>/gi,h=[],m=s.exec(e),g=null!==m?m[1]:"";p.exec(e);var y,_=function(e){for(var t=/\.(\S+)\s*{([^}]*)}/gi,n={},r=t.exec(e);Array.isArray(r);){var i=r[1],a=c(r[2],"lang");null!=i&&null!=a&&(n[a]=i),r=t.exec(e)}return n}(g);if((0,i.Z)(n)&&void 0===(y=_[n]))throw new Error("sami: could not find lang "+n+" in CSS");for(;d=v.exec(e),f=p.exec(e),null!==d||null!==f;){if(null===d||null===f||d.index>=f.index)throw new Error("parse error");var b=e.slice(d.index,f.index),T=l.exec(b);if(null===T)throw new Error("parse error (sync time attribute)");var E=+T[1];if(isNaN(E))throw new Error("parse error (sync time attribute NaN)");S(b.split("\n"),E/1e3)}return function(e){for(var t=[],n=0;n=0;)if(null!==(r=u.exec(e[s]))){var l=r,c=l[1],d=l[2];y===c&&(" "===d?h[h.length-1].end=n:h.push({text:(i=d,i.replace(o,"\n").replace(a,(function(e,t){return String.fromCharCode(Number(t))}))),start:n+t}))}}}},2061:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(6923);function i(e,t){for(var n=t+1;(0,r.Z)(e[n]);)n++;return n}function a(e){for(var t=[],n=0;n0&&(1===o.length?o[0].indexOf("--\x3e")>=0&&t.push(o):(o[1].indexOf("--\x3e")>=0||o[0].indexOf("--\x3e")>=0)&&t.push(o)),n=a}return t}},8675:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(2061),i=n(788);function a(e,t){for(var n=e.split(/\r\n|\n|\r/),a=(0,r.Z)(n),s=[],u=0;u0){var l=document.createTextNode(o[s]);r.appendChild(l)}}else if("B"===a.nodeName){var c=e(a);c.style.fontWeight="bold",r.appendChild(c)}else if("I"===a.nodeName){var d=e(a);d.style.fontStyle="italic",r.appendChild(d)}else if("U"===a.nodeName){var f=e(a);f.style.textDecoration="underline",r.appendChild(f)}else if(u(a)&&"string"==typeof a.color){var v=e(a);v.style.color=a.color,r.appendChild(v)}else{var p=e(a);r.appendChild(p)}}return r}(t)}function u(e){return"FONT"===e.nodeName&&"color"in e}},8057:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(7253),i=n(2061),a=n(788);function o(e,t){for(var n,o,s,u,l,c=e.split(/\r\n|\n|\r/),d=(0,i.Z)(c),f=[],v=0;v0){var _=p.getAttribute("xml:space"),b=(0,l.Z)(_)?"default"===_:o,E=(0,c.Z)({},i,(0,d.U)(g,[p],n,t));u.push.apply(u,e(p,E,[p].concat(a),b))}}return u}(e,(0,c.Z)({},r),[],i)}(e,n,r,i,s),E=0;E|\u2265/g,">").replace(/\u200E/g,"‎").replace(/\u200F/g,"‏").replace(/\u00A0/g," ")}else if((0,l.OE)(s))i+="\n";else if((0,l.jg)(s)&&s.nodeType===Node.ELEMENT_NODE&&s.childNodes.length>0){var d=s.getAttribute("xml:space");i+=n(s,(0,o.Z)(d)?"default"===d:t)}}return i}return n(e,t)}(t,v),y=(0,i.Z)(h+n,m+n,g);return null===y?null:((0,a.Z)(y)&&function(e,t){var n=t.extent;if((0,o.Z)(n)){var r=u._0.exec(n);null!=r&&(e.size=Number(r[1]))}switch(t.writingMode){case"tb":case"tblr":e.vertical="lr";break;case"tbrl":e.vertical="rl"}var i=t.origin;if((0,o.Z)(i))u._0.exec(i);var a=t.align;if((0,o.Z)(a)){e.align=a,"center"===a&&("center"!==e.align&&(e.align="middle"),e.position="auto");var s=d[a];e.positionAlign=void 0===s?"":s;var l=c[a];e.lineAlign=void 0===l?"":l}}(y,r),y)}var v=function(e,t){for(var n=(0,r.Z)(e,t),i=[],a=0;a0&&(t=n)}return t}function a(e){var t=e.getElementsByTagName("body");if(t.length>0)return t[0];var n=e.getElementsByTagName("tt:body");return n.length>0?n[0]:null}function o(e){var t=e.getElementsByTagName("style");if(t.length>0)return t;var n=e.getElementsByTagName("tt:style");return n.length>0?n:t}function s(e){var t=e.getElementsByTagName("region");if(t.length>0)return t;var n=e.getElementsByTagName("tt:region");return n.length>0?n:t}function u(e){var t=e.getElementsByTagName("p");if(t.length>0)return t;var n=e.getElementsByTagName("tt:p");return n.length>0?n:t}function l(e){return"br"===e.nodeName||"tt:br"===e.nodeName}function c(e){return"span"===e.nodeName||"tt:span"===e.nodeName}n.d(t,{DM:function(){return s},H:function(){return a},OE:function(){return l},jF:function(){return i},jg:function(){return c},kd:function(){return u},vU:function(){return o}})},1138:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(6923),i=n(360);function a(e,t){for(var n=[],a=t;a=2)for(var r=1;r0&&n.appendChild(document.createElement("br")),o[s].length>0){var u=document.createTextNode(o[s]);n.appendChild(u)}}else{var d=e.nodeName.toLowerCase().split("."),f=[];if(d.forEach((function(e){(0,i.Z)(t[e])&&f.push(t[e])})),0!==f.length){var v=document.createAttribute("style");f.forEach((function(e){v.value+=e}));var p=(0,l.Z)(r,a)?a:"span";(n=document.createElement(p)).setAttributeNode(v)}else{var h=(0,l.Z)(r,a)?a:"span";n=document.createElement(h)}for(var m=0;m/,"").replace(/<([u,i,b,c])(\..*?)?(?: .*?)?>(.*?)<\/\1>/g,"<$1$2>$3"),r=(new DOMParser).parseFromString(n,"text/html").body.childNodes,i=[],a=0;a=2){var a=parseInt(i[1],10);isNaN(a)||(t.position=a,void 0!==i[2]&&(t.positionAlign=i[2]))}}(0,u.Z)(e.size)&&(t.size=e.size),"string"==typeof e.align&&(0,s.Z)(["start","center","end","left"],e.align)&&(t.align=e.align)}var c=n(7253);var d=function(e,t){var n=e.split(/\r\n|\n|\r/);if(!/^WEBVTT($| |\t)/.test(n[0]))throw new Error("Can't parse WebVTT: Invalid file.");for(var s,u,d,f,v=(0,o.yE)(n),p=(0,i.Z)(n,v),h=[],m=0;m/;if(o.test(e[0]))n=e[0],r=e.slice(1,e.length);else{if(!o.test(e[1]))return null;a=e[0],n=e[1],r=e.slice(2,e.length)}var s=function(e){var t=/^([\d:.]+)[ |\t]+-->[ |\t]+([\d:.]+)[ |\t]*(.*)$/.exec(e);if(null===t)return null;var n=i(t[1]),r=i(t[2]);return null==n||null==r?null:{start:n,end:r,settings:t[3].split(/ |\t/).reduce((function(e,t){var n=t.split(":");return 2===n.length&&(e[n[0]]=n[1]),e}),{})}}(n);return null===s?null:{start:s.start+t,end:s.end+t,settings:s.settings,payload:r,header:a}}},360:function(e,t,n){"use strict";n.d(t,{$4:function(){return s},JF:function(){return a},tq:function(){return o},yE:function(){return i}});var r=n(6923);function i(e){for(var t=0;t=0)return!0;var r=e[t+1];return void 0!==r&&r.indexOf("--\x3e")>=0}function s(e,t){for(var n=t+1;(0,r.Z)(e[n]);)n++;return n}},85:function(e,t,n){"use strict";n.d(t,{Z:function(){return ae}});var r=n(7874),i=n(8791),a=n(5861),o=n(4687),s=n.n(o),u=n(4597),l=n(5278),c=n(9829);function d(e,t){return null===e?null:null===t.url?e.baseUrl:(0,c.Z)(e.baseUrl,t.url)}function f(e,t,n,r,i){return v.apply(this,arguments)}function v(){return(v=(0,a.Z)(s().mark((function e(t,n,r,i,a){var o,l,c;return s().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(o=n.segment,l=d(t,o),!o.isInit&&null!==l){e.next=4;break}return e.abrupt("return",{resultType:"segment-created",resultData:null});case 4:return e.next=6,(0,u.ZP)({url:l,responseType:"arraybuffer",timeout:r.timeout,onProgress:a.onProgress,cancelSignal:i});case 6:return c=e.sent,e.abrupt("return",{resultType:"segment-loaded",resultData:c});case 8:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function p(e,t){var n=t.segment,i=t.period,a=e.data,o=e.isChunked;if(t.segment.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if(o)throw new Error("Image data should not be downloaded in chunks");var s=(0,l.Z)(n.timestampOffset,0);return null===a||null===r.Z.imageParser?{segmentType:"media",chunkData:null,chunkSize:0,chunkInfos:{duration:n.duration,time:n.time},chunkOffset:s,protectionDataUpdate:!1,appendWindow:[i.start,i.end]}:{segmentType:"media",chunkData:{data:r.Z.imageParser(new Uint8Array(a)).thumbs,start:0,end:Number.MAX_VALUE,timescale:1,type:"bif"},chunkSize:void 0,chunkInfos:{time:0,duration:Number.MAX_VALUE},chunkOffset:s,protectionDataUpdate:!1,appendWindow:[i.start,i.end]}}var h=n(6872),m=n(8750),g=n(3887),y=n(1989),_=n(8026),b=n(3635);function T(e){var t=e.aggressiveMode,n=e.referenceDateTime,i=void 0!==e.serverSyncInfos?e.serverSyncInfos.serverTimestamp-e.serverSyncInfos.clientTime:void 0;return function(a,o,s,l,c){var d,f=a.responseData,v=o.externalClockOffset,p=null!==(d=a.url)&&void 0!==d?d:o.originalUrl,T=null!=i?i:v,E={aggressiveMode:!0===t,unsafelyBaseOnPreviousManifest:o.unsafeMode?o.previousManifest:null,url:p,referenceDateTime:n,externalClockOffset:T},S=r.Z.dashParsers;if(null===S.wasm||"uninitialized"===S.wasm.status||"failure"===S.wasm.status)return g.Z.debug("DASH: WASM MPD Parser not initialized. Running JS one."),k();var w=function(e){if(e instanceof ArrayBuffer)return e;if("string"==typeof e)return(0,b.tG)(e).buffer;if(e instanceof Document)return(0,b.tG)(e.documentElement.innerHTML).buffer;throw new Error("DASH Manifest Parser: Unrecognized Manifest format")}(f);return function(e){var t=new DataView(e);if(61371===t.getUint16(0)&&191===t.getUint8(2))return!0;if(65279===t.getUint16(0)||65534===t.getUint16(0))return!1;return!0}(w)?"initialized"===S.wasm.status?(g.Z.debug("DASH: Running WASM MPD Parser."),A(S.wasm.runWasmParser(w,E))):(g.Z.debug("DASH: Awaiting WASM initialization before parsing the MPD."),S.wasm.waitForInitialization().catch((function(){})).then((function(){return null===S.wasm||"initialized"!==S.wasm.status?(g.Z.warn("DASH: WASM MPD parser initialization failed. Running JS parser instead"),k()):(g.Z.debug("DASH: Running WASM MPD Parser."),A(S.wasm.runWasmParser(w,E)))}))):(g.Z.info("DASH: MPD doesn't seem to be UTF-8-encoded. Running JS parser instead of the WASM one."),k());function k(){if(null===S.js)throw new Error("No MPD parser is imported");var e=function(e){if(e instanceof ArrayBuffer)return(new DOMParser).parseFromString((0,b.uR)(new Uint8Array(e)),"text/xml");if("string"==typeof e)return(new DOMParser).parseFromString(e,"text/xml");if(e instanceof Document)return e;throw new Error("DASH Manifest Parser: Unrecognized Manifest format")}(f);return A(S.js(e,E))}function A(t){if("done"===t.type)return t.value.warnings.length>0&&s(t.value.warnings),l.isCancelled?Promise.reject(l.cancellationError):{manifest:new y.ZP(t.value.parsed,e),url:p};var n=t.value,r=n.urls.map((function(e){return c((function(){var t=h.Z.getCurrent().DEFAULT_REQUEST_TIMEOUT;return"string"===n.format?(0,u.ZP)({url:e,responseType:"text",timeout:t,cancelSignal:l}):(0,u.ZP)({url:e,responseType:"arraybuffer",timeout:t,cancelSignal:l})})).then((function(e){if("string"===n.format){if("string"!=typeof e.responseData)throw new Error("External DASH resources should have been a string");return(0,_.Z)(e,{responseData:{success:!0,data:e.responseData}})}if(!(e.responseData instanceof ArrayBuffer))throw new Error("External DASH resources should have been ArrayBuffers");return(0,_.Z)(e,{responseData:{success:!0,data:e.responseData}})}),(function(e){var t=(0,m.Z)(e,{defaultCode:"PIPELINE_PARSE_ERROR",defaultReason:"An unknown error occured when parsing ressources."});return(0,_.Z)({},{size:void 0,requestDuration:void 0,responseData:{success:!1,error:t}})}))}));return Promise.all(r).then((function(e){return n.format,A(n.continue(e))}))}}}var E=n(7839),S=n(9105),w=n(5992),k=n(1946),A="function"==typeof Headers?Headers:null,x="function"==typeof AbortController?AbortController:null;function I(){return"function"==typeof window.fetch&&!(0,k.Z)(x)&&!(0,k.Z)(A)}var Z=n(8806),R=n(281);function M(e,t){return"audio"===e||"video"===e?"video/mp4"===t.mimeType||"audio/mp4"===t.mimeType?"mp4":"video/webm"===t.mimeType||"audio/webm"===t.mimeType?"webm":void 0:"text"===e&&"application/mp4"===t.mimeType?"mp4":void 0}var C=n(288),P=n(4460);function D(e){return function(t,n,r,i,a){return new Promise((function(s,u){var l=new C.ZP({cancelOn:i}),c=l.signal.register(u);e(t,n,r,l.signal,Object.assign(Object.assign({},a),{onNewChunk:function(e){try{o(e),a.onNewChunk(e)}catch(e){c(),l.cancel(),u(e)}}})).then((function(e){if(!l.isUsed){if(c(),"segment-loaded"===e.resultType)try{o(e.resultData.responseData)}catch(e){return void u(e)}s(e)}}),(function(e){c(),u(e)}))}));function o(e){(e instanceof ArrayBuffer||e instanceof Uint8Array)&&"mp4"===M(n.adaptation.type,n.representation)&&(0,P.Z)(new Uint8Array(e),n.segment.isInit)}}}var N=n(6968);function O(e,t,n,r,i){if(void 0===t.range)return(0,u.ZP)({url:e,responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}));if(void 0===t.indexRange)return(0,u.ZP)({url:e,headers:{Range:(0,R.Z)(t.range)},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}));if(t.range[1]+1===t.indexRange[0])return(0,u.ZP)({url:e,headers:{Range:(0,R.Z)([t.range[0],t.indexRange[1]])},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}));var a=(0,u.ZP)({url:e,headers:{Range:(0,R.Z)(t.range)},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}),o=(0,u.ZP)({url:e,headers:{Range:(0,R.Z)(t.indexRange)},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress});return Promise.all([a,o]).then((function(t){var n=t[0],r=t[1],i=(0,N.zo)(new Uint8Array(n.responseData),new Uint8Array(r.responseData)),a=Math.min(n.sendingTime,r.sendingTime),o=Math.max(n.receivedTime,r.receivedTime);return{resultType:"segment-loaded",resultData:{url:e,responseData:i,size:n.size+r.size,requestDuration:o-a,sendingTime:a,receivedTime:o}}}))}var L=n(8766);function B(e,t,n,r,i){var o=t.segment,u=void 0!==o.range?{Range:(0,R.Z)(o.range)}:void 0,l=null;return function(e){var t;if(!(0,k.Z)(e.headers))if((0,k.Z)(A))t=e.headers;else{t=new A;for(var n=Object.keys(e.headers),r=0;r=300)throw g.Z.warn("Fetch: Request HTTP Error",t.status,t.url),new S.Z(t.url,t.status,w.br.ERROR_HTTP_CODE);if((0,k.Z)(t.body))throw new S.Z(t.url,t.status,w.br.PARSE_ERROR);var n=t.headers.get("Content-Length"),r=(0,k.Z)(n)||isNaN(+n)?void 0:+n,i=t.body.getReader(),u=0;return l();function l(){return d.apply(this,arguments)}function d(){return(d=(0,a.Z)(s().mark((function n(){var a,o,d,f,p;return s().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.next=2,i.read();case 2:if((a=n.sent).done||(0,k.Z)(a.value)){n.next=11;break}return u+=a.value.byteLength,o=performance.now(),d={url:t.url,currentTime:o,duration:o-c,sendingTime:c,chunkSize:a.value.byteLength,chunk:a.value.buffer,size:u,totalSize:r},e.onData(d),n.abrupt("return",l());case 11:if(!a.done){n.next=16;break}return v(),f=performance.now(),p=f-c,n.abrupt("return",{requestDuration:p,receivedTime:f,sendingTime:c,size:u,status:t.status,url:t.url});case 16:return n.abrupt("return",l());case 17:case"end":return n.stop()}}),n)})))).apply(this,arguments)}})).catch((function(t){if(null!==u)throw u;if(v(),l)throw g.Z.warn("Fetch: Request timeouted."),new S.Z(e.url,0,w.br.TIMEOUT);if(t instanceof S.Z)throw t;throw g.Z.warn("Fetch: Request Error",t instanceof Error?t.toString():""),new S.Z(e.url,0,w.br.ERROR_EVENT)}))}({url:e,headers:u,onData:function(e){var t=new Uint8Array(e.chunk),n=function(e){for(var t=0,n=[];te.length)return[n,r];var o=(0,L.Z)(r,1835295092);if(o<0)return[n,r];var s=t+o+(0,N.pX)(e,o+t);if(s>e.length)return[n,r];var u=Math.max(a,s),l=e.subarray(t,u);n.push(l),t=u}return[n,null]}(null!==l?(0,N.zo)(l,t):t),a=n[0];l=n[1];for(var o=0;o0)for(var p=0;p=Math.pow(2,8-n))return n}function q(e,t){var n=j(e,t);if(null==n)return g.Z.warn("webm: unrepresentable length"),null;if(t+n>e.length)return g.Z.warn("webm: impossible length"),null;for(var r=0,i=0;ie.length)return g.Z.warn("webm: impossible length"),null;for(var r=(e[t]&(1<<8-n)-1)*Math.pow(2,8*(n-1)),i=1;i=i)return!0}return!1}(r,t);return{inbandEvents:a,needsManifestRefresh:o}}}function ee(e){var t=e.__priv_patchLastSegmentInSidx;return function(e,n,r){var i,a=n.period,o=n.adaptation,s=n.representation,u=n.segment,c=n.manifest,d=e.data,f=e.isChunked,v=[a.start,a.end];if(null===d)return u.isInit?{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0}:{segmentType:"media",chunkData:null,chunkSize:0,chunkInfos:null,chunkOffset:0,protectionDataUpdate:!1,appendWindow:v};var p=d instanceof Uint8Array?d:new Uint8Array(d),h=M(o.type,s),m="mp4"===h||void 0===h,g=!1;if(m){var y,_=(0,F.Z)(p);u.isInit&&(y=null!==(i=(0,z.R0)(p))&&void 0!==i?i:void 0),(_.length>0||void 0!==y)&&(g=s._addProtectionData("cenc",y,_))}if(!u.isInit){var b=m?Q(p,f,u,r):null,T=(0,l.Z)(u.timestampOffset,0);if(m){var E=(0,z.s9)(p);if(void 0!==E){var S=J(E.filter((function(e){return void 0!==u.privateInfos&&void 0!==u.privateInfos.isEMSGWhitelisted&&u.privateInfos.isEMSGWhitelisted(e)})),c.publishTime);if(void 0!==S){var w=S.needsManifestRefresh,A=S.inbandEvents;return{segmentType:"media",chunkData:p,chunkSize:p.length,chunkInfos:b,chunkOffset:T,appendWindow:v,inbandEvents:A,protectionDataUpdate:g,needsManifestRefresh:w}}}}return{segmentType:"media",chunkData:p,chunkSize:p.length,chunkInfos:b,chunkOffset:T,protectionDataUpdate:g,appendWindow:v}}var x=u.indexRange,I=null;if("webm"===h)I=function(e,t){var n=G(V,[],e,[t,e.length]);if(null==n)return null;var r=n[0],i=n[1],a=H(e,r);if(null==a)return null;var o=W(e,r);if(null==o)return null;var s=G(475249515,[],e,[r,i]);if(null==s)return null;for(var u=[],l=s[0];l0)){var Z=I[I.length-1];Array.isArray(Z.range)&&(Z.range[1]=1/0)}s.index instanceof $.Z&&null!==I&&I.length>0&&s.index.initializeIndex(I);var R=m?(0,z.LD)(p):"webm"===h?H(p,0):void 0,C=(0,k.Z)(R)?void 0:R;return{segmentType:"init",initializationData:p,initializationDataSize:p.length,protectionDataUpdate:g,initTimescale:C}}}var te=n(6807);function ne(e,t,n,r){var i,a,o=e.segment,s=e.adaptation,u=e.representation;if(o.isInit)return null;null===n?r?(i=o.time,a=o.end):g.Z.warn("Transport: Unavailable time data for current text track."):(i=n.time,void 0!==n.duration?a=i+n.duration:!r&&o.complete&&(a=i+o.duration));var l=function(e){var t=e.codec;if(void 0===t)throw new Error("Cannot parse subtitles: unknown format");switch(t.toLowerCase()){case"stpp":case"stpp.ttml.im1t":return"ttml";case"wvtt":return"vtt"}throw new Error('The codec used for the subtitles "'+t+'" is not managed yet.')}(u),c=function(e){var t=(0,te.Le)(e);return null===t?"":(0,b.uR)(t)}(t);return{data:c,type:l,language:s.language,start:i,end:a}}function re(e,t,n){var r,i,a=e.segment,o=e.adaptation,s=e.representation;if(a.isInit)return null;n?g.Z.warn("Transport: Unavailable time data for current text track."):(r=a.time,a.complete&&(i=a.time+a.duration));var u=function(e){var t=e.mimeType,n=void 0===t?"":t;switch(e.mimeType){case"application/ttml+xml":return"ttml";case"application/x-sami":case"application/smil":return"sami";case"text/vtt":return"vtt"}var r=e.codec;if("srt"===(void 0===r?"":r).toLowerCase())return"srt";throw new Error("could not find a text-track parser for the type "+n)}(s);return{data:t,type:u,language:o.language,start:r,end:i}}function ie(e){var t=e.__priv_patchLastSegmentInSidx;return function(e,n,r){var i,a=n.period,o=n.adaptation,s=n.representation,u=n.segment,c=e.data,d=e.isChunked;if(null===c)return u.isInit?{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0}:{segmentType:"media",chunkData:null,chunkSize:0,chunkInfos:null,chunkOffset:null!==(i=u.timestampOffset)&&void 0!==i?i:0,protectionDataUpdate:!1,appendWindow:[a.start,a.end]};var f=M(o.type,s);if("webm"===f)throw new Error("Text tracks with a WEBM container are not yet handled.");return"mp4"===f?function(e,t,n,r,i){var a=n.period,o=n.representation,s=n.segment,u=s.isInit,c=s.indexRange,d="string"==typeof e?(0,b.tG)(e):e instanceof Uint8Array?e:new Uint8Array(e);if(u){var f=(0,z.Wf)(d,Array.isArray(c)?c[0]:0);if(!0===i&&null!==f&&f.length>0){var v=f[f.length-1];Array.isArray(v.range)&&(v.range[1]=1/0)}var p=(0,z.LD)(d);return o.index instanceof $.Z&&null!==f&&f.length>0&&o.index.initializeIndex(f),{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:p}}var h=Q(d,t,s,r),m=ne(n,d,h,t),g=(0,l.Z)(s.timestampOffset,0);return{segmentType:"media",chunkData:m,chunkSize:d.length,chunkInfos:h,chunkOffset:g,protectionDataUpdate:!1,appendWindow:[a.start,a.end]}}(c,d,n,r,t):function(e,t,n){var r,i,a=n.period,o=n.segment,s=o.timestampOffset,u=void 0===s?0:s;if(o.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if("string"!=typeof e){var l=e instanceof Uint8Array?e:new Uint8Array(e);r=(0,b.uR)(l),i=l.length}else r=e;return{segmentType:"media",chunkData:re(n,r,t),chunkSize:i,chunkInfos:null,chunkOffset:u,protectionDataUpdate:!1,appendWindow:[a.start,a.end]}}(c,d,n)}}var ae=function(e){var t=(0,i.Z)({customManifestLoader:e.manifestLoader},null===r.Z.dashParsers.wasm||"initialized"!==r.Z.dashParsers.wasm.status&&"initializing"!==r.Z.dashParsers.wasm.status?"arraybuffer":"text"),n=T(e),a=function(e){var t=e.lowLatencyMode,n=e.segmentLoader;return!0!==e.checkMediaSegmentIntegrity?r:D(r);function r(e,r,i,a,o){var s=d(e,r.segment);if(null==s)return Promise.resolve({resultType:"segment-created",resultData:null});if(t||void 0===n)return U(s,r,t,i,o,a);var u={adaptation:r.adaptation,manifest:r.manifest,period:r.period,representation:r.representation,segment:r.segment,transport:"dash",timeout:i.timeout,url:s};return new Promise((function(e,l){var c=!1,d=n(u,{reject:function(e){var t,n,r;if(!c&&!a.isCancelled){c=!0,a.deregister(f);var i=e,o=null!==(t=null==i?void 0:i.message)&&void 0!==t?t:"Unknown error when fetching a DASH segment through a custom segmentLoader.",s=new E.Z(o,null!==(n=null==i?void 0:i.canRetry)&&void 0!==n&&n,null!==(r=null==i?void 0:i.isOfflineError)&&void 0!==r&&r,null==i?void 0:i.xhr);l(s)}},resolve:function(t){c||a.isCancelled||(c=!0,a.deregister(f),e({resultType:"segment-loaded",resultData:{responseData:t.data,size:t.size,requestDuration:t.duration}}))},progress:function(e){c||a.isCancelled||o.onProgress({duration:e.duration,size:e.size,totalSize:e.totalSize})},fallback:function(){c||a.isCancelled||(c=!0,a.deregister(f),U(s,r,t,i,o,a).then(e,l))}});function f(e){c||(c=!0,"function"==typeof d&&d(),l(e))}a.register(f)}))}}(e),o=ee(e),s=function(e){var t=e.lowLatencyMode;return!0!==e.checkMediaSegmentIntegrity?n:D(n);function n(e,n,r,i,a){var o=n.adaptation,s=n.representation,l=n.segment,c=l.range,f=d(e,l);if(null===f)return Promise.resolve({resultType:"segment-created",resultData:null});if(l.isInit)return O(f,l,r,i,a);var v=M(o.type,s),p="mp4"===v||void 0===v;if(t&&p){if(I())return B(f,n,r,a,i);(0,Z.Z)("DASH: Your browser does not have the fetch API. You will have a higher chance of rebuffering when playing close to the live edge")}return p?(0,u.ZP)({url:f,responseType:"arraybuffer",headers:Array.isArray(c)?{Range:(0,R.Z)(c)}:null,timeout:r.timeout,onProgress:a.onProgress,cancelSignal:i}).then((function(e){return{resultType:"segment-loaded",resultData:e}})):(0,u.ZP)({url:f,responseType:"text",headers:Array.isArray(c)?{Range:(0,R.Z)(c)}:null,timeout:r.timeout,onProgress:a.onProgress,cancelSignal:i}).then((function(e){return{resultType:"segment-loaded",resultData:e}}))}}(e);return{manifest:{loadManifest:t,parseManifest:n},audio:{loadSegment:a,parseSegment:o},video:{loadSegment:a,parseSegment:o},text:{loadSegment:s,parseSegment:ie(e)},image:{loadSegment:f,parseSegment:p}}}},2339:function(e,t,n){"use strict";n.d(t,{Z:function(){return ye}});var r=n(5861),i=n(4687),a=n.n(i),o=n(7874),s=n(3887),u=n(1989),l=n(6807),c=n(9362),d=n(811),f=n(8232),v=n(3911),p=n(1091),h=n(5505);function m(e,t,n){var r=e.timeline,i=e.timescale,a=r[r.length-1],o=t.timescale===i?{time:t.time,duration:t.duration}:{time:t.time/t.timescale*i,duration:t.duration/t.timescale*i};return!(n.time===o.time)&&(o.time>=(0,v.jH)(a,null)&&(a.duration===o.duration?a.repeatCount++:e.timeline.push({duration:o.duration,start:o.time,repeatCount:0}),!0))}function g(e,t){return e.replace(/\{start time\}/g,String(t))}function y(e,t,n){var r=t-e;return r>0?Math.floor(r/n):0}function _(e,t){var n=e.repeatCount;if(null!=e.duration&&n<0){var r=void 0!==t?t.start:1/0;n=Math.ceil((r-e.start)/e.duration)-1}return n}var b=function(){function e(e,t){var n=t.aggressiveMode,r=t.isLive,i=t.segmentPrivateInfos,a=t.timeShiftBufferDepth,o=null==t.manifestReceivedTime?performance.now():t.manifestReceivedTime;if(this._index=e,this._indexValidityTime=o,this._timeShiftBufferDepth=a,this._initSegmentInfos={bitsPerSample:i.bitsPerSample,channels:i.channels,codecPrivateData:i.codecPrivateData,packetSize:i.packetSize,samplingRate:i.samplingRate,timescale:e.timescale,protection:i.protection},this._isAggressiveMode=n,this._isLive=r,0!==e.timeline.length){var s=e.timeline[e.timeline.length-1],u=(0,v.jH)(s,null);if(this._initialScaledLastPosition=u,r){var l=o/1e3*e.timescale;this._scaledLiveGap=l-u}}}var t=e.prototype;return t.getInitSegment=function(){return{id:"init",isInit:!0,privateInfos:{smoothInitSegment:this._initSegmentInfos},url:null,time:0,end:0,duration:0,timescale:1,complete:!0}},t.getSegments=function(e,t){this._refreshTimeline();for(var n,r=function(e,t,n){var r=void 0===e.timescale||0===e.timescale?1:e.timescale;return{up:t*r,to:(t+n)*r}}(this._index,e,t),i=r.up,a=r.to,o=this._index,s=o.timeline,u=o.timescale,l=o.media,c=this._isAggressiveMode,d=[],f=s.length,v=null==this._scaledLiveGap?void 0:performance.now()/1e3*u-this._scaledLiveGap,p=0;p=a)return d;null!=n&&(n+=T+1)}return d},t.shouldRefresh=function(e,t){if(this._refreshTimeline(),!this._isLive)return!1;var n=this._index,r=n.timeline,i=n.timescale,a=r[r.length-1];if(void 0===a)return!1;var o=a.repeatCount,s=a.start+(o+1)*a.duration;return!(t*i=s||e*i>a.start+o*a.duration)},t.getFirstAvailablePosition=function(){this._refreshTimeline();var e=this._index;return 0===e.timeline.length?null:e.timeline[0].start/e.timescale},t.getLastAvailablePosition=function(){this._refreshTimeline();var e=this._index;if(null==this._scaledLiveGap){var t=e.timeline[e.timeline.length-1];return(0,v.jH)(t,null)/e.timescale}for(var n=e.timeline.length-1;n>=0;n--)for(var r=e.timeline[n],i=performance.now()/1e3*e.timescale,a=r.start,o=r.duration,s=r.repeatCount;s>=0;s--){var u=a+o*(s+1);if((this._isAggressiveMode?u-o:u)<=i-this._scaledLiveGap)return u/e.timescale}},t.getEnd=function(){if(!this._isLive)return this.getLastAvailablePosition()},t.awaitSegmentBetween=function(e,t){var n;if((0,d.Z)(e<=t),this.isFinished())return!1;var r=this.getLastAvailablePosition();return!(void 0!==r&&t(null!==(n=this.getFirstAvailablePosition())&&void 0!==n?n:0)&&void 0)},t.checkDiscontinuity=function(e){return this._refreshTimeline(),(0,v._j)(this._index,e,void 0)},t.isSegmentStillAvailable=function(e){if(e.isInit)return!0;this._refreshTimeline();var t=this._index,n=t.timeline,r=t.timescale;return(0,p.Z)(e,n,r,0)},t.canBeOutOfSyncError=function(e){return!!this._isLive&&(e instanceof c.Z&&(e.isHttpError(404)||e.isHttpError(412)))},t._replace=function(e){var t=this._index.timeline,n=e._index.timeline,r=this._index.timescale,i=e._index.timescale;if(this._index=e._index,this._initialScaledLastPosition=e._initialScaledLastPosition,this._indexValidityTime=e._indexValidityTime,this._scaledLiveGap=e._scaledLiveGap,0!==t.length&&0!==n.length&&r===i){var a=t[t.length-1],o=n[n.length-1],u=(0,v.jH)(o,null);if(!((0,v.jH)(a,null)<=u))for(var l=0;lu){if(c.duration!==o.duration)return;var f=u-c.start;if(0===f)return s.Z.warn("Smooth Parser: a discontinuity detected in the previous manifest has been resolved."),void(this._index.timeline=this._index.timeline.concat(t.slice(l)));if(f<0||f%c.duration!=0)return;var p=f/c.duration-1,h=c.repeatCount-p;if(h<0)return;o.repeatCount+=h;var m=t.slice(l+1);return void(this._index.timeline=this._index.timeline.concat(m))}}}},t._update=function(e){(0,h.Z)(this._index.timeline,e._index.timeline),this._initialScaledLastPosition=e._initialScaledLastPosition,this._indexValidityTime=e._indexValidityTime,this._scaledLiveGap=e._scaledLiveGap},t.isFinished=function(){return!this._isLive},t.isInitialized=function(){return!0},t.addNewSegments=function(e,t){this._refreshTimeline();for(var n=0;n>3:2)?"mp4a.40.2":"mp4a.40."+n}(u,l);return{audiotag:void 0!==i?parseInt(i,10):i,bitrate:v,bitsPerSample:void 0!==a?parseInt(a,10):a,channels:void 0!==o?parseInt(o,10):o,codecPrivateData:u,codecs:p,customAttributes:n,mimeType:void 0!==l?F[l]:l,packetSize:void 0!==c?parseInt(c,10):c,samplingRate:void 0!==d?parseInt(d,10):d};case"video":var h=r("CodecPrivateData"),m=r("FourCC"),g=r("MaxWidth"),y=r("MaxHeight"),_=r("Bitrate"),b=void 0===_||isNaN(parseInt(_,10))?0:parseInt(_,10);if(void 0!==m&&void 0===F[m]||void 0===h)return s.Z.warn("Smooth parser: Unsupported video codec. Ignoring quality level."),null;var T=function(e){var t=/00000001\d7([0-9a-fA-F]{6})/.exec(e);return null!==t&&(0,w.Z)(t[1])?"avc1."+t[1]:"avc1.4D401E"}(h);return{bitrate:b,customAttributes:n,mimeType:void 0!==m?F[m]:m,codecPrivateData:h,codecs:T,width:void 0!==g?parseInt(g,10):void 0,height:void 0!==y?parseInt(y,10):void 0};case"text":var E=r("CodecPrivateData"),S=r("FourCC"),k=r("Bitrate");return{bitrate:void 0===k||isNaN(parseInt(k,10))?0:parseInt(k,10),customAttributes:n,mimeType:void 0!==S?F[S]:S,codecPrivateData:(0,I.Z)(E,"")};default:return s.Z.error("Smooth Parser: Unrecognized StreamIndex type: "+t),null}}function o(t){var r=t.root,i=t.timescale,o=t.baseUrl,u=t.protections,l=t.timeShiftBufferDepth,c=t.manifestReceivedTime,f=t.isLive,v=r.getAttribute("Timescale"),p=null===v||isNaN(+v)?i:+v,h=r.getAttribute("Type");if(null===h)throw new Error("StreamIndex without type.");(0,E.Z)(T.r,h)||s.Z.warn("Smooth Parser: Unrecognized adaptation type:",h);var m=h,g=r.getAttribute("Subtype"),y=r.getAttribute("Language"),_=r.getAttribute("Url"),A=null===_?"":_;var I,Z=B(r,(function(e,t,r){switch(t){case"QualityLevel":var i=a(r,m);if(null===i)return e;("video"!==m||i.bitrate>n)&&e.qualityLevels.push(i);break;case"c":e.cNodes.push(r)}return e}),{qualityLevels:[],cNodes:[]}),R=Z.qualityLevels,M=Z.cNodes,P={timeline:(I=M,I.reduce((function(e,t,n){var r=t.getAttribute("d"),i=t.getAttribute("t"),a=t.getAttribute("r"),o=null!==a?+a-1:0,s=null!==i?+i:void 0,u=null!==r?+r:void 0;if(0===n)s=void 0===s||isNaN(s)?0:s;else{var l=e[n-1];if(null==s||isNaN(s)){if(null==l.duration||isNaN(l.duration))throw new Error("Smooth: Invalid CNodes. Missing timestamp.");s=l.start+l.duration*(l.repeatCount+1)}}if(null==u||isNaN(u)){var c=I[n+1];if(void 0===c)return e;var d=c.getAttribute("t"),f=(0,w.Z)(d)?+d:null;if(null===f)throw new Error("Can't build index timeline from Smooth Manifest.");u=f-s}return e.push({duration:u,start:s,repeatCount:o}),e}),[])),timescale:p};(0,d.Z)(0!==R.length,"Adaptation should have at least one playable representation.");var D=m+((0,w.Z)(y)?"_"+y:""),N=R.map((function(t){var n,r,i,a,s={timeline:P.timeline,timescale:P.timescale,media:(n=A,r=t.bitrate,i=t.customAttributes,n.replace(/\{bitrate\}/g,String(r)).replace(/{CustomAttributes}/g,i.length>0?i[0]:""))},d=(0,w.Z)(t.mimeType)?t.mimeType:U[m],v=t.codecs,p=D+"_"+(null!=m?m+"-":"")+(null!=d?d+"-":"")+(null!=v?v+"-":"")+String(t.bitrate),h=[];u.length>0&&(a=u[0],u.forEach((function(e){var t=e.keyId;e.keySystems.forEach((function(e){h.push({keyId:t,systemId:e.systemId})}))})));var g={bitsPerSample:t.bitsPerSample,channels:t.channels,codecPrivateData:t.codecPrivateData,packetSize:t.packetSize,samplingRate:t.samplingRate,protection:null!=a?{keyId:a.keyId}:void 0},y=null!=e.aggressiveMode&&e.aggressiveMode,_=new b(s,{aggressiveMode:y,isLive:f,manifestReceivedTime:c,segmentPrivateInfos:g,timeShiftBufferDepth:l}),T=(0,k.Z)({},t,{index:_,cdnMetadata:[{baseUrl:o}],mimeType:d,codecs:v,id:p});if(h.length>0||void 0!==a){var E=void 0===a?[]:a.keySystems.map((function(e){var t=e.systemId,n=e.privateData,r=t.replace(/-/g,""),i=function(e,t){if(32!==e.length)throw new Error("HSS: wrong system id length");var n=0;return C("pssh",(0,S.zo)([n,0,0,0],(0,x.nr)(e),(0,S.kh)(t.length),t))}(r,n);return{systemId:r,data:i}}));if(E.length>0){var I=[{type:"cenc",values:E}];T.contentProtections={keyIds:h,initData:I}}else T.contentProtections={keyIds:h,initData:[]}}return T}));if("ADVT"===g)return null;var O={id:D,type:m,representations:N,language:null==y?void 0:y};return"text"===m&&"DESC"===g&&(O.closedCaption=!0),O}return function(n,r,a){var s="";if(void 0!==r){var u=(0,A.$)(r);s=r.substring(0,u)}var l=n.documentElement;if(null==l||"SmoothStreamingMedia"!==l.nodeName)throw new Error("document root should be SmoothStreamingMedia");var c=l.getAttribute("MajorVersion"),d=l.getAttribute("MinorVersion");if(null===c||null===d||!/^[2]-[0-2]$/.test(c+"-"+d))throw new Error("Version should be 2.0, 2.1 or 2.2");var f,v,p=l.getAttribute("Timescale"),h=(0,w.Z)(p)?isNaN(+p)?1e7:+p:1e7,m=B(l,(function(t,n,r){switch(n){case"Protection":t.protections.push(L(r,e.keySystems));break;case"StreamIndex":t.adaptationNodes.push(r)}return t}),{adaptationNodes:[],protections:[]}),g=m.protections,y=m.adaptationNodes,_="boolean"==typeof(f=l.getAttribute("IsLive"))?f:"string"==typeof f&&"TRUE"===f.toUpperCase();if(_){var b=l.getAttribute("DVRWindowLength");null==b||isNaN(+b)||0==+b||(v=+b/h)}var T,E,S,k,x,I,Z,R=y.reduce((function(e,t){var n=o({root:t,baseUrl:s,timescale:h,protections:g,isLive:_,timeShiftBufferDepth:v,manifestReceivedTime:a});if(null===n)return e;var r=n.type,i=e[r];return void 0===i?e[r]=[n]:i.push(n),e}),{}),M=null,C=void 0!==R.video?R.video[0]:void 0,P=void 0!==R.audio?R.audio[0]:void 0;if(void 0!==C||void 0!==P){var N=[],O=[];if(void 0!==C){var U=C.representations[0];if(void 0!==U){var F=U.index.getFirstAvailablePosition(),z=U.index.getLastAvailablePosition();null!=F&&N.push(F),null!=z&&O.push(z)}}if(void 0!==P){var V=P.representations[0];if(void 0!==V){var K=V.index.getFirstAvailablePosition(),G=V.index.getLastAvailablePosition();null!=K&&N.push(K),null!=G&&O.push(G)}}N.length>0&&(x=Math.max.apply(Math,N)),O.length>0&&(I=Math.min.apply(Math,O),Z=Math.max.apply(Math,O))}var H=l.getAttribute("Duration"),W=null!==H&&0!=+H?+H/h:void 0;if(_){T=e.suggestedPresentationDelay,E=t,S=null!=x?x:E;var j=Z;void 0===j&&(j=Date.now()/1e3-E);var q=I;void 0===q&&(q=j),k={isLinear:!0,maximumSafePosition:q,livePosition:j,time:performance.now()},M=null!=v?v:null}else{S=null!=x?x:0,k={isLinear:!1,maximumSafePosition:void 0!==I?I:void 0!==W?S+W:1/0,livePosition:void 0,time:performance.now()}}var Y=_?0:S,X=_?void 0:k.maximumSafePosition,$={availabilityStartTime:void 0===E?0:E,clockOffset:i,isLive:_,isDynamic:_,isLastPeriodKnown:!0,timeBounds:{minimumSafePosition:S,timeshiftDepth:M,maximumTimeData:k},periods:[{adaptations:R,duration:void 0!==X?X-Y:W,end:X,id:"gen-smooth-period-0",start:Y}],suggestedPresentationDelay:T,transportType:"smooth",uris:null==r?[]:[r]};return D($),$}},V=z,K=n(4597),G=n(8806),H=n(4460),W=n(8791),j=n(4644),q=n(2297);function Y(e,t,n,r,i){var a,o,u,c=[];if(i){var d=(0,l.XA)(e);null!==d?(u=function(e){var t=(0,q.nR)(e,3565190898,3392751253,2387879627,2655430559);if(void 0===t)return[];for(var n=[],r=t[0],i=t[4],a=0;a0)return e;var n=new Uint8Array(e.length+4);return n.set(e.subarray(0,t+8),0),n[t+3]=1|n[t+3],n.set([0,0,0,0],t+8),n.set(e.subarray(t+8,e.length),t+12),(0,j.J6)(n)}(l,s[1]-s[0]),f=te(u,c,d,i,(0,q.nR)(a,2721664850,1520127764,2722393154,2086964724)),v=P("moof",[i,f]),p=(0,q.Qy)(v,1836019558),h=(0,q.Qy)(f,1953653094),m=(0,q.Qy)(d,1953658222);if(null===p||null===h||null===m)throw new Error("Smooth: Invalid moof, trun or traf generation");var g=p[1]-p[0]+i.length+(h[1]-h[0])+u.length+c.length+(m[1]-m[0])+8,y=n[2]-n[0],_=v.length-y,b=(0,q.Qy)(e,1835295092);if(null===b)throw new Error("Smooth: Invalid ISOBMFF given");if(!X.YM&&(0===_||_<=-8)){var T=b[1];return v.set((0,S.kh)(T),g),e.set(v,n[0]),_<=-8&&e.set(C("free",new Uint8Array(-_-8)),v.length),e}var E=b[1]+_;v.set((0,S.kh)(E),g);var w=new Uint8Array(e.length+_),k=e.subarray(0,n[0]),A=e.subarray(n[2],e.length);return w.set(k,0),w.set(v,k.length),w.set(A,k.length+v.length),w}var re=n(7839),ie=n(281);function ae(e,t,n,r,i,a){var o,s,u,l=P("stbl",[n,C("stts",new Uint8Array(8)),C("stsc",new Uint8Array(8)),C("stsz",new Uint8Array(12)),C("stco",new Uint8Array(8))]),c=function(e){return C("dref",(0,S.zo)(7,[1],e))}(C("url ",new Uint8Array([0,0,0,1]))),d=P("dinf",[c]),f=P("minf",[r,d,l]),v=function(e){var t,n;switch(e){case"video":t="vide",n="VideoHandler";break;case"audio":t="soun",n="SoundHandler";break;default:t="hint",n=""}return C("hdlr",(0,S.zo)(8,(0,x.tG)(t),12,(0,x.tG)(n),1))}(t),p=function(e){return C("mdhd",(0,S.zo)(12,(0,S.kh)(e),8))}(e),h=P("mdia",[p,v,f]),m=function(e,t,n){return C("tkhd",(0,S.zo)((0,S.kh)(7),8,(0,S.kh)(n),20,[1,0,0,0],[0,1,0,0],12,[0,1,0,0],12,[64,0,0,0],(0,S.XT)(e),2,(0,S.XT)(t),2))}(i,a,1),g=P("trak",[m,h]),y=P("mvex",[(o=1,C("trex",(0,S.zo)(4,(0,S.kh)(o),[0,0,0,1],12)))]),_=function(e,t){return C("mvhd",(0,S.zo)(12,(0,S.kh)(e),4,[0,1],2,[1,0],10,[0,1],14,[0,1],14,[64,0,0,0],26,(0,S.XT)(t+1)))}(e,1),b=function(e,t,n){return P("moov",[e,t,n])}(_,y,g),T=(s="isom",u=["isom","iso2","iso6","avc1","dash"],C("ftyp",S.zo.apply(void 0,[(0,x.tG)(s),[0,0,0,1]].concat(u.map(x.tG)))));return(0,S.zo)(T,b)}function oe(e,t,n,r,i,a,o,s){var u=o.split("00000001"),l=u[1],c=u[2];if(void 0===l||void 0===c)throw new Error("Smooth: unsupported codec private data.");var d,f,v=function(e,t,n){var r=2===n?1:4===n?3:0,i=e[1],a=e[2],o=e[3];return C("avcC",(0,S.zo)([1,i,a,o,252|r,225],(0,S.XT)(e.length),e,[1],(0,S.XT)(t.length),t))}((0,x.nr)(l),(0,x.nr)(c),a);if(void 0===s){var p=function(e,t,n,r,i,a,o){return C("avc1",(0,S.zo)(6,(0,S.XT)(1),16,(0,S.XT)(e),(0,S.XT)(t),(0,S.XT)(n),2,(0,S.XT)(r),6,[0,1,i.length],(0,x.tG)(i),31-i.length,(0,S.XT)(a),[255,255],o))}(t,n,r,i,"AVC Coding",24,v);d=J([p])}else{var h=P("schi",[ee(1,8,s)]),m=$("cenc",65536),g=function(e,t,n,r,i,a,o,s){return C("encv",(0,S.zo)(6,(0,S.XT)(1),16,(0,S.XT)(e),(0,S.XT)(t),(0,S.XT)(n),2,(0,S.XT)(r),6,[0,1,i.length],(0,x.tG)(i),31-i.length,(0,S.XT)(a),[255,255],o,s))}(t,n,r,i,"AVC Coding",24,v,P("sinf",[Q("avc1"),m,h]));d=J([g])}return ae(e,"video",d,((f=new Uint8Array(12))[3]=1,C("vmhd",f)),t,n)}var se=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];function ue(e,t,n,r,i,a,o){var s,u,l,c=function(e,t){return C("esds",(0,S.zo)(4,[3,25],(0,S.XT)(e),[0,4,17,64,21],11,[5,2],(0,x.nr)(t),[6,1,2]))}(1,0===a.length?(s=i,u=t,l=((l=((l=(63&2)<<4)|31&se.indexOf(s))<<4)|31&u)<<3,(0,x.ci)((0,S.XT)(l))):a),d=function(){if(void 0===o){var e=function(e,t,n,r,i,a){return C("mp4a",(0,S.zo)(6,(0,S.XT)(e),8,(0,S.XT)(t),(0,S.XT)(n),2,(0,S.XT)(r),(0,S.XT)(i),2,a))}(1,t,n,r,i,c);return J([e])}var a=P("schi",[ee(1,8,o)]),s=$("cenc",65536),u=P("sinf",[Q("mp4a"),s,a]),l=function(e,t,n,r,i,a,o){return C("enca",(0,S.zo)(6,(0,S.XT)(e),8,(0,S.XT)(t),(0,S.XT)(n),2,(0,S.XT)(r),(0,S.XT)(i),2,a,o))}(1,t,n,r,i,c,u);return J([l])}();return ae(e,"audio",d,C("smhd",new Uint8Array(8)),0,0)}var le=/(\.isml?)(\?token=\S+)?$/,ce=/\?token=(\S+)/;function de(e,t){return(0,w.Z)(t)?e.replace(ce,"?token="+t):e.replace(ce,"")}function fe(e){return"string"==typeof e.mimeType&&e.mimeType.indexOf("mp4")>=0}function ve(e,t){return null===e?null:null===t.url?e.baseUrl:(0,A.Z)(e.baseUrl,t.url)}function pe(e,t,n,r,i,a){var o,s=t.segment.range;return Array.isArray(s)&&(o={Range:(0,ie.Z)(s)}),(0,K.ZP)({url:e,responseType:"arraybuffer",headers:o,timeout:r.timeout,cancelSignal:i,onProgress:n.onProgress}).then((function(e){if(!fe(t.representation)||!0!==a)return{resultType:"segment-loaded",resultData:e};var n=new Uint8Array(e.responseData);return(0,H.Z)(n,t.segment.isInit),{resultType:"segment-loaded",resultData:Object.assign(Object.assign({},e),{responseData:n})}}))}var he=function(e){var t=e.checkMediaSegmentIntegrity,n=e.customSegmentLoader;return function(e,r,i,a,o){var s=r.segment,u=r.manifest,l=r.period,c=r.adaptation,d=r.representation;if(s.isInit){if(void 0===s.privateInfos||void 0===s.privateInfos.smoothInitSegment)throw new Error("Smooth: Invalid segment format");var f,v=s.privateInfos.smoothInitSegment,p=v.codecPrivateData,h=v.timescale,m=v.protection,g=void 0===m?{keyId:void 0,keySystems:void 0}:m;if(void 0===p)throw new Error("Smooth: no codec private data.");switch(c.type){case"video":var y=d.width,_=void 0===y?0:y,b=d.height;f=oe(h,_,void 0===b?0:b,72,72,4,p,g.keyId);break;case"audio":var T=v.channels,E=void 0===T?0:T,S=v.bitsPerSample,w=void 0===S?0:S,k=v.packetSize,A=void 0===k?0:k,x=v.samplingRate;f=ue(h,E,w,A,void 0===x?0:x,p,g.keyId);break;default:0,f=new Uint8Array(0)}return Promise.resolve({resultType:"segment-created",resultData:f})}if(null===e)return Promise.resolve({resultType:"segment-created",resultData:null});var I={adaptation:c,manifest:u,period:l,representation:d,segment:s,transport:"smooth",timeout:i.timeout,url:e};return"function"!=typeof n?pe(e,r,o,i,a,t):new Promise((function(s,u){var l=!1,c=n(I,{reject:function(e){var t,n,r;if(!l&&!a.isCancelled){l=!0,a.deregister(d);var i=e,o=null!==(t=null==i?void 0:i.message)&&void 0!==t?t:"Unknown error when fetching a Smooth segment through a custom segmentLoader.",s=new re.Z(o,null!==(n=null==i?void 0:i.canRetry)&&void 0!==n&&n,null!==(r=null==i?void 0:i.isOfflineError)&&void 0!==r&&r,null==i?void 0:i.xhr);u(s)}},resolve:function(e){if(!l&&!a.isCancelled){l=!0,a.deregister(d),fe(r.representation)&&!0===t||s({resultType:"segment-loaded",resultData:{responseData:e.data,size:e.size,requestDuration:e.duration}});var n=e.data instanceof Uint8Array?e.data:new Uint8Array(e.data);(0,H.Z)(n,r.segment.isInit),s({resultType:"segment-loaded",resultData:{responseData:n,size:e.size,requestDuration:e.duration}})}},fallback:function(){l||a.isCancelled||(l=!0,a.deregister(d),pe(e,r,o,i,a,t).then(s,u))},progress:function(e){l||a.isCancelled||o.onProgress({duration:e.duration,size:e.size,totalSize:e.totalSize})}});function d(e){l||((l=!0)||"function"!=typeof c||c(),u(e))}a.register(d)}))}},me=/\.wsx?(\?token=\S+)?/;function ge(e,t,n){var r;s.Z.debug("Smooth Parser: update segments information.");for(var i=e.representations,a=0;a0&&ge(o,v,a),{segmentType:"media",chunkData:h,chunkInfos:p,chunkOffset:0,chunkSize:h.length,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}}},d={loadSegment:function(t,n,r,i,a){var o=n.segment,s=n.representation,u=ve(t,o);return o.isInit||null===u?Promise.resolve({resultType:"segment-created",resultData:null}):fe(s)?(0,K.ZP)({url:u,responseType:"arraybuffer",timeout:r.timeout,cancelSignal:i,onProgress:a.onProgress}).then((function(t){if(!0!==e.checkMediaSegmentIntegrity)return{resultType:"segment-loaded",resultData:t};var r=new Uint8Array(t.responseData);return(0,H.Z)(r,n.segment.isInit),{resultType:"segment-loaded",resultData:Object.assign(Object.assign({},t),{responseData:r})}})):(0,K.ZP)({url:u,responseType:"text",timeout:r.timeout,cancelSignal:i,onProgress:a.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}))},parseSegment:function(e,t,n){var r,i,a,o=t.manifest,u=t.adaptation,c=t.representation,d=t.segment,f=u.language,v=fe(c),p=c.mimeType,h=void 0===p?"":p,m=c.codec,g=void 0===m?"":m,y=e.data,_=e.isChunked;if(d.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if(null===y)return{segmentType:"media",chunkData:null,chunkInfos:null,chunkOffset:0,chunkSize:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]};var b,T,E,S,w=null;if(v){var k;i=(k="string"==typeof y?(0,x.tG)(y):y instanceof Uint8Array?y:new Uint8Array(y)).length;var A=void 0!==n?Y(k,_,n,d,o.isLive):null;a=null==A?void 0:A.nextSegments,null===(w=null!==(r=null==A?void 0:A.chunkInfos)&&void 0!==r?r:null)?_?s.Z.warn("Smooth: Unavailable time data for current text track."):(b=d.time,T=d.end):(b=w.time,T=void 0!==w.duration?w.time+w.duration:d.end);var I=g.toLowerCase();if("application/ttml+xml+mp4"===h||"stpp"===I||"stpp.ttml.im1t"===I)S="ttml";else{if("wvtt"!==I)throw new Error("could not find a text-track parser for the type "+h);S="vtt"}var Z=(0,l.Le)(k);E=null===Z?"":(0,x.uR)(Z)}else{var R;if(b=d.time,T=d.end,"string"!=typeof y){var M=y instanceof Uint8Array?y:new Uint8Array(y);i=M.length,R=(0,x.uR)(M)}else R=y;switch(h){case"application/x-sami":case"application/smil":S="sami";break;case"application/ttml+xml":S="ttml";break;case"text/vtt":S="vtt"}if(void 0===S){if("srt"!==g.toLowerCase())throw new Error("could not find a text-track parser for the type "+h);S="srt"}E=R}return null!==w&&Array.isArray(a)&&a.length>0&&ge(u,a,d),{segmentType:"media",chunkData:{type:S,data:E,start:b,end:T,language:f},chunkSize:i,chunkInfos:w,chunkOffset:null!=b?b:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}}};return{manifest:{resolveManifestUrl:function(e,t){if(void 0===e)return Promise.resolve(void 0);var n;me.test(e)?((0,G.Z)("Giving WSX URL to loadVideo is deprecated. You should only give Manifest URLs."),n=(0,K.ZP)({url:de(e,""),responseType:"document",cancelSignal:t}).then((function(e){var t=e.responseData.getElementsByTagName("media")[0].getAttribute("src");if(null===t||0===t.length)throw new Error("Invalid ISML");return t}))):n=Promise.resolve(e);var r=function(e){var t=ce.exec(e);if(null!==t){var n=t[1];if(void 0!==n)return n}return""}(e);return n.then((function(e){return de(function(e){return le.test(e)?((0,G.Z)("Giving a isml URL to loadVideo is deprecated. Please give the Manifest URL directly"),e.replace(le,"$1/manifest$2")):e}(e),r)}))},loadManifest:(0,W.Z)(i,"text"),parseManifest:function(n,r){var i,a=null!==(i=n.url)&&void 0!==i?i:r.originalUrl,o=n.receivedTime,s=n.responseData,l="string"==typeof s?(new DOMParser).parseFromString(s,"text/xml"):s,c=t(l,a,o);return{manifest:new u.ZP(c,{representationFilter:e.representationFilter,supplementaryImageTracks:e.supplementaryImageTracks,supplementaryTextTracks:e.supplementaryTextTracks}),url:a}}},audio:c,video:c,text:d,image:{loadSegment:function(e,t,n,i,o){return(0,r.Z)(a().mark((function r(){var s,u,l;return a().wrap((function(r){for(;;)switch(r.prev=r.next){case 0:if(s=t.segment,u=ve(e,s),!s.isInit&&null!==u){r.next=4;break}return r.abrupt("return",{resultType:"segment-created",resultData:null});case 4:return r.next=6,(0,K.ZP)({url:u,responseType:"arraybuffer",timeout:n.timeout,onProgress:o.onProgress,cancelSignal:i});case 6:return l=r.sent,r.abrupt("return",{resultType:"segment-loaded",resultData:l});case 8:case"end":return r.stop()}}),r)})))()},parseSegment:function(e,t,n){var r=e.data,i=e.isChunked;if(t.segment.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if(i)throw new Error("Image data should not be downloaded in chunks");return null===r||null===o.Z.imageParser?{segmentType:"media",chunkData:null,chunkInfos:null,chunkOffset:0,chunkSize:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}:{segmentType:"media",chunkData:{data:o.Z.imageParser(new Uint8Array(r)).thumbs,start:0,end:Number.MAX_VALUE,timescale:1,type:"bif"},chunkInfos:{time:0,duration:Number.MAX_VALUE},chunkSize:void 0,chunkOffset:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}}}}}},281:function(e,t,n){"use strict";function r(e){var t=e[0],n=e[1];return n===1/0?"bytes="+t+"-":"bytes="+t+"-"+n}n.d(t,{Z:function(){return r}})},4460:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(5389),i=n(8766);function a(e,t){if(t){if((0,i.Z)(e,1718909296)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `ftyp` box");if((0,i.Z)(e,1836019574)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `moov` box")}else{if((0,i.Z)(e,1836019558)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `moof` box");if((0,i.Z)(e,1835295092)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `mdat` box")}}},8766:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(6968);function i(e,t){for(var n=e.length,i=0;i+8<=n;){var a=(0,r.pX)(e,i);if(0===a)a=n-i;else if(1===a){if(i+16>n)return-1;a=(0,r.pV)(e,i+8)}if(isNaN(a)||a<=0)return-1;if((0,r.pX)(e,i+4)===t)return i+a<=n?i:-1;i+=a}return-1}},8791:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(7904),i=n(4597),a=n(7839);function o(e,t){var n=e.customManifestLoader,o=function(e){return function(t,n,a){if(void 0===t)throw new Error("Cannot perform HTTP(s) request. URL not known");switch(e){case"arraybuffer":return(0,i.ZP)({url:t,responseType:"arraybuffer",timeout:n.timeout,cancelSignal:a});case"text":return(0,i.ZP)({url:t,responseType:"text",timeout:n.timeout,cancelSignal:a});case"document":return(0,i.ZP)({url:t,responseType:"document",timeout:n.timeout,cancelSignal:a});default:(0,r.Z)(e)}}}(t);return"function"!=typeof n?o:function(e,t){return function(n,r,i){return new Promise((function(o,s){var u=Date.now()-performance.now(),l=!1,c=e(n,{reject:function(e){var t,n,r;if(!l&&!i.isCancelled){l=!0,i.deregister(d);var o=e,u=null!==(t=null==o?void 0:o.message)&&void 0!==t?t:"Unknown error when fetching the Manifest through a custom manifestLoader.",c=new a.Z(u,null!==(n=null==o?void 0:o.canRetry)&&void 0!==n&&n,null!==(r=null==o?void 0:o.isOfflineError)&&void 0!==r&&r,null==o?void 0:o.xhr);s(c)}},resolve:function(e){if(!l&&!i.isCancelled){l=!0,i.deregister(d);var t=void 0!==e.receivingTime?e.receivingTime-u:void 0,n=void 0!==e.sendingTime?e.sendingTime-u:void 0;o({responseData:e.data,size:e.size,requestDuration:e.duration,url:e.url,receivedTime:t,sendingTime:n})}},fallback:function(){l||i.isCancelled||(l=!0,i.deregister(d),t(n,r,i).then(o,s))}},{timeout:r.timeout});function d(e){l||(l=!0,"function"==typeof c&&c(),s(e))}i.register(d)}))}}(n,o)}},4791:function(e,t,n){"use strict";function r(e,t){if(e.length!==t.length)return!1;for(var n=e.length-1;n>=0;n--)if(e[n]!==t[n])return!1;return!0}n.d(t,{Z:function(){return r}})},3274:function(e,t,n){"use strict";function r(e,t,n){if("function"==typeof Array.prototype.find)return e.find(t,n);for(var r=e.length>>>0,i=0;i>>0,i=0;i>>0;if(0===r)return!1;for(var i,a,o=0|n,s=o>=0?Math.min(o,r-1):Math.max(r+o,0);s=a.length)throw new Error("Unable to parse base64 string.");var t=a[e];if(255===t)throw new Error("Unable to parse base64 string.");return t}function s(e){var t,n="",r=e.length;for(t=2;t>2],n+=i[(3&e[t-2])<<4|e[t-1]>>4],n+=i[(15&e[t-1])<<2|e[t]>>6],n+=i[63&e[t]];return t===r+1&&(n+=i[e[t-2]>>2],n+=i[(3&e[t-2])<<4],n+="=="),t===r&&(n+=i[e[t-2]>>2],n+=i[(3&e[t-2])<<4|e[t-1]>>4],n+=i[(15&e[t-1])<<2],n+="="),n}function u(e){var t=e.length%4,n=e;0!==t&&(r.Z.warn("base64ToBytes: base64 given miss padding"),n+=3===t?"=":2===t?"==":"===");var i=n.indexOf("=");if(-1!==i&&i>16,l[d+1]=a>>8&255,l[d+2]=255&a;return l.subarray(0,l.length-s)}},6968:function(e,t,n){"use strict";function r(){for(var e,t=arguments.length,n=-1,r=0;++n0&&(i.set(e,a),a+=e.length);return i}function i(e,t){return(e[t+0]<<8)+(e[t+1]<<0)}function a(e,t){return 65536*e[t+0]+256*e[t+1]+e[t+2]}function o(e,t){return 16777216*e[t+0]+65536*e[t+1]+256*e[t+2]+e[t+3]}function s(e,t){return 4294967296*(16777216*e[t+0]+65536*e[t+1]+256*e[t+2]+e[t+3])+16777216*e[t+4]+65536*e[t+5]+256*e[t+6]+e[t+7]}function u(e){return new Uint8Array([e>>>8&255,255&e])}function l(e){return new Uint8Array([e>>>24&255,e>>>16&255,e>>>8&255,255&e])}function c(e){var t=e%4294967296,n=(e-t)/4294967296;return new Uint8Array([n>>>24&255,n>>>16&255,n>>>8&255,255&n,t>>>24&255,t>>>16&255,t>>>8&255,255&t])}function d(e,t){return(e[t+0]<<0)+(e[t+1]<<8)}function f(e,t){return e[t+0]+256*e[t+1]+65536*e[t+2]+16777216*e[t+3]}function v(e){return new Uint8Array([255&e,e>>>8&255,e>>>16&255,e>>>24&255])}function p(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer)}n.d(t,{O_:function(){return v},QI:function(){return a},XT:function(){return u},_f:function(){return p},dN:function(){return f},el:function(){return c},kh:function(){return l},pV:function(){return s},pX:function(){return o},qb:function(){return d},zK:function(){return i},zo:function(){return r}})},7864:function(e,t,n){"use strict";function r(e,t){return new Promise((function(n,r){var i=setTimeout((function(){a(),n()}),e),a=t.register((function(e){clearTimeout(i),r(e)}))}))}n.d(t,{Z:function(){return r}})},8117:function(e,t,n){"use strict";var r=n(1480),i=n(3102),a=n(2817),o=n(1946);t.Z=function(e){return e instanceof r.y?e:e instanceof Promise||!(0,o.Z)(e)&&"function"==typeof e.then?(0,i.D)(e):(0,a.of)(e)}},8333:function(e,t,n){"use strict";n.d(t,{Z:function(){return m}});var r,i=n(8720),a=n(5987),o=n(8337),s=1,u={};function l(e){return e in u&&(delete u[e],!0)}var c=function(e){var t=s++;return u[t]=!0,r||(r=Promise.resolve()),r.then((function(){return l(t)&&e()})),t},d=function(e){l(e)},f={setImmediate:function(){for(var e=[],t=0;t0?e.prototype.requestAsyncId.call(this,t,n,r):(t.actions.push(this),t._scheduled||(t._scheduled=f.setImmediate(t.flush.bind(t,void 0))))},t.prototype.recycleAsyncId=function(t,n,r){var i;if(void 0===r&&(r=0),null!=r?r>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,t,n,r);var a=t.actions;null!=n&&(null===(i=a[a.length-1])||void 0===i?void 0:i.id)!==n&&(f.clearImmediate(n),t._scheduled=void 0)},t}(o.o),p=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return(0,a.ZT)(t,e),t.prototype.flush=function(e){this._active=!0;var t=this._scheduled;this._scheduled=void 0;var n,r=this.actions;e=e||r.shift();do{if(n=e.execute(e.state,e.delay))break}while((e=r[0])&&e.id===t&&r.shift());if(this._active=!1,n){for(;(e=r[0])&&e.id===t&&r.shift();)e.unsubscribe();throw n}},t}(n(9682).v),h=new p(v);function m(){return function(e){return e.pipe((0,i.R)(h))}}},1959:function(e,t,n){"use strict";n.d(t,{R:function(){return s},Z:function(){return o}});var r=n(1480),i=n(3887),a=n(1946),o=function(){function e(){this._listeners={}}var t=e.prototype;return t.addEventListener=function(e,t,n){var r=this,i=this._listeners[e];Array.isArray(i)?i.push(t):this._listeners[e]=[t],void 0!==n&&n.register((function(){r.removeEventListener(e,t)}))},t.removeEventListener=function(e,t){if((0,a.Z)(e))this._listeners={};else{var n=this._listeners[e];if(Array.isArray(n))if((0,a.Z)(t))delete this._listeners[e];else{var r=n.indexOf(t);-1!==r&&n.splice(r,1),0===n.length&&delete this._listeners[e]}}},t.trigger=function(e,t){var n=this._listeners[e];Array.isArray(n)&&n.slice().forEach((function(e){try{e(t)}catch(e){i.Z.error("EventEmitter: listener error",e instanceof Error?e:null)}}))},e}();function s(e,t){return new r.y((function(n){function r(e){n.next(e)}return e.addEventListener(t,r),function(){e.removeEventListener(t,r)}}))}},2793:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(9917),i=n(9127),a=n(4975);function o(e,t){return function(n){return(0,r.P)((function(){return n.pipe((0,i.U)(e),(0,a.h)((function(e){return e!==t})))}))}}},9592:function(e,t,n){"use strict";function r(e,t){return"function"==typeof Array.prototype.flatMap?e.flatMap(t):e.reduce((function(e,n){var r=t(n);return Array.isArray(r)?(e.push.apply(e,r),e):(e.push(r),e)}),[])}n.d(t,{Z:function(){return r}})},2572:function(e,t,n){"use strict";n.d(t,{Z:function(){return r}});function r(e){return e*(.3*(2*Math.random()-1)+1)}},2870:function(e,t,n){"use strict";function r(e){for(var t=0,n=0;n=Number.MAX_SAFE_INTEGER&&(e+="0",t=0),e+String(t)}}n.d(t,{Z:function(){return r}})},6923:function(e,t,n){"use strict";function r(e){return"string"==typeof e&&e.length>0}n.d(t,{Z:function(){return r}})},1946:function(e,t,n){"use strict";function r(e){return null==e}n.d(t,{Z:function(){return r}})},7829:function(e,t,n){"use strict";var r=n(5553);t.ZP=r.ZP},5553:function(e,t,n){"use strict";n.d(t,{ZP:function(){return c},iH:function(){return l},Y1:function(){return u}});var r=n(6923),i=n(1946),a={aa:"aar",ab:"abk",ae:"ave",af:"afr",ak:"aka",am:"amh",an:"arg",ar:"ara",as:"asm",av:"ava",ay:"aym",az:"aze",ba:"bak",be:"bel",bg:"bul",bi:"bis",bm:"bam",bn:"ben",bo:"bod",br:"bre",bs:"bos",ca:"cat",ce:"che",ch:"cha",co:"cos",cr:"cre",cs:"ces",cu:"chu",cv:"chv",cy:"cym",da:"dan",de:"deu",dv:"div",dz:"dzo",ee:"ewe",el:"ell",en:"eng",eo:"epo",es:"spa",et:"est",eu:"eus",fa:"fas",ff:"ful",fi:"fin",fj:"fij",fo:"fao",fr:"fra",fy:"fry",ga:"gle",gd:"gla",gl:"glg",gn:"grn",gu:"guj",gv:"glv",ha:"hau",he:"heb",hi:"hin",ho:"hmo",hr:"hrv",ht:"hat",hu:"hun",hy:"hye",hz:"her",ia:"ina",id:"ind",ie:"ile",ig:"ibo",ii:"iii",ik:"ipk",io:"ido",is:"isl",it:"ita",iu:"iku",ja:"jpn",jv:"jav",ka:"kat",kg:"kon",ki:"kik",kj:"kua",kk:"kaz",kl:"kal",km:"khm",kn:"kan",ko:"kor",kr:"kau",ks:"kas",ku:"kur",kv:"kom",kw:"cor",ky:"kir",la:"lat",lb:"ltz",lg:"lug",li:"lim",ln:"lin",lo:"lao",lt:"lit",lu:"lub",lv:"lav",mg:"mlg",mh:"mah",mi:"mri",mk:"mkd",ml:"mal",mn:"mon",mr:"mar",ms:"msa",mt:"mlt",my:"mya",na:"nau",nb:"nob",nd:"nde",ne:"nep",ng:"ndo",nl:"nld",nn:"nno",no:"nor",nr:"nbl",nv:"nav",ny:"nya",oc:"oci",oj:"oji",om:"orm",or:"ori",os:"oss",pa:"pan",pi:"pli",pl:"pol",ps:"pus",pt:"por",qu:"que",rm:"roh",rn:"run",ro:"ron",ru:"rus",rw:"kin",sa:"san",sc:"srd",sd:"snd",se:"sme",sg:"sag",si:"sin",sk:"slk",sl:"slv",sm:"smo",sn:"sna",so:"som",sq:"sqi",sr:"srp",ss:"ssw",st:"sot",su:"sun",sv:"swe",sw:"swa",ta:"tam",te:"tel",tg:"tgk",th:"tha",ti:"tir",tk:"tuk",tl:"tgl",tn:"tsn",to:"ton",tr:"tur",ts:"tso",tt:"tat",tw:"twi",ty:"tah",ug:"uig",uk:"ukr",ur:"urd",uz:"uzb",ve:"ven",vi:"vie",vo:"vol",wa:"wln",wo:"wol",xh:"xho",yi:"yid",yo:"yor",za:"zha",zh:"zho",zu:"zul"},o={alb:"sqi",arm:"hye",baq:"eus",bur:"mya",chi:"zho",cze:"ces",dut:"nld",fre:"fra",geo:"kat",ger:"deu",gre:"ell",ice:"isl",mac:"mkd",mao:"mri",may:"msa",per:"fas",slo:"slk",rum:"ron",tib:"bod",wel:"cym"};function s(e){if((0,i.Z)(e)||""===e)return"";var t=function(e){var t;switch(e.length){case 2:t=a[e];break;case 3:t=o[e]}return t}((""+e).toLowerCase().split("-")[0]);return(0,r.Z)(t)?t:e}function u(e){if(!(0,i.Z)(e)){var t,n=!1;return"string"==typeof e?t=e:(t=e.language,!0===e.closedCaption&&(n=!0)),{language:t,closedCaption:n,normalized:s(t)}}return e}function l(e){if((0,i.Z)(e))return e;if("string"==typeof e)return{language:e,audioDescription:!1,normalized:s(e)};var t={language:e.language,audioDescription:!0===e.audioDescription,normalized:s(s(e.language))};return!0===e.isDub&&(t.isDub=!0),t}var c=s},8894:function(e,t,n){"use strict";function r(){}n.d(t,{Z:function(){return r}})},8026:function(e,t){"use strict";t.Z="function"==typeof Object.assign?Object.assign:function(e){if(null==e)throw new TypeError("Cannot convert undefined or null to object");for(var t=Object(e),n=0;n<(arguments.length<=1?0:arguments.length-1);n++){var r=n+1<1||arguments.length<=n+1?void 0:arguments[n+1];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(t[i]=r[i])}return t}},1679:function(e,t,n){"use strict";t.Z="function"==typeof Object.values?Object.values:function(e){return Object.keys(e).map((function(t){return e[t]}))}},2829:function(e,t,n){"use strict";n.d(t,{A1:function(){return o},DD:function(){return h},F_:function(){return v},JN:function(){return c},L7:function(){return m},Ti:function(){return s},XS:function(){return f},at:function(){return p},kR:function(){return g},rx:function(){return d},tn:function(){return _},uH:function(){return b}});function r(e,t){return Math.abs(e-t)<.016666666666666666}function i(e,t){return{start:Math.min(e.start,t.start),end:Math.max(e.end,t.end)}}function a(e,t){return e.end<=t.start}function o(e,t){for(var n=0;n=0;n--){var r=e.start(n);if(t>=r){var i=e.end(n);if(t=o?r.push({start:a,end:o}):n={start:a,end:o}}return{outerRanges:r,innerRange:n}}function p(e,t){var n=d(e,t);return null!==n?n.end-n.start:0}function h(e,t){var n=d(e,t);return null!==n?t-n.start:0}function m(e,t){var n=d(e,t);return null!==n?n.end-t:1/0}function g(e,t){if(t.start===t.end)return e;for(var n=t,r=0;r0)for(var o=0;o0)for(var s=0;sl&&n.push({start:l,end:a[c].start}),l=a[c].end;l=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&n.splice(e,1)}}r.complete()}))},onUpdate:function(e,r){if(!0===(null==r?void 0:r.emitCurrentValue)&&e(t),!o){var i={trigger:e,complete:a,hasBeenCleared:!1};n.push(i),void 0!==(null==r?void 0:r.clearSignal)&&r.clearSignal.register(a)}function a(){i.hasBeenCleared=!0;var e=n.indexOf(i);e>=0&&n.splice(e,1)}},waitUntilDefined:function(e,n){if(void 0===t){if(!o){var r=new i.ZP;void 0!==(null==n?void 0:n.clearSignal)&&n.clearSignal.register((function(){return r.cancel()})),this.onUpdate((function(n){if(void 0!==n)return r.cancel(),void e(t)}),{clearSignal:r.signal})}}else e(t)},finish:function(){o=!0;for(var e,t=a(n.slice());!(e=t()).done;){var r=e.value;try{r.hasBeenCleared||r.complete(),r.hasBeenCleared=!0}catch(e){}}n.length=0}}}function u(e,t,n){var r=s(t(e.getValue()));return e.onUpdate((function(e){r.setValue(t(e))}),{clearSignal:n}),void 0!==n&&n.register((function(){r.finish()})),r}},4597:function(e,t,n){"use strict";n.d(t,{ZP:function(){return o}});var r=n(9105),i=n(6923),a=n(1946);var o=function(e){var t={url:e.url,headers:e.headers,responseType:(0,a.Z)(e.responseType)?"json":e.responseType,timeout:e.timeout};return new Promise((function(n,o){var s,u=e.onProgress,l=e.cancelSignal,c=t.url,d=t.headers,f=t.responseType,v=t.timeout,p=new XMLHttpRequest;if(p.open("GET",c,!0),void 0!==v&&(p.timeout=v,s=window.setTimeout((function(){_(),o(new r.Z(c,p.status,"TIMEOUT",p))}),v+3e3)),p.responseType=f,"document"===p.responseType&&p.overrideMimeType("text/xml"),!(0,a.Z)(d)){var h=d;for(var m in h)h.hasOwnProperty(m)&&p.setRequestHeader(m,h[m])}var g=performance.now(),y=null;function _(){void 0!==s&&clearTimeout(s),null!==y&&y()}void 0!==l&&(y=l.register((function(e){_(),(0,a.Z)(p)||4===p.readyState||p.abort(),o(e)})),l.isCancelled)||(p.onerror=function(){_(),o(new r.Z(c,p.status,"ERROR_EVENT",p))},p.ontimeout=function(){_(),o(new r.Z(c,p.status,"TIMEOUT",p))},void 0!==u&&(p.onprogress=function(e){var t=performance.now();u({url:c,duration:t-g,sendingTime:g,currentTime:t,size:e.loaded,totalSize:e.total})}),p.onload=function(e){if(4===p.readyState)if(_(),p.status>=200&&p.status<300){var t,s=performance.now(),u=p.response instanceof ArrayBuffer?p.response.byteLength:e.total,l=p.status,d=p.responseType,f=(0,i.Z)(p.responseURL)?p.responseURL:c;if(t="json"===d?"object"==typeof p.response?p.response:function(e){try{return JSON.parse(e)}catch(e){return null}}(p.responseText):p.response,(0,a.Z)(t))return void o(new r.Z(c,p.status,"PARSE_ERROR",p));n({status:l,url:f,responseType:d,sendingTime:g,receivedTime:s,requestDuration:s-g,size:u,responseData:t})}else o(new r.Z(c,p.status,"ERROR_HTTP_CODE",p))},p.send())}))}},9829:function(e,t,n){"use strict";n.d(t,{$:function(){return s},Z:function(){return o}});var r=/^(?:[a-z]+:)?\/\//i,i=/\/\.{1,2}\//;function a(e){if(!i.test(e))return e;for(var t=[],n=e.split("/"),r=0,a=n.length;r=0&&t===n+1)return e.length}var i=e.indexOf("?");return i>=0&&i>8&255}return n}function u(e){if(a)try{return new TextDecoder("utf-16le").decode(e)}catch(e){var t=e instanceof Error?e:"";r.Z.warn("Utils: could not use TextDecoder to parse UTF-16LE, fallbacking to another implementation",t)}for(var n="",i=0;i=t?n:new Array(t-n.length+1).join("0")+n}function d(e){if(a)try{return(new TextDecoder).decode(e)}catch(e){var t=e instanceof Error?e:"";r.Z.warn("Utils: could not use TextDecoder to parse UTF-8, fallbacking to another implementation",t)}var n=e;239===n[0]&&187===n[1]&&191===n[2]&&(n=n.subarray(3));var i,o=function(e){for(var t="",n=0;n=256?"%u"+c(l,4):"%"+c(l,2)}}return decodeURIComponent(i)}function f(e){for(var t=e.length,n=new Uint8Array(t/2),r=0,i=0;r>>4).toString(16),n+=(15&e[r]).toString(16),t.length>0&&r0;){(0,t._listeners.splice(t._listeners.length-1,1)[0])(e)}}))}var t=e.prototype;return t.register=function(e){var t=this;return this.isCancelled&&((0,o.Z)(null!==this.cancellationError),e(this.cancellationError)),this._listeners.push(e),function(){return t.deregister(e)}},t.deregister=function(e){if(!this.isCancelled)for(var t=0;t0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(t){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,t)},t.prototype._subscribe=function(e){return this._throwIfClosed(),this._checkFinalizedStatuses(e),this._innerSubscribe(e)},t.prototype._innerSubscribe=function(e){var t=this,n=this,r=n.hasError,i=n.isStopped,o=n.observers;return r||i?a.Lc:(this.currentObservers=null,o.push(e),new a.w0((function(){t.currentObservers=null,(0,s.P)(o,e)})))},t.prototype._checkFinalizedStatuses=function(e){var t=this,n=t.hasError,r=t.thrownError,i=t.isStopped;n?e.error(r):i&&e.complete()},t.prototype.asObservable=function(){var e=new i.y;return e.source=this,e},t.create=function(e,t){return new c(e,t)},t}(i.y),c=function(e){function t(t,n){var r=e.call(this)||this;return r.destination=t,r.source=n,r}return(0,r.ZT)(t,e),t.prototype.next=function(e){var t,n;null===(n=null===(t=this.destination)||void 0===t?void 0:t.next)||void 0===n||n.call(t,e)},t.prototype.error=function(e){var t,n;null===(n=null===(t=this.destination)||void 0===t?void 0:t.error)||void 0===n||n.call(t,e)},t.prototype.complete=function(){var e,t;null===(t=null===(e=this.destination)||void 0===e?void 0:e.complete)||void 0===t||t.call(e)},t.prototype._subscribe=function(e){var t,n;return null!==(n=null===(t=this.source)||void 0===t?void 0:t.subscribe(e))&&void 0!==n?n:a.Lc},t}(l)},6267:function(e,t,n){"use strict";n.d(t,{Hp:function(){return g},Lv:function(){return v}});var r=n(5987),i=n(8474),a=n(5720),o=n(3912),s=n(5),u=n(2967),l=c("C",void 0,void 0);function c(e,t,n){return{kind:e,value:t,error:n}}var d=n(8380),f=n(8846),v=function(e){function t(t){var n=e.call(this)||this;return n.isStopped=!1,t?(n.destination=t,(0,a.Nn)(t)&&t.add(n)):n.destination=b,n}return(0,r.ZT)(t,e),t.create=function(e,t,n){return new g(e,t,n)},t.prototype.next=function(e){this.isStopped?_(function(e){return c("N",e,void 0)}(e),this):this._next(e)},t.prototype.error=function(e){this.isStopped?_(c("E",void 0,e),this):(this.isStopped=!0,this._error(e))},t.prototype.complete=function(){this.isStopped?_(l,this):(this.isStopped=!0,this._complete())},t.prototype.unsubscribe=function(){this.closed||(this.isStopped=!0,e.prototype.unsubscribe.call(this),this.destination=null)},t.prototype._next=function(e){this.destination.next(e)},t.prototype._error=function(e){try{this.destination.error(e)}finally{this.unsubscribe()}},t.prototype._complete=function(){try{this.destination.complete()}finally{this.unsubscribe()}},t}(a.w0),p=Function.prototype.bind;function h(e,t){return p.call(e,t)}var m=function(){function e(e){this.partialObserver=e}return e.prototype.next=function(e){var t=this.partialObserver;if(t.next)try{t.next(e)}catch(e){y(e)}},e.prototype.error=function(e){var t=this.partialObserver;if(t.error)try{t.error(e)}catch(e){y(e)}else y(e)},e.prototype.complete=function(){var e=this.partialObserver;if(e.complete)try{e.complete()}catch(e){y(e)}},e}(),g=function(e){function t(t,n,r){var a,s,u=e.call(this)||this;(0,i.m)(t)||!t?a={next:null!=t?t:void 0,error:null!=n?n:void 0,complete:null!=r?r:void 0}:u&&o.v.useDeprecatedNextContext?((s=Object.create(t)).unsubscribe=function(){return u.unsubscribe()},a={next:t.next&&h(t.next,s),error:t.error&&h(t.error,s),complete:t.complete&&h(t.complete,s)}):a=t;return u.destination=new m(a),u}return(0,r.ZT)(t,e),t}(v);function y(e){o.v.useDeprecatedSynchronousErrorHandling?(0,f.O)(e):(0,s.h)(e)}function _(e,t){var n=o.v.onStoppedNotification;n&&d.z.setTimeout((function(){return n(e,t)}))}var b={closed:!0,next:u.Z,error:function(e){throw e},complete:u.Z}},5720:function(e,t,n){"use strict";n.d(t,{Lc:function(){return u},w0:function(){return s},Nn:function(){return l}});var r=n(5987),i=n(8474),a=(0,n(1819).d)((function(e){return function(t){e(this),this.message=t?t.length+" errors occurred during unsubscription:\n"+t.map((function(e,t){return t+1+") "+e.toString()})).join("\n "):"",this.name="UnsubscriptionError",this.errors=t}})),o=n(3699),s=function(){function e(e){this.initialTeardown=e,this.closed=!1,this._parentage=null,this._finalizers=null}var t;return e.prototype.unsubscribe=function(){var e,t,n,o,s;if(!this.closed){this.closed=!0;var u=this._parentage;if(u)if(this._parentage=null,Array.isArray(u))try{for(var l=(0,r.XA)(u),d=l.next();!d.done;d=l.next()){d.value.remove(this)}}catch(t){e={error:t}}finally{try{d&&!d.done&&(t=l.return)&&t.call(l)}finally{if(e)throw e.error}}else u.remove(this);var f=this.initialTeardown;if((0,i.m)(f))try{f()}catch(e){s=e instanceof a?e.errors:[e]}var v=this._finalizers;if(v){this._finalizers=null;try{for(var p=(0,r.XA)(v),h=p.next();!h.done;h=p.next()){var m=h.value;try{c(m)}catch(e){s=null!=s?s:[],e instanceof a?s=(0,r.ev)((0,r.ev)([],(0,r.CR)(s)),(0,r.CR)(e.errors)):s.push(e)}}}catch(e){n={error:e}}finally{try{h&&!h.done&&(o=p.return)&&o.call(p)}finally{if(n)throw n.error}}}if(s)throw new a(s)}},e.prototype.add=function(t){var n;if(t&&t!==this)if(this.closed)c(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=null!==(n=this._finalizers)&&void 0!==n?n:[]).push(t)}},e.prototype._hasParent=function(e){var t=this._parentage;return t===e||Array.isArray(t)&&t.includes(e)},e.prototype._addParent=function(e){var t=this._parentage;this._parentage=Array.isArray(t)?(t.push(e),t):t?[t,e]:e},e.prototype._removeParent=function(e){var t=this._parentage;t===e?this._parentage=null:Array.isArray(t)&&(0,o.P)(t,e)},e.prototype.remove=function(t){var n=this._finalizers;n&&(0,o.P)(n,t),t instanceof e&&t._removeParent(this)},e.EMPTY=((t=new e).closed=!0,t),e}(),u=s.EMPTY;function l(e){return e instanceof s||e&&"closed"in e&&(0,i.m)(e.remove)&&(0,i.m)(e.add)&&(0,i.m)(e.unsubscribe)}function c(e){(0,i.m)(e)?e():e.unsubscribe()}},3912:function(e,t,n){"use strict";n.d(t,{v:function(){return r}});var r={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1}},2034:function(e,t,n){"use strict";n.d(t,{z:function(){return s}});var r=n(4367);function i(){return(0,r.J)(1)}var a=n(2457),o=n(3102);function s(){for(var e=[],t=0;t=2,!0))}},5583:function(e,t,n){"use strict";n.d(t,{B:function(){return u}});var r=n(5987),i=n(7878),a=n(6716),o=n(6267),s=n(6798);function u(e){void 0===e&&(e={});var t=e.connector,n=void 0===t?function(){return new a.x}:t,r=e.resetOnError,u=void 0===r||r,c=e.resetOnComplete,d=void 0===c||c,f=e.resetOnRefCountZero,v=void 0===f||f;return function(e){var t,r,a,c=0,f=!1,p=!1,h=function(){null==r||r.unsubscribe(),r=void 0},m=function(){h(),t=a=void 0,f=p=!1},g=function(){var e=t;m(),null==e||e.unsubscribe()};return(0,s.e)((function(e,s){c++,p||f||h();var y=a=null!=a?a:n();s.add((function(){0!==--c||p||f||(r=l(g,v))})),y.subscribe(s),!t&&c>0&&(t=new o.Hp({next:function(e){return y.next(e)},error:function(e){p=!0,h(),r=l(m,u,e),y.error(e)},complete:function(){f=!0,h(),r=l(m,d),y.complete()}}),(0,i.Xf)(e).subscribe(t))}))(e)}}function l(e,t){for(var n=[],i=2;i0&&i[i.length-1])||6!==a[0]&&2!==a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function u(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,a=n.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(r=a.next()).done;)o.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=a.return)&&n.call(a)}finally{if(i)throw i.error}}return o}function l(e,t){for(var n=0,r=t.length,i=e.length;n1||s(e,t)}))})}function s(e,t){try{(n=i[e](t)).value instanceof c?Promise.resolve(n.value.v).then(u,l):d(a[0][2],n)}catch(e){d(a[0][3],e)}var n}function u(e){s("next",e)}function l(e){s("throw",e)}function d(e,t){e(t),a.shift(),a.length&&s(a[0][0],a[0][1])}}function f(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e=s(e),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,i){(function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)})(r,i,(t=e[n](t)).done,t.value)}))}}}Object.create},7061:function(e,t,n){var r=n(8698).default;function i(){"use strict";e.exports=i=function(){return t},e.exports.__esModule=!0,e.exports.default=e.exports;var t={},n=Object.prototype,a=n.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},s=o.iterator||"@@iterator",u=o.asyncIterator||"@@asyncIterator",l=o.toStringTag||"@@toStringTag";function c(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{c({},"")}catch(e){c=function(e,t,n){return e[t]=n}}function d(e,t,n,r){var i=t&&t.prototype instanceof p?t:p,a=Object.create(i.prototype),o=new A(r||[]);return a._invoke=function(e,t,n){var r="suspendedStart";return function(i,a){if("executing"===r)throw new Error("Generator is already running");if("completed"===r){if("throw"===i)throw a;return I()}for(n.method=i,n.arg=a;;){var o=n.delegate;if(o){var s=S(o,n);if(s){if(s===v)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===r)throw r="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r="executing";var u=f(e,t,n);if("normal"===u.type){if(r=n.done?"completed":"suspendedYield",u.arg===v)continue;return{value:u.arg,done:n.done}}"throw"===u.type&&(r="completed",n.method="throw",n.arg=u.arg)}}}(e,n,o),a}function f(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}t.wrap=d;var v={};function p(){}function h(){}function m(){}var g={};c(g,s,(function(){return this}));var y=Object.getPrototypeOf,_=y&&y(y(x([])));_&&_!==n&&a.call(_,s)&&(g=_);var b=m.prototype=p.prototype=Object.create(g);function T(e){["next","throw","return"].forEach((function(t){c(e,t,(function(e){return this._invoke(t,e)}))}))}function E(e,t){function n(i,o,s,u){var l=f(e[i],e,o);if("throw"!==l.type){var c=l.arg,d=c.value;return d&&"object"==r(d)&&a.call(d,"__await")?t.resolve(d.__await).then((function(e){n("next",e,s,u)}),(function(e){n("throw",e,s,u)})):t.resolve(d).then((function(e){c.value=e,s(c)}),(function(e){return n("throw",e,s,u)}))}u(l.arg)}var i;this._invoke=function(e,r){function a(){return new t((function(t,i){n(e,r,t,i)}))}return i=i?i.then(a,a):a()}}function S(e,t){var n=e.iterator[t.method];if(void 0===n){if(t.delegate=null,"throw"===t.method){if(e.iterator.return&&(t.method="return",t.arg=void 0,S(e,t),"throw"===t.method))return v;t.method="throw",t.arg=new TypeError("The iterator does not provide a 'throw' method")}return v}var r=f(n,e.iterator,t.arg);if("throw"===r.type)return t.method="throw",t.arg=r.arg,t.delegate=null,v;var i=r.arg;return i?i.done?(t[e.resultName]=i.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,v):i:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,v)}function w(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function k(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function A(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(w,this),this.reset(!0)}function x(e){if(e){var t=e[s];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var n=-1,r=function t(){for(;++n=0;--r){var i=this.tryEntries[r],o=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var s=a.call(i,"catchLoc"),u=a.call(i,"finallyLoc");if(s&&u){if(this.prev=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&a.call(r,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),k(n),v}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var i=r.arg;k(n)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:x(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),v}},t}e.exports=i,e.exports.__esModule=!0,e.exports.default=e.exports},8698:function(e){function t(n){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(n)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},4687:function(e,t,n){var r=n(7061)();e.exports=r;try{regeneratorRuntime=r}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=r:Function("r","regeneratorRuntime = r")(r)}},7326:function(e,t,n){"use strict";function r(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}n.d(t,{Z:function(){return r}})},5861:function(e,t,n){"use strict";function r(e,t,n,r,i,a,o){try{var s=e[a](o),u=s.value}catch(e){return void n(e)}s.done?t(u):Promise.resolve(u).then(r,i)}function i(e){return function(){var t=this,n=arguments;return new Promise((function(i,a){var o=e.apply(t,n);function s(e){r(o,i,a,s,u,"next",e)}function u(e){r(o,i,a,s,u,"throw",e)}s(void 0)}))}}n.d(t,{Z:function(){return i}})},3144:function(e,t,n){"use strict";function r(e,t){for(var n=0;n=500||404===e.status||415===e.status||412===e.status:e.type===Q.br.TIMEOUT||e.type===Q.br.ERROR_EVENT:e instanceof Ee.Z?"boolean"==typeof e.canRetry?e.canRetry:void 0!==e.xhr&&(e.xhr.status>=500||404===e.xhr.status||415===e.xhr.status||412===e.xhr.status):(0,Se.Z)(e)&&"INTEGRITY_ERROR"===e.code}function Ie(e){return e instanceof _e.Z?e.type===Q.br.ERROR_EVENT&&!1===navigator.onLine:e instanceof Ee.Z&&e.isOfflineError}function Ze(e){return Ie(e)?2:1}function Re(e,t,n,r,i){return Me.apply(this,arguments)}function Me(){return Me=(0,le.Z)(de().mark((function e(t,n,r,i,a){var o,s,u,l,c,d,f,v,p,h,m,g,y,_;return de().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(_=function(e){var t;if(0===d.size)return e[0];var n=performance.now();return null===(t=e.filter((function(e){var t;return!0!==(null===(t=d.get(e))||void 0===t?void 0:t.isBlacklisted)})).reduce((function(e,t){var r,i=null===(r=d.get(t))||void 0===r?void 0:r.blockedUntil;return void 0!==i&&i<=n&&(i=void 0),void 0===e?[t,i]:void 0===i?void 0===e[1]?e:[t,void 0]:void 0===e[1]?e:iv?(f.blockedUntil=void 0,f.isBlacklisted=!0):(p=f.errorCounter,h=Math.min(o*Math.pow(2,p-1),s),g=(0,ke.Z)(h),f.blockedUntil=performance.now()+g),e.abrupt("return",m(e.t0));case 22:case"end":return e.stop()}}),e,null,[[0,7]])})))).apply(this,arguments)},p=function(e){return h.apply(this,arguments)},v=function(){if(null===t){var e=d.get(null);if(void 0!==e&&e.isBlacklisted)return;return null}if(null===n)return _(t);var r=n.getCdnPreferenceForResource(t);return _(r)},null===a.cancellationError){e.next=9;break}return e.abrupt("return",Promise.reject(a.cancellationError));case 9:if(o=i.baseDelay,s=i.maxDelay,u=i.maxRetryRegular,l=i.maxRetryOffline,c=i.onRetry,null!==t&&0===t.length&&j.Z.warn("Fetchers: no CDN given to `scheduleRequestWithCdns`."),d=new Map,void 0!==(f=v())){e.next=15;break}throw new Error("No CDN to request");case 15:return e.abrupt("return",p(f));case 16:case"end":return e.stop()}}),e)}))),Me.apply(this,arguments)}function Ce(e,t,n){return Re(null,null,e,t,n)}function Pe(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return De(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return De(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function De(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1){var l=t[u],c=function(){for(var e=u+1;el)return e}();if(null!=c)if(r>=t[c])return n[c]}if((null==s||s<1.15)&&r=0;d--)if(n[d]=s.outOfStarvationGap&&(j.Z.info("ABR: exit starvation mode."),this._inStarvationMode=!1):this._inStarvationMode&&(j.Z.info("ABR: exit starvation mode."),this._inStarvationMode=!1),this._inStarvationMode&&(o=function(e,t,n,r,i){if(!r){var a=t.bufferGap,o=t.speed,s=t.position,u=isFinite(a)?a:0,l=function(e,t){for(var n=-1,r=0;r-1.2){n=r;break}if(a>t&&t-i.time>-1.2){n=r;break}}}if(n<0)return[];for(var o=e[n],s=o.content.segment.time,u=[o],l=n+1;l0?c.progress[c.progress.length-1]:void 0,v=je(c);if(void 0!==f&&void 0!==v){var p=qe(f,v);if((d-f.timestamp)/1e3<=p&&p-u/o>2e3)return v}if(c.content.segment.complete){var h=c.content.segment.duration,m=(d-c.requestTimestamp)/1e3;if(null!=n&&!(m<=(1.5*h+2)/o)){var g=h/m,y=n.bitrate*Math.min(.7,g);return void 0===i||y1&&(a/=e.speed),{bandwidthEstimate:o,bitrateChosen:a}},t.isUrgent=function(e,t,n,r){return null===t||e!==t.bitrate&&(e>t.bitrate?!this._inStarvationMode:function(e,t,n){if(n)return!0;var r=isFinite(e.bufferGap)?e.bufferGap:0,i=e.position.last+r,a=(0,He.Z)(t,(function(e){var t=e.content;return t.segment.duration>0&&t.segment.time+t.segment.duration>i}));if(void 0===a)return!0;var o=performance.now(),s=a.progress.length>0?a.progress[a.progress.length-1]:void 0,u=je(a);if(void 0===s||void 0===u)return!0;var l=qe(s,u);return(o-s.timestamp)/1e3>1.2*l||l-r/e.speed>-1.5}(r,n,this._lowLatencyMode))},e}();function Xe(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return $e(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return $e(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function $e(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ns.bitrate)return 2===this._lastAbrEstimate.algorithmType&&(null!==this._lastAbrEstimate.representation&&(this._lastMaintanableBitrate=this._lastAbrEstimate.representation.bitrate),this._consecutiveWrongGuesses=0),null;var u=this._scoreCalculator.getEstimate(n);if(2!==this._lastAbrEstimate.algorithmType){if(void 0===u)return null;if(this._canGuessHigher(a,o,u)){var l=Je(e,n);if(null!==l)return l}return null}if(this._isLastGuessValidated(s,r,u)&&(j.Z.debug("ABR: Guessed Representation validated",s.bitrate),this._lastMaintanableBitrate=s.bitrate,this._consecutiveWrongGuesses=0),n.id!==s.id)return s;if(this._shouldStopGuess(n,u,a,i))return this._consecutiveWrongGuesses++,this._blockGuessesUntil=performance.now()+Math.min(15e3*this._consecutiveWrongGuesses,12e4),function(e,t){var n=(0,Ve.Z)(e,(function(e){return e.id===t.id}));if(n<0)return j.Z.error("ABR: Current Representation not found."),null;for(;--n>=0;)if(e[n].bitrate=2.5&&performance.now()>this._blockGuessesUntil&&1===i&&r/t>1.01},t._shouldStopGuess=function(e,t,n,r){if(void 0!==t&&t[0]<1.01)return!0;if((void 0===t||t[0]<1.2)&&n<.6)return!0;for(var i,a=r.filter((function(t){return t.content.representation.id===e.id})),o=performance.now(),s=Xe(a);!(i=s()).done;){var u=i.value,l=o-u.requestTimestamp;if(u.content.segment.isInit){if(l>1e3)return!0}else{if(l>1e3*u.content.segment.duration+200)return!0;var c=je(u);if(void 0!==c&&c<.8*e.bitrate)return!0}}return!1},t._isLastGuessValidated=function(e,t,n){return void 0!==n&&1===n[1]&&n[0]>1.5||t>=e.bitrate&&(null===this._lastMaintanableBitrate||this._lastMaintanableBitratet.bitrate)return e[r];return null}var et=function(){function e(){var e=Y.Z.getCurrent(),t=e.ABR_FAST_EMA,n=e.ABR_SLOW_EMA;this._fastEWMA=new We(t),this._slowEWMA=new We(n),this._bytesSampled=0}var t=e.prototype;return t.addSample=function(e,t){if(!(t1&&this._lastRepresentationWithGoodScore!==e&&(j.Z.debug("ABR: New last stable representation",e.bitrate),this._lastRepresentationWithGoodScore=e)},t.getEstimate=function(e){if(null!==this._currentRepresentationData&&this._currentRepresentationData.representation.id===e.id){var t=this._currentRepresentationData,n=t.ewma,r=t.loadedSegments,i=t.loadedDuration;return[n.getEstimate(),r>=5&&i>=10?1:0]}},t.getLastStableRepresentation=function(){return this._lastRepresentationWithGoodScore},e}();function at(e,t,n,r){var i=t<=n?n:t>=r?r:t,a=(0,Ve.Z)(e,(function(e){return e.bitrate>i}));return-1===a?e[e.length-1]:0===a?e[0]:e[a-1]}var ot=function(e){var t={},n=e.manualBitrates,r=e.minAutoBitrates,i=e.maxAutoBitrates,a=e.initialBitrates,o=e.throttlers,s=e.lowLatencyMode;return function(e,u,l,c,d){var f=e.adaptation.type,v=function(e){var n=t[e];if(null==n){j.Z.debug("ABR: Creating new BandwidthEstimator for ",e);var r=new et;return t[e]=r,r}return n}(f),p=(0,ze.Z)(n[f],(0,oe.ZP)(-1)),h=(0,ze.Z)(r[f],(0,oe.ZP)(0)),m=(0,ze.Z)(i[f],(0,oe.ZP)(1/0)),g=(0,ze.Z)(a[f],0);return function(e,t){var n=e.bandwidthEstimator,r=e.context,i=e.currentRepresentation,a=e.filters,o=e.initialBitrate,s=e.lowLatencyMode,u=e.manualBitrate,l=e.maxAutoBitrate,c=e.minAutoBitrate,d=e.playbackObserver,f=e.representations,v=new it,p=new Ye(null!=o?o:0,s),h=new rt,m=Ae.Z,g={metrics:E,requestBegin:S,requestProgress:w,requestEnd:k,addedSegment:function(e){m(e)}},y=new se.ZP({cancelOn:t}),_=b(u.getValue(),f.getValue(),y.signal);return u.onUpdate(T,{clearSignal:t}),f.onUpdate(T,{clearSignal:t}),{estimates:_,callbacks:g};function b(e,t,o){if(e>=0){var u=at(t,e,0,1/0);return(0,oe.ZP)({representation:u,bitrate:void 0,knownStableBitrate:void 0,manual:!0,urgent:!0})}if(1===t.length)return(0,oe.ZP)({bitrate:void 0,representation:t[0],manual:!1,urgent:!0,knownStableBitrate:void 0});var f,g=!1,y=t.map((function(e){return e.bitrate})),_=new Ge(y),b=new tt,T=new Qe(v,b),E=d.getReference().getValue(),S=(0,oe.ZP)(k());return d.listen((function(e){E=e,w()}),{includeLastObservation:!1,clearSignal:o}),m=function(e){if(null!==E){var t=E,n=t.position,r=t.speed,i=e.buffered,a=(0,ae.L7)(i,n.last),o=e.content.representation,s=v.getEstimate(o),u=null==s?void 0:s[0],l={bufferGap:a,currentBitrate:o.bitrate,currentScore:u,speed:r};f=_.getEstimate(l),w()}},c.onUpdate(w,{clearSignal:o}),l.onUpdate(w,{clearSignal:o}),a.limitWidth.onUpdate(w,{clearSignal:o}),a.limitWidth.onUpdate(w,{clearSignal:o}),S;function w(){S.setValue(k())}function k(){var e=E,o=e.bufferGap,u=e.position,d=e.maximumPosition,m=a.limitWidth.getValue(),y=a.throttleBitrate.getValue(),_=i.getValue(),S=c.getValue(),w=l.getValue(),k=function(e,t,n){var r=e;n<1/0&&(r=function(e,t){if(0===e.length)return[];e.sort((function(e,t){return e.bitrate-t.bitrate}));var n=e[0].bitrate,r=Math.max(t,n),i=(0,Ve.Z)(e,(function(e){return e.bitrate>r}));return-1===i?e:e.slice(0,i)}(r,n));void 0!==t&&(r=function(e,t){var n=e.slice().sort((function(e,t){return(0,ze.Z)(e.width,0)-(0,ze.Z)(t.width,0)})),r=(0,He.Z)(n,(function(e){return"number"==typeof e.width&&e.width>=t}));if(void 0===r)return e;var i="number"==typeof r.width?r.width:0;return e.filter((function(e){return"number"!=typeof e.width||e.width<=i}))}(r,t));return r}(t,m,y),A=h.getRequests(),x=p.getBandwidthEstimate(E,n,_,A,b.bandwidth),I=x.bandwidthEstimate,Z=x.bitrateChosen,R=v.getLastStableRepresentation(),M=null===R?void 0:R.bitrate/(E.speed>0?E.speed:1);g&&o<=5?g=!1:!g&&isFinite(o)&&o>10&&(g=!0);var C=at(k,Z,S,w),P=C.bitrate,D=null;g&&void 0!==f&&f>P&&(P=(D=at(k,f,S,w)).bitrate);var N=null;return s&&null!==_&&r.manifest.isDynamic&&d-u.last<40&&(N=T.getGuess(t,E,_,P,A)),null!==N&&N.bitrate>P?(j.Z.debug("ABR: Choosing representation with guess-based estimation.",N.bitrate,N.id),b.update(N,I,2),{bitrate:I,representation:N,urgent:null===_||N.bitrate<_.bitrate,manual:!1,knownStableBitrate:M}):null!==D?(j.Z.debug("ABR: Choosing representation with buffer-based estimation.",D.bitrate,D.id),b.update(D,I,0),{bitrate:I,representation:D,urgent:p.isUrgent(D.bitrate,_,A,E),manual:!1,knownStableBitrate:M}):(j.Z.debug("ABR: Choosing representation with bandwidth estimation.",C.bitrate,C.id),b.update(C,I,1),{bitrate:I,representation:C,urgent:p.isUrgent(C.bitrate,_,A,E),manual:!1,knownStableBitrate:M})}}function T(){var e=u.getValue(),n=f.getValue();y.cancel(),b(e,n,(y=new se.ZP({cancelOn:t})).signal).onUpdate((function(e){_.setValue(e)}),{clearSignal:y.signal,emitCurrentValue:!0})}function E(e){var t=e.requestDuration,r=e.segmentDuration,i=e.size,a=e.content;if(n.addSample(t,i),!a.segment.isInit){var o=a.segment,s=a.representation;if(void 0===r&&!o.complete)return;var u=null!=r?r:o.duration;v.addSample(s,t/1e3,u)}}function S(e){h.add(e)}function w(e){h.addProgress(e)}function k(e){h.remove(e.id)}}({bandwidthEstimator:v,context:e,currentRepresentation:u,filters:{limitWidth:(0,ze.Z)(o.limitWidth[f],(0,oe.ZP)(void 0)),throttleBitrate:(0,ze.Z)(o.throttleBitrate[f],o.throttle[f],(0,oe.ZP)(1/0))},initialBitrate:g,manualBitrate:p,minAutoBitrate:h,maxAutoBitrate:m,playbackObserver:c,representations:l,lowLatencyMode:s},d)}};function st(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return ut(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ut(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ut(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&this._removeIndexFromDowngradeList(n);var r=Y.Z.getCurrent().DEFAULT_CDN_DOWNGRADE_TIME;this._downgradedCdnList.metadata.push(e);var i=window.setTimeout((function(){var n=ct(t._downgradedCdnList.metadata,e);n>=0&&t._removeIndexFromDowngradeList(n),t.trigger("priorityChange",null)}),r);this._downgradedCdnList.timeouts.push(i),this.trigger("priorityChange",null)},r._innerGetCdnPreferenceForResource=function(e){var t=this,n=e.reduce((function(e,n){return t._downgradedCdnList.metadata.some((function(e){return e.id===n.id&&e.baseUrl===n.baseUrl}))?e[1].push(n):e[0].push(n),e}),[[],[]]),r=n[0],i=n[1];return r.concat(i)},r._removeIndexFromDowngradeList=function(e){this._downgradedCdnList.metadata.splice(e,1);var t=this._downgradedCdnList.timeouts.splice(e,1);clearTimeout(t[0])},n}(ne.Z);function ct(e,t){return 0===e.length?-1:void 0!==t.id?(0,Ve.Z)(e,(function(e){return e.id===t.id})):(0,Ve.Z)(e,(function(e){return e.baseUrl===t.baseUrl}))}var dt=n(520),ft=n(7714),vt=n(908),pt=function(){function e(){this._cache=new WeakMap}var t=e.prototype;return t.add=function(e,t){var n=e.representation;e.segment.isInit&&this._cache.set(n,t)},t.get=function(e){var t=e.representation;if(e.segment.isInit){var n=this._cache.get(t);if(void 0!==n)return n}return null},e}(),ht=(0,vt.Z)();var mt=function(){function e(e){var t=e.prioritySteps;if(this._minPendingPriority=null,this._waitingQueue=[],this._pendingTasks=[],this._prioritySteps=t,this._prioritySteps.high>=this._prioritySteps.low)throw new Error("TP: the max high level priority should be given a lowerpriority number than the min low priority.")}var t=e.prototype;return t.create=function(e,t,n,r){var i,a=this;return new Promise((function(o,s){var u=r.register((function(e){a._endTask(i),s(e)})),l=function(){u(),a._endTask(i)},c=function(e){n.beforeEnded(),l(),o(e)},d=function(e){l(),s(e)};i={hasEnded:!1,priority:t,trigger:function(){if(i.hasEnded)u();else{var e=new se.ZP({cancelOn:r});i.interrupter=e,e.signal.register((function(){i.interrupter=null,r.isCancelled||n.beforeInterrupted()})),a._minPendingPriority=null===a._minPendingPriority?i.priority:Math.min(a._minPendingPriority,i.priority),a._pendingTasks.push(i),i.taskFn(e.signal).then(c).catch((function(t){!r.isCancelled&&e.isUsed&&t instanceof se.FU||d(t)}))}},taskFn:e,interrupter:null},a._canBeStartedNow(i)?(i.trigger(),a._isRunningHighPriorityTasks()&&a._interruptCancellableTasks()):a._waitingQueue.push(i)}))},t._endTask=function(e){e.hasEnded=!0;var t=gt(e.taskFn,this._waitingQueue);if(t>=0)this._waitingQueue.splice(t,1);else{var n=gt(e.taskFn,this._pendingTasks);if(n<0)return;this._pendingTasks.splice(n,1),this._pendingTasks.length>0?this._minPendingPriority===e.priority&&(this._minPendingPriority=Math.min.apply(Math,this._pendingTasks.map((function(e){return e.priority})))):this._minPendingPriority=null,this._loopThroughWaitingQueue()}},t.updatePriority=function(e,t){var n=gt(e,this._waitingQueue);if(n>=0){var r=this._waitingQueue[n];if(r.priority===t)return;if(r.priority=t,!this._canBeStartedNow(r))return;return this._findAndRunWaitingQueueTask(n),void(this._isRunningHighPriorityTasks()&&this._interruptCancellableTasks())}var i=gt(e,this._pendingTasks);if(i<0)j.Z.warn("TP: request to update the priority of a non-existent task");else{var a=this._pendingTasks[i];if(a.priority!==t){var o=a.priority;a.priority=t,null===this._minPendingPriority||tt.priority?t.priority:e}),null);if(!(null===e||null!==this._minPendingPriority&&this._minPendingPriority=this._prioritySteps.low)return this._interruptPendingTask(t),this._interruptCancellableTasks()}},t._findAndRunWaitingQueueTask=function(e){return e>=this._waitingQueue.length||e<0?(j.Z.warn("TP : Tried to start a non existing task"),!1):(this._waitingQueue.splice(e,1)[0].trigger(),!0)},t._interruptPendingTask=function(e){var t,n=gt(e.taskFn,this._pendingTasks);n<0?j.Z.warn("TP: Interrupting a non-existent pending task. Aborting..."):(this._pendingTasks.splice(n,1),this._waitingQueue.push(e),0===this._pendingTasks.length?this._minPendingPriority=null:this._minPendingPriority===e.priority&&(this._minPendingPriority=Math.min.apply(Math,this._pendingTasks.map((function(e){return e.priority})))),null===(t=e.interrupter)||void 0===t||t.cancel())},t._canBeStartedNow=function(e){return null===this._minPendingPriority||e.priority<=this._minPendingPriority},t._isRunningHighPriorityTasks=function(){return null!==this._minPendingPriority&&this._minPendingPriority<=this._prioritySteps.high},e}();function gt(e,t){return(0,Ve.Z)(t,(function(t){return t.taskFn===e}))}var yt=function(){function e(e,t,n){var r=new lt(n),i=Y.Z.getCurrent(),a=i.MIN_CANCELABLE_PRIORITY,o=i.MAX_HIGH_PRIORITY_LEVEL;this._transport=e,this._prioritizer=new mt({prioritySteps:{high:o,low:a}}),this._cdnPrioritizer=r,this._backoffOptions=t}return e.prototype.createSegmentFetcher=function(e,t){var n,r,i,a=function(e,t){var n=t.maxRetryRegular,r=t.maxRetryOffline,i=t.lowLatencyMode,a=t.requestTimeout,o=Y.Z.getCurrent(),s=o.DEFAULT_MAX_REQUESTS_RETRY_ON_ERROR,u=o.DEFAULT_REQUEST_TIMEOUT,l=o.DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE,c=o.INITIAL_BACKOFF_DELAY_BASE,d=o.MAX_BACKOFF_DELAY_BASE;return{maxRetryRegular:"image"===e?0:null!=n?n:s,maxRetryOffline:null!=r?r:l,baseDelay:i?c.LOW_LATENCY:c.REGULAR,maxDelay:i?d.LOW_LATENCY:d.REGULAR,requestTimeout:(0,re.Z)(a)?u:a}}(e,this._backoffOptions),o=function(e,t,n,r,i){var a={timeout:i.requestTimeout<0?void 0:i.requestTimeout},o=(0,ft.Z)(["audio","video"],e)?new pt:void 0,s=t.loadSegment,u=t.parseSegment;return function(){var e=(0,le.Z)(de().mark((function e(t,l,c){var d,f,v,p,h,m,g,y,_,b,T,E,S,w,k,A;return de().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(A=function(){var e;y||!(0,re.Z)(h)&&void 0!==h.size&&void 0!==h.requestDuration&&m.length>0&&m.every((function(e){return e}))&&(y=!0,null===(e=r.onMetrics)||void 0===e||e.call(r,{size:h.size,requestDuration:h.requestDuration,content:t,segmentDuration:g}))},k=function(e){l.onRetry(Te(e))},w=function(e,n){m.push(!1);var r=m.length-1;return function(i){var a={data:e,isChunked:n};try{var o=u(a,t,i);return m[r]||(g=void 0!==g&&"media"===o.segmentType&&null!==o.chunkInfos&&void 0!==o.chunkInfos.duration?g+o.chunkInfos.duration:void 0,m[r]=!0,A()),o}catch(e){throw(0,$.Z)(e,{defaultCode:"PIPELINE_PARSE_ERROR",defaultReason:"Unknown parsing error"})}}},S=function(e){return s(e,t,a,c,_)},v=(0,dt.K)(t),p=ht(),m=[],g=0,y=!1,_={onProgress:function(e){var t;void 0===h&&void 0!==e.totalSize&&e.size=0;a--){var o=i[a];try{"open"===r&&(j.Z.info("Init: Removing SourceBuffer from mediaSource"),o.abort()),t.removeSourceBuffer(o)}catch(e){j.Z.warn("Init: Error while disposing SourceBuffer",e instanceof Error?e:"")}}i.length>0&&j.Z.warn("Init: Not all SourceBuffers could have been removed.")}if((0,bt.Z)(e),null!==n)try{j.Z.debug("Init: Revoking previous URL"),URL.revokeObjectURL(n)}catch(e){j.Z.warn("Init: Error while revoking the media source URL",e instanceof Error?e:"")}}function kt(e){return function(e){return new v.y((function(t){if(null==Tt.J)throw new X.Z("MEDIA_SOURCE_NOT_SUPPORTED","No MediaSource Object was found in the current browser.");var n=(0,Et.Z)(e.src)?e.src:null;wt(e,null,n),j.Z.info("Init: Creating MediaSource");var r=new Tt.J,i=URL.createObjectURL(r);return j.Z.info("Init: Attaching MediaSource URL to the media element",i),e.src=i,t.next(r),function(){wt(e,r,i)}}))}(e).pipe((0,h.z)((function(e){return St(e).pipe((0,a.q)(1),(0,s.U)((function(){return e})))})))}var At=n(8343);var xt=n(9607),It=n(3610),Zt=n(7326);var Rt=n(7904),Mt=n(6968),Ct=n(2870),Pt=n(9612);var Dt=function(e){function n(t,n,r){var i;i=e.call(this)||this;var a=r.addSourceBuffer(n);i._canceller=new se.ZP,i.bufferType=t,i._mediaSource=r,i._sourceBuffer=a,i._queue=[],i._pendingTask=null,i._lastInitSegment=null,i.codec=n;var o=i._onPendingTaskError.bind((0,Zt.Z)(i)),s=i._flush.bind((0,Zt.Z)(i)),u=Y.Z.getCurrent().SOURCE_BUFFER_FLUSHING_INTERVAL,l=setInterval(s,u);return i._sourceBuffer.addEventListener("error",o),i._sourceBuffer.addEventListener("updateend",s),i._canceller.signal.register((function(){clearInterval(l),i._sourceBuffer.removeEventListener("error",o),i._sourceBuffer.removeEventListener("updateend",s)})),i}(0,t.Z)(n,e);var r=n.prototype;return r.pushChunk=function(e,t){return j.Z.debug("AVSB: receiving order to push data to the SourceBuffer",this.bufferType,(0,dt.K)(e.inventoryInfos)),this._addToQueue({type:Pt.f.Push,value:e},t)},r.removeBuffer=function(e,t,n){return j.Z.debug("AVSB: receiving order to remove data from the SourceBuffer",this.bufferType,e,t),this._addToQueue({type:Pt.f.Remove,value:{start:e,end:t}},n)},r.endOfSegment=function(e,t){return j.Z.debug("AVSB: receiving order for validating end of segment",this.bufferType,(0,dt.K)(e)),this._addToQueue({type:Pt.f.EndOfSegment,value:e},t)},r.getBufferedRanges=function(){return this._sourceBuffer.buffered},r.getPendingOperations=function(){var e=function(e){switch(e.type){case Pt.f.Push:case Pt.f.Remove:case Pt.f.EndOfSegment:return{type:e.type,value:e.value}}},t=this._queue.map(e);return null===this._pendingTask?t:[e(this._pendingTask)].concat(t)},r.dispose=function(){for(this._canceller.cancel(),null!==this._pendingTask&&(this._pendingTask.reject(new se.FU),this._pendingTask=null);this._queue.length>0;){var e=this._queue.shift();void 0!==e&&e.reject(new se.FU)}if("open"===this._mediaSource.readyState)try{this._sourceBuffer.abort()}catch(e){j.Z.warn("AVSB: Failed to abort a "+this.bufferType+" SourceBuffer:",e instanceof Error?e:"")}},r._onPendingTaskError=function(e){if(this._lastInitSegment=null,null!==this._pendingTask){var t=e instanceof Error?e:new Error("An unknown error occured when doing operations on the SourceBuffer");this._pendingTask.reject(t)}},r._addToQueue=function(e,t){var n=this;return new Promise((function(r,i){if(null!==t.cancellationError)return i(t.cancellationError);var a=0===n._queue.length&&null===n._pendingTask,o=(0,ie.Z)({resolve:r,reject:i},e);n._queue.push(o),t.register((function(e){var t=n._queue.indexOf(o);t>=0&&n._queue.splice(t,1),o.resolve=Ae.Z,o.reject=Ae.Z,i(e)})),a&&n._flush()}))},r._flush=function(){if(!this._sourceBuffer.updating){if(null!==this._pendingTask){var e=this._pendingTask;if(e.type!==Pt.f.Push||0===e.data.length){switch(e.type){case Pt.f.Push:null!==e.inventoryData&&this._segmentInventory.insertChunk(e.inventoryData);break;case Pt.f.EndOfSegment:this._segmentInventory.completeSegment(e.value,this.getBufferedRanges());break;case Pt.f.Remove:this.synchronizeInventory();break;default:(0,Rt.Z)(e)}var t=e.resolve;return this._pendingTask=null,t(),void this._flush()}}else{var n=this._queue.shift();if(void 0===n)return;if(n.type!==Pt.f.Push)this._pendingTask=n;else{var r,i=n.value;try{r=this._preparePushOperation(i.data)}catch(e){this._pendingTask=(0,ie.Z)({data:[],inventoryData:i.inventoryInfos},n);var a=e instanceof Error?e:new Error("An unknown error occured when preparing a push operation");return this._lastInitSegment=null,void n.reject(a)}this._pendingTask=(0,ie.Z)({data:r,inventoryData:i.inventoryInfos},n)}}try{switch(this._pendingTask.type){case Pt.f.EndOfSegment:return j.Z.debug("AVSB: Acknowledging complete segment",(0,dt.K)(this._pendingTask.value)),void this._flush();case Pt.f.Push:var o=this._pendingTask.data.shift();if(void 0===o)return void this._flush();j.Z.debug("AVSB: pushing segment",this.bufferType,(0,dt.K)(this._pendingTask.inventoryData)),this._sourceBuffer.appendBuffer(o);break;case Pt.f.Remove:var s=this._pendingTask.value,u=s.start,l=s.end;j.Z.debug("AVSB: removing data from SourceBuffer",this.bufferType,u,l),this._sourceBuffer.remove(u,l);break;default:(0,Rt.Z)(this._pendingTask)}}catch(e){this._onPendingTaskError(e)}}},r._preparePushOperation=function(e){var t=[],n=e.codec,r=e.timestampOffset,i=e.appendWindow,a=!1;if(void 0!==n&&n!==this.codec&&(j.Z.debug("AVSB: updating codec",n),a=function(e,t){if("function"==typeof e.changeType){try{e.changeType(t)}catch(e){return j.Z.warn("Could not call 'changeType' on the given SourceBuffer:",e instanceof Error?e:""),!1}return!0}return!1}(this._sourceBuffer,n),a?this.codec=n:j.Z.debug("AVSB: could not update codec",n,this.codec)),this._sourceBuffer.timestampOffset!==r){var o=r;j.Z.debug("AVSB: updating timestampOffset",this.bufferType,this._sourceBuffer.timestampOffset,o),this._sourceBuffer.timestampOffset=o}if(void 0===i[0]?this._sourceBuffer.appendWindowStart>0&&(this._sourceBuffer.appendWindowStart=0):i[0]!==this._sourceBuffer.appendWindowStart&&(i[0]>=this._sourceBuffer.appendWindowEnd&&(this._sourceBuffer.appendWindowEnd=i[0]+1),this._sourceBuffer.appendWindowStart=i[0]),void 0===i[1]?this._sourceBuffer.appendWindowEnd!==1/0&&(this._sourceBuffer.appendWindowEnd=1/0):i[1]!==this._sourceBuffer.appendWindowEnd&&(this._sourceBuffer.appendWindowEnd=i[1]),null!==e.initSegment&&(a||!this._isLastInitSegment(e.initSegment))){var s=e.initSegment;t.push(s);var u=(0,Mt._f)(s);this._lastInitSegment={data:u,hash:(0,Ct.Z)(u)}}return null!==e.chunk&&t.push(e.chunk),t},r._isLastInitSegment=function(e){if(null===this._lastInitSegment)return!1;if(this._lastInitSegment.data===e)return!0;var t=this._lastInitSegment.data;if(t.byteLength===e.byteLength){var n=(0,Mt._f)(e);if((0,Ct.Z)(n)===this._lastInitSegment.hash&&(0,te.Z)(t,n))return!0}return!1},n}(Pt.C),Nt=["audio","video","text","image"];function Ot(e){return"audio"===e||"video"===e}var Lt=function(){function e(e,t){this._mediaElement=e,this._mediaSource=t,this._initializedSegmentBuffers={},this._onNativeBufferAddedOrDisabled=[]}e.isNative=function(e){return Ot(e)};var t=e.prototype;return t.getBufferTypes=function(){var e=this.getNativeBufferTypes();return null==J.Z.nativeTextTracksBuffer&&null==J.Z.htmlTextTracksBuffer||e.push("text"),null!=J.Z.imageBuffer&&e.push("image"),e},t.getNativeBufferTypes=function(){return"AUDIO"===this._mediaElement.nodeName?["audio"]:["video","audio"]},t.getStatus=function(e){var t=this._initializedSegmentBuffers[e];return void 0===t?{type:"uninitialized"}:null===t?{type:"disabled"}:{type:"initialized",value:t}},t.waitForUsableBuffers=function(e){var t=this;return this._areNativeBuffersUsable()?Promise.resolve():new Promise((function(n,r){var i=function(){t._areNativeBuffersUsable()&&n()};t._onNativeBufferAddedOrDisabled.push(i),e.register((function(e){var n=t._onNativeBufferAddedOrDisabled.indexOf(i);n>=0&&t._onNativeBufferAddedOrDisabled.splice(n,1),r(e)}))}))},t.disableSegmentBuffer=function(t){var n=this._initializedSegmentBuffers[t];if(null!==n){if(void 0!==n)throw new Error("Cannot disable an active SegmentBuffer.");this._initializedSegmentBuffers[t]=null,e.isNative(t)&&this._onNativeBufferAddedOrDisabled.forEach((function(e){return e()}))}else j.Z.warn("SBS: The "+t+" SegmentBuffer was already disabled.")},t.createSegmentBuffer=function(e,t,n){void 0===n&&(n={});var r,i=this._initializedSegmentBuffers[e];if(Ot(e)){if(null!=i)return i instanceof Dt&&i.codec!==t?j.Z.warn("SB: Reusing native SegmentBuffer with codec",i.codec,"for codec",t):j.Z.info("SB: Reusing native SegmentBuffer with codec",t),i;j.Z.info("SB: Adding native SegmentBuffer with codec",t);var a=new Dt(e,t,this._mediaSource);return this._initializedSegmentBuffers[e]=a,this._onNativeBufferAddedOrDisabled.forEach((function(e){return e()})),a}if(null!=i)return j.Z.info("SB: Reusing a previous custom SegmentBuffer for the type",e),i;if("text"===e){if(j.Z.info("SB: Creating a new text SegmentBuffer"),"html"===n.textTrackMode){if(null==J.Z.htmlTextTracksBuffer)throw new Error("HTML Text track feature not activated");r=new J.Z.htmlTextTracksBuffer(this._mediaElement,n.textTrackElement)}else{if(null==J.Z.nativeTextTracksBuffer)throw new Error("Native Text track feature not activated");r=new J.Z.nativeTextTracksBuffer(this._mediaElement,!0===n.hideNativeSubtitle)}return this._initializedSegmentBuffers.text=r,r}if("image"===e){if(null==J.Z.imageBuffer)throw new Error("Image buffer feature not activated");return j.Z.info("SB: Creating a new image SegmentBuffer"),r=new J.Z.imageBuffer,this._initializedSegmentBuffers.image=r,r}throw j.Z.error("SB: Unknown buffer type:",e),new X.Z("BUFFER_TYPE_UNKNOWN","The player wants to create a SegmentBuffer of an unknown type.")},t.disposeSegmentBuffer=function(e){var t=this._initializedSegmentBuffers[e];null!=t?(j.Z.info("SB: Aborting SegmentBuffer",e),t.dispose(),delete this._initializedSegmentBuffers[e]):j.Z.warn("SB: Trying to dispose a SegmentBuffer that does not exist")},t.disposeAll=function(){var e=this;Nt.forEach((function(t){"initialized"===e.getStatus(t).type&&e.disposeSegmentBuffer(t)}))},t._areNativeBuffersUsable=function(){var e=this,t=this.getNativeBufferTypes();return!t.some((function(t){return void 0===e._initializedSegmentBuffers[t]}))&&!t.every((function(t){return null===e._initializedSegmentBuffers[t]}))},e}(),Bt=n(7878);function Ut(e,t){return t?function(n){return n.pipe(Ut((function(n,r){return(0,Bt.Xf)(e(n,r)).pipe((0,s.U)((function(e,i){return t(n,e,r,i)})))})))}:(0,l.e)((function(t,n){var r=0,i=null,a=!1;t.subscribe((0,c.x)(n,(function(t){i||(i=(0,c.x)(n,void 0,(function(){i=null,a&&n.complete()})),(0,Bt.Xf)(e(t,r++)).subscribe(i))}),(function(){a=!0,!i&&n.complete()})))}))}function Ft(e,t){return new v.y((function(n){var r=!1,i=!1;return t().then((function(e){r||(i=!0,n.next(e),n.complete())}),(function(e){i=!0,r||n.error(e)})),function(){i||(r=!0,e.cancel())}}))}var zt=n(7473),Vt=n.n(zt);function Kt(){return new v.y((function(e){var t=!1;return Vt()((function(){t||(e.next(),e.complete())})),function(){t=!0}}))}var Gt=function(){function e(e){this._array=[],this._sortingFn=e}var t=e.prototype;return t.add=function(){for(var e=arguments.length,t=new Array(e),n=0;n=this._array.length)throw new Error("Invalid index.");return this._array[e]},t.findFirst=function(e){return(0,He.Z)(this._array,e)},t.has=function(e){return(0,ft.Z)(this._array,e)},t.removeElement=function(e){var t=this._array.indexOf(e);if(t>=0)return this._array.splice(t,1),t},t.head=function(){return this._array[0]},t.last=function(){return this._array[this._array.length-1]},t.shift=function(){return this._array.shift()},t.pop=function(){return this._array.pop()},e}(),Ht=function(){function e(e){this._weakMap=new WeakMap,this._fn=e}var t=e.prototype;return t.get=function(e){var t=this._weakMap.get(e);if(void 0===t){var n=this._fn(e);return this._weakMap.set(e,n),n}return t},t.destroy=function(e){this._weakMap.delete(e)},e}();function Wt(e,t){var n,r=e.segmentBuffer,i=e.playbackObserver,a=e.maxBufferBehind,o=e.maxBufferAhead;function s(){(function(e,t,n,r,i){return jt.apply(this,arguments)})(r,n,a.getValue(),o.getValue(),t).catch((function(e){var t=e instanceof Error?e.message:"Unknown error";j.Z.error("Could not run BufferGarbageCollector:",t)}))}i.listen((function(e){var t;n=null!==(t=e.position.pending)&&void 0!==t?t:e.position.last,s()}),{includeLastObservation:!0,clearSignal:t}),a.onUpdate(s,{clearSignal:t}),o.onUpdate(s,{clearSignal:t}),s()}function jt(){return(jt=(0,le.Z)(de().mark((function e(t,n,r,i,a){var o,s,u,l,c,d,f,v;return de().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(isFinite(r)||isFinite(i)){e.next=2;break}return e.abrupt("return",Promise.resolve());case 2:o=[],s=(0,ae.F_)(t.getBufferedRanges(),n),u=s.innerRange,l=s.outerRanges,c=function(){if(isFinite(i)){for(var e=0;et.start&&o.push({start:n+i,end:t.end})}null!=u&&n+i=t.end?o.push(t):n>=t.end&&n-r>t.start&&n-ru.start&&o.push({start:u.start,end:n-r})}}(),c(),d=0,f=o;case 9:if(!(d=n.length)return null!==a;if(null===a)return!0;var o=n[r];return a.segment.id!==o.segment.id||(a.priority!==o.priority&&e._segmentFetcher.updatePriority(a.request,o.priority),!1)})),(0,L.w)((function(t){return t.segmentQueue.length>0?e._requestMediaSegments():U.E}))),n=e._downloadQueue.asObservable().pipe((0,b.h)((function(t){var n=e._initSegmentRequest;return null!==t.initSegment&&null!==n?(t.initSegment.priority!==n.priority&&e._segmentFetcher.updatePriority(n.request,t.initSegment.priority),!1):null===t.initSegment||null===n})),(0,L.w)((function(t){return null===t.initSegment?U.E:e._requestInitSegment(t.initSegment)})));return(0,B.T)(n,t)})).pipe((0,T.B)());return this._currentObs$=t,t},t._requestMediaSegments=function(){var e=this,t=this._downloadQueue.getValue().segmentQueue[0],n=this;return(0,g.P)((function(){return r(t)})).pipe((0,Le.x)((function(){e._mediaSegmentRequest=null})));function r(e){if(void 0===e)return(0,p.of)({type:"end-of-queue",value:null});var t=e.segment,i=e.priority,a=(0,ie.Z)({segment:t},n._content);return new v.y((function(e){var o,s=new se.ZP,u=!1,l=!1,c=n._segmentFetcher.createRequest(a,i,{onRetry:function(n){e.next({type:"retry",value:{segment:t,error:n}})},beforeInterrupted:function(){j.Z.info("Stream: segment request interrupted temporarly.",t.id,t.time)},onChunk:function(e){var t=n._initSegmentInfoRef.getValue();void 0!==t?d(e(null!=t?t:void 0)):(l=!0,n._initSegmentInfoRef.waitUntilDefined((function(t){d(e(null!=t?t:void 0))}),{clearSignal:s.signal}))},onAllChunksReceived:function(){l?(n._mediaSegmentsAwaitingInitMetadata.add(t.id),n._initSegmentInfoRef.waitUntilDefined((function(){e.next({type:"end-of-segment",value:{segment:t}}),n._mediaSegmentsAwaitingInitMetadata.delete(t.id),l=!1}),{clearSignal:s.signal})):e.next({type:"end-of-segment",value:{segment:t}})},beforeEnded:function(){n._mediaSegmentRequest=null,l?n._initSegmentInfoRef.waitUntilDefined(f,{clearSignal:s.signal}):f()}},s.signal);return c.catch((function(t){u||(u=!0,e.error(t))})),n._mediaSegmentRequest={segment:t,priority:i,request:c},function(){n._mediaSegmentsAwaitingInitMetadata.delete(t.id),void 0!==o&&o.unsubscribe(),u||(u=!0,l=!1,s.cancel())};function d(n){(0,ye.Z)("media"===n.segmentType,"Should have loaded a media segment."),e.next((0,ie.Z)({},n,{type:"parsed-media",segment:t}))}function f(){var i=n._downloadQueue.getValue().segmentQueue;if(0===i.length)return e.next({type:"end-of-queue",value:null}),u=!0,void e.complete();i[0].segment.id===t.id&&i.shift(),u=!0,o=r(i[0]).subscribe(e)}}))}},t._requestInitSegment=function(e){var t=this;if(null===e)return this._initSegmentRequest=null,U.E;var n=this;return new v.y((function(r){var i=new se.ZP,a=e.segment,o=e.priority,s=(0,ie.Z)({segment:a},t._content),u=!1,l=t._segmentFetcher.createRequest(s,o,{onRetry:function(e){r.next({type:"retry",value:{segment:a,error:e}})},beforeInterrupted:function(){j.Z.info("Stream: init segment request interrupted temporarly.",a.id)},beforeEnded:function(){n._initSegmentRequest=null,u=!0,r.complete()},onChunk:function(e){var t,i=e(void 0);(0,ye.Z)("init"===i.segmentType,"Should have loaded an init segment."),r.next((0,ie.Z)({},i,{type:"parsed-init",segment:a})),"init"===i.segmentType&&n._initSegmentInfoRef.setValue(null!==(t=i.initTimescale)&&void 0!==t?t:null)},onAllChunksReceived:function(){r.next({type:"end-of-segment",value:{segment:a}})}},i.signal);return l.catch((function(e){u||(u=!0,r.error(e))})),t._initSegmentRequest={segment:a,priority:o,request:l},function(){t._initSegmentRequest=null,u||(u=!0,i.cancel())}}))},e}();function en(e,t,n,r,i){var a=e.period,o=e.adaptation,s=e.representation,u=function(e,t){for(var n=0;n=t.end)return null;if(r.bufferedEnd>t.start)return n}return null}(i,t);if(null===u){if(null===n){if(r&&void 0!==a.end&&t.end>=a.end)return{start:void 0,end:null};var l=s.index.checkDiscontinuity(t.start);if(null!==l)return{start:void 0,end:l}}return null}var c=i[u];if(void 0!==c.bufferedStart&&c.bufferedStart>t.start&&(null===n||c.infos.segment.end<=n)){var d=c.bufferedStart;return r||!1===s.index.awaitSegmentBetween(t.start,d)?(j.Z.debug("RS: current discontinuity encountered",o.type,c.bufferedStart),{start:void 0,end:d}):null}var f=function(e,t,n){if(n<=0)return j.Z.error("RS: Asked to check a discontinuity before the first chunk."),null;for(var r=n;r=t.end)return null;if(i.bufferedStart-a.bufferedEnd>0)return r}return null}(i,t,u+1);if(null!==f){var v=i[f-1],p=i[f];if(null===n||p.infos.segment.end<=n){if(!r&&!1!==s.index.awaitSegmentBetween(v.infos.segment.end,p.infos.segment.time))return null;var h=v.bufferedEnd,m=p.bufferedStart;return j.Z.debug("RS: future discontinuity encountered",o.type,h,m),{start:h,end:m}}}if(null===n){if(r&&void 0!==a.end){if(t.end=0;n--){var r=e[n];if(void 0===r.bufferedStart)return null;if(r.bufferedStart=a.end)return null;for(var _=i.length-1;_>=0;_--){var b=i[_];if(void 0===b.bufferedStart)break;if(b.bufferedStart=n.length-1?null:n[t+1],s=null;if(function(e,t,n){var r=Y.Z.getCurrent().MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT;if(void 0===e.bufferedStart)return j.Z.warn("Stream: Start of a segment unknown. Assuming it is garbage collected by default.",e.start),!0;if(null!==t&&void 0!==t.bufferedEnd&&e.bufferedStart-t.bufferedEnd<.1)return!1;if(nr)return j.Z.info("Stream: The start of the wanted segment has been garbage collected",e.start,e.bufferedStart),!0;return!1}(e,r,o.start)){if(function(e,t){var n,r;if(e.length<2)return!0;var i=e[e.length-1],a=null===(n=i.buffered)||void 0===n?void 0:n.start;if(void 0!==t&&void 0!==a&&t-a>.05)return!0;var o=e[e.length-2],s=null===(r=o.buffered)||void 0===r?void 0:r.start;if(void 0===s||void 0===a)return!0;return Math.abs(s-a)>.01}(s=a(e.infos),e.bufferedStart))return!1;j.Z.debug("Stream: skipping segment gc-ed at the start",e.start,e.bufferedStart)}if(function(e,t,n){var r=Y.Z.getCurrent().MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT;if(void 0===e.bufferedEnd)return j.Z.warn("Stream: End of a segment unknown. Assuming it is garbage collected by default.",e.end),!0;if(null!==t&&void 0!==t.bufferedStart&&t.bufferedStart-e.bufferedEnd<.1)return!1;if(n>e.bufferedEnd&&e.end-e.bufferedEnd>r)return j.Z.info("Stream: The end of the wanted segment has been garbage collected",e.start,e.bufferedStart),!0;return!1}(e,i,o.end)){if(function(e,t){var n,r;if(e.length<2)return!0;var i=e[e.length-1],a=null===(n=i.buffered)||void 0===n?void 0:n.end;if(void 0!==t&&void 0!==a&&a-t>.05)return!0;var o=e[e.length-2],s=null===(r=o.buffered)||void 0===r?void 0:r.end;if(void 0===s||void 0===a)return!0;return Math.abs(s-a)>.01}(s=null!=s?s:a(e.infos),e.bufferedEnd))return!1;j.Z.debug("Stream: skipping segment gc-ed at the end",e.end,e.bufferedEnd)}return!0})),p=Y.Z.getCurrent(),h=p.MINIMUM_SEGMENT_SIZE,m=p.MIN_BUFFER_AHEAD,g=!1,y=Math.min(1/60,h),_=!1,b=[],T=f.filter((function(e){var t=(0,ie.Z)({segment:e},n);if(s.length>0&&s.some((function(e){return(0,dt.z)(t,e)})))return!1;var u=e.duration,f=e.time,p=e.end;if(e.isInit)return!0;if(g)return b.push(e),!1;if(e.complete&&u0&&s.some((function(e){if(e.period.id!==n.period.id||e.adaptation.id!==n.adaptation.id)return!1;var a=e.segment;return!(a.time-y>f)&&(!(a.end+y-y&&S.end-p>-y)return!1}}var w=u*n.representation.bitrate;if(d-w<0&&(_=!0,f>o.start+m))return g=!0,b.push(e),!1;var k=a(t);if(k.length>1){var A=k[k.length-1],x=k[k.length-2];if(null===A.buffered&&null===x.buffered)return j.Z.warn("Stream: Segment GCed multiple times in a row, ignoring it.","If this happens a lot and lead to unpleasant experience, please check your device's available memory. If it's low when this message is emitted, you might want to update the RxPlayer's settings (`maxBufferAhead`, `maxVideoBufferSize` etc.) so less memory is used by regular media data buffering."+l.type,c.id,e.time),!1}for(var I=0;If){var R=Z.start>f+y||nn(v,I).ende[n].start;)n++;return e[--n]}function rn(e,t,n,r){var i=Y.Z.getCurrent().CONTENT_REPLACEMENT_PADDING;return e.period.id===t.period.id&&(!(e.segment.timea}return rr}(e.representation,t.representation,r)))}function an(e,t){for(var n=e-t,r=Y.Z.getCurrent().SEGMENT_PRIORITIES_STEPS,i=0;i=u&&l.isInitialized()&&l.isFinished()&&function(e,t,n){var r;return t.containsTime(n)&&e.isLastPeriodKnown&&t.id===(null===(r=e.periods[e.periods.length-1])||void 0===r?void 0:r.id)}(a,o,t)?u-1:t-.1;var c,d=i+n;c=!(!s.index.isInitialized()||!s.index.isFinished()||void 0===o.end)&&(void 0===u?d>=o.end:null===u||d>=u);return{start:Math.max(i,o.start),end:Math.min(d,null!==(r=o.end)&&void 0!==r?r:1/0),hasReachedPeriodEnd:c}}(e,u,i),c=s.index.shouldRefresh(l.start,l.end),d=o.getPendingOperations().filter((function(e){return e.type===Pt.f.EndOfSegment})).map((function(e){return e.value})),f=function(e,t){for(var n=Y.Z.getCurrent().MINIMUM_SEGMENT_SIZE,r=Math.max(1/60,n),i=e.start+r,a=e.end-r,o=[],s=t.length-1;s>=0;s--){var u=t[s],l=u.infos.representation;if(!u.partiallyPushed&&!1!==l.decipherable&&l.isSupported){var c=u.infos.segment,d=c.time/c.timescale;((c.complete?d+c.duration/c.timescale:u.end)>i&&di&&u.start0&&(_=Math.min.apply(Math,d.map((function(e){return e.segment.time})))),h.length>0&&(_=null!==_?Math.min(_,h[0].time):h[0].time),g.length>0&&(_=null!==_?Math.min(_,g[0].segment.time):g[0].segment.time),{imminentDiscontinuity:en(e,l,_,y,f),hasFinishedLoading:y,neededSegments:g,isBufferFull:m,shouldRefreshManifest:c}}function sn(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return un(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return un(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function un(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nu.end||e+ni.start&&o.push({start:i.start,end:e-n}),e+n0&&M.every((function(e){return void 0!==e.keyIds}))&&(R=p.of.apply(void 0,M.map((function(e){return qt.Z.encryptionDataEncountered(e,t)}))),Z=!0)}var P,D,L=I.start().pipe((0,h.z)((function(e){switch(e.type){case"retry":return(0,O.z)((0,p.of)({type:"warning",value:e.value.error}),(0,g.P)((function(){var t=e.value.segment,n=m.index;if(!1===n.isSegmentStillAvailable(t))k.next();else if(n.canBeOutOfSyncError(e.value.error,t))return(0,p.of)(qt.Z.manifestMightBeOufOfSync());return U.E})));case"parsed-init":case"parsed-media":return function(e){if("init"===e.segmentType){Vt()((function(){k.next()})),w.segmentData=e.initializationData,w.isLoaded=!0;var n=m.getAllEncryptionData(),i=!Z&&n.length>0?p.of.apply(void 0,n.map((function(e){return qt.Z.encryptionDataEncountered(e,t)}))):U.E,a=function(e){var t=e.playbackObserver,n=e.content,r=e.segment,i=e.segmentData,a=e.segmentBuffer;return(0,g.P)((function(){if(null===i)return U.E;var e=n.representation.getMimeTypeString(),o={initSegment:i,chunk:null,timestampOffset:0,appendWindow:[void 0,void 0],codec:e},u=new se.ZP;return Ft(u,(function(){return fn(t,a,{data:o,inventoryInfos:null},u.signal)})).pipe((0,s.U)((function(){var e=a.getBufferedRanges();return qt.Z.addedSegment(n,r,e,i)})))}))}({playbackObserver:r,content:t,segment:e.segment,segmentData:e.initializationData,segmentBuffer:o});return(0,B.T)(i,a)}var u=e.inbandEvents,l=e.needsManifestRefresh,c=e.protectionDataUpdate&&!Z?p.of.apply(void 0,m.getAllEncryptionData().map((function(e){return qt.Z.encryptionDataEncountered(e,t)}))):U.E,d=!0===l?(0,p.of)(qt.Z.needsManifestRefresh()):U.E,f=void 0!==u&&u.length>0?(0,p.of)({type:"inband-events",value:u}):U.E,v=w.segmentData,h=function(e){var t=e.playbackObserver,n=e.content,r=e.initSegmentData,i=e.parsedSegment,a=e.segment,o=e.segmentBuffer;return(0,g.P)((function(){var e,u;if(null===i.chunkData)return U.E;var l=i.chunkData,c=i.chunkInfos,d=i.chunkOffset,f=i.chunkSize,v=i.appendWindow,p=n.representation.getMimeTypeString(),h=Y.Z.getCurrent().APPEND_WINDOW_SECURITIES,m=[void 0!==v[0]?Math.max(0,v[0]-h.START):void 0,void 0!==v[1]?v[1]+h.END:void 0],g={initSegment:r,chunk:l,timestampOffset:d,appendWindow:m,codec:p},y=null!==(e=null==c?void 0:c.time)&&void 0!==e?e:a.time,_=y+(null!==(u=null==c?void 0:c.duration)&&void 0!==u?u:a.duration);void 0!==m[0]&&(y=Math.max(y,m[0])),void 0!==m[1]&&(_=Math.min(_,m[1]));var b=(0,ie.Z)({segment:a,chunkSize:f,start:y,end:_},n),T=new se.ZP;return Ft(T,(function(){return fn(t,o,{data:g,inventoryInfos:b},T.signal)})).pipe((0,s.U)((function(){var e=o.getBufferedRanges();return qt.Z.addedSegment(n,a,e,l)})))}))}({playbackObserver:r,content:t,initSegmentData:v,parsedSegment:e,segment:e.segment,segmentBuffer:o});return(0,O.z)(c,d,f,h)}(e);case"end-of-segment":var n=e.value.segment,i=new se.ZP;return Ft(i,(function(){return o.endOfSegment((0,ie.Z)({segment:n},t),i.signal)})).pipe((0,Be.l)());case"end-of-queue":return k.next(),U.E;default:(0,Rt.Z)(e)}}))),F=C([r.getReference().asObservable(),y,_,d.pipe((0,a.q)(1),(0,N.O)(null)),k.pipe((0,N.O)(void 0))]).pipe((0,Qt.M)(E),(0,h.z)((function(e){var n,i,a=e[0],s=a[0],u=a[1],l=a[2],c=a[3],d=e[1],v=null!==(n=s.position.pending)&&void 0!==n?n:s.position.last,h=on(t,v,r,d,u,l,o),g=h.neededSegments,y=null;if(m.index.isInitialized()){if(g.length>0&&!w.isLoaded&&null!==w.segment){var _=g[0].priority;y={segment:w.segment,priority:_}}}else if(null===w.segment)j.Z.warn("Stream: Uninitialized index without an initialization segment");else if(w.isLoaded)j.Z.warn("Stream: Uninitialized index with an already loaded initialization segment");else{var b=null!==(i=s.position.pending)&&void 0!==i?i:s.position.last;y={segment:w.segment,priority:an(f.start,b)}}if(null===c)A.setValue({initSegment:y,segmentQueue:g});else{if(c.urgent)return j.Z.debug("Stream: Urgent switch, terminate now.",S),A.setValue({initSegment:null,segmentQueue:[]}),A.finish(),(0,p.of)(qt.Z.streamTerminating());var T=g[0],E=I.getRequestedInitSegment(),k=I.getRequestedMediaSegment(),x=null===k||void 0===T||k.id!==T.segment.id?[]:[T],Z=null===E?null:y;if(A.setValue({initSegment:Z,segmentQueue:x}),0===x.length&&null===Z)return j.Z.debug("Stream: No request left, terminate",S),A.finish(),(0,p.of)(qt.Z.streamTerminating())}var R=(0,p.of)({type:"stream-status",value:{period:f,position:s.position.last,bufferType:S,imminentDiscontinuity:h.imminentDiscontinuity,hasFinishedLoading:h.hasFinishedLoading,neededSegments:h.neededSegments}}),M=U.E,C=Y.Z.getCurrent().UPTO_CURRENT_POSITION_CLEANUP;if(h.isBufferFull){var P=Math.max(0,v-C);if(P>0){var D=new se.ZP;M=Ft(D,(function(){return o.removeBuffer(0,P,D.signal)})).pipe((0,Be.l)())}}return h.shouldRefreshManifest?(0,O.z)((0,p.of)(qt.Z.needsManifestRefresh()),R,M):(0,O.z)(R,M)})),(P=function(e){return"stream-terminating"!==e.type},void 0===(D=!0)&&(D=!1),(0,l.e)((function(e,t){var n=0;e.subscribe((0,c.x)(t,(function(e){var r=P(e,n++);(r||D)&&t.next(e),!r&&t.complete()})))}))));return(0,B.T)(F,L,R).pipe((0,T.B)())};var hn=function(e){var t=e.playbackObserver,n=e.content,r=e.options,o=e.representationEstimator,u=e.segmentBuffer,l=e.segmentFetcherCreator,c=e.wantedBufferAhead,f=e.maxVideoBufferSize,m="direct"===r.manualBitrateSwitchingMode,y=n.manifest,_=n.period,E=n.adaptation,S={},w=(0,oe.$l)(null),k=new i.x,A=new se.ZP,x=function(e,t,n,r,i,a){var o=e.manifest,s=e.adaptation,u=(0,oe.ZP)([]);f(),o.addEventListener("decipherabilityUpdate",f);var l=a.register(v),c=t(e,n,u,r,a),d=c.estimates;return{abrCallbacks:c.callbacks,estimateRef:d};function f(){var e=s.getPlayableRepresentations();if(0===e.length){var t=new X.Z("NO_PLAYABLE_REPRESENTATION","No Representation in the chosen "+s.type+" Adaptation can be played");return v(),void i(t)}var n=u.getValue();n.length===e.length&&n.every((function(t,n){return t.id===e[n].id}))||u.setValue(e)}function v(){o.removeEventListener("decipherabilityUpdate",f),u.finish(),void 0!==l&&l()}}(n,o,w,t,(function(e){k.error(e)}),A.signal),I=x.estimateRef,Z=x.abrCallbacks,R=l.createSegmentFetcher(E.type,{onRequestBegin:Z.requestBegin,onRequestEnd:Z.requestEnd,onProgress:Z.requestProgress,onMetrics:Z.metrics}),M=(0,oe.$l)(null),C=I.asObservable().pipe((0,z.b)((function(e){M.setValue(e)})),(0,Ue.Z)(),(0,T.B)()),P=C.pipe((0,b.h)((function(e){return null!=e.bitrate})),d((function(e,t){return e.bitrate===t.bitrate})),(0,s.U)((function(e){var t=e.bitrate;return j.Z.debug("Stream: new "+E.type+" bitrate estimate",t),qt.Z.bitrateEstimationChange(E.type,t)}))),D=C.pipe(Ut((function(e,t){return N(e,0===t)})));return(0,B.T)(k,D,P,new v.y((function(){return function(){return A.cancel()}})));function N(e,n){var i=e.representation;if(m&&e.manual&&!n){var o=Y.Z.getCurrent().DELTA_POSITION_AFTER_RELOAD;return $t(_,E.type,t,o.bitrateSwitch)}var u=M.asObservable().pipe((0,b.h)((function(t){return null===t||t.representation.id!==i.id||t.manual&&!e.manual})),(0,a.q)(1),(0,s.U)((function(e){return null===e?(j.Z.info("Stream: urgent Representation termination",E.type),{urgent:!0}):e.urgent?(j.Z.info("Stream: urgent Representation switch",E.type),{urgent:!0}):(j.Z.info("Stream: slow Representation switch",E.type),{urgent:!1})}))),l=r.enableFastSwitching?M.asObservable().pipe((0,s.U)((function(e){return null===e?void 0:e.knownStableBitrate})),d()):(0,p.of)(0),c=(0,p.of)(qt.Z.representationChange(E.type,_,i));return(0,O.z)(c,L(i,u,l)).pipe((0,z.b)((function(e){"added-segment"===e.type&&Z.addedSegment(e.value),"representationChange"===e.type&&w.setValue(e.value.representation)})),(0,h.z)((function(e){if("stream-terminating"===e.type){var t=M.getValue();return null===t?U.E:N(t,!1)}return(0,p.of)(e)})))}function L(e,n,i){return(0,g.P)((function(){var a=S[e.id],o=null!=a?a:1;S[e.id]=o;var l=c.asObservable().pipe((0,s.U)((function(e){return e*o}))),d="video"===E.type?f.asObservable():(0,p.of)(1/0);return j.Z.info("Stream: changing representation",E.type,e.id,e.bitrate),pn({playbackObserver:t,content:{representation:e,adaptation:E,period:_,manifest:y},segmentBuffer:u,segmentFetcher:R,terminate$:n,options:{bufferGoal$:l,maxBufferSize$:d,drmSystemId:r.drmSystemId,fastSwitchThreshold$:i}}).pipe((0,Xt.K)((function(t){var r=(0,$.Z)(t,{defaultCode:"NONE",defaultReason:"Unknown `RepresentationStream` error"});if("BUFFER_FULL_ERROR"===r.code){var a=c.getValue(),s=o;if(s<=.25||a*s<=2)throw r;return S[e.id]=s-.25,L(e,n,i)}throw r})))}))}};function mn(e,t,n,r){var i=r.period,a=!1,o=t.asObservable();return C([e.getReference().asObservable(),o]).pipe((0,h.z)((function(e){var t=e[0],r=e[1],o=t.position.last;return void 0!==i.end&&o+r>=i.end&&(j.Z.debug('Stream: full "empty" AdaptationStream',n),a=!0),(0,p.of)({type:"stream-status",value:{period:i,bufferType:n,position:o,imminentDiscontinuity:null,hasFinishedLoading:a,neededSegments:[],shouldRefreshManifest:!1}})})))}var gn=n(9252);var yn=function(e,t){var n=e.split(";"),r=n[0],i=n.slice(1),a=t.split(";"),o=a[0],s=a.slice(1);if(r!==o)return!1;var u=(0,He.Z)(i,(function(e){return(0,gn.Z)(e,"codecs=")})),l=(0,He.Z)(s,(function(e){return(0,gn.Z)(e,"codecs=")}));if(void 0===u||void 0===l)return!1;var c=u.substring(7),d=l.substring(7);return c.split(".")[0]===d.split(".")[0]};function _n(e,t,n,r,i){if(void 0!==e.codec&&"reload"===i.onCodecSwitch&&!function(e,t){return e.getPlayableRepresentations().some((function(e){return yn(e.getMimeTypeString(),t)}))}(n,e.codec))return{type:"needs-reload",value:void 0};var a=e.getBufferedRanges();if(0===a.length)return{type:"continue",value:void 0};var o=(0,ae.JN)(a),s=t.start,u=null==t.end?1/0:t.end,l=(0,ae.tn)(o,[{start:s,end:u}]);if(0===l.length)return{type:"continue",value:void 0};e.synchronizeInventory();var c=e.getInventory();if(!c.some((function(e){return e.infos.period.id===t.id&&e.infos.adaptation.id!==n.id})))return{type:"continue",value:void 0};var d=function(e,t,n){return e.reduce((function(e,r){if(r.infos.period.id!==t.id||r.infos.adaptation.id!==n.id)return e;var i=r.bufferedStart,a=r.bufferedEnd;return void 0===i||void 0===a||e.push({start:i,end:a}),e}),[])}(c,t,n),f=(0,ae.uH)(l,d);if(0===f.length)return{type:"continue",value:void 0};var v=r.currentTime,p=i.audioTrackSwitchingMode;if(("video"===n.type||"audio"===n.type&&"reload"===p)&&(0,ae.Ti)({start:s,end:u},v)&&(r.readyState>1||!n.getPlayableRepresentations().some((function(t){var n;return yn(t.getMimeTypeString(),null!==(n=e.codec)&&void 0!==n?n:"")})))&&!(0,ae.A1)(d,v))return{type:"needs-reload",value:void 0};var h="audio"===n.type&&"direct"===p,m=[],g=function(e,t){for(var n=0;n=t.start)return n>0?e[n-1]:null;return e.length>0?e[e.length-1]:null}(c,t);null!==g&&(void 0===g.bufferedEnd||t.start-g.bufferedEnd<1)&&m.push({start:0,end:t.start+1});var y=n.type,_=Y.Z.getCurrent().ADAPTATION_SWITCH_BUFFER_PADDINGS,b=_[y].before;null==b&&(b=0);var T=_[y].after;if(null==T&&(T=0),h||m.push({start:v-b,end:v+T}),void 0!==t.end){var E=function(e,t){for(var n=0;nt.start)return e[n];return null}(c,t);null!==E&&(void 0===E.bufferedStart||E.bufferedStart-t.end<1)&&m.push({start:t.end-1,end:Number.MAX_VALUE})}var S=(0,ae.uH)(f,m);return 0===S.length?{type:"continue",value:void 0}:h?{type:"flush-buffer",value:S}:{type:"clean-buffer",value:S}}var bn=function(e){var t=e.bufferType,n=e.content,r=e.garbageCollectors,i=e.playbackObserver,a=e.representationEstimator,o=e.segmentFetcherCreator,u=e.segmentBuffersStore,l=e.options,c=e.wantedBufferAhead,d=e.maxVideoBufferSize,f=n.period,v=new Yt.t(1);return v.pipe((0,L.w)((function(e,v){var m=Y.Z.getCurrent().DELTA_POSITION_AFTER_RELOAD,y=0===v?0:"audio"===t?m.trackSwitch.audio:"video"===t?m.trackSwitch.video:m.trackSwitch.other;if(null===e){j.Z.info("Stream: Set no "+t+" Adaptation. P:",f.start);var _,b=u.getStatus(t);if("initialized"===b.type){if(j.Z.info("Stream: Clearing previous "+t+" SegmentBuffer"),Lt.isNative(t))return $t(f,t,i,0);var T=new se.ZP;_=Ft(T,(function(){return void 0===f.end?b.value.removeBuffer(f.start,1/0,T.signal):f.end<=f.start?Promise.resolve():b.value.removeBuffer(f.start,f.end,T.signal)}))}else"uninitialized"===b.type&&u.disableSegmentBuffer(t),_=(0,p.of)(null);return(0,O.z)(_.pipe((0,s.U)((function(){return qt.Z.adaptationChange(t,null,f)}))),mn(i,c,t,{period:f}))}if(Lt.isNative(t)&&"disabled"===u.getStatus(t).type)return $t(f,t,i,y);j.Z.info("Stream: Updating "+t+" adaptation","A: "+e.id,"P: "+f.start);var E=(0,g.P)((function(){var s=i.getReadyState(),v=function(e,t,n,r){var i=e.getStatus(t);if("initialized"===i.type)return j.Z.info("Stream: Reusing a previous SegmentBuffer for the type",t),i.value;var a=function(e){var t=e.getPlayableRepresentations();if(0===t.length){throw new X.Z("NO_PLAYABLE_REPRESENTATION","No Representation in the chosen "+e.type+" Adaptation can be played")}return t[0].getMimeTypeString()}(n),o="text"===t?r.textTrackOptions:void 0;return e.createSegmentBuffer(t,a,o)}(u,t,e,l),m={currentTime:i.getCurrentTime(),readyState:s},g=_n(v,f,e,m,l);if("needs-reload"===g.type)return $t(f,t,i,y);var _="flush-buffer"===g.type?(0,p.of)(qt.Z.needsBufferFlush()):U.E,b="clean-buffer"===g.type||"flush-buffer"===g.type?O.z.apply(void 0,g.value.map((function(e){var t=e.start,n=e.end,r=new se.ZP;return Ft(r,(function(){return v.removeBuffer(t,n,r.signal)}))}))).pipe((0,Be.l)()):U.E,T=r.get(v),E=function(e,r){var s=n.manifest,v=function(e,t){return e.deriveReadOnlyObserver((function(e,n){var r=(0,oe.ZP)(i());return e.onUpdate(a,{clearSignal:n,emitCurrentValue:!1}),n.register((function(){r.finish()})),r;function i(){var n=e.getValue(),r=t.getBufferedRanges(),i=(0,ae.L7)(r,n.position.last);return(0,ie.Z)({},n,{bufferGap:i})}function a(){r.setValue(i())}}))}(i,r);return hn({content:{manifest:s,period:f,adaptation:e},options:l,playbackObserver:v,representationEstimator:a,segmentBuffer:r,segmentFetcherCreator:o,wantedBufferAhead:c,maxVideoBufferSize:d}).pipe((0,Xt.K)((function(e){if(!Lt.isNative(t)){j.Z.error("Stream: "+t+" Stream crashed. Aborting it.",e instanceof Error?e:""),u.disposeSegmentBuffer(t);var n=(0,$.Z)(e,{defaultCode:"NONE",defaultReason:"Unknown `AdaptationStream` error"});return(0,O.z)((0,p.of)(qt.Z.warning(n)),mn(i,c,t,{period:f}))}throw j.Z.error("Stream: "+t+" Stream crashed. Stopping playback.",e instanceof Error?e:""),e})))}(e,v),S=new se.ZP;return Ft(S,(function(){return u.waitForUsableBuffers(S.signal)})).pipe((0,h.z)((function(){return(0,O.z)(b,_,(0,B.T)(E,T))})))}));return(0,O.z)((0,p.of)(qt.Z.adaptationChange(t,e,f)),E)})),(0,N.O)(qt.Z.periodStreamReady(t,f,v)))},Tn=n(3074);function En(){for(var e=arguments.length,t=new Array(e),n=0;n=0?t[t.length-1].end>=n-5:t[0].start<=n+5}var kn=function(e,t,n,r,u,l){var c,f,m=e.manifest,y=e.initialPeriod,_=l.maxBufferAhead,E=l.maxBufferBehind,S=l.wantedBufferAhead,w=l.maxVideoBufferSize,k=Y.Z.getCurrent(),A=k.MAXIMUM_MAX_BUFFER_AHEAD,x=k.MAXIMUM_MAX_BUFFER_BEHIND,I=new Ht((function(e){var n=e.bufferType,r=null!=x[n]?x[n]:1/0,i=null!=A[n]?A[n]:1/0;return new v.y((function(){var n=new se.ZP;return Wt({segmentBuffer:e,playbackObserver:t,maxBufferBehind:(0,oe.lR)(E,(function(e){return Math.min(e,r)}),n.signal),maxBufferAhead:(0,oe.lR)(_,(function(e){return Math.min(e,i)}),n.signal)},n.signal),function(){n.cancel()}}))})),Z=r.getBufferTypes().map((function(e){return function(e,n){var o=new Gt((function(e,t){return e.start-t.start})),u=new i.x,l=!1;function c(t){return D(e,t,u).pipe((0,s.U)((function(e){switch(e.type){case"waiting-media-source-reload":var t=o.head();if(void 0===t||t.id!==e.value.period.id)return qt.Z.lockedStream(e.value.bufferType,e.value.period);var n=e.value,r=n.position,i=n.autoPlay;return qt.Z.needsMediaSourceReload(r,i);case"periodStreamReady":l=!0,o.add(e.value.period);break;case"periodStreamCleared":o.removeElement(e.value.period)}return e})),(0,T.B)())}function d(e){var t=o.head(),n=o.last();return null==t||null==n||(t.start>e||(null==n.end?1/0:n.end)=n)return U.E;var r=new se.ZP;return Ft(r,(function(){return s.removeBuffer(t,n,r.signal)})).pipe((0,Be.l)())}));return O.z.apply(void 0,_.concat([Kt().pipe((0,Be.l)()),t.getReference().asObservable().pipe((0,a.q)(1),(0,h.z)((function(e){var n,r=(0,g.P)((function(){var t,n=null!==(t=e.position.pending)&&void 0!==t?t:e.position.last,r=m.getPeriodForTime(n);if(null==r)throw new X.Z("MEDIA_TIME_NOT_FOUND","The wanted position is not found in the Manifest.");return c(r)}));if(wn(e,v)){var i=!(null!==(n=e.paused.pending)&&void 0!==n?n:t.getIsPaused());return(0,O.z)((0,p.of)(qt.Z.needsDecipherabilityFlush(e.position.last,i,e.duration)),r)}return wn(e,y)?(0,O.z)((0,p.of)(qt.Z.needsBufferFlush()),r):r})))]))}}(e,y).pipe((0,Ue.Z)(),(0,T.B)())})),R=(c=Z,f=c.length,B.T.apply(void 0,c).pipe((0,b.h)((function(e){var t=e.type;return"periodStreamCleared"===t||"adaptationChange"===t||"representationChange"===t})),(0,Tn.R)((function(e,t){switch(t.type){case"periodStreamCleared":var n=t.value,r=n.period,i=n.type,a=e[r.id];void 0!==a&&a.buffers.has(i)&&(a.buffers.delete(i),0===a.buffers.size&&delete e[r.id]);break;case"adaptationChange":if(null!==t.value.adaptation)return e;case"representationChange":var o=t.value,s=o.period,u=o.type,l=e[s.id];if(void 0===l){var c=new Set;c.add(u),e[s.id]={period:s,buffers:c}}else l.buffers.has(u)||l.buffers.add(u)}return e}),{}),(0,s.U)((function(e){for(var t=Object.keys(e),n=[],r=0;r=s.end}))),g=d.pipe(Ut((function(t){return D(e,t,f)}))),y=c.pipe((0,a.q)(1),(0,z.b)((function(){d.complete(),f.next(),f.complete()})),(0,T.B)()),_=(0,B.T)(v,y),E=bn({bufferType:e,content:{manifest:m,period:s},garbageCollectors:I,maxVideoBufferSize:w,segmentFetcherCreator:u,segmentBuffersStore:r,options:l,playbackObserver:t,representationEstimator:n,wantedBufferAhead:S}).pipe((0,h.z)((function(t){if("stream-status"===t.type)if(t.value.hasFinishedLoading){var n=m.getPeriodAfter(s);if(null===n)return(0,O.z)((0,p.of)(t),(0,p.of)(qt.Z.streamComplete(e)));d.next(n)}else f.next();return(0,p.of)(t)})),(0,T.B)()),k=(0,O.z)(E.pipe((0,o.R)(_)),(0,p.of)(qt.Z.periodStreamCleared(e,s)).pipe((0,z.b)((function(){j.Z.info("SO: Destroying Stream for",e,s.start)}))));return(0,B.T)(k,g,y.pipe((0,Be.l)()))}},An=kn;var xn=function(){function e(e){this._manifest=e,this._lastAudioAdaptation=void 0,this._lastVideoAdaptation=void 0}var t=e.prototype;return t.updateLastAudioAdaptation=function(e){this._lastAudioAdaptation=e},t.updateLastVideoAdaptation=function(e){this._lastVideoAdaptation=e},t.getMaximumAvailablePosition=function(){var e;if(this._manifest.isDynamic)return null!==(e=this._manifest.getLivePosition())&&void 0!==e?e:this._manifest.getMaximumSafePosition();if(void 0===this._lastVideoAdaptation||void 0===this._lastAudioAdaptation)return this._manifest.getMaximumSafePosition();if(null===this._lastAudioAdaptation){if(null===this._lastVideoAdaptation)return this._manifest.getMaximumSafePosition();var t=In(this._lastVideoAdaptation);return"number"!=typeof t?this._manifest.getMaximumSafePosition():t}if(null===this._lastVideoAdaptation){var n=In(this._lastAudioAdaptation);return"number"!=typeof n?this._manifest.getMaximumSafePosition():n}var r=In(this._lastAudioAdaptation),i=In(this._lastVideoAdaptation);return"number"!=typeof r||"number"!=typeof i?this._manifest.getMaximumSafePosition():Math.min(r,i)},t.getEndingPosition=function(){var e,t;if(!this._manifest.isDynamic)return this.getMaximumAvailablePosition();if(void 0!==this._lastVideoAdaptation&&void 0!==this._lastAudioAdaptation){if(null===this._lastAudioAdaptation)return null===this._lastVideoAdaptation?void 0:null!==(e=Zn(this._lastVideoAdaptation))&&void 0!==e?e:void 0;if(null===this._lastVideoAdaptation)return null!==(t=Zn(this._lastAudioAdaptation))&&void 0!==t?t:void 0;var n=Zn(this._lastAudioAdaptation),r=Zn(this._lastVideoAdaptation);return"number"!=typeof n||"number"!=typeof r?void 0:Math.min(n,r)}},e}();function In(e){for(var t,n=e.representations,r=null,i=0;i0&&(o=Math.max(u.buffered.end(l-1)))}if(i===e.duration)return"success";if(o>i){if(o=.1?d0})),d(),(0,L.w)((function(e){return e?C([Kn(Y.Z.getCurrent().STREAM_EVENT_EMITTER_POLL_INTERVAL).pipe((0,N.O)(null)),n]).pipe((0,s.U)((function(e){e[0];return{isSeeking:e[1].seeking,currentTime:t.currentTime}})),(0,l.e)((function(e,t){var n,r=!1;e.subscribe((0,c.x)(t,(function(e){var i=n;n=e,r&&t.next([i,e]),r=!0})))})),(0,h.z)((function(e){var t=e[0],n=e[1];return function(e,t,n){for(var i=t.currentTime,a=n.isSeeking,o=n.currentTime,s=[],u=[],l=0;lo||void 0!==f&&o>=f)&&(Yn(c)&&u.push(c.publicEvent),r.delete(c)):d<=o&&void 0!==f&&o=(null!=f?f:d)&&(a?s.push({type:"stream-event-skip",value:c.publicEvent}):(s.push({type:"stream-event",value:c.publicEvent}),Yn(c)&&u.push(c.publicEvent)))}return(0,O.z)(s.length>0?p.of.apply(void 0,s):U.E,u.length>0?p.of.apply(void 0,u).pipe((0,z.b)((function(e){"function"==typeof e.onExit&&e.onExit()})),(0,Be.l)()):U.E)}(i,t,n)}))):U.E})))},$n=Xn;function Qn(e){var t=e.mediaElement,n=e.manifest,r=e.speed,a=e.bufferOptions,u=e.representationEstimator,l=e.playbackObserver,c=e.segmentFetcherCreator;return function(e,f,v){var m,g=new Hn(n,e),y=null!==(m=n.getPeriodForTime(f))&&void 0!==m?m:n.getNextPeriod(f);if(void 0===y){var _=new X.Z("MEDIA_STARTING_TIME_NOT_FOUND","Wanted starting time not found in the Manifest.");return(0,It._)((function(){return _}))}var T=new Lt(t,e),E=(0,Un.Z)({mediaElement:t,playbackObserver:l,startTime:f,mustAutoPlay:v}),S=E.seekAndPlay$,w=E.initialPlayPerformed,k=E.initialSeekPerformed,A=l.getReference().asObservable(),x=w.asObservable().pipe((0,b.h)((function(e){return e})),(0,h.z)((function(){return $n(n,t,A)}))),I=function(e,t,n){var r=n.autoPlay,i=n.initialPlayPerformed,a=n.initialSeekPerformed,o=n.speed,s=n.startTime;return t.deriveReadOnlyObserver((function(t,n){var u=(0,oe.ZP)(l());return o.onUpdate(c,{clearSignal:n,emitCurrentValue:!1}),t.onUpdate(c,{clearSignal:n,emitCurrentValue:!1}),n.register((function(){u.finish()})),u;function l(){var n,u=t.getValue(),l=o.getValue();if(a.getValue()){if(!e.isDynamic||e.isLastPeriodKnown){var c=e.periods[e.periods.length-1];void 0!==c&&void 0!==c.end&&u.position>c.end&&(n=c.end-1)}}else n=s;return{maximumPosition:e.getMaximumSafePosition(),position:{last:u.position,pending:n},duration:u.duration,paused:{last:u.paused,pending:i.getValue()||!r===u.paused?void 0:!r},readyState:u.readyState,speed:l}}function c(){u.setValue(l())}}))}(n,l,{autoPlay:v,initialPlayPerformed:w,initialSeekPerformed:k,speed:r,startTime:f}),Z=new i.x,R=new i.x,M=new i.x,C=(0,oe.ZP)(null),P=An({manifest:n,initialPeriod:y},I,u,T,c,a).pipe((0,h.z)((function(t){switch(t.type){case"end-of-stream":return j.Z.debug("Init: end-of-stream order received."),function(e){return On(e).pipe((0,N.O)(null),(0,L.w)((function(){return Bn(e)})))}(e).pipe((0,Be.l)(),(0,o.R)(Z));case"resume-stream":return j.Z.debug("Init: resume-stream order received."),Z.next(null),U.E;case"stream-status":var n=t.value,r=n.period,i=n.bufferType,a=n.imminentDiscontinuity,s=n.position;return R.next({period:r,bufferType:i,discontinuity:a,position:s}),U.E;case"locked-stream":return M.next(t.value),U.E;case"adaptationChange":return C.setValue(t),(0,p.of)(t);default:return(0,p.of)(t)}}))),D=function(e,t,n){var r=new xn(e),i=n.getReference().asObservable().pipe((0,Fe.Z)((function(t){var n,i=t.position,a=null!==(n=i.pending)&&void 0!==n?n:i.last;if(ar.getMaximumAvailablePosition()){var s=new X.Z("MEDIA_TIME_AFTER_MANIFEST","The current position is after the latest time announced in the Manifest.");return At.Z.warning(s)}return null}),null)),a=(0,oe.ZP)(void 0),o=(0,ne.R)(e,"manifestUpdate").pipe((0,N.O)(null),(0,z.b)((function(){var t=e.isDynamic?r.getEndingPosition():r.getMaximumAvailablePosition();a.setValue(t)})),(0,Be.l)()),u=t.asObservable().pipe((0,z.b)((function(t){if(null!==t&&e.isLastPeriodKnown){var n=e.periods[e.periods.length-1];if(t.value.period.id===(null==n?void 0:n.id)&&("audio"===t.value.type||"video"===t.value.type)){"audio"===t.value.type?r.updateLastAudioAdaptation(t.value.adaptation):r.updateLastVideoAdaptation(t.value.adaptation);var i=e.isDynamic?r.getMaximumAvailablePosition():r.getEndingPosition();a.setValue(i)}}})),(0,Be.l)());return(0,B.T)(o,u,i,a.asObservable().pipe(F((function(e){return void 0===e})),d(),(0,s.U)((function(e){return{type:"contentDurationUpdate",value:e}}))))}(n,C,I).pipe((0,h.z)((function(e){return"contentDurationUpdate"===e.type?(j.Z.debug("Init: Duration has to be updated.",e.value),g.updateKnownDuration(e.value),U.E):(0,p.of)(e)}))),O=(0,Wn.Z)(l,n,r,M,R),V=S.pipe((0,L.w)((function(e){return"warning"===e.type?(0,p.of)(e):(0,Rn.Z)(A,t,T,!1)})));return(0,B.T)(V,O,P,D,x).pipe((0,Le.x)((function(){g.stop(),T.disposeAll()})))}}function Jn(e){var t,n,r=e.initialManifest,i=e.manifestFetcher,o=e.minimumManifestUpdateInterval,u=e.scheduleRefresh$,l=(t=function(e,t){return i.fetch(e).pipe((0,h.z)((function(e){return"warning"===e.type?(0,p.of)(e):e.parse(t)})),(0,T.B)())},n=!1,function(){for(var e=arguments.length,r=new Array(e),i=0;i0?d=g,_=void 0===t?0:performance.now()-t,b=Math.max(o-_,0),T=u.pipe((0,h.z)((function(e){var n=e.completeRefresh,r=e.delay,i=e.canUseUnsafeMode&&y;return er(null!=r?r:0,o,t).pipe((0,s.U)((function(){return{completeRefresh:n,unsafeMode:i}})))}))),E=null===c.expired?U.E:(0,Fn.H)(b).pipe((0,h.z)((function(){return null===c.expired?U.E:(0,x.D)(c.expired)})),(0,s.U)((function(){return{completeRefresh:!0,unsafeMode:y}}))),S=function(){if(void 0===c.lifetime||c.lifetime<0)return U.E;var e,t=1e3*c.lifetime-_;void 0===i?e=t:c.lifetime<3&&i>=100?(e=Math.min(Math.max(3e3-_,Math.max(t,0)+i),6*t),j.Z.info("MUS: Manifest update rythm is too frequent. Postponing next request.",t,e)):i>=1e3*c.lifetime/10?(e=Math.min(Math.max(t,0)+i,6*t),j.Z.info("MUS: Manifest took too long to parse. Postponing next request",e,e)):e=t;return(0,Fn.H)(Math.max(e,b)).pipe((0,s.U)((function(){return{completeRefresh:!1,unsafeMode:y}})))}();return(0,B.T)(S,T,E).pipe((0,a.q)(1),(0,h.z)((function(e){return m({completeRefresh:e.completeRefresh,unsafeMode:e.unsafeMode})})),(0,h.z)((function(e){return"warning"===e.type?(0,p.of)(e):f(e)})))}function m(e){var t=e.completeRefresh,n=e.unsafeMode,r=c.updateUrl,i=t||void 0===r,a=i?c.getUrl():r,s=c.clockOffset;return n?(d+=1,j.Z.info('Init: Refreshing the Manifest in "unsafeMode" for the '+String(d)+" consecutive time.")):d>0&&(j.Z.info('Init: Not parsing the Manifest in "unsafeMode" anymore after '+String(d)+" consecutive times."),d=0),l(a,{externalClockOffset:s,previousManifest:c,unsafeMode:n}).pipe((0,h.z)((function(e){if("warning"===e.type)return(0,p.of)(e);var t=e.manifest,n=e.sendingTime,r=e.receivedTime,a=e.parsingTime,s=performance.now();if(i)c.replace(t);else try{c.update(t)}catch(e){var u=e instanceof Error?e.message:"unknown error";return j.Z.warn("MUS: Attempt to update Manifest failed: "+u,"Re-downloading the Manifest fully"),er(Y.Z.getCurrent().FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY,o,n).pipe((0,h.z)((function(){return m({completeRefresh:!0,unsafeMode:!1})})))}return(0,p.of)({type:"parsed",manifest:c,sendingTime:n,receivedTime:r,parsingTime:a,updatingTime:performance.now()-s})})))}}function er(e,t,n){return(0,g.P)((function(){var r=void 0===n?0:performance.now()-n,i=Math.max(t-r,0);return(0,Fn.H)(Math.max(e-r,i))}))}var tr=n(2447);var nr=function(e){var t=e.adaptiveOptions,n=e.autoPlay,r=e.bufferOptions,u=e.keySystems,l=e.lowLatencyMode,c=e.manifest$,d=e.manifestFetcher,f=e.mediaElement,v=e.minimumManifestUpdateInterval,g=e.playbackObserver,y=e.segmentRequestOptions,_=e.speed,E=e.startAt,S=e.transport,w=e.textTrackOptions,k=ot(t),A=new se.ZP,x=kt(f).pipe((0,m.d)({refCount:!0})),I=new i.x,Z=(0,xt.Z)(f,u,I,x).pipe((0,Ue.Z)(),(0,T.B)()),R=(0,tr.Z)(f),M=C([c,Z.pipe((0,b.h)((function(e){return"decryption-ready"===e.type||"decryption-disabled"===e.type})),(0,s.U)((function(e){return e.value})),(0,a.q)(1))]).pipe((0,h.z)((function(e){var t=e[0],a=e[1],u=a.drmSystemId,c=a.mediaSource;if("warning"===t.type)return(0,p.of)(t);var m=t.manifest;j.Z.debug("Init: Calculating initial time");var b=function(e,t,n){if(!(0,re.Z)(n)){var r,i=e.getMinimumSafePosition();if(e.isLive&&(r=e.getLivePosition()),void 0===r&&(r=e.getMaximumSafePosition()),!(0,re.Z)(n.position))return j.Z.debug("Init: using startAt.minimumPosition"),Math.max(Math.min(n.position,r),i);if(!(0,re.Z)(n.wallClockTime)){j.Z.debug("Init: using startAt.wallClockTime");var a=void 0===e.availabilityStartTime?0:e.availabilityStartTime,o=n.wallClockTime-a;return Math.max(Math.min(o,r),i)}if(!(0,re.Z)(n.fromFirstPosition)){j.Z.debug("Init: using startAt.fromFirstPosition");var s=n.fromFirstPosition;return s<=0?i:Math.min(r,i+s)}if(!(0,re.Z)(n.fromLastPosition)){j.Z.debug("Init: using startAt.fromLastPosition");var u=n.fromLastPosition;return u>=0?r:Math.max(i,r+u)}if(!(0,re.Z)(n.percentage)){j.Z.debug("Init: using startAt.percentage");var l=n.percentage;return l>100?r:l<0?i:i+ +l/100*(r-i)}}var c=e.getMinimumSafePosition();if(e.isLive){var d,f=e.suggestedPresentationDelay,v=e.clockOffset,p=e.getMaximumSafePosition(),h=Y.Z.getCurrent().DEFAULT_LIVE_GAP;if(void 0===v)j.Z.info("Init: no clock offset found for a live content, starting close to maximum available position"),d=p;else{j.Z.info("Init: clock offset found for a live content, checking if we can start close to it");var m=void 0===e.availabilityStartTime?0:e.availabilityStartTime,g=(performance.now()+v)/1e3-m;d=Math.min(p,g)}var y=void 0!==f?f:t?h.LOW_LATENCY:h.DEFAULT;return j.Z.debug("Init: "+d+" defined as the live time, applying a live gap of "+y),Math.max(d-y,c)}return j.Z.info("Init: starting at the minimum available position:",c),c}(m,l,E);j.Z.debug("Init: Initial time calculated:",b);var T={lowLatencyMode:l,requestTimeout:y.requestTimeout,maxRetryRegular:y.regularError,maxRetryOffline:y.offlineError},x=new _t(S,T,A.signal),Z=Qn({bufferOptions:(0,ie.Z)({textTrackOptions:w,drmSystemId:u},r),manifest:m,mediaElement:f,playbackObserver:g,representationEstimator:k,segmentFetcherCreator:x,speed:_}),R=function e(t,n,r){var a=new i.x,s=Z(t,n,r).pipe((0,Fe.Z)((function(e){switch(e.type){case"needs-manifest-refresh":return M.next({completeRefresh:!1,canUseUnsafeMode:!0}),null;case"manifest-might-be-out-of-sync":var t=Y.Z.getCurrent().OUT_OF_SYNC_MANIFEST_REFRESH_DELAY;return M.next({completeRefresh:!0,canUseUnsafeMode:!1,delay:t}),null;case"needs-media-source-reload":return a.next(e.value),null;case"needs-decipherability-flush":var n=me(f);if(null===(i=n)||i.indexOf("widevine")<0)return a.next(e.value),null;var r=e.value.position;return r+.001=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ar(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(i="internal-seeking",r=t._internalSeeksIncoming.shift());var a=null!=e?e:t._generateInitialObservation(),o=lr(t._mediaElement,i,t._withMediaSource),s=null;o.seeking&&("number"==typeof r?s=r:null!==a.pendingInternalSeek&&"seeking"!==n&&(s=a.pendingInternalSeek));var u=function(e,t,n){var r,i,a=n.withMediaSource,o=n.lowLatencyMode,s=Y.Z.getCurrent().REBUFFERING_GAP,u=t.event,l=t.position,c=t.bufferGap,d=t.currentRange,f=t.duration,v=t.paused,p=t.readyState,h=t.ended,m=e.rebuffering,g=e.event,y=e.position,_=function(e,t,n,r,i){var a=Y.Z.getCurrent().REBUFFERING_GAP,o=i?"LOW_LATENCY":"DEFAULT";if(void 0===t)return n&&Math.abs(r-e)<=a[o];return null!==t&&r-t.end<=a[o]}(l,d,h,f,o),b=p>=1&&"loadedmetadata"!==u&&null===m&&!(_||h),T=null,E=o?s.LOW_LATENCY:s.DEFAULT;if(a){if(b)c===1/0?(r=!0,T=l):void 0===c?p<3&&(r=!0,T=void 0):c<=E&&(r=!0,T=l+c);else if(null!==m){var S=ur(m,o);!0!==r&&null!==m&&p>1&&(_||h||void 0!==c&&isFinite(c)&&c>S)||void 0===c&&p>=3?i=!0:void 0===c?T=void 0:c===1/0?T=l:c<=S&&(T=l+c)}}else b&&(!v&&"timeupdate"===u&&"timeupdate"===g&&l===y||"seeking"===u&&(c===1/0||void 0===c&&p<3))?r=!0:null!==m&&("seeking"!==u&&l!==y||"canplay"===u||void 0===c&&p>=3||void 0!==c&&c<1/0&&(c>ur(m,o)||_||h))&&(i=!0);if(!0===i)return null;var w;if(!0===r||null!==m)return w="seeking"===u||null!==m&&"seeking"===m.reason||t.seeking?"seeking":1===p?"not-ready":"buffering",null!==m&&m.reason===w?{reason:m.reason,timestamp:m.timestamp,position:T}:{reason:w,timestamp:performance.now(),position:T};return null}(a,o,{lowLatencyMode:t._lowLatencyMode,withMediaSource:t._withMediaSource}),l=function(e,t){var n=Y.Z.getCurrent().MINIMUM_BUFFER_AMOUNT_BEFORE_FREEZING;if(e.freezing)return t.ended||t.paused||0===t.readyState||0===t.playbackRate||e.position!==t.position?null:e.freezing;return"timeupdate"===t.event&&void 0!==t.bufferGap&&t.bufferGap>n&&!t.ended&&!t.paused&&t.readyState>=1&&0!==t.playbackRate&&t.position===e.position?{timestamp:performance.now()}:null}(a,o),c=(0,ie.Z)({},{rebuffering:u,freezing:l,pendingInternalSeek:s},o);return j.Z.hasLevel("DEBUG")&&j.Z.debug("API: current media element state tick","event",c.event,"position",c.position,"seeking",c.seeking,"internalSeek",c.pendingInternalSeek,"rebuffering",null!==c.rebuffering,"freezing",null!==c.freezing,"ended",c.ended,"paused",c.paused,"playbackRate",c.playbackRate,"readyState",c.readyState),c},s=(0,oe.ZP)(o("init")),u=function(t){var n=o(t);j.Z.hasLevel("DEBUG")&&j.Z.debug("API: current playback timeline:\n"+function(e,t){for(var n="",r="",i=0;it){var c=n.length-Math.floor(l.length/2);r=" ".repeat(c)+"^"+t}if(i=3?(r=void 0,i=void 0):i=null!==(r=(0,ae.rx)(a,o))?r.end-o:1/0,{bufferGap:i,buffered:a,currentRange:r,position:o,duration:s,ended:u,paused:l,playbackRate:c,readyState:d,seeking:f,event:t}}function cr(e,t,n){var r=t(e.getReference(),n);return{getCurrentTime:function(){return e.getCurrentTime()},getReadyState:function(){return e.getReadyState()},getPlaybackRate:function(){return e.getPlaybackRate()},getIsPaused:function(){return e.getIsPaused()},getReference:function(){return r},listen:function(e,t){var i;n.isCancelled||!0===(null===(i=null==t?void 0:t.clearSignal)||void 0===i?void 0:i.isCancelled)||r.onUpdate(e,{clearSignal:null==t?void 0:t.clearSignal,emitCurrentValue:null==t?void 0:t.includeLastObservation})},deriveReadOnlyObserver:function(e){return cr(this,e,n)}}}var dr=n(7829);function fr(e){return e.map((function(e){return null===e?e:{normalized:void 0===e.language?void 0:(0,dr.ZP)(e.language),audioDescription:e.audioDescription,codec:e.codec}}))}function vr(e){return e.map((function(e){return null===e?e:{normalized:(0,dr.ZP)(e.language),closedCaption:e.closedCaption}}))}var pr=function(){function e(e){this._periods=new Gt((function(e,t){return e.period.start-t.period.start})),this._audioChoiceMemory=new WeakMap,this._textChoiceMemory=new WeakMap,this._videoChoiceMemory=new WeakMap,this._preferredAudioTracks=[],this._preferredTextTracks=[],this._preferredVideoTracks=[],this.trickModeTrackEnabled=e.preferTrickModeTracks}var t=e.prototype;return t.setPreferredAudioTracks=function(e,t){this._preferredAudioTracks=e,t&&this._applyAudioPreferences()},t.setPreferredTextTracks=function(e,t){this._preferredTextTracks=e,t&&this._applyTextPreferences()},t.setPreferredVideoTracks=function(e,t){this._preferredVideoTracks=e,t&&this._applyVideoPreferences()},t.addPeriod=function(e,t,n){var r=Tr(this._periods,t),i=t.getSupportedAdaptations(e);if(void 0!==r){if(void 0!==r[e])return void j.Z.warn("TrackChoiceManager: "+e+" already added for period",t.start);r[e]={adaptations:i,adaptation$:n}}else{var a;this._periods.add(((a={period:t})[e]={adaptations:i,adaptation$:n},a))}},t.removePeriod=function(e,t){var n=function(e,t){for(var n=0;n0;)this._periods.pop()},t.update=function(){this._resetChosenAudioTracks(),this._resetChosenTextTracks(),this._resetChosenVideoTracks()},t.setInitialAudioTrack=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.audio:null;if((0,re.Z)(n)||void 0===t)throw new Error("TrackChoiceManager: Given Period not found.");var r=e.getSupportedAdaptations("audio"),i=this._audioChoiceMemory.get(e);if(null===i)n.adaptation$.next(null);else if(void 0!==i&&(0,ft.Z)(r,i))n.adaptation$.next(i);else{var a=mr(r,fr(this._preferredAudioTracks));this._audioChoiceMemory.set(e,a),n.adaptation$.next(a)}},t.setInitialTextTrack=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.text:null;if((0,re.Z)(n)||void 0===t)throw new Error("TrackChoiceManager: Given Period not found.");var r=e.getSupportedAdaptations("text"),i=this._textChoiceMemory.get(e);if(null===i)n.adaptation$.next(null);else if(void 0!==i&&(0,ft.Z)(r,i))n.adaptation$.next(i);else{var a=yr(r,vr(this._preferredTextTracks));this._textChoiceMemory.set(e,a),n.adaptation$.next(a)}},t.setInitialVideoTrack=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.video:null;if((0,re.Z)(n)||void 0===t)throw new Error("TrackChoiceManager: Given Period not found.");var r,i=e.getSupportedAdaptations("video"),a=this._videoChoiceMemory.get(e);if(null===a)r=null;else if(void 0!==a&&(0,ft.Z)(i,a.baseAdaptation))r=a.baseAdaptation;else{r=br(i,this._preferredVideoTracks)}if(null===r)return this._videoChoiceMemory.set(e,null),void n.adaptation$.next(null);var o=wr(r,this.trickModeTrackEnabled);this._videoChoiceMemory.set(e,{baseAdaptation:r,adaptation:o}),n.adaptation$.next(o)},t.setAudioTrackByID=function(e,t){var n=Tr(this._periods,e),r=void 0!==n?n.audio:null;if((0,re.Z)(r))throw new Error("TrackChoiceManager: Given Period not found.");var i=(0,He.Z)(r.adaptations,(function(e){return e.id===t}));if(void 0===i)throw new Error("Audio Track not found.");this._audioChoiceMemory.get(e)!==i&&(this._audioChoiceMemory.set(e,i),r.adaptation$.next(i))},t.setTextTrackByID=function(e,t){var n=Tr(this._periods,e),r=void 0!==n?n.text:null;if((0,re.Z)(r))throw new Error("TrackChoiceManager: Given Period not found.");var i=(0,He.Z)(r.adaptations,(function(e){return e.id===t}));if(void 0===i)throw new Error("Text Track not found.");this._textChoiceMemory.get(e)!==i&&(this._textChoiceMemory.set(e,i),r.adaptation$.next(i))},t.setVideoTrackByID=function(e,t){var n=Tr(this._periods,e),r=void 0!==n?n.video:null;if((0,re.Z)(r))throw new Error("LanguageManager: Given Period not found.");var i=(0,He.Z)(r.adaptations,(function(e){return e.id===t}));if(void 0===i)throw new Error("Video Track not found.");var a=wr(i,this.trickModeTrackEnabled);this._videoChoiceMemory.set(e,{baseAdaptation:i,adaptation:a}),r.adaptation$.next(a)},t.disableTextTrack=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.text:null;if((0,re.Z)(n))throw new Error("TrackChoiceManager: Given Period not found.");null!==this._textChoiceMemory.get(e)&&(this._textChoiceMemory.set(e,null),n.adaptation$.next(null))},t.disableVideoTrack=function(e){var t=Tr(this._periods,e),n=null==t?void 0:t.video;if(void 0===n)throw new Error("TrackManager: Given Period not found.");null!==this._videoChoiceMemory.get(e)&&(this._videoChoiceMemory.set(e,null),n.adaptation$.next(null))},t.disableVideoTrickModeTracks=function(){this.trickModeTrackEnabled=!1,this._resetChosenVideoTracks()},t.enableVideoTrickModeTracks=function(){this.trickModeTrackEnabled=!0,this._resetChosenVideoTracks()},t.isTrickModeEnabled=function(){return this.trickModeTrackEnabled},t.getChosenAudioTrack=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.audio:null;if((0,re.Z)(n))return null;var r=this._audioChoiceMemory.get(e);if((0,re.Z)(r))return null;var i={language:(0,ze.Z)(r.language,""),normalized:(0,ze.Z)(r.normalizedLanguage,""),audioDescription:!0===r.isAudioDescription,id:r.id,representations:r.representations.map(Sr),label:r.label};return!0===r.isDub&&(i.dub=!0),i},t.getChosenTextTrack=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.text:null;if((0,re.Z)(n))return null;var r=this._textChoiceMemory.get(e);return(0,re.Z)(r)?null:{language:(0,ze.Z)(r.language,""),normalized:(0,ze.Z)(r.normalizedLanguage,""),closedCaption:!0===r.isClosedCaption,id:r.id,label:r.label}},t.getChosenVideoTrack=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.video:null;if((0,re.Z)(n))return null;var r=this._videoChoiceMemory.get(e);if((0,re.Z)(r))return null;var i=r.adaptation,a=void 0!==i.trickModeTracks?i.trickModeTracks.map((function(e){var t=e.representations.map(Er),n={id:e.id,representations:t,isTrickModeTrack:!0};return!0===e.isSignInterpreted&&(n.signInterpreted=!0),n})):void 0,o={id:i.id,representations:i.representations.map(Er),label:i.label};return!0===i.isSignInterpreted&&(o.signInterpreted=!0),!0===i.isTrickModeTrack&&(o.isTrickModeTrack=!0),void 0!==a&&(o.trickModeTracks=a),o},t.getAvailableAudioTracks=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.audio:null;if((0,re.Z)(n))return[];var r=this._audioChoiceMemory.get(e),i=(0,re.Z)(r)?null:r.id;return n.adaptations.map((function(e){var t={language:(0,ze.Z)(e.language,""),normalized:(0,ze.Z)(e.normalizedLanguage,""),audioDescription:!0===e.isAudioDescription,id:e.id,active:null!==i&&i===e.id,representations:e.representations.map(Sr),label:e.label};return!0===e.isDub&&(t.dub=!0),t}))},t.getAvailableTextTracks=function(e){var t=Tr(this._periods,e),n=void 0!==t?t.text:null;if((0,re.Z)(n))return[];var r=this._textChoiceMemory.get(e),i=(0,re.Z)(r)?null:r.id;return n.adaptations.map((function(e){return{language:(0,ze.Z)(e.language,""),normalized:(0,ze.Z)(e.normalizedLanguage,""),closedCaption:!0===e.isClosedCaption,id:e.id,active:null!==i&&i===e.id,label:e.label}}))},t.getAvailableVideoTracks=function(e){var t,n=Tr(this._periods,e),r=void 0!==n?n.video:null;if((0,re.Z)(r))return[];var i=this._videoChoiceMemory.get(e),a=void 0===i?void 0:null!==(t=null==i?void 0:i.adaptation.id)&&void 0!==t?t:void 0;return r.adaptations.map((function(e){var t=void 0!==e.trickModeTracks?e.trickModeTracks.map((function(e){var t=null!==a&&a===e.id,n=e.representations.map(Er),r={id:e.id,representations:n,isTrickModeTrack:!0,active:t};return!0===e.isSignInterpreted&&(r.signInterpreted=!0),r})):void 0,n={id:e.id,active:null!==a&&a===e.id,representations:e.representations.map(Er),label:e.label};return!0===e.isSignInterpreted&&(n.signInterpreted=!0),void 0!==t&&(n.trickModeTracks=t),n}))},t._applyAudioPreferences=function(){this._audioChoiceMemory=new WeakMap,this._resetChosenAudioTracks()},t._applyTextPreferences=function(){this._textChoiceMemory=new WeakMap,this._resetChosenTextTracks()},t._applyVideoPreferences=function(){this._videoChoiceMemory=new WeakMap,this._resetChosenVideoTracks()},t._resetChosenAudioTracks=function(){var e=this,t=fr(this._preferredAudioTracks);!function n(r){if(!(r>=e._periods.length())){var i=e._periods.get(r);if((0,re.Z)(i.audio))n(r+1);else{var a=i.period,o=i.audio,s=a.getSupportedAdaptations("audio"),u=e._audioChoiceMemory.get(a);if(null===u||void 0!==u&&(0,ft.Z)(s,u))n(r+1);else{var l=mr(s,t);e._audioChoiceMemory.set(a,l),o.adaptation$.next(l),n(0)}}}}(0)},t._resetChosenTextTracks=function(){var e=this,t=vr(this._preferredTextTracks);!function n(r){if(!(r>=e._periods.length())){var i=e._periods.get(r);if((0,re.Z)(i.text))n(r+1);else{var a=i.period,o=i.text,s=a.getSupportedAdaptations("text"),u=e._textChoiceMemory.get(a);if(null===u||void 0!==u&&(0,ft.Z)(s,u))n(r+1);else{var l=yr(s,t);e._textChoiceMemory.set(a,l),o.adaptation$.next(l),n(0)}}}}(0)},t._resetChosenVideoTracks=function(){var e=this,t=this._preferredVideoTracks;!function n(r){if(!(r>=e._periods.length())){var i=e._periods.get(r);if((0,re.Z)(i.video))n(r+1);else{var a=i.period,o=i.video,s=a.getSupportedAdaptations("video"),u=e._videoChoiceMemory.get(a);if(null!==u){if(void 0!==u&&(0,ft.Z)(s,u.baseAdaptation)){var l=wr(u.baseAdaptation,e.trickModeTrackEnabled);return l.id===u.adaptation.id?void n(r+1):(e._videoChoiceMemory.set(a,{baseAdaptation:u.baseAdaptation,adaptation:l}),o.adaptation$.next(l),n(0))}var c=br(s,t);if(null===c)return e._videoChoiceMemory.set(a,null),o.adaptation$.next(null),n(0);var d=wr(c,e.trickModeTrackEnabled);return e._videoChoiceMemory.set(a,{baseAdaptation:c,adaptation:d}),o.adaptation$.next(d),n(0)}n(r+1)}}}(0)},e}();function hr(e){return function(t){var n;if(void 0!==e.normalized&&(null!==(n=t.normalizedLanguage)&&void 0!==n?n:"")!==e.normalized)return!1;if(void 0!==e.audioDescription)if(e.audioDescription){if(!0!==t.isAudioDescription)return!1}else if(!0===t.isAudioDescription)return!1;if(void 0===e.codec)return!0;var r=e.codec.test,i=function(e){return void 0!==e.codec&&r.test(e.codec)};return e.codec.all?t.representations.every(i):t.representations.some(i)}}function mr(e,t){if(0===e.length)return null;for(var n=0;nm)throw new Error('Invalid maxVideoBitrate parameter. Its value, "'+m+'", is inferior to the set minVideoBitrate, "'+p+'"')}if((0,re.Z)(e.maxAudioBitrate))h=T.audio;else{if(h=Number(e.maxAudioBitrate),isNaN(h))throw new Error("Invalid maxAudioBitrate parameter. Should be a number.");if(v>h)throw new Error('Invalid maxAudioBitrate parameter. Its value, "'+h+'", is inferior to the set minAudioBitrate, "'+v+'"')}return{maxBufferAhead:t,maxBufferBehind:n,limitVideoWidth:Z,videoElement:c,wantedBufferAhead:r,maxVideoBufferSize:i,throttleWhenHidden:a,throttleVideoBitrateWhenHidden:o,preferredAudioTracks:s,preferredTextTracks:u,preferredVideoTracks:l,initialAudioBitrate:f,initialVideoBitrate:d,minAudioBitrate:v,minVideoBitrate:p,maxAudioBitrate:h,maxVideoBitrate:m,stopAtEnd:(0,re.Z)(e.stopAtEnd)?k:!!e.stopAtEnd}}(e),u=r.initialAudioBitrate,l=r.initialVideoBitrate,c=r.limitVideoWidth,f=r.minAudioBitrate,v=r.minVideoBitrate,p=r.maxAudioBitrate,h=r.maxBufferAhead,m=r.maxBufferBehind,g=r.maxVideoBitrate,y=r.preferredAudioTracks,_=r.preferredTextTracks,b=r.preferredVideoTracks,T=r.throttleWhenHidden,E=r.throttleVideoBitrateWhenHidden,S=r.videoElement,w=r.wantedBufferAhead,k=r.maxVideoBufferSize,A=r.stopAtEnd,x=Y.Z.getCurrent().DEFAULT_UNMUTED_VOLUME;S.preload="auto",t.version="3.29.0",t.log=j.Z,t.state="STOPPED",t.videoElement=S;var I=new se.ZP;return t._priv_destroy$=new i.x,t._priv_destroy$.pipe((0,a.q)(1)).subscribe((function(){I.cancel()})),t._priv_pictureInPictureRef=Ar(S,I.signal),Zr(S).pipe((0,o.R)(t._priv_destroy$)).subscribe((function(){return t.trigger("fullscreenChange",t.isFullscreen())})),Rr(S.textTracks).pipe((0,o.R)(t._priv_destroy$),(0,s.U)((function(e){for(var t=e.target,n=[],r=0;r0?e.textTracks[0]:null},u.getPlayerState=function(){return this.state},u.isLive=function(){if(null===this._priv_contentInfos)return!1;var e=this._priv_contentInfos,t=e.isDirectFile,n=e.manifest;return!t&&null!==n&&n.isLive},u.areTrickModeTracksEnabled=function(){return this._priv_preferTrickModeTracks},u.getUrl=function(){if(null!==this._priv_contentInfos){var e=this._priv_contentInfos,t=e.isDirectFile,n=e.manifest,r=e.url;return t?r:null!==n?n.getUrl():void 0}},u.getVideoDuration=function(){if(null===this.videoElement)throw new Error("Disposed player");return this.videoElement.duration},u.getVideoBufferGap=function(){if(null===this.videoElement)throw new Error("Disposed player");var e=this.videoElement;return(0,ae.L7)(e.buffered,e.currentTime)},u.getVideoLoadedTime=function(){if(null===this.videoElement)throw new Error("Disposed player");var e=this.videoElement;return(0,ae.at)(e.buffered,e.currentTime)},u.getVideoPlayedTime=function(){if(null===this.videoElement)throw new Error("Disposed player");var e=this.videoElement;return(0,ae.DD)(e.buffered,e.currentTime)},u.getWallClockTime=function(){if(null===this.videoElement)throw new Error("Disposed player");if(null===this._priv_contentInfos)return this.videoElement.currentTime;var e=this._priv_contentInfos,t=e.isDirectFile,n=e.manifest;if(t){var r=K(this.videoElement);return(null!=r?r:0)+this.videoElement.currentTime}return null!==n?this.videoElement.currentTime+(void 0!==n.availabilityStartTime?n.availabilityStartTime:0):0},u.getPosition=function(){if(null===this.videoElement)throw new Error("Disposed player");return this.videoElement.currentTime},u.getPlaybackRate=function(){return this._priv_speed.getValue()},u.setPlaybackRate=function(e,t){e!==this._priv_speed.getValue()&&this._priv_speed.setValue(e);var n=null==t?void 0:t.preferTrickModeTracks;"boolean"==typeof n&&(this._priv_preferTrickModeTracks=n,null!==this._priv_trackChoiceManager&&(n&&!this._priv_trackChoiceManager.isTrickModeEnabled()?this._priv_trackChoiceManager.enableVideoTrickModeTracks():!n&&this._priv_trackChoiceManager.isTrickModeEnabled()&&this._priv_trackChoiceManager.disableVideoTrickModeTracks()))},u.getAvailableVideoBitrates=function(){if(null===this._priv_contentInfos)return[];var e=this._priv_contentInfos,t=e.currentPeriod,n=e.activeAdaptations;if(null===t||null===n)return[];var r=n[t.id];return void 0===r||(0,re.Z)(r.video)?[]:r.video.getAvailableBitrates()},u.getAvailableAudioBitrates=function(){if(null===this._priv_contentInfos)return[];var e=this._priv_contentInfos,t=e.currentPeriod,n=e.activeAdaptations;if(null===t||null===n)return[];var r=n[t.id];return void 0===r||(0,re.Z)(r.audio)?[]:r.audio.getAvailableBitrates()},u.getManualAudioBitrate=function(){return this._priv_bitrateInfos.manualBitrates.audio.getValue()},u.getManualVideoBitrate=function(){return this._priv_bitrateInfos.manualBitrates.video.getValue()},u.getVideoBitrate=function(){var e=this._priv_getCurrentRepresentations();if(null!==e&&!(0,re.Z)(e.video))return e.video.bitrate},u.getAudioBitrate=function(){var e=this._priv_getCurrentRepresentations();if(null!==e&&!(0,re.Z)(e.audio))return e.audio.bitrate},u.getMinVideoBitrate=function(){return this._priv_bitrateInfos.minAutoBitrates.video.getValue()},u.getMinAudioBitrate=function(){return this._priv_bitrateInfos.minAutoBitrates.audio.getValue()},u.getMaxVideoBitrate=function(){return this._priv_bitrateInfos.maxAutoBitrates.video.getValue()},u.getMaxAudioBitrate=function(){return this._priv_bitrateInfos.maxAutoBitrates.audio.getValue()},u.play=function(){var e=this;if(null===this.videoElement)throw new Error("Disposed player");var t=this.videoElement.play();return(0,re.Z)(t)||"function"!=typeof t.catch?Promise.resolve():t.catch((function(t){if("NotAllowedError"===t.name){var n=new X.Z("MEDIA_ERR_PLAY_NOT_ALLOWED",t.toString());e.trigger("warning",n)}throw t}))},u.pause=function(){if(null===this.videoElement)throw new Error("Disposed player");this.videoElement.pause()},u.seekTo=function(e){var t;if(null===this.videoElement)throw new Error("Disposed player");if(null===this._priv_contentInfos)throw new Error("player: no content loaded");var n,r=this._priv_contentInfos,i=r.isDirectFile,a=r.manifest;if(!i&&null===a)throw new Error("player: the content did not load yet");if("number"==typeof e)n=e;else if("object"==typeof e){var o=e,s=this.videoElement.currentTime;if((0,re.Z)(o.relative))if((0,re.Z)(o.position)){if((0,re.Z)(o.wallClockTime))throw new Error('invalid time object. You must set one of the following properties: "relative", "position" or "wallClockTime"');if(null!==a)n=o.wallClockTime-(null!==(t=a.availabilityStartTime)&&void 0!==t?t:0);else if(i&&null!==this.videoElement){var u=K(this.videoElement);void 0!==u&&(n=o.wallClockTime-u)}void 0===n&&(n=o.wallClockTime)}else n=o.position;else n=s+o.relative}if(void 0===n)throw new Error("invalid time given");return this.videoElement.currentTime=n,n},u.isFullscreen=function(){return(0,ue.Z)("isFullscreen is deprecated. Fullscreen management should now be managed by the application"),H()},u.setFullscreen=function(e){if(void 0===e&&(e=!0),(0,ue.Z)("setFullscreen is deprecated. Fullscreen management should now be managed by the application"),null===this.videoElement)throw new Error("Disposed player");e?function(e){if(!H()){var t=e;"function"==typeof t.requestFullscreen?t.requestFullscreen():"function"==typeof t.msRequestFullscreen?t.msRequestFullscreen():"function"==typeof t.mozRequestFullScreen?t.mozRequestFullScreen():"function"==typeof t.webkitRequestFullscreen&&t.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}}(this.videoElement):G()},u.exitFullscreen=function(){(0,ue.Z)("exitFullscreen is deprecated. Fullscreen management should now be managed by the application"),G()},u.getVolume=function(){if(null===this.videoElement)throw new Error("Disposed player");return this.videoElement.volume},u.setVolume=function(e){if(null===this.videoElement)throw new Error("Disposed player");var t=this.videoElement;e!==t.volume&&(t.volume=e,this.trigger("volumeChange",e))},u.isMute=function(){return 0===this.getVolume()},u.mute=function(){this._priv_mutedMemory=this.getVolume(),this.setVolume(0)},u.unMute=function(){var e=Y.Z.getCurrent().DEFAULT_UNMUTED_VOLUME;0===this.getVolume()&&this.setVolume(0===this._priv_mutedMemory?e:this._priv_mutedMemory)},u.setVideoBitrate=function(e){this._priv_bitrateInfos.manualBitrates.video.setValue(e)},u.setAudioBitrate=function(e){this._priv_bitrateInfos.manualBitrates.audio.setValue(e)},u.setMinVideoBitrate=function(e){var t=this._priv_bitrateInfos.maxAutoBitrates.video.getValue();if(e>t)throw new Error('Invalid minimum video bitrate given. Its value, "'+e+'" is superior the current maximum video birate, "'+t+'".');this._priv_bitrateInfos.minAutoBitrates.video.setValue(e)},u.setMinAudioBitrate=function(e){var t=this._priv_bitrateInfos.maxAutoBitrates.audio.getValue();if(e>t)throw new Error('Invalid minimum audio bitrate given. Its value, "'+e+'" is superior the current maximum audio birate, "'+t+'".');this._priv_bitrateInfos.minAutoBitrates.audio.setValue(e)},u.setMaxVideoBitrate=function(e){var t=this._priv_bitrateInfos.minAutoBitrates.video.getValue();if(e0?r.next(i[0]):r.next(null)}},u._priv_onPeriodStreamCleared=function(e){var t=e.type,n=e.period;switch(t){case"audio":case"text":case"video":null!==this._priv_trackChoiceManager&&this._priv_trackChoiceManager.removePeriod(t,n)}if(null!==this._priv_contentInfos){var r=this._priv_contentInfos,i=r.activeAdaptations,a=r.activeRepresentations;if(!(0,re.Z)(i)&&!(0,re.Z)(i[n.id])){var o=i[n.id];delete o[t],0===Object.keys(o).length&&delete i[n.id]}if(!(0,re.Z)(a)&&!(0,re.Z)(a[n.id])){var s=a[n.id];delete s[t],0===Object.keys(s).length&&delete a[n.id]}}},u._priv_onReloadingMediaSource=function(){null!==this._priv_contentInfos&&(this._priv_contentInfos.segmentBuffersStore=null),null!==this._priv_trackChoiceManager&&this._priv_trackChoiceManager.resetPeriods()},u._priv_onAdaptationChange=function(e){var t=e.type,n=e.adaptation,r=e.period;if(null!==this._priv_contentInfos){null===this._priv_contentInfos.activeAdaptations&&(this._priv_contentInfos.activeAdaptations={});var i,a=this._priv_contentInfos,o=a.activeAdaptations,s=a.currentPeriod,u=o[r.id];if((0,re.Z)(u))o[r.id]=((i={})[t]=n,i);else u[t]=n;if(null!==this._priv_trackChoiceManager&&null!==s&&!(0,re.Z)(r)&&r.id===s.id)switch(t){case"audio":var l=this._priv_trackChoiceManager.getChosenAudioTrack(s);this.trigger("audioTrackChange",l);var c=this.getAvailableAudioBitrates();this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange",c);break;case"text":var d=this._priv_trackChoiceManager.getChosenTextTrack(s);this.trigger("textTrackChange",d);break;case"video":var f=this._priv_trackChoiceManager.getChosenVideoTrack(s);this.trigger("videoTrackChange",f);var v=this.getAvailableVideoBitrates();this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange",v)}}else j.Z.error("API: The adaptations changed but no content is loaded")},u._priv_onRepresentationChange=function(e){var t,n=e.type,r=e.period,i=e.representation;if(null!==this._priv_contentInfos){null===this._priv_contentInfos.activeRepresentations&&(this._priv_contentInfos.activeRepresentations={});var a,o=this._priv_contentInfos,s=o.activeRepresentations,u=o.currentPeriod,l=s[r.id];if((0,re.Z)(l))s[r.id]=((a={})[n]=i,a);else l[n]=i;var c=null!==(t=null==i?void 0:i.bitrate)&&void 0!==t?t:-1;(0,re.Z)(r)||null===u||u.id!==r.id||("video"===n?this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange",c):"audio"===n&&this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange",c))}else j.Z.error("API: The representations changed but no content is loaded")},u._priv_onBitrateEstimationChange=function(e){var t=e.type,n=e.bitrate;void 0!==n&&(this._priv_bitrateInfos.lastBitrates[t]=n),this.trigger("bitrateEstimationChange",{type:t,bitrate:n})},u._priv_onNativeTextTracksNext=function(e){this.trigger("nativeTextTracksChange",e)},u._priv_setPlayerState=function(e){this.state!==e&&(this.state=e,j.Z.info("API: playerStateChange event",e),this.trigger("playerStateChange",e))},u._priv_triggerPositionUpdate=function(e){var t;if(null!==this._priv_contentInfos){var n=this._priv_contentInfos,r=n.isDirectFile,i=n.manifest;if((r||null!==i)&&!(0,re.Z)(e)){var a=null!==i?i.getMaximumSafePosition():void 0,o={position:e.position,duration:e.duration,playbackRate:e.playbackRate,maximumBufferTime:a,bufferGap:void 0!==e.bufferGap&&isFinite(e.bufferGap)?e.bufferGap:0};if(null!==i&&i.isLive&&e.position>0){var s=null!==(t=i.availabilityStartTime)&&void 0!==t?t:0;o.wallClockTime=e.position+s;var u=i.getLivePosition();void 0!==u&&(o.liveGap=u-e.position)}else if(r&&null!==this.videoElement){var l=K(this.videoElement);void 0!==l&&(o.wallClockTime=l+e.position)}this.trigger("positionUpdate",o)}}else j.Z.warn("API: Cannot perform time update: no content loaded.")},u._priv_triggerAvailableBitratesChangeEvent=function(e,t){var n=this._priv_contentEventsMemory[e];void 0!==n&&(0,te.Z)(t,n)||(this._priv_contentEventsMemory[e]=t,this.trigger(e,t))},u._priv_triggerCurrentBitrateChangeEvent=function(e,t){t!==this._priv_contentEventsMemory[e]&&(this._priv_contentEventsMemory[e]=t,this.trigger(e,t))},u._priv_getCurrentRepresentations=function(){if(null===this._priv_contentInfos)return null;var e=this._priv_contentInfos,t=e.currentPeriod,n=e.activeRepresentations;return null===t||null===n||(0,re.Z)(n[t.id])?null:n[t.id]},(0,e.Z)(r,null,[{key:"ErrorTypes",get:function(){return Q.ZB}},{key:"ErrorCodes",get:function(){return Q.SM}},{key:"LogLevel",get:function(){return j.Z.getLevel()},set:function(e){j.Z.setLevel(e)}}]),r}(ne.Z);Mr.version="3.29.0";var Cr=Mr,Pr=n(7273);!function(){Pr.Z.ContentDecryptor=n(1266).ZP,Pr.Z.imageBuffer=n(7127).Z,Pr.Z.imageParser=n(3203).Z,Pr.Z.transports.smooth=n(2339).Z,Pr.Z.transports.dash=n(85).Z,Pr.Z.dashParsers.js=n(4541).Z,Pr.Z.nativeTextTracksBuffer=n(9059).Z,Pr.Z.nativeTextTracksParsers.vtt=n(9405).Z,Pr.Z.nativeTextTracksParsers.ttml=n(1570).Z,Pr.Z.nativeTextTracksParsers.sami=n(1812).Z,Pr.Z.nativeTextTracksParsers.srt=n(8057).Z,Pr.Z.htmlTextTracksBuffer=n(5192).Z,Pr.Z.htmlTextTracksParsers.sami=n(5734).Z,Pr.Z.htmlTextTracksParsers.ttml=n(7439).Z,Pr.Z.htmlTextTracksParsers.srt=n(8675).Z,Pr.Z.htmlTextTracksParsers.vtt=n(4099).Z;var e=n(8969).Z,t=n(6796).Z;Pr.Z.directfile={initDirectFile:e,mediaElementTrackChoiceManager:t}}(),"boolean"==typeof __RX_PLAYER_DEBUG_MODE__&&__RX_PLAYER_DEBUG_MODE__&&j.Z.setLevel("DEBUG");var Dr=Cr}(),r=r.default}()})); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.RxPlayer=t():e.RxPlayer=t()}(self,(function(){return function(){var e={3774:function(e,t,n){"use strict";n.d(t,{J:function(){return a},c:function(){return o}});var r=n(1946),i=n(2203).Z?void 0:window,a=void 0===i?void 0:(0,r.Z)(i.MediaSource)?(0,r.Z)(i.MozMediaSource)?(0,r.Z)(i.WebKitMediaSource)?i.MSMediaSource:i.WebKitMediaSource:i.MozMediaSource:i.MediaSource,o={HAVE_NOTHING:0,HAVE_METADATA:1,HAVE_CURRENT_DATA:2,HAVE_FUTURE_DATA:3,HAVE_ENOUGH_DATA:4}},3666:function(e,t,n){"use strict";n.d(t,{$u:function(){return p},SB:function(){return c},YM:function(){return u},fq:function(){return s},kD:function(){return o},l_:function(){return h},op:function(){return f},vS:function(){return d},vU:function(){return l},yS:function(){return v}});var r,i,a=n(2203),o=!1,s=!1,u=!1,l=!1,d=!1,c=!1,f=!1,v=!1,p=!1,h=!1;a.Z||(void 0!==window.MSInputMethodContext&&void 0!==document.documentMode?(s=!0,u=!0):"Microsoft Internet Explorer"===navigator.appName||"Netscape"===navigator.appName&&/(Trident|Edge)\//.test(navigator.userAgent)?u=!0:-1!==navigator.userAgent.toLowerCase().indexOf("edg/")?o=!0:-1!==navigator.userAgent.toLowerCase().indexOf("firefox")?l=!0:"string"==typeof navigator.platform&&/iPad|iPhone|iPod/.test(navigator.platform)?c=!0:(Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>=0||"[object SafariRemoteNotification]"===(null===(i=null===(r=window.safari)||void 0===r?void 0:r.pushNotification)||void 0===i?void 0:i.toString()))&&(d=!0),/SamsungBrowser/.test(navigator.userAgent)&&(f=!0),/Tizen/.test(navigator.userAgent)?v=!0:/[Ww]eb[O0]S/.test(navigator.userAgent)?(p=!0,/[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent)||/[Cc]hr[o0]me\/87/.test(navigator.userAgent)||(/[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent)||/[Cc]hr[o0]me\/79/.test(navigator.userAgent))):/[Pp]anasonic/.test(navigator.userAgent)&&(h=!0))},5767:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(3887),i=n(1946);function a(e){var t=e.textTracks;if(!(0,i.Z)(t)){for(var n=0;n=0;o--)if("track"===a[o].nodeName)try{e.removeChild(a[o])}catch(e){r.Z.warn("Compat: Could not remove text track child from element.")}}e.src="",e.removeAttribute("src")}},6139:function(e,t,n){"use strict";n.d(t,{N:function(){return M},Y:function(){return C}});var r,i=n(3714),a=n(811),o=n(3666),s=n(2203),u=n(5059),l=n(3144),d=function(){function e(e,t,n){this._keyType=e,this._mediaKeys=t,this._configuration=n}var t=e.prototype;return t.createMediaKeys=function(){var e=this;return new Promise((function(t){return t(e._mediaKeys)}))},t.getConfiguration=function(){return this._configuration},(0,l.Z)(e,[{key:"keySystem",get:function(){return this._keyType}}]),e}();if(!s.Z){var c=window.MSMediaKeys;void 0!==c&&void 0!==c.prototype&&"function"==typeof c.isTypeSupported&&"function"==typeof c.prototype.createSession&&(r=c)}var f,v=n(4578),p=n(1959),h=n(288),m=n(3038),g=function(e){function t(t){var n;return(n=e.call(this)||this).expiration=NaN,n.keyStatuses=new Map,n._mk=t,n._sessionClosingCanceller=new h.ZP,n.closed=new Promise((function(e){n._sessionClosingCanceller.signal.register((function(){return e()}))})),n.update=function(e){return new Promise((function(t,r){if(void 0===n._ss)return r("MediaKeySession not set.");try{t(n._ss.update(e,""))}catch(e){r(e)}}))},n}(0,v.Z)(t,e);var n=t.prototype;return n.generateRequest=function(e,t){var n=this;return new Promise((function(e){var r=t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer);n._ss=n._mk.createSession("video/mp4",r),m.RV(n._ss,(function(e){var t;n.trigger(null!==(t=e.type)&&void 0!==t?t:"message",e)}),n._sessionClosingCanceller.signal),m.kk(n._ss,(function(e){var t;n.trigger(null!==(t=e.type)&&void 0!==t?t:"keyadded",e)}),n._sessionClosingCanceller.signal),m.Dl(n._ss,(function(e){var t;n.trigger(null!==(t=e.type)&&void 0!==t?t:"keyerror",e)}),n._sessionClosingCanceller.signal),e()}))},n.close=function(){var e=this;return new Promise((function(t){null!=e._ss&&(e._ss.close(),e._ss=void 0),e._sessionClosingCanceller.cancel(),t()}))},n.load=function(){return Promise.resolve(!1)},n.remove=function(){return Promise.resolve()},(0,l.Z)(t,[{key:"sessionId",get:function(){var e,t;return null!==(t=null===(e=this._ss)||void 0===e?void 0:e.sessionId)&&void 0!==t?t:""}}]),t}(p.Z),y=function(){function e(e){if(void 0===r)throw new Error("No MSMediaKeys API.");this._mediaKeys=new r(e)}var t=e.prototype;return t._setVideo=function(e){if(this._videoElement=e,void 0!==this._videoElement.msSetMediaKeys)return this._videoElement.msSetMediaKeys(this._mediaKeys)},t.createSession=function(){if(void 0===this._videoElement||void 0===this._mediaKeys)throw new Error("Video not attached to the MediaKeys");return new g(this._mediaKeys)},t.setServerCertificate=function(){throw new Error("Server certificate is not implemented in your browser")},e}();if(!s.Z){var _=window.MozMediaKeys;void 0!==_&&void 0!==_.prototype&&"function"==typeof _.isTypeSupported&&"function"==typeof _.prototype.createSession&&(f=_)}var b=n(9689),S=n(8894),T=n(3635);function E(e){return"function"==typeof e.webkitGenerateKeyRequest}var k=function(e){function t(t,n){var r;(r=e.call(this)||this)._vid=t,r._key=n,r.sessionId="",r._closeSession=S.Z,r.keyStatuses=new Map,r.expiration=NaN;var i=function(e){r.trigger(e.type,e)};return r.closed=new Promise((function(e){r._closeSession=function(){["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(e){t.removeEventListener(e,i),t.removeEventListener("webkit"+e,i)})),e()}})),["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(e){t.addEventListener(e,i),t.addEventListener("webkit"+e,i)})),r}(0,v.Z)(t,e);var n=t.prototype;return n.update=function(e){var t=this;return new Promise((function(n,r){try{if(t._key.indexOf("clearkey")>=0){var i=e instanceof ArrayBuffer?new Uint8Array(e):e,a=JSON.parse((0,T.uR)(i)),o=(0,b.K)(a.keys[0].k),s=(0,b.K)(a.keys[0].kid);n(t._vid.webkitAddKey(t._key,o,s,""))}else n(t._vid.webkitAddKey(t._key,e,null,""))}catch(e){r(e)}}))},n.generateRequest=function(e,t){var n=this;return new Promise((function(e){n._vid.webkitGenerateKeyRequest(n._key,t),e()}))},n.close=function(){var e=this;return new Promise((function(t){e._closeSession(),t()}))},n.load=function(){return Promise.resolve(!1)},n.remove=function(){return Promise.resolve()},t}(p.Z),w=function(){function e(e){this._keySystem=e}var t=e.prototype;return t._setVideo=function(e){if(!E(e))throw new Error("Video not attached to the MediaKeys");this._videoElement=e},t.createSession=function(){if(null==this._videoElement)throw new Error("Video not attached to the MediaKeys");return new k(this._videoElement,this._keySystem)},t.setServerCertificate=function(){throw new Error("Server certificate is not implemented in your browser")},e}();var A=n(6968);var I=n(158);function Z(e,t){var n=e;if(void 0===n.webkitSetMediaKeys)throw new Error("No webKitMediaKeys API.");return n.webkitSetMediaKeys(t)}var x=function(e){function t(t,n,r){var i;return(i=e.call(this)||this)._serverCertificate=r,i._videoElement=t,i._keyType=n,i._unbindSession=S.Z,i._closeSession=S.Z,i.closed=new Promise((function(e){i._closeSession=e})),i.keyStatuses=new Map,i.expiration=NaN,i}(0,v.Z)(t,e);var n=t.prototype;return n.update=function(e){var t=this;return new Promise((function(n,r){if(void 0===t._nativeSession||void 0===t._nativeSession.update||"function"!=typeof t._nativeSession.update)return r("Unavailable WebKit key session.");try{var i;i=e instanceof ArrayBuffer?new Uint8Array(e):e instanceof Uint8Array?e:new Uint8Array(e.buffer),n(t._nativeSession.update(i))}catch(e){r(e)}}))},n.generateRequest=function(e,t){var n=this;return new Promise((function(e){var r,i,a,o=n._videoElement;if(void 0===(null===(r=o.webkitKeys)||void 0===r?void 0:r.createSession))throw new Error("No WebKitMediaKeys API.");if("com.apple.fps.1_0"===(a=n._keyType)||"com.apple.fps.2_0"===a){if(void 0===n._serverCertificate)throw new Error("A server certificate is needed for creating fairplay session.");i=function(e,t){var n=e instanceof Uint8Array?e:new Uint8Array(e),r=t instanceof Uint8Array?t:new Uint8Array(t);if((0,A.dN)(n,0)+4!==n.length)throw new Error("Unsupported WebKit initData.");var i=(0,T.wV)(n),a=i.indexOf("skd://"),o=a>-1?i.substring(a+6):i,s=(0,T.TZ)(o),u=0,l=new Uint8Array(n.byteLength+4+s.byteLength+4+r.byteLength);return l.set(n),u+=n.length,l.set((0,A.O_)(s.byteLength),u),u+=4,l.set(s,u),u+=s.byteLength,l.set((0,A.O_)(r.byteLength),u),u+=4,l.set(r,u),l}(t,n._serverCertificate)}else i=t;var s=o.webkitKeys.createSession("video/mp4",i);if(null==s)throw new Error("Impossible to get the key sessions");n._listenEvent(s),n._nativeSession=s,e()}))},n.close=function(){var e=this;return new Promise((function(t,n){e._unbindSession(),e._closeSession(),void 0!==e._nativeSession?(e._nativeSession.close(),t()):n("No session to close.")}))},n.load=function(){return Promise.resolve(!1)},n.remove=function(){return Promise.resolve()},n._listenEvent=function(e){var t=this;this._unbindSession();var n=function(e){t.trigger(e.type,e)};["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(t){e.addEventListener(t,n),e.addEventListener("webkit"+t,n)})),this._unbindSession=function(){["keymessage","message","keyadded","ready","keyerror","error"].forEach((function(t){e.removeEventListener(t,n),e.removeEventListener("webkit"+t,n)}))}},(0,l.Z)(t,[{key:"sessionId",get:function(){var e,t;return null!==(t=null===(e=this._nativeSession)||void 0===e?void 0:e.sessionId)&&void 0!==t?t:""}}]),t}(p.Z),R=function(){function e(e){if(void 0===I.t)throw new Error("No WebKitMediaKeys API.");this._keyType=e,this._mediaKeys=new I.t(e)}var t=e.prototype;return t._setVideo=function(e){if(this._videoElement=e,void 0===this._videoElement)throw new Error("Video not attached to the MediaKeys");return Z(this._videoElement,this._mediaKeys)},t.createSession=function(){if(void 0===this._videoElement||void 0===this._mediaKeys)throw new Error("Video not attached to the MediaKeys");return new x(this._videoElement,this._keyType,this._serverCertificate)},t.setServerCertificate=function(e){return this._serverCertificate=e,Promise.resolve()},e}();var M=null,C=function(e,t){var n=e;return"function"==typeof n.setMediaKeys?n.setMediaKeys(t):"function"==typeof n.webkitSetMediaKeys?n.webkitSetMediaKeys(t):"function"==typeof n.mozSetMediaKeys?n.mozSetMediaKeys(t):"function"==typeof n.msSetMediaKeys&&null!==t?n.msSetMediaKeys(t):void 0};if(s.Z||null!=navigator.requestMediaKeySystemAccess&&!(0,u.Z)())M=function(){var e;return(e=navigator).requestMediaKeySystemAccess.apply(e,arguments)};else{var P,D;if(E(HTMLVideoElement.prototype)){var N={isTypeSupported:function(e){var t=document.querySelector("video");return null==t&&(t=document.createElement("video")),null!=t&&"function"==typeof t.canPlayType&&!!t.canPlayType("video/mp4",e)},createCustomMediaKeys:function(e){return new w(e)},setMediaKeys:function(e,t){if(null!==t){if(!(t instanceof w))throw new Error("Custom setMediaKeys is supposed to be called with old webkit custom MediaKeys.");return t._setVideo(e)}}};P=N.isTypeSupported,D=N.createCustomMediaKeys,C=N.setMediaKeys}else if(void 0!==I.t){var O=function(){if(void 0===I.t)throw new Error("No WebKitMediaKeys API.");return{isTypeSupported:I.t.isTypeSupported,createCustomMediaKeys:function(e){return new R(e)},setMediaKeys:function(e,t){if(null===t)return Z(e,t);if(!(t instanceof R))throw new Error("Custom setMediaKeys is supposed to be called with webkit custom MediaKeys.");return t._setVideo(e)}}}();P=O.isTypeSupported,D=O.createCustomMediaKeys,C=O.setMediaKeys}else if(o.fq&&void 0!==r){var L={isTypeSupported:function(e,t){if(void 0===r)throw new Error("No MSMediaKeys API.");return void 0!==t?r.isTypeSupported(e,t):r.isTypeSupported(e)},createCustomMediaKeys:function(e){return new y(e)},setMediaKeys:function(e,t){if(null!==t){if(!(t instanceof y))throw new Error("Custom setMediaKeys is supposed to be called with IE11 custom MediaKeys.");return t._setVideo(e)}}};P=L.isTypeSupported,D=L.createCustomMediaKeys,C=L.setMediaKeys}else if(void 0!==f){var U={isTypeSupported:function(e,t){if(void 0===f)throw new Error("No MozMediaKeys API.");return void 0!==t?f.isTypeSupported(e,t):f.isTypeSupported(e)},createCustomMediaKeys:function(e){if(void 0===f)throw new Error("No MozMediaKeys API.");return new f(e)},setMediaKeys:function(e,t){var n=e;if(void 0===n.mozSetMediaKeys||"function"!=typeof n.mozSetMediaKeys)throw new Error("Can't set video on MozMediaKeys.");return n.mozSetMediaKeys(t)}};P=U.isTypeSupported,D=U.createCustomMediaKeys,C=U.setMediaKeys}else{var B=window.MediaKeys,F=function(){if(void 0===B)throw new i.Z("MEDIA_KEYS_NOT_SUPPORTED","No `MediaKeys` implementation found in the current browser.");if(void 0===B.isTypeSupported){throw new Error("This browser seems to be unable to play encrypted contents currently. Note: Some browsers do not allow decryption in some situations, like when not using HTTPS.")}};P=function(e){return F(),(0,a.Z)("function"==typeof B.isTypeSupported),B.isTypeSupported(e)},D=function(e){return F(),new B(e)}}M=function(e,t){if(!P(e))return Promise.reject(new Error("Unsupported key type"));for(var n=0;n=t)return r.Z.warn("Compat: Invalid cue times: "+e+" - "+t),null;if((0,i.Z)(window.VTTCue)){if((0,i.Z)(window.TextTrackCue))throw new Error("VTT cues not supported in your target");return new TextTrackCue(e,t,n)}return new VTTCue(e,t,n)}},5059:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(3666),i=n(158);function a(){return(r.vS||r.SB)&&void 0!==i.t}},1669:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(3666);function i(){return r.op}},6872:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r={DEFAULT_UNMUTED_VOLUME:.1,DEFAULT_REQUEST_TIMEOUT:3e4,DEFAULT_TEXT_TRACK_MODE:"native",DEFAULT_MANUAL_BITRATE_SWITCHING_MODE:"seamless",DEFAULT_ENABLE_FAST_SWITCHING:!0,DEFAULT_AUDIO_TRACK_SWITCHING_MODE:"seamless",DELTA_POSITION_AFTER_RELOAD:{bitrateSwitch:-.1,trackSwitch:{audio:-.7,video:-.1,other:0}},DEFAULT_CODEC_SWITCHING_BEHAVIOR:"continue",DEFAULT_AUTO_PLAY:!1,DEFAULT_SHOW_NATIVE_SUBTITLE:!0,DEFAULT_STOP_AT_END:!0,DEFAULT_WANTED_BUFFER_AHEAD:30,DEFAULT_MAX_BUFFER_AHEAD:1/0,DEFAULT_MAX_BUFFER_BEHIND:1/0,DEFAULT_MAX_VIDEO_BUFFER_SIZE:1/0,MAXIMUM_MAX_BUFFER_AHEAD:{text:18e3},MAXIMUM_MAX_BUFFER_BEHIND:{text:18e3},DEFAULT_INITIAL_BITRATES:{audio:0,video:0,other:0},DEFAULT_MIN_BITRATES:{audio:0,video:0,other:0},DEFAULT_MAX_BITRATES:{audio:1/0,video:1/0,other:1/0},INACTIVITY_DELAY:6e4,DEFAULT_THROTTLE_WHEN_HIDDEN:!1,DEFAULT_THROTTLE_VIDEO_BITRATE_WHEN_HIDDEN:!1,DEFAULT_LIMIT_VIDEO_WIDTH:!1,DEFAULT_LIVE_GAP:{DEFAULT:10,LOW_LATENCY:3.5},BUFFER_DISCONTINUITY_THRESHOLD:.2,FORCE_DISCONTINUITY_SEEK_DELAY:5e3,BITRATE_REBUFFERING_RATIO:1.5,BUFFER_GC_GAPS:{CALM:240,BEEFY:30},DEFAULT_MAX_MANIFEST_REQUEST_RETRY:4,DEFAULT_CDN_DOWNGRADE_TIME:60,DEFAULT_MAX_REQUESTS_RETRY_ON_ERROR:4,DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE:1/0,INITIAL_BACKOFF_DELAY_BASE:{REGULAR:200,LOW_LATENCY:50},MAX_BACKOFF_DELAY_BASE:{REGULAR:3e3,LOW_LATENCY:1e3},SAMPLING_INTERVAL_MEDIASOURCE:1e3,SAMPLING_INTERVAL_LOW_LATENCY:250,SAMPLING_INTERVAL_NO_MEDIASOURCE:500,ABR_MINIMUM_TOTAL_BYTES:15e4,ABR_MINIMUM_CHUNK_SIZE:16e3,ABR_STARVATION_FACTOR:{DEFAULT:.72,LOW_LATENCY:.72},ABR_REGULAR_FACTOR:{DEFAULT:.8,LOW_LATENCY:.8},ABR_STARVATION_GAP:{DEFAULT:5,LOW_LATENCY:5},OUT_OF_STARVATION_GAP:{DEFAULT:7,LOW_LATENCY:7},ABR_STARVATION_DURATION_DELTA:.1,ABR_FAST_EMA:2,ABR_SLOW_EMA:10,RESUME_GAP_AFTER_SEEKING:{DEFAULT:1.5,LOW_LATENCY:.5},RESUME_GAP_AFTER_NOT_ENOUGH_DATA:{DEFAULT:.5,LOW_LATENCY:.5},RESUME_GAP_AFTER_BUFFERING:{DEFAULT:5,LOW_LATENCY:.5},REBUFFERING_GAP:{DEFAULT:.5,LOW_LATENCY:.2},MINIMUM_BUFFER_AMOUNT_BEFORE_FREEZING:2,UNFREEZING_SEEK_DELAY:6e3,FREEZING_STALLED_DELAY:600,UNFREEZING_DELTA_POSITION:.001,MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT:.15,MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE:.4,MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE:.3,MINIMUM_SEGMENT_SIZE:.005,APPEND_WINDOW_SECURITIES:{START:.2,END:.1},MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL:50,TEXT_TRACK_SIZE_CHECKS_INTERVAL:250,BUFFER_PADDING:{audio:1,video:3,other:1},SEGMENT_PRIORITIES_STEPS:[2,4,8,12,18,25],MAX_HIGH_PRIORITY_LEVEL:1,MIN_CANCELABLE_PRIORITY:3,EME_DEFAULT_VIDEO_CODECS:['video/mp4;codecs="avc1.4d401e"','video/mp4;codecs="avc1.42e01e"','video/webm;codecs="vp8"'],EME_DEFAULT_AUDIO_CODECS:['audio/mp4;codecs="mp4a.40.2"',"audio/webm;codecs=opus"],EME_DEFAULT_WIDEVINE_ROBUSTNESSES:["HW_SECURE_ALL","HW_SECURE_DECODE","HW_SECURE_CRYPTO","SW_SECURE_DECODE","SW_SECURE_CRYPTO"],EME_DEFAULT_PLAYREADY_ROBUSTNESSES:["3000","2000"],EME_KEY_SYSTEMS:{clearkey:["webkit-org.w3.clearkey","org.w3.clearkey"],widevine:["com.widevine.alpha"],playready:["com.microsoft.playready.recommendation","com.microsoft.playready","com.chromecast.playready","com.youtube.playready"],fairplay:["com.apple.fps.1_0"]},MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE:10,MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE:200,MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY:300,OUT_OF_SYNC_MANIFEST_REFRESH_DELAY:3e3,FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY:3e3,DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0:3,EME_DEFAULT_MAX_SIMULTANEOUS_MEDIA_KEY_SESSIONS:15,EME_MAX_STORED_PERSISTENT_SESSION_INFORMATION:1e3,EME_WAITING_DELAY_LOADED_SESSION_EMPTY_KEYSTATUSES:100,FORCED_ENDED_THRESHOLD:8e-4,ADAPTATION_SWITCH_BUFFER_PADDINGS:{video:{before:5,after:5},audio:{before:2,after:2.5},text:{before:0,after:0},image:{before:0,after:0}},SOURCE_BUFFER_FLUSHING_INTERVAL:500,CONTENT_REPLACEMENT_PADDING:1.2,CACHE_LOAD_DURATION_THRESHOLDS:{video:50,audio:10},STREAM_EVENT_EMITTER_POLL_INTERVAL:250,DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR:.001,BUFFERED_HISTORY_RETENTION_TIME:6e4,BUFFERED_HISTORY_MAXIMUM_ENTRIES:200,MIN_BUFFER_AHEAD:5,UPTO_CURRENT_POSITION_CLEANUP:5},i=n(8026);function a(e){return null!=e&&!Array.isArray(e)&&"object"==typeof e}function o(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r=e.length||(e[t].enabled=!0)}(this._audioTracks.map((function(e){return e.nativeTrack})),e)},t}(a.Z);function f(e){for(var t=0;te.length)return u.Z.warn("Compat: Unrecognized initialization data. Use as is."),[{systemId:void 0,data:e}];var i=e.subarray(n,n+r),a={systemId:(0,l.Y)(i,8),data:i};p(t,a)?u.Z.warn("Compat: Duplicated PSSH found in initialization data, removing it."):t.push(a),n+=r}return n!==e.length?(u.Z.warn("Compat: Unrecognized initialization data. Use as is."),[{systemId:void 0,data:e}]):t}(new Uint8Array(t));return{type:n,values:r}}var m=n(6872),g=n(5157),y=n(5389),_=n(3274),b=n(7714),S=n(1959),T=n(1946),E=n(288),k=n(6139),w=n(770);function A(e){w.Z.setState(e,null),(0,k.Y)(e,null)}function I(){return(I=(0,r.Z)(o().mark((function e(t,n,r){var i,a,s,l,d,c;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return i=n.keySystemOptions,a=n.loadedSessionsStore,s=n.mediaKeySystemAccess,l=n.mediaKeys,d=w.Z.getState(t),c=null!==d&&d.loadedSessionsStore!==a?d.loadedSessionsStore.closeAllSessions():Promise.resolve(),e.next=5,c;case 5:if(!r.isCancelled()){e.next=7;break}throw r.cancellationError;case 7:if(w.Z.setState(t,{keySystemOptions:i,mediaKeySystemAccess:s,mediaKeys:l,loadedSessionsStore:a}),t.mediaKeys!==l){e.next=10;break}return e.abrupt("return");case 10:u.Z.info("DRM: Attaching MediaKeys to the media element"),(0,k.Y)(t,l),u.Z.info("DRM: MediaKeys attached with success");case 13:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Z(e){if(""===e.sessionId)return!1;var t=e.keyStatuses,n=[];return t.forEach((function(e){n.push(e)})),n.length<=0?(u.Z.debug("DRM: isSessionUsable: MediaKeySession given has an empty keyStatuses",e.sessionId),!1):(0,b.Z)(n,"expired")?(u.Z.debug("DRM: isSessionUsable: MediaKeySession given has an expired key",e.sessionId),!1):(0,b.Z)(n,"internal-error")?(u.Z.debug("DRM: isSessionUsable: MediaKeySession given has a key with an internal-error",e.sessionId),!1):(u.Z.debug("DRM: isSessionUsable: MediaKeySession is usable",e.sessionId),!0)}function x(e,t,n,r){var i=e.loadedSessionsStore,a=e.persistentSessionsStore;return"temporary"===n?R(i,t):null===a?(u.Z.warn("DRM: Cannot create persistent MediaKeySession, PersistentSessionsStore not created."),R(i,t)):function(e,t,n,r){return M.apply(this,arguments)}(i,a,t,r)}function R(e,t){u.Z.info("DRM: Creating a new temporary session");var n=e.createSession(t,"temporary");return Promise.resolve({type:"created-session",value:n})}function M(){return M=(0,r.Z)(o().mark((function e(t,n,i,a){var s,l,d,c,f,v;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(v=function(){return v=(0,r.Z)(o().mark((function e(){var r,l;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(null===a.cancellationError){e.next=2;break}throw a.cancellationError;case 2:return u.Z.info("DRM: Removing previous persistent session."),null!==(r=n.get(i))&&n.delete(r.sessionId),e.prev=5,e.next=8,t.closeSession(s.mediaKeySession);case 8:e.next=15;break;case 10:if(e.prev=10,e.t0=e.catch(5),""===s.mediaKeySession.sessionId){e.next=14;break}throw e.t0;case 14:t.removeSessionWithoutClosingIt(s.mediaKeySession);case 15:if(null===a.cancellationError){e.next=17;break}throw a.cancellationError;case 17:return l=t.createSession(i,"persistent-license"),e.abrupt("return",{type:"created-session",value:l});case 19:case"end":return e.stop()}}),e,null,[[5,10]])}))),v.apply(this,arguments)},f=function(){return v.apply(this,arguments)},null===a.cancellationError){e.next=4;break}throw a.cancellationError;case 4:if(u.Z.info("DRM: Creating persistent MediaKeySession"),s=t.createSession(i,"persistent-license"),null!==(l=n.getAndReuse(i))){e.next=9;break}return e.abrupt("return",{type:"created-session",value:s});case 9:return e.prev=9,e.next=12,t.loadPersistentSession(s.mediaKeySession,l.sessionId);case 12:if(d=e.sent){e.next=19;break}return u.Z.warn("DRM: No data stored for the loaded session"),n.delete(l.sessionId),t.removeSessionWithoutClosingIt(s.mediaKeySession),c=t.createSession(i,"persistent-license"),e.abrupt("return",{type:"created-session",value:c});case 19:if(!d||!Z(s.mediaKeySession)){e.next=23;break}return n.add(i,i.keyIds,s.mediaKeySession),u.Z.info("DRM: Succeeded to load persistent session."),e.abrupt("return",{type:"loaded-persistent-session",value:s});case 23:return u.Z.warn("DRM: Previous persistent session not usable anymore."),e.abrupt("return",f());case 27:return e.prev=27,e.t0=e.catch(9),u.Z.warn("DRM: Unable to load persistent session: "+(e.t0 instanceof Error?e.t0.toString():"Unknown Error")),e.abrupt("return",f());case 31:case"end":return e.stop()}}),e,null,[[9,27]])}))),M.apply(this,arguments)}function C(e,t){return P.apply(this,arguments)}function P(){return(P=(0,r.Z)(o().mark((function e(t,n){var r,i,a,s,u;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!(n<0||n>=t.getLength())){e.next=2;break}return e.abrupt("return");case 2:for(r=[],i=t.getAll().slice(),a=i.length-n,s=0;s=s.length)){e.next=2;break}throw new g.Z("INCOMPATIBLE_KEYSYSTEMS","No key system compatible with your wanted configuration has been found in the current browser.");case 2:if(null!=k.N){e.next=4;break}throw new Error("requestMediaKeySystemAccess is not implemented in your browser.");case 4:return r=s[t],i=r.keyName,a=r.keyType,d=r.keySystemOptions,c=F(i,a,d),u.Z.debug("DRM: Request keysystem access "+a+","+(t+1)+" of "+s.length),e.prev=7,e.next=10,(0,k.N)(a,c);case 10:return f=e.sent,u.Z.info("DRM: Found compatible keysystem",a,t+1),e.abrupt("return",{type:"create-media-key-system-access",value:{options:d,mediaKeySystemAccess:f}});case 15:if(e.prev=15,e.t0=e.catch(7),u.Z.debug("DRM: Rejected access to keysystem",a,t+1),null===n.cancellationError){e.next=20;break}throw n.cancellationError;case 20:return e.abrupt("return",l(t+1));case 21:case"end":return e.stop()}}),e,null,[[7,15]])})))).apply(this,arguments)}}var z=n(2297);function K(e,t,n){var r;u.Z.debug("Compat: Calling generateRequest on the MediaKeySession");try{r=function(e){u.Z.info("Compat: Trying to move CENC PSSH from init data at the end of it.");for(var t=!1,n=new Uint8Array,r=new Uint8Array,i=0;ie.length)throw u.Z.warn("Compat: unrecognized initialization data. Cannot patch it."),new Error("Compat: unrecognized initialization data. Cannot patch it.");var o=e.subarray(i,i+a);if(16===e[i+12]&&119===e[i+13]&&239===e[i+14]&&236===e[i+15]&&192===e[i+16]&&178===e[i+17]&&77===e[i+18]&&2===e[i+19]&&172===e[i+20]&&227===e[i+21]&&60===e[i+22]&&30===e[i+23]&&82===e[i+24]&&226===e[i+25]&&251===e[i+26]&&75===e[i+27]){var s=(0,z.Xj)(o),l=null===s?void 0:o[s[1]];u.Z.info("Compat: CENC PSSH found with version",l),void 0===l?u.Z.warn("Compat: could not read version of CENC PSSH"):t===(1===l)?n=(0,c.zo)(n,o):1===l?(u.Z.warn("Compat: cenc version 1 encountered, removing every other cenc pssh box."),n=o,t=!0):u.Z.warn("Compat: filtering out cenc pssh box with wrong version",l)}else r=(0,c.zo)(r,o);i+=a}if(i!==e.length)throw u.Z.warn("Compat: unrecognized initialization data. Cannot patch it."),new Error("Compat: unrecognized initialization data. Cannot patch it.");return(0,c.zo)(r,n)}(n)}catch(e){r=n}var i=null!=t?t:"";return e.generateRequest(i,r).catch((function(t){if(""!==i||!(t instanceof TypeError))throw t;return u.Z.warn('Compat: error while calling `generateRequest` with an empty initialization data type. Retrying with a default "cenc" value.',t),e.generateRequest("cenc",r)}))}function G(e,t){return W.apply(this,arguments)}function W(){return(W=(0,r.Z)(o().mark((function e(t,n){var r;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return u.Z.info("Compat/DRM: Load persisted session",n),e.next=3,t.load(n);case 3:if((r=e.sent)&&!(t.keyStatuses.size>0)){e.next=6;break}return e.abrupt("return",r);case 6:return e.abrupt("return",new Promise((function(e){t.addEventListener("keystatuseschange",i);var n=setTimeout(i,100);function i(){clearTimeout(n),t.removeEventListener("keystatuseschange",i),e(r)}})));case 7:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var H=n(7864);function j(e){var t=new E.ZP;return Promise.race([e.close().then((function(){t.cancel()})),e.closed.then((function(){t.cancel()})),function(){return n.apply(this,arguments)}()]);function n(){return(n=(0,r.Z)(o().mark((function e(){var n;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,(0,H.Z)(1e3,t.signal);case 3:return e.next=5,i();case 5:e.next=13;break;case 7:if(e.prev=7,e.t0=e.catch(0),!(e.t0 instanceof E.FU)){e.next=11;break}return e.abrupt("return");case 11:n=e.t0 instanceof Error?e.t0.message:"Unknown error made it impossible to close the session",u.Z.error("DRM: "+n);case 13:case"end":return e.stop()}}),e,null,[[0,7]])})))).apply(this,arguments)}function i(){return a.apply(this,arguments)}function a(){return(a=(0,r.Z)(o().mark((function n(){return o().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.prev=0,n.next=3,e.update(new Uint8Array(1));case 3:n.next=13;break;case 5:if(n.prev=5,n.t0=n.catch(0),!t.isUsed()){n.next=9;break}return n.abrupt("return");case 9:if(!(n.t0 instanceof Error&&"The session is already closed."===n.t0.message)){n.next=11;break}return n.abrupt("return");case 11:return n.next=13,(0,H.Z)(1e3,t.signal);case 13:if(!t.isUsed()){n.next=15;break}return n.abrupt("return");case 15:throw new Error("Compat: Couldn't know if session is closed");case 16:case"end":return n.stop()}}),n,null,[[0,5]])})))).apply(this,arguments)}}var q=n(811);function Y(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return X(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return X(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function X(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function te(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){if(null!==this._keyIds&&J(t,this._keyIds))return!0;if(void 0!==this._initializationData.keyIds)return J(t,this._initializationData.keyIds)}return this._checkInitializationDataCompatibility(e)},t._checkInitializationDataCompatibility=function(e){return void 0!==e.keyIds&&e.keyIds.length>0&&void 0!==this._initializationData.keyIds?J(e.keyIds,this._initializationData.keyIds):this._initializationData.type===e.type&&this._initializationData.values.isCompatibleWith(e.values)},e}();function re(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return ie(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ie(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ie(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&n._storage[e].mediaKeySession===i&&n._storage.splice(e,1)})).catch((function(e){u.Z.warn("DRM-LSS: MediaKeySession.closed rejected: "+e)})),u.Z.debug("DRM-LSS: Add MediaKeySession",a.sessionType),this._storage.push(Object.assign({},a)),a},t.reuse=function(e){for(var t=this._storage.length-1;t>=0;t--){var n=this._storage[t];if(n.keySessionRecord.isCompatibleWith(e))return this._storage.splice(t,1),this._storage.push(n),Object.assign({},n)}return null},t.getEntryForSession=function(e){for(var t=this._storage.length-1;t>=0;t--){var n=this._storage[t];if(n.mediaKeySession===e)return Object.assign({},n)}return null},t.generateLicenseRequest=function(){var e=(0,r.Z)(o().mark((function e(t,n,r){var i,a,s,l;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:a=re(this._storage);case 1:if((s=a()).done){e.next=8;break}if((l=s.value).mediaKeySession!==t){e.next=6;break}return i=l,e.abrupt("break",8);case 6:e.next=1;break;case 8:if(void 0!==i){e.next=11;break}return u.Z.error("DRM-LSS: generateRequest error. No MediaKeySession found with the given initData and initDataType"),e.abrupt("return",K(t,n,r));case 11:if(i.isGeneratingRequest=!0,"none"===i.closingStatus.type){e.next=14;break}throw new Error("The `MediaKeySession` is being closed.");case 14:return e.prev=14,e.next=17,K(t,n,r);case 17:e.next=26;break;case 19:if(e.prev=19,e.t0=e.catch(14),void 0!==i){e.next=23;break}throw e.t0;case 23:throw i.isGeneratingRequest=!1,"awaiting"===i.closingStatus.type&&i.closingStatus.start(),e.t0;case 26:if(void 0!==i){e.next=28;break}return e.abrupt("return",void 0);case 28:i.isGeneratingRequest=!1,"awaiting"===i.closingStatus.type&&i.closingStatus.start();case 30:case"end":return e.stop()}}),e,this,[[14,19]])})));return function(t,n,r){return e.apply(this,arguments)}}(),t.loadPersistentSession=function(){var e=(0,r.Z)(o().mark((function e(t,n){var r,i,a,s,l;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:i=re(this._storage);case 1:if((a=i()).done){e.next=8;break}if((s=a.value).mediaKeySession!==t){e.next=6;break}return r=s,e.abrupt("break",8);case 6:e.next=1;break;case 8:if(void 0!==r){e.next=11;break}return u.Z.error("DRM-LSS: loadPersistentSession error. No MediaKeySession found with the given initData and initDataType"),e.abrupt("return",G(t,n));case 11:if(r.isLoadingPersistentSession=!0,"none"===r.closingStatus.type){e.next=14;break}throw new Error("The `MediaKeySession` is being closed.");case 14:return e.prev=14,e.next=17,G(t,n);case 17:l=e.sent,e.next=27;break;case 20:if(e.prev=20,e.t0=e.catch(14),void 0!==r){e.next=24;break}throw e.t0;case 24:throw r.isLoadingPersistentSession=!1,"awaiting"===r.closingStatus.type&&r.closingStatus.start(),e.t0;case 27:if(void 0!==r){e.next=29;break}return e.abrupt("return",l);case 29:return r.isLoadingPersistentSession=!1,"awaiting"===r.closingStatus.type&&r.closingStatus.start(),e.abrupt("return",l);case 32:case"end":return e.stop()}}),e,this,[[14,20]])})));return function(t,n){return e.apply(this,arguments)}}(),t.closeSession=function(){var e=(0,r.Z)(o().mark((function e(t){var n,r,i,a;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:r=re(this._storage);case 1:if((i=r()).done){e.next=8;break}if((a=i.value).mediaKeySession!==t){e.next=6;break}return n=a,e.abrupt("break",8);case 6:e.next=1;break;case 8:if(void 0!==n){e.next=11;break}return u.Z.warn("DRM-LSS: No MediaKeySession found with the given initData and initDataType"),e.abrupt("return",Promise.resolve(!1));case 11:return e.abrupt("return",this._closeEntry(n));case 12:case"end":return e.stop()}}),e,this)})));return function(t){return e.apply(this,arguments)}}(),t.getLength=function(){return this._storage.length},t.getAll=function(){return this._storage},t.closeAllSessions=function(){var e=(0,r.Z)(o().mark((function e(){var t,n,r=this;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t=this._storage,u.Z.debug("DRM-LSS: Closing all current MediaKeySessions",t.length),this._storage=[],n=t.map((function(e){return r._closeEntry(e)})),e.next=6,Promise.all(n);case 6:case"end":return e.stop()}}),e,this)})));return function(){return e.apply(this,arguments)}}(),t.removeSessionWithoutClosingIt=function(e){(0,q.Z)(""===e.sessionId,"Initialized `MediaKeySession`s should always be properly closed");for(var t=this._storage.length-1;t>=0;t--){if(this._storage[t].mediaKeySession===e)return this._storage.splice(t,1),!0}return!1},t.getIndex=function(e){for(var t=0;t=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function he(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0){var o=void 0===t?3:4,s=this._entries[a];if((null!==(r=s.version)&&void 0!==r?r:-1)>=o&&i===s.sessionId)return;u.Z.info("DRM-PSS: Updating session info.",i),this._entries.splice(a,1)}else u.Z.info("DRM-PSS: Add new session",i);var l=e.values.getFormattedValues().map((function(e){var t=e.systemId,n=e.data;return{systemId:t,hash:e.hash,data:new ce(n)}}));void 0===t?this._entries.push({version:3,sessionId:i,values:l,initDataType:e.type}):this._entries.push({version:4,sessionId:i,keyIds:t.map((function(e){return new ce(e)})),values:l,initDataType:e.type}),this._save()}else u.Z.warn("DRM-PSS: Invalid Persisten Session given.")},t.delete=function(e){for(var t=-1,n=0;n0&&(r=new g.Z("KEY_STATUS_CHANGE_ERROR","One or several problematic key statuses have been encountered",{keyStatuses:c})),{warning:r,blacklistedKeyIds:l,whitelistedKeyIds:d}}var Ue=s.Dl,Be=s.RV,Fe=s.qo;function Ve(e,t,n,i,a){u.Z.info("DRM: Binding session events",e.sessionId);var s=t.getLicenseConfig,l=void 0===s?{}:s,d=new E.ZP;return d.linkToSignal(a),(0,T.Z)(e.closed)||e.closed.then((function(){return d.cancel()})).catch((function(e){a.isCancelled()||(d.cancel(),i.onError(e))})),Ue(e,(function(e){d.cancel(),i.onError(new g.Z("KEY_ERROR",e.type))}),d.signal),Fe(e,(function(e){(function(e){return c.apply(this,arguments)})(e).catch((function(e){a.isCancelled()||d.isUsed()&&e instanceof E.XG||(d.cancel(),i.onError(e))}))}),d.signal),Be(e,(function(n){var a,s=n,c=new Uint8Array(s.message),f=(0,de.Z)(s.messageType)?s.messageType:"license-request";u.Z.info("DRM: Received message event, type "+f,e.sessionId),function(e,t,n){var i=t.baseDelay,a=t.maxDelay,s=t.totalRetry,u=t.shouldRetry,l=t.onRetry,d=0;return c();function c(){return f.apply(this,arguments)}function f(){return(f=(0,r.Z)(o().mark((function t(){var r,f,v,p;return o().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(null===n.cancellationError){t.next=2;break}throw n.cancellationError;case 2:return t.prev=2,t.next=5,e();case 5:return r=t.sent,t.abrupt("return",r);case 9:if(t.prev=9,t.t0=t.catch(2),null===n.cancellationError){t.next=13;break}throw n.cancellationError;case 13:if(!(!(0,T.Z)(u)&&!u(t.t0)||d++>=s)){t.next=15;break}throw t.t0;case 15:return"function"==typeof l&&l(t.t0,d),f=Math.min(i*Math.pow(2,d-1),a),v=(0,Re.Z)(f),t.next=20,Me(v);case 20:return p=c(),t.abrupt("return",p);case 22:case"end":return t.stop()}}),t,null,[[2,9]])})))).apply(this,arguments)}}((function(){return function(e,n){var r;return new Promise((function(i,a){try{u.Z.debug("DRM: Calling `getLicense`",n);var o=t.getLicense(e,n),s=(0,T.Z)(l.timeout)?1e4:l.timeout;s>=0&&(r=setTimeout((function(){a(new He('"getLicense" timeout exceeded ('+s+" ms)"))}),s)),Promise.resolve(o).then(d,c)}catch(e){c(e)}function d(e){void 0!==r&&clearTimeout(r),i(e)}function c(e){void 0!==r&&clearTimeout(r),a(e)}}))}(c,f)}),{totalRetry:null!=(a=l.retry)?a:2,baseDelay:200,maxDelay:3e3,shouldRetry:function(e){return e instanceof He||(0,T.Z)(e)||!0!==e.noRetry},onRetry:function(e){return i.onWarning(ze(e))}},d.signal).then((function(t){return d.isUsed()?Promise.resolve():(0,T.Z)(t)?void u.Z.info("DRM: No license given, skipping session.update"):Ke(e,t)})).catch((function(e){if(!d.isUsed()){d.cancel();var t=ze(e);if(!(0,T.Z)(e))if(!0===e.fallbackOnLastTry)return u.Z.warn("DRM: Last `getLicense` attempt failed. Blacklisting the current session."),void i.onError(new We(t));i.onError(t)}}))}),d.signal),void f();function c(){return c=(0,r.Z)(o().mark((function n(i){var s,l;return o().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return l=function(){return(l=(0,r.Z)(o().mark((function n(){var r,s;return o().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:if(!d.isUsed()){n.next=2;break}return n.abrupt("return");case 2:if("function"!=typeof t.onKeyStatusesChange){n.next=24;break}return n.prev=3,n.next=6,t.onKeyStatusesChange(i,e);case 6:if(r=n.sent,!d.isUsed()){n.next=9;break}return n.abrupt("return");case 9:n.next=18;break;case 11:if(n.prev=11,n.t0=n.catch(3),!a.isCancelled()){n.next=15;break}return n.abrupt("return");case 15:throw s=new g.Z("KEY_STATUS_CHANGE_ERROR","Unknown `onKeyStatusesChange` error"),!(0,T.Z)(n.t0)&&(0,de.Z)(n.t0.message)&&(s.message=n.t0.message),s;case 18:if(!(0,T.Z)(r)){n.next=22;break}u.Z.info("DRM: No license given, skipping session.update"),n.next=24;break;case 22:return n.next=24,Ke(e,r);case 24:case"end":return n.stop()}}),n,null,[[3,11]])})))).apply(this,arguments)},s=function(){return l.apply(this,arguments)},u.Z.info("DRM: keystatuseschange event received",e.sessionId),n.next=5,Promise.all([s(),Promise.resolve(f())]);case 5:case"end":return n.stop()}}),n)}))),c.apply(this,arguments)}function f(){if(!d.isUsed()&&0!==e.keyStatuses.size){var r=Le(e,t,n),a=r.warning,o=r.blacklistedKeyIds,s=r.whitelistedKeyIds;void 0!==a&&(i.onWarning(a),d.isUsed())||i.onKeyUpdate({whitelistedKeyIds:s,blacklistedKeyIds:o})}}}function ze(e){if(e instanceof He)return new g.Z("KEY_LOAD_TIMEOUT","The license server took too much time to respond.");var t=new g.Z("KEY_LOAD_ERROR","An error occured when calling `getLicense`.");return!(0,T.Z)(e)&&(0,de.Z)(e.message)&&(t.message=e.message),t}function Ke(e,t){return Ge.apply(this,arguments)}function Ge(){return(Ge=(0,r.Z)(o().mark((function e(t,n){var r;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return u.Z.info("DRM: Updating MediaKeySession with message"),e.prev=1,e.next=4,t.update(n);case 4:e.next=10;break;case 6:throw e.prev=6,e.t0=e.catch(1),r=e.t0 instanceof Error?e.t0.toString():"`session.update` failed",new g.Z("KEY_UPDATE_ERROR",r);case 10:u.Z.info("DRM: MediaKeySession update succeeded.");case 11:case"end":return e.stop()}}),e,null,[[1,6]])})))).apply(this,arguments)}var We=function(e){function t(n){var r;return r=e.call(this)||this,Object.setPrototypeOf((0,Ze.Z)(r),t.prototype),r.sessionError=n,r}return(0,i.Z)(t,e),t}((0,xe.Z)(Error)),He=function(e){function t(t){var n;return n=e.call(this)||this,Object.setPrototypeOf((0,Ze.Z)(n),We.prototype),n.message=t,n}return(0,i.Z)(t,e),t}((0,xe.Z)(Error)),je=n(9822);function qe(e,t){return Ye.apply(this,arguments)}function Ye(){return(Ye=(0,r.Z)(o().mark((function e(t,n){var r,i;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,t.setServerCertificate(n);case 3:return r=e.sent,e.abrupt("return",r);case 7:throw e.prev=7,e.t0=e.catch(0),u.Z.warn("DRM: mediaKeys.setServerCertificate returned an error",e.t0 instanceof Error?e.t0:""),i=e.t0 instanceof Error?e.t0.toString():"`setServerCertificate` error",new g.Z("LICENSE_SERVER_CERTIFICATE_ERROR",i);case 12:case"end":return e.stop()}}),e,null,[[0,7]])})))).apply(this,arguments)}function Xe(e,t){return Qe.apply(this,arguments)}function Qe(){return(Qe=(0,r.Z)(o().mark((function e(t,n){var r,i;return o().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!0!==be(t)){e.next=3;break}return u.Z.info("DRM: The MediaKeys already has a server certificate, skipping..."),e.abrupt("return",{type:"already-has-one"});case 3:if("function"==typeof t.setServerCertificate){e.next=6;break}return u.Z.warn("DRM: Could not set the server certificate. mediaKeys.setServerCertificate is not a function"),e.abrupt("return",{type:"method-not-implemented"});case 6:return u.Z.info("DRM: Setting server certificate on the MediaKeys"),ye(t),e.prev=8,e.next=11,qe(t,n);case 11:return r=e.sent,_e(t,n),e.abrupt("return",{type:"success",value:r});case 16:return e.prev=16,e.t0=e.catch(8),i=(0,je.Z)(e.t0)?e.t0:new g.Z("LICENSE_SERVER_CERTIFICATE_ERROR","Unknown error when setting the server certificate."),e.abrupt("return",{type:"error",value:i});case 20:case"end":return e.stop()}}),e,null,[[8,16]])})))).apply(this,arguments)}function $e(e,t){if(!(isNaN(t)||t<0||t>=e.getLength())){var n=e.getLength(),r=n-t;u.Z.info("DRM: Too many stored persistent sessions, removing some.",n,r),e.deleteOldSessions(r)}}var Je=n(9252);var et=function(){function e(e){this._innerValues=e,this._lazyFormattedValues=null}var t=e.prototype;return t.constructRequestData=function(){return c.zo.apply(void 0,this._innerValues.map((function(e){return e.data})))},t.isCompatibleWith=function(t){var n=t instanceof e?t.getFormattedValues():t;return fe(this.getFormattedValues(),n)},t.getFormattedValues=function(){return null===this._lazyFormattedValues&&(this._lazyFormattedValues=this._innerValues.slice().sort((function(e,t){return e.systemId===t.systemId?0:void 0===e.systemId?1:void 0===t.systemId||e.systemId=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function nt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&K._currentSessions.splice(r),void 0!==t.content&&st(t.content.manifest,[],[],N.record.getAssociatedKeyIds()),null===(n=i.persistentSessionsStore)||void 0===n||n.delete(L.sessionId),i.loadedSessionsStore.closeSession(L).catch((function(e){var t=e instanceof Error?e:"unknown error";u.Z.warn("DRM: failed to close expired session",t)})).then((function(){return K._unlockInitDataQueue()})).catch((function(e){return K._onFatalError(e)})),void(K._isStopped()||K.trigger("warning",e.reason))}if(e instanceof We){if(N.blacklistedSessionError=e,void 0!==t.content){var a=t.content.manifest;u.Z.info("DRM: blacklisting Representations based on protection data."),ut(a,t)}K._unlockInitDataQueue()}else K._onFatalError(e)}},this._canceller.signal),void 0!==a.singleLicensePer&&"init-data"!==a.singleLicensePer||this._unlockInitDataQueue(),"created-session"!==P.type){e.next=67;break}return F=t.values.constructRequestData(),e.prev=54,e.next=57,i.loadedSessionsStore.generateLicenseRequest(L,t.type,F);case 57:e.next=67;break;case 59:if(e.prev=59,e.t0=e.catch(54),null!==(V=i.loadedSessionsStore.getEntryForSession(L))&&"none"===V.closingStatus.type){e.next=66;break}return(z=this._currentSessions.indexOf(N))>=0&&this._currentSessions.splice(z,1),e.abrupt("return",Promise.resolve());case 66:throw new g.Z("KEY_GENERATE_REQUEST_ERROR",e.t0 instanceof Error?e.t0.toString():"Unknown error");case 67:return e.abrupt("return",Promise.resolve());case 68:case"end":return e.stop()}}),e,this,[[54,59]])})));return function(t,n){return e.apply(this,arguments)}}(),n._tryToUseAlreadyCreatedSession=function(e,t){var n=t.stores,r=t.options,i=(0,_.Z)(this._currentSessions,(function(t){return t.record.isCompatibleWith(e)}));if(void 0===i)return!1;var a=i.blacklistedSessionError;if(!(0,T.Z)(a))return void 0===e.type||void 0===e.content?(u.Z.error("DRM: This initialization data has already been blacklisted but the current content is not known."),!0):(u.Z.info("DRM: This initialization data has already been blacklisted. Blacklisting the related content."),ut(e.content.manifest,e),!0);if(void 0!==e.keyIds){var o;if(void 0===r.singleLicensePer||"init-data"===r.singleLicensePer){var s=i.keyStatuses.blacklisted;o=function(e,t){for(var n,r=function(){var e=n.value;if(t.some((function(t){return Q(t,e)})))return{v:!0}},i=Y(e);!(n=i()).done;){var a=r();if("object"==typeof a)return a.v}return!1}(e.keyIds,s)}else{var l=i.keyStatuses.whitelisted;o=!J(e.keyIds,l)}if(o)return void 0===e.content?(u.Z.error("DRM: Cannot forbid key id, the content is unknown."),!0):(u.Z.info("DRM: Current initialization data is linked to blacklisted keys. Marking Representations as not decipherable"),st(e.content.manifest,[],e.keyIds,[]),!0)}if(null!==n.loadedSessionsStore.reuse(e))return u.Z.debug("DRM: Init data already processed. Skipping it."),!0;var d=this._currentSessions.indexOf(i);return-1===d?u.Z.error("DRM: Unable to remove processed init data: not found."):(u.Z.debug("DRM: A session from a processed init data is not available anymore. Re-processing it."),this._currentSessions.splice(d,1)),!1},n._onFatalError=function(e){if(!this._canceller.isUsed()){var t=e instanceof Error?e:new y.Z("NONE","Unknown decryption error");this.error=t,this._initDataQueue.length=0,this._stateData={state:rt.Error,isMediaKeysAttached:void 0,isInitDataQueueLocked:void 0,data:null},this._canceller.cancel(),this.trigger("error",t),this._stateData.state===rt.Error&&this.trigger("stateChange",this._stateData.state)}},n._isStopped=function(){return this._stateData.state===rt.Disposed||this._stateData.state===rt.Error},n._processCurrentInitDataQueue=function(){for(;!1===this._stateData.isInitDataQueueLocked;){var e=this._initDataQueue.shift();if(void 0===e)return;this.onInitializationData(e)}},n._lockInitDataQueue=function(){!1===this._stateData.isInitDataQueueLocked&&(this._stateData.isInitDataQueueLocked=!0)},n._unlockInitDataQueue=function(){!0===this._stateData.isMediaKeysAttached?(this._stateData.isInitDataQueueLocked=!1,this._processCurrentInitDataQueue()):u.Z.error("DRM: Trying to unlock in the wrong state")},t}(S.Z);function ot(e){var t=e.getConfiguration().sessionTypes;return void 0!==t&&(0,b.Z)(t,"persistent-license")}function st(e,t,n,r){e.updateRepresentationsDeciperability((function(e){if(void 0===e.contentProtections)return e.decipherable;var i=e.contentProtections.keyIds;if(void 0!==i)for(var a,o=tt(i);!(a=o()).done;){for(var s,u=a.value,l=tt(n);!(s=l()).done;){if(Q(s.value,u.keyId))return!1}for(var d,c=tt(t);!(d=c()).done;){if(Q(d.value,u.keyId))return!0}for(var f,v=tt(r);!(f=v()).done;){if(Q(f.value,u.keyId))return}}return e.decipherable}))}function ut(e,t){e.updateRepresentationsDeciperability((function(e){var n,r;if(!1===e.decipherable)return!1;for(var i,a=function(){var e=i.value;if((void 0===t.type||e.type===t.type)&&t.values.getFormattedValues().every((function(t){return e.values.some((function(e){return(void 0===t.systemId||e.systemId===t.systemId)&&(0,d.Z)(e.data,t.data)}))})))return{v:!1}},o=tt(null!==(r=null===(n=e.contentProtections)||void 0===n?void 0:n.initData)&&void 0!==r?r:[]);!(i=o()).done;){var s=a();if("object"==typeof s)return s.v}return e.decipherable}))}function lt(e,t,n,r,i,a){for(var o,s,l=[].concat(i,a),d=function(){var e=s.value;l.some((function(t){return Q(t,e)}))||(u.Z.hasLevel("DEBUG")&&u.Z.debug("DRM: KeySessionRecord's key missing in the license, blacklisting it",(0,f.ci)(e)),l.push(e))},c=tt(t.getAssociatedKeyIds());!(s=c()).done;)d();if(void 0!==n&&"init-data"!==n){var v=e.keyIds,p=e.content;if(void 0!==v){var h=v.filter((function(e){return!l.some((function(t){return Q(t,e)}))}));h.length>0&&(u.Z.hasLevel("DEBUG")&&u.Z.debug("DRM: init data keys missing in the license, blacklisting them",h.map((function(e){return(0,f.ci)(e)})).join(", ")),l.push.apply(l,h))}if(r&&void 0!==p)if("content"===n){for(var m,g=new Set,y=tt(p.manifest.periods);!(m=y()).done;){ct(g,m.value)}dt(g,l)}else if("periods"===n)for(var _,b=tt(p.manifest.periods);!(_=b()).done;){var S=_.value,T=new Set;if(ct(T,S),(null===(o=e.content)||void 0===o?void 0:o.period.id)===S.id)dt(T,l);else for(var E=Array.from(T),k=function(){var e=A[w];if(l.some((function(t){return Q(t,e)})))return dt(T,l),"break"},w=0,A=E;w=100?n:r<=0?0:n*(+r/100)}return 0}(e,s);return a.Z.debug("Init: Initial time calculated:",t),t}),o,(function(e){return n.trigger("warning",e)}),r).autoPlayResult.then((function(){return(0,l.Z)(t,e,!0,r).onUpdate((function(e,t){e&&(t(),n.trigger("loaded",{segmentBuffersStore:null}))}),{emitCurrentValue:!0,clearSignal:r})})).catch((function(e){r.isCancelled()||n._onFatalError(e)}))},t}(u.K)},379:function(e,t,n){"use strict";n.d(t,{K:function(){return i}});var r=n(4578),i=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.Z)(t,e),t}(n(1959).Z)},1757:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(3666);var i=n(1669),a=n(5095),o=n(288);function s(e,t,n,s){var u=new o.ZP;u.linkToSignal(s);var l=(0,a.ZP)(!1,u.signal);return e.listen((function(e){if(null===e.rebuffering&&null===e.freezing&&0!==e.readyState)return!function(e,t){return!e||!r.SB||t}(n,t.hasAttribute("playsinline"))&&t.duration>0||e.readyState>=3&&null!==e.currentRange&&(!(0,i.Z)()||t.duration>0)?(l.setValue(!0),void u.cancel()):void 0}),{includeLastObservation:!0,clearSignal:u.signal}),l}},8833:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(1669),i=n(3774),a=n(3714),o=n(3887),s=n(5095);function u(e,t,n,u,l,d){var c,f,v=new Promise((function(e,t){c=e,f=t})),p=(0,s.$l)(!1,d),h=(0,s.$l)(!1,d);e.addEventListener("loadedmetadata",g),e.readyState>=i.c.HAVE_METADATA&&g();var m=d.register((function(t){e.removeEventListener("loadedmetadata",g),f(t)}));return{autoPlayResult:v,initialPlayPerformed:h,initialSeekPerformed:p};function g(){e.removeEventListener("loadedmetadata",g);var i="function"==typeof n?n():n;if(o.Z.info("Init: Set initial time",i),t.setCurrentTime(i),p.setValue(!0),p.finish(),(0,r.Z)()&&0===e.duration){var s=new a.Z("MEDIA_ERR_NOT_LOADED_METADATA","Cannot load automatically: your browser falsely announced having loaded the content.");l(s)}d.isCancelled()||t.listen((function(t,n){!t.seeking&&null===t.rebuffering&&t.readyState>=1&&(n(),function(){var t,n;if(o.Z.info("Init: Can begin to play content"),!u)return e.autoplay&&o.Z.warn("Init: autoplay is enabled on HTML media element. Media will play as soon as possible."),h.setValue(!0),h.finish(),m(),c({type:"skipped"});try{n=null!==(t=e.play())&&void 0!==t?t:Promise.resolve()}catch(e){return m(),f(e)}n.then((function(){if(!d.isCancelled())return h.setValue(!0),h.finish(),m(),c({type:"autoplay"})})).catch((function(e){if(m(),!d.isCancelled()){if(e instanceof Error&&"NotAllowedError"===e.name){o.Z.warn("Init: Media element can't play. It may be due to browser auto-play policies.");var t=new a.Z("MEDIA_ERR_BLOCKED_AUTOPLAY","Cannot trigger auto-play automatically: your browser does not allow it.");if(l(t),d.isCancelled())return;return c({type:"autoplay-blocked"})}f(e)}}))}())}),{includeLastObservation:!0,clearSignal:d})}}},8799:function(e,t,n){"use strict";n.d(t,{Z:function(){return d}});var r=n(6139);var i=n(5157),a=n(3887),o=n(5095),s=n(288),u=n(1266),l=n(1960);function d(e,t,n,d,c){if(0===t.length){n.onUpdate((function(e,t){if(null!==e){t(),a.Z.error("Init: Encrypted event but EME feature not activated");var n=new i.Z("MEDIA_IS_ENCRYPTED_ERROR","EME feature not activated.");d.onError(n)}}),{clearSignal:c});var f=(0,o.ZP)({initializationState:{type:"initialized",value:null},drmSystemId:void 0});return f.finish(),f}if("function"!=typeof r.N){n.onUpdate((function(e,t){if(null!==e){t(),a.Z.error("Init: Encrypted event but no EME API available");var n=new i.Z("MEDIA_IS_ENCRYPTED_ERROR","Encryption APIs not found.");d.onError(n)}}),{clearSignal:c});var v=(0,o.ZP)({initializationState:{type:"initialized",value:null},drmSystemId:void 0});return v.finish(),v}var p=new s.ZP;p.linkToSignal(c);var h=(0,o.ZP)({initializationState:{type:"uninitialized",value:null},drmSystemId:void 0},c);a.Z.debug("Init: Creating ContentDecryptor");var m=new u.ZP(e,t);return m.addEventListener("stateChange",(function(e){if(e===l.u.WaitingForAttachment){var t=(0,o.ZP)(!1);t.onUpdate((function(t,n){t&&(n(),e===l.u.WaitingForAttachment&&m.attach())}),{clearSignal:p.signal}),h.setValue({initializationState:{type:"awaiting-media-link",value:{isMediaLinked:t}},drmSystemId:m.systemId})}else e===l.u.ReadyForContent&&(h.setValue({initializationState:{type:"initialized",value:null},drmSystemId:m.systemId}),m.removeEventListener("stateChange"))})),m.addEventListener("error",(function(e){p.cancel(),d.onError(e)})),m.addEventListener("warning",(function(e){d.onWarning(e)})),n.onUpdate((function(e){null!==e&&m.onInitializationData(e)}),{clearSignal:p.signal}),p.signal.register((function(){m.dispose()})),h}},6199:function(e,t,n){"use strict";n.d(t,{Z:function(){return f}});var r=n(4578),i=n(3666).yS,a=n(6872),o=n(3714),s=n(3887),u=n(1959),l=n(2829),d=n(288),c=1/60,f=function(e){function t(t,n,r){var i;return(i=e.call(this)||this)._playbackObserver=t,i._manifest=n,i._speed=r,i._discontinuitiesStore=[],i._isStarted=!1,i._canceller=new d.ZP,i}(0,r.Z)(t,e);var n=t.prototype;return n.start=function(){var e=this;if(!this._isStarted){var t;this._isStarted=!0;var n=null,r=new h(this._playbackObserver,this._speed);this._canceller.signal.register((function(){r.dispose()}));var o=null;this._playbackObserver.listen((function(u){var d,f,v=e._discontinuitiesStore,h=u.buffered,m=u.position,g=u.readyState,y=u.rebuffering,_=u.freezing,b=a.Z.getCurrent(),S=b.BUFFER_DISCONTINUITY_THRESHOLD,T=b.FORCE_DISCONTINUITY_SEEK_DELAY,E=b.FREEZING_STALLED_DELAY,k=b.UNFREEZING_SEEK_DELAY,w=b.UNFREEZING_DELTA_POSITION;if(!u.seeking&&i&&null===n&&null!==t&&u.positionk&&(s.Z.warn("Init: trying to seek to un-freeze player"),e._playbackObserver.setCurrentTime(e._playbackObserver.getCurrentTime()+w),o={attemptTimestamp:I}),I-_.timestamp>E)return null===y||null!==n?r.stopRebuffering():r.startRebuffering(),void e.trigger("stalled","freezing")}else o=null;if(null===y)return r.stopRebuffering(),1===g?(f=u.seeking?null!==u.pendingInternalSeek?"internal-seek":"seeking":"not-ready",void e.trigger("stalled",f)):void e.trigger("unstalled",null);var Z="seeking"===y.reason&&null!==u.pendingInternalSeek?"internal-seek":y.reason;if(null!==n){var x=performance.now();if(x-n0){var M=function(e,t,n){if(0===e.length)return null;for(var r=null,i=0;in)return r;var o=void 0;if(void 0===a.end||a.end>n){var u=e[i],l=u.discontinuity,d=u.position,f=l.start,v=l.end;if(n>=(null!=f?f:d)-c)if(null===v){var p=t.getPeriodAfter(a);null!==p?o=p.start+c:s.Z.warn("Init: discontinuity at Period's end but no next Period")}else no?r:o)}}return r}(v,e._manifest,R);if(null!==M){var C=M+.001;if(!(C<=e._playbackObserver.getCurrentTime()))return s.Z.warn("SA: skippable discontinuity found in the stream",m,C),e._playbackObserver.setCurrentTime(C),void e.trigger("warning",p(R,C));s.Z.info("Init: position to seek already reached, no seeking",e._playbackObserver.getCurrentTime(),C)}}var P=null!=R?R:m,D=(0,l.XS)(h,P);if(e._speed.getValue()>0&&D=0;O--){var L=e._manifest.periods[O];if(void 0!==L.end&&L.end<=P){if(e._manifest.periods[O+1].start>P&&e._manifest.periods[O+1].start>e._playbackObserver.getCurrentTime()){var U=e._manifest.periods[O+1];return e._playbackObserver.setCurrentTime(U.start),void e.trigger("warning",p(P,U.start))}break}}e.trigger("stalled",Z)}else e.trigger("stalled",Z)}),{includeLastObservation:!0,clearSignal:this._canceller.signal})}},n.updateDiscontinuityInfo=function(e){this._isStarted||this.start();var t=this._playbackObserver.getReference().getValue();!function(e,t,n){for(;e.length>0&&void 0!==e[0].period.end&&e[0].period.end+10r.start)return void(v(t)&&e.splice(a,0,t));v(t)&&e.push(t)}(this._discontinuitiesStore,e,t)},n.onLockedStream=function(e,t){var n;this._isStarted||this.start();var r=this._playbackObserver.getReference().getValue();if(!(!r.rebuffering||r.paused||this._speed.getValue()<=0||"audio"!==e&&"video"!==e)){var i=r.position,a=null!==(n=r.rebuffering.position)&&void 0!==n?n:i,o=t.start;i=0;n--){if(e[n].startt)return e.slice(n,e.length)}return[]}function m(e,t,n){var r=Math.max(e.start,t),i=p(e.cues,t),a={start:e.start,end:r,cues:i},o=Math.min(n,e.end),s=h(e.cues,n);return[a,{start:o,end:e.end,cues:s}]}var g=function(){function e(){this._cuesBuffer=[]}var t=e.prototype;return t.get=function(e){for(var t=this._cuesBuffer,n=[],r=t.length-1;r>=0;r--){var i=t[r];if(e=i.start){for(var a=i.cues,o=0;o=a[o].start&&ee){var a=r[i];if(a.start>=n)return;if(a.end>=n){if(e<=a.start)a.cues=h(a.cues,n),a.start=n;else{var o=m(a,e,n),s=o[0],u=o[1];this._cuesBuffer[i]=s,r.splice(i+1,0,u)}return}a.start>=e?(r.splice(i,1),i--):(a.cues=p(a.cues,e),a.end=Math.max(e,a.start))}},t.insert=function(e,t,n){var r=this._cuesBuffer,i={start:t,end:n,cues:e};function a(e){var t=r[e];void 0===t||v(i.end,t.end)?r[e]=i:(t.start>=i.end||(t.cues=h(t.cues,i.end),t.start=i.end),r.splice(e,0,i))}for(var o=0;os.end);return void a(o)}if(ts.end);return void a(o)}if(v(s.end,n))return s.cues=p(s.cues,t),s.end=t,void r.splice(o+1,0,i);if(s.end>n){var u=m(s,t,n),l=u[0],d=u[1];return this._cuesBuffer[o]=l,r.splice(o+1,0,i),void r.splice(o+2,0,d)}s.cues=p(s.cues,t),s.end=t;var c=o+1;for(s=r[c];void 0!==s&&n>s.end;)r.splice(c,1),s=r[c];return void a(c)}}r.push(i)},e}();function y(e,t,n,r){for(var i=[t/n.columns,e/n.rows],a=r.getElementsByClassName("proportional-style"),o=0;o0}var _=i.M4,b=i.bQ,S=i.Q$;function T(e,t){try{e.removeChild(t)}catch(e){a.Z.warn("HTSB: Can't remove text track: not in the element.")}}function E(e){var t=e.getAttribute("data-resolution-rows"),n=e.getAttribute("data-resolution-columns");if(null===t||null===n)return null;var r=parseInt(t,10),i=parseInt(n,10);return null===r||null===i?null:{rows:r,columns:i}}var k=function(e){function t(t,n){var r;return a.Z.debug("HTSB: Creating HTMLTextSegmentBuffer"),(r=e.call(this)||this).bufferType="text",r._buffered=new c.Z,r._videoElement=t,r._textTrackElement=n,r._sizeUpdateCanceller=new l.ZP,r._canceller=new l.ZP,r._buffer=new g,r._currentCues=[],r.autoRefreshSubtitles(r._canceller.signal),r}(0,r.Z)(t,e);var n=t.prototype;return n.pushChunk=function(e){try{this.pushChunkSync(e)}catch(e){return Promise.reject(e)}return Promise.resolve()},n.removeBuffer=function(e,t){return this.removeBufferSync(e,t),Promise.resolve()},n.endOfSegment=function(e){return this._segmentInventory.completeSegment(e,this._buffered),Promise.resolve()},n.getBufferedRanges=function(){return this._buffered},n.dispose=function(){a.Z.debug("HTSB: Disposing HTMLTextSegmentBuffer"),this._disableCurrentCues(),this._buffer.remove(0,1/0),this._buffered.remove(0,1/0),this._canceller.cancel()},n.pushChunkSync=function(e){var t,n;a.Z.debug("HTSB: Appending new html text tracks");var r=e.data,i=r.timestampOffset,o=r.appendWindow,s=r.chunk;if(null!==s){var u,l,d=s.start,c=s.end,v=s.data,p=s.type,h=s.language,m=null!==(t=o[0])&&void 0!==t?t:0,g=null!==(n=o[1])&&void 0!==n?n:1/0,y=function(e,t,n,r){a.Z.debug("HTSB: Finding parser for html text tracks:",e);var i=f.Z.htmlTextTracksParsers[e];if("function"!=typeof i)throw new Error("no parser found for the given text track");a.Z.debug("HTSB: Parser found, parsing...");var o=i(t,n,r);return a.Z.debug("HTTB: Parsed successfully!",o.length),o}(p,v,i,h);if(0!==m&&g!==1/0){for(var _=0;_=0&&y[_].start>=g;)_--;for(y.splice(_,y.length),_=y.length-1;_>=0&&y[_].end>g;)y[_].end=g,_--}if(void 0!==d)u=Math.max(m,d);else{if(y.length<=0)return void a.Z.warn("HTSB: Current text tracks have no cues nor start time. Aborting");a.Z.warn("HTSB: No start time given. Guessing from cues."),u=y[0].start}if(void 0!==c)l=Math.min(g,c);else{if(y.length<=0)return void a.Z.warn("HTSB: Current text tracks have no cues nor end time. Aborting");a.Z.warn("HTSB: No end time given. Guessing from cues."),l=y[y.length-1].end}l<=u?a.Z.warn("HTSB: Invalid text track appended: ","the start time is inferior or equal to the end time."):(null!==e.inventoryInfos&&this._segmentInventory.insertChunk(e.inventoryInfos),this._buffer.insert(y,u,l),this._buffered.insert(u,l))}},n.removeBufferSync=function(e,t){a.Z.debug("HTSB: Removing html text track data",e,t),this._buffer.remove(e,t),this._buffered.remove(e,t)},n._disableCurrentCues=function(){if(this._sizeUpdateCanceller.cancel(),this._currentCues.length>0){for(var e=0;e0){this._sizeUpdateCanceller=new l.ZP,this._sizeUpdateCanceller.linkToSignal(this._canceller.signal);var c=u.Z.getCurrent().TEXT_TRACK_SIZE_CHECKS_INTERVAL,f=function(e,t,n){var r=e.getBoundingClientRect(),i=r.height,u=r.width,l=(0,o.ZP)({height:i,width:u},n),d=i,c=u;if(void 0!==s){var f=new s((function(e){if(0!==e.length){var t=e[0].contentRect,n=t.height,r=t.width;n===d&&r===c||(d=n,c=r,l.setValue({height:n,width:r}))}else a.Z.error("Compat: Resized but no observed element.")}));f.observe(e),n.register((function(){f.disconnect()}))}else{var v=setInterval((function(){var t=e.getBoundingClientRect(),n=t.height,r=t.width;n===d&&r===c||(d=n,c=r,l.setValue({height:n,width:r}))}),t);n.register((function(){clearInterval(v)}))}return l}(this._textTrackElement,c,this._sizeUpdateCanceller.signal);f.onUpdate((function(e){for(var t=e.height,n=e.width,r=0;r0?e.textTracks[u-1]:e.addTextTrack(s)).mode=t?null!==(n=a.HIDDEN)&&void 0!==n?n:"hidden":null!==(r=a.SHOWING)&&void 0!==r?r:"showing"}else o=document.createElement("track"),e.appendChild(o),a=o.track,o.kind=s,a.mode=t?"hidden":"showing";return{track:a,trackElement:o}}(t,n),s=o.track,l=o.trackElement;return r.bufferType="text",r._buffered=new u.Z,r._videoElement=t,r._track=s,r._trackElement=l,r}(0,r.Z)(t,e);var n=t.prototype;return n.pushChunk=function(e){var t,n;if(a.Z.debug("NTSB: Appending new native text tracks"),null===e.data.chunk)return Promise.resolve();var r=e.data,i=r.timestampOffset,o=r.appendWindow,s=r.chunk,u=s.start,d=s.end,c=s.data,f=s.type,v=s.language,p=null!==(t=o[0])&&void 0!==t?t:0,h=null!==(n=o[1])&&void 0!==n?n:1/0;try{var m,g,y=function(e,t,n,r){a.Z.debug("NTSB: Finding parser for native text tracks:",e);var i=l.Z.nativeTextTracksParsers[e];if("function"!=typeof i)throw new Error("no parser found for the given text track");a.Z.debug("NTSB: Parser found, parsing...");var o=i(t,n,r);return a.Z.debug("NTSB: Parsed successfully!",o.length),o}(f,c,i,v);if(0!==p&&h!==1/0){for(var _=0;_=0&&y[_].startTime>=h;)_--;for(y.splice(_,y.length),_=y.length-1;_>=0&&y[_].endTime>h;)y[_].endTime=h,_--}if(void 0!==u)m=Math.max(p,u);else{if(y.length<=0)return a.Z.warn("NTSB: Current text tracks have no cues nor start time. Aborting"),Promise.resolve();a.Z.warn("NTSB: No start time given. Guessing from cues."),m=y[0].startTime}if(void 0!==d)g=Math.min(h,d);else{if(y.length<=0)return a.Z.warn("NTSB: Current text tracks have no cues nor end time. Aborting"),Promise.resolve();a.Z.warn("NTSB: No end time given. Guessing from cues."),g=y[y.length-1].endTime}if(g<=m)return a.Z.warn("NTSB: Invalid text track appended: ","the start time is inferior or equal to the end time."),Promise.resolve();if(y.length>0){var b=y[0],S=this._track.cues;null!==S&&S.length>0&&b.startTime=0;i--){var s=r[i],u=s.startTime,l=s.endTime;u>=e&&u<=t&&l<=t&&o(n,s)}this._buffered.remove(e,t)},t}(s.C)},9612:function(e,t,n){"use strict";n.d(t,{C:function(){return _},f:function(){return g}});var r=n(6872),i=n(3887),a=n(520),o=n(5278);function s(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return u(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return u(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(this._history=this._history.splice(r)),this._history.length>this._maxHistoryLength){var a=this._history.length-this._maxHistoryLength;this._history=this._history.splice(a)}},e}();function d(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return c(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return c(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){var _=t[p+y-1];g={end:(0,o.Z)(_.bufferedEnd,_.end),precizeEnd:_.precizeEnd},i.Z.debug("SI: "+y+" segments GCed.",u);for(var b,S=d(t.splice(p,y));!(b=S()).done;){var T=b.value;void 0===T.bufferedStart&&void 0===T.bufferedEnd&&this._bufferedHistory.addBufferedSegment(T.infos,null)}n=p}if(void 0===a)return;if(v-(0,o.Z)(a.bufferedStart,a.start)>=s){if(h(a,f,g,u),n===t.length-1)return void m(a,v,u);a=t[++n];for(var E=(0,o.Z)(a.bufferedStart,a.start),k=(0,o.Z)(a.bufferedEnd,a.end),w=c=s&&(void 0===w||v-E>=k-w);){var A=t[n-1];void 0===A.bufferedEnd&&(A.bufferedEnd=a.precizeStart?a.start:A.end,i.Z.debug("SI: calculating buffered end of contiguous segment",u,A.bufferedEnd,A.end)),a.bufferedStart=A.bufferedEnd,void 0!==(a=t[++n])&&(E=(0,o.Z)(a.bufferedStart,a.start),k=(0,o.Z)(a.bufferedEnd,a.end))}}var I=t[n-1];void 0!==I&&m(I,v,u)}}if(null!=a){i.Z.debug("SI: last segments have been GCed",u,n,t.length);for(var Z,x=d(t.splice(n,t.length-n));!(Z=x()).done;){var R=Z.value;void 0===R.bufferedStart&&void 0===R.bufferedEnd&&this._bufferedHistory.addBufferedSegment(R.infos,null)}}void 0!==u&&i.Z.hasLevel("DEBUG")&&i.Z.debug("SI: current "+u+" inventory timeline:\n"+function(e){var t=1/60,n={},r=[],i=null,a=null;function o(e){var t=String.fromCharCode(r.length+65);return r.push({letter:t,periodId:e.period.id,representationId:e.representation.id,bitrate:e.representation.bitrate}),t}for(var s="",u=0;u=u)i.Z.warn("SI: Invalid chunked inserted: starts before it ends",l,s,u);else{for(var d=this._inventory,c={partiallyPushed:!0,chunkSize:o,splitted:!1,start:s,end:u,precizeStart:!1,precizeEnd:!1,bufferedStart:void 0,bufferedEnd:void 0,infos:{segment:a,period:t,adaptation:n,representation:r}},f=d.length-1;f>=0;f--){var v=d[f];if(v.start<=s){if(v.end<=s){for(i.Z.debug("SI: Pushing segment strictly after previous one.",l,s,v.end),this._inventory.splice(f+1,0,c),f+=2;fc.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,c.end,d[f].start),d[f].start=c.end,d[f].bufferedStart=void 0,void(d[f].precizeStart=d[f].precizeStart&&c.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,d[f].start,d[f].end),d.splice(f,1)}return}if(v.start===s){if(v.end<=u){for(i.Z.debug("SI: Segment pushed replace another one",l,s,u,v.end),this._inventory.splice(f,1,c),f+=1;fc.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,c.end,d[f].start),d[f].start=c.end,d[f].bufferedStart=void 0,void(d[f].precizeStart=d[f].precizeStart&&c.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,d[f].start,d[f].end),d.splice(f,1)}return}return i.Z.debug("SI: Segment pushed ends before another with the same start",l,s,u,v.end),d.splice(f,0,c),v.start=c.end,v.bufferedStart=void 0,void(v.precizeStart=v.precizeStart&&c.precizeEnd)}if(v.end<=c.end){for(i.Z.debug("SI: Segment pushed updates end of previous one",l,s,u,v.start,v.end),this._inventory.splice(f+1,0,c),v.end=c.start,v.bufferedEnd=void 0,v.precizeEnd=v.precizeEnd&&c.precizeStart,f+=2;fc.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,c.end,d[f].start),d[f].start=c.end,d[f].bufferedStart=void 0,void(d[f].precizeStart=d[f].precizeStart&&c.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,d[f].start,d[f].end),d.splice(f,1)}return}i.Z.warn("SI: Segment pushed is contained in a previous one",l,s,u,v.start,v.end);var p={partiallyPushed:v.partiallyPushed,chunkSize:v.chunkSize,splitted:!0,start:c.end,end:v.end,precizeStart:v.precizeStart&&v.precizeEnd&&c.precizeEnd,precizeEnd:v.precizeEnd,bufferedStart:void 0,bufferedEnd:v.end,infos:v.infos};return v.end=c.start,v.splitted=!0,v.bufferedEnd=void 0,v.precizeEnd=v.precizeEnd&&c.precizeStart,d.splice(f+1,0,c),void d.splice(f+2,0,p)}}var h=this._inventory[0];if(void 0===h)return i.Z.debug("SI: first segment pushed",l,s,u),void this._inventory.push(c);if(!(h.start>=u)){if(h.end<=u){for(i.Z.debug("SI: Segment pushed starts before and completely recovers the previous first one",l,s,u,h.start,h.end),this._inventory.splice(0,1,c);d.length>1&&d[1].startc.end)return i.Z.debug("SI: Segment pushed updates the start of the next one",l,c.end,d[1].start),d[1].start=c.end,d[1].bufferedStart=void 0,void(d[1].precizeStart=c.precizeEnd);i.Z.debug("SI: Segment pushed removes the next one",l,s,u,d[1].start,d[1].end),d.splice(1,1)}return}return i.Z.debug("SI: Segment pushed start of the next one",l,s,u,h.start,h.end),h.start=u,h.bufferedStart=void 0,h.precizeStart=c.precizeEnd,void this._inventory.splice(0,0,c)}i.Z.debug("SI: Segment pushed comes before all previous ones",l,s,u,h.start),this._inventory.splice(0,0,c)}}},t.completeSegment=function(e,t){if(!e.segment.isInit){for(var n=this._inventory,r=[],o=0;o0&&(s=!0,1===r.length&&(i.Z.warn("SI: Completed Segment is splitted.",e.segment.id,e.segment.time,e.segment.end),r[0].splitted=!0));var u=o,l=n[o].chunkSize;for(o+=1;o0&&(this._inventory.splice(u+1,v),o-=v),this._inventory[u].partiallyPushed=!1,this._inventory[u].chunkSize=l,this._inventory[u].end=p,this._inventory[u].bufferedEnd=h,this._inventory[u].splitted=s,r.push(this._inventory[u])}if(0===r.length)i.Z.warn("SI: Completed Segment not found",e.segment.id,e.segment.time);else{this.synchronizeBuffered(t);for(var m,g=d(r);!(m=g()).done;){var y=m.value;void 0!==y.bufferedStart&&void 0!==y.bufferedEnd?this._bufferedHistory.addBufferedSegment(y.infos,{start:y.bufferedStart,end:y.bufferedEnd}):i.Z.debug("SI: buffered range not known after sync. Skipping history.",y.start,y.end)}}}},t.getInventory=function(){return this._inventory},t.getHistoryFor=function(e){return this._bufferedHistory.getHistoryFor(e)},e}();function v(e){if(void 0===e.bufferedStart||e.partiallyPushed)return!1;var t=e.start,n=e.end-t,i=r.Z.getCurrent(),a=i.MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE,o=i.MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE;return Math.abs(t-e.bufferedStart)<=a&&(void 0===e.bufferedEnd||e.bufferedEnd>e.bufferedStart&&Math.abs(e.bufferedEnd-e.bufferedStart-n)<=Math.min(o,n/3))}function p(e){if(void 0===e.bufferedEnd||e.partiallyPushed)return!1;var t=e.start,n=e.end,i=n-t,a=r.Z.getCurrent(),o=a.MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE,s=a.MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE;return Math.abs(n-e.bufferedEnd)<=o&&null!=e.bufferedStart&&e.bufferedEnd>e.bufferedStart&&Math.abs(e.bufferedEnd-e.bufferedStart-i)<=Math.min(s,i/3)}function h(e,t,n,a){var o=r.Z.getCurrent().MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE;void 0!==e.bufferedStart?(e.bufferedStartt&&(n.precizeEnd||e.start-n.end<=o)?(i.Z.debug("SI: buffered start is end of previous segment",a,t,e.start,n.end),e.bufferedStart=n.end,v(e)&&(e.start=n.end,e.precizeStart=!0)):e.start-t<=o?(i.Z.debug("SI: found true buffered start",a,t,e.start),e.bufferedStart=t,v(e)&&(e.start=t,e.precizeStart=!0)):tt&&(i.Z.debug("SI: Segment partially GCed at the end",n,e.bufferedEnd,t),e.bufferedEnd=t),!e.precizeEnd&&t-e.end<=a&&p(e)&&(e.precizeEnd=!0,e.end=t)):e.precizeEnd?(i.Z.debug("SI: buffered end is precize end",n,e.end),e.bufferedEnd=e.end):t-e.end<=a?(i.Z.debug("SI: found true buffered end",n,t,e.end),e.bufferedEnd=t,p(e)&&(e.end=t,e.precizeEnd=!0)):t>e.end?(i.Z.debug("SI: range end too far from expected end",n,t,e.end),e.bufferedEnd=e.end):(i.Z.debug("SI: Segment appears immediately garbage collected at the end",n,e.bufferedEnd,t),e.bufferedEnd=t)}var g,y=f,_=function(){function e(){this._segmentInventory=new y}var t=e.prototype;return t.synchronizeInventory=function(){this._segmentInventory.synchronizeBuffered(this.getBufferedRanges())},t.getInventory=function(){return this._segmentInventory.getInventory()},t.getPendingOperations=function(){return[]},t.getSegmentHistory=function(e){return this._segmentInventory.getHistoryFor(e)},e}();!function(e){e[e.Push=0]="Push",e[e.Remove=1]="Remove",e[e.EndOfSegment=2]="EndOfSegment"}(g||(g={}))},4309:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(2829),i=function(){function e(){this._ranges=[],this.length=0}var t=e.prototype;return t.insert=function(e,t){(0,r.kR)(this._ranges,{start:e,end:t}),this.length=this._ranges.length},t.remove=function(e,t){var n=[];e>0&&n.push({start:0,end:e}),t<1/0&&n.push({start:t,end:1/0}),this._ranges=(0,r.tn)(this._ranges,n),this.length=this._ranges.length},t.start=function(e){if(e>=this._ranges.length)throw new Error("INDEX_SIZE_ERROR");return this._ranges[e].start},t.end=function(e){if(e>=this._ranges.length)throw new Error("INDEX_SIZE_ERROR");return this._ranges[e].end},e}()},7839:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(7326),i=n(4578),a=function(e){function t(n,i,a,o){var s;return s=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(s),t.prototype),s.name="CustomLoaderError",s.message=n,s.canRetry=i,s.isOfflineError=a,s.xhr=o,s}return(0,i.Z)(t,e),t}((0,n(2146).Z)(Error))},5157:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i,a){var u;return u=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(u),t.prototype),u.name="EncryptedMediaError",u.type=o.ZB.ENCRYPTED_MEDIA_ERROR,u.code=n,u.message=(0,s.Z)(u.name,u.code,i),u.fatal=!1,"string"==typeof(null==a?void 0:a.keyStatuses)&&(u.keyStatuses=a.keyStatuses),u}return(0,i.Z)(t,e),t}((0,a.Z)(Error))},5992:function(e,t,n){"use strict";n.d(t,{SM:function(){return a},ZB:function(){return r},br:function(){return i}});var r={NETWORK_ERROR:"NETWORK_ERROR",MEDIA_ERROR:"MEDIA_ERROR",ENCRYPTED_MEDIA_ERROR:"ENCRYPTED_MEDIA_ERROR",OTHER_ERROR:"OTHER_ERROR"},i={TIMEOUT:"TIMEOUT",ERROR_EVENT:"ERROR_EVENT",ERROR_HTTP_CODE:"ERROR_HTTP_CODE",PARSE_ERROR:"PARSE_ERROR"},a={PIPELINE_LOAD_ERROR:"PIPELINE_LOAD_ERROR",PIPELINE_PARSE_ERROR:"PIPELINE_PARSE_ERROR",INTEGRITY_ERROR:"INTEGRITY_ERROR",MANIFEST_PARSE_ERROR:"MANIFEST_PARSE_ERROR",MANIFEST_INCOMPATIBLE_CODECS_ERROR:"MANIFEST_INCOMPATIBLE_CODECS_ERROR",MANIFEST_UPDATE_ERROR:"MANIFEST_UPDATE_ERROR",MANIFEST_UNSUPPORTED_ADAPTATION_TYPE:"MANIFEST_UNSUPPORTED_ADAPTATION_TYPE",MEDIA_STARTING_TIME_NOT_FOUND:"MEDIA_STARTING_TIME_NOT_FOUND",MEDIA_TIME_BEFORE_MANIFEST:"MEDIA_TIME_BEFORE_MANIFEST",MEDIA_TIME_AFTER_MANIFEST:"MEDIA_TIME_AFTER_MANIFEST",MEDIA_TIME_NOT_FOUND:"MEDIA_TIME_NOT_FOUND",NO_PLAYABLE_REPRESENTATION:"NO_PLAYABLE_REPRESENTATION",MEDIA_IS_ENCRYPTED_ERROR:"MEDIA_IS_ENCRYPTED_ERROR",CREATE_MEDIA_KEYS_ERROR:"CREATE_MEDIA_KEYS_ERROR",KEY_ERROR:"KEY_ERROR",KEY_STATUS_CHANGE_ERROR:"KEY_STATUS_CHANGE_ERROR",KEY_UPDATE_ERROR:"KEY_UPDATE_ERROR",KEY_LOAD_ERROR:"KEY_LOAD_ERROR",KEY_LOAD_TIMEOUT:"KEY_LOAD_TIMEOUT",KEY_GENERATE_REQUEST_ERROR:"KEY_GENERATE_REQUEST_ERROR",INCOMPATIBLE_KEYSYSTEMS:"INCOMPATIBLE_KEYSYSTEMS",INVALID_ENCRYPTED_EVENT:"INVALID_ENCRYPTED_EVENT",INVALID_KEY_SYSTEM:"INVALID_KEY_SYSTEM",LICENSE_SERVER_CERTIFICATE_ERROR:"LICENSE_SERVER_CERTIFICATE_ERROR",MULTIPLE_SESSIONS_SAME_INIT_DATA:"MULTIPLE_SESSIONS_SAME_INIT_DATA",BUFFER_APPEND_ERROR:"BUFFER_APPEND_ERROR",BUFFER_FULL_ERROR:"BUFFER_FULL_ERROR",BUFFER_TYPE_UNKNOWN:"BUFFER_TYPE_UNKNOWN",MEDIA_ERR_BLOCKED_AUTOPLAY:"MEDIA_ERR_BLOCKED_AUTOPLAY",MEDIA_ERR_PLAY_NOT_ALLOWED:"MEDIA_ERR_PLAY_NOT_ALLOWED",MEDIA_ERR_NOT_LOADED_METADATA:"MEDIA_ERR_NOT_LOADED_METADATA",MEDIA_ERR_ABORTED:"MEDIA_ERR_ABORTED",MEDIA_ERR_NETWORK:"MEDIA_ERR_NETWORK",MEDIA_ERR_DECODE:"MEDIA_ERR_DECODE",MEDIA_ERR_SRC_NOT_SUPPORTED:"MEDIA_ERR_SRC_NOT_SUPPORTED",MEDIA_ERR_UNKNOWN:"MEDIA_ERR_UNKNOWN",MEDIA_SOURCE_NOT_SUPPORTED:"MEDIA_SOURCE_NOT_SUPPORTED",MEDIA_KEYS_NOT_SUPPORTED:"MEDIA_KEYS_NOT_SUPPORTED",DISCONTINUITY_ENCOUNTERED:"DISCONTINUITY_ENCOUNTERED",NONE:"NONE"}},7367:function(e,t,n){"use strict";function r(e,t,n){return e+" ("+t+") "+n}n.d(t,{Z:function(){return r}})},8750:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(9822),i=n(5389);function a(e,t){var n=t.defaultCode,a=t.defaultReason;if((0,r.Z)(e))return e;var o=e instanceof Error?e.toString():a;return new i.Z(n,o)}},9822:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(5157),i=n(5992),a=n(3714),o=n(9362),s=n(5389);function u(e){return(e instanceof r.Z||e instanceof a.Z||e instanceof s.Z||e instanceof o.Z)&&Object.keys(i.ZB).indexOf(e.type)>=0}},3714:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i){var a;return a=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(a),t.prototype),a.name="MediaError",a.type=o.ZB.MEDIA_ERROR,a.code=n,a.message=(0,s.Z)(a.name,a.code,i),a.fatal=!1,a}return(0,i.Z)(t,e),t}((0,a.Z)(Error))},9362:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i){var a;return a=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(a),t.prototype),a.name="NetworkError",a.type=o.ZB.NETWORK_ERROR,a.xhr=void 0===i.xhr?null:i.xhr,a.url=i.url,a.status=i.status,a.errorType=i.type,a.code=n,a.message=(0,s.Z)(a.name,a.code,i.message),a.fatal=!1,a}return(0,i.Z)(t,e),t.prototype.isHttpError=function(e){return this.errorType===o.br.ERROR_HTTP_CODE&&this.status===e},t}((0,a.Z)(Error))},5389:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(7326),i=n(4578),a=n(2146),o=n(5992),s=n(7367),u=function(e){function t(n,i){var a;return a=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(a),t.prototype),a.name="OtherError",a.type=o.ZB.OTHER_ERROR,a.code=n,a.message=(0,s.Z)(a.name,a.code,i),a.fatal=!1,a}return(0,i.Z)(t,e),t}((0,a.Z)(Error))},9105:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(7326),i=n(4578),a=function(e){function t(n,i,a,o){var s;return s=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(s),t.prototype),s.name="RequestError",s.url=n,void 0!==o&&(s.xhr=o),s.status=i,s.type=a,s.message=a,s}return(0,i.Z)(t,e),t}((0,n(2146).Z)(Error))},7273:function(e,t){"use strict";t.Z={dashParsers:{wasm:null,js:null},createDebugElement:null,directfile:null,ContentDecryptor:null,htmlTextTracksBuffer:null,htmlTextTracksParsers:{},imageBuffer:null,imageParser:null,nativeTextTracksBuffer:null,nativeTextTracksParsers:{},transports:{}}},7874:function(e,t,n){"use strict";var r=n(7273);t.Z=r.Z},3887:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(8894),i=new(function(){function e(){this.error=r.Z,this.warn=r.Z,this.info=r.Z,this.debug=r.Z,this._levels={NONE:0,ERROR:1,WARNING:2,INFO:3,DEBUG:4},this._currentLevel="NONE"}var t=e.prototype;return t.setLevel=function(e){var t,n=this._levels[e];"number"==typeof n?(t=n,this._currentLevel=e):(t=0,this._currentLevel="NONE"),this.error=t>=this._levels.ERROR?console.error.bind(console):r.Z,this.warn=t>=this._levels.WARNING?console.warn.bind(console):r.Z,this.info=t>=this._levels.INFO?console.info.bind(console):r.Z,this.debug=t>=this._levels.DEBUG?console.log.bind(console):r.Z},t.getLevel=function(){return this._currentLevel},t.hasLevel=function(e){return this._levels[e]>=this._levels[this._currentLevel]},e}())},8999:function(e,t,n){"use strict";n.d(t,{r:function(){return v},Z:function(){return p}});var r=n(3887),i=n(3274),a=n(1946),o=n(7829);var s="undefined"!=typeof window&&"function"==typeof window.Set&&"function"==typeof Array.from?function(e){return Array.from(new Set(e))}:function(e){return e.filter((function(e,t,n){return n.indexOf(e)===t}))},u=n(3774);var l=n(4791);function d(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return c(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return c(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(this.trickModeTracks=i.map((function(t){return new e(t)})));for(var d=t.representations,c=[],v=!1,p=0;p0&&!r.isSupported){var i=new a.Z("MANIFEST_INCOMPATIBLE_CODECS_ERROR","An Adaptation contains only incompatible codecs.");n.contentWarnings.push(i)}return r})).filter((function(e){return e.representations.length>0}));if(s.every((function(e){return!e.isSupported}))&&o.length>0&&("video"===i||"audio"===i))throw new a.Z("MANIFEST_PARSE_ERROR","No supported "+i+" adaptations");return s.length>0&&(r[i]=s),r}),{}),!Array.isArray(this.adaptations.video)&&!Array.isArray(this.adaptations.audio))throw new a.Z("MANIFEST_PARSE_ERROR","No supported audio and video tracks.");this.duration=e.duration,this.start=e.start,null!=this.duration&&null!=this.start&&(this.end=this.start+this.duration),this.streamEvents=void 0===e.streamEvents?[]:e.streamEvents}var t=e.prototype;return t.getAdaptations=function(){var e=this.adaptations;return(0,v.Z)(e).reduce((function(e,t){return null!=t?e.concat(t):e}),[])},t.getAdaptationsForType=function(e){var t=this.adaptations[e];return null==t?[]:t},t.getAdaptation=function(e){return(0,s.Z)(this.getAdaptations(),(function(t){var n=t.id;return e===n}))},t.getSupportedAdaptations=function(e){if(void 0===e)return this.getAdaptations().filter((function(e){return e.isSupported}));var t=this.adaptations[e];return void 0===t?[]:t.filter((function(e){return e.isSupported}))},t.containsTime=function(e){return e>=this.start&&(void 0===this.end||e=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function y(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0)o.Z.warn("Manifest: "+y.length+" new Representations found when merging."),(h=t.representations).push.apply(h,y),v.push.apply(v,y)}l=e},l=0;l0){o.Z.warn("Manifest: "+s.length+" new Adaptations found when merging.");for(var d,c=g(s);!(d=c()).done;){var f=d.value,v=e.adaptations[f.type];void 0===v?e.adaptations[f.type]=[f]:v.push(f),i.addedAdaptations.push(f)}}return i}function b(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return S(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return S(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function S(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&r._addSupplementaryImageAdaptations(u),o.length>0&&r._addSupplementaryTextAdaptations(o),r}(0,i.Z)(t,e);var n=t.prototype;return n.getPeriod=function(e){return(0,s.Z)(this.periods,(function(t){return e===t.id}))},n.getPeriodForTime=function(e){return(0,s.Z)(this.periods,(function(t){return e>=t.start&&(void 0===t.end||t.end>e)}))},n.getNextPeriod=function(e){return(0,s.Z)(this.periods,(function(t){return t.start>e}))},n.getPeriodAfter=function(e){var t=e.end;if(void 0===t)return null;var n=(0,s.Z)(this.periods,(function(e){return void 0===e.end||t0&&this.trigger("decipherabilityUpdate",t)},n.getAdaptations=function(){(0,c.Z)("manifest.getAdaptations() is deprecated. Please use manifest.period[].getAdaptations() instead");var e=this.periods[0];if(void 0===e)return[];var t=e.adaptations,n=[];for(var r in t)if(t.hasOwnProperty(r)){var i=t[r];n.push.apply(n,i)}return n},n.getAdaptationsForType=function(e){(0,c.Z)("manifest.getAdaptationsForType(type) is deprecated. Please use manifest.period[].getAdaptationsForType(type) instead");var t=this.periods[0];if(void 0===t)return[];var n=t.adaptations[e];return void 0===n?[]:n},n.getAdaptation=function(e){return(0,c.Z)("manifest.getAdaptation(id) is deprecated. Please use manifest.period[].getAdaptation(id) instead"),(0,s.Z)(this.getAdaptations(),(function(t){var n=t.id;return e===n}))},n._addSupplementaryImageAdaptations=function(e){var t=this,n=(Array.isArray(e)?e:[e]).map((function(e){var n=e.mimeType,r=e.url,i="gen-image-ada-"+T(),o="gen-image-rep-"+T(),s=(0,d.$)(r),u=r.substring(0,s),l=r.substring(s),c=new f.Z({id:i,type:"image",representations:[{bitrate:0,cdnMetadata:[{baseUrl:u}],id:o,mimeType:n,index:new h({media:l})}]},{isManuallyAdded:!0});if(c.representations.length>0&&!c.isSupported){var v=new a.Z("MANIFEST_INCOMPATIBLE_CODECS_ERROR","An Adaptation contains only incompatible codecs.");t.contentWarnings.push(v)}return c}));if(n.length>0&&this.periods.length>0){var r=this.periods[0].adaptations;r.image=null!=r.image?r.image.concat(n):n}},n._addSupplementaryTextAdaptations=function(e){var t=this,n=(Array.isArray(e)?e:[e]).reduce((function(e,n){var r=n.mimeType,i=n.codecs,o=n.url,s=n.language,u=n.languages,l=n.closedCaption,c=null!=s?[s]:null!=u?u:[],v=(0,d.$)(o),p=o.substring(0,v),m=o.substring(v);return e.concat(c.map((function(e){var n="gen-text-ada-"+T(),o="gen-text-rep-"+T(),s=new f.Z({id:n,type:"text",language:e,closedCaption:l,representations:[{bitrate:0,cdnMetadata:[{baseUrl:p}],id:o,mimeType:r,codecs:i,index:new h({media:m})}]},{isManuallyAdded:!0});if(s.representations.length>0&&!s.isSupported){var u=new a.Z("MANIFEST_INCOMPATIBLE_CODECS_ERROR","An Adaptation contains only incompatible codecs.");t.contentWarnings.push(u)}return s})))}),[]);if(n.length>0&&this.periods.length>0){var r=this.periods[0].adaptations;r.text=null!=r.text?r.text.concat(n):n}},n._performUpdate=function(e,t){var n;if(this.availabilityStartTime=e.availabilityStartTime,this.expired=e.expired,this.isDynamic=e.isDynamic,this.isLive=e.isLive,this.isLastPeriodKnown=e.isLastPeriodKnown,this.lifetime=e.lifetime,this.contentWarnings=e.contentWarnings,this.suggestedPresentationDelay=e.suggestedPresentationDelay,this.transport=e.transport,this.publishTime=e.publishTime,t===r.Full)this._timeBounds=e._timeBounds,this.uris=e.uris,n=function(e,t){for(var n={updatedPeriods:[],addedPeriods:[],removedPeriods:[]},i=0,a=0;ae.length)return o.Z.error("Manifest: error when updating Periods"),n;if(i0&&(e.push.apply(e,b),(y=n.addedPeriods).push.apply(y,b)),n}(this.periods,e.periods);else{this._timeBounds.maximumTimeData=e._timeBounds.maximumTimeData,this.updateUrl=e.uris[0],n=function(e,t){var n,i={updatedPeriods:[],addedPeriods:[],removedPeriods:[]};if(0===e.length)return e.splice.apply(e,[0,0].concat(t)),(n=i.addedPeriods).push.apply(n,t),i;if(0===t.length)return i;var s=e[e.length-1];if(s.startc){var E;o.Z.warn("Manifest: old Periods not found in new when updating, removing");var k=e.splice(c,p-c);(E=i.removedPeriods).push.apply(E,k),p=c}var w=_(e[p],v,r.Full);i.updatedPeriods.push({period:e[p],result:w})}c++}if(c0;){var s=this.periods[0];if(void 0===s.end||s.end>i)break;this.periods.shift()}}this.adaptations=void 0===this.periods[0]?{}:this.periods[0].adaptations,this.trigger("manifestUpdate",n)},t}(u.Z);var w=k},520:function(e,t,n){"use strict";n.d(t,{K:function(){return a},z:function(){return i}});var r=n(1946);function i(e,t){return e.segment.id===t.segment.id&&e.representation.id===t.representation.id&&e.adaptation.id===t.adaptation.id&&e.period.id===t.period.id}function a(e){if((0,r.Z)(e))return"";var t=e.period,n=e.adaptation,i=e.representation,a=e.segment;return n.type+" P: "+t.id+" A: "+n.id+" R: "+i.id+" S: "+(a.isInit?"init":a.complete?a.time+"-"+a.duration:""+a.time)}},2689:function(e,t,n){"use strict";n.d(t,{s:function(){return r}});var r=Math.pow(2,32)-1},2297:function(e,t,n){"use strict";n.d(t,{Qy:function(){return f},Xj:function(){return p},iz:function(){return c},lp:function(){return d},nR:function(){return v},t_:function(){return l},vA:function(){return u}});var r=n(3887),i=n(811),a=n(6968);function o(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return s(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return s(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ni)return null;s=(0,a.pV)(e,r),r+=8}if(s<0)throw new Error("ISOBMFF: Size out of range");if(n===t)return 1970628964===t&&(r+=16),[o,r,o+s];o+=s}return null}function v(e,t,n,r,i){for(var o,s=e.length,u=0;us)return;o=(0,a.pV)(e,l),l+=8}if(1970628964===d&&l+16<=s&&(0,a.pX)(e,l)===t&&(0,a.pX)(e,l+4)===n&&(0,a.pX)(e,l+8)===r&&(0,a.pX)(e,l+12)===i)return l+=16,e.subarray(l,u+o)}}function p(e){var t=e.length;if(t<8)return r.Z.warn("ISOBMFF: box inferior to 8 bytes, cannot find offsets"),null;var n=0,i=(0,a.pX)(e,n);n+=4;var o=(0,a.pX)(e,n);if(n+=4,0===i)i=t;else if(1===i){if(n+8>t)return r.Z.warn("ISOBMFF: box too short, cannot find offsets"),null;i=(0,a.pV)(e,n),n+=8}if(i<0)throw new Error("ISOBMFF: Size out of range");return 1970628964===o&&(n+=16),[0,n,i]}},6807:function(e,t,n){"use strict";n.d(t,{E3:function(){return u},Le:function(){return o},XA:function(){return i},fs:function(){return s},uq:function(){return a}});var r=n(2297);function i(e){var t=(0,r.t_)(e,1836019558);return null===t?null:(0,r.t_)(t,1953653094)}function a(e){return(0,r.lp)(e,1836019558).reduce((function(e,t){var n=(0,r.t_)(t,1953653094);return null!==n&&e.push(n),e}),[])}function o(e){return(0,r.t_)(e,1835295092)}function s(e){var t=(0,r.t_)(e,1836019574);if(null===t)return null;var n=(0,r.t_)(t,1953653099);return null===n?null:(0,r.t_)(n,1835297121)}function u(e,t){return void 0===t&&(t=0),(0,r.t_)(e.subarray(t),1701671783)}},6490:function(e,t,n){"use strict";n.d(t,{Z:function(){return s},Y:function(){return u}});var r=n(3887);var i="function"==typeof Uint8Array.prototype.slice?function(e,t,n){return e.slice(t,n)}:function(e,t,n){return new Uint8Array(Array.prototype.slice.call(e,t,n))},a=n(3635),o=n(2297);function s(e){var t=0,n=(0,o.t_)(e,1836019574);if(null===n)return[];for(var a=[];t1)r.Z.warn("ISOBMFF: un-handled PSSH version");else{var n=t+4;if(!(n+16>e.length)){var o=i(e,n,n+16);return(0,a.ci)(o)}}}},4644:function(e,t,n){"use strict";n.d(t,{J6:function(){return m},LD:function(){return h},MM:function(){return p},Qx:function(){return f},R0:function(){return y},Wf:function(){return c},s9:function(){return g}});var r=n(3887),i=n(6968),a=n(3635),o=n(2689),s=n(2297),u=n(6807);function l(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return d(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return d(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0;){var v=(0,i.pX)(e,o);o+=4;var p=2147483647&v;if(1===(2147483648&v)>>>31)throw new Error("sidx with reference_type `1` not yet implemented");var h=(0,i.pX)(e,o);o+=4,o+=4,c.push({time:l,duration:h,timescale:d,range:[r,r+p-1]}),l+=h,r+=p}return c}function f(e){var t=(0,u.XA)(e);if(null!==t){var n=(0,s.t_)(t,1952867444);if(null!==n){var r=n[0];return 1===r?(0,i.pV)(n,4):0===r?(0,i.pX)(n,4):void 0}}}function v(e){var t=(0,s.t_)(e,1952868452);if(null!==t){var n=1,r=(0,i.QI)(t,n);if(n+=3,(8&r)>0)return n+=4,(1&r)>0&&(n+=8),(2&r)>0&&(n+=4),(0,i.pX)(t,n)}}function p(e){var t=(0,u.uq)(e);if(0!==t.length){for(var n,r=0,a=l(t);!(n=a()).done;){var o=n.value,d=(0,s.t_)(o,1953658222);if(null===d)return;var c=0,f=d[c];if(c+=1,f>1)return;var p=(0,i.QI)(d,c);c+=3;var h=(256&p)>0,m=0;if(!h&&void 0===(m=v(o)))return;var g=(1&p)>0,y=(4&p)>0,_=(512&p)>0,b=(1024&p)>0,S=(2048&p)>0,T=(0,i.pX)(d,c);c+=4,g&&(c+=4),y&&(c+=4);for(var E=T,k=0;E-- >0;)h?(k+=(0,i.pX)(d,c),c+=4):k+=m,_&&(c+=4),b&&(c+=4),S&&(c+=4);r+=k}return r}}function h(e){var t=(0,u.fs)(e);if(null!==t){var n=(0,s.t_)(t,1835296868);if(null!==n){var r=0,a=n[r];return r+=4,1===a?(0,i.pX)(n,r+16):0===a?(0,i.pX)(n,r+8):void 0}}}function m(e){var t=e.length;if(t<4)throw new Error("Cannot update box length: box too short");var n=(0,i.pX)(e,0);if(0===n){if(t>o.s){var r=new Uint8Array(t+8);return r.set((0,i.kh)(1),0),r.set(e.subarray(4,8),4),r.set((0,i.el)(t+8),8),r.set(e.subarray(8,t),16),r}return e.set((0,i.kh)(t),0),e}if(1===n){if(t<16)throw new Error("Cannot update box length: box too short");return e.set((0,i.el)(t),8),e}if(t<=o.s)return e.set((0,i.kh)(t),0),e;var a=new Uint8Array(t+8);return a.set((0,i.kh)(1),0),a.set(e.subarray(4,8),4),a.set((0,i.el)(t+8),8),a.set(e.subarray(8,t),16),a}function g(e){for(var t=[],n=0;n0)throw new Error("Unhandled version: "+s);var c=(0,r.dN)(e,t);t+=4;var f=(0,r.dN)(e,t);t+=4;var v=(0,i.uR)(e.subarray(t,t+4));t+=4;var p=(0,r.qb)(e,t);t+=2;var h=(0,r.qb)(e,t),m=[e[t+=2],e[t+1]].join(":"),g=1===e[t+=2];t=64;var y=[];if(0===c)throw new Error("bif: no images to parse");for(var _=0,b=null;t0,this._isEMSGWhitelisted=c}var t=e.prototype;return t.getInitSegment=function(){return(0,a.Z)(this._index,this._isEMSGWhitelisted)},t.getSegments=function(e,t){return(0,o.Z)(this._index,e,t,this._isEMSGWhitelisted,this._scaledPeriodEnd)},t.shouldRefresh=function(){return!1},t.getFirstAvailablePosition=function(){var e=this._index;return 0===e.timeline.length?null:(0,i.zG)(Math.max(this._scaledPeriodStart,e.timeline[0].start),e)},t.getLastAvailablePosition=function(){var e,t=this._index.timeline;if(0===t.length)return null;var n=t[t.length-1],r=Math.min((0,i.jH)(n,null,this._scaledPeriodEnd),null!==(e=this._scaledPeriodEnd)&&void 0!==e?e:1/0);return(0,i.zG)(r,this._index)},t.getEnd=function(){return this.getLastAvailablePosition()},t.awaitSegmentBetween=function(){return!1},t.isSegmentStillAvailable=function(){return!0},t.checkDiscontinuity=function(){return null},t.initializeIndex=function(e){for(var t=0;t0?Math.floor(u/s):0),I=T+A*S;Ih)break;var x=null===v?null:(0,i.QB)(I,Z)(v),R=I-e.indexTimeOffset,M=S;R<0&&(M=S+R,R=0);var C={id:String(I),time:R/f,end:(R+M)/f,duration:M/f,isInit:!1,range:E,timescale:1,url:x,number:Z,timestampOffset:-e.indexTimeOffset/f,complete:w,privateInfos:{isEMSGWhitelisted:a}};g.push(C),I=T+ ++A*S}if(I>=d)return g;if(m+=k+1,void 0!==h&&m>h)return g}return g}},4784:function(e,t,n){"use strict";n.d(t,{QB:function(){return o},zA:function(){return a}});var r=n(6923);function i(e){return function(t,n,i){var a,o,s,u=(0,r.Z)(i)?parseInt(i,10):1;return a=String(e),o=u,(s=a.toString()).length>=o?s:(new Array(o+1).join("0")+s).slice(-o)}}function a(e,t,n){return function(e,t,n){return-1===e.indexOf("$")?e:e.replace(/\$\$/g,"$").replace(/\$RepresentationID\$/g,String(t)).replace(/\$Bandwidth(\%0(\d+)d)?\$/g,i(void 0===n?0:n))}(e,t,n)}function o(e,t){return function(n){return-1===n.indexOf("$")?n:n.replace(/\$\$/g,"$").replace(/\$Number(\%0(\d+)d)?\$/g,(function(e,n,r){if(void 0===t)throw new Error("Segment number not defined in a $Number$ scheme");return i(t)(e,n,r)})).replace(/\$Time(\%0(\d+)d)?\$/g,(function(t,n,r){if(void 0===e)throw new Error("Segment time not defined in a $Time$ scheme");return i(e)(t,n,r)}))}}},4541:function(e,t,n){"use strict";n.d(t,{Z:function(){return je}});var r=n(7904),i=n(1946),a=n(6872),o=n(3887),s=n(3274),u=n(9829);function l(e){var t=Date.parse(e)-performance.now();if(!isNaN(t))return t;o.Z.warn("DASH Parser: Invalid clock received: ",e)}function d(e){for(var t=e.representations,n=null,r=0;r=0;t--){var n=e[t].adaptations,r=void 0===n.audio?void 0:n.audio[0],i=void 0===n.video?void 0:n.video[0];if(void 0!==r||void 0!==i){var a=null,s=null;if(void 0!==r){var u=d(r);if(void 0===u)return{safe:void 0,unsafe:void 0};a=u}if(void 0!==i){var l=d(i);if(void 0===l)return{safe:void 0,unsafe:void 0};s=l}if(void 0!==r&&null===a||void 0!==i&&null===s)return o.Z.info("Parser utils: found Period with no segment. ","Going to previous one to calculate last position"),{safe:void 0,unsafe:void 0};if(null!==s)return null!==a?{safe:Math.min(a,s),unsafe:Math.max(a,s)}:{safe:s,unsafe:s};if(null!==a)return{safe:a,unsafe:a}}}return{safe:void 0,unsafe:void 0}}(e);return{minimumSafePosition:t,maximumSafePosition:n.safe,maximumUnsafePosition:n.unsafe}}var v=n(9592),p=n(908),h=n(1679),m=n(3635);var g=function(){function e(e){this._isDynamic=e.isDynamic,this._timeShiftBufferDepth=e.isDynamic&&void 0!==e.timeShiftBufferDepth?e.timeShiftBufferDepth:null}var t=e.prototype;return t.setLastPosition=function(e,t){this._lastPosition=e,this._positionTime=t},t.lastPositionIsKnown=function(){return this._isDynamic?null!=this._positionTime&&null!=this._lastPosition:null!=this._lastPosition},t.estimateMinimumBound=function(){if(!this._isDynamic||null===this._timeShiftBufferDepth)return 0;var e=this.estimateMaximumBound();return void 0!==e?e-this._timeShiftBufferDepth:void 0},t.estimateMaximumBound=function(){return this._isDynamic&&null!=this._positionTime&&null!=this._lastPosition?Math.max(this._lastPosition-this._positionTime+performance.now()/1e3,0):this._lastPosition},e}(),y=n(8999),_=n(5138),b=n(7714),S=n(6923);function T(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return E(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return E(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function E(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){var s=i-a.start;if(s%a.duration==0&&s/a.duration<=a.repeatCount)return{repeatNumberInPrevSegments:s/a.duration,prevSegmentsIdx:o,newElementsIdx:0,repeatNumberInNewElements:0}}if(++o>=e.length)return null;if((a=e[o]).start===i)return{prevSegmentsIdx:o,newElementsIdx:0,repeatNumberInPrevSegments:0,repeatNumberInNewElements:0};if(a.start>i)return null}else for(var u=0,l=t[0],d=i;;){var c=l.getAttribute("d"),f=null===c?null:parseInt(c,10);if(null===f||Number.isNaN(f))return null;var v=l.getAttribute("r"),p=null===v?null:parseInt(v,10);if(null!==p){if(Number.isNaN(p)||p<0)return null;if(p>0){var h=n-d;if(h%f==0&&h/f<=p)return{repeatNumberInPrevSegments:0,repeatNumberInNewElements:h/f,prevSegmentsIdx:0,newElementsIdx:u}}d+=f*(p+1)}else d+=f;if(++u>=t.length)return null;var m=(l=t[u]).getAttribute("t"),g=null===m?null:parseInt(m,10);if(null!==g){if(Number.isNaN(g))return null;d=g}if(d===n)return{newElementsIdx:u,prevSegmentsIdx:0,repeatNumberInPrevSegments:0,repeatNumberInNewElements:0};if(d>i)return null}}(t,e);if(null===r)return o.Z.warn('DASH: Cannot perform "based" update. Common segment not found.'),z(e);var i=r.prevSegmentsIdx,a=r.newElementsIdx,s=r.repeatNumberInPrevSegments,u=r.repeatNumberInNewElements,l=t.length-i+a-1;if(l>=e.length)return o.Z.info('DASH: Cannot perform "based" update. New timeline too short'),z(e);var d=t.slice(i);if(s>0){var c=d[0];c.start+=c.duration*s,d[0].repeatCount-=s}if(u>0&&0!==a)return o.Z.info('DASH: Cannot perform "based" update. The new timeline has a different form.'),z(e);var f=d[d.length-1],v=V(e[l]),p=(null!==(n=v.repeatCount)&&void 0!==n?n:0)-u;if(v.duration!==f.duration||f.repeatCount>p)return o.Z.info('DASH: Cannot perform "based" update. The new timeline has a different form at the beginning.'),z(e);void 0!==v.repeatCount&&v.repeatCount>f.repeatCount&&(f.repeatCount=v.repeatCount);for(var h=[],m=[],g=l+1;g0){var s=i[i.length-1];if((0,x.jH)(s,null,this._scaledPeriodEnd)+a>=Math.min(o,null!==(n=this._scaledPeriodEnd)&&void 0!==n?n:1/0))return!1}return void 0===this._scaledPeriodEnd?o+a>this._scaledPeriodStart&&void 0:(0,x.gT)(e,this._index)-athis._scaledPeriodStart},t.isSegmentStillAvailable=function(e){if(e.isInit)return!0;this._refreshTimeline(),null===this._index.timeline&&(this._index.timeline=this._getTimeline());var t=this._index,n=t.timeline,r=t.timescale,i=t.indexTimeOffset;return(0,O.Z)(e,n,r,i)},t.checkDiscontinuity=function(e){this._refreshTimeline();var t=this._index.timeline;return null===t&&(t=this._getTimeline(),this._index.timeline=t),(0,x._j)({timeline:t,timescale:this._index.timescale,indexTimeOffset:this._index.indexTimeOffset},e,this._scaledPeriodEnd)},t.canBeOutOfSyncError=function(e){return!!this._isDynamic&&(e instanceof P.Z&&e.isHttpError(404))},t._replace=function(e){this._parseTimeline=e._parseTimeline,this._index=e._index,this._isDynamic=e._isDynamic,this._scaledPeriodStart=e._scaledPeriodStart,this._scaledPeriodEnd=e._scaledPeriodEnd,this._lastUpdate=e._lastUpdate,this._manifestBoundsCalculator=e._manifestBoundsCalculator,this._isLastPeriod=e._isLastPeriod},t._update=function(e){null===this._index.timeline&&(this._index.timeline=this._getTimeline()),null===e._index.timeline&&(e._index.timeline=e._getTimeline()),(0,L.Z)(this._index.timeline,e._index.timeline)&&(this._index.startNumber=e._index.startNumber),this._index.endNumber=e._index.endNumber,this._isDynamic=e._isDynamic,this._scaledPeriodStart=e._scaledPeriodStart,this._scaledPeriodEnd=e._scaledPeriodEnd,this._lastUpdate=e._lastUpdate,this._isLastPeriod=e._isLastPeriod},t.isFinished=function(){if(!this._isDynamic||!this._isLastPeriod)return!0;null===this._index.timeline&&(this._index.timeline=this._getTimeline());var e=this._index.timeline;if(void 0===this._scaledPeriodEnd||0===e.length)return!1;var t=e[e.length-1];return(0,x.jH)(t,null,this._scaledPeriodEnd)+B(this._index.timescale)>=this._scaledPeriodEnd},t.isInitialized=function(){return!0},e.isTimelineIndexArgument=function(e){return"function"==typeof e.timelineParser||Array.isArray(e.timeline)},t._refreshTimeline=function(){if(null===this._index.timeline&&(this._index.timeline=this._getTimeline()),this._isDynamic){var e=this._manifestBoundsCalculator.estimateMinimumBound();if(null!=e){var t=(0,x.gT)(e,this._index),n=(0,N.Z)(this._index.timeline,t);void 0!==this._index.startNumber?this._index.startNumber+=n:void 0!==this._index.endNumber&&(this._index.startNumber=n+1)}}},e.getIndexEnd=function(e,t){return e.length<=0?null:Math.min((0,x.jH)(e[e.length-1],null,t),null!=t?t:1/0)},t._getTimeline=function(){if(null===this._parseTimeline)return null!==this._index.timeline?this._index.timeline:(o.Z.error("DASH: Timeline already lazily parsed."),[]);var e=this._parseTimeline();this._parseTimeline=null;var t,n=a.Z.getCurrent().MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY;return null===this._unsafelyBaseOnPreviousIndex||e.lengthn){if(r===n+1)return e.slice(0,i+1);var o=e.slice(0,i),s=Object.assign({},a),u=r-a.repeatCount-1;return s.repeatCount=Math.max(0,n-u),o.push(s),o}}return e}var H=G,j=function(){function e(e,t){var n,r,i=t.aggressiveMode,a=t.availabilityTimeOffset,o=t.manifestBoundsCalculator,s=t.isDynamic,u=t.periodEnd,l=t.periodStart,d=t.representationId,c=t.representationBitrate,f=t.isEMSGWhitelisted,v=null!==(n=e.timescale)&&void 0!==n?n:1;this._availabilityTimeOffset=a,this._manifestBoundsCalculator=o,this._aggressiveMode=i;var p=null!=e.presentationTimeOffset?e.presentationTimeOffset:0,h=p-l*v;if(void 0===e.duration)throw new Error("Invalid SegmentTemplate: no duration");var m=void 0===(null===(r=e.initialization)||void 0===r?void 0:r.media)?null:(0,M.zA)(e.initialization.media,d,c),g=void 0===e.media?null:(0,M.zA)(e.media,d,c);this._index={duration:e.duration,timescale:v,indexRange:e.indexRange,indexTimeOffset:h,initialization:null==e.initialization?void 0:{url:m,range:e.initialization.range},url:g,presentationTimeOffset:p,startNumber:e.startNumber,endNumber:e.endNumber},this._isDynamic=s,this._periodStart=l,this._scaledRelativePeriodEnd=void 0===u?void 0:(u-l)*v,this._isEMSGWhitelisted=f}var t=e.prototype;return t.getInitSegment=function(){return(0,R.Z)(this._index,this._isEMSGWhitelisted)},t.getSegments=function(e,t){var n=this._index,r=n.duration,i=n.startNumber,a=n.endNumber,o=n.timescale,s=n.url,u=this._periodStart*o,l=this._scaledRelativePeriodEnd,d=e*o-u,c=(e+t)*o-u,f=this._getFirstSegmentStart(),v=this._getLastSegmentStart();if(null==f||null==v)return[];var p=Math.max(f,d),h=Math.min(v,c);if(h+r<=p)return[];for(var m=[],g=null!=i?i:1,y=Math.floor(p/r),_=y*r;_<=h;_+=r){var b=y+g;if(void 0!==a&&b>a)return m;var S=null!=l&&_+r>l?l-_:r,T=_+u,E=_+this._index.presentationTimeOffset,k=null===s?null:(0,M.QB)(E,b)(s),w={id:String(b),number:b,time:T/o,end:(T+S)/o,duration:S/o,timescale:1,isInit:!1,scaledDuration:S/o,url:k,timestampOffset:-n.indexTimeOffset/o,complete:!0,privateInfos:{isEMSGWhitelisted:this._isEMSGWhitelisted}};m.push(w),y++}return m},t.getFirstAvailablePosition=function(){var e=this._getFirstSegmentStart();return null==e?e:e/this._index.timescale+this._periodStart},t.getLastAvailablePosition=function(){var e=this._getLastSegmentStart();if((0,i.Z)(e))return e;var t=this._estimateRelativeScaledEnd();return Math.min(e+this._index.duration,null!=t?t:1/0)/this._index.timescale+this._periodStart},t.getEnd=function(){if(!this._isDynamic)return this.getLastAvailablePosition();var e=this._estimateRelativeScaledEnd();if(void 0!==e){var t=this._index.timescale;return(e+this._periodStart*t)/t}},t.awaitSegmentBetween=function(e,t){if((0,D.Z)(e<=t),!this._isDynamic)return!1;var n=this._index.timescale,r=B(n),i=this._periodStart*n,a=t*n-i,o=this._estimateRelativeScaledEnd();return void 0===o?a+r>=0:e*n-i-r=e},t.isInitialized=function(){return!0},t._replace=function(e){this._index=e._index,this._aggressiveMode=e._aggressiveMode,this._isDynamic=e._isDynamic,this._periodStart=e._periodStart,this._scaledRelativePeriodEnd=e._scaledRelativePeriodEnd,this._manifestBoundsCalculator=e._manifestBoundsCalculator},t._update=function(e){this._replace(e)},t._getFirstSegmentStart=function(){if(!this._isDynamic)return 0;if(0===this._scaledRelativePeriodEnd||void 0===this._scaledRelativePeriodEnd){var e=this._manifestBoundsCalculator.estimateMaximumBound();if(void 0!==e&&ethis._periodStart?(i-this._periodStart)*r:0;return Math.floor(a/n)*n}},t._getLastSegmentStart=function(){var e,t=this._index,n=t.duration,r=t.timescale,i=t.endNumber,o=t.startNumber,s=void 0===o?1:o;if(this._isDynamic){var u=this._manifestBoundsCalculator.estimateMaximumBound();if(void 0===u)return;var l=this._aggressiveMode?n/r:0;if(void 0!==this._scaledRelativePeriodEnd&&this._scaledRelativePeriodEnd<(u+l-this._periodStart)*this._index.timescale){var d=Math.ceil(this._scaledRelativePeriodEnd/n);return void 0!==i&&i-s+1g||h<2?m:(h-2)*n},t._estimateRelativeScaledEnd=function(){var e,t;if(void 0!==this._index.endNumber){var n=this._index.endNumber-(null!==(e=this._index.startNumber)&&void 0!==e?e:1)+1;return Math.max(Math.min(n*this._index.duration,null!==(t=this._scaledRelativePeriodEnd)&&void 0!==t?t:1/0),0)}if(void 0!==this._scaledRelativePeriodEnd)return Math.max(this._scaledRelativePeriodEnd,0)},e}();function q(e,t){var n;if(0===t.length)return e;var r=t.map((function(e){return{url:e.value}}));if(0===e.length)return r;for(var i=[],a=0;a=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function X(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){var y=t.parentSegmentTemplates.slice(),_=e.children.segmentTemplate;void 0!==_&&y.push(_);var b=I.Z.apply(void 0,[{}].concat(y));h.availabilityTimeComplete=null!==(n=b.availabilityTimeComplete)&&void 0!==n?n:t.availabilityTimeComplete,h.availabilityTimeOffset=(null!==(r=b.availabilityTimeOffset)&&void 0!==r?r:0)+t.availabilityTimeOffset,i=H.isTimelineIndexArgument(b)?new H(b,h):new j(b,h)}else{var S=t.adaptation.children;if(void 0!==S.segmentBase){var T=S.segmentBase;i=new Z.Z(T,h)}else if(void 0!==S.segmentList){var E=S.segmentList;i=new C(E,h)}else i=new j({duration:Number.MAX_VALUE,timescale:1,startNumber:0,media:""},h)}return i}(e,(0,I.Z)({},n,{availabilityTimeOffset:h,availabilityTimeComplete:p,unsafelyBaseOnPreviousRepresentation:f,adaptation:t,inbandEventStreams:v})),g=void 0;null==e.attributes.bitrate?(o.Z.warn("DASH: No usable bitrate found in the Representation."),g=0):g=e.attributes.bitrate;var y=q(n.baseURLs,e.children.baseURLs),_={bitrate:g,cdnMetadata:0===y.length?[{baseUrl:"",id:void 0}]:y.map((function(e){return{baseUrl:e.url,id:e.serviceLocation}})),index:m,id:c},b=void 0;null!=e.attributes.codecs?b=e.attributes.codecs:null!=t.attributes.codecs&&(b=t.attributes.codecs),null!=b&&(b="mp4a.40.02"===b?"mp4a.40.2":b,_.codecs=b),null!=e.attributes.frameRate?_.frameRate=e.attributes.frameRate:null!=t.attributes.frameRate&&(_.frameRate=t.attributes.frameRate),null!=e.attributes.height?_.height=e.attributes.height:null!=t.attributes.height&&(_.height=t.attributes.height),null!=e.attributes.mimeType?_.mimeType=e.attributes.mimeType:null!=t.attributes.mimeType&&(_.mimeType=t.attributes.mimeType),null!=e.attributes.width?_.width=e.attributes.width:null!=t.attributes.width&&(_.width=t.attributes.width);var S=void 0!==t.children.contentProtections?t.children.contentProtections:[];if(void 0!==e.children.contentProtections&&S.push.apply(S,e.children.contentProtections),S.length>0){var T=S.reduce((function(e,t){var n;if(void 0!==t.attributes.schemeIdUri&&"urn:uuid:"===t.attributes.schemeIdUri.substring(0,9)&&(n=t.attributes.schemeIdUri.substring(9).replace(/-/g,"").toLowerCase()),void 0!==t.attributes.keyId&&t.attributes.keyId.length>0){var r={keyId:t.attributes.keyId,systemId:n};void 0===e.keyIds?e.keyIds=[r]:e.keyIds.push(r)}if(void 0!==n){for(var i,a=[],o=Y(t.children.cencPssh);!(i=o()).done;){var u=i.value;a.push({systemId:n,data:u})}if(a.length>0){var l,d=(0,s.Z)(e.initData,(function(e){return"cenc"===e.type}));if(void 0===d)e.initData.push({type:"cenc",values:a});else(l=d.values).push.apply(l,a)}}return e}),{keyIds:void 0,initData:[]});(Object.keys(T.initData).length>0||void 0!==T.keyIds&&T.keyIds.length>0)&&(_.contentProtections=T)}_.hdrInfo=Q({adaptationProfiles:t.attributes.profiles,supplementalProperties:t.children.supplementalProperties,essentialProperties:t.children.essentialProperties,manifestProfiles:n.manifestProfiles,codecs:b}),d.push(_)},f=Y(e);!(l=f()).done;)c();return d}function J(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return ee(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ee(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ee(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ue(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(n.sort(oe),e[t]=n.map((function(e){return e[0]}))),e}),{});return d.video.sort(oe),k(le,c),le}(w.children.adaptations,V),K=(null!==(u=t.xmlNamespaces)&&void 0!==u?u:[]).concat(null!==(l=w.attributes.namespaces)&&void 0!==l?l:[]),G=function(e,t,n){for(var r,i,a,o=[],s=se(e);!(a=s()).done;)for(var u,l=a.value,d=l.attributes,c=d.schemeIdUri,f=void 0===c?"":c,v=d.timescale,p=void 0===v?1:v,h=n.concat(null!==(r=l.attributes.namespaces)&&void 0!==r?r:[]),g=se(l.children.events);!(u=g()).done;){var y=u.value;if(void 0!==y.eventStreamData){var _=(null!==(i=y.presentationTime)&&void 0!==i?i:0)/p+t,b=void 0===y.duration?void 0:_+y.duration/p,S=void 0;if(y.eventStreamData instanceof Element)S=y.eventStreamData;else{var T=h.reduce((function(e,t){return e+"xmlns:"+t.key+'="'+t.value+'" '}),"","application/xml").documentElement.childNodes[0]}o.push({start:_,end:b,id:y.id,data:{type:"dash-event-stream",value:{schemeIdUri:f,timescale:p,element:S}}})}}return o}(w.children.eventStreams,R,K),W={id:P,start:R,end:C,duration:M,adaptations:z,streamEvents:G};if(d.unshift(W),!T.lastPositionIsKnown()){var H=function(e){for(var t,n=null,r=!0,i=(0,h.Z)(e).filter((function(e){return null!=e})),a=se((0,v.Z)(i,(function(e){return e})));!(t=a()).done;)for(var o,s=se(t.value.representations);!(o=s()).done;){var u=o.value.index.getLastAvailablePosition();null!==u&&(r=!1,"number"==typeof u&&(n=null==n?u:Math.max(n,u)))}if(null!=n)return n;if(r)return null;return}(z);if(f)if("number"==typeof H){var j=performance.now()/1e3;T.setLastPosition(H,j)}else{var Y=ce(t,R);if(void 0!==Y){var X=Y[0],Q=Y[1];T.setLastPosition(X,Q)}}else"number"==typeof H&&T.setLastPosition(H)}},w=e.length-1;w>=0;w--)E(w);if(t.isDynamic&&!T.lastPositionIsKnown()){var I=ce(t,0);if(void 0!==I){var Z=I[0],x=I[1];T.setLastPosition(Z,x)}}return function(e){if(0===e.length)return[];for(var t=[e[0]],n=1;nr.start)&&(o.Z.warn("DASH: Updating overlapping Periods.",null==i?void 0:i.start,r.start),i.duration=r.start-i.start,i.end=r.start,!(i.duration>0));)t.pop(),i=t[t.length-1];t.push(r)}return t}(d)}function ce(e,t){if(null!=e.clockOffset){var n=e.clockOffset/1e3-e.availabilityStartTime,r=performance.now()/1e3,i=r+n;if(i>=t)return[i,r]}else{var a=Date.now()/1e3;if(a>=t)return o.Z.warn("DASH Parser: no clock synchronization mechanism found. Using the system clock instead."),[a-e.availabilityStartTime,performance.now()/1e3]}}function fe(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return ve(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ve(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ve(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0?t[0].value:void 0}(t);if(null!=y&&y.length>0)return{type:"needs-clock",value:{url:y,continue:function(i){return i.success?(n.externalClockOffset=l(i.data),e(t,n,r,!0)):(r.push(i.error),o.Z.warn("DASH Parser: Error on fetching the clock ressource",i.error),e(t,n,r,!0))}}}}}for(var _=[],b=0;b=0&&(d=0===h.minimumUpdatePeriod?a.Z.getCurrent().DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0:h.minimumUpdatePeriod);var A=f(E),I=A.minimumSafePosition,Z=A.maximumSafePosition,x=A.maximumUnsafePosition,R=performance.now();if(m){var M,C;if(c=I,w=null!=_?_:null,void 0!==x&&(C=x),void 0!==Z)M=Z;else{var P=null!=y?y:0,D=t.externalClockOffset;if(void 0===D)o.Z.warn("DASH Parser: use system clock to define maximum position"),M=Date.now()/1e3-P;else M=(performance.now()+D)/1e3-P}void 0===C&&(C=M),v={isLinear:!0,maximumSafePosition:M,livePosition:C,time:R},null!==w&&void 0!==c&&M-c>w&&(w=M-c)}else{c=void 0!==I?I:void 0!==(null===(i=E[0])||void 0===i?void 0:i.start)?E[0].start:0;var N=null!=k?k:1/0;if(void 0!==E[E.length-1]){var O=E[E.length-1],L=null!==(s=O.end)&&void 0!==s?s:void 0!==O.duration?O.start+O.duration:void 0;void 0!==L&&L=0;o--){var s,u=_[o].index,l=a[o],f=l.parsed,v=l.warnings,p=l.receivedTime,h=l.sendingTime,m=l.url;v.length>0&&r.push.apply(r,v);for(var g,y=fe(f);!(g=y()).done;){var b=g.value;d.set(b,{receivedTime:p,sendingTime:h,url:m})}(s=c.periods).splice.apply(s,[u,1].concat(f))}return e(t,n,r,i,d)}}}};function he(e){var t=e.textContent,n=[];return null===t||0===t.length?[void 0,n]:[{value:t},n]}function me(e){for(var t={},n=0;n0){var s=Re(a,"cenc:pssh"),u=s[0],l=s[1];null!==l&&(o.Z.warn(l.message),t.push(l)),null!==u&&n.push(u)}}}return[{cencPssh:n},t]}(e.childNodes),n=t[0],r=t[1];return[{children:n,attributes:function(e){for(var t={},n=0;n0&&(r=r.concat(c));break;case"SegmentList":var f=Ue(a),v=f[0],p=f[1];r=r.concat(p),t.segmentList=v;break;case"SegmentTemplate":var h=Fe(a),m=h[0],g=h[1];r=r.concat(g),t.segmentTemplate=m;break;case"ContentProtection":var y=De(a),_=y[0],b=y[1];b.length>0&&(r=r.concat(b)),void 0!==_&&n.push(_)}}return n.length>0&&(t.contentProtections=n),[t,r]}(e.childNodes),n=t[0],r=t[1],i=function(e){for(var t={},n=[],r=Ce(t,n),i=0;i0&&(r=r.concat(u));break;case"ContentComponent":t.contentComponent=me(a);break;case"EssentialProperty":null==t.essentialProperties?t.essentialProperties=[Me(a)]:t.essentialProperties.push(Me(a));break;case"InbandEventStream":void 0===t.inbandEventStreams&&(t.inbandEventStreams=[]),t.inbandEventStreams.push(Me(a));break;case"Label":var l=a.textContent;null!=l&&(t.label=l);break;case"Representation":var d=Ve(a),c=d[0],f=d[1];t.representations.push(c),f.length>0&&(r=r.concat(f));break;case"Role":null==t.roles?t.roles=[Me(a)]:t.roles.push(Me(a));break;case"SupplementalProperty":null==t.supplementalProperties?t.supplementalProperties=[Me(a)]:t.supplementalProperties.push(Me(a));break;case"SegmentBase":var v=Oe(a),p=v[0],h=v[1];t.segmentBase=p,h.length>0&&(r=r.concat(h));break;case"SegmentList":var m=Ue(a),g=m[0],y=m[1];t.segmentList=g,y.length>0&&(r=r.concat(y));break;case"SegmentTemplate":var _=Fe(a),b=_[0],S=_[1];t.segmentTemplate=b,S.length>0&&(r=r.concat(S));break;case"ContentProtection":var T=De(a),E=T[0],k=T[1];k.length>0&&(r=r.concat(k)),void 0!==E&&n.push(E)}}return n.length>0&&(t.contentProtections=n),[t,r]}(e.childNodes),n=t[0],r=t[1],i=function(e){for(var t={},n=[],r=Ce(t,n),i=0;i0&&(n=n.concat(d))}}return[t,n]}function Ge(e){for(var t={eventStreamData:e},n=[],r=Ce(t,n),i=0;i0&&(i=i.concat(_))}}return[{baseURLs:n,adaptations:r,eventStreams:a,segmentTemplate:t},i]}(e.childNodes),n=t[0],r=t[1],i=function(e){for(var t={},n=[],r=Ce(t,n),i=0;i",c=(new DOMParser).parseFromString(d,"text/xml");if(null==c||0===c.children.length)throw new Error("DASH parser: Invalid external ressources");for(var f=c.children[0].children,v=[],p=[],h=0;h0;){var r=e[0];if(r.start>=t)return n;if(-1===r.repeatCount)return n;if(0===r.repeatCount)e.shift(),n+=1;else{var i=e[1];if(void 0!==i&&i.start<=t)e.shift(),n+=1;else{if(r.duration<=0)return n;for(var a=r.start+r.duration,o=1;ar.repeatCount)){var s=r.repeatCount-o;return r.start=a,r.repeatCount=s,n+=o}e.shift(),n=r.repeatCount+1}}}return n}n.d(t,{Z:function(){return r}})},3911:function(e,t,n){"use strict";n.d(t,{KF:function(){return i},PZ:function(){return u},_j:function(){return l},gT:function(){return o},jH:function(){return a},zG:function(){return s}});var r=n(1946);function i(e,t,n){var i,a=e.repeatCount;return a>=0?a:(i=(0,r.Z)(t)?void 0!==n?n:Number.MAX_VALUE:t.start,Math.ceil((i-e.start)/e.duration)-1)}function a(e,t,n){var r=e.start,a=e.duration;return a<=0?r:r+(i(e,t,n)+1)*a}function o(e,t){var n;return e*t.timescale+(null!==(n=t.indexTimeOffset)&&void 0!==n?n:0)}function s(e,t){var n;return(e-(null!==(n=t.indexTimeOffset)&&void 0!==n?n:0))/t.timescale}function u(e,t,n){return[e*n,(e+t)*n]}function l(e,t,n){var r=e.timeline,i=o(t,e);if(i<0)return null;var u=function(e,t){for(var n=0,r=e.length;n>>1;e[i].start<=t?n=i+1:r=i}return n-1}(r,i);if(u<0||u>=r.length-1)return null;var l=r[u];if(l.duration<=0)return null;var d=r[u+1];if(void 0===d)return null;var c=d.start;return i>=a(l,d,n)&&ie.time)return!1;if(o===e.time)return void 0===a.range?void 0===e.range:null!=e.range&&a.range[0]===e.range[0]&&a.range[1]===e.range[1];if(a.repeatCount>=0&&void 0!==a.duration){var s=(o-a.start)/a.duration-1;return s%1==0&&s<=a.repeatCount}}return!1}n.d(t,{Z:function(){return r}})},5505:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(3714),i=n(3887),a=n(3911);function o(e,t){if(0===e.length)return e.push.apply(e,t),!0;if(0===t.length)return!1;var n=e.length,o=t[0].start,s=e[n-1];if((0,a.jH)(s,t[0])=0;u--){var l=e[u].start;if(l===o){var d=n-u;return e.splice.apply(e,[u,d].concat(t)),!1}if(lo)return i.Z.warn("RepresentationIndex: Manifest update removed all previous segments"),e.splice.apply(e,[0,n].concat(t)),!0;if(void 0===c.repeatCount||c.repeatCount<=0)return c.repeatCount<0&&(c.repeatCount=Math.floor((o-c.start)/c.duration)-1),e.splice.apply(e,[u+1,n-(u+1)].concat(t)),!1;if(c.start+c.duration*(c.repeatCount+1)<=o)return e.splice.apply(e,[u+1,n-(u+1)].concat(t)),!1;var f=(o-c.start)/c.duration-1;if(f%1==0&&c.duration===t[0].duration){var v=t[0].repeatCount<0?-1:t[0].repeatCount+f+1;return e.splice.apply(e,[u,n-u].concat(t)),e[u].start=c.start,e[u].repeatCount=v,!1}return i.Z.warn("RepresentationIndex: Manifest update removed previous segments"),e[u].repeatCount=Math.floor(f),e.splice.apply(e,[u+1,n-(u+1)].concat(t)),!1}}var p=e[e.length-1],h=t[t.length-1];return void 0!==p.repeatCount&&p.repeatCount<0?p.start>h.start?(i.Z.warn("RepresentationIndex: The new index is older than the previous one"),!1):(i.Z.warn('RepresentationIndex: The new index is "bigger" than the previous one'),e.splice.apply(e,[0,n].concat(t)),!0):p.start+p.duration*(p.repeatCount+1)>=h.start+h.duration*(h.repeatCount+1)?(i.Z.warn("RepresentationIndex: The new index is older than the previous one"),!1):(i.Z.warn('RepresentationIndex: The new index is "bigger" than the previous one'),e.splice.apply(e,[0,n].concat(t)),!0)}},5734:function(e,t,n){"use strict";var r=n(6923),i=/&#([0-9]+);/g,a=/
    /gi,o=/]*>([\s\S]*?)<\/style[^>]*>/i,s=/\s*

    ]+))?>(.*)/i,u=/]+?start="?([0-9]*)"?[^0-9]/i;function l(e,t){var n=new RegExp("\\s*"+t+":\\s*(\\S+);","i").exec(e);return Array.isArray(n)?n[1]:null}t.Z=function(e,t,n){var d,c,f=/]/gi,v=/]|<\/body>/gi,p=[],h=o.exec(e),m=Array.isArray(h)?h[1]:"";v.exec(e);var g,y=function(e){for(var t=/\.(\S+)\s*{([^}]*)}/gi,n={},r=t.exec(e);null!==r;){var i=r[1],a=l(r[2],"lang");null!=i&&null!=a&&(n[a]=i),r=t.exec(e)}return n}(m),_=function(e){var t=/p\s*{([^}]*)}/gi.exec(e);return null===t?"":t[1]}(m);if((0,r.Z)(n)&&void 0===(g=y[n]))throw new Error("sami: could not find lang "+n+" in CSS");for(;d=f.exec(e),c=v.exec(e),null!==d||null!==c;){if(null===d||null===c||d.index>=c.index)throw new Error("parse error");var b=e.slice(d.index,c.index),S=u.exec(b);if(!Array.isArray(S))throw new Error("parse error (sync time attribute)");var T=+S[1];if(isNaN(T))throw new Error("parse error (sync time attribute NaN)");E(b.split("\n"),T/1e3)}return p;function E(e,n){for(var o=e.length;--o>=0;){var u=s.exec(e[o]);if(Array.isArray(u)){var l=u[1],d=u[2];if(g===l)if(" "===d)p[p.length-1].end=n;else{var c=document.createElement("DIV");c.className="rxp-texttrack-region";var f=document.createElement("DIV");f.className="rxp-texttrack-div",f.style.position="absolute",f.style.bottom="0",f.style.width="100%",f.style.color="#fff",f.style.textShadow="-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000";var v=document.createElement("div");v.className="rxp-texttrack-p",(0,r.Z)(_)&&(v.style.cssText=_);for(var h=d.split(a),m=0;m/gi,s=/]*>([\s\S]*?)<\/style[^>]*>/i,u=/\s*

    ]+))?>(.*)/i,l=/]+?start="?([0-9]*)"?[^0-9]/i;function d(e,t){var n=new RegExp("\\s*"+t+":\\s*(\\S+);","i").exec(e);return Array.isArray(n)?n[1]:null}t.Z=function(e,t,n){var c,f,v=/]/gi,p=/]|<\/body>/gi,h=[],m=s.exec(e),g=null!==m?m[1]:"";p.exec(e);var y,_=function(e){for(var t=/\.(\S+)\s*{([^}]*)}/gi,n={},r=t.exec(e);Array.isArray(r);){var i=r[1],a=d(r[2],"lang");null!=i&&null!=a&&(n[a]=i),r=t.exec(e)}return n}(g);if((0,i.Z)(n)&&void 0===(y=_[n]))throw new Error("sami: could not find lang "+n+" in CSS");for(;c=v.exec(e),f=p.exec(e),null!==c||null!==f;){if(null===c||null===f||c.index>=f.index)throw new Error("parse error");var b=e.slice(c.index,f.index),S=l.exec(b);if(null===S)throw new Error("parse error (sync time attribute)");var T=+S[1];if(isNaN(T))throw new Error("parse error (sync time attribute NaN)");E(b.split("\n"),T/1e3)}return function(e){for(var t=[],n=0;n=0;)if(null!==(r=u.exec(e[s]))){var l=r,d=l[1],c=l[2];y===d&&(" "===c?h[h.length-1].end=n:h.push({text:(i=c,i.replace(o,"\n").replace(a,(function(e,t){return String.fromCharCode(Number(t))}))),start:n+t}))}}}},2061:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(6923);function i(e,t){for(var n=t+1;(0,r.Z)(e[n]);)n++;return n}function a(e){for(var t=[],n=0;n0&&(1===o.length?o[0].indexOf("--\x3e")>=0&&t.push(o):(o[1].indexOf("--\x3e")>=0||o[0].indexOf("--\x3e")>=0)&&t.push(o)),n=a}return t}},8675:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(2061),i=n(788);function a(e,t){for(var n=e.split(/\r\n|\n|\r/),a=(0,r.Z)(n),s=[],u=0;u0){var l=document.createTextNode(o[s]);r.appendChild(l)}}else if("B"===a.nodeName){var d=e(a);d.style.fontWeight="bold",r.appendChild(d)}else if("I"===a.nodeName){var c=e(a);c.style.fontStyle="italic",r.appendChild(c)}else if("U"===a.nodeName){var f=e(a);f.style.textDecoration="underline",r.appendChild(f)}else if(u(a)&&"string"==typeof a.color){var v=e(a);v.style.color=a.color,r.appendChild(v)}else{var p=e(a);r.appendChild(p)}}return r}(t)}function u(e){return"FONT"===e.nodeName&&"color"in e}},8057:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(7253),i=n(2061),a=n(788);function o(e,t){for(var n,o,s,u,l,d=e.split(/\r\n|\n|\r/),c=(0,i.Z)(d),f=[],v=0;v0){var _=p.getAttribute("xml:space"),b=(0,l.Z)(_)?"default"===_:o,T=(0,d.Z)({},i,(0,c.U)(g,[p],n,t));u.push.apply(u,e(p,T,[p].concat(a),b))}}return u}(e,(0,d.Z)({},r),[],i)}(e,n,r,i,s),T=0;T|\u2265/g,">").replace(/\u200E/g,"‎").replace(/\u200F/g,"‏").replace(/\u00A0/g," ")}else if((0,l.OE)(s))i+="\n";else if((0,l.jg)(s)&&s.nodeType===Node.ELEMENT_NODE&&s.childNodes.length>0){var c=s.getAttribute("xml:space");i+=n(s,(0,o.Z)(c)?"default"===c:t)}}return i}return n(e,t)}(t,v),y=(0,i.Z)(h+n,m+n,g);return null===y?null:((0,a.Z)(y)&&function(e,t){var n=t.extent;if((0,o.Z)(n)){var r=u._0.exec(n);null!=r&&(e.size=Number(r[1]))}switch(t.writingMode){case"tb":case"tblr":e.vertical="lr";break;case"tbrl":e.vertical="rl"}var i=t.origin;if((0,o.Z)(i))u._0.exec(i);var a=t.align;if((0,o.Z)(a)){e.align=a,"center"===a&&("center"!==e.align&&(e.align="middle"),e.position="auto");var s=c[a];e.positionAlign=void 0===s?"":s;var l=d[a];e.lineAlign=void 0===l?"":l}}(y,r),y)}var v=function(e,t){for(var n=(0,r.Z)(e,t),i=[],a=0;a0&&(t=n)}return t}function a(e){var t=e.getElementsByTagName("body");if(t.length>0)return t[0];var n=e.getElementsByTagName("tt:body");return n.length>0?n[0]:null}function o(e){var t=e.getElementsByTagName("style");if(t.length>0)return t;var n=e.getElementsByTagName("tt:style");return n.length>0?n:t}function s(e){var t=e.getElementsByTagName("region");if(t.length>0)return t;var n=e.getElementsByTagName("tt:region");return n.length>0?n:t}function u(e){var t=e.getElementsByTagName("p");if(t.length>0)return t;var n=e.getElementsByTagName("tt:p");return n.length>0?n:t}function l(e){return"br"===e.nodeName||"tt:br"===e.nodeName}function d(e){return"span"===e.nodeName||"tt:span"===e.nodeName}n.d(t,{DM:function(){return s},H:function(){return a},OE:function(){return l},jF:function(){return i},jg:function(){return d},kd:function(){return u},vU:function(){return o}})},1138:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(6923),i=n(360);function a(e,t){for(var n=[],a=t;a=2)for(var r=1;r0&&n.appendChild(document.createElement("br")),o[s].length>0){var u=document.createTextNode(o[s]);n.appendChild(u)}}else{var c=e.nodeName.toLowerCase().split("."),f=[];if(c.forEach((function(e){(0,i.Z)(t[e])&&f.push(t[e])})),0!==f.length){var v=document.createAttribute("style");f.forEach((function(e){v.value+=e}));var p=(0,l.Z)(r,a)?a:"span";(n=document.createElement(p)).setAttributeNode(v)}else{var h=(0,l.Z)(r,a)?a:"span";n=document.createElement(h)}for(var m=0;m/,"").replace(/<([u,i,b,c])(\..*?)?(?: .*?)?>(.*?)<\/\1>/g,"<$1$2>$3"),r=(new DOMParser).parseFromString(n,"text/html").body.childNodes,i=[],a=0;a=2){var a=parseInt(i[1],10);isNaN(a)||(t.position=a,void 0!==i[2]&&(t.positionAlign=i[2]))}}(0,u.Z)(e.size)&&(t.size=e.size),"string"==typeof e.align&&(0,s.Z)(["start","center","end","left"],e.align)&&(t.align=e.align)}var d=n(7253);var c=function(e,t){var n=e.split(/\r\n|\n|\r/);if(!/^WEBVTT($| |\t)/.test(n[0]))throw new Error("Can't parse WebVTT: Invalid file.");for(var s,u,c,f,v=(0,o.yE)(n),p=(0,i.Z)(n,v),h=[],m=0;m/;if(o.test(e[0]))n=e[0],r=e.slice(1,e.length);else{if(!o.test(e[1]))return null;a=e[0],n=e[1],r=e.slice(2,e.length)}var s=function(e){var t=/^([\d:.]+)[ |\t]+-->[ |\t]+([\d:.]+)[ |\t]*(.*)$/.exec(e);if(null===t)return null;var n=i(t[1]),r=i(t[2]);return null==n||null==r?null:{start:n,end:r,settings:t[3].split(/ |\t/).reduce((function(e,t){var n=t.split(":");return 2===n.length&&(e[n[0]]=n[1]),e}),{})}}(n);return null===s?null:{start:s.start+t,end:s.end+t,settings:s.settings,payload:r,header:a}}},360:function(e,t,n){"use strict";n.d(t,{$4:function(){return s},JF:function(){return a},tq:function(){return o},yE:function(){return i}});var r=n(6923);function i(e){for(var t=0;t=0)return!0;var r=e[t+1];return void 0!==r&&r.indexOf("--\x3e")>=0}function s(e,t){for(var n=t+1;(0,r.Z)(e[n]);)n++;return n}},85:function(e,t,n){"use strict";n.d(t,{Z:function(){return ae}});var r=n(7874),i=n(8791),a=n(5861),o=n(4687),s=n.n(o),u=n(4597),l=n(5278),d=n(9829);function c(e,t){return null===e?null:null===t.url?e.baseUrl:(0,d.Z)(e.baseUrl,t.url)}function f(e,t,n,r,i){return v.apply(this,arguments)}function v(){return(v=(0,a.Z)(s().mark((function e(t,n,r,i,a){var o,l,d;return s().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(o=n.segment,l=c(t,o),!o.isInit&&null!==l){e.next=4;break}return e.abrupt("return",{resultType:"segment-created",resultData:null});case 4:return e.next=6,(0,u.ZP)({url:l,responseType:"arraybuffer",timeout:r.timeout,onProgress:a.onProgress,cancelSignal:i});case 6:return d=e.sent,e.abrupt("return",{resultType:"segment-loaded",resultData:d});case 8:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function p(e,t){var n=t.segment,i=t.period,a=e.data,o=e.isChunked;if(t.segment.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if(o)throw new Error("Image data should not be downloaded in chunks");var s=(0,l.Z)(n.timestampOffset,0);return null===a||null===r.Z.imageParser?{segmentType:"media",chunkData:null,chunkSize:0,chunkInfos:{duration:n.duration,time:n.time},chunkOffset:s,protectionDataUpdate:!1,appendWindow:[i.start,i.end]}:{segmentType:"media",chunkData:{data:r.Z.imageParser(new Uint8Array(a)).thumbs,start:0,end:Number.MAX_VALUE,timescale:1,type:"bif"},chunkSize:void 0,chunkInfos:{time:0,duration:Number.MAX_VALUE},chunkOffset:s,protectionDataUpdate:!1,appendWindow:[i.start,i.end]}}var h=n(6872),m=n(8750),g=n(3887),y=n(1989),_=n(8026),b=n(3635);function S(e){var t=e.aggressiveMode,n=e.referenceDateTime,i=void 0!==e.serverSyncInfos?e.serverSyncInfos.serverTimestamp-e.serverSyncInfos.clientTime:void 0;return function(a,o,s,l,d){var c,f=a.responseData,v=o.externalClockOffset,p=null!==(c=a.url)&&void 0!==c?c:o.originalUrl,S=null!=i?i:v,T={aggressiveMode:!0===t,unsafelyBaseOnPreviousManifest:o.unsafeMode?o.previousManifest:null,url:p,referenceDateTime:n,externalClockOffset:S},E=r.Z.dashParsers;if(null===E.wasm||"uninitialized"===E.wasm.status||"failure"===E.wasm.status)return g.Z.debug("DASH: WASM MPD Parser not initialized. Running JS one."),w();var k=function(e){if(e instanceof ArrayBuffer)return e;if("string"==typeof e)return(0,b.tG)(e).buffer;if(e instanceof Document)return(0,b.tG)(e.documentElement.innerHTML).buffer;throw new Error("DASH Manifest Parser: Unrecognized Manifest format")}(f);return function(e){var t=new DataView(e);if(61371===t.getUint16(0)&&191===t.getUint8(2))return!0;if(65279===t.getUint16(0)||65534===t.getUint16(0))return!1;return!0}(k)?"initialized"===E.wasm.status?(g.Z.debug("DASH: Running WASM MPD Parser."),A(E.wasm.runWasmParser(k,T))):(g.Z.debug("DASH: Awaiting WASM initialization before parsing the MPD."),E.wasm.waitForInitialization().catch((function(){})).then((function(){return null===E.wasm||"initialized"!==E.wasm.status?(g.Z.warn("DASH: WASM MPD parser initialization failed. Running JS parser instead"),w()):(g.Z.debug("DASH: Running WASM MPD Parser."),A(E.wasm.runWasmParser(k,T)))}))):(g.Z.info("DASH: MPD doesn't seem to be UTF-8-encoded. Running JS parser instead of the WASM one."),w());function w(){if(null===E.js)throw new Error("No MPD parser is imported");var e=function(e){if(e instanceof ArrayBuffer)return(new DOMParser).parseFromString((0,b.uR)(new Uint8Array(e)),"text/xml");if("string"==typeof e)return(new DOMParser).parseFromString(e,"text/xml");if(e instanceof Document)return e;throw new Error("DASH Manifest Parser: Unrecognized Manifest format")}(f);return A(E.js(e,T))}function A(t){if("done"===t.type)return t.value.warnings.length>0&&s(t.value.warnings),l.isCancelled()?Promise.reject(l.cancellationError):{manifest:new y.ZP(t.value.parsed,e),url:p};var n=t.value,r=n.urls.map((function(e){return d((function(){var t=h.Z.getCurrent().DEFAULT_REQUEST_TIMEOUT;return"string"===n.format?(0,u.ZP)({url:e,responseType:"text",timeout:t,cancelSignal:l}):(0,u.ZP)({url:e,responseType:"arraybuffer",timeout:t,cancelSignal:l})})).then((function(e){if("string"===n.format){if("string"!=typeof e.responseData)throw new Error("External DASH resources should have been a string");return(0,_.Z)(e,{responseData:{success:!0,data:e.responseData}})}if(!(e.responseData instanceof ArrayBuffer))throw new Error("External DASH resources should have been ArrayBuffers");return(0,_.Z)(e,{responseData:{success:!0,data:e.responseData}})}),(function(e){var t=(0,m.Z)(e,{defaultCode:"PIPELINE_PARSE_ERROR",defaultReason:"An unknown error occured when parsing ressources."});return(0,_.Z)({},{size:void 0,requestDuration:void 0,responseData:{success:!1,error:t}})}))}));return Promise.all(r).then((function(e){return n.format,A(n.continue(e))}))}}}var T=n(7839),E=n(9105),k=n(5992),w=n(1946),A="function"==typeof Headers?Headers:null,I="function"==typeof AbortController?AbortController:null;function Z(){return"function"==typeof window.fetch&&!(0,w.Z)(I)&&!(0,w.Z)(A)}var x=n(8806),R=n(281);function M(e,t){return"audio"===e||"video"===e?"video/mp4"===t.mimeType||"audio/mp4"===t.mimeType?"mp4":"video/webm"===t.mimeType||"audio/webm"===t.mimeType?"webm":void 0:"text"===e&&"application/mp4"===t.mimeType?"mp4":void 0}var C=n(288),P=n(4460);function D(e){return function(t,n,r,i,a){return new Promise((function(s,u){var l=new C.ZP,d=l.linkToSignal(i);function c(){l.signal.deregister(u),d()}l.signal.register(u),e(t,n,r,l.signal,Object.assign(Object.assign({},a),{onNewChunk:function(e){try{o(e),a.onNewChunk(e)}catch(e){c(),l.cancel(),u(e)}}})).then((function(e){if(c(),!l.isUsed()){if("segment-loaded"===e.resultType)try{o(e.resultData.responseData)}catch(e){return void u(e)}s(e)}}),(function(e){c(),u(e)}))}));function o(e){(e instanceof ArrayBuffer||e instanceof Uint8Array)&&"mp4"===M(n.adaptation.type,n.representation)&&(0,P.Z)(new Uint8Array(e),n.segment.isInit)}}}var N=n(6968);function O(e,t,n,r,i){if(void 0===t.range)return(0,u.ZP)({url:e,responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}));if(void 0===t.indexRange)return(0,u.ZP)({url:e,headers:{Range:(0,R.Z)(t.range)},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}));if(t.range[1]+1===t.indexRange[0])return(0,u.ZP)({url:e,headers:{Range:(0,R.Z)([t.range[0],t.indexRange[1]])},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}));var a=(0,u.ZP)({url:e,headers:{Range:(0,R.Z)(t.range)},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress}),o=(0,u.ZP)({url:e,headers:{Range:(0,R.Z)(t.indexRange)},responseType:"arraybuffer",timeout:n.timeout,cancelSignal:r,onProgress:i.onProgress});return Promise.all([a,o]).then((function(t){var n=t[0],r=t[1],i=(0,N.zo)(new Uint8Array(n.responseData),new Uint8Array(r.responseData)),a=Math.min(n.sendingTime,r.sendingTime),o=Math.max(n.receivedTime,r.receivedTime);return{resultType:"segment-loaded",resultData:{url:e,responseData:i,size:n.size+r.size,requestDuration:o-a,sendingTime:a,receivedTime:o}}}))}var L=n(8766);function U(e,t,n,r,i){var o=t.segment,u=void 0!==o.range?{Range:(0,R.Z)(o.range)}:void 0,l=null;return function(e){var t;if(!(0,w.Z)(e.headers))if((0,w.Z)(A))t=e.headers;else{t=new A;for(var n=Object.keys(e.headers),r=0;r=300)throw g.Z.warn("Fetch: Request HTTP Error",t.status,t.url),new E.Z(t.url,t.status,k.br.ERROR_HTTP_CODE);if((0,w.Z)(t.body))throw new E.Z(t.url,t.status,k.br.PARSE_ERROR);var n=t.headers.get("Content-Length"),r=(0,w.Z)(n)||isNaN(+n)?void 0:+n,i=t.body.getReader(),u=0;return l();function l(){return c.apply(this,arguments)}function c(){return(c=(0,a.Z)(s().mark((function n(){var a,o,c,f,p;return s().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.next=2,i.read();case 2:if((a=n.sent).done||(0,w.Z)(a.value)){n.next=11;break}return u+=a.value.byteLength,o=performance.now(),c={url:t.url,currentTime:o,duration:o-d,sendingTime:d,chunkSize:a.value.byteLength,chunk:a.value.buffer,size:u,totalSize:r},e.onData(c),n.abrupt("return",l());case 11:if(!a.done){n.next=16;break}return v(),f=performance.now(),p=f-d,n.abrupt("return",{requestDuration:p,receivedTime:f,sendingTime:d,size:u,status:t.status,url:t.url});case 16:return n.abrupt("return",l());case 17:case"end":return n.stop()}}),n)})))).apply(this,arguments)}})).catch((function(t){if(null!==u)throw u;if(v(),l)throw g.Z.warn("Fetch: Request timeouted."),new E.Z(e.url,0,k.br.TIMEOUT);if(t instanceof E.Z)throw t;throw g.Z.warn("Fetch: Request Error",t instanceof Error?t.toString():""),new E.Z(e.url,0,k.br.ERROR_EVENT)}))}({url:e,headers:u,onData:function(e){var t=new Uint8Array(e.chunk),n=function(e){for(var t=0,n=[];te.length)return[n,r];var o=(0,L.Z)(r,1835295092);if(o<0)return[n,r];var s=t+o+(0,N.pX)(e,o+t);if(s>e.length)return[n,r];var u=Math.max(a,s),l=e.subarray(t,u);n.push(l),t=u}return[n,null]}(null!==l?(0,N.zo)(l,t):t),a=n[0];l=n[1];for(var o=0;o0)for(var p=0;p=Math.pow(2,8-n))return n}function q(e,t){var n=j(e,t);if(null==n)return g.Z.warn("webm: unrepresentable length"),null;if(t+n>e.length)return g.Z.warn("webm: impossible length"),null;for(var r=0,i=0;ie.length)return g.Z.warn("webm: impossible length"),null;for(var r=(e[t]&(1<<8-n)-1)*Math.pow(2,8*(n-1)),i=1;i=i)return!0}return!1}(r,t);return{inbandEvents:a,needsManifestRefresh:o}}}function ee(e){var t=e.__priv_patchLastSegmentInSidx;return function(e,n,r){var i,a=n.period,o=n.adaptation,s=n.representation,u=n.segment,d=n.manifest,c=e.data,f=e.isChunked,v=[a.start,a.end];if(null===c)return u.isInit?{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0}:{segmentType:"media",chunkData:null,chunkSize:0,chunkInfos:null,chunkOffset:0,protectionDataUpdate:!1,appendWindow:v};var p=c instanceof Uint8Array?c:new Uint8Array(c),h=M(o.type,s),m="mp4"===h||void 0===h,g=!1;if(m){var y,_=(0,F.Z)(p);u.isInit&&(y=null!==(i=(0,V.R0)(p))&&void 0!==i?i:void 0),(_.length>0||void 0!==y)&&(g=s._addProtectionData("cenc",y,_))}if(!u.isInit){var b=m?$(p,f,u,r):null,S=(0,l.Z)(u.timestampOffset,0);if(m){var T=(0,V.s9)(p);if(void 0!==T){var E=J(T.filter((function(e){return void 0!==u.privateInfos&&void 0!==u.privateInfos.isEMSGWhitelisted&&u.privateInfos.isEMSGWhitelisted(e)})),d.publishTime);if(void 0!==E){var k=E.needsManifestRefresh,A=E.inbandEvents;return{segmentType:"media",chunkData:p,chunkSize:p.length,chunkInfos:b,chunkOffset:S,appendWindow:v,inbandEvents:A,protectionDataUpdate:g,needsManifestRefresh:k}}}}return{segmentType:"media",chunkData:p,chunkSize:p.length,chunkInfos:b,chunkOffset:S,protectionDataUpdate:g,appendWindow:v}}var I=u.indexRange,Z=null;if("webm"===h)Z=function(e,t){var n=G(z,[],e,[t,e.length]);if(null==n)return null;var r=n[0],i=n[1],a=W(e,r);if(null==a)return null;var o=H(e,r);if(null==o)return null;var s=G(475249515,[],e,[r,i]);if(null==s)return null;for(var u=[],l=s[0];l0)){var x=Z[Z.length-1];Array.isArray(x.range)&&(x.range[1]=1/0)}s.index instanceof Q.Z&&null!==Z&&Z.length>0&&s.index.initializeIndex(Z);var R=m?(0,V.LD)(p):"webm"===h?W(p,0):void 0,C=(0,w.Z)(R)?void 0:R;return{segmentType:"init",initializationData:p,initializationDataSize:p.length,protectionDataUpdate:g,initTimescale:C}}}var te=n(6807);function ne(e,t,n,r){var i,a,o=e.segment,s=e.adaptation,u=e.representation;if(o.isInit)return null;null===n?r?(i=o.time,a=o.end):g.Z.warn("Transport: Unavailable time data for current text track."):(i=n.time,void 0!==n.duration?a=i+n.duration:!r&&o.complete&&(a=i+o.duration));var l=function(e){var t=e.codec;if(void 0===t)throw new Error("Cannot parse subtitles: unknown format");switch(t.toLowerCase()){case"stpp":case"stpp.ttml.im1t":return"ttml";case"wvtt":return"vtt"}throw new Error('The codec used for the subtitles "'+t+'" is not managed yet.')}(u),d=function(e){var t=(0,te.Le)(e);return null===t?"":(0,b.uR)(t)}(t);return{data:d,type:l,language:s.language,start:i,end:a}}function re(e,t,n){var r,i,a=e.segment,o=e.adaptation,s=e.representation;if(a.isInit)return null;n?g.Z.warn("Transport: Unavailable time data for current text track."):(r=a.time,a.complete&&(i=a.time+a.duration));var u=function(e){var t=e.mimeType,n=void 0===t?"":t;switch(e.mimeType){case"application/ttml+xml":return"ttml";case"application/x-sami":case"application/smil":return"sami";case"text/vtt":return"vtt"}var r=e.codec;if("srt"===(void 0===r?"":r).toLowerCase())return"srt";throw new Error("could not find a text-track parser for the type "+n)}(s);return{data:t,type:u,language:o.language,start:r,end:i}}function ie(e){var t=e.__priv_patchLastSegmentInSidx;return function(e,n,r){var i,a=n.period,o=n.adaptation,s=n.representation,u=n.segment,d=e.data,c=e.isChunked;if(null===d)return u.isInit?{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0}:{segmentType:"media",chunkData:null,chunkSize:0,chunkInfos:null,chunkOffset:null!==(i=u.timestampOffset)&&void 0!==i?i:0,protectionDataUpdate:!1,appendWindow:[a.start,a.end]};var f=M(o.type,s);if("webm"===f)throw new Error("Text tracks with a WEBM container are not yet handled.");return"mp4"===f?function(e,t,n,r,i){var a=n.period,o=n.representation,s=n.segment,u=s.isInit,d=s.indexRange,c="string"==typeof e?(0,b.tG)(e):e instanceof Uint8Array?e:new Uint8Array(e);if(u){var f=(0,V.Wf)(c,Array.isArray(d)?d[0]:0);if(!0===i&&null!==f&&f.length>0){var v=f[f.length-1];Array.isArray(v.range)&&(v.range[1]=1/0)}var p=(0,V.LD)(c);return o.index instanceof Q.Z&&null!==f&&f.length>0&&o.index.initializeIndex(f),{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:p}}var h=$(c,t,s,r),m=ne(n,c,h,t),g=(0,l.Z)(s.timestampOffset,0);return{segmentType:"media",chunkData:m,chunkSize:c.length,chunkInfos:h,chunkOffset:g,protectionDataUpdate:!1,appendWindow:[a.start,a.end]}}(d,c,n,r,t):function(e,t,n){var r,i,a=n.period,o=n.segment,s=o.timestampOffset,u=void 0===s?0:s;if(o.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if("string"!=typeof e){var l=e instanceof Uint8Array?e:new Uint8Array(e);r=(0,b.uR)(l),i=l.length}else r=e;return{segmentType:"media",chunkData:re(n,r,t),chunkSize:i,chunkInfos:null,chunkOffset:u,protectionDataUpdate:!1,appendWindow:[a.start,a.end]}}(d,c,n)}}var ae=function(e){var t=(0,i.Z)({customManifestLoader:e.manifestLoader},null===r.Z.dashParsers.wasm||"initialized"!==r.Z.dashParsers.wasm.status&&"initializing"!==r.Z.dashParsers.wasm.status?"arraybuffer":"text"),n=S(e),a=function(e){var t=e.lowLatencyMode,n=e.segmentLoader;return!0!==e.checkMediaSegmentIntegrity?r:D(r);function r(e,r,i,a,o){var s=c(e,r.segment);if(null==s)return Promise.resolve({resultType:"segment-created",resultData:null});if(t||void 0===n)return B(s,r,t,i,o,a);var u={adaptation:r.adaptation,manifest:r.manifest,period:r.period,representation:r.representation,segment:r.segment,transport:"dash",timeout:i.timeout,url:s};return new Promise((function(e,l){var d=!1,c=n(u,{reject:function(e){var t,n,r;if(!d&&!a.isCancelled()){d=!0,a.deregister(f);var i=e,o=null!==(t=null==i?void 0:i.message)&&void 0!==t?t:"Unknown error when fetching a DASH segment through a custom segmentLoader.",s=new T.Z(o,null!==(n=null==i?void 0:i.canRetry)&&void 0!==n&&n,null!==(r=null==i?void 0:i.isOfflineError)&&void 0!==r&&r,null==i?void 0:i.xhr);l(s)}},resolve:function(t){d||a.isCancelled()||(d=!0,a.deregister(f),e({resultType:"segment-loaded",resultData:{responseData:t.data,size:t.size,requestDuration:t.duration}}))},progress:function(e){d||a.isCancelled()||o.onProgress({duration:e.duration,size:e.size,totalSize:e.totalSize})},fallback:function(){d||a.isCancelled()||(d=!0,a.deregister(f),B(s,r,t,i,o,a).then(e,l))}});function f(e){d||(d=!0,"function"==typeof c&&c(),l(e))}a.register(f)}))}}(e),o=ee(e),s=function(e){var t=e.lowLatencyMode;return!0!==e.checkMediaSegmentIntegrity?n:D(n);function n(e,n,r,i,a){var o=n.adaptation,s=n.representation,l=n.segment,d=l.range,f=c(e,l);if(null===f)return Promise.resolve({resultType:"segment-created",resultData:null});if(l.isInit)return O(f,l,r,i,a);var v=M(o.type,s),p="mp4"===v||void 0===v;if(t&&p){if(Z())return U(f,n,r,a,i);(0,x.Z)("DASH: Your browser does not have the fetch API. You will have a higher chance of rebuffering when playing close to the live edge")}return p?(0,u.ZP)({url:f,responseType:"arraybuffer",headers:Array.isArray(d)?{Range:(0,R.Z)(d)}:null,timeout:r.timeout,onProgress:a.onProgress,cancelSignal:i}).then((function(e){return{resultType:"segment-loaded",resultData:e}})):(0,u.ZP)({url:f,responseType:"text",headers:Array.isArray(d)?{Range:(0,R.Z)(d)}:null,timeout:r.timeout,onProgress:a.onProgress,cancelSignal:i}).then((function(e){return{resultType:"segment-loaded",resultData:e}}))}}(e);return{manifest:{loadManifest:t,parseManifest:n},audio:{loadSegment:a,parseSegment:o},video:{loadSegment:a,parseSegment:o},text:{loadSegment:s,parseSegment:ie(e)},image:{loadSegment:f,parseSegment:p}}}},2339:function(e,t,n){"use strict";n.d(t,{Z:function(){return ye}});var r=n(5861),i=n(4687),a=n.n(i),o=n(7874),s=n(3887),u=n(1989),l=n(6807),d=n(9362),c=n(811),f=n(8232),v=n(3911),p=n(1091),h=n(5505);function m(e,t,n){var r=e.timeline,i=e.timescale,a=r[r.length-1],o=t.timescale===i?{time:t.time,duration:t.duration}:{time:t.time/t.timescale*i,duration:t.duration/t.timescale*i};return!(n.time===o.time)&&(o.time>=(0,v.jH)(a,null)&&(a.duration===o.duration?a.repeatCount++:e.timeline.push({duration:o.duration,start:o.time,repeatCount:0}),!0))}function g(e,t){return e.replace(/\{start time\}/g,String(t))}function y(e,t,n){var r=t-e;return r>0?Math.floor(r/n):0}function _(e,t){var n=e.repeatCount;if(null!=e.duration&&n<0){var r=void 0!==t?t.start:1/0;n=Math.ceil((r-e.start)/e.duration)-1}return n}var b=function(){function e(e,t){var n=t.aggressiveMode,r=t.isLive,i=t.segmentPrivateInfos,a=t.timeShiftBufferDepth,o=null==t.manifestReceivedTime?performance.now():t.manifestReceivedTime;if(this._index=e,this._indexValidityTime=o,this._timeShiftBufferDepth=a,this._initSegmentInfos={bitsPerSample:i.bitsPerSample,channels:i.channels,codecPrivateData:i.codecPrivateData,packetSize:i.packetSize,samplingRate:i.samplingRate,timescale:e.timescale,protection:i.protection},this._isAggressiveMode=n,this._isLive=r,0!==e.timeline.length){var s=e.timeline[e.timeline.length-1],u=(0,v.jH)(s,null);if(this._initialScaledLastPosition=u,r){var l=o/1e3*e.timescale;this._scaledLiveGap=l-u}}}var t=e.prototype;return t.getInitSegment=function(){return{id:"init",isInit:!0,privateInfos:{smoothInitSegment:this._initSegmentInfos},url:null,time:0,end:0,duration:0,timescale:1,complete:!0}},t.getSegments=function(e,t){this._refreshTimeline();for(var n,r=function(e,t,n){var r=void 0===e.timescale||0===e.timescale?1:e.timescale;return{up:t*r,to:(t+n)*r}}(this._index,e,t),i=r.up,a=r.to,o=this._index,s=o.timeline,u=o.timescale,l=o.media,d=this._isAggressiveMode,c=[],f=s.length,v=null==this._scaledLiveGap?void 0:performance.now()/1e3*u-this._scaledLiveGap,p=0;p=a)return c;null!=n&&(n+=S+1)}return c},t.shouldRefresh=function(e,t){if(this._refreshTimeline(),!this._isLive)return!1;var n=this._index,r=n.timeline,i=n.timescale,a=r[r.length-1];if(void 0===a)return!1;var o=a.repeatCount,s=a.start+(o+1)*a.duration;return!(t*i=s||e*i>a.start+o*a.duration)},t.getFirstAvailablePosition=function(){this._refreshTimeline();var e=this._index;return 0===e.timeline.length?null:e.timeline[0].start/e.timescale},t.getLastAvailablePosition=function(){this._refreshTimeline();var e=this._index;if(null==this._scaledLiveGap){var t=e.timeline[e.timeline.length-1];return(0,v.jH)(t,null)/e.timescale}for(var n=e.timeline.length-1;n>=0;n--)for(var r=e.timeline[n],i=performance.now()/1e3*e.timescale,a=r.start,o=r.duration,s=r.repeatCount;s>=0;s--){var u=a+o*(s+1);if((this._isAggressiveMode?u-o:u)<=i-this._scaledLiveGap)return u/e.timescale}},t.getEnd=function(){if(!this._isLive)return this.getLastAvailablePosition()},t.awaitSegmentBetween=function(e,t){var n;if((0,c.Z)(e<=t),this.isFinished())return!1;var r=this.getLastAvailablePosition();return!(void 0!==r&&t(null!==(n=this.getFirstAvailablePosition())&&void 0!==n?n:0)&&void 0)},t.checkDiscontinuity=function(e){return this._refreshTimeline(),(0,v._j)(this._index,e,void 0)},t.isSegmentStillAvailable=function(e){if(e.isInit)return!0;this._refreshTimeline();var t=this._index,n=t.timeline,r=t.timescale;return(0,p.Z)(e,n,r,0)},t.canBeOutOfSyncError=function(e){return!!this._isLive&&(e instanceof d.Z&&(e.isHttpError(404)||e.isHttpError(412)))},t._replace=function(e){var t=this._index.timeline,n=e._index.timeline,r=this._index.timescale,i=e._index.timescale;if(this._index=e._index,this._initialScaledLastPosition=e._initialScaledLastPosition,this._indexValidityTime=e._indexValidityTime,this._scaledLiveGap=e._scaledLiveGap,0!==t.length&&0!==n.length&&r===i){var a=t[t.length-1],o=n[n.length-1],u=(0,v.jH)(o,null);if(!((0,v.jH)(a,null)<=u))for(var l=0;lu){if(d.duration!==o.duration)return;var f=u-d.start;if(0===f)return s.Z.warn("Smooth Parser: a discontinuity detected in the previous manifest has been resolved."),void(this._index.timeline=this._index.timeline.concat(t.slice(l)));if(f<0||f%d.duration!=0)return;var p=f/d.duration-1,h=d.repeatCount-p;if(h<0)return;o.repeatCount+=h;var m=t.slice(l+1);return void(this._index.timeline=this._index.timeline.concat(m))}}}},t._update=function(e){(0,h.Z)(this._index.timeline,e._index.timeline),this._initialScaledLastPosition=e._initialScaledLastPosition,this._indexValidityTime=e._indexValidityTime,this._scaledLiveGap=e._scaledLiveGap},t.isFinished=function(){return!this._isLive},t.isInitialized=function(){return!0},t.addNewSegments=function(e,t){this._refreshTimeline();for(var n=0;n>3:2)?"mp4a.40.2":"mp4a.40."+n}(u,l);return{audiotag:void 0!==i?parseInt(i,10):i,bitrate:v,bitsPerSample:void 0!==a?parseInt(a,10):a,channels:void 0!==o?parseInt(o,10):o,codecPrivateData:u,codecs:p,customAttributes:n,mimeType:void 0!==l?F[l]:l,packetSize:void 0!==d?parseInt(d,10):d,samplingRate:void 0!==c?parseInt(c,10):c};case"video":var h=r("CodecPrivateData"),m=r("FourCC"),g=r("MaxWidth"),y=r("MaxHeight"),_=r("Bitrate"),b=void 0===_||isNaN(parseInt(_,10))?0:parseInt(_,10);if(void 0!==m&&void 0===F[m]||void 0===h)return s.Z.warn("Smooth parser: Unsupported video codec. Ignoring quality level."),null;var S=function(e){var t=/00000001\d7([0-9a-fA-F]{6})/.exec(e);return null!==t&&(0,k.Z)(t[1])?"avc1."+t[1]:"avc1.4D401E"}(h);return{bitrate:b,customAttributes:n,mimeType:void 0!==m?F[m]:m,codecPrivateData:h,codecs:S,width:void 0!==g?parseInt(g,10):void 0,height:void 0!==y?parseInt(y,10):void 0};case"text":var T=r("CodecPrivateData"),E=r("FourCC"),w=r("Bitrate");return{bitrate:void 0===w||isNaN(parseInt(w,10))?0:parseInt(w,10),customAttributes:n,mimeType:void 0!==E?F[E]:E,codecPrivateData:(0,Z.Z)(T,"")};default:return s.Z.error("Smooth Parser: Unrecognized StreamIndex type: "+t),null}}function o(t){var r=t.root,i=t.timescale,o=t.baseUrl,u=t.protections,l=t.timeShiftBufferDepth,d=t.manifestReceivedTime,f=t.isLive,v=r.getAttribute("Timescale"),p=null===v||isNaN(+v)?i:+v,h=r.getAttribute("Type");if(null===h)throw new Error("StreamIndex without type.");(0,T.Z)(S.r,h)||s.Z.warn("Smooth Parser: Unrecognized adaptation type:",h);var m=h,g=r.getAttribute("Subtype"),y=r.getAttribute("Language"),_=r.getAttribute("Url"),A=null===_?"":_;var Z,x=U(r,(function(e,t,r){switch(t){case"QualityLevel":var i=a(r,m);if(null===i)return e;("video"!==m||i.bitrate>n)&&e.qualityLevels.push(i);break;case"c":e.cNodes.push(r)}return e}),{qualityLevels:[],cNodes:[]}),R=x.qualityLevels,M=x.cNodes,P={timeline:(Z=M,Z.reduce((function(e,t,n){var r=t.getAttribute("d"),i=t.getAttribute("t"),a=t.getAttribute("r"),o=null!==a?+a-1:0,s=null!==i?+i:void 0,u=null!==r?+r:void 0;if(0===n)s=void 0===s||isNaN(s)?0:s;else{var l=e[n-1];if(null==s||isNaN(s)){if(null==l.duration||isNaN(l.duration))throw new Error("Smooth: Invalid CNodes. Missing timestamp.");s=l.start+l.duration*(l.repeatCount+1)}}if(null==u||isNaN(u)){var d=Z[n+1];if(void 0===d)return e;var c=d.getAttribute("t"),f=(0,k.Z)(c)?+c:null;if(null===f)throw new Error("Can't build index timeline from Smooth Manifest.");u=f-s}return e.push({duration:u,start:s,repeatCount:o}),e}),[])),timescale:p};(0,c.Z)(0!==R.length,"Adaptation should have at least one playable representation.");var D=m+((0,k.Z)(y)?"_"+y:""),N=R.map((function(t){var n,r,i,a,s={timeline:P.timeline,timescale:P.timescale,media:(n=A,r=t.bitrate,i=t.customAttributes,n.replace(/\{bitrate\}/g,String(r)).replace(/{CustomAttributes}/g,i.length>0?i[0]:""))},c=(0,k.Z)(t.mimeType)?t.mimeType:B[m],v=t.codecs,p=D+"_"+(null!=m?m+"-":"")+(null!=c?c+"-":"")+(null!=v?v+"-":"")+String(t.bitrate),h=[];u.length>0&&(a=u[0],u.forEach((function(e){var t=e.keyId;e.keySystems.forEach((function(e){h.push({keyId:t,systemId:e.systemId})}))})));var g={bitsPerSample:t.bitsPerSample,channels:t.channels,codecPrivateData:t.codecPrivateData,packetSize:t.packetSize,samplingRate:t.samplingRate,protection:null!=a?{keyId:a.keyId}:void 0},y=null!=e.aggressiveMode&&e.aggressiveMode,_=new b(s,{aggressiveMode:y,isLive:f,manifestReceivedTime:d,segmentPrivateInfos:g,timeShiftBufferDepth:l}),S=(0,w.Z)({},t,{index:_,cdnMetadata:[{baseUrl:o}],mimeType:c,codecs:v,id:p});if(h.length>0||void 0!==a){var T=void 0===a?[]:a.keySystems.map((function(e){var t=e.systemId,n=e.privateData,r=t.replace(/-/g,""),i=function(e,t){if(32!==e.length)throw new Error("HSS: wrong system id length");var n=0;return C("pssh",(0,E.zo)([n,0,0,0],(0,I.nr)(e),(0,E.kh)(t.length),t))}(r,n);return{systemId:r,data:i}}));if(T.length>0){var Z=[{type:"cenc",values:T}];S.contentProtections={keyIds:h,initData:Z}}else S.contentProtections={keyIds:h,initData:[]}}return S}));if("ADVT"===g)return null;var O={id:D,type:m,representations:N,language:null==y?void 0:y};return"text"===m&&"DESC"===g&&(O.closedCaption=!0),O}return function(n,r,a){var s="";if(void 0!==r){var u=(0,A.$)(r);s=r.substring(0,u)}var l=n.documentElement;if(null==l||"SmoothStreamingMedia"!==l.nodeName)throw new Error("document root should be SmoothStreamingMedia");var d=l.getAttribute("MajorVersion"),c=l.getAttribute("MinorVersion");if(null===d||null===c||!/^[2]-[0-2]$/.test(d+"-"+c))throw new Error("Version should be 2.0, 2.1 or 2.2");var f,v,p=l.getAttribute("Timescale"),h=(0,k.Z)(p)?isNaN(+p)?1e7:+p:1e7,m=U(l,(function(t,n,r){switch(n){case"Protection":t.protections.push(L(r,e.keySystems));break;case"StreamIndex":t.adaptationNodes.push(r)}return t}),{adaptationNodes:[],protections:[]}),g=m.protections,y=m.adaptationNodes,_="boolean"==typeof(f=l.getAttribute("IsLive"))?f:"string"==typeof f&&"TRUE"===f.toUpperCase();if(_){var b=l.getAttribute("DVRWindowLength");null==b||isNaN(+b)||0==+b||(v=+b/h)}var S,T,E,w,I,Z,x,R=y.reduce((function(e,t){var n=o({root:t,baseUrl:s,timescale:h,protections:g,isLive:_,timeShiftBufferDepth:v,manifestReceivedTime:a});if(null===n)return e;var r=n.type,i=e[r];return void 0===i?e[r]=[n]:i.push(n),e}),{}),M=null,C=void 0!==R.video?R.video[0]:void 0,P=void 0!==R.audio?R.audio[0]:void 0;if(void 0!==C||void 0!==P){var N=[],O=[];if(void 0!==C){var B=C.representations[0];if(void 0!==B){var F=B.index.getFirstAvailablePosition(),V=B.index.getLastAvailablePosition();null!=F&&N.push(F),null!=V&&O.push(V)}}if(void 0!==P){var z=P.representations[0];if(void 0!==z){var K=z.index.getFirstAvailablePosition(),G=z.index.getLastAvailablePosition();null!=K&&N.push(K),null!=G&&O.push(G)}}N.length>0&&(I=Math.max.apply(Math,N)),O.length>0&&(Z=Math.min.apply(Math,O),x=Math.max.apply(Math,O))}var W=l.getAttribute("Duration"),H=null!==W&&0!=+W?+W/h:void 0;if(_){S=e.suggestedPresentationDelay,T=t,E=null!=I?I:T;var j=x;void 0===j&&(j=Date.now()/1e3-T);var q=Z;void 0===q&&(q=j),w={isLinear:!0,maximumSafePosition:q,livePosition:j,time:performance.now()},M=null!=v?v:null}else{E=null!=I?I:0,w={isLinear:!1,maximumSafePosition:void 0!==Z?Z:void 0!==H?E+H:1/0,livePosition:void 0,time:performance.now()}}var Y=_?0:E,X=_?void 0:w.maximumSafePosition,Q={availabilityStartTime:void 0===T?0:T,clockOffset:i,isLive:_,isDynamic:_,isLastPeriodKnown:!0,timeBounds:{minimumSafePosition:E,timeshiftDepth:M,maximumTimeData:w},periods:[{adaptations:R,duration:void 0!==X?X-Y:H,end:X,id:"gen-smooth-period-0",start:Y}],suggestedPresentationDelay:S,transportType:"smooth",uris:null==r?[]:[r]};return D(Q),Q}},z=V,K=n(4597),G=n(8806),W=n(4460),H=n(8791),j=n(4644),q=n(2297);function Y(e,t,n,r,i){var a,o,u,d=[];if(i){var c=(0,l.XA)(e);null!==c?(u=function(e){var t=(0,q.nR)(e,3565190898,3392751253,2387879627,2655430559);if(void 0===t)return[];for(var n=[],r=t[0],i=t[4],a=0;a0)return e;var n=new Uint8Array(e.length+4);return n.set(e.subarray(0,t+8),0),n[t+3]=1|n[t+3],n.set([0,0,0,0],t+8),n.set(e.subarray(t+8,e.length),t+12),(0,j.J6)(n)}(l,s[1]-s[0]),f=te(u,d,c,i,(0,q.nR)(a,2721664850,1520127764,2722393154,2086964724)),v=P("moof",[i,f]),p=(0,q.Qy)(v,1836019558),h=(0,q.Qy)(f,1953653094),m=(0,q.Qy)(c,1953658222);if(null===p||null===h||null===m)throw new Error("Smooth: Invalid moof, trun or traf generation");var g=p[1]-p[0]+i.length+(h[1]-h[0])+u.length+d.length+(m[1]-m[0])+8,y=n[2]-n[0],_=v.length-y,b=(0,q.Qy)(e,1835295092);if(null===b)throw new Error("Smooth: Invalid ISOBMFF given");if(!X.YM&&(0===_||_<=-8)){var S=b[1];return v.set((0,E.kh)(S),g),e.set(v,n[0]),_<=-8&&e.set(C("free",new Uint8Array(-_-8)),v.length),e}var T=b[1]+_;v.set((0,E.kh)(T),g);var k=new Uint8Array(e.length+_),w=e.subarray(0,n[0]),A=e.subarray(n[2],e.length);return k.set(w,0),k.set(v,w.length),k.set(A,w.length+v.length),k}var re=n(7839),ie=n(281);function ae(e,t,n,r,i,a){var o,s,u,l=P("stbl",[n,C("stts",new Uint8Array(8)),C("stsc",new Uint8Array(8)),C("stsz",new Uint8Array(12)),C("stco",new Uint8Array(8))]),d=function(e){return C("dref",(0,E.zo)(7,[1],e))}(C("url ",new Uint8Array([0,0,0,1]))),c=P("dinf",[d]),f=P("minf",[r,c,l]),v=function(e){var t,n;switch(e){case"video":t="vide",n="VideoHandler";break;case"audio":t="soun",n="SoundHandler";break;default:t="hint",n=""}return C("hdlr",(0,E.zo)(8,(0,I.tG)(t),12,(0,I.tG)(n),1))}(t),p=function(e){return C("mdhd",(0,E.zo)(12,(0,E.kh)(e),8))}(e),h=P("mdia",[p,v,f]),m=function(e,t,n){return C("tkhd",(0,E.zo)((0,E.kh)(7),8,(0,E.kh)(n),20,[1,0,0,0],[0,1,0,0],12,[0,1,0,0],12,[64,0,0,0],(0,E.XT)(e),2,(0,E.XT)(t),2))}(i,a,1),g=P("trak",[m,h]),y=P("mvex",[(o=1,C("trex",(0,E.zo)(4,(0,E.kh)(o),[0,0,0,1],12)))]),_=function(e,t){return C("mvhd",(0,E.zo)(12,(0,E.kh)(e),4,[0,1],2,[1,0],10,[0,1],14,[0,1],14,[64,0,0,0],26,(0,E.XT)(t+1)))}(e,1),b=function(e,t,n){return P("moov",[e,t,n])}(_,y,g),S=(s="isom",u=["isom","iso2","iso6","avc1","dash"],C("ftyp",E.zo.apply(void 0,[(0,I.tG)(s),[0,0,0,1]].concat(u.map(I.tG)))));return(0,E.zo)(S,b)}function oe(e,t,n,r,i,a,o,s){var u=o.split("00000001"),l=u[1],d=u[2];if(void 0===l||void 0===d)throw new Error("Smooth: unsupported codec private data.");var c,f,v=function(e,t,n){var r=2===n?1:4===n?3:0,i=e[1],a=e[2],o=e[3];return C("avcC",(0,E.zo)([1,i,a,o,252|r,225],(0,E.XT)(e.length),e,[1],(0,E.XT)(t.length),t))}((0,I.nr)(l),(0,I.nr)(d),a);if(void 0===s){var p=function(e,t,n,r,i,a,o){return C("avc1",(0,E.zo)(6,(0,E.XT)(1),16,(0,E.XT)(e),(0,E.XT)(t),(0,E.XT)(n),2,(0,E.XT)(r),6,[0,1,i.length],(0,I.tG)(i),31-i.length,(0,E.XT)(a),[255,255],o))}(t,n,r,i,"AVC Coding",24,v);c=J([p])}else{var h=P("schi",[ee(1,8,s)]),m=Q("cenc",65536),g=function(e,t,n,r,i,a,o,s){return C("encv",(0,E.zo)(6,(0,E.XT)(1),16,(0,E.XT)(e),(0,E.XT)(t),(0,E.XT)(n),2,(0,E.XT)(r),6,[0,1,i.length],(0,I.tG)(i),31-i.length,(0,E.XT)(a),[255,255],o,s))}(t,n,r,i,"AVC Coding",24,v,P("sinf",[$("avc1"),m,h]));c=J([g])}return ae(e,"video",c,((f=new Uint8Array(12))[3]=1,C("vmhd",f)),t,n)}var se=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];function ue(e,t,n,r,i,a,o){var s,u,l,d=function(e,t){return C("esds",(0,E.zo)(4,[3,25],(0,E.XT)(e),[0,4,17,64,21],11,[5,2],(0,I.nr)(t),[6,1,2]))}(1,0===a.length?(s=i,u=t,l=((l=((l=(63&2)<<4)|31&se.indexOf(s))<<4)|31&u)<<3,(0,I.ci)((0,E.XT)(l))):a),c=function(){if(void 0===o){var e=function(e,t,n,r,i,a){return C("mp4a",(0,E.zo)(6,(0,E.XT)(e),8,(0,E.XT)(t),(0,E.XT)(n),2,(0,E.XT)(r),(0,E.XT)(i),2,a))}(1,t,n,r,i,d);return J([e])}var a=P("schi",[ee(1,8,o)]),s=Q("cenc",65536),u=P("sinf",[$("mp4a"),s,a]),l=function(e,t,n,r,i,a,o){return C("enca",(0,E.zo)(6,(0,E.XT)(e),8,(0,E.XT)(t),(0,E.XT)(n),2,(0,E.XT)(r),(0,E.XT)(i),2,a,o))}(1,t,n,r,i,d,u);return J([l])}();return ae(e,"audio",c,C("smhd",new Uint8Array(8)),0,0)}var le=/(\.isml?)(\?token=\S+)?$/,de=/\?token=(\S+)/;function ce(e,t){return(0,k.Z)(t)?e.replace(de,"?token="+t):e.replace(de,"")}function fe(e){return"string"==typeof e.mimeType&&e.mimeType.indexOf("mp4")>=0}function ve(e,t){return null===e?null:null===t.url?e.baseUrl:(0,A.Z)(e.baseUrl,t.url)}function pe(e,t,n,r,i,a){var o,s=t.segment.range;return Array.isArray(s)&&(o={Range:(0,ie.Z)(s)}),(0,K.ZP)({url:e,responseType:"arraybuffer",headers:o,timeout:r.timeout,cancelSignal:i,onProgress:n.onProgress}).then((function(e){if(!fe(t.representation)||!0!==a)return{resultType:"segment-loaded",resultData:e};var n=new Uint8Array(e.responseData);return(0,W.Z)(n,t.segment.isInit),{resultType:"segment-loaded",resultData:Object.assign(Object.assign({},e),{responseData:n})}}))}var he=function(e){var t=e.checkMediaSegmentIntegrity,n=e.customSegmentLoader;return function(e,r,i,a,o){var s=r.segment,u=r.manifest,l=r.period,d=r.adaptation,c=r.representation;if(s.isInit){if(void 0===s.privateInfos||void 0===s.privateInfos.smoothInitSegment)throw new Error("Smooth: Invalid segment format");var f,v=s.privateInfos.smoothInitSegment,p=v.codecPrivateData,h=v.timescale,m=v.protection,g=void 0===m?{keyId:void 0,keySystems:void 0}:m;if(void 0===p)throw new Error("Smooth: no codec private data.");switch(d.type){case"video":var y=c.width,_=void 0===y?0:y,b=c.height;f=oe(h,_,void 0===b?0:b,72,72,4,p,g.keyId);break;case"audio":var S=v.channels,T=void 0===S?0:S,E=v.bitsPerSample,k=void 0===E?0:E,w=v.packetSize,A=void 0===w?0:w,I=v.samplingRate;f=ue(h,T,k,A,void 0===I?0:I,p,g.keyId);break;default:0,f=new Uint8Array(0)}return Promise.resolve({resultType:"segment-created",resultData:f})}if(null===e)return Promise.resolve({resultType:"segment-created",resultData:null});var Z={adaptation:d,manifest:u,period:l,representation:c,segment:s,transport:"smooth",timeout:i.timeout,url:e};return"function"!=typeof n?pe(e,r,o,i,a,t):new Promise((function(s,u){var l=!1,d=n(Z,{reject:function(e){var t,n,r;if(!l&&!a.isCancelled()){l=!0,a.deregister(c);var i=e,o=null!==(t=null==i?void 0:i.message)&&void 0!==t?t:"Unknown error when fetching a Smooth segment through a custom segmentLoader.",s=new re.Z(o,null!==(n=null==i?void 0:i.canRetry)&&void 0!==n&&n,null!==(r=null==i?void 0:i.isOfflineError)&&void 0!==r&&r,null==i?void 0:i.xhr);u(s)}},resolve:function(e){if(!l&&!a.isCancelled()){l=!0,a.deregister(c),fe(r.representation)&&!0===t||s({resultType:"segment-loaded",resultData:{responseData:e.data,size:e.size,requestDuration:e.duration}});var n=e.data instanceof Uint8Array?e.data:new Uint8Array(e.data);(0,W.Z)(n,r.segment.isInit),s({resultType:"segment-loaded",resultData:{responseData:n,size:e.size,requestDuration:e.duration}})}},fallback:function(){l||a.isCancelled()||(l=!0,a.deregister(c),pe(e,r,o,i,a,t).then(s,u))},progress:function(e){l||a.isCancelled()||o.onProgress({duration:e.duration,size:e.size,totalSize:e.totalSize})}});function c(e){l||((l=!0)||"function"!=typeof d||d(),u(e))}a.register(c)}))}},me=/\.wsx?(\?token=\S+)?/;function ge(e,t,n){var r;s.Z.debug("Smooth Parser: update segments information.");for(var i=e.representations,a=0;a0&&ge(o,v,a),{segmentType:"media",chunkData:h,chunkInfos:p,chunkOffset:0,chunkSize:h.length,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}}},c={loadSegment:function(t,n,r,i,a){var o=n.segment,s=n.representation,u=ve(t,o);return o.isInit||null===u?Promise.resolve({resultType:"segment-created",resultData:null}):fe(s)?(0,K.ZP)({url:u,responseType:"arraybuffer",timeout:r.timeout,cancelSignal:i,onProgress:a.onProgress}).then((function(t){if(!0!==e.checkMediaSegmentIntegrity)return{resultType:"segment-loaded",resultData:t};var r=new Uint8Array(t.responseData);return(0,W.Z)(r,n.segment.isInit),{resultType:"segment-loaded",resultData:Object.assign(Object.assign({},t),{responseData:r})}})):(0,K.ZP)({url:u,responseType:"text",timeout:r.timeout,cancelSignal:i,onProgress:a.onProgress}).then((function(e){return{resultType:"segment-loaded",resultData:e}}))},parseSegment:function(e,t,n){var r,i,a,o=t.manifest,u=t.adaptation,d=t.representation,c=t.segment,f=u.language,v=fe(d),p=d.mimeType,h=void 0===p?"":p,m=d.codec,g=void 0===m?"":m,y=e.data,_=e.isChunked;if(c.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if(null===y)return{segmentType:"media",chunkData:null,chunkInfos:null,chunkOffset:0,chunkSize:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]};var b,S,T,E,k=null;if(v){var w;i=(w="string"==typeof y?(0,I.tG)(y):y instanceof Uint8Array?y:new Uint8Array(y)).length;var A=void 0!==n?Y(w,_,n,c,o.isLive):null;a=null==A?void 0:A.nextSegments,null===(k=null!==(r=null==A?void 0:A.chunkInfos)&&void 0!==r?r:null)?_?s.Z.warn("Smooth: Unavailable time data for current text track."):(b=c.time,S=c.end):(b=k.time,S=void 0!==k.duration?k.time+k.duration:c.end);var Z=g.toLowerCase();if("application/ttml+xml+mp4"===h||"stpp"===Z||"stpp.ttml.im1t"===Z)E="ttml";else{if("wvtt"!==Z)throw new Error("could not find a text-track parser for the type "+h);E="vtt"}var x=(0,l.Le)(w);T=null===x?"":(0,I.uR)(x)}else{var R;if(b=c.time,S=c.end,"string"!=typeof y){var M=y instanceof Uint8Array?y:new Uint8Array(y);i=M.length,R=(0,I.uR)(M)}else R=y;switch(h){case"application/x-sami":case"application/smil":E="sami";break;case"application/ttml+xml":E="ttml";break;case"text/vtt":E="vtt"}if(void 0===E){if("srt"!==g.toLowerCase())throw new Error("could not find a text-track parser for the type "+h);E="srt"}T=R}return null!==k&&Array.isArray(a)&&a.length>0&&ge(u,a,c),{segmentType:"media",chunkData:{type:E,data:T,start:b,end:S,language:f},chunkSize:i,chunkInfos:k,chunkOffset:null!=b?b:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}}};return{manifest:{resolveManifestUrl:function(e,t){if(void 0===e)return Promise.resolve(void 0);var n;me.test(e)?((0,G.Z)("Giving WSX URL to loadVideo is deprecated. You should only give Manifest URLs."),n=(0,K.ZP)({url:ce(e,""),responseType:"document",cancelSignal:t}).then((function(e){var t=e.responseData.getElementsByTagName("media")[0].getAttribute("src");if(null===t||0===t.length)throw new Error("Invalid ISML");return t}))):n=Promise.resolve(e);var r=function(e){var t=de.exec(e);if(null!==t){var n=t[1];if(void 0!==n)return n}return""}(e);return n.then((function(e){return ce(function(e){return le.test(e)?((0,G.Z)("Giving a isml URL to loadVideo is deprecated. Please give the Manifest URL directly"),e.replace(le,"$1/manifest$2")):e}(e),r)}))},loadManifest:(0,H.Z)(i,"text"),parseManifest:function(n,r){var i,a=null!==(i=n.url)&&void 0!==i?i:r.originalUrl,o=n.receivedTime,s=n.responseData,l="string"==typeof s?(new DOMParser).parseFromString(s,"text/xml"):s,d=t(l,a,o);return{manifest:new u.ZP(d,{representationFilter:e.representationFilter,supplementaryImageTracks:e.supplementaryImageTracks,supplementaryTextTracks:e.supplementaryTextTracks}),url:a}}},audio:d,video:d,text:c,image:{loadSegment:function(e,t,n,i,o){return(0,r.Z)(a().mark((function r(){var s,u,l;return a().wrap((function(r){for(;;)switch(r.prev=r.next){case 0:if(s=t.segment,u=ve(e,s),!s.isInit&&null!==u){r.next=4;break}return r.abrupt("return",{resultType:"segment-created",resultData:null});case 4:return r.next=6,(0,K.ZP)({url:u,responseType:"arraybuffer",timeout:n.timeout,onProgress:o.onProgress,cancelSignal:i});case 6:return l=r.sent,r.abrupt("return",{resultType:"segment-loaded",resultData:l});case 8:case"end":return r.stop()}}),r)})))()},parseSegment:function(e,t,n){var r=e.data,i=e.isChunked;if(t.segment.isInit)return{segmentType:"init",initializationData:null,initializationDataSize:0,protectionDataUpdate:!1,initTimescale:void 0};if(i)throw new Error("Image data should not be downloaded in chunks");return null===r||null===o.Z.imageParser?{segmentType:"media",chunkData:null,chunkInfos:null,chunkOffset:0,chunkSize:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}:{segmentType:"media",chunkData:{data:o.Z.imageParser(new Uint8Array(r)).thumbs,start:0,end:Number.MAX_VALUE,timescale:1,type:"bif"},chunkInfos:{time:0,duration:Number.MAX_VALUE},chunkSize:void 0,chunkOffset:0,protectionDataUpdate:!1,appendWindow:[void 0,void 0]}}}}}},281:function(e,t,n){"use strict";function r(e){var t=e[0],n=e[1];return n===1/0?"bytes="+t+"-":"bytes="+t+"-"+n}n.d(t,{Z:function(){return r}})},4460:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(5389),i=n(8766);function a(e,t){if(t){if((0,i.Z)(e,1718909296)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `ftyp` box");if((0,i.Z)(e,1836019574)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `moov` box")}else{if((0,i.Z)(e,1836019558)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `moof` box");if((0,i.Z)(e,1835295092)<0)throw new r.Z("INTEGRITY_ERROR","Incomplete `mdat` box")}}},8766:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(6968);function i(e,t){for(var n=e.length,i=0;i+8<=n;){var a=(0,r.pX)(e,i);if(0===a)a=n-i;else if(1===a){if(i+16>n)return-1;a=(0,r.pV)(e,i+8)}if(isNaN(a)||a<=0)return-1;if((0,r.pX)(e,i+4)===t)return i+a<=n?i:-1;i+=a}return-1}},8791:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(7904),i=n(4597),a=n(7839);function o(e,t){var n=e.customManifestLoader,o=function(e){return function(t,n,a){if(void 0===t)throw new Error("Cannot perform HTTP(s) request. URL not known");switch(e){case"arraybuffer":return(0,i.ZP)({url:t,responseType:"arraybuffer",timeout:n.timeout,cancelSignal:a});case"text":return(0,i.ZP)({url:t,responseType:"text",timeout:n.timeout,cancelSignal:a});case"document":return(0,i.ZP)({url:t,responseType:"document",timeout:n.timeout,cancelSignal:a});default:(0,r.Z)(e)}}}(t);return"function"!=typeof n?o:function(e,t){return function(n,r,i){return new Promise((function(o,s){var u=Date.now()-performance.now(),l=!1,d=e(n,{reject:function(e){var t,n,r;if(!l&&!i.isCancelled()){l=!0,i.deregister(c);var o=e,u=null!==(t=null==o?void 0:o.message)&&void 0!==t?t:"Unknown error when fetching the Manifest through a custom manifestLoader.",d=new a.Z(u,null!==(n=null==o?void 0:o.canRetry)&&void 0!==n&&n,null!==(r=null==o?void 0:o.isOfflineError)&&void 0!==r&&r,null==o?void 0:o.xhr);s(d)}},resolve:function(e){if(!l&&!i.isCancelled()){l=!0,i.deregister(c);var t=void 0!==e.receivingTime?e.receivingTime-u:void 0,n=void 0!==e.sendingTime?e.sendingTime-u:void 0;o({responseData:e.data,size:e.size,requestDuration:e.duration,url:e.url,receivedTime:t,sendingTime:n})}},fallback:function(){l||i.isCancelled()||(l=!0,i.deregister(c),t(n,r,i).then(o,s))}},{timeout:r.timeout});function c(e){l||(l=!0,"function"==typeof d&&d(),s(e))}i.register(c)}))}}(n,o)}},4791:function(e,t,n){"use strict";function r(e,t){if(e.length!==t.length)return!1;for(var n=e.length-1;n>=0;n--)if(e[n]!==t[n])return!1;return!0}n.d(t,{Z:function(){return r}})},3274:function(e,t,n){"use strict";function r(e,t,n){if("function"==typeof Array.prototype.find)return e.find(t,n);for(var r=e.length>>>0,i=0;i>>0,i=0;i>>0;if(0===r)return!1;for(var i,a,o=0|n,s=o>=0?Math.min(o,r-1):Math.max(r+o,0);s=a.length)throw new Error("Unable to parse base64 string.");var t=a[e];if(255===t)throw new Error("Unable to parse base64 string.");return t}function s(e){var t,n="",r=e.length;for(t=2;t>2],n+=i[(3&e[t-2])<<4|e[t-1]>>4],n+=i[(15&e[t-1])<<2|e[t]>>6],n+=i[63&e[t]];return t===r+1&&(n+=i[e[t-2]>>2],n+=i[(3&e[t-2])<<4],n+="=="),t===r&&(n+=i[e[t-2]>>2],n+=i[(3&e[t-2])<<4|e[t-1]>>4],n+=i[(15&e[t-1])<<2],n+="="),n}function u(e){var t=e.length%4,n=e;0!==t&&(r.Z.warn("base64ToBytes: base64 given miss padding"),n+=3===t?"=":2===t?"==":"===");var i=n.indexOf("=");if(-1!==i&&i>16,l[c+1]=a>>8&255,l[c+2]=255&a;return l.subarray(0,l.length-s)}},6968:function(e,t,n){"use strict";function r(){for(var e,t=arguments.length,n=-1,r=0;++n0&&(i.set(e,a),a+=e.length);return i}function i(e,t){return(e[t+0]<<8)+(e[t+1]<<0)}function a(e,t){return 65536*e[t+0]+256*e[t+1]+e[t+2]}function o(e,t){return 16777216*e[t+0]+65536*e[t+1]+256*e[t+2]+e[t+3]}function s(e,t){return 4294967296*(16777216*e[t+0]+65536*e[t+1]+256*e[t+2]+e[t+3])+16777216*e[t+4]+65536*e[t+5]+256*e[t+6]+e[t+7]}function u(e){return new Uint8Array([e>>>8&255,255&e])}function l(e){return new Uint8Array([e>>>24&255,e>>>16&255,e>>>8&255,255&e])}function d(e){var t=e%4294967296,n=(e-t)/4294967296;return new Uint8Array([n>>>24&255,n>>>16&255,n>>>8&255,255&n,t>>>24&255,t>>>16&255,t>>>8&255,255&t])}function c(e,t){return(e[t+0]<<0)+(e[t+1]<<8)}function f(e,t){return e[t+0]+256*e[t+1]+65536*e[t+2]+16777216*e[t+3]}function v(e){return new Uint8Array([255&e,e>>>8&255,e>>>16&255,e>>>24&255])}function p(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer)}n.d(t,{O_:function(){return v},QI:function(){return a},XT:function(){return u},_f:function(){return p},dN:function(){return f},el:function(){return d},kh:function(){return l},pV:function(){return s},pX:function(){return o},qb:function(){return c},zK:function(){return i},zo:function(){return r}})},7864:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(7733);function i(e,t){return(0,r.Z)(t,(function(t){var n=setTimeout((function(){return t()}),e);return function(){return clearTimeout(n)}}))}},7733:function(e,t,n){"use strict";function r(e,t){var n;return new Promise((function(r,i){if(null!==e.cancellationError)return i(e.cancellationError);var a=!1;function o(e){void 0!==n&&n(),i(e)}n=t((function(t){e.deregister(o),a=!0,r(t)}),(function(t){e.deregister(o),a=!0,i(t)})),a||e.register(o)}))}n.d(t,{Z:function(){return r}})},1959:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(3887),i=n(1946),a=function(){function e(){this._listeners={}}var t=e.prototype;return t.addEventListener=function(e,t,n){var r=this,i=this._listeners[e];Array.isArray(i)?i.push(t):this._listeners[e]=[t],void 0!==n&&n.register((function(){r.removeEventListener(e,t)}))},t.removeEventListener=function(e,t){if((0,i.Z)(e))this._listeners={};else{var n=this._listeners[e];if(Array.isArray(n))if((0,i.Z)(t))delete this._listeners[e];else{var r=n.indexOf(t);-1!==r&&n.splice(r,1),0===n.length&&delete this._listeners[e]}}},t.trigger=function(e,t){var n=this._listeners[e];Array.isArray(n)&&n.slice().forEach((function(e){try{e(t)}catch(e){r.Z.error("EventEmitter: listener error",e instanceof Error?e:null)}}))},e}()},9592:function(e,t,n){"use strict";function r(e,t){return"function"==typeof Array.prototype.flatMap?e.flatMap(t):e.reduce((function(e,n){var r=t(n);return Array.isArray(r)?(e.push.apply(e,r),e):(e.push(r),e)}),[])}n.d(t,{Z:function(){return r}})},2572:function(e,t,n){"use strict";n.d(t,{Z:function(){return r}});function r(e){return e*(.3*(2*Math.random()-1)+1)}},2870:function(e,t,n){"use strict";function r(e){for(var t=0,n=0;n=Number.MAX_SAFE_INTEGER&&(e+="0",t=0),e+String(t)}}n.d(t,{Z:function(){return r}})},6923:function(e,t,n){"use strict";function r(e){return"string"==typeof e&&e.length>0}n.d(t,{Z:function(){return r}})},1946:function(e,t,n){"use strict";function r(e){return null==e}n.d(t,{Z:function(){return r}})},7829:function(e,t,n){"use strict";var r=n(5553);t.ZP=r.ZP},5553:function(e,t,n){"use strict";n.d(t,{ZP:function(){return d},iH:function(){return l},Y1:function(){return u}});var r=n(6923),i=n(1946),a={aa:"aar",ab:"abk",ae:"ave",af:"afr",ak:"aka",am:"amh",an:"arg",ar:"ara",as:"asm",av:"ava",ay:"aym",az:"aze",ba:"bak",be:"bel",bg:"bul",bi:"bis",bm:"bam",bn:"ben",bo:"bod",br:"bre",bs:"bos",ca:"cat",ce:"che",ch:"cha",co:"cos",cr:"cre",cs:"ces",cu:"chu",cv:"chv",cy:"cym",da:"dan",de:"deu",dv:"div",dz:"dzo",ee:"ewe",el:"ell",en:"eng",eo:"epo",es:"spa",et:"est",eu:"eus",fa:"fas",ff:"ful",fi:"fin",fj:"fij",fo:"fao",fr:"fra",fy:"fry",ga:"gle",gd:"gla",gl:"glg",gn:"grn",gu:"guj",gv:"glv",ha:"hau",he:"heb",hi:"hin",ho:"hmo",hr:"hrv",ht:"hat",hu:"hun",hy:"hye",hz:"her",ia:"ina",id:"ind",ie:"ile",ig:"ibo",ii:"iii",ik:"ipk",io:"ido",is:"isl",it:"ita",iu:"iku",ja:"jpn",jv:"jav",ka:"kat",kg:"kon",ki:"kik",kj:"kua",kk:"kaz",kl:"kal",km:"khm",kn:"kan",ko:"kor",kr:"kau",ks:"kas",ku:"kur",kv:"kom",kw:"cor",ky:"kir",la:"lat",lb:"ltz",lg:"lug",li:"lim",ln:"lin",lo:"lao",lt:"lit",lu:"lub",lv:"lav",mg:"mlg",mh:"mah",mi:"mri",mk:"mkd",ml:"mal",mn:"mon",mr:"mar",ms:"msa",mt:"mlt",my:"mya",na:"nau",nb:"nob",nd:"nde",ne:"nep",ng:"ndo",nl:"nld",nn:"nno",no:"nor",nr:"nbl",nv:"nav",ny:"nya",oc:"oci",oj:"oji",om:"orm",or:"ori",os:"oss",pa:"pan",pi:"pli",pl:"pol",ps:"pus",pt:"por",qu:"que",rm:"roh",rn:"run",ro:"ron",ru:"rus",rw:"kin",sa:"san",sc:"srd",sd:"snd",se:"sme",sg:"sag",si:"sin",sk:"slk",sl:"slv",sm:"smo",sn:"sna",so:"som",sq:"sqi",sr:"srp",ss:"ssw",st:"sot",su:"sun",sv:"swe",sw:"swa",ta:"tam",te:"tel",tg:"tgk",th:"tha",ti:"tir",tk:"tuk",tl:"tgl",tn:"tsn",to:"ton",tr:"tur",ts:"tso",tt:"tat",tw:"twi",ty:"tah",ug:"uig",uk:"ukr",ur:"urd",uz:"uzb",ve:"ven",vi:"vie",vo:"vol",wa:"wln",wo:"wol",xh:"xho",yi:"yid",yo:"yor",za:"zha",zh:"zho",zu:"zul"},o={alb:"sqi",arm:"hye",baq:"eus",bur:"mya",chi:"zho",cze:"ces",dut:"nld",fre:"fra",geo:"kat",ger:"deu",gre:"ell",ice:"isl",mac:"mkd",mao:"mri",may:"msa",per:"fas",slo:"slk",rum:"ron",tib:"bod",wel:"cym"};function s(e){if((0,i.Z)(e)||""===e)return"";var t=function(e){var t;switch(e.length){case 2:t=a[e];break;case 3:t=o[e]}return t}((""+e).toLowerCase().split("-")[0]);return(0,r.Z)(t)?t:e}function u(e){if(!(0,i.Z)(e)){var t,n=!1;return"string"==typeof e?t=e:(t=e.language,!0===e.closedCaption&&(n=!0)),{language:t,closedCaption:n,normalized:s(t)}}return e}function l(e){if((0,i.Z)(e))return e;if("string"==typeof e)return{language:e,audioDescription:!1,normalized:s(e)};var t={language:e.language,audioDescription:!0===e.audioDescription,normalized:s(s(e.language))};return!0===e.isDub&&(t.isDub=!0),t}var d=s},8894:function(e,t,n){"use strict";function r(){}n.d(t,{Z:function(){return r}})},8026:function(e,t){"use strict";t.Z="function"==typeof Object.assign?Object.assign:function(e){if(null==e)throw new TypeError("Cannot convert undefined or null to object");for(var t=Object(e),n=0;n<(arguments.length<=1?0:arguments.length-1);n++){var r=n+1<1||arguments.length<=n+1?void 0:arguments[n+1];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(t[i]=r[i])}return t}},1679:function(e,t,n){"use strict";t.Z="function"==typeof Object.values?Object.values:function(e){return Object.keys(e).map((function(t){return e[t]}))}},2829:function(e,t,n){"use strict";n.d(t,{A1:function(){return o},DD:function(){return h},F_:function(){return v},JN:function(){return d},L7:function(){return m},Ti:function(){return s},XS:function(){return f},at:function(){return p},kR:function(){return g},rx:function(){return c},tn:function(){return _},uH:function(){return b}});function r(e,t){return Math.abs(e-t)<.016666666666666666}function i(e,t){return{start:Math.min(e.start,t.start),end:Math.max(e.end,t.end)}}function a(e,t){return e.end<=t.start}function o(e,t){for(var n=0;n=0;n--){var r=e.start(n);if(t>=r){var i=e.end(n);if(t=o?r.push({start:a,end:o}):n={start:a,end:o}}return{outerRanges:r,innerRange:n}}function p(e,t){var n=c(e,t);return null!==n?n.end-n.start:0}function h(e,t){var n=c(e,t);return null!==n?t-n.start:0}function m(e,t){var n=c(e,t);return null!==n?n.end-t:1/0}function g(e,t){if(t.start===t.end)return e;for(var n=t,r=0;r0)for(var o=0;o0)for(var s=0;sl&&n.push({start:l,end:a[d].start}),l=a[d].end;l=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&i.splice(e,1)}}i.push(r),!0===(null==t?void 0:t.emitCurrentValue)&&e(n,o),a||r.hasBeenCleared?o():void 0!==(null==t?void 0:t.clearSignal)&&t.clearSignal.register(o)},waitUntilDefined:function(e,t){this.onUpdate((function(t,r){void 0!==t&&(r(),e(n))}),{clearSignal:null==t?void 0:t.clearSignal,emitCurrentValue:!0})},finish:o};function o(){void 0!==t&&t.deregister(o),a=!0;for(var e,n=r(i.slice());!(e=n()).done;){var s=e.value;try{s.hasBeenCleared||(s.complete(),s.hasBeenCleared=!0)}catch(e){}}i.length=0}}function o(e,t,n){var r=a(t(e.getValue()),n);return e.onUpdate((function(e){r.setValue(t(e))}),{clearSignal:n}),r}n.d(t,{$l:function(){return a},ZP:function(){return a},lR:function(){return o}})},4597:function(e,t,n){"use strict";n.d(t,{ZP:function(){return o}});var r=n(9105),i=n(6923),a=n(1946);var o=function(e){var t={url:e.url,headers:e.headers,responseType:(0,a.Z)(e.responseType)?"json":e.responseType,timeout:e.timeout};return new Promise((function(n,o){var s,u=e.onProgress,l=e.cancelSignal,d=t.url,c=t.headers,f=t.responseType,v=t.timeout,p=new XMLHttpRequest;if(p.open("GET",d,!0),void 0!==v&&(p.timeout=v,s=window.setTimeout((function(){_(),o(new r.Z(d,p.status,"TIMEOUT",p))}),v+3e3)),p.responseType=f,"document"===p.responseType&&p.overrideMimeType("text/xml"),!(0,a.Z)(c)){var h=c;for(var m in h)h.hasOwnProperty(m)&&p.setRequestHeader(m,h[m])}var g=performance.now(),y=null;function _(){void 0!==s&&clearTimeout(s),null!==y&&y()}void 0!==l&&(y=l.register((function(e){_(),(0,a.Z)(p)||4===p.readyState||p.abort(),o(e)})),l.isCancelled())||(p.onerror=function(){_(),o(new r.Z(d,p.status,"ERROR_EVENT",p))},p.ontimeout=function(){_(),o(new r.Z(d,p.status,"TIMEOUT",p))},void 0!==u&&(p.onprogress=function(e){var t=performance.now();u({url:d,duration:t-g,sendingTime:g,currentTime:t,size:e.loaded,totalSize:e.total})}),p.onload=function(e){if(4===p.readyState)if(_(),p.status>=200&&p.status<300){var t,s=performance.now(),u=p.response instanceof ArrayBuffer?p.response.byteLength:e.total,l=p.status,c=p.responseType,f=(0,i.Z)(p.responseURL)?p.responseURL:d;if(t="json"===c?"object"==typeof p.response?p.response:function(e){try{return JSON.parse(e)}catch(e){return null}}(p.responseText):p.response,(0,a.Z)(t))return void o(new r.Z(d,p.status,"PARSE_ERROR",p));n({status:l,url:f,responseType:c,sendingTime:g,receivedTime:s,requestDuration:s-g,size:u,responseData:t})}else o(new r.Z(d,p.status,"ERROR_HTTP_CODE",p))},p.send())}))}},9829:function(e,t,n){"use strict";n.d(t,{$:function(){return s},Z:function(){return o}});var r=/^(?:[a-z]+:)?\/\//i,i=/\/\.{1,2}\//;function a(e){if(!i.test(e))return e;for(var t=[],n=e.split("/"),r=0,a=n.length;r=0&&t===n+1)return e.length}var i=e.indexOf("?");return i>=0&&i>8&255}return n}function u(e){if(a)try{return new TextDecoder("utf-16le").decode(e)}catch(e){var t=e instanceof Error?e:"";r.Z.warn("Utils: could not use TextDecoder to parse UTF-16LE, fallbacking to another implementation",t)}for(var n="",i=0;i=t?n:new Array(t-n.length+1).join("0")+n}function c(e){if(a)try{return(new TextDecoder).decode(e)}catch(e){var t=e instanceof Error?e:"";r.Z.warn("Utils: could not use TextDecoder to parse UTF-8, fallbacking to another implementation",t)}var n=e;239===n[0]&&187===n[1]&&191===n[2]&&(n=n.subarray(3));var i,o=function(e){for(var t="",n=0;n=256?"%u"+d(l,4):"%"+d(l,2)}}return decodeURIComponent(i)}function f(e){for(var t=e.length,n=new Uint8Array(t/2),r=0,i=0;r>>4).toString(16),n+=(15&e[r]).toString(16),t.length>0&&r0;)try{var n=t._listeners.pop();null==n||n(e)}catch(e){o.Z.error("Error while calling clean up listener",e instanceof Error?e.toString():"Unknown error")}}))}var t=e.prototype;return t.isCancelled=function(){return this._isCancelled},t.register=function(e){var t=this;return this._isCancelled?((0,s.Z)(null!==this.cancellationError),e(this.cancellationError),u.Z):(this._listeners.push(e),function(){return t.deregister(e)})},t.deregister=function(e){for(var t=this._listeners.length-1;t>=0;t--)this._listeners[t]===e&&this._listeners.splice(t,1)},e}(),c=function(e){function t(){var n;return n=e.call(this)||this,Object.setPrototypeOf((0,r.Z)(n),t.prototype),n.name="CancellationError",n.message="This task was cancelled.",n}return(0,i.Z)(t,e),t}((0,a.Z)(Error))},8806:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(7714),i=[];function a(e){(0,r.Z)(i,e)||(console.warn(e),i.push(e))}},7473:function(e){"use strict";var t=function(e){if("function"!=typeof e)throw new TypeError(e+" is not a function");return e},n=function(e){var n,r,i=document.createTextNode(""),a=0;return new e((function(){var e;if(n)r&&(n=r.concat(n));else{if(!r)return;n=r}if(r=n,n=null,"function"==typeof r)return e=r,r=null,void e();for(i.data=a=++a%2;r;)e=r.shift(),r.length||(r=null),e()})).observe(i,{characterData:!0}),function(e){t(e),n?"function"==typeof n?n=[n,e]:n.push(e):(n=e,i.data=a=++a%2)}};e.exports=function(){if("object"==typeof process&&process&&"function"==typeof process.nextTick)return process.nextTick;if("function"==typeof queueMicrotask)return function(e){queueMicrotask(t(e))};if("object"==typeof document&&document){if("function"==typeof MutationObserver)return n(MutationObserver);if("function"==typeof WebKitMutationObserver)return n(WebKitMutationObserver)}return"function"==typeof setImmediate?function(e){setImmediate(t(e))}:"function"==typeof setTimeout||"object"==typeof setTimeout?function(e){setTimeout(t(e),0)}:null}()},7061:function(e,t,n){var r=n(8698).default;function i(){"use strict";e.exports=i=function(){return t},e.exports.__esModule=!0,e.exports.default=e.exports;var t={},n=Object.prototype,a=n.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},s=o.iterator||"@@iterator",u=o.asyncIterator||"@@asyncIterator",l=o.toStringTag||"@@toStringTag";function d(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{d({},"")}catch(e){d=function(e,t,n){return e[t]=n}}function c(e,t,n,r){var i=t&&t.prototype instanceof p?t:p,a=Object.create(i.prototype),o=new A(r||[]);return a._invoke=function(e,t,n){var r="suspendedStart";return function(i,a){if("executing"===r)throw new Error("Generator is already running");if("completed"===r){if("throw"===i)throw a;return Z()}for(n.method=i,n.arg=a;;){var o=n.delegate;if(o){var s=E(o,n);if(s){if(s===v)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===r)throw r="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r="executing";var u=f(e,t,n);if("normal"===u.type){if(r=n.done?"completed":"suspendedYield",u.arg===v)continue;return{value:u.arg,done:n.done}}"throw"===u.type&&(r="completed",n.method="throw",n.arg=u.arg)}}}(e,n,o),a}function f(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}t.wrap=c;var v={};function p(){}function h(){}function m(){}var g={};d(g,s,(function(){return this}));var y=Object.getPrototypeOf,_=y&&y(y(I([])));_&&_!==n&&a.call(_,s)&&(g=_);var b=m.prototype=p.prototype=Object.create(g);function S(e){["next","throw","return"].forEach((function(t){d(e,t,(function(e){return this._invoke(t,e)}))}))}function T(e,t){function n(i,o,s,u){var l=f(e[i],e,o);if("throw"!==l.type){var d=l.arg,c=d.value;return c&&"object"==r(c)&&a.call(c,"__await")?t.resolve(c.__await).then((function(e){n("next",e,s,u)}),(function(e){n("throw",e,s,u)})):t.resolve(c).then((function(e){d.value=e,s(d)}),(function(e){return n("throw",e,s,u)}))}u(l.arg)}var i;this._invoke=function(e,r){function a(){return new t((function(t,i){n(e,r,t,i)}))}return i=i?i.then(a,a):a()}}function E(e,t){var n=e.iterator[t.method];if(void 0===n){if(t.delegate=null,"throw"===t.method){if(e.iterator.return&&(t.method="return",t.arg=void 0,E(e,t),"throw"===t.method))return v;t.method="throw",t.arg=new TypeError("The iterator does not provide a 'throw' method")}return v}var r=f(n,e.iterator,t.arg);if("throw"===r.type)return t.method="throw",t.arg=r.arg,t.delegate=null,v;var i=r.arg;return i?i.done?(t[e.resultName]=i.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,v):i:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,v)}function k(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function w(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function A(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(k,this),this.reset(!0)}function I(e){if(e){var t=e[s];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var n=-1,r=function t(){for(;++n=0;--r){var i=this.tryEntries[r],o=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var s=a.call(i,"catchLoc"),u=a.call(i,"finallyLoc");if(s&&u){if(this.prev=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&a.call(r,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),w(n),v}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var i=r.arg;w(n)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:I(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),v}},t}e.exports=i,e.exports.__esModule=!0,e.exports.default=e.exports},8698:function(e){function t(n){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(n)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},4687:function(e,t,n){var r=n(7061)();e.exports=r;try{regeneratorRuntime=r}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=r:Function("r","regeneratorRuntime = r")(r)}},7326:function(e,t,n){"use strict";function r(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}n.d(t,{Z:function(){return r}})},5861:function(e,t,n){"use strict";function r(e,t,n,r,i,a,o){try{var s=e[a](o),u=s.value}catch(e){return void n(e)}s.done?t(u):Promise.resolve(u).then(r,i)}function i(e){return function(){var t=this,n=arguments;return new Promise((function(i,a){var o=e.apply(t,n);function s(e){r(o,i,a,s,u,"next",e)}function u(e){r(o,i,a,s,u,"throw",e)}s(void 0)}))}}n.d(t,{Z:function(){return i}})},3144:function(e,t,n){"use strict";function r(e,t){for(var n=0;n1){var d=t[u],c=function(){for(var e=u+1;ed)return e}();if(null!=c)if(r>=t[c])return n[c]}if((null==s||s<1.15)&&r=0;f--)if(n[f]=s.outOfStarvationGap&&(l.Z.info("ABR: exit starvation mode."),this._inStarvationMode=!1):this._inStarvationMode&&(l.Z.info("ABR: exit starvation mode."),this._inStarvationMode=!1),this._inStarvationMode&&(o=function(e,t,n,r,i){if(!r){var a=t.bufferGap,o=t.speed,s=t.position,u=isFinite(a)?a:0,l=function(e,t){for(var n=-1,r=0;r-1.2){n=r;break}if(a>t&&t-i.time>-1.2){n=r;break}}}if(n<0)return[];for(var o=e[n],s=o.content.segment.time,u=[o],l=n+1;l0?d.progress[d.progress.length-1]:void 0,v=K(d);if(void 0!==f&&void 0!==v){var p=G(f,v);if((c-f.timestamp)/1e3<=p&&p-u/o>2e3)return v}if(d.content.segment.complete){var h=d.content.segment.duration,m=(c-d.requestTimestamp)/1e3;if(null!=n&&!(m<=(1.5*h+2)/o)){var g=h/m,y=n.bitrate*Math.min(.7,g);return void 0===i||y1&&(a/=e.speed),{bandwidthEstimate:o,bitrateChosen:a}},t.isUrgent=function(e,t,n,r){return null===t||e!==t.bitrate&&(e>t.bitrate?!this._inStarvationMode:function(e,t,n){if(n)return!0;var r=isFinite(e.bufferGap)?e.bufferGap:0,i=e.position.last+r,a=(0,V.Z)(t,(function(e){var t=e.content;return t.segment.duration>0&&t.segment.time+t.segment.duration>i}));if(void 0===a)return!0;var o=performance.now(),s=a.progress.length>0?a.progress[a.progress.length-1]:void 0,u=K(a);if(void 0===s||void 0===u)return!0;var l=G(s,u);return(o-s.timestamp)/1e3>1.2*l||l-r/e.speed>-1.5}(r,n,this._lowLatencyMode))},e}();function H(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return j(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return j(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function j(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ns.bitrate)return 2===this._lastAbrEstimate.algorithmType&&(null!==this._lastAbrEstimate.representation&&(this._lastMaintanableBitrate=this._lastAbrEstimate.representation.bitrate),this._consecutiveWrongGuesses=0),null;var u=this._scoreCalculator.getEstimate(n);if(2!==this._lastAbrEstimate.algorithmType){if(void 0===u)return null;if(this._canGuessHigher(a,o,u)){var d=Y(e,n);if(null!==d)return d}return null}if(this._isLastGuessValidated(s,r,u)&&(l.Z.debug("ABR: Guessed Representation validated",s.bitrate),this._lastMaintanableBitrate=s.bitrate,this._consecutiveWrongGuesses=0),n.id!==s.id)return s;if(this._shouldStopGuess(n,u,a,i))return this._consecutiveWrongGuesses++,this._blockGuessesUntil=performance.now()+Math.min(15e3*this._consecutiveWrongGuesses,12e4),function(e,t){var n=(0,U.Z)(e,(function(e){return e.id===t.id}));if(n<0)return l.Z.error("ABR: Current Representation not found."),null;for(;--n>=0;)if(e[n].bitrate=2.5&&performance.now()>this._blockGuessesUntil&&1===i&&r/t>1.01},t._shouldStopGuess=function(e,t,n,r){if(void 0!==t&&t[0]<1.01)return!0;if((void 0===t||t[0]<1.2)&&n<.6)return!0;for(var i,a=r.filter((function(t){return t.content.representation.id===e.id})),o=performance.now(),s=H(a);!(i=s()).done;){var u=i.value,l=o-u.requestTimestamp;if(u.content.segment.isInit){if(l>1e3)return!0}else{if(l>1e3*u.content.segment.duration+200)return!0;var d=K(u);if(void 0!==d&&d<.8*e.bitrate)return!0}}return!1},t._isLastGuessValidated=function(e,t,n){return void 0!==n&&1===n[1]&&n[0]>1.5||t>=e.bitrate&&(null===this._lastMaintanableBitrate||this._lastMaintanableBitratet.bitrate)return e[r];return null}var X=function(){function e(){var e=c.Z.getCurrent(),t=e.ABR_FAST_EMA,n=e.ABR_SLOW_EMA;this._fastEWMA=new z(t),this._slowEWMA=new z(n),this._bytesSampled=0}var t=e.prototype;return t.addSample=function(e,t){if(!(t1&&this._lastRepresentationWithGoodScore!==e&&(l.Z.debug("ABR: New last stable representation",e.bitrate),this._lastRepresentationWithGoodScore=e)},t.getEstimate=function(e){if(null!==this._currentRepresentationData&&this._currentRepresentationData.representation.id===e.id){var t=this._currentRepresentationData,n=t.ewma,r=t.loadedSegments,i=t.loadedDuration;return[n.getEstimate(),r>=5&&i>=10?1:0]}},t.getLastStableRepresentation=function(){return this._lastRepresentationWithGoodScore},e}();function te(e,t,n,r){var i=t<=n?n:t>=r?r:t,a=(0,U.Z)(e,(function(e){return e.bitrate>i}));return-1===a?e[e.length-1]:0===a?e[0]:e[a-1]}var ne=(0,E.ZP)(-1);ne.finish();var re=(0,E.ZP)(0);re.finish();var ie=(0,E.ZP)(1/0);ie.finish();var ae=(0,E.ZP)(void 0);ae.finish();var oe=(0,E.ZP)(1/0);oe.finish();var se=function(e){var t={},n=e.manualBitrates,r=e.minAutoBitrates,i=e.maxAutoBitrates,a=e.initialBitrates,o=e.throttlers,s=e.lowLatencyMode;return function(e,u,d,c,f){var v=e.adaptation.type,p=function(e){var n=t[e];if(null==n){l.Z.debug("ABR: Creating new BandwidthEstimator for ",e);var r=new X;return t[e]=r,r}return n}(v),h=(0,L.Z)(n[v],ne),m=(0,L.Z)(r[v],re),g=(0,L.Z)(i[v],ie),y=(0,L.Z)(a[v],0);return function(e,t){var n=e.bandwidthEstimator,r=e.context,i=e.currentRepresentation,a=e.filters,o=e.initialBitrate,s=e.lowLatencyMode,u=e.manualBitrate,d=e.maxAutoBitrate,c=e.minAutoBitrate,f=e.playbackObserver,v=e.representations,p=new ee,h=new W(null!=o?o:0,s),m=new J,g=O.Z,y={metrics:A,requestBegin:I,requestProgress:Z,requestEnd:x,addedSegment:function(e){g(e)}},_=new k.ZP;_.linkToSignal(t);var b=S(u.getValue(),v.getValue(),_.signal);return u.onUpdate(w,{clearSignal:t}),v.onUpdate(w,{clearSignal:t}),{estimates:b,callbacks:y};function S(e,t,o){if(0===t.length)return(0,E.ZP)({representation:null,bitrate:void 0,knownStableBitrate:void 0,manual:!1,urgent:!0});if(e>=0){var u=te(t,e,0,1/0);return(0,E.ZP)({representation:u,bitrate:void 0,knownStableBitrate:void 0,manual:!0,urgent:!0})}if(1===t.length)return(0,E.ZP)({bitrate:void 0,representation:t[0],manual:!1,urgent:!0,knownStableBitrate:void 0});var v,y=!1,_=t.map((function(e){return e.bitrate})),b=new F(_),S=new Q,k=new q(p,S),w=f.getReference().getValue(),A=(0,E.ZP)(Z());return f.listen((function(e){w=e,I()}),{includeLastObservation:!1,clearSignal:o}),g=function(e){if(null!==w){var t=w,n=t.position,r=t.speed,i=e.buffered,a=(0,T.L7)(i,n.last),o=e.content.representation,s=p.getEstimate(o),u=null==s?void 0:s[0],l={bufferGap:a,currentBitrate:o.bitrate,currentScore:u,speed:r};v=b.getEstimate(l),I()}},c.onUpdate(I,{clearSignal:o}),d.onUpdate(I,{clearSignal:o}),a.limitWidth.onUpdate(I,{clearSignal:o}),a.limitWidth.onUpdate(I,{clearSignal:o}),A;function I(){A.setValue(Z())}function Z(){var e=w,o=e.bufferGap,u=e.position,f=e.maximumPosition,g=a.limitWidth.getValue(),_=a.throttleBitrate.getValue(),b=i.getValue(),T=c.getValue(),E=d.getValue(),A=function(e,t,n){var r=e;n<1/0&&(r=function(e,t){if(0===e.length)return[];e.sort((function(e,t){return e.bitrate-t.bitrate}));var n=e[0].bitrate,r=Math.max(t,n),i=(0,U.Z)(e,(function(e){return e.bitrate>r}));return-1===i?e:e.slice(0,i)}(r,n));void 0!==t&&(r=function(e,t){var n=e.slice().sort((function(e,t){return(0,L.Z)(e.width,0)-(0,L.Z)(t.width,0)})),r=(0,V.Z)(n,(function(e){return"number"==typeof e.width&&e.width>=t}));if(void 0===r)return e;var i="number"==typeof r.width?r.width:0;return e.filter((function(e){return"number"!=typeof e.width||e.width<=i}))}(r,t));return r}(t,g,_),I=m.getRequests(),Z=h.getBandwidthEstimate(w,n,b,I,S.bandwidth),x=Z.bandwidthEstimate,R=Z.bitrateChosen,M=p.getLastStableRepresentation(),C=null===M?void 0:M.bitrate/(w.speed>0?w.speed:1);y&&o<=5?y=!1:!y&&isFinite(o)&&o>10&&(y=!0);var P=te(A,R,T,E),D=P.bitrate,N=null;y&&void 0!==v&&v>D&&(D=(N=te(A,v,T,E)).bitrate);var O=null;return s&&null!==b&&r.manifest.isDynamic&&f-u.last<40&&(O=k.getGuess(t,w,b,D,I)),null!==O&&O.bitrate>D?(l.Z.debug("ABR: Choosing representation with guess-based estimation.",O.bitrate,O.id),S.update(O,x,2),{bitrate:x,representation:O,urgent:null===b||O.bitrate=500||404===e.status||415===e.status||412===e.status:e.type===p.br.TIMEOUT||e.type===p.br.ERROR_EVENT:e instanceof fe.Z?"boolean"==typeof e.canRetry?e.canRetry:void 0!==e.xhr&&(e.xhr.status>=500||404===e.xhr.status||415===e.xhr.status||412===e.xhr.status):(0,ve.Z)(e)&&"INTEGRITY_ERROR"===e.code}function ge(e){return e instanceof le.Z?e.type===p.br.ERROR_EVENT&&!1===navigator.onLine:e instanceof fe.Z&&e.isOfflineError}function ye(e){return ge(e)?2:1}function _e(e,t,n,r,i){return be.apply(this,arguments)}function be(){return be=(0,A.Z)(Z().mark((function e(t,n,r,i,a){var o,s,u,d,c,f,v,p,h,m,g,y,_,b;return Z().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(b=function(e){var t;if(0===f.size)return e[0];var n=performance.now();return null===(t=e.filter((function(e){var t;return!0!==(null===(t=f.get(e))||void 0===t?void 0:t.isBlacklisted)})).reduce((function(e,t){var r,i=null===(r=f.get(t))||void 0===r?void 0:r.blockedUntil;return void 0!==i&&i<=n&&(i=void 0),void 0===e?[t,i]:void 0===i?void 0===e[1]?e:[t,void 0]:void 0===e[1]?e:iv?(c.blockedUntil=void 0,c.isBlacklisted=!0):(p=c.errorCounter,h=Math.min(o*Math.pow(2,p-1),s),m=(0,he.Z)(h),c.blockedUntil=performance.now()+m),e.abrupt("return",g(e.t0));case 22:case"end":return e.stop()}}),e,null,[[0,7]])})))).apply(this,arguments)},h=function(e){return m.apply(this,arguments)},p=function(){if(null===t){var e=f.get(null);if(void 0!==e&&e.isBlacklisted)return;return null}if(null===n)return b(t);var r=n.getCdnPreferenceForResource(t);return b(r)},null===a.cancellationError){e.next=9;break}return e.abrupt("return",Promise.reject(a.cancellationError));case 9:if(o=i.baseDelay,s=i.maxDelay,u=i.maxRetryRegular,d=i.maxRetryOffline,c=i.onRetry,null!==t&&0===t.length&&l.Z.warn("Fetchers: no CDN given to `scheduleRequestWithCdns`."),f=new Map,void 0!==(v=p())){e.next=15;break}throw new Error("No CDN to request");case 15:return e.abrupt("return",h(v));case 16:case"end":return e.stop()}}),e)}))),be.apply(this,arguments)}function Se(e,t,n){return _e(null,null,e,t,n)}function Te(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return Ee(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Ee(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function Ee(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0?this._consecutiveUnsafeMode=u,v=void 0===r?0:performance.now()-r,p=Math.max(this._settings.minimumManifestUpdateInterval-v,0),h=new k.ZP;if(h.linkToSignal(this._canceller.signal),this.scheduleManualRefresh=function(t){var i=t.enablePartialRefresh,a=t.delay,o=t.canUseUnsafeMode&&f,s=void 0===r?0:performance.now()-r,u=Math.max(n._settings.minimumManifestUpdateInterval-s,0),l=setTimeout((function(){h.cancel(),n._triggerNextManifestRefresh(e,{enablePartialRefresh:i,unsafeMode:o})}),Math.max((null!=a?a:0)-s,u));h.signal.register((function(){clearTimeout(l)}))},null!==e.expired){var m=setTimeout((function(){var t;null===(t=e.expired)||void 0===t||t.then((function(){h.cancel(),n._triggerNextManifestRefresh(e,{enablePartialRefresh:!1,unsafeMode:f})}),O.Z)}),p);h.signal.register((function(){clearTimeout(m)}))}if(void 0!==e.lifetime&&e.lifetime>=0){var g,y=1e3*e.lifetime-v;void 0===d?g=y:e.lifetime<3&&d>=100?(g=Math.min(Math.max(3e3-v,Math.max(y,0)+d),6*y),l.Z.info("MUS: Manifest update rythm is too frequent. Postponing next request.",y,g)):d>=1e3*e.lifetime/10?(g=Math.min(Math.max(y,0)+d,6*y),l.Z.info("MUS: Manifest took too long to parse. Postponing next request",g,g)):g=y;var _=setTimeout((function(){h.cancel(),n._triggerNextManifestRefresh(e,{enablePartialRefresh:!1,unsafeMode:f})}),Math.max(g,p));h.signal.register((function(){clearTimeout(_)}))}},r._triggerNextManifestRefresh=function(e,t){var n,r,i=this,a=t.enablePartialRefresh,o=t.unsafeMode,s=e.updateUrl;null!==this._prioritizedContentUrl?(n=!0,r=this._prioritizedContentUrl,this._prioritizedContentUrl=null):r=(n=!a||void 0===s)?e.getUrl():s;var u=e.clockOffset;o?(this._consecutiveUnsafeMode+=1,l.Z.info('Init: Refreshing the Manifest in "unsafeMode" for the '+String(this._consecutiveUnsafeMode)+" consecutive time.")):this._consecutiveUnsafeMode>0&&(l.Z.info('Init: Not parsing the Manifest in "unsafeMode" anymore after '+String(this._consecutiveUnsafeMode)+" consecutive times."),this._consecutiveUnsafeMode=0),this._isRefreshPending||(this._isRefreshPending=!0,this._fetchManifest(r).then((function(t){return t.parse({externalClockOffset:u,previousManifest:e,unsafeMode:o})})).then((function(t){i._isRefreshPending=!1;var r=t.manifest,a=t.sendingTime,o=t.parsingTime,s=performance.now();if(n)e.replace(r);else try{e.update(r)}catch(t){var u=t instanceof Error?t.message:"unknown error";l.Z.warn("MUS: Attempt to update Manifest failed: "+u,"Re-downloading the Manifest fully");var d=c.Z.getCurrent().FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY,f=void 0===a?0:performance.now()-a,v=Math.max(i._settings.minimumManifestUpdateInterval-f,0),p=O.Z,h=setTimeout((function(){p(),i._triggerNextManifestRefresh(e,{enablePartialRefresh:!1,unsafeMode:!1})}),Math.max(d-f,v));return void(p=i._canceller.signal.register((function(){clearTimeout(h)})))}var m=performance.now()-s;i._recursivelyRefreshManifest(e,{sendingTime:a,parsingTime:o,updatingTime:m})})).catch((function(e){i._isRefreshPending=!1,i._onFatalError(e)})))},r._onFatalError=function(e){this._canceller.isUsed()||(this.trigger("error",e),this.dispose())},n}(y.Z);var we=ke;function Ae(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return Ie(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Ie(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function Ie(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0&&this._removeIndexFromDowngradeList(n);var r=c.Z.getCurrent().DEFAULT_CDN_DOWNGRADE_TIME;this._downgradedCdnList.metadata.push(e);var i=window.setTimeout((function(){var n=xe(t._downgradedCdnList.metadata,e);n>=0&&t._removeIndexFromDowngradeList(n),t.trigger("priorityChange",null)}),r);this._downgradedCdnList.timeouts.push(i),this.trigger("priorityChange",null)},r._innerGetCdnPreferenceForResource=function(e){var t=this,n=e.reduce((function(e,n){return t._downgradedCdnList.metadata.some((function(e){return e.id===n.id&&e.baseUrl===n.baseUrl}))?e[1].push(n):e[0].push(n),e}),[[],[]]),r=n[0],i=n[1];return r.concat(i)},r._removeIndexFromDowngradeList=function(e){this._downgradedCdnList.metadata.splice(e,1);var t=this._downgradedCdnList.timeouts.splice(e,1);clearTimeout(t[0])},n}(y.Z);function xe(e,t){return 0===e.length?-1:void 0!==t.id?(0,U.Z)(e,(function(e){return e.id===t.id})):(0,U.Z)(e,(function(e){return e.baseUrl===t.baseUrl}))}var Re=n(520),Me=n(7714),Ce=function(){function e(){this._cache=new WeakMap}var t=e.prototype;return t.add=function(e,t){var n=e.representation;e.segment.isInit&&this._cache.set(n,t)},t.get=function(e){var t=e.representation;if(e.segment.isInit){var n=this._cache.get(t);if(void 0!==n)return n}return null},e}(),Pe=(0,_.Z)();var De=function(){function e(e){var t=e.prioritySteps;if(this._minPendingPriority=null,this._waitingQueue=[],this._pendingTasks=[],this._prioritySteps=t,this._prioritySteps.high>=this._prioritySteps.low)throw new Error("TP: the max high level priority should be given a lowerpriority number than the min low priority.")}var t=e.prototype;return t.create=function(e,t,n,r){var i,a=this;return(0,N.Z)(r,(function(o,s){return i={hasEnded:!1,priority:t,trigger:function(){if(!i.hasEnded){var e=function(){u(),a._endTask(i)},t=new k.ZP,u=t.linkToSignal(r);i.interrupter=t,t.signal.register((function(){i.interrupter=null,r.isCancelled()||n.beforeInterrupted()})),a._minPendingPriority=null===a._minPendingPriority?i.priority:Math.min(a._minPendingPriority,i.priority),a._pendingTasks.push(i),i.taskFn(t.signal).then((function(t){n.beforeEnded(),e(),o(t)})).catch((function(n){!r.isCancelled()&&t.isUsed()&&n instanceof k.FU||function(t){e(),s(t)}(n)}))}},taskFn:e,interrupter:null},a._canBeStartedNow(i)?(i.trigger(),a._isRunningHighPriorityTasks()&&a._interruptCancellableTasks()):a._waitingQueue.push(i),function(){return a._endTask(i)}}))},t._endTask=function(e){e.hasEnded=!0;var t=Ne(e.taskFn,this._waitingQueue);if(t>=0)this._waitingQueue.splice(t,1);else{var n=Ne(e.taskFn,this._pendingTasks);if(n<0)return;this._pendingTasks.splice(n,1),this._pendingTasks.length>0?this._minPendingPriority===e.priority&&(this._minPendingPriority=Math.min.apply(Math,this._pendingTasks.map((function(e){return e.priority})))):this._minPendingPriority=null,this._loopThroughWaitingQueue()}},t.updatePriority=function(e,t){var n=Ne(e,this._waitingQueue);if(n>=0){var r=this._waitingQueue[n];if(r.priority===t)return;if(r.priority=t,!this._canBeStartedNow(r))return;return this._findAndRunWaitingQueueTask(n),void(this._isRunningHighPriorityTasks()&&this._interruptCancellableTasks())}var i=Ne(e,this._pendingTasks);if(i<0)l.Z.warn("TP: request to update the priority of a non-existent task");else{var a=this._pendingTasks[i];if(a.priority!==t){var o=a.priority;a.priority=t,null===this._minPendingPriority||tt.priority?t.priority:e}),null);if(!(null===e||null!==this._minPendingPriority&&this._minPendingPriority=this._prioritySteps.low)return this._interruptPendingTask(t),this._interruptCancellableTasks()}},t._findAndRunWaitingQueueTask=function(e){return e>=this._waitingQueue.length||e<0?(l.Z.warn("TP : Tried to start a non existing task"),!1):(this._waitingQueue.splice(e,1)[0].trigger(),!0)},t._interruptPendingTask=function(e){var t,n=Ne(e.taskFn,this._pendingTasks);n<0?l.Z.warn("TP: Interrupting a non-existent pending task. Aborting..."):(this._pendingTasks.splice(n,1),this._waitingQueue.push(e),0===this._pendingTasks.length?this._minPendingPriority=null:this._minPendingPriority===e.priority&&(this._minPendingPriority=Math.min.apply(Math,this._pendingTasks.map((function(e){return e.priority})))),null===(t=e.interrupter)||void 0===t||t.cancel())},t._canBeStartedNow=function(e){return null===this._minPendingPriority||e.priority<=this._minPendingPriority},t._isRunningHighPriorityTasks=function(){return null!==this._minPendingPriority&&this._minPendingPriority<=this._prioritySteps.high},e}();function Ne(e,t){return(0,U.Z)(t,(function(t){return t.taskFn===e}))}var Oe=function(){function e(e,t,n){var r=new Ze(n),i=c.Z.getCurrent(),a=i.MIN_CANCELABLE_PRIORITY,o=i.MAX_HIGH_PRIORITY_LEVEL;this._transport=e,this._prioritizer=new De({prioritySteps:{high:o,low:a}}),this._cdnPrioritizer=r,this._backoffOptions=t}return e.prototype.createSegmentFetcher=function(e,t){var n,r,i,a=function(e,t){var n=t.maxRetryRegular,r=t.maxRetryOffline,i=t.lowLatencyMode,a=t.requestTimeout,o=c.Z.getCurrent(),s=o.DEFAULT_MAX_REQUESTS_RETRY_ON_ERROR,u=o.DEFAULT_REQUEST_TIMEOUT,l=o.DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE,d=o.INITIAL_BACKOFF_DELAY_BASE,f=o.MAX_BACKOFF_DELAY_BASE;return{maxRetryRegular:"image"===e?0:null!=n?n:s,maxRetryOffline:null!=r?r:l,baseDelay:i?d.LOW_LATENCY:d.REGULAR,maxDelay:i?f.LOW_LATENCY:f.REGULAR,requestTimeout:(0,b.Z)(a)?u:a}}(e,this._backoffOptions),o=function(e,t,n,r,i){var a={timeout:i.requestTimeout<0?void 0:i.requestTimeout},o=(0,Me.Z)(["audio","video"],e)?new Ce:void 0,s=t.loadSegment,u=t.parseSegment;return function(){var e=(0,A.Z)(Z().mark((function e(t,d,c){var v,p,h,m,g,y,_,T,E,w,A,I,x,R,M,C,P,D;return Z().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(D=function(){var e;E||!(0,b.Z)(y)&&void 0!==y.size&&void 0!==y.requestDuration&&_.length>0&&_.every((function(e){return e}))&&(E=!0,null===(e=r.onMetrics)||void 0===e||e.call(r,{size:y.size,requestDuration:y.requestDuration,content:t,segmentDuration:T}))},P=function(e){d.onRetry(ce(e))},C=function(e,n){_.push(!1);var r=_.length-1;return function(i){var a={data:e,isChunked:n};try{var o=u(a,t,i);return _[r]||(T=void 0!==T&&"media"===o.segmentType&&null!==o.chunkInfos&&void 0!==o.chunkInfos.duration?T+o.chunkInfos.duration:void 0,_[r]=!0,D()),o}catch(e){throw(0,f.Z)(e,{defaultCode:"PIPELINE_PARSE_ERROR",defaultReason:"Unknown parsing error"})}}},M=function(e){return s(e,t,a,c,w)},R=function(){var e;void 0===y&&(l.Z.debug("SF: Segment request cancelled",m),y=null,null===(e=r.onRequestEnd)||void 0===e||e.call(r,{id:g}))},m=(0,Re.K)(t),g=Pe(),_=[],T=0,E=!1,w={onProgress:function(e){var t;void 0===y&&void 0!==e.totalSize&&e.size0;){var e=this._queue.shift();void 0!==e&&e.reject(new k.FU)}if("open"===this._mediaSource.readyState)try{this._sourceBuffer.abort()}catch(e){l.Z.warn("AVSB: Failed to abort a "+this.bufferType+" SourceBuffer:",e instanceof Error?e:"")}},r._onPendingTaskError=function(e){if(this._lastInitSegment=null,null!==this._pendingTask){var t=e instanceof Error?e:new Error("An unknown error occured when doing operations on the SourceBuffer");this._pendingTask.reject(t)}},r._addToQueue=function(e,t){var n=this;return(0,N.Z)(t,(function(t,r){var i=0===n._queue.length&&null===n._pendingTask,a=(0,S.Z)({resolve:t,reject:r},e);return n._queue.push(a),i&&n._flush(),function(){var e=n._queue.indexOf(a);e>=0&&n._queue.splice(e,1),a.resolve=O.Z,a.reject=O.Z}}))},r._flush=function(){if(!this._sourceBuffer.updating){if(null!==this._pendingTask){var e=this._pendingTask;if(e.type!==ze.f.Push||0===e.data.length){switch(e.type){case ze.f.Push:null!==e.inventoryData&&this._segmentInventory.insertChunk(e.inventoryData);break;case ze.f.EndOfSegment:this._segmentInventory.completeSegment(e.value,this.getBufferedRanges());break;case ze.f.Remove:this.synchronizeInventory();break;default:(0,Be.Z)(e)}var t=e.resolve;return this._pendingTask=null,t(),void this._flush()}}else{var n=this._queue.shift();if(void 0===n)return;if(n.type!==ze.f.Push)this._pendingTask=n;else{var r,i=n.value;try{r=this._preparePushOperation(i.data)}catch(e){this._pendingTask=(0,S.Z)({data:[],inventoryData:i.inventoryInfos},n);var a=e instanceof Error?e:new Error("An unknown error occured when preparing a push operation");return this._lastInitSegment=null,void n.reject(a)}this._pendingTask=(0,S.Z)({data:r,inventoryData:i.inventoryInfos},n)}}try{switch(this._pendingTask.type){case ze.f.EndOfSegment:return l.Z.debug("AVSB: Acknowledging complete segment",(0,Re.K)(this._pendingTask.value)),void this._flush();case ze.f.Push:var o=this._pendingTask.data.shift();if(void 0===o)return void this._flush();l.Z.debug("AVSB: pushing segment",this.bufferType,(0,Re.K)(this._pendingTask.inventoryData)),this._sourceBuffer.appendBuffer(o);break;case ze.f.Remove:var s=this._pendingTask.value,u=s.start,d=s.end;l.Z.debug("AVSB: removing data from SourceBuffer",this.bufferType,u,d),this._sourceBuffer.remove(u,d);break;default:(0,Be.Z)(this._pendingTask)}}catch(e){this._onPendingTaskError(e)}}},r._preparePushOperation=function(e){var t=[],n=e.codec,r=e.timestampOffset,i=e.appendWindow,a=!1;if(void 0!==n&&n!==this.codec&&(l.Z.debug("AVSB: updating codec",n),a=function(e,t){if("function"==typeof e.changeType){try{e.changeType(t)}catch(e){return l.Z.warn("Could not call 'changeType' on the given SourceBuffer:",e instanceof Error?e:""),!1}return!0}return!1}(this._sourceBuffer,n),a?this.codec=n:l.Z.debug("AVSB: could not update codec",n,this.codec)),this._sourceBuffer.timestampOffset!==r){var o=r;l.Z.debug("AVSB: updating timestampOffset",this.bufferType,this._sourceBuffer.timestampOffset,o),this._sourceBuffer.timestampOffset=o}if(void 0===i[0]?this._sourceBuffer.appendWindowStart>0&&(this._sourceBuffer.appendWindowStart=0):i[0]!==this._sourceBuffer.appendWindowStart&&(i[0]>=this._sourceBuffer.appendWindowEnd&&(this._sourceBuffer.appendWindowEnd=i[0]+1),this._sourceBuffer.appendWindowStart=i[0]),void 0===i[1]?this._sourceBuffer.appendWindowEnd!==1/0&&(this._sourceBuffer.appendWindowEnd=1/0):i[1]!==this._sourceBuffer.appendWindowEnd&&(this._sourceBuffer.appendWindowEnd=i[1]),null!==e.initSegment&&(a||!this._isLastInitSegment(e.initSegment))){var s=e.initSegment;t.push(s);var u=(0,Fe._f)(s);this._lastInitSegment={data:u,hash:(0,Ve.Z)(u)}}return null!==e.chunk&&t.push(e.chunk),t},r._isLastInitSegment=function(e){if(null===this._lastInitSegment)return!1;if(this._lastInitSegment.data===e)return!0;var t=this._lastInitSegment.data;if(t.byteLength===e.byteLength){var n=(0,Fe._f)(e);if((0,Ve.Z)(n)===this._lastInitSegment.hash&&(0,m.Z)(t,n))return!0}return!1},n}(ze.C),Ge=["audio","video","text","image"];function We(e){return"audio"===e||"video"===e}var He=function(){function e(e,t){this._mediaElement=e,this._mediaSource=t,this._initializedSegmentBuffers={},this._onNativeBufferAddedOrDisabled=[]}e.isNative=function(e){return We(e)};var t=e.prototype;return t.getBufferTypes=function(){var e=this.getNativeBufferTypes();return null==h.Z.nativeTextTracksBuffer&&null==h.Z.htmlTextTracksBuffer||e.push("text"),null!=h.Z.imageBuffer&&e.push("image"),e},t.getNativeBufferTypes=function(){return"AUDIO"===this._mediaElement.nodeName?["audio"]:["video","audio"]},t.getStatus=function(e){var t=this._initializedSegmentBuffers[e];return void 0===t?{type:"uninitialized"}:null===t?{type:"disabled"}:{type:"initialized",value:t}},t.waitForUsableBuffers=function(e){var t=this;return this._areNativeBuffersUsable()?Promise.resolve():(0,N.Z)(e,(function(e){var n,r=function(){var e=t._onNativeBufferAddedOrDisabled.indexOf(n);e>=0&&t._onNativeBufferAddedOrDisabled.splice(e,1)};return n=function(){t._areNativeBuffersUsable()&&(r(),e())},t._onNativeBufferAddedOrDisabled.push(n),r}))},t.disableSegmentBuffer=function(t){var n=this._initializedSegmentBuffers[t];if(null!==n){if(void 0!==n)throw new Error("Cannot disable an active SegmentBuffer.");this._initializedSegmentBuffers[t]=null,e.isNative(t)&&this._onNativeBufferAddedOrDisabled.forEach((function(e){return e()}))}else l.Z.warn("SBS: The "+t+" SegmentBuffer was already disabled.")},t.createSegmentBuffer=function(e,t,n){void 0===n&&(n={});var r,i=this._initializedSegmentBuffers[e];if(We(e)){if(null!=i)return i instanceof Ke&&i.codec!==t?l.Z.warn("SB: Reusing native SegmentBuffer with codec",i.codec,"for codec",t):l.Z.info("SB: Reusing native SegmentBuffer with codec",t),i;l.Z.info("SB: Adding native SegmentBuffer with codec",t);var a=new Ke(e,t,this._mediaSource);return this._initializedSegmentBuffers[e]=a,this._onNativeBufferAddedOrDisabled.forEach((function(e){return e()})),a}if(null!=i)return l.Z.info("SB: Reusing a previous custom SegmentBuffer for the type",e),i;if("text"===e){if(l.Z.info("SB: Creating a new text SegmentBuffer"),"html"===n.textTrackMode){if(null==h.Z.htmlTextTracksBuffer)throw new Error("HTML Text track feature not activated");r=new h.Z.htmlTextTracksBuffer(this._mediaElement,n.textTrackElement)}else{if(null==h.Z.nativeTextTracksBuffer)throw new Error("Native Text track feature not activated");r=new h.Z.nativeTextTracksBuffer(this._mediaElement,!0===n.hideNativeSubtitle)}return this._initializedSegmentBuffers.text=r,r}if("image"===e){if(null==h.Z.imageBuffer)throw new Error("Image buffer feature not activated");return l.Z.info("SB: Creating a new image SegmentBuffer"),r=new h.Z.imageBuffer,this._initializedSegmentBuffers.image=r,r}throw l.Z.error("SB: Unknown buffer type:",e),new v.Z("BUFFER_TYPE_UNKNOWN","The player wants to create a SegmentBuffer of an unknown type.")},t.disposeSegmentBuffer=function(e){var t=this._initializedSegmentBuffers[e];null!=t?(l.Z.info("SB: Aborting SegmentBuffer",e),t.dispose(),delete this._initializedSegmentBuffers[e]):l.Z.warn("SB: Trying to dispose a SegmentBuffer that does not exist")},t.disposeAll=function(){var e=this;Ge.forEach((function(t){"initialized"===e.getStatus(t).type&&e.disposeSegmentBuffer(t)}))},t._areNativeBuffersUsable=function(){var e=this,t=this.getNativeBufferTypes();return!t.some((function(t){return void 0===e._initializedSegmentBuffers[t]}))&&!t.every((function(t){return null===e._initializedSegmentBuffers[t]}))},e}(),je=n(7473),qe=n.n(je),Ye=function(){function e(e){this._array=[],this._sortingFn=e}var t=e.prototype;return t.add=function(){for(var e=arguments.length,t=new Array(e),n=0;n=this._array.length)throw new Error("Invalid index.");return this._array[e]},t.toArray=function(){return this._array.slice()},t.findFirst=function(e){return(0,V.Z)(this._array,e)},t.has=function(e){return(0,Me.Z)(this._array,e)},t.removeElement=function(e){var t=this._array.indexOf(e);if(t>=0)return this._array.splice(t,1),t},t.head=function(){return this._array[0]},t.last=function(){return this._array[this._array.length-1]},t.shift=function(){return this._array.shift()},t.pop=function(){return this._array.pop()},e}(),Xe=function(){function e(e){this._weakMap=new WeakMap,this._fn=e}var t=e.prototype;return t.get=function(e){var t=this._weakMap.get(e);if(void 0===t){var n=this._fn(e);return this._weakMap.set(e,n),n}return t},t.destroy=function(e){this._weakMap.delete(e)},e}();function Qe(e,t){var n,r=e.segmentBuffer,i=e.playbackObserver,a=e.maxBufferBehind,o=e.maxBufferAhead;function s(){(function(e,t,n,r,i){return $e.apply(this,arguments)})(r,n,a.getValue(),o.getValue(),t).catch((function(e){var t=e instanceof Error?e.message:"Unknown error";l.Z.error("Could not run BufferGarbageCollector:",t)}))}i.listen((function(e){var t;n=null!==(t=e.position.pending)&&void 0!==t?t:e.position.last,s()}),{includeLastObservation:!0,clearSignal:t}),a.onUpdate(s,{clearSignal:t}),o.onUpdate(s,{clearSignal:t}),s()}function $e(){return($e=(0,A.Z)(Z().mark((function e(t,n,r,i,a){var o,s,u,d,c,f,v,p;return Z().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(isFinite(r)||isFinite(i)){e.next=2;break}return e.abrupt("return",Promise.resolve());case 2:o=[],s=(0,T.F_)(t.getBufferedRanges(),n),u=s.innerRange,d=s.outerRanges,c=function(){if(isFinite(i)){for(var e=0;et.start&&o.push({start:n+i,end:t.end})}null!=u&&n+i=t.end?o.push(t):n>=t.end&&n-r>t.start&&n-ru.start&&o.push({start:u.start,end:n-r})}}(),c(),f=0,v=o;case 9:if(!(f0&&n[0].segment.id===e._mediaSegmentAwaitingInitMetadata)){var r=e._mediaSegmentRequest;if(0===n.length){if(null===r)return;return l.Z.debug("Stream: no more media segment to request. Cancelling queue.",e._content.adaptation.type),void e._restartMediaSegmentDownloadingQueue()}if(null===r)return l.Z.debug("Stream: Media segments now need to be requested. Starting queue.",e._content.adaptation.type,n.length),void e._restartMediaSegmentDownloadingQueue();var i=n[0];return r.segment.id!==i.segment.id?(l.Z.debug("Stream: Next media segment changed, cancelling previous",e._content.adaptation.type),void e._restartMediaSegmentDownloadingQueue()):void(r.priority!==i.priority&&(l.Z.debug("Stream: Priority of next media segment changed, updating",e._content.adaptation.type,r.priority,i.priority),e._segmentFetcher.updatePriority(r.request,i.priority)))}}),{emitCurrentValue:!0,clearSignal:this._currentCanceller.signal}),this._downloadQueue.onUpdate((function(t){var n,r=e._initSegmentRequest;null===t.initSegment||null===r?(null===(n=t.initSegment)||void 0===n?void 0:n.segment.id)!==(null==r?void 0:r.segment.id)&&(null===t.initSegment&&l.Z.debug("Stream: no more init segment to request. Cancelling queue.",e._content.adaptation.type),e._restartInitSegmentDownloadingQueue(t.initSegment)):t.initSegment.priority!==r.priority&&e._segmentFetcher.updatePriority(r.request,t.initSegment.priority)}),{emitCurrentValue:!0,clearSignal:this._currentCanceller.signal}))},r.stop=function(){var e;null===(e=this._currentCanceller)||void 0===e||e.cancel(),this._currentCanceller=null},r._restartMediaSegmentDownloadingQueue=function(){var e=this;null!==this._mediaSegmentRequest&&this._mediaSegmentRequest.canceller.cancel();!function t(n){if(null!==e._currentCanceller&&e._currentCanceller.isUsed())e._mediaSegmentRequest=null;else{if(void 0===n)return e._mediaSegmentRequest=null,void e.trigger("emptyQueue",null);var r=new k.ZP,i=null===e._currentCanceller?O.Z:r.linkToSignal(e._currentCanceller.signal),a=n.segment,o=n.priority,s=(0,S.Z)({segment:a},e._content),u=!1,d=!1;r.signal.register((function(){e._mediaSegmentRequest=null,u||(e._mediaSegmentAwaitingInitMetadata===a.id&&(e._mediaSegmentAwaitingInitMetadata=null),u=!0,d=!1)}));var c=function(t){(0,g.Z)("media"===t.segmentType,"Should have loaded a media segment."),e.trigger("parsedMediaSegment",(0,S.Z)({},t,{segment:a}))},f=function(){var n=e._downloadQueue.getValue().segmentQueue;if(0===n.length)return u=!0,void e.trigger("emptyQueue",null);n[0].segment.id===a.id&&n.shift(),u=!0,t(n[0])},v=e._segmentFetcher.createRequest(s,o,{onRetry:function(t){e.trigger("requestRetry",{segment:a,error:t})},beforeInterrupted:function(){l.Z.info("Stream: segment request interrupted temporarly.",a.id,a.time)},onChunk:function(t){var n=e._initSegmentInfoRef.getValue();void 0!==n?c(t(null!=n?n:void 0)):(d=!0,e._initSegmentInfoRef.waitUntilDefined((function(e){c(t(null!=e?e:void 0))}),{clearSignal:r.signal}))},onAllChunksReceived:function(){d?(e._mediaSegmentAwaitingInitMetadata=a.id,e._initSegmentInfoRef.waitUntilDefined((function(){e._mediaSegmentAwaitingInitMetadata=null,d=!1,e.trigger("fullyLoadedSegment",a)}),{clearSignal:r.signal})):e.trigger("fullyLoadedSegment",a)},beforeEnded:function(){i(),e._mediaSegmentRequest=null,d?e._initSegmentInfoRef.waitUntilDefined(f,{clearSignal:r.signal}):f()}},r.signal);v.catch((function(t){i(),u||(u=!0,e.stop(),e.trigger("error",t))})),e._mediaSegmentRequest={segment:a,priority:o,request:v,canceller:r}}}(this._downloadQueue.getValue().segmentQueue[0])},r._restartInitSegmentDownloadingQueue=function(e){var t=this;if((null===this._currentCanceller||!this._currentCanceller.isUsed())&&(null!==this._initSegmentRequest&&this._initSegmentRequest.canceller.cancel(),null!==e)){var n=new k.ZP,r=null===this._currentCanceller?O.Z:n.linkToSignal(this._currentCanceller.signal),i=e.segment,a=e.priority,o=(0,S.Z)({segment:i},this._content),s=!1,u=this._segmentFetcher.createRequest(o,a,{onRetry:function(e){t.trigger("requestRetry",{segment:i,error:e})},beforeInterrupted:function(){l.Z.info("Stream: init segment request interrupted temporarly.",i.id)},beforeEnded:function(){r(),t._initSegmentRequest=null,s=!0},onChunk:function(e){var n,r=e(void 0);(0,g.Z)("init"===r.segmentType,"Should have loaded an init segment."),t.trigger("parsedInitSegment",(0,S.Z)({},r,{segment:i})),"init"===r.segmentType&&t._initSegmentInfoRef.setValue(null!==(n=r.initTimescale)&&void 0!==n?n:null)},onAllChunksReceived:function(){t.trigger("fullyLoadedSegment",i)}},n.signal);u.catch((function(e){r(),s||(s=!0,t.stop(),t.trigger("error",e))})),n.signal.register((function(){t._initSegmentRequest=null,s||(s=!0)})),this._initSegmentRequest={segment:i,priority:a,request:u,canceller:n}}},n}(y.Z);function et(e,t,n,r,i){var a=e.period,o=e.adaptation,s=e.representation,u=function(e,t){for(var n=0;n=t.end)return null;if(r.bufferedEnd>t.start)return n}return null}(i,t);if(null===u){if(null===n){if(r&&void 0!==a.end&&t.end>=a.end)return{start:void 0,end:null};var d=s.index.checkDiscontinuity(t.start);if(null!==d)return{start:void 0,end:d}}return null}var c=i[u];if(void 0!==c.bufferedStart&&c.bufferedStart>t.start&&(null===n||c.infos.segment.end<=n)){var f=c.bufferedStart;return r||!1===s.index.awaitSegmentBetween(t.start,f)?(l.Z.debug("RS: current discontinuity encountered",o.type,c.bufferedStart),{start:void 0,end:f}):null}var v=function(e,t,n){if(n<=0)return l.Z.error("RS: Asked to check a discontinuity before the first chunk."),null;for(var r=n;r=t.end)return null;if(i.bufferedStart-a.bufferedEnd>0)return r}return null}(i,t,u+1);if(null!==v){var p=i[v-1],h=i[v];if(null===n||h.infos.segment.end<=n){if(!r&&!1!==s.index.awaitSegmentBetween(p.infos.segment.end,h.infos.segment.time))return null;var m=p.bufferedEnd,g=h.bufferedStart;return l.Z.debug("RS: future discontinuity encountered",o.type,m,g),{start:m,end:g}}}if(null===n){if(r&&void 0!==a.end){if(t.end=0;n--){var r=e[n];if(void 0===r.bufferedStart)return null;if(r.bufferedStart=a.end)return null;for(var b=i.length-1;b>=0;b--){var S=i[b];if(void 0===S.bufferedStart)break;if(S.bufferedStart=n.length-1?null:n[t+1],s=null;if(function(e,t,n){var r=c.Z.getCurrent().MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT;if(void 0===e.bufferedStart)return l.Z.warn("Stream: Start of a segment unknown. Assuming it is garbage collected by default.",e.start),!0;if(null!==t&&void 0!==t.bufferedEnd&&e.bufferedStart-t.bufferedEnd<.1)return!1;if(nr)return l.Z.info("Stream: The start of the wanted segment has been garbage collected",e.start,e.bufferedStart),!0;return!1}(e,r,o.start)){if(function(e,t){var n,r;if(e.length<2)return!0;var i=e[e.length-1],a=null===(n=i.buffered)||void 0===n?void 0:n.start;if(void 0!==t&&void 0!==a&&t-a>.05)return!0;var o=e[e.length-2],s=null===(r=o.buffered)||void 0===r?void 0:r.start;if(void 0===s||void 0===a)return!0;return Math.abs(s-a)>.01}(s=a(e.infos),e.bufferedStart))return!1;l.Z.debug("Stream: skipping segment gc-ed at the start",e.start,e.bufferedStart)}if(function(e,t,n){var r=c.Z.getCurrent().MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT;if(void 0===e.bufferedEnd)return l.Z.warn("Stream: End of a segment unknown. Assuming it is garbage collected by default.",e.end),!0;if(null!==t&&void 0!==t.bufferedStart&&t.bufferedStart-e.bufferedEnd<.1)return!1;if(n>e.bufferedEnd&&e.end-e.bufferedEnd>r)return l.Z.info("Stream: The end of the wanted segment has been garbage collected",e.start,e.bufferedStart),!0;return!1}(e,i,o.end)){if(function(e,t){var n,r;if(e.length<2)return!0;var i=e[e.length-1],a=null===(n=i.buffered)||void 0===n?void 0:n.end;if(void 0!==t&&void 0!==a&&a-t>.05)return!0;var o=e[e.length-2],s=null===(r=o.buffered)||void 0===r?void 0:r.end;if(void 0===s||void 0===a)return!0;return Math.abs(s-a)>.01}(s=null!=s?s:a(e.infos),e.bufferedEnd))return!1;l.Z.debug("Stream: skipping segment gc-ed at the end",e.end,e.bufferedEnd)}return!0})),m=c.Z.getCurrent(),g=m.MINIMUM_SEGMENT_SIZE,y=m.MIN_BUFFER_AHEAD,_=!1,b=Math.min(1/60,g),T=!1,E=[],k=p.filter((function(e){var t=(0,S.Z)({segment:e},n);if(s.length>0&&s.some((function(e){return(0,Re.z)(t,e)})))return!1;var u=e.duration,c=e.time,p=e.end;if(e.isInit)return!0;if(_)return E.push(e),!1;if(e.complete&&u0&&s.some((function(e){if(e.period.id!==n.period.id||e.adaptation.id!==n.adaptation.id)return!1;var a=e.segment;return!(a.time-b>c)&&(!(a.end+b-b&&w.end-p>-b)return!1}}var A=u*n.representation.bitrate;if(v-A<0&&(T=!0,c>o.start+y))return _=!0,E.push(e),!1;var I=a(t);if(I.length>1){var Z=I[I.length-1],x=I[I.length-2];if(null===Z.buffered&&null===x.buffered)return l.Z.warn("Stream: Segment GCed multiple times in a row, ignoring it.","If this happens a lot and lead to unpleasant experience, please check your device's available memory. If it's low when this message is emitted, you might want to update the RxPlayer's settings (`maxBufferAhead`, `maxVideoBufferSize` etc.) so less memory is used by regular media data buffering."+d.type,f.id,e.time),!1}for(var R=0;Rc){var C=M.start>c+b||nt(h,R).ende[n].start;)n++;return e[--n]}function rt(e,t,n,r){var i=c.Z.getCurrent().CONTENT_REPLACEMENT_PADDING;return e.period.id===t.period.id&&(!(e.segment.timea}return rr}(e.representation,t.representation,r)))}function it(e,t){for(var n=e-t,r=c.Z.getCurrent().SEGMENT_PRIORITIES_STEPS,i=0;i=u&&l.isInitialized()&&l.isFinished()&&function(e,t,n){var r;return t.containsTime(n)&&e.isLastPeriodKnown&&t.id===(null===(r=e.periods[e.periods.length-1])||void 0===r?void 0:r.id)}(a,o,t)?u-1:t-.1;var d,c=i+n;d=!(!s.index.isInitialized()||!s.index.isFinished()||void 0===o.end)&&(void 0===u?c>=o.end:null===u||c>=u);return{start:Math.max(i,o.start),end:Math.min(c,null!==(r=o.end)&&void 0!==r?r:1/0),hasReachedPeriodEnd:d}}(e,u,i),d=s.index.shouldRefresh(l.start,l.end),f=o.getPendingOperations().filter((function(e){return e.type===ze.f.EndOfSegment})).map((function(e){return e.value})),v=function(e,t){for(var n=c.Z.getCurrent().MINIMUM_SEGMENT_SIZE,r=Math.max(1/60,n),i=e.start+r,a=e.end-r,o=[],s=t.length-1;s>=0;s--){var u=t[s],l=u.infos.representation;if(!u.partiallyPushed&&!1!==l.decipherable&&l.isSupported){var d=u.infos.segment,f=d.time/d.timescale;((d.complete?f+d.duration/d.timescale:u.end)>i&&fi&&u.start0&&(S=Math.min.apply(Math,f.map((function(e){return e.segment.time})))),m.length>0&&(S=null!==S?Math.min(S,m[0].time):m[0].time),y.length>0&&(S=null!==S?Math.min(S,y[0].segment.time):y[0].segment.time),{imminentDiscontinuity:et(e,l,S,_,v),hasFinishedLoading:_,neededSegments:y,isBufferFull:g,shouldRefreshManifest:d}}function ot(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return st(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return st(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function st(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nu.end||e+ni.start&&o.push({start:i.start,end:e-n}),e+n0&&Z.every((function(e){return void 0!==e.keyIds}))&&(I=!0,t.encryptionDataEncountered(Z.map((function(e){return(0,S.Z)({content:r},e)}))),_.isUsed()))return}var x=new Je(r,w,s,A);return x.addEventListener("error",(function(e){b.signal.isCancelled()||(_.cancel(),t.error(e))})),x.addEventListener("parsedInitSegment",M),x.addEventListener("parsedMediaSegment",M),x.addEventListener("emptyQueue",R),x.addEventListener("requestRetry",(function(e){if(t.warning(e.error),!b.signal.isCancelled()){var n=e.segment,r=v.index;!1===r.isSegmentStillAvailable(n)?R():r.canBeOutOfSyncError(e.error,n)&&t.manifestMightBeOufOfSync()}})),x.addEventListener("fullyLoadedSegment",(function(e){o.endOfSegment((0,S.Z)({segment:e},r),_.signal).catch(C)})),x.start(),b.signal.register((function(){x.removeEventListener(),x.stop()})),a.listen(R,{includeLastObservation:!1,clearSignal:b.signal}),p.onUpdate(R,{emitCurrentValue:!1,clearSignal:b.signal}),h.onUpdate(R,{emitCurrentValue:!1,clearSignal:b.signal}),u.onUpdate(R,{emitCurrentValue:!1,clearSignal:b.signal}),void R();function R(){var e,n;if(!b.isUsed()){var i=a.getReference().getValue(),s=null!==(e=i.position.pending)&&void 0!==e?e:i.position.last,f=at(r,s,a,g.getValue(),p.getValue(),h.getValue(),o),m=f.neededSegments,S=null;if(v.index.isInitialized()){if(m.length>0&&!T.isLoaded&&null!==T.segment){var E=m[0].priority;S={segment:T.segment,priority:E}}}else if(null===T.segment)l.Z.warn("Stream: Uninitialized index without an initialization segment");else if(T.isLoaded)l.Z.warn("Stream: Uninitialized index with an already loaded initialization segment");else{var k=null!==(n=i.position.pending)&&void 0!==n?n:i.position.last;S={segment:T.segment,priority:it(d.start,k)}}var A=u.getValue();if(null===A)w.setValue({initSegment:S,segmentQueue:m});else{if(A.urgent)return l.Z.debug("Stream: Urgent switch, terminate now.",y),w.setValue({initSegment:null,segmentQueue:[]}),w.finish(),b.cancel(),void t.terminating();var I=m[0],Z=x.getRequestedInitSegment(),R=x.getRequestedMediaSegment(),M=null===R||void 0===I||R.id!==I.segment.id?[]:[I],P=null===Z?null:S;if(w.setValue({initSegment:P,segmentQueue:M}),0===M.length&&null===P)return l.Z.debug("Stream: No request left, terminate",y),w.finish(),b.cancel(),void t.terminating()}if(t.streamStatusUpdate({period:d,position:i.position.last,bufferType:y,imminentDiscontinuity:f.imminentDiscontinuity,isEmptyStream:!1,hasFinishedLoading:f.hasFinishedLoading,neededSegments:f.neededSegments}),!b.signal.isCancelled()){var D=c.Z.getCurrent().UPTO_CURRENT_POSITION_CLEANUP;if(f.isBufferFull){var N=Math.max(0,s-D);N>0&&o.removeBuffer(0,N,_.signal).catch(C)}f.shouldRefreshManifest&&t.needsManifestRefresh()}}}function M(e){if(!_.isUsed())if("init"===e.segmentType){if(T.segmentData=e.initializationData,T.isLoaded=!0,!I){var n=v.getAllEncryptionData();n.length>0&&t.encryptionDataEncountered(n.map((function(e){return(0,S.Z)({content:r},e)})))}(function(e,t){return vt.apply(this,arguments)})({playbackObserver:a,content:r,segment:e.segment,segmentData:e.initializationData,segmentBuffer:o},_.signal).then((function(e){null!==e&&t.addedSegment(e)})).catch(C),R()}else{var i=e.inbandEvents,s=e.needsManifestRefresh,u=e.protectionDataUpdate;if(!I&&u){var l=v.getAllEncryptionData();if(l.length>0&&(t.encryptionDataEncountered(l.map((function(e){return(0,S.Z)({content:r},e)}))),_.isUsed()))return}if(!0===s&&(t.needsManifestRefresh(),_.isUsed()))return;if(void 0!==i&&i.length>0&&(t.inbandEvent(i),_.isUsed()))return;var d=T.segmentData;(function(e,t){return pt.apply(this,arguments)})({playbackObserver:a,content:r,initSegmentData:d,parsedSegment:e,segment:e.segment,segmentBuffer:o},_.signal).then((function(e){null!==e&&t.addedSegment(e)})).catch(C)}}function C(e){_.isUsed()&&e instanceof k.FU||(_.cancel(),t.error(e))}};var mt=function(e,t,n){var r=e.playbackObserver,i=e.content,a=e.options,o=e.representationEstimator,s=e.segmentBuffer,u=e.segmentFetcherCreator,d=e.wantedBufferAhead,p=e.maxVideoBufferSize,h="direct"===a.manualBitrateSwitchingMode,m=i.manifest,g=i.period,y=i.adaptation,_=new k.ZP;_.linkToSignal(n);var b,T=new Map,w=(0,E.$l)(null,_.signal),A=function(e,t,n,r,i,a){var o=e.manifest,s=e.adaptation,u=(0,E.ZP)([],a);f(),o.addEventListener("decipherabilityUpdate",f);var l=a.register(p),d=t(e,n,u,r,a),c=d.estimates;return{abrCallbacks:d.callbacks,estimateRef:c};function f(){var e=s.getPlayableRepresentations();if(0===e.length){var t=new v.Z("NO_PLAYABLE_REPRESENTATION","No Representation in the chosen "+s.type+" Adaptation can be played");return p(),void i(t)}var n=u.getValue();n.length===e.length&&n.every((function(t,n){return t.id===e[n].id}))||u.setValue(e)}function p(){o.removeEventListener("decipherabilityUpdate",f),void 0!==l&&l()}}(i,o,w,r,(function(e){_.cancel(),t.error(e)}),_.signal),I=A.estimateRef,Z=A.abrCallbacks,x=u.createSegmentFetcher(y.type,{onRequestBegin:Z.requestBegin,onRequestEnd:Z.requestEnd,onProgress:Z.requestProgress,onMetrics:Z.metrics});function R(e,t,n,i){var o=new k.ZP;o.linkToSignal(_.signal);var u=(0,E.lR)(d,(function(t){return t*function(e){var t=T.get(e.id),n=void 0!==t?t:1;void 0===t&&T.set(e.id,n);return n}(e)}),o.signal),c="video"===y.type?p:(0,E.$l)(1/0);l.Z.info("Stream: changing representation",y.type,e.id,e.bitrate);var v=(0,S.Z)({},i,{error:function(r){var a,o=(0,f.Z)(r,{defaultCode:"NONE",defaultReason:"Unknown `RepresentationStream` error"});if("BUFFER_FULL_ERROR"!==o.code)i.error(r);else{var s=d.getValue(),u=.7*(null!==(a=T.get(e.id))&&void 0!==a?a:1);if(u<=.05||s*u<=2)throw o;T.set(e.id,u),(0,pe.Z)(4e3,_.signal).then((function(){return R(e,t,n,i)})).catch(O.Z)}},terminating:function(){o.cancel(),i.terminating()}});ht({playbackObserver:r,content:{representation:e,adaptation:y,period:g,manifest:m},segmentBuffer:s,segmentFetcher:x,terminate:t,options:{bufferGoal:u,maxBufferSize:c,drmSystemId:a.drmSystemId,fastSwitchThreshold:n}},v,_.signal)}I.onUpdate((function(e){var n=e.bitrate;void 0!==n&&n!==b&&(b=n,l.Z.debug("Stream: new "+y.type+" bitrate estimate",n),t.bitrateEstimationChange({type:y.type,bitrate:n}))}),{emitCurrentValue:!0,clearSignal:_.signal}),function e(n){var i=new k.ZP;i.linkToSignal(_.signal);var o=I.getValue(),s=o.representation,u=o.manual;if(null===s)return;if(h&&u&&!n){var d=c.Z.getCurrent().DELTA_POSITION_AFTER_RELOAD;return qe()((function(){r.listen((function(e){var n,i;if(I.getValue().manual){var a=r.getCurrentTime()+d.bitrateSwitch,o=Math.min(Math.max(g.start,a),null!==(n=g.end)&&void 0!==n?n:1/0),s=!(null!==(i=e.paused.pending)&&void 0!==i?i:r.getIsPaused());return t.waitingMediaSourceReload({bufferType:y.type,period:g,position:o,autoPlay:s})}}),{includeLastObservation:!0,clearSignal:i.signal})}))}var f=(0,E.$l)(null,i.signal);I.onUpdate((function(e){if(null!==e.representation&&e.representation.id!==s.id)return e.urgent?(l.Z.info("Stream: urgent Representation switch",y.type),f.setValue({urgent:!0})):(l.Z.info("Stream: slow Representation switch",y.type),f.setValue({urgent:!1}))}),{clearSignal:i.signal,emitCurrentValue:!0});var v=(0,E.$l)(0);a.enableFastSwitching&&I.onUpdate((function(e){v.setValueIfChanged(null==e?void 0:e.knownStableBitrate)}),{clearSignal:i.signal,emitCurrentValue:!0});var p={type:y.type,period:g,representation:s};if(w.setValue(s),_.isUsed())return;if(t.representationChange(p),_.isUsed())return;var m={streamStatusUpdate:t.streamStatusUpdate,encryptionDataEncountered:t.encryptionDataEncountered,manifestMightBeOufOfSync:t.manifestMightBeOufOfSync,needsManifestRefresh:t.needsManifestRefresh,inbandEvent:t.inbandEvent,warning:t.warning,error:function(e){_.cancel(),t.error(e)},addedSegment:function(e){Z.addedSegment(e),_.isUsed()||t.addedSegment(e)},terminating:function(){if(!i.isUsed())return i.cancel(),e(!1)}};R(s,f,v,m)}(!0)},gt=n(9252);var yt=function(e,t){var n=e.split(";"),r=n[0],i=n.slice(1),a=t.split(";"),o=a[0],s=a.slice(1);if(r!==o)return!1;var u=(0,V.Z)(i,(function(e){return(0,gt.Z)(e,"codecs=")})),l=(0,V.Z)(s,(function(e){return(0,gt.Z)(e,"codecs=")}));if(void 0===u||void 0===l)return!1;var d=u.substring(7),c=l.substring(7);return d.split(".")[0]===c.split(".")[0]};function _t(e,t,n,r,i){if(void 0!==e.codec&&"reload"===i.onCodecSwitch&&!function(e,t){return e.getPlayableRepresentations().some((function(e){return yt(e.getMimeTypeString(),t)}))}(n,e.codec))return{type:"needs-reload",value:void 0};var a=e.getBufferedRanges();if(0===a.length)return{type:"continue",value:void 0};var o=(0,T.JN)(a),s=t.start,u=null==t.end?1/0:t.end,l=(0,T.tn)(o,[{start:s,end:u}]);if(0===l.length)return{type:"continue",value:void 0};e.synchronizeInventory();var d=e.getInventory();if(!d.some((function(e){return e.infos.period.id===t.id&&e.infos.adaptation.id!==n.id})))return{type:"continue",value:void 0};var f=function(e,t,n){return e.reduce((function(e,r){if(r.infos.period.id!==t.id||r.infos.adaptation.id!==n.id)return e;var i=r.bufferedStart,a=r.bufferedEnd;return void 0===i||void 0===a||e.push({start:i,end:a}),e}),[])}(d,t,n),v=(0,T.uH)(l,f);if(0===v.length)return{type:"continue",value:void 0};var p=r.currentTime,h=i.audioTrackSwitchingMode;if(("video"===n.type||"audio"===n.type&&"reload"===h)&&(0,T.Ti)({start:s,end:u},p)&&(r.readyState>1||!n.getPlayableRepresentations().some((function(t){var n;return yt(t.getMimeTypeString(),null!==(n=e.codec)&&void 0!==n?n:"")})))&&!(0,T.A1)(f,p))return{type:"needs-reload",value:void 0};var m="audio"===n.type&&"direct"===h,g=[],y=function(e,t){for(var n=0;n=t.start)return n>0?e[n-1]:null;return e.length>0?e[e.length-1]:null}(d,t);null!==y&&(void 0===y.bufferedEnd||t.start-y.bufferedEnd<1)&&g.push({start:0,end:t.start+1});var _=n.type,b=c.Z.getCurrent().ADAPTATION_SWITCH_BUFFER_PADDINGS,S=b[_].before;null==S&&(S=0);var E=b[_].after;if(null==E&&(E=0),m||g.push({start:p-S,end:p+E}),void 0!==t.end){var k=function(e,t){for(var n=0;nt.start)return e[n];return null}(d,t);null!==k&&(void 0===k.bufferedStart||k.bufferedStart-t.end<1)&&g.push({start:t.end-1,end:Number.MAX_VALUE})}var w=(0,T.uH)(v,g);return 0===w.length?{type:"continue",value:void 0}:m?{type:"flush-buffer",value:w}:{type:"clean-buffer",value:w}}function bt(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return St(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return St(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function St(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=o.end&&(l.Z.debug('Stream: full "empty" AdaptationStream',n),s=!0),i.streamStatusUpdate({period:o,bufferType:n,position:u,imminentDiscontinuity:null,isEmptyStream:!0,hasFinishedLoading:s,neededSegments:[]})}t.onUpdate(u,{emitCurrentValue:!1,clearSignal:a}),e.listen(u,{includeLastObservation:!1,clearSignal:a}),u()}var kt=function(e,t,n){var r=e.bufferType,i=e.content,a=e.garbageCollectors,o=e.playbackObserver,s=e.representationEstimator,u=e.segmentFetcherCreator,d=e.segmentBuffersStore,v=e.options,p=e.wantedBufferAhead,h=e.maxVideoBufferSize,m=i.period,g=(0,E.ZP)(void 0,n);if(t.periodStreamReady({type:r,period:m,adaptationRef:g}),!n.isCancelled()){var y,_=!0;g.onUpdate((function(e){(0,A.Z)(Z().mark((function i(){var s,u,f,h,g,S,T,E,A,I,x,R,M,C,P,D;return Z().wrap((function(i){for(;;)switch(i.prev=i.next){case 0:if(void 0!==e){i.next=2;break}return i.abrupt("return");case 2:if((u=new k.ZP).linkToSignal(n),null==y||y.cancel(),y=u,null!==e){i.next=34;break}if(l.Z.info("Stream: Set no "+r+" Adaptation. P:",m.start),"initialized"!==(f=d.getStatus(r)).type){i.next=26;break}if(l.Z.info("Stream: Clearing previous "+r+" SegmentBuffer"),!He.isNative(r)){i.next=15;break}return i.abrupt("return",w(0,u.signal));case 15:if(h=null!==(s=m.end)&&void 0!==s?s:1/0,!(m.start>h)){i.next=20;break}l.Z.warn("Stream: Can't free buffer: period's start is after its end"),i.next=24;break;case 20:return i.next=22,f.value.removeBuffer(m.start,h,u.signal);case 22:if(!u.isUsed()){i.next=24;break}return i.abrupt("return");case 24:i.next=30;break;case 26:if("uninitialized"!==f.type){i.next=30;break}if(d.disableSegmentBuffer(r),!u.isUsed()){i.next=30;break}return i.abrupt("return");case 30:if(t.adaptationChange({type:r,adaptation:null,period:m}),!u.isUsed()){i.next=33;break}return i.abrupt("return");case 33:return i.abrupt("return",Et(o,p,r,{period:m},t,u.signal));case 34:if(g=c.Z.getCurrent(),S=g.DELTA_POSITION_AFTER_RELOAD,T=_?0:"audio"===r?S.trackSwitch.audio:"video"===r?S.trackSwitch.video:S.trackSwitch.other,_=!1,!He.isNative(r)||"disabled"!==d.getStatus(r).type){i.next=39;break}return i.abrupt("return",w(T,u.signal));case 39:if(l.Z.info("Stream: Updating "+r+" adaptation","A: "+e.id,"P: "+m.start),t.adaptationChange({type:r,adaptation:e,period:m}),!u.isUsed()){i.next=43;break}return i.abrupt("return");case 43:if(E=o.getReadyState(),A=Tt(d,r,e,v),I={currentTime:o.getCurrentTime(),readyState:E},"needs-reload"!==(x=_t(A,m,e,I,v)).type){i.next=49;break}return i.abrupt("return",w(T,u.signal));case 49:return i.next=51,d.waitForUsableBuffers(u.signal);case 51:if(!u.isUsed()){i.next=53;break}return i.abrupt("return");case 53:if("flush-buffer"!==x.type&&"clean-buffer"!==x.type){i.next=67;break}R=bt(x.value);case 55:if((M=R()).done){i.next=63;break}return C=M.value,P=C.start,D=C.end,i.next=59,A.removeBuffer(P,D,u.signal);case 59:if(!u.isUsed()){i.next=61;break}return i.abrupt("return");case 61:i.next=55;break;case 63:if("flush-buffer"!==x.type){i.next=67;break}if(t.needsBufferFlush(),!u.isUsed()){i.next=67;break}return i.abrupt("return");case 67:a.get(A)(u.signal),b(e,A,u.signal);case 69:case"end":return i.stop()}}),i)})))().catch((function(e){e instanceof k.FU||(null==y||y.cancel(),t.error(e))}))}),{clearSignal:n,emitCurrentValue:!0})}function b(e,n,a){var c=i.manifest,g=function(e,t){return e.deriveReadOnlyObserver((function(e,n){var r=(0,E.ZP)(i(),n);return e.onUpdate(a,{clearSignal:n,emitCurrentValue:!1}),r;function i(){var n=e.getValue(),r=t.getBufferedRanges(),i=(0,T.L7)(r,n.position.last);return(0,S.Z)({},n,{bufferGap:i})}function a(){r.setValue(i())}}))}(o,n);mt({content:{manifest:c,period:m,adaptation:e},options:v,playbackObserver:g,representationEstimator:s,segmentBuffer:n,segmentFetcherCreator:u,wantedBufferAhead:p,maxVideoBufferSize:h},Object.assign(Object.assign({},t),{error:function(e){if(!He.isNative(r)){l.Z.error("Stream: "+r+" Stream crashed. Aborting it.",e instanceof Error?e:""),d.disposeSegmentBuffer(r);var n=(0,f.Z)(e,{defaultCode:"NONE",defaultReason:"Unknown `AdaptationStream` error"});if(t.warning(n),a.isCancelled())return;return Et(o,p,r,{period:m},t,a)}l.Z.error("Stream: "+r+" Stream crashed. Stopping playback.",e instanceof Error?e:""),t.error(e)}}),a)}function w(e,n){qe()((function(){o.listen((function(n){var i,a,s=o.getCurrentTime()+e,u=Math.min(Math.max(m.start,s),null!==(i=m.end)&&void 0!==i?i:1/0),l=!(null!==(a=n.paused.pending)&&void 0!==a?a:o.getIsPaused());t.waitingMediaSourceReload({bufferType:r,period:m,position:u,autoPlay:l})}),{includeLastObservation:!0,clearSignal:n})}))}};function wt(e,t){if(0===t.length)return[];e.synchronizeInventory();for(var n=[],r=e.getInventory(),i=function(i){var a=r[i];if(t.some((function(e){return a.infos.period.id===e.period.id&&a.infos.adaptation.id===e.adaptation.id&&a.infos.representation.id===e.representation.id}))){var o=a.bufferedStart,s=a.bufferedEnd;if(void 0===o||void 0===s){l.Z.warn("SO: No buffered start or end found from a segment.");var u=e.getBufferedRanges(),d=u.length;return 0===d?{v:[]}:{v:[{start:u.start(0),end:u.end(d-1)}]}}var c=n[n.length-1];void 0!==c&&c.end===o?c.end=s:n.push({start:o,end:s})}},a=0;a=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function It(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0?t[t.length-1].end>=n-5:t[0].start<=n+5}var xt=function(e,t,n,r,i,a,o,s){for(var u,d=e.manifest,f=e.initialPeriod,p=a.maxBufferAhead,h=a.maxBufferBehind,m=a.wantedBufferAhead,g=a.maxVideoBufferSize,y=c.Z.getCurrent(),_=y.MAXIMUM_MAX_BUFFER_AHEAD,b=y.MAXIMUM_MAX_BUFFER_BEHIND,S=new Xe((function(e){var n=e.bufferType,r=null!=b[n]?b[n]:1/0,i=null!=_[n]?_[n]:1/0;return function(n){Qe({segmentBuffer:e,playbackObserver:t,maxBufferBehind:(0,E.lR)(h,(function(e){return Math.min(e,r)}),n),maxBufferAhead:(0,E.lR)(p,(function(e){return Math.min(e,i)}),n)},n)}})),T=At(r.getBufferTypes());!(u=T()).done;){w(u.value,f)}function w(e,n){var i=new Ye((function(e,t){return e.start-t.start})),a=!1,u=new k.ZP;return u.linkToSignal(s),t.listen((function(t){var n,r,f=t.position,v=null!==(n=f.pending)&&void 0!==n?n:f.last;if(a&&function(e){var t=i.head(),n=i.last();if(null==t||null==n)return!0;return t.start>e||(null==n.end?1/0:n.end)0;){var p=i.get(i.length()-1);i.removeElement(p),o.periodStreamCleared({type:e,period:p})}u.cancel(),(u=new k.ZP).linkToSignal(s);var h=null!==(r=d.getPeriodForTime(v))&&void 0!==r?r:d.getNextPeriod(v);void 0!==h?c(h):l.Z.warn("Stream: The wanted position is not found in the Manifest.")}}),{clearSignal:s,includeLastObservation:!0}),d.addEventListener("decipherabilityUpdate",(function(e){(function(e){return f.apply(this,arguments)})(e).catch((function(e){u.cancel(),o.error(e)}))}),s),c(n);function c(t){var n=Object.assign(Object.assign({},o),{waitingMediaSourceReload:function(e){var t=i.head();if(void 0===t||t.id!==e.period.id)o.lockedStream({bufferType:e.bufferType,period:e.period});else{var n=e.position,r=e.autoPlay;o.needsMediaSourceReload({position:n,autoPlay:r})}},periodStreamReady:function(e){a=!0,i.add(e.period),o.periodStreamReady(e)},periodStreamCleared:function(e){i.removeElement(e.period),o.periodStreamCleared(e)},error:function(e){u.cancel(),o.error(e)}});I(e,t,n,u.signal)}function f(){return f=(0,A.Z)(Z().mark((function n(f){var p,h,m,g,y,_,b,S,T,E,w,A,I;return Z().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:if(p=r.getStatus(e),0!==(h=f.filter((function(t){return t.adaptation.type===e}))).length&&"initialized"===p.type&&!h.every((function(e){return!0===e.representation.decipherable}))){n.next=4;break}return n.abrupt("return");case 4:for(m=p.value,g=h.filter((function(e){return void 0===e.representation.decipherable})),y=h.filter((function(e){return!1===e.representation.decipherable})),_=wt(m,y),b=wt(m,g),a=!1,l.Z.info("Stream: Destroying all PeriodStreams for decipherability matters",e);i.length()>0;)S=i.get(i.length()-1),i.removeElement(S),o.periodStreamCleared({type:e,period:S});u.cancel(),(u=new k.ZP).linkToSignal(s),T=0,E=[].concat(_,b);case 16:if(!(T=o.end&&(l.Z.info("Stream: Destroying PeriodStream as the current playhead moved above it",e,o.start,null!==(i=a.pending)&&void 0!==i?i:a.last,o.end),n(),s.periodStreamCleared({type:e,period:o}),f.cancel())}),{clearSignal:u,includeLastObservation:!0});var v={bufferType:e,content:{manifest:d,period:o},garbageCollectors:S,maxVideoBufferSize:g,segmentFetcherCreator:i,segmentBuffersStore:r,options:a,playbackObserver:t,representationEstimator:n,wantedBufferAhead:m},p=Object.assign(Object.assign({},s),{streamStatusUpdate:function(t){if(t.hasFinishedLoading){var n=d.getPeriodAfter(o);null!==n&&function(t){null!==c&&(l.Z.warn("Stream: Creating next `PeriodStream` while it was already created."),s.periodStreamCleared({type:e,period:c.period}),c.canceller.cancel());var n=new k.ZP;n.linkToSignal(u),I(e,t,s,(c={canceller:n,period:t}).canceller.signal)}(n)}else null!==c&&(l.Z.info("Stream: Destroying next PeriodStream due to current one being active",e,c.period.start),s.periodStreamCleared({type:e,period:c.period}),c.canceller.cancel(),c=null);s.streamStatusUpdate(t)},error:function(e){null!==c&&(c.canceller.cancel(),c=null),f.cancel(),s.error(e)}});kt(v,p,f.signal)}},Rt=xt,Mt=n(379);function Ct(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return Pt(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Pt(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function Pt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);na.getMaximumAvailablePosition()){var u=new v.Z("MEDIA_TIME_AFTER_MANIFEST","The current position is after the latest time announced in the Manifest.");i.trigger("warning",u)}}),{includeLastObservation:!0,clearSignal:o}),t.addEventListener("manifestUpdate",(function(){i.trigger("durationUpdate",t.isDynamic?a.getMaximumAvailablePosition():a.getEndingPosition()),o.isCancelled()||i._checkEndOfStream()}),o),i}(0,t.Z)(n,e);var r=n.prototype;return r.onAdaptationChange=function(e,t,n){if(this._manifest.isLastPeriodKnown){var r=this._manifest.periods[this._manifest.periods.length-1];if(t.id===(null==r?void 0:r.id)&&("audio"===e||"video"===e)){"audio"===e?this._maximumPositionCalculator.updateLastAudioAdaptation(n):this._maximumPositionCalculator.updateLastVideoAdaptation(n);var i=this._manifest.isDynamic?this._maximumPositionCalculator.getMaximumAvailablePosition():this._maximumPositionCalculator.getEndingPosition();this.trigger("durationUpdate",i)}}this._canceller.isUsed()||null===n&&this._addActivelyLoadedPeriod(t,e)},r.onRepresentationChange=function(e,t){this._addActivelyLoadedPeriod(t,e)},r.onPeriodCleared=function(e,t){this._removeActivelyLoadedPeriod(t,e)},r.onLastSegmentFinishedLoading=function(e){var t=this._lazilyCreateActiveStreamInfo(e);t.hasFinishedLoadingLastPeriod||(t.hasFinishedLoadingLastPeriod=!0,this._checkEndOfStream())},r.onLastSegmentLoadingResume=function(e){var t=this._lazilyCreateActiveStreamInfo(e);t.hasFinishedLoadingLastPeriod&&(t.hasFinishedLoadingLastPeriod=!1,this._checkEndOfStream())},r.dispose=function(){this.removeEventListener(),this._canceller.cancel()},r._addActivelyLoadedPeriod=function(e,t){var n=this._lazilyCreateActiveStreamInfo(t);n.activePeriods.has(e)||(n.activePeriods.add(e),this._checkCurrentPeriod())},r._removeActivelyLoadedPeriod=function(e,t){var n=this._activeStreams.get(t);void 0!==n&&n.activePeriods.has(e)&&(n.activePeriods.removeElement(e),this._checkCurrentPeriod())},r._checkCurrentPeriod=function(){var e=this;if(0!==this._allBufferTypes.length){var t=this._activeStreams.get(this._allBufferTypes[0]);if(void 0!==t)for(var n,r=function(){for(var t=n.value,r=!0,i=1;i=0;a--){var o=i[a];try{"open"===r&&(l.Z.info("Init: Removing SourceBuffer from mediaSource"),o.abort()),t.removeSourceBuffer(o)}catch(e){l.Z.warn("Init: Error while disposing SourceBuffer",e instanceof Error?e:"")}}i.length>0&&l.Z.warn("Init: Not all SourceBuffers could have been removed.")}if((0,Ut.Z)(e),null!==n)try{l.Z.debug("Init: Revoking previous URL"),URL.revokeObjectURL(n)}catch(e){l.Z.warn("Init: Error while revoking the media source URL",e instanceof Error?e:"")}}function zt(e,t){return(0,N.Z)(t,(function(n){var r=function(e,t){if(null==Bt.J)throw new v.Z("MEDIA_SOURCE_NOT_SUPPORTED","No MediaSource Object was found in the current browser.");var n=(0,Ft.Z)(e.src)?e.src:null;Vt(e,null,n),l.Z.info("Init: Creating MediaSource");var r=new Bt.J,i=URL.createObjectURL(r);return l.Z.info("Init: Attaching MediaSource URL to the media element",i),e.src=i,t.register((function(){Vt(e,r,i)})),r}(e,t);i.u_(r,(function(){n(r)}),t)}))}function Kt(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return Gt(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Gt(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function Gt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0?r:Math.max(i,r+u)}if(!(0,b.Z)(n.percentage)){l.Z.debug("Init: using startAt.percentage");var d=n.percentage;return d>100?r:d<0?i:i+(r-i)*(+d/100)}}var f=e.getMinimumSafePosition();if(e.isLive){var v,p=e.suggestedPresentationDelay,h=e.clockOffset,m=e.getMaximumSafePosition(),g=c.Z.getCurrent().DEFAULT_LIVE_GAP;if(void 0===h)l.Z.info("Init: no clock offset found for a live content, starting close to maximum available position"),v=m;else{l.Z.info("Init: clock offset found for a live content, checking if we can start close to it");var y=void 0===e.availabilityStartTime?0:e.availabilityStartTime,_=(performance.now()+h)/1e3-y;v=Math.min(m,_)}var S=void 0!==p?p:t?g.LOW_LATENCY:g.DEFAULT;return l.Z.debug("Init: "+v+" defined as the live time, applying a live gap of "+S),Math.max(v-S,f)}return l.Z.info("Init: starting at the minimum available position:",f),f}var Xt=n(1757),Qt=n(8833),$t=n(8799),Jt=function(){function e(e,t){var n=new k.ZP,r=(0,E.ZP)(void 0,n.signal);this._canceller=n,this._currentKnownDuration=r;var a=function(e,t){var n=(0,E.ZP)("open"===e.readyState,t);return(0,i.u_)(e,(function(){n.setValueIfChanged(!0)}),t),(0,i.N8)(e,(function(){n.setValueIfChanged(!1)}),t),(0,i.k6)(e,(function(){n.setValueIfChanged(!1)}),t),n}(t,this._canceller.signal),o=new k.ZP;function s(n){var i=function(e,t){if(0===e.length){var n=(0,E.ZP)(!1);return n.finish(),n}var r=(0,E.ZP)(!1,t);o();for(var i=function(n){var r=e[n];r.addEventListener("updatestart",o),r.addEventListener("update",o),t.register((function(){r.removeEventListener("updatestart",o),r.removeEventListener("update",o)}))},a=0;a0&&(a=Math.max(s.buffered.end(u-1)))}if(i===e.duration)return"success";if(a>i){if(a=.1?c=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function on(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nl||void 0!==h&&l>=h)&&(sn(v)&&c.push(v.publicEvent),o.delete(v)):p<=l&&void 0!==h&&l=(null!=h?h:p)&&(u?d.push({type:"stream-event-skip",value:v.publicEvent}):(d.push({type:"stream-event",value:v.publicEvent}),sn(v)&&c.push(v.publicEvent)))}if(d.length>0)for(var m,g=an(d);!(m=g()).done;){var y=m.value;if("stream-event"===y.type?r(y.value):i(y.value),a.isCancelled())return}if(c.length>0)for(var _,b=an(c);!(_=b()).done;){var S=_.value;if("function"==typeof S.onExit&&S.onExit(),a.isCancelled())return}}(s.getValue(),d,e,l.signal),d=e}function h(){var e=n.getReference().getValue().seeking;return{currentTime:t.currentTime,isSeeking:e}}}),{emitCurrentValue:!0,clearSignal:a})},ln=n(4576);function dn(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return cn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return cn(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function cn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nd.end&&(n=d.end-1)}}else n=s;return{maximumPosition:e.getMaximumSafePosition(),position:{last:u.position,pending:n},duration:u.duration,paused:{last:u.paused,pending:i.getValue()||!r===u.paused?void 0:!r},readyState:u.readyState,speed:l}}function d(){u.setValue(l())}}))}(u,f,{autoPlay:a,initialPlayPerformed:k,initialSeekPerformed:w,speed:g,startTime:s}),I=this._createRebufferingController(f,u,g,n),Z=this._createContentTimeBoundariesObserver(u,d,A,b,n);T.then((function(){(0,Xt.Z)(f,l,!1,n).onUpdate((function(e,t){e&&(t(),i.trigger("loaded",{segmentBuffersStore:b}))}),{emitCurrentValue:!0,clearSignal:n})})).catch((function(e){n.isCancelled()||i._onFatalError(e)}));var x=this;Rt({manifest:u,initialPeriod:y},A,h,b,m,o,{needsBufferFlush:function(){return f.setCurrentTime(l.currentTime+.001)},streamStatusUpdate:function(e){var t=e.period,r=e.bufferType,i=e.imminentDiscontinuity,a=e.position;I.updateDiscontinuityInfo({period:t,bufferType:r,discontinuity:i,position:a}),n.isCancelled()||u.isLastPeriodKnown&&e.period.id===u.periods[u.periods.length-1].id&&(e.hasFinishedLoading||e.isEmptyStream?Z.onLastSegmentFinishedLoading(e.bufferType):Z.onLastSegmentLoadingResume(e.bufferType))},needsManifestRefresh:function(){return x._manifestFetcher.scheduleManualRefresh({enablePartialRefresh:!0,canUseUnsafeMode:!0})},manifestMightBeOufOfSync:function(){var e=c.Z.getCurrent().OUT_OF_SYNC_MANIFEST_REFRESH_DELAY;x._manifestFetcher.scheduleManualRefresh({enablePartialRefresh:!1,canUseUnsafeMode:!1,delay:e})},lockedStream:function(e){return I.onLockedStream(e.bufferType,e.period)},adaptationChange:function(e){x.trigger("adaptationChange",e),n.isCancelled()||Z.onAdaptationChange(e.type,e.period,e.adaptation)},representationChange:function(e){x.trigger("representationChange",e),n.isCancelled()||Z.onRepresentationChange(e.type,e.period)},inbandEvent:function(e){return x.trigger("inbandEvents",e)},warning:function(e){return x.trigger("warning",e)},periodStreamReady:function(e){return x.trigger("periodStreamReady",e)},periodStreamCleared:function(e){Z.onPeriodCleared(e.type,e.period),n.isCancelled()||x.trigger("periodStreamCleared",e)},bitrateEstimationChange:function(e){return x.trigger("bitrateEstimationChange",e)},addedSegment:function(e){return x.trigger("addedSegment",e)},needsMediaSourceReload:function(e){return t(e)},needsDecipherabilityFlush:function(e){var n,r=P(l);void 0===(n=null==r?void 0:r[0])||n.indexOf("widevine")<0?t(e):e.position+.001=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function hn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(i="internal-seeking",r=t._internalSeeksIncoming.shift());var a=null!=e?e:t._generateInitialObservation(),o=_n(t._mediaElement,i,t._withMediaSource),s=null;o.seeking&&("number"==typeof r?s=r:null!==a.pendingInternalSeek&&"seeking"!==n&&(s=a.pendingInternalSeek));var u=function(e,t,n){var r,i,a=n.withMediaSource,o=n.lowLatencyMode,s=c.Z.getCurrent().REBUFFERING_GAP,u=t.event,l=t.position,d=t.bufferGap,f=t.currentRange,v=t.duration,p=t.paused,h=t.readyState,m=t.ended,g=e.rebuffering,y=e.event,_=e.position,b=function(e,t,n,r,i){var a=c.Z.getCurrent().REBUFFERING_GAP,o=i?"LOW_LATENCY":"DEFAULT";if(void 0===t)return n&&Math.abs(r-e)<=a[o];return null!==t&&r-t.end<=a[o]}(l,f,m,v,o),S=h>=1&&"loadedmetadata"!==u&&null===g&&!(b||m),T=null,E=o?s.LOW_LATENCY:s.DEFAULT;if(a){if(S)d===1/0?(r=!0,T=l):void 0===d?h<3&&(r=!0,T=void 0):d<=E&&(r=!0,T=l+d);else if(null!==g){var k=yn(g,o);!0!==r&&null!==g&&h>1&&(b||m||void 0!==d&&isFinite(d)&&d>k)||void 0===d&&h>=3?i=!0:void 0===d?T=void 0:d===1/0?T=l:d<=k&&(T=l+d)}}else S&&(!p&&"timeupdate"===u&&"timeupdate"===y&&l===_||"seeking"===u&&(d===1/0||void 0===d&&h<3))?r=!0:null!==g&&("seeking"!==u&&l!==_||"canplay"===u||void 0===d&&h>=3||void 0!==d&&d<1/0&&(d>yn(g,o)||b||m))&&(i=!0);if(!0===i)return null;var w;if(!0===r||null!==g)return w="seeking"===u||null!==g&&"seeking"===g.reason||t.seeking?"seeking":1===h?"not-ready":"buffering",null!==g&&g.reason===w?{reason:g.reason,timestamp:g.timestamp,position:T}:{reason:w,timestamp:performance.now(),position:T};return null}(a,o,{lowLatencyMode:t._lowLatencyMode,withMediaSource:t._withMediaSource}),d=function(e,t){var n=c.Z.getCurrent().MINIMUM_BUFFER_AMOUNT_BEFORE_FREEZING;if(e.freezing)return t.ended||t.paused||0===t.readyState||0===t.playbackRate||e.position!==t.position?null:e.freezing;return"timeupdate"===t.event&&void 0!==t.bufferGap&&t.bufferGap>n&&!t.ended&&!t.paused&&t.readyState>=1&&0!==t.playbackRate&&t.position===e.position?{timestamp:performance.now()}:null}(a,o),f=(0,S.Z)({},{rebuffering:u,freezing:d,pendingInternalSeek:s},o);return l.Z.hasLevel("DEBUG")&&l.Z.debug("API: current media element state tick","event",f.event,"position",f.position,"seeking",f.seeking,"internalSeek",f.pendingInternalSeek,"rebuffering",null!==f.rebuffering,"freezing",null!==f.freezing,"ended",f.ended,"paused",f.paused,"playbackRate",f.playbackRate,"readyState",f.readyState),f},s=(0,E.ZP)(o("init"),this._canceller.signal),u=function(t){var n=o(t);l.Z.hasLevel("DEBUG")&&l.Z.debug("API: current playback timeline:\n"+function(e,t){for(var n="",r="",i=0;it){var d=n.length-Math.floor(l.length/2);r=" ".repeat(d)+"^"+t}if(i=3?(r=void 0,i=void 0):i=null!==(r=(0,T.rx)(a,o))?r.end-o:1/0,{bufferGap:i,buffered:a,currentRange:r,position:o,duration:s,ended:u,paused:l,playbackRate:d,readyState:c,seeking:f,event:t}}function bn(e,t,n){var r=t(e.getReference(),n);return{getCurrentTime:function(){return e.getCurrentTime()},getReadyState:function(){return e.getReadyState()},getPlaybackRate:function(){return e.getPlaybackRate()},getIsPaused:function(){return e.getIsPaused()},getReference:function(){return r},listen:function(e,t){var i;n.isCancelled()||!0===(null===(i=null==t?void 0:t.clearSignal)||void 0===i?void 0:i.isCancelled())||r.onUpdate(e,{clearSignal:null==t?void 0:t.clearSignal,emitCurrentValue:null==t?void 0:t.includeLastObservation})},deriveReadOnlyObserver:function(e){return bn(this,e,n)}}}var Sn=n(7829);function Tn(e){return e.map((function(e){return null===e?e:{normalized:void 0===e.language?void 0:(0,Sn.ZP)(e.language),audioDescription:e.audioDescription,codec:e.codec}}))}function En(e){return e.map((function(e){return null===e?e:{normalized:(0,Sn.ZP)(e.language),forced:e.forced,closedCaption:e.closedCaption}}))}var kn=function(){function e(e){this._periods=new Ye((function(e,t){return e.period.start-t.period.start})),this._audioChoiceMemory=new WeakMap,this._textChoiceMemory=new WeakMap,this._videoChoiceMemory=new WeakMap,this._preferredAudioTracks=[],this._preferredTextTracks=[],this._preferredVideoTracks=[],this.trickModeTrackEnabled=e.preferTrickModeTracks}var t=e.prototype;return t.setPreferredAudioTracks=function(e,t){this._preferredAudioTracks=e,t&&this._applyAudioPreferences()},t.setPreferredTextTracks=function(e,t){this._preferredTextTracks=e,t&&this._applyTextPreferences()},t.setPreferredVideoTracks=function(e,t){this._preferredVideoTracks=e,t&&this._applyVideoPreferences()},t.addPeriod=function(e,t,n){var r=Mn(this._periods,t),i=t.getSupportedAdaptations(e);if(void 0!==r){if(void 0!==r[e])return void l.Z.warn("TrackChoiceManager: "+e+" already added for period",t.start);r[e]={adaptations:i,adaptationRef:n}}else{var a;this._periods.add(((a={period:t})[e]={adaptations:i,adaptationRef:n},a))}},t.removePeriod=function(e,t){var n=function(e,t){for(var n=0;n0;)this._periods.pop()},t.update=function(){this._resetChosenAudioTracks(),this._resetChosenTextTracks(),this._resetChosenVideoTracks()},t.setInitialAudioTrack=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.audio:null;if((0,b.Z)(n)||void 0===t)throw new Error("TrackChoiceManager: Given Period not found.");var r=e.getSupportedAdaptations("audio"),i=this._audioChoiceMemory.get(e);if(null===i)n.adaptationRef.setValue(null);else if(void 0!==i&&(0,Me.Z)(r,i))n.adaptationRef.setValue(i);else{var a=An(r,Tn(this._preferredAudioTracks));this._audioChoiceMemory.set(e,a),n.adaptationRef.setValue(a)}},t.setInitialTextTrack=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.text:null;if((0,b.Z)(n)||void 0===t)throw new Error("TrackChoiceManager: Given Period not found.");var r=e.getSupportedAdaptations("text"),i=this._textChoiceMemory.get(e);if(null===i)n.adaptationRef.setValue(null);else if(void 0!==i&&(0,Me.Z)(r,i))n.adaptationRef.setValue(i);else{var a=Zn(r,En(this._preferredTextTracks),this._audioChoiceMemory.get(e));this._textChoiceMemory.set(e,a),n.adaptationRef.setValue(a)}},t.setInitialVideoTrack=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.video:null;if((0,b.Z)(n)||void 0===t)throw new Error("TrackChoiceManager: Given Period not found.");var r,i=e.getSupportedAdaptations("video"),a=this._videoChoiceMemory.get(e);if(null===a)r=null;else if(void 0!==a&&(0,Me.Z)(i,a.baseAdaptation))r=a.baseAdaptation;else{r=Rn(i,this._preferredVideoTracks)}if(null===r)return this._videoChoiceMemory.set(e,null),void n.adaptationRef.setValue(null);var o=Dn(r,this.trickModeTrackEnabled);this._videoChoiceMemory.set(e,{baseAdaptation:r,adaptation:o}),n.adaptationRef.setValue(o)},t.setAudioTrackByID=function(e,t){var n=Mn(this._periods,e),r=void 0!==n?n.audio:null;if((0,b.Z)(r))throw new Error("TrackChoiceManager: Given Period not found.");var i=(0,V.Z)(r.adaptations,(function(e){return e.id===t}));if(void 0===i)throw new Error("Audio Track not found.");this._audioChoiceMemory.get(e)!==i&&(this._audioChoiceMemory.set(e,i),r.adaptationRef.setValue(i))},t.setTextTrackByID=function(e,t){var n=Mn(this._periods,e),r=void 0!==n?n.text:null;if((0,b.Z)(r))throw new Error("TrackChoiceManager: Given Period not found.");var i=(0,V.Z)(r.adaptations,(function(e){return e.id===t}));if(void 0===i)throw new Error("Text Track not found.");this._textChoiceMemory.get(e)!==i&&(this._textChoiceMemory.set(e,i),r.adaptationRef.setValue(i))},t.setVideoTrackByID=function(e,t){var n=Mn(this._periods,e),r=void 0!==n?n.video:null;if((0,b.Z)(r))throw new Error("LanguageManager: Given Period not found.");var i=(0,V.Z)(r.adaptations,(function(e){return e.id===t}));if(void 0===i)throw new Error("Video Track not found.");var a=Dn(i,this.trickModeTrackEnabled);this._videoChoiceMemory.set(e,{baseAdaptation:i,adaptation:a}),r.adaptationRef.setValue(a)},t.disableTextTrack=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.text:null;if((0,b.Z)(n))throw new Error("TrackChoiceManager: Given Period not found.");null!==this._textChoiceMemory.get(e)&&(this._textChoiceMemory.set(e,null),n.adaptationRef.setValue(null))},t.disableVideoTrack=function(e){var t=Mn(this._periods,e),n=null==t?void 0:t.video;if(void 0===n)throw new Error("TrackManager: Given Period not found.");null!==this._videoChoiceMemory.get(e)&&(this._videoChoiceMemory.set(e,null),n.adaptationRef.setValue(null))},t.disableVideoTrickModeTracks=function(){this.trickModeTrackEnabled=!1,this._resetChosenVideoTracks()},t.enableVideoTrickModeTracks=function(){this.trickModeTrackEnabled=!0,this._resetChosenVideoTracks()},t.isTrickModeEnabled=function(){return this.trickModeTrackEnabled},t.getChosenAudioTrack=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.audio:null;if((0,b.Z)(n))return null;var r=this._audioChoiceMemory.get(e);if((0,b.Z)(r))return null;var i={language:(0,L.Z)(r.language,""),normalized:(0,L.Z)(r.normalizedLanguage,""),audioDescription:!0===r.isAudioDescription,id:r.id,representations:r.representations.map(Pn),label:r.label};return!0===r.isDub&&(i.dub=!0),i},t.getChosenTextTrack=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.text:null;if((0,b.Z)(n))return null;var r=this._textChoiceMemory.get(e);if((0,b.Z)(r))return null;var i={language:(0,L.Z)(r.language,""),normalized:(0,L.Z)(r.normalizedLanguage,""),closedCaption:!0===r.isClosedCaption,id:r.id,label:r.label};return void 0!==r.isForcedSubtitles&&(i.forced=r.isForcedSubtitles),i},t.getChosenVideoTrack=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.video:null;if((0,b.Z)(n))return null;var r=this._videoChoiceMemory.get(e);if((0,b.Z)(r))return null;var i=r.adaptation,a=void 0!==i.trickModeTracks?i.trickModeTracks.map((function(e){var t=e.representations.map(Cn),n={id:e.id,representations:t,isTrickModeTrack:!0};return!0===e.isSignInterpreted&&(n.signInterpreted=!0),n})):void 0,o={id:i.id,representations:i.representations.map(Cn),label:i.label};return!0===i.isSignInterpreted&&(o.signInterpreted=!0),!0===i.isTrickModeTrack&&(o.isTrickModeTrack=!0),void 0!==a&&(o.trickModeTracks=a),o},t.getAvailableAudioTracks=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.audio:null;if((0,b.Z)(n))return[];var r=this._audioChoiceMemory.get(e),i=(0,b.Z)(r)?null:r.id;return n.adaptations.map((function(e){var t={language:(0,L.Z)(e.language,""),normalized:(0,L.Z)(e.normalizedLanguage,""),audioDescription:!0===e.isAudioDescription,id:e.id,active:null!==i&&i===e.id,representations:e.representations.map(Pn),label:e.label};return!0===e.isDub&&(t.dub=!0),t}))},t.getAvailableTextTracks=function(e){var t=Mn(this._periods,e),n=void 0!==t?t.text:null;if((0,b.Z)(n))return[];var r=this._textChoiceMemory.get(e),i=(0,b.Z)(r)?null:r.id;return n.adaptations.map((function(e){var t={language:(0,L.Z)(e.language,""),normalized:(0,L.Z)(e.normalizedLanguage,""),closedCaption:!0===e.isClosedCaption,id:e.id,active:null!==i&&i===e.id,label:e.label};return void 0!==e.isForcedSubtitles&&(t.forced=e.isForcedSubtitles),t}))},t.getAvailableVideoTracks=function(e){var t,n=Mn(this._periods,e),r=void 0!==n?n.video:null;if((0,b.Z)(r))return[];var i=this._videoChoiceMemory.get(e),a=void 0===i?void 0:null!==(t=null==i?void 0:i.adaptation.id)&&void 0!==t?t:void 0;return r.adaptations.map((function(e){var t=void 0!==e.trickModeTracks?e.trickModeTracks.map((function(e){var t=null!==a&&a===e.id,n=e.representations.map(Cn),r={id:e.id,representations:n,isTrickModeTrack:!0,active:t};return!0===e.isSignInterpreted&&(r.signInterpreted=!0),r})):void 0,n={id:e.id,active:null!==a&&a===e.id,representations:e.representations.map(Cn),label:e.label};return!0===e.isSignInterpreted&&(n.signInterpreted=!0),void 0!==t&&(n.trickModeTracks=t),n}))},t._applyAudioPreferences=function(){this._audioChoiceMemory=new WeakMap,this._resetChosenAudioTracks()},t._applyTextPreferences=function(){this._textChoiceMemory=new WeakMap,this._resetChosenTextTracks()},t._applyVideoPreferences=function(){this._videoChoiceMemory=new WeakMap,this._resetChosenVideoTracks()},t._resetChosenAudioTracks=function(){var e=this,t=Tn(this._preferredAudioTracks);!function n(r){if(!(r>=e._periods.length())){var i=e._periods.get(r);if((0,b.Z)(i.audio))n(r+1);else{var a=i.period,o=i.audio,s=a.getSupportedAdaptations("audio"),u=e._audioChoiceMemory.get(a);if(null===u||void 0!==u&&(0,Me.Z)(s,u))n(r+1);else{var l=An(s,t);e._audioChoiceMemory.set(a,l),o.adaptationRef.setValue(l),n(0)}}}}(0)},t._resetChosenTextTracks=function(){var e=this,t=En(this._preferredTextTracks);!function n(r){if(!(r>=e._periods.length())){var i=e._periods.get(r);if((0,b.Z)(i.text))n(r+1);else{var a=i.period,o=i.text,s=a.getSupportedAdaptations("text"),u=e._textChoiceMemory.get(a);if(null===u||void 0!==u&&(0,Me.Z)(s,u))n(r+1);else{var l=Zn(s,t,e._audioChoiceMemory.get(a));e._textChoiceMemory.set(a,l),o.adaptationRef.setValue(l),n(0)}}}}(0)},t._resetChosenVideoTracks=function(){var e=this,t=this._preferredVideoTracks;!function n(r){if(!(r>=e._periods.length())){var i=e._periods.get(r);if((0,b.Z)(i.video))n(r+1);else{var a=i.period,o=i.video,s=a.getSupportedAdaptations("video"),u=e._videoChoiceMemory.get(a);if(null!==u){if(void 0!==u&&(0,Me.Z)(s,u.baseAdaptation)){var l=Dn(u.baseAdaptation,e.trickModeTrackEnabled);return l.id===u.adaptation.id?void n(r+1):(e._videoChoiceMemory.set(a,{baseAdaptation:u.baseAdaptation,adaptation:l}),o.adaptationRef.setValue(l),n(0))}var d=Rn(s,t);if(null===d)return e._videoChoiceMemory.set(a,null),o.adaptationRef.setValue(null),n(0);var c=Dn(d,e.trickModeTrackEnabled);return e._videoChoiceMemory.set(a,{baseAdaptation:d,adaptation:c}),o.adaptationRef.setValue(c),n(0)}n(r+1)}}}(0)},e}();function wn(e){return function(t){var n;if(void 0!==e.normalized&&(null!==(n=t.normalizedLanguage)&&void 0!==n?n:"")!==e.normalized)return!1;if(void 0!==e.audioDescription)if(e.audioDescription){if(!0!==t.isAudioDescription)return!1}else if(!0===t.isAudioDescription)return!1;if(void 0===e.codec)return!0;var r=e.codec.test,i=function(e){return void 0!==e.codec&&r.test(e.codec)};return e.codec.all?t.representations.every(i):t.representations.some(i)}}function An(e,t){if(0===e.length)return null;for(var n=0;n0){if(null!=n){var l=(0,V.Z)(u,(function(e){return e.normalizedLanguage===n.normalizedLanguage}));if(void 0!==l)return l}return null!==(r=(0,V.Z)(u,(function(e){return void 0===e.normalizedLanguage})))&&void 0!==r?r:null}return null}function xn(e){return function(t){if(void 0!==e.signInterpreted&&e.signInterpreted!==t.isSignInterpreted)return!1;if(void 0===e.codec)return!0;var n=e.codec.test,r=function(e){return void 0!==e.codec&&n.test(e.codec)};return e.codec.all?t.representations.every(r):t.representations.some(r)}}function Rn(e,t){if(0===e.length)return null;for(var n=0;n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function Un(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ng)throw new Error('Invalid maxVideoBitrate parameter. Its value, "'+g+'", is inferior to the set minVideoBitrate, "'+h+'"')}if((0,b.Z)(e.maxAudioBitrate))m=E.audio;else{if(m=Number(e.maxAudioBitrate),isNaN(m))throw new Error("Invalid maxAudioBitrate parameter. Should be a number.");if(p>m)throw new Error('Invalid maxAudioBitrate parameter. Its value, "'+m+'", is inferior to the set minAudioBitrate, "'+p+'"')}return{maxBufferAhead:t,maxBufferBehind:n,limitVideoWidth:C,videoElement:d,wantedBufferAhead:r,maxVideoBufferSize:i,throttleWhenHidden:a,throttleVideoBitrateWhenHidden:o,preferredAudioTracks:s,preferredTextTracks:u,preferredVideoTracks:l,initialAudioBitrate:v,initialVideoBitrate:f,minAudioBitrate:p,minVideoBitrate:h,maxAudioBitrate:m,maxVideoBitrate:g,stopAtEnd:(0,b.Z)(e.stopAtEnd)?Z:!!e.stopAtEnd}}(e),o=a.initialAudioBitrate,s=a.initialVideoBitrate,u=a.limitVideoWidth,d=a.minAudioBitrate,f=a.minVideoBitrate,v=a.maxAudioBitrate,p=a.maxBufferAhead,h=a.maxBufferBehind,m=a.maxVideoBitrate,g=a.preferredAudioTracks,y=a.preferredTextTracks,_=a.preferredVideoTracks,S=a.throttleWhenHidden,T=a.throttleVideoBitrateWhenHidden,A=a.videoElement,I=a.wantedBufferAhead,Z=a.maxVideoBufferSize,x=a.stopAtEnd,R=c.Z.getCurrent().DEFAULT_UNMUTED_VOLUME;A.preload="auto",t.version="3.30.0",t.log=l.Z,t.state="STOPPED",t.videoElement=A;var M=new k.ZP;t._destroyCanceller=M,t._priv_pictureInPictureRef=Vn(A,M.signal),Gn(A,(function(){t.trigger("fullscreenChange",t.isFullscreen())}),M.signal);for(var C=[],P=0;P<(null===(r=A.textTracks)||void 0===r?void 0:r.length);P++){var D=null===(i=A.textTracks)||void 0===i?void 0:i[P];(0,b.Z)(D)||C.push(D)}var N=function(e){for(var n=e.target,r=[],i=0;i0?e.textTracks[0]:null},i.getPlayerState=function(){return this.state},i.isLive=function(){if(null===this._priv_contentInfos)return!1;var e=this._priv_contentInfos,t=e.isDirectFile,n=e.manifest;return!t&&null!==n&&n.isLive},i.areTrickModeTracksEnabled=function(){return this._priv_preferTrickModeTracks},i.getUrl=function(){if(null!==this._priv_contentInfos){var e=this._priv_contentInfos,t=e.isDirectFile,n=e.manifest,r=e.originalUrl;return t?r:null!==n?n.getUrl():void 0}},i.updateContentUrls=function(e,t){if(null===this._priv_contentInfos)throw new Error("No content loaded");var n=!0===(null==t?void 0:t.refresh);this._priv_contentInfos.initializer.updateContentUrls(e,n)},i.getVideoDuration=function(){if(null===this.videoElement)throw new Error("Disposed player");return this.videoElement.duration},i.getVideoBufferGap=function(){if(null===this.videoElement)throw new Error("Disposed player");var e=this.videoElement;return(0,T.L7)(e.buffered,e.currentTime)},i.getVideoLoadedTime=function(){if((0,w.Z)("`getVideoLoadedTime` is deprecated and won't be present in the next major version"),null===this.videoElement)throw new Error("Disposed player");var e=this.videoElement;return(0,T.at)(e.buffered,e.currentTime)},i.getVideoPlayedTime=function(){if((0,w.Z)("`getVideoPlayedTime` is deprecated and won't be present in the next major version"),null===this.videoElement)throw new Error("Disposed player");var e=this.videoElement;return(0,T.DD)(e.buffered,e.currentTime)},i.getWallClockTime=function(){if(null===this.videoElement)throw new Error("Disposed player");if(null===this._priv_contentInfos)return this.videoElement.currentTime;var e=this._priv_contentInfos,t=e.isDirectFile,n=e.manifest;if(t){var r=a(this.videoElement);return(null!=r?r:0)+this.videoElement.currentTime}return null!==n?this.videoElement.currentTime+(void 0!==n.availabilityStartTime?n.availabilityStartTime:0):0},i.getPosition=function(){if(null===this.videoElement)throw new Error("Disposed player");return this.videoElement.currentTime},i.getPlaybackRate=function(){return this._priv_speed.getValue()},i.setPlaybackRate=function(e,t){var n;e!==this._priv_speed.getValue()&&this._priv_speed.setValue(e);var r=null==t?void 0:t.preferTrickModeTracks;if("boolean"==typeof r){this._priv_preferTrickModeTracks=r;var i=null===(n=this._priv_contentInfos)||void 0===n?void 0:n.trackChoiceManager;(0,b.Z)(i)||(r&&!i.isTrickModeEnabled()?i.enableVideoTrickModeTracks():!r&&i.isTrickModeEnabled()&&i.disableVideoTrickModeTracks())}},i.getAvailableVideoBitrates=function(){if(null===this._priv_contentInfos)return[];var e=this._priv_contentInfos,t=e.currentPeriod,n=e.activeAdaptations;if(null===t||null===n)return[];var r=n[t.id];return void 0===r||(0,b.Z)(r.video)?[]:r.video.getAvailableBitrates()},i.getAvailableAudioBitrates=function(){if(null===this._priv_contentInfos)return[];var e=this._priv_contentInfos,t=e.currentPeriod,n=e.activeAdaptations;if(null===t||null===n)return[];var r=n[t.id];return void 0===r||(0,b.Z)(r.audio)?[]:r.audio.getAvailableBitrates()},i.getManualAudioBitrate=function(){return this._priv_bitrateInfos.manualBitrates.audio.getValue()},i.getManualVideoBitrate=function(){return this._priv_bitrateInfos.manualBitrates.video.getValue()},i.getVideoBitrate=function(){var e=this._priv_getCurrentRepresentations();if(null!==e&&!(0,b.Z)(e.video))return e.video.bitrate},i.getAudioBitrate=function(){var e=this._priv_getCurrentRepresentations();if(null!==e&&!(0,b.Z)(e.audio))return e.audio.bitrate},i.getMinVideoBitrate=function(){return this._priv_bitrateInfos.minAutoBitrates.video.getValue()},i.getMinAudioBitrate=function(){return this._priv_bitrateInfos.minAutoBitrates.audio.getValue()},i.getMaxVideoBitrate=function(){return this._priv_bitrateInfos.maxAutoBitrates.video.getValue()},i.getMaxAudioBitrate=function(){return this._priv_bitrateInfos.maxAutoBitrates.audio.getValue()},i.play=function(){var e=this;if(null===this.videoElement)throw new Error("Disposed player");var t=this.videoElement.play();return(0,b.Z)(t)||"function"!=typeof t.catch?Promise.resolve():t.catch((function(t){if("NotAllowedError"===t.name){var n=new v.Z("MEDIA_ERR_PLAY_NOT_ALLOWED",t.toString());e.trigger("warning",n)}throw t}))},i.pause=function(){if(null===this.videoElement)throw new Error("Disposed player");this.videoElement.pause()},i.seekTo=function(e){var t;if(null===this.videoElement)throw new Error("Disposed player");if(null===this._priv_contentInfos)throw new Error("player: no content loaded");var n,r=this._priv_contentInfos,i=r.isDirectFile,o=r.manifest;if(!i&&null===o)throw new Error("player: the content did not load yet");if("number"==typeof e)n=e;else if("object"==typeof e){var s=e,u=this.videoElement.currentTime;if((0,b.Z)(s.relative))if((0,b.Z)(s.position)){if((0,b.Z)(s.wallClockTime))throw new Error('invalid time object. You must set one of the following properties: "relative", "position" or "wallClockTime"');if(null!==o)n=s.wallClockTime-(null!==(t=o.availabilityStartTime)&&void 0!==t?t:0);else if(i&&null!==this.videoElement){var l=a(this.videoElement);void 0!==l&&(n=s.wallClockTime-l)}void 0===n&&(n=s.wallClockTime)}else n=s.position;else n=u+s.relative}if(void 0===n)throw new Error("invalid time given");return this.videoElement.currentTime=n,n},i.isFullscreen=function(){return(0,w.Z)("isFullscreen is deprecated. Fullscreen management should now be managed by the application"),s()},i.setFullscreen=function(e){if(void 0===e&&(e=!0),(0,w.Z)("setFullscreen is deprecated. Fullscreen management should now be managed by the application"),null===this.videoElement)throw new Error("Disposed player");e?function(e){if(!s()){var t=e;"function"==typeof t.requestFullscreen?t.requestFullscreen():"function"==typeof t.msRequestFullscreen?t.msRequestFullscreen():"function"==typeof t.mozRequestFullScreen?t.mozRequestFullScreen():"function"==typeof t.webkitRequestFullscreen&&t.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}}(this.videoElement):o()},i.exitFullscreen=function(){(0,w.Z)("exitFullscreen is deprecated. Fullscreen management should now be managed by the application"),o()},i.getVolume=function(){if(null===this.videoElement)throw new Error("Disposed player");return this.videoElement.volume},i.setVolume=function(e){if(null===this.videoElement)throw new Error("Disposed player");var t=this.videoElement;e!==t.volume&&(t.volume=e,this.trigger("volumeChange",e))},i.isMute=function(){return 0===this.getVolume()},i.mute=function(){this._priv_mutedMemory=this.getVolume(),this.setVolume(0)},i.unMute=function(){var e=c.Z.getCurrent().DEFAULT_UNMUTED_VOLUME;0===this.getVolume()&&this.setVolume(0===this._priv_mutedMemory?e:this._priv_mutedMemory)},i.setVideoBitrate=function(e){this._priv_bitrateInfos.manualBitrates.video.setValue(e)},i.setAudioBitrate=function(e){this._priv_bitrateInfos.manualBitrates.audio.setValue(e)},i.setMinVideoBitrate=function(e){var t=this._priv_bitrateInfos.maxAutoBitrates.video.getValue();if(e>t)throw new Error('Invalid minimum video bitrate given. Its value, "'+e+'" is superior the current maximum video birate, "'+t+'".');this._priv_bitrateInfos.minAutoBitrates.video.setValue(e)},i.setMinAudioBitrate=function(e){var t=this._priv_bitrateInfos.maxAutoBitrates.audio.getValue();if(e>t)throw new Error('Invalid minimum audio bitrate given. Its value, "'+e+'" is superior the current maximum audio birate, "'+t+'".');this._priv_bitrateInfos.minAutoBitrates.audio.setValue(e)},i.setMaxVideoBitrate=function(e){var t=this._priv_bitrateInfos.minAutoBitrates.video.getValue();if(e0||c.result.removedAdaptations.length>0)){var f=u.getAvailableAudioTracks(s);r._priv_triggerEventIfNotStopped("availableAudioTracksChange",null!=f?f:[],i);var v=u.getAvailableTextTracks(s);r._priv_triggerEventIfNotStopped("availableTextTracksChange",null!=v?v:[],i);var p=u.getAvailableVideoTracks(s);r._priv_triggerEventIfNotStopped("availableVideoTracksChange",null!=p?p:[],i)}return}}),e.currentContentCanceller.signal)}},i._priv_onActivePeriodChanged=function(e,t){var n,r,i,a,o,s,u,l,d=t.period;if(e.contentId===(null===(n=this._priv_contentInfos)||void 0===n?void 0:n.contentId)){e.currentPeriod=d;var c=e.currentContentCanceller.signal;this._priv_contentEventsMemory.periodChange!==d&&(this._priv_contentEventsMemory.periodChange=d,this._priv_triggerEventIfNotStopped("periodChange",d,c)),this._priv_triggerEventIfNotStopped("availableAudioTracksChange",this.getAvailableAudioTracks(),c),this._priv_triggerEventIfNotStopped("availableTextTracksChange",this.getAvailableTextTracks(),c),this._priv_triggerEventIfNotStopped("availableVideoTracksChange",this.getAvailableVideoTracks(),c);var f=null===(r=this._priv_contentInfos)||void 0===r?void 0:r.trackChoiceManager;if((0,b.Z)(f))this._priv_triggerEventIfNotStopped("audioTrackChange",null,c),this._priv_triggerEventIfNotStopped("textTrackChange",null,c),this._priv_triggerEventIfNotStopped("videoTrackChange",null,c);else{var v=f.getChosenAudioTrack(d);this._priv_triggerEventIfNotStopped("audioTrackChange",v,c);var p=f.getChosenTextTrack(d);this._priv_triggerEventIfNotStopped("textTrackChange",p,c);var h=f.getChosenVideoTrack(d);this._priv_triggerEventIfNotStopped("videoTrackChange",h,c)}if(this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange",this.getAvailableAudioBitrates(),c),!e.currentContentCanceller.isUsed()&&(this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange",this.getAvailableVideoBitrates(),c),!e.currentContentCanceller.isUsed())){var m=null!==(o=null===(a=null===(i=this._priv_getCurrentRepresentations())||void 0===i?void 0:i.audio)||void 0===a?void 0:a.bitrate)&&void 0!==o?o:-1;if(this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange",m,c),!e.currentContentCanceller.isUsed()){var g=null!==(l=null===(u=null===(s=this._priv_getCurrentRepresentations())||void 0===s?void 0:s.video)||void 0===u?void 0:u.bitrate)&&void 0!==l?l:-1;this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange",g,c)}}}},i._priv_onPeriodStreamReady=function(e,t){var n;if(e.contentId===(null===(n=this._priv_contentInfos)||void 0===n?void 0:n.contentId)){var r=t.type,i=t.period,a=t.adaptationRef,o=e.trackChoiceManager;switch(r){case"video":(0,b.Z)(o)?(l.Z.error("API: TrackChoiceManager not instanciated for a new video period"),a.setValue(null)):(o.addPeriod(r,i,a),o.setInitialVideoTrack(i));break;case"audio":(0,b.Z)(o)?(l.Z.error("API: TrackChoiceManager not instanciated for a new "+r+" period"),a.setValue(null)):(o.addPeriod(r,i,a),o.setInitialAudioTrack(i));break;case"text":(0,b.Z)(o)?(l.Z.error("API: TrackChoiceManager not instanciated for a new "+r+" period"),a.setValue(null)):(o.addPeriod(r,i,a),o.setInitialTextTrack(i));break;default:var s=i.adaptations[r];!(0,b.Z)(s)&&s.length>0?a.setValue(s[0]):a.setValue(null)}}},i._priv_onPeriodStreamCleared=function(e,t){var n;if(e.contentId===(null===(n=this._priv_contentInfos)||void 0===n?void 0:n.contentId)){var r=t.type,i=t.period,a=e.trackChoiceManager;switch(r){case"audio":case"text":case"video":(0,b.Z)(a)||a.removePeriod(r,i)}var o=e.activeAdaptations,s=e.activeRepresentations;if(!(0,b.Z)(o)&&!(0,b.Z)(o[i.id])){var u=o[i.id];delete u[r],0===Object.keys(u).length&&delete o[i.id]}if(!(0,b.Z)(s)&&!(0,b.Z)(s[i.id])){var l=s[i.id];delete l[r],0===Object.keys(l).length&&delete s[i.id]}}},i._priv_onAdaptationChange=function(e,t){var n,r=t.type,i=t.adaptation,a=t.period;if(e.contentId===(null===(n=this._priv_contentInfos)||void 0===n?void 0:n.contentId)){null===e.activeAdaptations&&(e.activeAdaptations={});var o,s=e.activeAdaptations,u=e.currentPeriod,l=s[a.id];if((0,b.Z)(l))s[a.id]=((o={})[r]=i,o);else l[r]=i;var d=e.trackChoiceManager,c=e.currentContentCanceller.signal;if(null!==d&&null!==u&&!(0,b.Z)(a)&&a.id===u.id)switch(r){case"audio":var f=d.getChosenAudioTrack(u);this._priv_triggerEventIfNotStopped("audioTrackChange",f,c);var v=this.getAvailableAudioBitrates();this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange",v,c);break;case"text":var p=d.getChosenTextTrack(u);this._priv_triggerEventIfNotStopped("textTrackChange",p,c);break;case"video":var h=d.getChosenVideoTrack(u);this._priv_triggerEventIfNotStopped("videoTrackChange",h,c);var m=this.getAvailableVideoBitrates();this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange",m,c)}}},i._priv_onRepresentationChange=function(e,t){var n,r,i=t.type,a=t.period,o=t.representation;if(e.contentId===(null===(n=this._priv_contentInfos)||void 0===n?void 0:n.contentId)){null===e.activeRepresentations&&(e.activeRepresentations={});var s,u=e.activeRepresentations,l=e.currentPeriod,d=u[a.id];if((0,b.Z)(d))u[a.id]=((s={})[i]=o,s);else d[i]=o;var c=null!==(r=null==o?void 0:o.bitrate)&&void 0!==r?r:-1;if(!(0,b.Z)(a)&&null!==l&&l.id===a.id){var f=this._priv_contentInfos.currentContentCanceller.signal;"video"===i?this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange",c,f):"audio"===i&&this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange",c,f)}}},i._priv_onBitrateEstimationChange=function(e){var t=e.type,n=e.bitrate;void 0!==n&&(this._priv_bitrateInfos.lastBitrates[t]=n),this.trigger("bitrateEstimationChange",{type:t,bitrate:n})},i._priv_onNativeTextTracksNext=function(e){this.trigger("nativeTextTracksChange",e)},i._priv_setPlayerState=function(e){this.state!==e&&(this.state=e,l.Z.info("API: playerStateChange event",e),this.trigger("playerStateChange",e))},i._priv_triggerPositionUpdate=function(e,t){var n,r;if(e.contentId===(null===(n=this._priv_contentInfos)||void 0===n?void 0:n.contentId)){var i=e.isDirectFile,o=e.manifest;if((i||null!==o)&&!(0,b.Z)(t)){var s=null!==o?o.getMaximumSafePosition():void 0,u={position:t.position,duration:t.duration,playbackRate:t.playbackRate,maximumBufferTime:s,bufferGap:void 0!==t.bufferGap&&isFinite(t.bufferGap)?t.bufferGap:0};if(null!==o&&o.isLive&&t.position>0){var l=null!==(r=o.availabilityStartTime)&&void 0!==r?r:0;u.wallClockTime=t.position+l;var d=o.getLivePosition();void 0!==d&&(u.liveGap=d-t.position)}else if(i&&null!==this.videoElement){var c=a(this.videoElement);void 0!==c&&(u.wallClockTime=c+t.position)}this.trigger("positionUpdate",u)}}},i._priv_triggerAvailableBitratesChangeEvent=function(e,t,n){var r=this._priv_contentEventsMemory[e];n.isCancelled()||void 0!==r&&(0,m.Z)(t,r)||(this._priv_contentEventsMemory[e]=t,this.trigger(e,t))},i._priv_triggerCurrentBitrateChangeEvent=function(e,t,n){n.isCancelled()||t===this._priv_contentEventsMemory[e]||(this._priv_contentEventsMemory[e]=t,this.trigger(e,t))},i._priv_getCurrentRepresentations=function(){if(null===this._priv_contentInfos)return null;var e=this._priv_contentInfos,t=e.currentPeriod,n=e.activeRepresentations;return null===t||null===n||(0,b.Z)(n[t.id])?null:n[t.id]},i._priv_triggerEventIfNotStopped=function(e,t,n){n.isCancelled()||this.trigger(e,t)},i._priv_initializeMediaElementTrackChoiceManager=function(e,t,n){var r,i,a,o=this;(0,g.Z)(null!==h.Z.directfile,"Initializing `MediaElementTrackChoiceManager` without Directfile feature"),(0,g.Z)(null!==this.videoElement,"Initializing `MediaElementTrackChoiceManager` on a disposed RxPlayer");var s=new h.Z.directfile.mediaElementTrackChoiceManager(this.videoElement),u=void 0===e?this._priv_preferredAudioTracks:[e];s.setPreferredAudioTracks(u,!0);var l=void 0===t?this._priv_preferredTextTracks:[t];return s.setPreferredTextTracks(l,!0),s.setPreferredVideoTracks(this._priv_preferredVideoTracks,!0),this._priv_triggerEventIfNotStopped("availableAudioTracksChange",s.getAvailableAudioTracks(),n),this._priv_triggerEventIfNotStopped("availableVideoTracksChange",s.getAvailableVideoTracks(),n),this._priv_triggerEventIfNotStopped("availableTextTracksChange",s.getAvailableTextTracks(),n),this._priv_triggerEventIfNotStopped("audioTrackChange",null!==(r=s.getChosenAudioTrack())&&void 0!==r?r:null,n),this._priv_triggerEventIfNotStopped("textTrackChange",null!==(i=s.getChosenTextTrack())&&void 0!==i?i:null,n),this._priv_triggerEventIfNotStopped("videoTrackChange",null!==(a=s.getChosenVideoTrack())&&void 0!==a?a:null,n),s.addEventListener("availableVideoTracksChange",(function(e){return o.trigger("availableVideoTracksChange",e)})),s.addEventListener("availableAudioTracksChange",(function(e){return o.trigger("availableAudioTracksChange",e)})),s.addEventListener("availableTextTracksChange",(function(e){return o.trigger("availableTextTracksChange",e)})),s.addEventListener("audioTrackChange",(function(e){return o.trigger("audioTrackChange",e)})),s.addEventListener("videoTrackChange",(function(e){return o.trigger("videoTrackChange",e)})),s.addEventListener("textTrackChange",(function(e){return o.trigger("textTrackChange",e)})),s},(0,e.Z)(r,null,[{key:"ErrorTypes",get:function(){return p.ZB}},{key:"ErrorCodes",get:function(){return p.SM}},{key:"LogLevel",get:function(){return l.Z.getLevel()},set:function(e){l.Z.setLevel(e)}}]),r}(y.Z);jn.version="3.30.0";var qn=jn,Yn=n(7273);!function(){Yn.Z.ContentDecryptor=n(1266).ZP,Yn.Z.imageBuffer=n(7127).Z,Yn.Z.imageParser=n(3203).Z,Yn.Z.transports.smooth=n(2339).Z,Yn.Z.transports.dash=n(85).Z,Yn.Z.dashParsers.js=n(4541).Z,Yn.Z.nativeTextTracksBuffer=n(9059).Z,Yn.Z.nativeTextTracksParsers.vtt=n(9405).Z,Yn.Z.nativeTextTracksParsers.ttml=n(1570).Z,Yn.Z.nativeTextTracksParsers.sami=n(1812).Z,Yn.Z.nativeTextTracksParsers.srt=n(8057).Z,Yn.Z.htmlTextTracksBuffer=n(5192).Z,Yn.Z.htmlTextTracksParsers.sami=n(5734).Z,Yn.Z.htmlTextTracksParsers.ttml=n(7439).Z,Yn.Z.htmlTextTracksParsers.srt=n(8675).Z,Yn.Z.htmlTextTracksParsers.vtt=n(4099).Z;var e=n(9372).Z,t=n(6796).Z;Yn.Z.directfile={initDirectFile:e,mediaElementTrackChoiceManager:t}}(),"boolean"==typeof __RX_PLAYER_DEBUG_MODE__&&__RX_PLAYER_DEBUG_MODE__&&l.Z.setLevel("DEBUG");var Xn=qn}(),r=r.default}()})); \ No newline at end of file diff --git a/dist/rx-player.min.js.LICENSE.txt b/dist/rx-player.min.js.LICENSE.txt index 823c091e2f..ae386fb79c 100644 --- a/dist/rx-player.min.js.LICENSE.txt +++ b/dist/rx-player.min.js.LICENSE.txt @@ -1,16 +1 @@ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ - /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ diff --git a/doc/Getting_Started/Minimal_Player.md b/doc/Getting_Started/Minimal_Player.md index bf23b17e39..cb0b800a00 100644 --- a/doc/Getting_Started/Minimal_Player.md +++ b/doc/Getting_Started/Minimal_Player.md @@ -131,6 +131,7 @@ Here is the anotated exhaustive list (notes are at the bottom of the table): | `DASH_WASM` [5] [6] | Enable DASH playback using a WebAssembly-based MPD parser | | `LOCAL_MANIFEST` [5] | Enable playback of "local" contents | | `METAPLAYLIST` [5] | Enable playback of "metaplaylist" contents | +| `DEBUG_ELEMENT` [5] | Allows to use the `createDebugElement` RxPlayer method | --- @@ -255,6 +256,11 @@ build. False by default. If set to "true", all code relative to metaplaylist streaming will be included during a build. +#### RXP_DEBUG_ELEMENT + +False by default. If set to "true", the method RxPlayer's `createDebugElement` +method will be callable. + #### RXP_EME True by default. If set to "false", all code relative to encrypted contents will diff --git a/doc/api/Buffer_Information/.docConfig.json b/doc/api/Buffer_Information/.docConfig.json index e109a2dd14..f28570ab09 100644 --- a/doc/api/Buffer_Information/.docConfig.json +++ b/doc/api/Buffer_Information/.docConfig.json @@ -3,14 +3,6 @@ { "path": "./getVideoBufferGap.md", "displayName": "getVideoBufferGap" - }, - { - "path": "./getVideoLoadedTime.md", - "displayName": "getVideoLoadedTime" - }, - { - "path": "./getVideoPlayedTime.md", - "displayName": "getVideoPlayedTime" } ] } diff --git a/doc/api/Content_Information/.docConfig.json b/doc/api/Content_Information/.docConfig.json index b61314dd79..1d63b22e8a 100644 --- a/doc/api/Content_Information/.docConfig.json +++ b/doc/api/Content_Information/.docConfig.json @@ -4,13 +4,21 @@ "path": "./getUrl.md", "displayName": "getUrl" }, + { + "path": "./updateContentUrls.md", + "displayName": "updateContentUrls" + }, { "path": "./isLive.md", "displayName": "isLive" }, { - "path": "./getCurrentKeySystem.md", - "displayName": "getCurrentKeySystem" + "path": "./getKeySystemConfiguration.md", + "displayName": "getKeySystemConfiguration" + }, + { + "path": "./createDebugElement.md", + "displayName": "createDebugElement" } ] } diff --git a/doc/api/Content_Information/createDebugElement.md b/doc/api/Content_Information/createDebugElement.md new file mode 100644 index 0000000000..bd355dbf15 --- /dev/null +++ b/doc/api/Content_Information/createDebugElement.md @@ -0,0 +1,28 @@ +# createDebugElement + +## Description + +Displays the default RxPlayer's debugging element inside the HTML element given +in argument. + +The debugging information will then be regularly updated until the `dispose` +method of the returned object is called. + +This method can only be called if the `DEBUG_ELEMENT` experimental feature has +been added to the RxPlayer. + +You can have more information on this feature [in the Debug Element +documentation page](../Miscellaneous/Debug_Element.md). + +## Syntax + +```js +const debugInfo = player.createDebugElement(myElement); +``` + + - **arguments**: + 1. _element_ `HTMLElement`: HTML element in which the debugging information + should be displayed. + + - **return value** `Object`: Object with a `dispose method, allowing to remove + and stop updating the debugging element. diff --git a/doc/api/Content_Information/getKeySystemConfiguration.md b/doc/api/Content_Information/getKeySystemConfiguration.md new file mode 100644 index 0000000000..13f810e658 --- /dev/null +++ b/doc/api/Content_Information/getKeySystemConfiguration.md @@ -0,0 +1,45 @@ +# getKeySystemConfiguration + +## Description + +Returns information on the key system configuration currently associated to the +HTMLMediaElement (e.g. `

    +In DirectFile mode (see loadVideo options), +this method has no effect. +
    + +## Syntax + +```js +player.updateContentUrls(urls); +// or +player.updateContentUrls(urls, params); +``` + + - **arguments**: + + 1. _urls_ `Array.|under`: URLs to reach that content / Manifest + from the most prioritized URL to the least prioritized URL. + + 2. _params_ `Object|undefined`: Optional parameters linked to this URL + change. + + Can contain the following properties: + + - _refresh_ `boolean`: If `true` the resource in question (e.g. + DASH's MPD) will be refreshed immediately. + +## Examples + +```js +// Update with only one URL +player.updateContentUrls(["http://my.new.url"]); + +// Update with multiple URLs +player.updateContentUrls([ + "http://more.prioritized.url", + "http://less.prioritized.url", +]); + +// Set no URL (only is useful in some very specific situations, like for content +// with no Manifest refresh or when a `manifestLoader` is set). +player.updateContentUrls(undefined); + +// Update and ask to refresh immediately +player.updateContentUrls(["http://my.new.url"], { refresh: true }); +``` diff --git a/doc/api/Creating_a_Player.md b/doc/api/Creating_a_Player.md index 16b681c6e5..bee4490bce 100644 --- a/doc/api/Creating_a_Player.md +++ b/doc/api/Creating_a_Player.md @@ -308,7 +308,7 @@ is currently buffered and an estimation of the size of the next segments.
    In DirectFile mode (see loadVideo options), +href="./Loading_a_Content.md#transport">loadVideo options), this method has no effect.
    diff --git a/doc/api/Decryption_Options.md b/doc/api/Decryption_Options.md index 51f4bf58a5..89eb0b12f1 100644 --- a/doc/api/Decryption_Options.md +++ b/doc/api/Decryption_Options.md @@ -503,6 +503,12 @@ updated. ### onKeyStatusesChange +
    +This option is deprecated, it will disappear in the next major release +`v4.0.0` (see Deprecated +APIs). +
    + _type_: `Function | undefined` Callback triggered each time one of the key's diff --git a/doc/api/Deprecated/.docConfig.json b/doc/api/Deprecated/.docConfig.json index 9f7041343f..e0606ed77e 100644 --- a/doc/api/Deprecated/.docConfig.json +++ b/doc/api/Deprecated/.docConfig.json @@ -24,6 +24,10 @@ "path": "./exitFullscreen.md", "displayName": "exitFullscreen" }, + { + "path": "./getCurrentKeySystem.md", + "displayName": "getCurrentKeySystem" + }, { "path": "./getImageTrackData.md", "displayName": "getImageTrackData" @@ -31,6 +35,14 @@ { "path": "./getNativeTextTrack.md", "displayName": "getNativeTextTrack" + }, + { + "path": "./getVideoLoadedTime.md", + "displayName": "getVideoLoadedTime" + }, + { + "path": "./getVideoPlayedTime.md", + "displayName": "getVideoPlayedTime" } ] } diff --git a/doc/api/Content_Information/getCurrentKeySystem.md b/doc/api/Deprecated/getCurrentKeySystem.md similarity index 54% rename from doc/api/Content_Information/getCurrentKeySystem.md rename to doc/api/Deprecated/getCurrentKeySystem.md index 0f09589e12..81da1611ae 100644 --- a/doc/api/Content_Information/getCurrentKeySystem.md +++ b/doc/api/Deprecated/getCurrentKeySystem.md @@ -1,5 +1,11 @@ # getCurrentKeySystem +
    +This option is deprecated, it will disappear in the next major release +`v4.0.0` (see Deprecated +APIs). +
    + ## Description Returns the type of keySystem used for DRM-protected contents. diff --git a/doc/api/Buffer_Information/getVideoLoadedTime.md b/doc/api/Deprecated/getVideoLoadedTime.md similarity index 76% rename from doc/api/Buffer_Information/getVideoLoadedTime.md rename to doc/api/Deprecated/getVideoLoadedTime.md index 719d6292f3..c7c4641957 100644 --- a/doc/api/Buffer_Information/getVideoLoadedTime.md +++ b/doc/api/Deprecated/getVideoLoadedTime.md @@ -1,5 +1,11 @@ # getVideoLoadedTime +
    +This method is deprecated, it will disappear in the next major release +`v4.0.0` (see Deprecated +APIs). +
    + ## Description Returns in seconds the difference between: diff --git a/doc/api/Buffer_Information/getVideoPlayedTime.md b/doc/api/Deprecated/getVideoPlayedTime.md similarity index 77% rename from doc/api/Buffer_Information/getVideoPlayedTime.md rename to doc/api/Deprecated/getVideoPlayedTime.md index 5b262e58cf..c647fea9c1 100644 --- a/doc/api/Buffer_Information/getVideoPlayedTime.md +++ b/doc/api/Deprecated/getVideoPlayedTime.md @@ -1,5 +1,11 @@ # getVideoPlayedTime +
    +This method is deprecated, it will disappear in the next major release +`v4.0.0` (see Deprecated +APIs). +
    + ## Description Returns in seconds the difference between: diff --git a/doc/api/Deprecated/isFullscreen.md b/doc/api/Deprecated/isFullscreen.md index 43d322c8d0..82dca8dbdc 100644 --- a/doc/api/Deprecated/isFullscreen.md +++ b/doc/api/Deprecated/isFullscreen.md @@ -1,7 +1,7 @@ # isFullscreen
    -This option is deprecated, it will disappear in the next major release +This method is deprecated, it will disappear in the next major release `v4.0.0` (see Deprecated APIs).
    diff --git a/doc/api/Loading_a_Content.md b/doc/api/Loading_a_Content.md index 41278ead5f..da1d71db8a 100644 --- a/doc/api/Loading_a_Content.md +++ b/doc/api/Loading_a_Content.md @@ -533,6 +533,12 @@ considered stable: - **aggressiveMode** (`boolean|undefined`): +
    +This option is deprecated, it will disappear in the next major release +`v4.0.0` (see Deprecated +APIs). +
    + If set to true, we will try to download segments very early, even if we are not sure they had time to be completely generated. diff --git a/doc/api/Miscellaneous/.docConfig.json b/doc/api/Miscellaneous/.docConfig.json index a6a3d9fdea..34e478858f 100644 --- a/doc/api/Miscellaneous/.docConfig.json +++ b/doc/api/Miscellaneous/.docConfig.json @@ -16,6 +16,10 @@ "path": "./hdr.md", "displayName": "HDR" }, + { + "path": "./Debug_Element.md", + "displayName": "Debug Element" + }, { "path": "./Local_Contents.md", "displayName": "Local Contents" diff --git a/doc/api/Miscellaneous/Debug_Element.md b/doc/api/Miscellaneous/Debug_Element.md new file mode 100644 index 0000000000..c103dac582 --- /dev/null +++ b/doc/api/Miscellaneous/Debug_Element.md @@ -0,0 +1,122 @@ +# Displaying the RxPlayer's debugging element + +The `DEBUG_ELEMENT` feature is an experimental (meaning its API can still evolve +from version to version) feature allowing to render an HTML element displaying +debug information that might be interesting while debugging playback. + +![Example of a debug element](../../static/img/debug_elt.png) + +That element displays various metrics, some of the RxPlayer configuration +currently set and displays information on the content of the various buffers. +Details on everything that may be diplayed is described lower in this page. + +Before that feature, each application generally created its own way of +displaying debug information. + +Using one directly defined in the RxPlayer API instead allows to: + + 1. Use a default and complete debugging element, if you don't want to have to + create one from scratch + + 2. Display debugging information that is not even available through the API, + like a representation of the content in the various buffers. + + +## Importing the DEBUG\_ELEMENT feature + +This feature is not present in default builds to prevent adding unnecessary code +to codebases that don't need it. + +As such, to add it, you will need to rely on the [minimal](../../Getting_Started/Minimal_Player.md) +build of the RxPlayer and you will need to add the +`DEBUG_ELEMENT` experimental feature: +```js +import RxPlayer from "rx-player/minimal"; +import { DEBUG_ELEMENT } from "rx-player/experimental/features"; + +RxPlayer.addFeatures([DEBUG_ELEMENT]); +``` + + +## Rendering the debugging element + +The debugging element will be displayed inside an HTML element of your choosing. + +That element is communicated through the same RxPlayer's method used to display +debugging information: `createDebugElement`: +```js +const myElement = document.querySelector(".debug-element"); +const debuggingInfo = rxPlayer.createDebugElement(myElement); + +// Note: the debugging info can be removed at any time by calling `dispose` +// on the returned object: +debuggingInfo.dispose(); +``` + +Note that more or less information may be displayed depending on the height of +that HTML element. If playing video, it would be a good default to communicate +an HTML element of the same size than the video element on which the content +plays. Ultimately, only the upper left corner of that element should be used +to display current debugging information. + + +## Displayed information + +The debug element contains a lof of values, each preceded by a 2-to-4 letter +acronyms put in bold and followed by a slash (/). Each of these designates a +specific metric that is explained below. + +It should however be noted that the data inside the debugging element is updated +at regular interval, every seconds or so. As such, information might not always +reflect exactly what's going on at a particular point in time. + + - General information: + - **ct**: _Current time_. The current position on the media element. + - **bg**: _Buffer gap_. The difference between the last buffered second of the current buffered range and the current second. + - **rs**: _Ready State_. Reflects the HTMLMediaElement property `readyState` + - **pr**: _Playback Rate_. Reflects the HTMLMediaElement property `playbackRate` + - **sp**: _Speed_. The playback rate configurated through the `setPlaybackRate` method + - **pr**: _Paused_. Reflects the HTMLMediaElement property `paused`. `0` for `false` and `1` for `true`. + - **en**: _Ended_. Reflects the HTMLMediaElement property `ended`. `0` for `false` and `1` for `true`. + - **li**: _Live_. If `1`, the current content can be considered a live content. + - **wba**: _WantedBufferAhead_. The configured `wantedBufferAhead`, which is the amount of buffer ahead of the current position that is pre-buffered, in seconds. + - **st**: _State_. The current state of the RxPlayer. + - **ks**: _Key System_. If set, the current key system used to decrypt contents. + - **mbb**: _Max Buffer Behind_. If set, the configured `maxBufferBehind` (amount of buffer to keep in memory behind the current position, in seconds). + - **mba**: _Max Buffer Ahead_. If set, the configured `maxBufferAhead` (amount of buffer to keep in memory ahead of the current position, in seconds). + - **mia**: _Min Audio Bitrate_. If set, the configured `minAudioBitrate` (minimum audio bitrate reachable through adaptive streaming). + - **miv**: _Min video Bitrate_. If set, the configured `minVideoBitrate` (minimum video bitrate reachable through adaptive streaming). + - **maa**: _Max Audio Bitrate_. If set, the configured `maxAudioBitrate` (maximum audio bitrate reachable through adaptive streaming). + - **mav**: _Max video Bitrate_. If set, the configured `maxVideoBitrate` (maximum video bitrate reachable through adaptive streaming). + - **fab**: _Forced Audio bitrate_. If set, the forced audio bitrate, set by example through `setAudioBitrate` + - **fvb**: _Forced Video bitrate_. If set, the forced video bitrate, set by example through `setVideoBitrate` + - **mbs**: _Max video Buffer Size_. If set, the configured `maxVideoBufferSize` (maximum amount of video data kept in the video buffer, in kilobytes) + - **mip**: _Minimum Position_. The minimum position, obtainable through the `getMinimumPosition` API, at which the content is reachable + - **dmi**: _Distance to Minimum Position_. The difference between the current position and the minimum position, in seconds + - **map**: _Maximum Position_. The maximum position, obtainable through the `getmaximumPosition` API, at which the content is reachable + - **dma**: _Distance to Minimum Position_. The difference between the maximum position and the current position, in seconds + - **er**: _Error_. Error converted to string, if one. + - **url**: _URL_. URL of the current content, may be truncated if too long. + - **vt**: _Video tracks_. List of the video tracks' `id` property. The line begins with a number indicating the number of available video tracks, followed by `:`, followed by each video track's id separated by a space. The current video track is prepended by a `*` character. + - **at**: _Audio tracks_. List of the audio tracks' `id` property. The line begins with a number indicating the number of available audio tracks, followed by `:`, followed by each audio track's id separated by a space. The current audio track is prepended by a `*` character. + - **tt**: _Text tracks_. List of the text tracks' `id` property. The line begins with a number indicating the number of available text tracks, followed by `:`, followed by each text track's id separated by a space. The current text track is prepended by a `*` character. + - **vb**: _Video Bitrates_. The available video bitrates in the current video track, separated by a space. + - **ab**: _Audio Bitrates_. The available audio bitrates in the current audio track, separated by a space. + + - Buffer information + - **vbuf**: _Graphical representation of the video buffer_. The red rectangle indicates the current position, the different colors indicate different video qualities in the buffer. + - **abuf**: _Graphical representation of the audio buffer_. The red rectangle indicates the current position, the different colors indicate different audio qualities in the buffer. + - **tbuf**: _Graphical representation of the text buffer_. The red rectangle indicates the current position, the different colors indicate different text qualities in the buffer. + - **play**: Information on the content that should be currently playing on the buffer of the type in the previously represented buffer (audio, video or text). Information depends on the type and on properties but always begin with the Representation's `id` property between quotes (`"`). + It can also indicate: + - the video resolution in a format `x`, + - the bitrate in a format `(kbps)`, + - the codec in a format `c:""`, + - the language in a format `l:""`, + - if the corresponding track contains sign language interpretation: with `si:1`. `si:0` indicates the opposite, + - if the corresponding track is a trickmode video track: with `tm:1`. `tm:0` indicates the opposite, + - if the corresponding track contains an audio description: with `ad:1`. `ad:0` indicates the opposite, + - if the corresponding track contains closed captions: with `cc:1`. `cc:0` indicates the opposite, + - the start and end time of the Period this track is a part of, in the format `p:-`. + - **load**: Information on the content that is currently loaded on the buffer of the type in the previously represented buffer (audio, video or text). Information represented follows the exact same format than the **play** metric. + - **bgap**: _Buffer gap_. Representation of the evolution of the amount of pre-buffered data in the intersection of the video and audio buffers. A raising value means that the buffer is being filled and a lowering one means that the buffer is emptying. If the bar becomes fully transparent, we may enter into rebuffering mode. diff --git a/doc/api/Miscellaneous/Deprecated_APIs.md b/doc/api/Miscellaneous/Deprecated_APIs.md index a95d4d15bb..a96ccbb5eb 100644 --- a/doc/api/Miscellaneous/Deprecated_APIs.md +++ b/doc/api/Miscellaneous/Deprecated_APIs.md @@ -225,6 +225,132 @@ You can read [the related chapter](#bif-apis) for more information. You can replace this API by using the [parseBifThumbnails](../Tools/parseBifThumbnails.md) tool. + +### getCurrentKeySystem + +`getCurrentKeySystem` has been deprecated in profit of the similar +[`getKeySystemConfiguration`](../Content_Information/getKeySystemConfiguration.md) +method. + +Note however that the key system string optionally returned as a `keySystem` +property from the latter is slightly different than the optional string returned +from the former: + + - `getCurrentKeySystem` returned the same keysystem name used as `type` + property of the `keySystems` `loadVideo` option originally communicated. + + For example, calling the `loadVideo` like this: + ```js + rxPlayer.loadVideo({ + keySystems: [{ + type: "widevine", + // ... + }], + // ... + }); + ``` + May lead to `getCurrentKeySystem` returning just `"widevine"`. + + - The `keySystem` property from `getKeySystemConfiguration` returns the actual + key system string used, alongside the actual configuration used in its + `configuration` key. + + For example, calling the `loadVideo` like this: + ```js + rxPlayer.loadVideo({ + keySystems: [{ + type: "widevine", + // ... + }], + // ... + }); + ``` + May lead to a more complex `keySystem` property being reported, like for + example, `"com.widevine.alpha"`. + + +### getVideoLoadedTime + +The `getVideoLoadedTime` method has been deprecated and won't be replaced in the +next major version because it was poorly named, poorly understood, and it is +easy to replace. + +#### How to replace that method + +To replace it, you can write: +```js +function getVideoLoadedTime() { + const position = rxPlayer.getPosition(); + const mediaElement = rxPlayer.getVideoElement(); + if (mediaElement === null) { + console.error("The RxPlayer is disposed"); + } else { + const range = getRange(mediaElement.buffered, currentTime); + return range !== null ? range.end - range.start : + 0; + } +} + +/** + * Get range object of a specific time in a TimeRanges object. + * @param {TimeRanges} timeRanges + * @returns {Object} + */ +function getRange(timeRanges, time) { + for (let i = timeRanges.length - 1; i >= 0; i--) { + const start = timeRanges.start(i); + if (time >= start) { + const end = timeRanges.end(i); + if (time < end) { + return { start, end }; + } + } + } + return null; +} +``` + +### getVideoPlayedTime + +The `getVideoPlayedTime` method has been deprecated and won't be replaced in the +next major version because it was poorly named, poorly understood, and it is +easy to replace. + +#### How to replace that method + +To replace it, you can write: +```js +function getVideoPlayedTime() { + const position = rxPlayer.getPosition(); + const mediaElement = rxPlayer.getVideoElement(); + if (mediaElement === null) { + console.error("The RxPlayer is disposed"); + } else { + const range = getRange(mediaElement.buffered, currentTime); + return range !== null ? currentTime - range.start : + } +} + +/** + * Get range object of a specific time in a TimeRanges object. + * @param {TimeRanges} timeRanges + * @returns {Object} + */ +function getRange(timeRanges, time) { + for (let i = timeRanges.length - 1; i >= 0; i--) { + const start = timeRanges.start(i); + if (time >= start) { + const end = timeRanges.end(i); + if (time < end) { + return { start, end }; + } + } + } + return null; +} +``` + + ## RxPlayer Events The following RxPlayer events has been deprecated. @@ -355,7 +481,7 @@ Or at any time, through the `setPreferredTextTracks` method: player.setPreferredTextTracks([{ language: "fra", closedCaption: false }]); ``` -### supplementaryTextTracks +### transportOptions.supplementaryTextTracks The `supplementaryTextTracks` has been deprecated for multiple reasons: @@ -403,7 +529,7 @@ the transition might take some time. The `TextTrackRenderer` tool is documented [here](../Tools/TextTrackRenderer.md). -### supplementaryImageTracks +### transportOptions.supplementaryImageTracks The `supplementaryImageTracks` events have been deprecated like most API related to BIF thumbnail parsing. @@ -412,6 +538,15 @@ You can read [the related chapter](#bif-apis) for more information. You can replace this API by using the [parseBifThumbnails](../Tools/parseBifThumbnails.md) tool. + +## transportOptions.aggressiveMode + +The `aggressiveMode` boolean from the `transportOptions` option will be removed +from the next major version. + +It has no planned replacement. Please open an issue if you need it. + + ### hideNativeSubtitle The `hideNativeSubtitle` option is deprecated and won't be replaced. @@ -458,12 +593,20 @@ rxPlayer.loadVideo({ ``` You can have more information on the `onKeyExpiration` option [in the -correspnding API documentation](./Decryption_Options.md#onkeyexpiration). +correspnding API documentation](../Decryption_Options.md#onkeyexpiration). If you previously set `throwOnLicenseExpiration` to `true` or `undefined`, you can just remove this property as this still the default behavior. +### keySystems[].onKeyStatusesChange + +The `onKeyStatusesChange` callback from the `keySystems` option will be removed +from the next major version. + +It has no planned replacement. Please open an issue if you need it. + + ## RxPlayer constructor options The following RxPlayer constructor options are deprecated. diff --git a/doc/api/Player_Events.md b/doc/api/Player_Events.md index 7503c990cc..b5612790e1 100644 --- a/doc/api/Player_Events.md +++ b/doc/api/Player_Events.md @@ -112,8 +112,13 @@ This chapter describes events linked to the current audio, video or text track. _payload type_: `Array.` -Triggered when the currently available audio tracks change (e.g.: at the -beginning of the content, when period changes...). +Triggered when the currently available audio tracks might have changed (e.g.: at +the beginning of the content, when period changes...) for the currently-playing +Period. + +_The event might also rarely be emitted even if the list of available audio +tracks did not really change - as the RxPlayer might send it in situations where +there's a chance it had without thoroughly checking it._ The array emitted contains object describing each available audio track: @@ -152,8 +157,13 @@ This event only concerns the currently-playing Period. _payload type_: `Array.` -Triggered when the currently available video tracks change (e.g.: at the -beginning of the content, when period changes...). +Triggered when the currently available video tracks might change (e.g.: at the +beginning of the content, when period changes...) for the currently-playing +Period. + +_The event might also rarely be emitted even if the list of available video +tracks did not really change - as the RxPlayer might send it in situations where +there's a chance it had without thoroughly checking it._ The array emitted contains object describing each available video track: @@ -192,8 +202,13 @@ This event only concerns the currently-playing Period. _payload type_: `Array.` -Triggered when the currently available text tracks change (e.g.: at the -beginning of the content, when period changes...). +Triggered when the currently available text tracks might change (e.g.: at the +beginning of the content, when period changes...) for the currently-playing +Period. + +_The event might also rarely be emitted even if the list of available text +tracks did not really change - as the RxPlayer might send it in situations where +there's a chance it had without thoroughly checking it._ The array emitted contains object describing each available text track: @@ -217,6 +232,12 @@ The array emitted contains object describing each available text track: - `closedCaption` (`Boolean`): Whether the track is specially adapted for the hard of hearing or not. +- `forced` (`Boolean`): If `true` this text track is meant to be displayed by + default if no other text track is selected. + + It is often used to clarify dialogue, alternate languages, texted graphics or + location and person identification. + - `active` (`Boolean`): Whether the track is the one currently active or not. @@ -259,9 +280,18 @@ The payload is an object describing the new track, with the following properties: - `id` (`Number|string`): The id used to identify the track. + - `language` (`string`): The language the text track is in. + - `closedCaption` (`Boolean`): Whether the track is specially adapted for the hard of hearing or not. + +- `forced` (`Boolean`): If `true` this text track is meant to be displayed by + default if no other text track is selected. + + It is often used to clarify dialogue, alternate languages, texted graphics or + location and person identification. + - `label` (`string|undefined`): A human readable label that may be displayed in the user interface providing a choice between text tracks. diff --git a/doc/api/Track_Selection/getAvailableTextTracks.md b/doc/api/Track_Selection/getAvailableTextTracks.md index c83b5e1c4d..46019df395 100644 --- a/doc/api/Track_Selection/getAvailableTextTracks.md +++ b/doc/api/Track_Selection/getAvailableTextTracks.md @@ -21,6 +21,12 @@ Each of the objects in the returned array have the following properties: - `closedCaption` (`Boolean`): Whether the track is specially adapted for the hard of hearing or not. +- `forced` (`Boolean`): If `true` this text track is meant to be displayed by + default if no other text track is selected. + + It is often used to clarify dialogue, alternate languages, texted graphics or + location and person identification. + - `label` (`string|undefined`): A human readable label that may be displayed in the user interface providing a choice between text tracks. diff --git a/doc/api/Track_Selection/getPreferredTextTracks.md b/doc/api/Track_Selection/getPreferredTextTracks.md index cfbd38b6f5..14be57caeb 100644 --- a/doc/api/Track_Selection/getPreferredTextTracks.md +++ b/doc/api/Track_Selection/getPreferredTextTracks.md @@ -12,8 +12,10 @@ it was called: { language: "fra", // {string} The wanted language // (ISO 639-1, ISO 639-2 or ISO 639-3 language code) - closedCaption: false // {Boolean} Whether the text track should be a closed - // caption for the hard of hearing + closedCaption: false, // {Boolean} Whether the text track should be a closed + // caption for the hard of hearing + forced: false // {Boolean|undefined} If `true` this text track is meant to be + // displayed by default if no other text track is selected. } ``` diff --git a/doc/api/Track_Selection/getTextTrack.md b/doc/api/Track_Selection/getTextTrack.md index c86de6e12b..9ba3211f73 100644 --- a/doc/api/Track_Selection/getTextTrack.md +++ b/doc/api/Track_Selection/getTextTrack.md @@ -31,6 +31,12 @@ return an object with the following properties: - `closedCaption` (`Boolean`): Whether the track is specially adapted for the hard of hearing or not. +- `forced` (`Boolean`): If `true` this text track is meant to be displayed by + default if no other text track is selected. + + It is often used to clarify dialogue, alternate languages, texted graphics or + location and person identification. + `undefined` if no text content has been loaded yet or if its information is unknown. diff --git a/doc/api/Track_Selection/setPreferredTextTracks.md b/doc/api/Track_Selection/setPreferredTextTracks.md index 635eb5d19e..ebce65ab52 100644 --- a/doc/api/Track_Selection/setPreferredTextTracks.md +++ b/doc/api/Track_Selection/setPreferredTextTracks.md @@ -13,14 +13,22 @@ apply to every future loaded content in the current RxPlayer instance. The first argument should be set as an array of objects, each object describing constraints a text track should respect. +Here are the list of properties that can be set on each of those objects: + + - **language** (`string`): The wanted language (preferably as an ISO 639-1, + ISO 639-2 or ISO 639-3 language code) + + - **closedCaption** (`boolean`): Whether the text track should be a closed + caption for the hard of hearing + + - **forced** (`boolean|undefined`): If `true` the text track should be a + "forced subtitle", which are default text tracks used when no other text + track is selected. + Here is all the properties that should be set in a single object of that array. ```js { - language: "fra", // {string} The wanted language - // (ISO 639-1, ISO 639-2 or ISO 639-3 language code) - closedCaption: false // {Boolean} Whether the text track should be a closed - // caption for the hard of hearing } ``` diff --git a/doc/reference/API_Reference.md b/doc/reference/API_Reference.md index 86ec8cd93e..56a8d74bbb 100644 --- a/doc/reference/API_Reference.md +++ b/doc/reference/API_Reference.md @@ -442,22 +442,29 @@ properties, methods, events and so on. Returns in seconds the difference between the current position and the end of the current media time range. - - [`getVideoLoadedTime`](../api/Buffer_Information/getVideoLoadedTime.md): - Returns in seconds the difference between the start and the end of the - current media time range. + - [`getVideoLoadedTime`](../api/Deprecated/getVideoLoadedTime.md): + [Deprecated] Returns in seconds the difference between the start and the end + of the current media time range. - - [`getVideoPlayedTime`](../api/Buffer_Information/getVideoPlayedTime.md): - Returns in seconds the difference between the start of the current media - time range and the current position. + - [`getVideoPlayedTime`](../api/Deprecated/getVideoPlayedTime.md): + [Deprecated] Returns in seconds the difference between the start of the + current media time range and the current position. - [`getUrl`](../api/Content_Information/getUrl.md): Get URL of the currently-played content. + - [`updateContentUrls`](../api/Content_Information/updateContentUrls.md): + Update URL(s) of the content currently being played. + - [`isLive`](../api/Content_Information/isLive.md): Returns `true` if the content is a "live" content. - - [`getCurrentKeySystem`](../api/Content_Information/getCurrentKeySystem.md): - Returns the name of the current key system. + - [`getKeySystemConfiguration`](../api/Content_Information/getKeySystemConfiguration.md): + Returns information on the key system currently attached to the + HTMLMediaElement linked to the RxPlayer. + + - [`getCurrentKeySystem`](../api/Deprecated/getCurrentKeySystem.md): + [Deprecated] Returns the name of the current key system. - [`getManifest`](../api/Deprecated/getManifest.md): [Deprecated] Information on the current Manifest. diff --git a/doc/static/img/debug_elt.png b/doc/static/img/debug_elt.png new file mode 100644 index 0000000000..bbc538768f Binary files /dev/null and b/doc/static/img/debug_elt.png differ diff --git a/jest.config.js b/jest.config.js index bfbbe3450e..dd5d7b3274 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,11 +6,6 @@ module.exports = { roots: ["/src"], preset: "ts-jest", testEnvironment: "jsdom", - // Without this, Jest just fails when importing rxjs, for some arcane - // ESM-vs-CommonJS reasons linked to how it works internally. - moduleNameMapper: { - "^rxjs$": require.resolve("rxjs"), - }, testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], collectCoverageFrom: [ "src/**/*.ts", @@ -59,6 +54,7 @@ module.exports = { HTML_VTT: 1, LOCAL_MANIFEST: 1, METAPLAYLIST: 1, + DEBUG_ELEMENT: 1, NATIVE_SAMI: 1, NATIVE_SRT: 1, NATIVE_TTML: 1, diff --git a/manifest.mpd b/manifest.mpd new file mode 100644 index 0000000000..aacc1cd6d3 --- /dev/null +++ b/manifest.mpd @@ -0,0 +1,149 @@ + + + + + + VAMAAAEAAQBKAzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgAyAEsAcwBzAGgARgA2AGMAawBDAFEAYwBpAE0AQwA0AGUATwBqAEwAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBEAGsARQBYAHQAYwBsAFMAWABrAFUAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBsAGkAYwBlAG4AcwBlAC4AYwB1AGIAbwB2AGkAcwBpAG8AbgAuAGkAdAAvAEwAaQBjAGUAbgBzAGUALwByAGkAZwBoAHQAcwBtAGEAbgBhAGcAZQByAC4AYQBzAG0AeAA8AC8ATABBAF8AVQBSAEwAPgA8AEwAVQBJAF8AVQBSAEwAPgBoAHQAdABwADoALwAvAGMAbwBuAHQAbwBzAG8ALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8APAAvAEwAVQBJAF8AVQBSAEwAPgA8AEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAIAB4AG0AbABuAHMAPQAiACIAPgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4AAAADdHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1RUAwAAAQABAEoDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBGADIASwBzAHMAaABGADYAYwBrAEMAUQBjAGkATQBDADQAZQBPAGoATABBAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEQAawBFAFgAdABjAGwAUwBYAGsAVQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAGwAaQBjAGUAbgBzAGUALgBjAHUAYgBvAHYAaQBzAGkAbwBuAC4AaQB0AC8ATABpAGMAZQBuAHMAZQAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwATABVAEkAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AYwBvAG4AdABvAHMAbwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwA8AC8ATABVAEkAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwAgAHgAbQBsAG4AcwA9ACIAIgA+ADwALwBDAFUAUwBUAE8ATQBBAFQAVABSAEkAQgBVAFQARQBTAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + AAAAYnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEIIARIQsqxiF3oRQHKQciMC4eOjLBoAIh45MDIwMDEzMV8yMDIyMDIwMTAwMDBfTElWRUNFTkMqAkhEMgBI49yVmwY= + + + + + + + + + + + + + + + + + VAMAAAEAAQBKAzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARgAyAEsAcwBzAGgARgA2AGMAawBDAFEAYwBpAE0AQwA0AGUATwBqAEwAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBEAGsARQBYAHQAYwBsAFMAWABrAFUAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBsAGkAYwBlAG4AcwBlAC4AYwB1AGIAbwB2AGkAcwBpAG8AbgAuAGkAdAAvAEwAaQBjAGUAbgBzAGUALwByAGkAZwBoAHQAcwBtAGEAbgBhAGcAZQByAC4AYQBzAG0AeAA8AC8ATABBAF8AVQBSAEwAPgA8AEwAVQBJAF8AVQBSAEwAPgBoAHQAdABwADoALwAvAGMAbwBuAHQAbwBzAG8ALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAC8APAAvAEwAVQBJAF8AVQBSAEwAPgA8AEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAIAB4AG0AbABuAHMAPQAiACIAPgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4AAAADdHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1RUAwAAAQABAEoDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBGADIASwBzAHMAaABGADYAYwBrAEMAUQBjAGkATQBDADQAZQBPAGoATABBAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEQAawBFAFgAdABjAGwAUwBYAGsAVQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAGwAaQBjAGUAbgBzAGUALgBjAHUAYgBvAHYAaQBzAGkAbwBuAC4AaQB0AC8ATABpAGMAZQBuAHMAZQAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwATABVAEkAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AYwBvAG4AdABvAHMAbwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwA8AC8ATABVAEkAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwAgAHgAbQBsAG4AcwA9ACIAIgA+ADwALwBDAFUAUwBUAE8ATQBBAFQAVABSAEkAQgBVAFQARQBTAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + AAAAYnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEIIARIQsqxiF3oRQHKQciMC4eOjLBoAIh45MDIwMDEzMV8yMDIyMDIwMTAwMDBfTElWRUNFTkMqAkhEMgBI49yVmwY= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index a48e041434..30dd2fab31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,78 +1,92 @@ { "name": "rx-player", - "version": "3.29.0", + "version": "3.30.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "rx-player", - "version": "3.29.0", + "version": "3.30.0", "license": "Apache-2.0", "dependencies": { - "next-tick": "1.1.0", - "rxjs": "7.5.7" + "next-tick": "1.1.0" }, "devDependencies": { - "@babel/core": "7.20.2", - "@babel/plugin-transform-runtime": "7.19.6", + "@babel/core": "7.21.0", + "@babel/plugin-transform-runtime": "7.21.0", "@babel/preset-env": "7.20.2", "@babel/preset-react": "7.18.6", "@types/chai": "4.3.4", - "@types/jest": "29.2.3", - "@types/mocha": "10.0.0", - "@types/node": "18.11.9", + "@types/jest": "29.4.0", + "@types/mocha": "10.0.1", + "@types/node": "18.14.0", "@types/sinon": "10.0.13", - "@typescript-eslint/eslint-plugin": "5.43.0", - "@typescript-eslint/eslint-plugin-tslint": "5.43.0", - "@typescript-eslint/parser": "5.43.0", + "@typescript-eslint/eslint-plugin": "5.53.0", + "@typescript-eslint/eslint-plugin-tslint": "5.53.0", + "@typescript-eslint/parser": "5.53.0", "arraybuffer-loader": "1.0.8", - "babel-loader": "9.1.0", + "babel-loader": "9.1.2", "chai": "4.3.7", "cheerio": "1.0.0-rc.12", - "core-js": "3.26.1", - "esbuild": "0.15.14", - "eslint": "8.27.0", - "eslint-plugin-import": "2.26.0", - "eslint-plugin-jsdoc": "39.6.2", - "eslint-plugin-react": "7.31.10", + "core-js": "3.28.0", + "esbuild": "0.17.10", + "eslint": "8.34.0", + "eslint-plugin-ban": "1.6.0", + "eslint-plugin-import": "2.27.5", + "eslint-plugin-jsdoc": "40.0.0", + "eslint-plugin-react": "7.32.2", "esm": "3.2.25", "express": "4.18.2", - "highlight.js": "11.6.0", + "highlight.js": "11.7.0", "html-entities": "2.3.3", - "jest": "29.3.1", - "jest-environment-jsdom": "29.3.1", + "jest": "29.4.3", + "jest-environment-jsdom": "29.4.3", "karma": "6.4.1", "karma-chrome-launcher": "3.1.1", "karma-firefox-launcher": "2.1.2", "karma-mocha": "2.0.1", "karma-webpack": "5.0.0", "markdown-it": "13.0.1", - "mocha": "10.1.0", + "mocha": "10.2.0", "mocha-loader": "5.1.5", "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "regenerator-runtime": "0.13.10", - "rimraf": "3.0.2", + "regenerator-runtime": "0.13.11", + "rimraf": "4.1.2", "semver": "7.3.8", - "sinon": "14.0.2", + "sinon": "15.0.1", "terser-webpack-plugin": "5.3.6", - "ts-jest": "29.0.3", - "ts-loader": "9.4.1", + "ts-jest": "29.0.5", + "ts-loader": "9.4.2", "tslint": "6.1.3", - "typescript": "4.9.3", + "typescript": "4.9.5", "webpack": "5.75.0", - "webpack-bundle-analyzer": "4.7.0", - "webpack-cli": "4.10.0" + "webpack-bundle-analyzer": "4.8.0", + "webpack-cli": "5.0.1" } }, "node_modules/@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.0" + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" }, "engines": { "node": ">=6.0.0" @@ -91,34 +105,34 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", - "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==", + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", + "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz", - "integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", + "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", "dev": true, "dependencies": { - "@ampproject/remapping": "^2.1.0", + "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.2", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.1", - "@babel/parser": "^7.20.2", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2", + "@babel/generator": "^7.21.0", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.0", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.0", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" }, "engines": { @@ -139,13 +153,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.3.tgz", - "integrity": "sha512-Wl5ilw2UD1+ZYprHVprxHZJCFeBWlzZYOovE4SDYLZnqCOD11j+0QzNeEWKLLTWM7nixrZEh7vNIyb76MyJg3A==", + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", + "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", "dev": true, "dependencies": { - "@babel/types": "^7.20.2", + "@babel/types": "^7.21.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -178,14 +193,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.0", + "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", "semver": "^6.3.0" }, "engines": { @@ -195,6 +211,15 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -204,6 +229,12 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.18.13", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz", @@ -289,13 +320,13 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" }, "engines": { "node": ">=6.9.0" @@ -338,9 +369,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", - "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.0.tgz", + "integrity": "sha512-eD/JQ21IG2i1FraJnTMbUarAUkA7G988ofehG5MDCRXaUU91rEBJuCeSoou2Sk1y4RbLYXzqEg1QLwEmRU4qcQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", @@ -348,9 +379,9 @@ "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" }, "engines": { "node": ">=6.9.0" @@ -490,14 +521,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", - "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.0" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" }, "engines": { "node": ">=6.9.0" @@ -518,9 +549,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", - "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz", + "integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1497,13 +1528,13 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz", + "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-plugin-utils": "^7.20.2", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -1779,33 +1810,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", - "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.0.tgz", + "integrity": "sha512-Xdt2P1H4LKTO8ApPfnO1KmzYMFpp7D/EinoXzLYN/cHcBNrVCAkAtGUcXnHXrl/VGktureU6fkQrHSBE2URfoA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.1", + "@babel/generator": "^7.21.0", "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", + "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.1", - "@babel/types": "^7.20.0", + "@babel/parser": "^7.21.0", + "@babel/types": "^7.21.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1814,9 +1845,9 @@ } }, "node_modules/@babel/types": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", - "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.0.tgz", + "integrity": "sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.19.4", @@ -1843,18 +1874,18 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", - "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true, "engines": { "node": ">=10.0.0" } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.0.tgz", - "integrity": "sha512-u0XZyvUF6Urb2cSivSXA8qXIpT/CxkHcdtZKoWusAzgzmsTWpg0F2FpWXsolHmMUyVY3dLWaoy+0ccJ5uf2QjA==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", + "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", "dev": true, "dependencies": { "comment-parser": "1.3.1", @@ -1866,9 +1897,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.14.tgz", - "integrity": "sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==", + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.10.tgz", + "integrity": "sha512-7YEBfZ5lSem9Tqpsz+tjbdsEshlO9j/REJrfv4DXgKTt1+/MHqGwbtlyxQuaSlMeUZLxUKBaX8wdzlTfHkmnLw==", "cpu": [ "arm" ], @@ -1881,486 +1912,504 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz", - "integrity": "sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.10.tgz", + "integrity": "sha512-ht1P9CmvrPF5yKDtyC+z43RczVs4rrHpRqrmIuoSvSdn44Fs1n6DGlpZKdK6rM83pFLbVaSUwle8IN+TPmkv7g==", "cpu": [ - "loong64" + "arm64" ], "dev": true, "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "node_modules/@esbuild/android-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.10.tgz", + "integrity": "sha512-CYzrm+hTiY5QICji64aJ/xKdN70IK8XZ6iiyq0tZkd3tfnwwSWTYH1t3m6zyaaBxkuj40kxgMyj1km/NqdjQZA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.10.tgz", + "integrity": "sha512-3HaGIowI+nMZlopqyW6+jxYr01KvNaLB5znXfbyyjuo4lE0VZfvFGcguIJapQeQMS4cX/NEispwOekJt3gr5Dg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.10.tgz", + "integrity": "sha512-J4MJzGchuCRG5n+B4EHpAMoJmBeAE1L3wGYDIN5oWNqX0tEr7VKOzw0ymSwpoeSpdCa030lagGUfnfhS7OvzrQ==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", - "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.10.tgz", + "integrity": "sha512-ZkX40Z7qCbugeK4U5/gbzna/UQkM9d9LNV+Fro8r7HA7sRof5Rwxc46SsqeMvB5ZaR0b1/ITQ/8Y1NmV2F0fXQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10.10.0" + "node": ">=12" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.10.tgz", + "integrity": "sha512-0m0YX1IWSLG9hWh7tZa3kdAugFbZFFx9XrvfpaCMMvrswSTvUZypp0NFKriUurHpBA3xsHVE9Qb/0u2Bbi/otg==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=12" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.10.tgz", + "integrity": "sha512-whRdrrl0X+9D6o5f0sTZtDM9s86Xt4wk1bf7ltx6iQqrIIOH+sre1yjpcCdrVXntQPCNw/G+XqsD4HuxeS+2QA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.10.tgz", + "integrity": "sha512-g1EZJR1/c+MmCgVwpdZdKi4QAJ8DCLP5uTgLWSAVd9wlqk9GMscaNMEViG3aE1wS+cNMzXXgdWiW/VX4J+5nTA==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.10.tgz", + "integrity": "sha512-1vKYCjfv/bEwxngHERp7huYfJ4jJzldfxyfaF7hc3216xiDA62xbXJfRlradiMhGZbdNLj2WA1YwYFzs9IWNPw==", + "cpu": [ + "ia32" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.10.tgz", + "integrity": "sha512-mvwAr75q3Fgc/qz3K6sya3gBmJIYZCgcJ0s7XshpoqIAIBszzfXsqhpRrRdVFAyV1G9VUjj7VopL2HnAS8aHFA==", + "cpu": [ + "loong64" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.10.tgz", + "integrity": "sha512-XilKPgM2u1zR1YuvCsFQWl9Fc35BqSqktooumOY2zj7CSn5czJn279j9TE1JEqSqz88izJo7yE4x3LSf7oxHzg==", + "cpu": [ + "mips64el" + ], "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.10.tgz", + "integrity": "sha512-kM4Rmh9l670SwjlGkIe7pYWezk8uxKHX4Lnn5jBZYBNlWpKMBCVfpAgAJqp5doLobhzF3l64VZVrmGeZ8+uKmQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.10.tgz", + "integrity": "sha512-r1m9ZMNJBtOvYYGQVXKy+WvWd0BPvSxMsVq8Hp4GzdMBQvfZRvRr5TtX/1RdN6Va8JMVQGpxqde3O+e8+khNJQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.10.tgz", + "integrity": "sha512-LsY7QvOLPw9WRJ+fU5pNB3qrSfA00u32ND5JVDrn/xG5hIQo3kvTxSlWFRP0NJ0+n6HmhPGG0Q4jtQsb6PFoyg==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.10.tgz", + "integrity": "sha512-zJUfJLebCYzBdIz/Z9vqwFjIA7iSlLCFvVi7glMgnu2MK7XYigwsonXshy9wP9S7szF+nmwrelNaP3WGanstEg==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.10.tgz", + "integrity": "sha512-lOMkailn4Ok9Vbp/q7uJfgicpDTbZFlXlnKT2DqC8uBijmm5oGtXAJy2ZZVo5hX7IOVXikV9LpCMj2U8cTguWA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.10.tgz", + "integrity": "sha512-/VE0Kx6y7eekqZ+ZLU4AjMlB80ov9tEz4H067Y0STwnGOYL8CsNg4J+cCmBznk1tMpxMoUOf0AbWlb1d2Pkbig==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=12" } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.10.tgz", + "integrity": "sha512-ERNO0838OUm8HfUjjsEs71cLjLMu/xt6bhOlxcJ0/1MG3hNqCmbWaS+w/8nFLa0DDjbwZQuGKVtCUJliLmbVgg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.10.tgz", + "integrity": "sha512-fXv+L+Bw2AeK+XJHwDAQ9m3NRlNemG6Z6ijLwJAAVdu4cyoFbBWbEtyZzDeL+rpG2lWI51cXeMt70HA8g2MqIg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.10.tgz", + "integrity": "sha512-3s+HADrOdCdGOi5lnh5DMQEzgbsFsd4w57L/eLKKjMnN0CN4AIEP0DCP3F3N14xnxh3ruNc32A0Na9zYe1Z/AQ==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@esbuild/win32-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.10.tgz", + "integrity": "sha512-oP+zFUjYNaMNmjTwlFtWep85hvwUu19cZklB3QsBOcZSs6y7hmH4LNCJ7075bsqzYaNvZFXJlAVaQ2ApITDXtw==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1" + "type-fest": "^0.20.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" + "argparse": "^2.0.1" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "dependencies": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.10.0" } }, - "node_modules/@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.4.3.tgz", + "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { + "node_modules/@jest/console/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -2375,7 +2424,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/chalk": { + "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -2391,7 +2440,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/color-convert": { + "node_modules/@jest/console/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -2403,13 +2452,13 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/reporters/node_modules/color-name": { + "node_modules/@jest/console/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@jest/reporters/node_modules/has-flag": { + "node_modules/@jest/console/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -2418,37 +2467,124 @@ "node": ">=8" } }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.4.3.tgz", + "integrity": "sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==", "dev": true, "dependencies": { + "@jest/console": "^29.4.3", + "@jest/reporters": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.4.3", + "jest-config": "^29.4.3", + "jest-haste-map": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-resolve-dependencies": "^29.4.3", + "jest-runner": "^29.4.3", + "jest-runtime": "^29.4.3", + "jest-snapshot": "^29.4.3", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", + "jest-watcher": "^29.4.3", + "micromatch": "^4.0.4", + "pretty-format": "^29.4.3", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -2460,89 +2596,122 @@ "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "node_modules/@jest/environment": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.4.3.tgz", + "integrity": "sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@jest/fake-timers": "^29.4.3", + "@jest/types": "^29.4.3", + "@types/node": "*", + "jest-mock": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", + "node_modules/@jest/expect": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.4.3.tgz", + "integrity": "sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "expect": "^29.4.3", + "jest-snapshot": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", + "node_modules/@jest/expect-utils": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.4.3.tgz", + "integrity": "sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==", "dev": true, "dependencies": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "jest-get-type": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", + "node_modules/@jest/fake-timers": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.4.3.tgz", + "integrity": "sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "slash": "^3.0.0" + "@jest/types": "^29.4.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.4.3", + "jest-mock": "^29.4.3", + "jest-util": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", + "node_modules/@jest/globals": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.4.3.tgz", + "integrity": "sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==", "dev": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", + "@jest/environment": "^29.4.3", + "@jest/expect": "^29.4.3", + "@jest/types": "^29.4.3", + "jest-mock": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.4.3.tgz", + "integrity": "sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", + "@types/node": "*", "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3", + "jest-worker": "^29.4.3", "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { + "node_modules/@jest/reporters/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -2557,7 +2726,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/chalk": { + "node_modules/@jest/reporters/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -2573,7 +2742,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/color-convert": { + "node_modules/@jest/reporters/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -2585,19 +2754,13 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/transform/node_modules/color-name": { + "node_modules/@jest/reporters/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { + "node_modules/@jest/reporters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -2606,25 +2769,213 @@ "node": ">=8" } }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/reporters/node_modules/jest-worker": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@types/node": "*", + "jest-util": "^29.4.3", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", - "dev": true, + "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.4.3.tgz", + "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.4.3", + "@jest/types": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.4.3.tgz", + "integrity": "sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.4.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.4.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.4.3.tgz", + "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.4.3", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.4.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.3.tgz", + "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.4.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -2720,9 +3071,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, "engines": { "node": ">=6.0.0" @@ -2748,19 +3099,19 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/@nodelib/fs.scandir": { @@ -2805,27 +3156,27 @@ "dev": true }, "node_modules/@sinclair/typebox": { - "version": "0.24.44", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.44.tgz", - "integrity": "sha512-ka0W0KN5i6LfrSocduwliMMpqVgohtPFidKdMEOUjoOFCHcOOYkKsPRxfs5f15oPNHTm6ERAm0GV/+/LTKeiWg==", + "version": "0.25.23", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.23.tgz", + "integrity": "sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ==", "dev": true }, "node_modules/@sinonjs/commons": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz", - "integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "^2.0.0" } }, "node_modules/@sinonjs/samsam": { @@ -2839,15 +3190,6 @@ "type-detect": "^4.0.8" } }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, "node_modules/@sinonjs/text-encoding": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", @@ -2864,13 +3206,13 @@ } }, "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" @@ -2955,9 +3297,9 @@ "dev": true }, "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dev": true, "dependencies": { "@types/node": "*" @@ -2988,9 +3330,9 @@ } }, "node_modules/@types/jest": { - "version": "29.2.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.3.tgz", - "integrity": "sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w==", + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz", + "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -3021,21 +3363,21 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-rADY+HtTOA52l9VZWtgQfn4p+UDVM2eDVkMZT1I6syp0YKxW2F9v+0pbRZLsvskhQv/vMb6ZfCay81GHbz5SHg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz", + "integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==", "dev": true }, "node_modules/@types/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", "dev": true }, "node_modules/@types/semver": { @@ -3087,15 +3429,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz", - "integrity": "sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", + "integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.43.0", - "@typescript-eslint/type-utils": "5.43.0", - "@typescript-eslint/utils": "5.43.0", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/type-utils": "5.53.0", + "@typescript-eslint/utils": "5.53.0", "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", @@ -3120,12 +3463,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin-tslint": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-5.43.0.tgz", - "integrity": "sha512-IKy2fJm9PlNflb2ZW5gDJHb0Inte2lVv9+Dq9ZaNP2pUMFdTYKUV7VcFkG6TlFEGjNsoEPZGHQY6JUMRKIYtFA==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-5.53.0.tgz", + "integrity": "sha512-fnnikBucKtXo8lK3rYTaWQiRlWIlN/kskELLbkWf1STuAj6Kzf2odX8vKltb24IoefwyKTQMCs7tQSipqPwcYA==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "5.43.0", + "@typescript-eslint/utils": "5.53.0", "lodash": "^4.17.21" }, "engines": { @@ -3138,14 +3481,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.43.0.tgz", - "integrity": "sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", + "integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.43.0", - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/typescript-estree": "5.43.0", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", "debug": "^4.3.4" }, "engines": { @@ -3165,13 +3508,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz", - "integrity": "sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", + "integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/visitor-keys": "5.43.0" + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3182,13 +3525,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz", - "integrity": "sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", + "integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.43.0", - "@typescript-eslint/utils": "5.43.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "@typescript-eslint/utils": "5.53.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3209,9 +3552,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz", - "integrity": "sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", + "integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3222,13 +3565,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz", - "integrity": "sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", + "integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/visitor-keys": "5.43.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3249,16 +3592,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", - "integrity": "sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", + "integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.43.0", - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/typescript-estree": "5.43.0", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -3275,12 +3618,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz", - "integrity": "sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", + "integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/types": "5.53.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3438,34 +3781,42 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", "dev": true, - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -3687,15 +4038,15 @@ "dev": true }, "node_modules/array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", "is-string": "^1.0.7" }, "engines": { @@ -3715,14 +4066,15 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -3732,14 +4084,14 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -3749,6 +4101,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, "node_modules/arraybuffer-loader": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/arraybuffer-loader/-/arraybuffer-loader-1.0.8.tgz", @@ -3776,16 +4141,28 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.4.3.tgz", + "integrity": "sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==", "dev": true, "dependencies": { - "@jest/transform": "^29.3.1", + "@jest/transform": "^29.4.3", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", + "babel-preset-jest": "^29.4.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -3868,9 +4245,9 @@ } }, "node_modules/babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "dependencies": { "find-cache-dir": "^3.3.2", @@ -3901,9 +4278,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz", + "integrity": "sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -3987,12 +4364,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz", + "integrity": "sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.2.0", + "babel-plugin-jest-hoist": "^29.4.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -4579,9 +4956,9 @@ "dev": true }, "node_modules/core-js": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", - "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz", + "integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==", "dev": true, "hasInstallScript": true, "funding": { @@ -4770,9 +5147,9 @@ "dev": true }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4847,9 +5224,9 @@ } }, "node_modules/diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5053,501 +5430,205 @@ }, "node_modules/engine.io/node_modules/ws": { "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.14.tgz", - "integrity": "sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.14", - "@esbuild/linux-loong64": "0.15.14", - "esbuild-android-64": "0.15.14", - "esbuild-android-arm64": "0.15.14", - "esbuild-darwin-64": "0.15.14", - "esbuild-darwin-arm64": "0.15.14", - "esbuild-freebsd-64": "0.15.14", - "esbuild-freebsd-arm64": "0.15.14", - "esbuild-linux-32": "0.15.14", - "esbuild-linux-64": "0.15.14", - "esbuild-linux-arm": "0.15.14", - "esbuild-linux-arm64": "0.15.14", - "esbuild-linux-mips64le": "0.15.14", - "esbuild-linux-ppc64le": "0.15.14", - "esbuild-linux-riscv64": "0.15.14", - "esbuild-linux-s390x": "0.15.14", - "esbuild-netbsd-64": "0.15.14", - "esbuild-openbsd-64": "0.15.14", - "esbuild-sunos-64": "0.15.14", - "esbuild-windows-32": "0.15.14", - "esbuild-windows-64": "0.15.14", - "esbuild-windows-arm64": "0.15.14" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz", - "integrity": "sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz", - "integrity": "sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz", - "integrity": "sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz", - "integrity": "sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz", - "integrity": "sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz", - "integrity": "sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz", - "integrity": "sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz", - "integrity": "sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz", - "integrity": "sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz", - "integrity": "sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz", - "integrity": "sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==", - "cpu": [ - "mips64el" - ], + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz", - "integrity": "sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==", - "cpu": [ - "ppc64" - ], + "node_modules/enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, "engines": { - "node": ">=12" + "node": ">=10.13.0" } }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz", - "integrity": "sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==", - "cpu": [ - "riscv64" - ], + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz", - "integrity": "sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==", - "cpu": [ - "s390x" - ], + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "bin": { + "envinfo": "dist/cli.js" + }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz", - "integrity": "sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==", - "cpu": [ - "x64" - ], + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" + "dependencies": { + "is-arrayish": "^0.2.1" } }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz", - "integrity": "sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==", - "cpu": [ - "x64" - ], + "node_modules/es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", "dev": true, - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz", - "integrity": "sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==", - "cpu": [ - "x64" - ], + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", "dev": true, - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/esbuild-windows-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz", - "integrity": "sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==", - "cpu": [ - "ia32" - ], + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "has": "^1.0.3" } }, - "node_modules/esbuild-windows-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz", - "integrity": "sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==", - "cpu": [ - "x64" - ], + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz", - "integrity": "sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==", - "cpu": [ - "arm64" - ], + "node_modules/esbuild": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.10.tgz", + "integrity": "sha512-n7V3v29IuZy5qgxx25TKJrEm0FHghAlS6QweUcyIgh/U0zYmQcvogWROitrTyZId1mHSkuhhuyEXtI9OXioq7A==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.10", + "@esbuild/android-arm64": "0.17.10", + "@esbuild/android-x64": "0.17.10", + "@esbuild/darwin-arm64": "0.17.10", + "@esbuild/darwin-x64": "0.17.10", + "@esbuild/freebsd-arm64": "0.17.10", + "@esbuild/freebsd-x64": "0.17.10", + "@esbuild/linux-arm": "0.17.10", + "@esbuild/linux-arm64": "0.17.10", + "@esbuild/linux-ia32": "0.17.10", + "@esbuild/linux-loong64": "0.17.10", + "@esbuild/linux-mips64el": "0.17.10", + "@esbuild/linux-ppc64": "0.17.10", + "@esbuild/linux-riscv64": "0.17.10", + "@esbuild/linux-s390x": "0.17.10", + "@esbuild/linux-x64": "0.17.10", + "@esbuild/netbsd-x64": "0.17.10", + "@esbuild/openbsd-x64": "0.17.10", + "@esbuild/sunos-x64": "0.17.10", + "@esbuild/win32-arm64": "0.17.10", + "@esbuild/win32-ia32": "0.17.10", + "@esbuild/win32-x64": "0.17.10" } }, "node_modules/escalade": { @@ -5657,13 +5738,13 @@ } }, "node_modules/eslint": { - "version": "8.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.27.0.tgz", - "integrity": "sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", + "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -5682,7 +5763,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -5713,13 +5794,14 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", "dev": true, "dependencies": { "debug": "^3.2.7", - "resolve": "^1.20.0" + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -5731,113 +5813,81 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dev": true, "dependencies": { - "debug": "^3.2.7", - "find-up": "^2.1.0" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" + "bin": { + "resolve": "bin/resolve" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-module-utils/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", "dev": true, "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "debug": "^3.2.7" }, "engines": { "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/eslint-module-utils/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" + "ms": "^2.1.1" } }, - "node_modules/eslint-module-utils/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "node_modules/eslint-plugin-ban": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-ban/-/eslint-plugin-ban-1.6.0.tgz", + "integrity": "sha512-gZptoV+SFHOHO57/5lmPvizMvSXrjFatP9qlVQf3meL/WHo9TxSoERygrMlESl19CPh95U86asTxohT8OprwDw==", "dev": true, "dependencies": { - "p-limit": "^1.1.0" + "requireindex": "~1.2.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, "node_modules/eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", "dev": true, "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", "has": "^1.0.3", - "is-core-module": "^2.8.1", + "is-core-module": "^2.11.0", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", "tsconfig-paths": "^3.14.1" }, "engines": { @@ -5846,14 +5896,14 @@ "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "node_modules/eslint-plugin-import/node_modules/doctrine": { @@ -5868,19 +5918,39 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "node_modules/eslint-plugin-import/node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } }, "node_modules/eslint-plugin-jsdoc": { - "version": "39.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.2.tgz", - "integrity": "sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A==", + "version": "40.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.0.0.tgz", + "integrity": "sha512-LOPyIu1vAVvGPkye3ci0moj0iNf3f8bmin6do2DYDj+77NRXWnkmhKRy8swWsatUs3mB5jYPWPUsFg9pyfEiyA==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.36.0", + "@es-joy/jsdoccomment": "~0.36.1", "comment-parser": "1.3.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", @@ -5908,25 +5978,26 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.31.10", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", - "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", "dev": true, "dependencies": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", "doctrine": "^2.1.0", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", + "resolve": "^2.0.0-next.4", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" + "string.prototype.matchall": "^4.0.8" }, "engines": { "node": ">=4" @@ -5957,13 +6028,17 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", "dev": true, "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6139,9 +6214,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -6253,9 +6328,9 @@ } }, "node_modules/espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", "dev": true, "dependencies": { "acorn": "^8.8.0", @@ -6399,16 +6474,16 @@ } }, "node_modules/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.4.3.tgz", + "integrity": "sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" + "@jest/expect-utils": "^29.4.3", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -6703,6 +6778,21 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flatted": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", @@ -6729,6 +6819,15 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -6865,14 +6964,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6962,6 +7061,21 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -6982,6 +7096,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -7048,6 +7174,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -7085,9 +7223,9 @@ } }, "node_modules/highlight.js": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", - "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==", "dev": true, "engines": { "node": ">=12.0.0" @@ -7305,12 +7443,12 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.1.3", "has": "^1.0.3", "side-channel": "^1.0.4" }, @@ -7319,12 +7457,12 @@ } }, "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/ipaddr.js": { @@ -7336,6 +7474,20 @@ "node": ">= 0.10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -7383,9 +7535,9 @@ } }, "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "engines": { "node": ">= 0.4" @@ -7395,9 +7547,9 @@ } }, "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -7614,6 +7766,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -7780,15 +7951,15 @@ } }, "node_modules/jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.4.3.tgz", + "integrity": "sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==", "dev": true, "dependencies": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/core": "^29.4.3", + "@jest/types": "^29.4.3", "import-local": "^3.0.2", - "jest-cli": "^29.3.1" + "jest-cli": "^29.4.3" }, "bin": { "jest": "bin/jest.js" @@ -7806,9 +7977,9 @@ } }, "node_modules/jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.4.3.tgz", + "integrity": "sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==", "dev": true, "dependencies": { "execa": "^5.0.0", @@ -7834,28 +8005,28 @@ } }, "node_modules/jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.4.3.tgz", + "integrity": "sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==", "dev": true, "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/environment": "^29.4.3", + "@jest/expect": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", + "jest-each": "^29.4.3", + "jest-matcher-utils": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-runtime": "^29.4.3", + "jest-snapshot": "^29.4.3", + "jest-util": "^29.4.3", "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -7949,21 +8120,21 @@ } }, "node_modules/jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.4.3.tgz", + "integrity": "sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==", "dev": true, "dependencies": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/core": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/types": "^29.4.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", + "jest-config": "^29.4.3", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -8067,9 +8238,9 @@ } }, "node_modules/jest-cli/node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -8094,31 +8265,31 @@ } }, "node_modules/jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.4.3.tgz", + "integrity": "sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", + "@jest/test-sequencer": "^29.4.3", + "@jest/types": "^29.4.3", + "babel-jest": "^29.4.3", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", + "jest-circus": "^29.4.3", + "jest-environment-node": "^29.4.3", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-runner": "^29.4.3", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -8209,15 +8380,15 @@ } }, "node_modules/jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.3.tgz", + "integrity": "sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8294,9 +8465,9 @@ } }, "node_modules/jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -8306,16 +8477,16 @@ } }, "node_modules/jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.4.3.tgz", + "integrity": "sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==", "dev": true, "dependencies": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" + "jest-get-type": "^29.4.3", + "jest-util": "^29.4.3", + "pretty-format": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8392,18 +8563,18 @@ } }, "node_modules/jest-environment-jsdom": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz", - "integrity": "sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.4.3.tgz", + "integrity": "sha512-rFjf8JXrw3OjUzzmSE5l0XjMj0/MSVEUMCSXBGPDkfwb1T03HZI7iJSL0cGctZApPSyJxbjyKDVxkZuyhHkuTw==", "dev": true, "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/environment": "^29.4.3", + "@jest/fake-timers": "^29.4.3", + "@jest/types": "^29.4.3", "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1", + "jest-mock": "^29.4.3", + "jest-util": "^29.4.3", "jsdom": "^20.0.0" }, "engines": { @@ -8419,46 +8590,46 @@ } }, "node_modules/jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.4.3.tgz", + "integrity": "sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==", "dev": true, "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/environment": "^29.4.3", + "@jest/fake-timers": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" + "jest-mock": "^29.4.3", + "jest-util": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.4.3.tgz", + "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", "dev": true, "dependencies": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.4.3", + "jest-worker": "^29.4.3", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -8479,13 +8650,13 @@ } }, "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -8509,28 +8680,28 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz", + "integrity": "sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==", "dev": true, "dependencies": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" + "jest-get-type": "^29.4.3", + "pretty-format": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz", + "integrity": "sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" + "jest-diff": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8607,18 +8778,18 @@ } }, "node_modules/jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.4.3.tgz", + "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -8697,14 +8868,14 @@ } }, "node_modules/jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.4.3.tgz", + "integrity": "sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==", "dev": true, "dependencies": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/node": "*", - "jest-util": "^29.3.1" + "jest-util": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8728,28 +8899,28 @@ } }, "node_modules/jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.4.3.tgz", + "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", + "jest-haste-map": "^29.4.3", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", + "resolve.exports": "^2.0.0", "slash": "^3.0.0" }, "engines": { @@ -8757,13 +8928,13 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz", + "integrity": "sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==", "dev": true, "dependencies": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8840,30 +9011,30 @@ } }, "node_modules/jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.4.3.tgz", + "integrity": "sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==", "dev": true, "dependencies": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/console": "^29.4.3", + "@jest/environment": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.4.3", + "jest-haste-map": "^29.4.3", + "jest-leak-detector": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-runtime": "^29.4.3", + "jest-util": "^29.4.3", + "jest-watcher": "^29.4.3", + "jest-worker": "^29.4.3", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -8930,13 +9101,13 @@ } }, "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -8987,31 +9158,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.4.3.tgz", + "integrity": "sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.4.3", + "@jest/fake-timers": "^29.4.3", + "@jest/globals": "^29.4.3", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", + "jest-haste-map": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-mock": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-snapshot": "^29.4.3", + "jest-util": "^29.4.3", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -9090,9 +9261,9 @@ } }, "node_modules/jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.4.3.tgz", + "integrity": "sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -9101,23 +9272,23 @@ "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/expect-utils": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/babel__traverse": "^7.0.6", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.3.1", + "expect": "^29.4.3", "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", + "jest-diff": "^29.4.3", + "jest-get-type": "^29.4.3", + "jest-haste-map": "^29.4.3", + "jest-matcher-utils": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3", "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "semver": "^7.3.5" }, "engines": { @@ -9195,12 +9366,12 @@ } }, "node_modules/jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.3.tgz", + "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", "dev": true, "dependencies": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -9282,17 +9453,17 @@ } }, "node_modules/jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.4.3.tgz", + "integrity": "sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==", "dev": true, "dependencies": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", + "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^29.3.1" + "pretty-format": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -9369,18 +9540,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.4.3.tgz", + "integrity": "sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==", "dev": true, "dependencies": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/test-result": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "string-length": "^4.0.1" }, "engines": { @@ -9626,9 +9797,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -9793,6 +9964,21 @@ "node": ">=4.0.0" } }, + "node_modules/karma/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -9872,9 +10058,9 @@ } }, "node_modules/loader-utils/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -10178,9 +10364,9 @@ } }, "node_modules/mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -10593,15 +10779,6 @@ "path-to-regexp": "^1.7.0" } }, - "node_modules/nise/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, "node_modules/nise/node_modules/@sinonjs/fake-timers": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", @@ -10708,43 +10885,46 @@ } }, "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" @@ -10754,27 +10934,27 @@ } }, "node_modules/object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", "dev": true, "dependencies": { "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" @@ -11134,12 +11314,12 @@ } }, "node_modules/pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.3.tgz", + "integrity": "sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==", "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -11385,15 +11565,15 @@ } }, "node_modules/rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "dependencies": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/regenerate": { @@ -11415,9 +11595,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==", + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, "node_modules/regenerator-transform": { @@ -11520,6 +11700,15 @@ "node": ">=0.10.0" } }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -11574,9 +11763,9 @@ } }, "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.0.tgz", + "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", "dev": true, "engines": { "node": ">=10" @@ -11599,15 +11788,15 @@ "dev": true }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz", + "integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==", "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11636,25 +11825,26 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -11900,13 +12090,13 @@ "dev": true }, "node_modules/sinon": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", - "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.1.tgz", + "integrity": "sha512-PZXKc08f/wcA/BMRGBze2Wmw50CWPiAH3E21EOi4B49vJ616vW4DQh4fQrqsYox2aNR/N3kCqLuB0PwwOucQrg==", "dev": true, "dependencies": { "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/fake-timers": "10.0.2", "@sinonjs/samsam": "^7.0.1", "diff": "^5.0.0", "nise": "^5.1.2", @@ -11917,15 +12107,6 @@ "url": "https://opencollective.com/sinon" } }, - "node_modules/sinon/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, "node_modules/sinon/node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -12162,18 +12343,18 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", + "regexp.prototype.flags": "^1.4.3", "side-channel": "^1.0.4" }, "funding": { @@ -12181,28 +12362,28 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12398,6 +12579,21 @@ "node": ">=8.17.0" } }, + "node_modules/tmp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -12471,15 +12667,15 @@ } }, "node_modules/ts-jest": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", - "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", + "version": "29.0.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", + "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", "dev": true, "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", - "json5": "^2.2.1", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", "semver": "7.x", @@ -12523,9 +12719,9 @@ } }, "node_modules/ts-loader": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz", - "integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -12618,9 +12814,9 @@ } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -12750,10 +12946,24 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -12764,9 +12974,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true, "funding": [ { @@ -12928,9 +13138,9 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -13050,11 +13260,12 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz", + "integrity": "sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg==", "dev": true, "dependencies": { + "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", "chalk": "^4.1.0", @@ -13146,44 +13357,42 @@ } }, "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^9.4.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -13193,12 +13402,12 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "engines": { - "node": ">= 10" + "node": "^12.20.0 || >=14" } }, "node_modules/webpack-merge": { @@ -13327,6 +13536,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -13531,12 +13760,25 @@ }, "dependencies": { "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.0" + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } } }, "@babel/code-frame": { @@ -13549,31 +13791,31 @@ } }, "@babel/compat-data": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", - "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==", + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", + "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", "dev": true }, "@babel/core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz", - "integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", + "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", "dev": true, "requires": { - "@ampproject/remapping": "^2.1.0", + "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.2", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.1", - "@babel/parser": "^7.20.2", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2", + "@babel/generator": "^7.21.0", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.0", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.0", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" }, "dependencies": { @@ -13586,13 +13828,14 @@ } }, "@babel/generator": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.3.tgz", - "integrity": "sha512-Wl5ilw2UD1+ZYprHVprxHZJCFeBWlzZYOovE4SDYLZnqCOD11j+0QzNeEWKLLTWM7nixrZEh7vNIyb76MyJg3A==", + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", + "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", "dev": true, "requires": { - "@babel/types": "^7.20.2", + "@babel/types": "^7.21.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" } }, @@ -13616,22 +13859,38 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.0", + "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", "semver": "^6.3.0" }, "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -13698,13 +13957,13 @@ } }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" } }, "@babel/helper-hoist-variables": { @@ -13735,9 +13994,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", - "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.0.tgz", + "integrity": "sha512-eD/JQ21IG2i1FraJnTMbUarAUkA7G988ofehG5MDCRXaUU91rEBJuCeSoou2Sk1y4RbLYXzqEg1QLwEmRU4qcQ==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.18.9", @@ -13745,9 +14004,9 @@ "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" } }, "@babel/helper-optimise-call-expression": { @@ -13848,14 +14107,14 @@ } }, "@babel/helpers": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", - "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.0" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" } }, "@babel/highlight": { @@ -13870,9 +14129,9 @@ } }, "@babel/parser": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", - "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz", + "integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -14516,13 +14775,13 @@ } }, "@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz", + "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-plugin-utils": "^7.20.2", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -14730,38 +14989,38 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", "dev": true, "requires": { "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" } }, "@babel/traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", - "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.0.tgz", + "integrity": "sha512-Xdt2P1H4LKTO8ApPfnO1KmzYMFpp7D/EinoXzLYN/cHcBNrVCAkAtGUcXnHXrl/VGktureU6fkQrHSBE2URfoA==", "dev": true, "requires": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.1", + "@babel/generator": "^7.21.0", "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", + "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.1", - "@babel/types": "^7.20.0", + "@babel/parser": "^7.21.0", + "@babel/types": "^7.21.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", - "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.0.tgz", + "integrity": "sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.19.4", @@ -14782,15 +15041,15 @@ "dev": true }, "@discoveryjs/json-ext": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", - "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, "@es-joy/jsdoccomment": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.0.tgz", - "integrity": "sha512-u0XZyvUF6Urb2cSivSXA8qXIpT/CxkHcdtZKoWusAzgzmsTWpg0F2FpWXsolHmMUyVY3dLWaoy+0ccJ5uf2QjA==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", + "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", "dev": true, "requires": { "comment-parser": "1.3.1", @@ -14799,29 +15058,169 @@ } }, "@esbuild/android-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.14.tgz", - "integrity": "sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==", + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.10.tgz", + "integrity": "sha512-7YEBfZ5lSem9Tqpsz+tjbdsEshlO9j/REJrfv4DXgKTt1+/MHqGwbtlyxQuaSlMeUZLxUKBaX8wdzlTfHkmnLw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.10.tgz", + "integrity": "sha512-ht1P9CmvrPF5yKDtyC+z43RczVs4rrHpRqrmIuoSvSdn44Fs1n6DGlpZKdK6rM83pFLbVaSUwle8IN+TPmkv7g==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.10.tgz", + "integrity": "sha512-CYzrm+hTiY5QICji64aJ/xKdN70IK8XZ6iiyq0tZkd3tfnwwSWTYH1t3m6zyaaBxkuj40kxgMyj1km/NqdjQZA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.10.tgz", + "integrity": "sha512-3HaGIowI+nMZlopqyW6+jxYr01KvNaLB5znXfbyyjuo4lE0VZfvFGcguIJapQeQMS4cX/NEispwOekJt3gr5Dg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.10.tgz", + "integrity": "sha512-J4MJzGchuCRG5n+B4EHpAMoJmBeAE1L3wGYDIN5oWNqX0tEr7VKOzw0ymSwpoeSpdCa030lagGUfnfhS7OvzrQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.10.tgz", + "integrity": "sha512-ZkX40Z7qCbugeK4U5/gbzna/UQkM9d9LNV+Fro8r7HA7sRof5Rwxc46SsqeMvB5ZaR0b1/ITQ/8Y1NmV2F0fXQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.10.tgz", + "integrity": "sha512-0m0YX1IWSLG9hWh7tZa3kdAugFbZFFx9XrvfpaCMMvrswSTvUZypp0NFKriUurHpBA3xsHVE9Qb/0u2Bbi/otg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.10.tgz", + "integrity": "sha512-whRdrrl0X+9D6o5f0sTZtDM9s86Xt4wk1bf7ltx6iQqrIIOH+sre1yjpcCdrVXntQPCNw/G+XqsD4HuxeS+2QA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.10.tgz", + "integrity": "sha512-g1EZJR1/c+MmCgVwpdZdKi4QAJ8DCLP5uTgLWSAVd9wlqk9GMscaNMEViG3aE1wS+cNMzXXgdWiW/VX4J+5nTA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.10.tgz", + "integrity": "sha512-1vKYCjfv/bEwxngHERp7huYfJ4jJzldfxyfaF7hc3216xiDA62xbXJfRlradiMhGZbdNLj2WA1YwYFzs9IWNPw==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz", - "integrity": "sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==", + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.10.tgz", + "integrity": "sha512-mvwAr75q3Fgc/qz3K6sya3gBmJIYZCgcJ0s7XshpoqIAIBszzfXsqhpRrRdVFAyV1G9VUjj7VopL2HnAS8aHFA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.10.tgz", + "integrity": "sha512-XilKPgM2u1zR1YuvCsFQWl9Fc35BqSqktooumOY2zj7CSn5czJn279j9TE1JEqSqz88izJo7yE4x3LSf7oxHzg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.10.tgz", + "integrity": "sha512-kM4Rmh9l670SwjlGkIe7pYWezk8uxKHX4Lnn5jBZYBNlWpKMBCVfpAgAJqp5doLobhzF3l64VZVrmGeZ8+uKmQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.10.tgz", + "integrity": "sha512-r1m9ZMNJBtOvYYGQVXKy+WvWd0BPvSxMsVq8Hp4GzdMBQvfZRvRr5TtX/1RdN6Va8JMVQGpxqde3O+e8+khNJQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.10.tgz", + "integrity": "sha512-LsY7QvOLPw9WRJ+fU5pNB3qrSfA00u32ND5JVDrn/xG5hIQo3kvTxSlWFRP0NJ0+n6HmhPGG0Q4jtQsb6PFoyg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.10.tgz", + "integrity": "sha512-zJUfJLebCYzBdIz/Z9vqwFjIA7iSlLCFvVi7glMgnu2MK7XYigwsonXshy9wP9S7szF+nmwrelNaP3WGanstEg==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.10.tgz", + "integrity": "sha512-lOMkailn4Ok9Vbp/q7uJfgicpDTbZFlXlnKT2DqC8uBijmm5oGtXAJy2ZZVo5hX7IOVXikV9LpCMj2U8cTguWA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.10.tgz", + "integrity": "sha512-/VE0Kx6y7eekqZ+ZLU4AjMlB80ov9tEz4H067Y0STwnGOYL8CsNg4J+cCmBznk1tMpxMoUOf0AbWlb1d2Pkbig==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.10.tgz", + "integrity": "sha512-ERNO0838OUm8HfUjjsEs71cLjLMu/xt6bhOlxcJ0/1MG3hNqCmbWaS+w/8nFLa0DDjbwZQuGKVtCUJliLmbVgg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.10.tgz", + "integrity": "sha512-fXv+L+Bw2AeK+XJHwDAQ9m3NRlNemG6Z6ijLwJAAVdu4cyoFbBWbEtyZzDeL+rpG2lWI51cXeMt70HA8g2MqIg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.10.tgz", + "integrity": "sha512-3s+HADrOdCdGOi5lnh5DMQEzgbsFsd4w57L/eLKKjMnN0CN4AIEP0DCP3F3N14xnxh3ruNc32A0Na9zYe1Z/AQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.10.tgz", + "integrity": "sha512-oP+zFUjYNaMNmjTwlFtWep85hvwUu19cZklB3QsBOcZSs6y7hmH4LNCJ7075bsqzYaNvZFXJlAVaQ2ApITDXtw==", "dev": true, "optional": true }, "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -14836,9 +15235,9 @@ "dev": true }, "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -14862,14 +15261,14 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", - "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" } }, "@humanwhocodes/module-importer": { @@ -14918,16 +15317,16 @@ "dev": true }, "@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.4.3.tgz", + "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", "dev": true, "requires": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3", "slash": "^3.0.0" }, "dependencies": { @@ -14983,37 +15382,37 @@ } }, "@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.4.3.tgz", + "integrity": "sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==", "dev": true, "requires": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/console": "^29.4.3", + "@jest/reporters": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", + "jest-changed-files": "^29.4.3", + "jest-config": "^29.4.3", + "jest-haste-map": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-resolve-dependencies": "^29.4.3", + "jest-runner": "^29.4.3", + "jest-runtime": "^29.4.3", + "jest-snapshot": "^29.4.3", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", + "jest-watcher": "^29.4.3", "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -15070,73 +15469,73 @@ } }, "@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.4.3.tgz", + "integrity": "sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==", "dev": true, "requires": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/fake-timers": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", - "jest-mock": "^29.3.1" + "jest-mock": "^29.4.3" } }, "@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.4.3.tgz", + "integrity": "sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==", "dev": true, "requires": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" + "expect": "^29.4.3", + "jest-snapshot": "^29.4.3" } }, "@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.4.3.tgz", + "integrity": "sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==", "dev": true, "requires": { - "jest-get-type": "^29.2.0" + "jest-get-type": "^29.4.3" } }, "@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.4.3.tgz", + "integrity": "sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==", "dev": true, "requires": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", + "@jest/types": "^29.4.3", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" + "jest-message-util": "^29.4.3", + "jest-mock": "^29.4.3", + "jest-util": "^29.4.3" } }, "@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.4.3.tgz", + "integrity": "sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==", "dev": true, "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" + "@jest/environment": "^29.4.3", + "@jest/expect": "^29.4.3", + "@jest/types": "^29.4.3", + "jest-mock": "^29.4.3" } }, "@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.4.3.tgz", + "integrity": "sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/console": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@jridgewell/trace-mapping": "^0.3.15", "@types/node": "*", "chalk": "^4.0.0", @@ -15149,9 +15548,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3", + "jest-worker": "^29.4.3", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -15199,13 +15598,13 @@ "dev": true }, "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -15233,18 +15632,18 @@ } }, "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", "dev": true, "requires": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.25.16" } }, "@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.15", @@ -15253,50 +15652,50 @@ } }, "@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.4.3.tgz", + "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", "dev": true, "requires": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/console": "^29.4.3", + "@jest/types": "^29.4.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.4.3.tgz", + "integrity": "sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==", "dev": true, "requires": { - "@jest/test-result": "^29.3.1", + "@jest/test-result": "^29.4.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", + "jest-haste-map": "^29.4.3", "slash": "^3.0.0" } }, "@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.4.3.tgz", + "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", "dev": true, "requires": { "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@jridgewell/trace-mapping": "^0.3.15", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", + "jest-haste-map": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.4.3", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" + "write-file-atomic": "^4.0.2" }, "dependencies": { "ansi-styles": { @@ -15357,12 +15756,12 @@ } }, "@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.3.tgz", + "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", "dev": true, "requires": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.4.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -15433,9 +15832,9 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true }, "@jridgewell/set-array": { @@ -15455,19 +15854,19 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "@nodelib/fs.scandir": { @@ -15503,27 +15902,27 @@ "dev": true }, "@sinclair/typebox": { - "version": "0.24.44", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.44.tgz", - "integrity": "sha512-ka0W0KN5i6LfrSocduwliMMpqVgohtPFidKdMEOUjoOFCHcOOYkKsPRxfs5f15oPNHTm6ERAm0GV/+/LTKeiWg==", + "version": "0.25.23", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.23.tgz", + "integrity": "sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ==", "dev": true }, "@sinonjs/commons": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz", - "integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "^2.0.0" } }, "@sinonjs/samsam": { @@ -15535,17 +15934,6 @@ "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", "type-detect": "^4.0.8" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } } }, "@sinonjs/text-encoding": { @@ -15561,13 +15949,13 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", "dev": true, "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" @@ -15652,9 +16040,9 @@ "dev": true }, "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dev": true, "requires": { "@types/node": "*" @@ -15685,9 +16073,9 @@ } }, "@types/jest": { - "version": "29.2.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.3.tgz", - "integrity": "sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w==", + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz", + "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", "dev": true, "requires": { "expect": "^29.0.0", @@ -15718,21 +16106,21 @@ "dev": true }, "@types/mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-rADY+HtTOA52l9VZWtgQfn4p+UDVM2eDVkMZT1I6syp0YKxW2F9v+0pbRZLsvskhQv/vMb6ZfCay81GHbz5SHg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz", + "integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==", "dev": true }, "@types/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", "dev": true }, "@types/semver": { @@ -15784,15 +16172,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz", - "integrity": "sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", + "integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.43.0", - "@typescript-eslint/type-utils": "5.43.0", - "@typescript-eslint/utils": "5.43.0", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/type-utils": "5.53.0", + "@typescript-eslint/utils": "5.53.0", "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", @@ -15801,63 +16190,63 @@ } }, "@typescript-eslint/eslint-plugin-tslint": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-5.43.0.tgz", - "integrity": "sha512-IKy2fJm9PlNflb2ZW5gDJHb0Inte2lVv9+Dq9ZaNP2pUMFdTYKUV7VcFkG6TlFEGjNsoEPZGHQY6JUMRKIYtFA==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-5.53.0.tgz", + "integrity": "sha512-fnnikBucKtXo8lK3rYTaWQiRlWIlN/kskELLbkWf1STuAj6Kzf2odX8vKltb24IoefwyKTQMCs7tQSipqPwcYA==", "dev": true, "requires": { - "@typescript-eslint/utils": "5.43.0", + "@typescript-eslint/utils": "5.53.0", "lodash": "^4.17.21" } }, "@typescript-eslint/parser": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.43.0.tgz", - "integrity": "sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", + "integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.43.0", - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/typescript-estree": "5.43.0", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz", - "integrity": "sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", + "integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", "dev": true, "requires": { - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/visitor-keys": "5.43.0" + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0" } }, "@typescript-eslint/type-utils": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz", - "integrity": "sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", + "integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.43.0", - "@typescript-eslint/utils": "5.43.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "@typescript-eslint/utils": "5.53.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz", - "integrity": "sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", + "integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz", - "integrity": "sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", + "integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", "dev": true, "requires": { - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/visitor-keys": "5.43.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -15866,28 +16255,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", - "integrity": "sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", + "integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.43.0", - "@typescript-eslint/types": "5.43.0", - "@typescript-eslint/typescript-estree": "5.43.0", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz", - "integrity": "sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", + "integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", "dev": true, "requires": { - "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/types": "5.53.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -16038,25 +16427,23 @@ } }, "@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "requires": {} }, "@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true, "requires": {} }, @@ -16229,15 +16616,15 @@ "dev": true }, "array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", "is-string": "^1.0.7" } }, @@ -16248,28 +16635,42 @@ "dev": true }, "array.prototype.flat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" } }, "array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", "es-shim-unscopables": "^1.0.0" } }, + "array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, "arraybuffer-loader": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/arraybuffer-loader/-/arraybuffer-loader-1.0.8.tgz", @@ -16291,16 +16692,22 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, "babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.4.3.tgz", + "integrity": "sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==", "dev": true, "requires": { - "@jest/transform": "^29.3.1", + "@jest/transform": "^29.4.3", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", + "babel-preset-jest": "^29.4.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -16358,9 +16765,9 @@ } }, "babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "requires": { "find-cache-dir": "^3.3.2", @@ -16381,9 +16788,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz", + "integrity": "sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -16451,12 +16858,12 @@ } }, "babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz", + "integrity": "sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^29.2.0", + "babel-plugin-jest-hoist": "^29.4.3", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -16909,9 +17316,9 @@ "dev": true }, "core-js": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", - "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz", + "integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==", "dev": true }, "core-js-compat": { @@ -17052,9 +17459,9 @@ "dev": true }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", "dev": true }, "define-properties": { @@ -17104,9 +17511,9 @@ "dev": true }, "diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true }, "dir-glob": { @@ -17303,232 +17710,113 @@ "is-arrayish": "^0.2.1" } }, - "es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "esbuild": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.14.tgz", - "integrity": "sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.15.14", - "@esbuild/linux-loong64": "0.15.14", - "esbuild-android-64": "0.15.14", - "esbuild-android-arm64": "0.15.14", - "esbuild-darwin-64": "0.15.14", - "esbuild-darwin-arm64": "0.15.14", - "esbuild-freebsd-64": "0.15.14", - "esbuild-freebsd-arm64": "0.15.14", - "esbuild-linux-32": "0.15.14", - "esbuild-linux-64": "0.15.14", - "esbuild-linux-arm": "0.15.14", - "esbuild-linux-arm64": "0.15.14", - "esbuild-linux-mips64le": "0.15.14", - "esbuild-linux-ppc64le": "0.15.14", - "esbuild-linux-riscv64": "0.15.14", - "esbuild-linux-s390x": "0.15.14", - "esbuild-netbsd-64": "0.15.14", - "esbuild-openbsd-64": "0.15.14", - "esbuild-sunos-64": "0.15.14", - "esbuild-windows-32": "0.15.14", - "esbuild-windows-64": "0.15.14", - "esbuild-windows-arm64": "0.15.14" - } - }, - "esbuild-android-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz", - "integrity": "sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz", - "integrity": "sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz", - "integrity": "sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz", - "integrity": "sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz", - "integrity": "sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz", - "integrity": "sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz", - "integrity": "sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz", - "integrity": "sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz", - "integrity": "sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz", - "integrity": "sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz", - "integrity": "sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz", - "integrity": "sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz", - "integrity": "sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz", - "integrity": "sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz", - "integrity": "sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz", - "integrity": "sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==", + "es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", "dev": true, - "optional": true + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } }, - "esbuild-sunos-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz", - "integrity": "sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==", - "dev": true, - "optional": true + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true }, - "esbuild-windows-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz", - "integrity": "sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==", + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", "dev": true, - "optional": true + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } }, - "esbuild-windows-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz", - "integrity": "sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==", + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, - "optional": true + "requires": { + "has": "^1.0.3" + } }, - "esbuild-windows-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz", - "integrity": "sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==", + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, - "optional": true + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.10.tgz", + "integrity": "sha512-n7V3v29IuZy5qgxx25TKJrEm0FHghAlS6QweUcyIgh/U0zYmQcvogWROitrTyZId1mHSkuhhuyEXtI9OXioq7A==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.10", + "@esbuild/android-arm64": "0.17.10", + "@esbuild/android-x64": "0.17.10", + "@esbuild/darwin-arm64": "0.17.10", + "@esbuild/darwin-x64": "0.17.10", + "@esbuild/freebsd-arm64": "0.17.10", + "@esbuild/freebsd-x64": "0.17.10", + "@esbuild/linux-arm": "0.17.10", + "@esbuild/linux-arm64": "0.17.10", + "@esbuild/linux-ia32": "0.17.10", + "@esbuild/linux-loong64": "0.17.10", + "@esbuild/linux-mips64el": "0.17.10", + "@esbuild/linux-ppc64": "0.17.10", + "@esbuild/linux-riscv64": "0.17.10", + "@esbuild/linux-s390x": "0.17.10", + "@esbuild/linux-x64": "0.17.10", + "@esbuild/netbsd-x64": "0.17.10", + "@esbuild/openbsd-x64": "0.17.10", + "@esbuild/sunos-x64": "0.17.10", + "@esbuild/win32-arm64": "0.17.10", + "@esbuild/win32-ia32": "0.17.10", + "@esbuild/win32-x64": "0.17.10" + } }, "escalade": { "version": "3.1.1", @@ -17609,13 +17897,13 @@ } }, "eslint": { - "version": "8.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.27.0.tgz", - "integrity": "sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", + "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -17634,7 +17922,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -17737,9 +18025,9 @@ } }, "globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -17805,13 +18093,14 @@ } }, "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", "dev": true, "requires": { "debug": "^3.2.7", - "resolve": "^1.20.0" + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" }, "dependencies": { "debug": { @@ -17822,17 +18111,27 @@ "requires": { "ms": "^2.1.1" } + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } } } }, "eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", "dev": true, "requires": { - "debug": "^3.2.7", - "find-up": "^2.1.0" + "debug": "^3.2.7" }, "dependencies": { "debug": { @@ -17843,86 +18142,48 @@ "requires": { "ms": "^2.1.1" } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true } } }, + "eslint-plugin-ban": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-ban/-/eslint-plugin-ban-1.6.0.tgz", + "integrity": "sha512-gZptoV+SFHOHO57/5lmPvizMvSXrjFatP9qlVQf3meL/WHo9TxSoERygrMlESl19CPh95U86asTxohT8OprwDw==", + "dev": true, + "requires": { + "requireindex": "~1.2.0" + } + }, "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", "dev": true, "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", "has": "^1.0.3", - "is-core-module": "^2.8.1", + "is-core-module": "^2.11.0", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", "tsconfig-paths": "^3.14.1" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "doctrine": { @@ -17934,21 +18195,32 @@ "esutils": "^2.0.2" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "eslint-plugin-jsdoc": { - "version": "39.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.2.tgz", - "integrity": "sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A==", + "version": "40.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.0.0.tgz", + "integrity": "sha512-LOPyIu1vAVvGPkye3ci0moj0iNf3f8bmin6do2DYDj+77NRXWnkmhKRy8swWsatUs3mB5jYPWPUsFg9pyfEiyA==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "~0.36.0", + "@es-joy/jsdoccomment": "~0.36.1", "comment-parser": "1.3.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", @@ -17966,25 +18238,26 @@ } }, "eslint-plugin-react": { - "version": "7.31.10", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", - "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", "dev": true, "requires": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", "doctrine": "^2.1.0", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", + "resolve": "^2.0.0-next.4", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" + "string.prototype.matchall": "^4.0.8" }, "dependencies": { "doctrine": { @@ -18003,13 +18276,14 @@ "dev": true }, "resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "semver": { @@ -18060,9 +18334,9 @@ "dev": true }, "espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", "dev": true, "requires": { "acorn": "^8.8.0", @@ -18164,16 +18438,16 @@ "dev": true }, "expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.4.3.tgz", + "integrity": "sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==", "dev": true, "requires": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" + "@jest/expect-utils": "^29.4.3", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3" } }, "express": { @@ -18414,6 +18688,17 @@ "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "flatted": { @@ -18428,6 +18713,15 @@ "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "dev": true }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -18526,14 +18820,14 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" } }, "get-package-type": { @@ -18593,6 +18887,15 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -18607,6 +18910,15 @@ "slash": "^3.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -18658,6 +18970,12 @@ "get-intrinsic": "^1.1.1" } }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -18680,9 +18998,9 @@ "dev": true }, "highlight.js": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", - "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==", "dev": true }, "html-encoding-sniffer": { @@ -18847,20 +19165,20 @@ "dev": true }, "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", "dev": true, "requires": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.1.3", "has": "^1.0.3", "side-channel": "^1.0.4" } }, "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, "ipaddr.js": { @@ -18869,6 +19187,17 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true }, + "is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -18904,15 +19233,15 @@ } }, "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true }, "is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "requires": { "has": "^1.0.3" @@ -19051,6 +19380,19 @@ "has-symbols": "^1.0.2" } }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -19176,21 +19518,21 @@ } }, "jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.4.3.tgz", + "integrity": "sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==", "dev": true, "requires": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/core": "^29.4.3", + "@jest/types": "^29.4.3", "import-local": "^3.0.2", - "jest-cli": "^29.3.1" + "jest-cli": "^29.4.3" } }, "jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.4.3.tgz", + "integrity": "sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==", "dev": true, "requires": { "execa": "^5.0.0", @@ -19209,28 +19551,28 @@ } }, "jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.4.3.tgz", + "integrity": "sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==", "dev": true, "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/environment": "^29.4.3", + "@jest/expect": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", + "jest-each": "^29.4.3", + "jest-matcher-utils": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-runtime": "^29.4.3", + "jest-snapshot": "^29.4.3", + "jest-util": "^29.4.3", "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -19296,21 +19638,21 @@ } }, "jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.4.3.tgz", + "integrity": "sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==", "dev": true, "requires": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/core": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/types": "^29.4.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", + "jest-config": "^29.4.3", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -19376,9 +19718,9 @@ } }, "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -19399,31 +19741,31 @@ } }, "jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.4.3.tgz", + "integrity": "sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==", "dev": true, "requires": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", + "@jest/test-sequencer": "^29.4.3", + "@jest/types": "^29.4.3", + "babel-jest": "^29.4.3", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", + "jest-circus": "^29.4.3", + "jest-environment-node": "^29.4.3", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-runner": "^29.4.3", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -19480,15 +19822,15 @@ } }, "jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.3.tgz", + "integrity": "sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.4.3" }, "dependencies": { "ansi-styles": { @@ -19543,25 +19885,25 @@ } }, "jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.4.3.tgz", + "integrity": "sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==", "dev": true, "requires": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" + "jest-get-type": "^29.4.3", + "jest-util": "^29.4.3", + "pretty-format": "^29.4.3" }, "dependencies": { "ansi-styles": { @@ -19616,57 +19958,57 @@ } }, "jest-environment-jsdom": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz", - "integrity": "sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.4.3.tgz", + "integrity": "sha512-rFjf8JXrw3OjUzzmSE5l0XjMj0/MSVEUMCSXBGPDkfwb1T03HZI7iJSL0cGctZApPSyJxbjyKDVxkZuyhHkuTw==", "dev": true, "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/environment": "^29.4.3", + "@jest/fake-timers": "^29.4.3", + "@jest/types": "^29.4.3", "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1", + "jest-mock": "^29.4.3", + "jest-util": "^29.4.3", "jsdom": "^20.0.0" } }, "jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.4.3.tgz", + "integrity": "sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==", "dev": true, "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/environment": "^29.4.3", + "@jest/fake-timers": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" + "jest-mock": "^29.4.3", + "jest-util": "^29.4.3" } }, "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", "dev": true }, "jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.4.3.tgz", + "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", "dev": true, "requires": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.3.2", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.4.3", + "jest-worker": "^29.4.3", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -19678,13 +20020,13 @@ "dev": true }, "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } @@ -19701,25 +20043,25 @@ } }, "jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz", + "integrity": "sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==", "dev": true, "requires": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" + "jest-get-type": "^29.4.3", + "pretty-format": "^29.4.3" } }, "jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz", + "integrity": "sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" + "jest-diff": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.4.3" }, "dependencies": { "ansi-styles": { @@ -19774,18 +20116,18 @@ } }, "jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.4.3.tgz", + "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -19842,14 +20184,14 @@ } }, "jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.4.3.tgz", + "integrity": "sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==", "dev": true, "requires": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/node": "*", - "jest-util": "^29.3.1" + "jest-util": "^29.4.3" } }, "jest-pnp-resolver": { @@ -19860,25 +20202,25 @@ "requires": {} }, "jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", "dev": true }, "jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.4.3.tgz", + "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", "dev": true, "requires": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", + "jest-haste-map": "^29.4.3", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", + "jest-util": "^29.4.3", + "jest-validate": "^29.4.3", "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", + "resolve.exports": "^2.0.0", "slash": "^3.0.0" }, "dependencies": { @@ -19934,40 +20276,40 @@ } }, "jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz", + "integrity": "sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==", "dev": true, "requires": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.4.3" } }, "jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.4.3.tgz", + "integrity": "sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==", "dev": true, "requires": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/console": "^29.4.3", + "@jest/environment": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.4.3", + "jest-haste-map": "^29.4.3", + "jest-leak-detector": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-runtime": "^29.4.3", + "jest-util": "^29.4.3", + "jest-watcher": "^29.4.3", + "jest-worker": "^29.4.3", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -20013,13 +20355,13 @@ "dev": true }, "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", + "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -20056,31 +20398,31 @@ } }, "jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.4.3.tgz", + "integrity": "sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==", + "dev": true, + "requires": { + "@jest/environment": "^29.4.3", + "@jest/fake-timers": "^29.4.3", + "@jest/globals": "^29.4.3", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", + "jest-haste-map": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-mock": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.4.3", + "jest-snapshot": "^29.4.3", + "jest-util": "^29.4.3", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -20137,9 +20479,9 @@ } }, "jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.4.3.tgz", + "integrity": "sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==", "dev": true, "requires": { "@babel/core": "^7.11.6", @@ -20148,23 +20490,23 @@ "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/expect-utils": "^29.4.3", + "@jest/transform": "^29.4.3", + "@jest/types": "^29.4.3", "@types/babel__traverse": "^7.0.6", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.3.1", + "expect": "^29.4.3", "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", + "jest-diff": "^29.4.3", + "jest-get-type": "^29.4.3", + "jest-haste-map": "^29.4.3", + "jest-matcher-utils": "^29.4.3", + "jest-message-util": "^29.4.3", + "jest-util": "^29.4.3", "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", + "pretty-format": "^29.4.3", "semver": "^7.3.5" }, "dependencies": { @@ -20220,12 +20562,12 @@ } }, "jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.3.tgz", + "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", "dev": true, "requires": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -20285,17 +20627,17 @@ } }, "jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.4.3.tgz", + "integrity": "sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==", "dev": true, "requires": { - "@jest/types": "^29.3.1", + "@jest/types": "^29.4.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", + "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^29.3.1" + "pretty-format": "^29.4.3" }, "dependencies": { "ansi-styles": { @@ -20350,18 +20692,18 @@ } }, "jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.4.3.tgz", + "integrity": "sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==", "dev": true, "requires": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", + "@jest/test-result": "^29.4.3", + "@jest/types": "^29.4.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.3.1", + "jest-util": "^29.4.3", "string-length": "^4.0.1" }, "dependencies": { @@ -20540,9 +20882,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonfile": { @@ -20616,6 +20958,15 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, @@ -20741,9 +21092,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -20987,9 +21338,9 @@ } }, "mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { "ansi-colors": "4.1.1", @@ -21299,15 +21650,6 @@ "path-to-regexp": "^1.7.0" }, "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, "@sinonjs/fake-timers": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", @@ -21400,58 +21742,58 @@ "dev": true }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, "object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", "dev": true, "requires": { "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" } }, "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "on-finished": { @@ -21711,12 +22053,12 @@ "dev": true }, "pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.3.tgz", + "integrity": "sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==", "dev": true, "requires": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -21903,12 +22245,12 @@ } }, "rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "requires": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" } }, "regenerate": { @@ -21927,9 +22269,9 @@ } }, "regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==", + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, "regenerator-transform": { @@ -22007,6 +22349,12 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -22048,9 +22396,9 @@ "dev": true }, "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.0.tgz", + "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", "dev": true }, "reusify": { @@ -22066,13 +22414,10 @@ "dev": true }, "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz", + "integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==", + "dev": true }, "run-parallel": { "version": "1.2.0", @@ -22083,27 +22428,23 @@ "queue-microtask": "^1.2.2" } }, - "rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - } - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -22308,28 +22649,19 @@ "dev": true }, "sinon": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", - "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.1.tgz", + "integrity": "sha512-PZXKc08f/wcA/BMRGBze2Wmw50CWPiAH3E21EOi4B49vJ616vW4DQh4fQrqsYox2aNR/N3kCqLuB0PwwOucQrg==", "dev": true, "requires": { "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/fake-timers": "10.0.2", "@sinonjs/samsam": "^7.0.1", "diff": "^5.0.0", "nise": "^5.1.2", "supports-color": "^7.2.0" }, "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -22521,41 +22853,41 @@ } }, "string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", + "regexp.prototype.flags": "^1.4.3", "side-channel": "^1.0.4" } }, "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" } }, "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" } }, "strip-ansi": { @@ -22686,6 +23018,17 @@ "dev": true, "requires": { "rimraf": "^3.0.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "tmpl": { @@ -22743,15 +23086,15 @@ } }, "ts-jest": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", - "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", + "version": "29.0.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", + "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", "dev": true, "requires": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", - "json5": "^2.2.1", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", "semver": "7.x", @@ -22767,9 +23110,9 @@ } }, "ts-loader": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz", - "integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -22842,9 +23185,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -22942,16 +23285,27 @@ "mime-types": "~2.1.24" } }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true }, "uc.micro": { @@ -23060,9 +23414,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.12", @@ -23169,11 +23523,12 @@ } }, "webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz", + "integrity": "sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg==", "dev": true, "requires": { + "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", "chalk": "^4.1.0", @@ -23243,29 +23598,30 @@ } }, "webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^9.4.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true } } @@ -23344,6 +23700,20 @@ "is-symbol": "^1.0.3" } }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", diff --git a/package.json b/package.json index 956bc46426..6731c5e448 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rx-player", "author": "Canal+", - "version": "3.29.0", + "version": "3.30.0", "description": "Canal+ HTML5 Video Player", "main": "./dist/rx-player.js", "keywords": [ @@ -12,8 +12,6 @@ "mediasource", "mse", "reactive", - "rx", - "rxjs", "smooth", "streaming", "typescript", @@ -77,61 +75,61 @@ "url": "git://github.com/canalplus/rx-player.git" }, "dependencies": { - "next-tick": "1.1.0", - "rxjs": "7.5.7" + "next-tick": "1.1.0" }, "devDependencies": { - "@babel/core": "7.20.2", - "@babel/plugin-transform-runtime": "7.19.6", + "@babel/core": "7.21.0", + "@babel/plugin-transform-runtime": "7.21.0", "@babel/preset-env": "7.20.2", "@babel/preset-react": "7.18.6", "@types/chai": "4.3.4", - "@types/jest": "29.2.3", - "@types/mocha": "10.0.0", - "@types/node": "18.11.9", + "@types/jest": "29.4.0", + "@types/mocha": "10.0.1", + "@types/node": "18.14.0", "@types/sinon": "10.0.13", - "@typescript-eslint/eslint-plugin": "5.43.0", - "@typescript-eslint/eslint-plugin-tslint": "5.43.0", - "@typescript-eslint/parser": "5.43.0", + "@typescript-eslint/eslint-plugin": "5.53.0", + "@typescript-eslint/eslint-plugin-tslint": "5.53.0", + "@typescript-eslint/parser": "5.53.0", "arraybuffer-loader": "1.0.8", - "babel-loader": "9.1.0", + "babel-loader": "9.1.2", "chai": "4.3.7", "cheerio": "1.0.0-rc.12", - "core-js": "3.26.1", - "esbuild": "0.15.14", - "eslint": "8.27.0", - "eslint-plugin-import": "2.26.0", - "eslint-plugin-jsdoc": "39.6.2", - "eslint-plugin-react": "7.31.10", + "core-js": "3.28.0", + "esbuild": "0.17.10", + "eslint": "8.34.0", + "eslint-plugin-ban": "1.6.0", + "eslint-plugin-import": "2.27.5", + "eslint-plugin-jsdoc": "40.0.0", + "eslint-plugin-react": "7.32.2", "esm": "3.2.25", "express": "4.18.2", - "highlight.js": "11.6.0", + "highlight.js": "11.7.0", "html-entities": "2.3.3", - "jest": "29.3.1", - "jest-environment-jsdom": "29.3.1", + "jest": "29.4.3", + "jest-environment-jsdom": "29.4.3", "karma": "6.4.1", "karma-chrome-launcher": "3.1.1", "karma-firefox-launcher": "2.1.2", "karma-mocha": "2.0.1", "karma-webpack": "5.0.0", "markdown-it": "13.0.1", - "mocha": "10.1.0", + "mocha": "10.2.0", "mocha-loader": "5.1.5", "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "regenerator-runtime": "0.13.10", - "rimraf": "3.0.2", + "regenerator-runtime": "0.13.11", + "rimraf": "4.1.2", "semver": "7.3.8", - "sinon": "14.0.2", + "sinon": "15.0.1", "terser-webpack-plugin": "5.3.6", - "ts-jest": "29.0.3", - "ts-loader": "9.4.1", + "ts-jest": "29.0.5", + "ts-loader": "9.4.2", "tslint": "6.1.3", - "typescript": "4.9.3", + "typescript": "4.9.5", "webpack": "5.75.0", - "webpack-bundle-analyzer": "4.7.0", - "webpack-cli": "4.10.0" + "webpack-bundle-analyzer": "4.8.0", + "webpack-cli": "5.0.1" }, "scripts-list": { "Build a demo page (e.g. to test a code change)": { diff --git a/scripts/build/constants.d.ts b/scripts/build/constants.d.ts index 017344c5aa..30fdd81f10 100644 --- a/scripts/build/constants.d.ts +++ b/scripts/build/constants.d.ts @@ -42,6 +42,7 @@ declare const enum __FEATURES__ { HTML_VTT = IS_DISABLED, LOCAL_MANIFEST = IS_DISABLED, METAPLAYLIST = IS_DISABLED, + DEBUG_ELEMENT = IS_DISABLED, NATIVE_SAMI = IS_DISABLED, NATIVE_SRT = IS_DISABLED, NATIVE_TTML = IS_DISABLED, diff --git a/scripts/build/generate_build.js b/scripts/build/generate_build.js index 045a65f279..ae125d2b34 100755 --- a/scripts/build/generate_build.js +++ b/scripts/build/generate_build.js @@ -124,14 +124,7 @@ async function generateImportFilesFromTemplates() { * @returns {Promise} */ function removeFile(fileName) { - return new Promise((res, rej) => { - rimraf(fileName, (err) => { - if (err !== null && err !== undefined) { - rej(err); - } - res(); - }); - }); + return rimraf(fileName); } /** diff --git a/scripts/fast_demo_build.js b/scripts/fast_demo_build.js index 38b5823df0..4b520350e0 100644 --- a/scripts/fast_demo_build.js +++ b/scripts/fast_demo_build.js @@ -52,34 +52,39 @@ function fastDemoBuild(options) { const watch = !!options.watch; const isDevMode = !options.production; const includeWasmParser = !!options.includeWasmParser; - let beforeTime = process.hrtime.bigint(); - esbuild.build({ + /** Declare a plugin to anounce when a build begins and ends */ + const consolePlugin = { + name: "onEnd", + setup(build) { + build.onStart(() => { + console.log(`\x1b[33m[${getHumanReadableHours()}]\x1b[0m ` + + "New demo build started"); + }) + build.onEnd(result => { + if (result.errors.length > 0 || result.warnings.length > 0) { + const { errors, warnings } = result; + console.log(`\x1b[33m[${getHumanReadableHours()}]\x1b[0m ` + + `Demo re-built with ${errors.length} error(s) and ` + + ` ${warnings.length} warning(s) ` + + `(in ${stats.endTime - stats.startTime} ms).`); + } + console.log(`\x1b[32m[${getHumanReadableHours()}]\x1b[0m ` + + "Demo built!"); + }); + }, + }; + + // Create a context for incremental builds + esbuild.context({ entryPoints: [path.join(__dirname, "../demo/full/scripts/index.jsx")], bundle: true, minify, outfile: path.join(__dirname, "../demo/full/bundle.js"), - watch: !watch ? undefined : { - onRebuild(error, result) { - if (error) { - console.error(`\x1b[31m[${getHumanReadableHours()}]\x1b[0m Demo re-build failed:`, - err); - } else { - if (result.errors > 0 || result.warnings > 0) { - const { errors, warnings } = result; - console.log(`\x1b[33m[${getHumanReadableHours()}]\x1b[0m ` + - `Demo re-built with ${errors.length} error(s) and ` + - ` ${warnings.length} warning(s) ` + - `(in ${stats.endTime - stats.startTime} ms).`); - } - console.log(`\x1b[32m[${getHumanReadableHours()}]\x1b[0m ` + - "Demo re-built!"); - } - }, - }, + plugins: [consolePlugin], define: { "process.env.NODE_ENV": JSON.stringify(isDevMode ? "development" : "production"), - __INCLUDE_WASM_PARSER__: includeWasmParser, + __INCLUDE_WASM_PARSER__: JSON.stringify(includeWasmParser), __ENVIRONMENT__: JSON.stringify({ PRODUCTION: 0, DEV: 1, @@ -89,24 +94,18 @@ function fastDemoBuild(options) { CURRENT_LEVEL: "INFO", }), } - }).then( - (result) => { - if (result.errors > 0 || result.warnings > 0) { - const { errors, warnings } = result; - console.log(`\x1b[33m[${getHumanReadableHours()}]\x1b[0m ` + - `Demo built with ${errors.length} error(s) and ` + - ` ${warnings.length} warning(s) ` + - `(in ${stats.endTime - stats.startTime} ms).`); - } - const fullTime = (process.hrtime.bigint() - beforeTime) / 1000000n; - console.log(`\x1b[32m[${getHumanReadableHours()}]\x1b[0m ` + - `Build done in ${fullTime}ms`); - }, - (err) => { - console.error(`\x1b[31m[${getHumanReadableHours()}]\x1b[0m Demo build failed:`, - err); - process.exit(1); - }); + }) + .then((context) => { + if (watch) { + return context.watch(); + } + }) + .catch((err) => { + console.error( + `\x1b[31m[${getHumanReadableHours()}]\x1b[0m Demo build failed:`, + err); + process.exit(1); + }); } /** diff --git a/scripts/report_build_sizes b/scripts/report_build_sizes index e0f5116ca9..8ff43c4ce3 100755 --- a/scripts/report_build_sizes +++ b/scripts/report_build_sizes @@ -87,6 +87,10 @@ RXP_METAPLAYLIST=true npm run build:min echo -n "with METAPLAYLIST: " >> sizes stat -c %s dist/rx-player.min.js >> sizes +RXP_DEBUG_ELEMENT=true npm run build:min +echo -n "with DEBUG_ELEMENT: " >> sizes +stat -c %s dist/rx-player.min.js >> sizes + RXP_LOCAL_MANIFEST=true npm run build:min echo -n "with LOCAL_MANIFEST: " >> sizes stat -c %s dist/rx-player.min.js >> sizes diff --git a/sonar-project.properties b/sonar-project.properties index ebe1583017..32a792cf60 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.projectKey=rx-player sonar.organization=rx-player sonar.projectName=rx-player -sonar.projectVersion=3.29.0 +sonar.projectVersion=3.30.0 sonar.sources=./src,./demo,./tests sonar.exclusions=demo/full/bundle.js,demo/standalone/lib.js,demo/bundle.js sonar.host.url=https://sonarcloud.io diff --git a/src/README.md b/src/README.md index 4f1cdb6445..45ba61a152 100644 --- a/src/README.md +++ b/src/README.md @@ -37,8 +37,8 @@ To better understand the player's architecture, you can find below a Facilitate track V | Abstract the streaming ^ switching for +---------------+ | protocol | the API | | | | - +----------------+ | | +--------------------------+ | - | Content | | Init | ------> | | | + +----------------+ | Content | +--------------------------+ | + | Content | | Initializer | ------> | | | | Decryptor | <---- | (./core/init) | | Manifest Fetcher | | |(./core/decrypt)| | | |(./core/fetchers/manifest)| | | | | | | | | diff --git a/src/compat/__tests__/play.test.ts b/src/compat/__tests__/play.test.ts deleted file mode 100644 index 0136fc1e1a..0000000000 --- a/src/compat/__tests__/play.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ - -describe("compat - play", () => { - it("should call play and returns an Observable if play returns a Promise", (done) => { - const mockPlay = jest.fn(() => Promise.resolve()); - const fakeMediaElement = { play: mockPlay }; - - const play$ = jest.requireActual("../play").default; - play$(fakeMediaElement).subscribe(() => { - expect(mockPlay).toHaveBeenCalledTimes(1); - done(); - }); - }); - - it("should return an Observable even if play does not return a promise", (done) => { - const mockPlay = jest.fn(); - const fakeMediaElement = { play: mockPlay }; - - const play$ = jest.requireActual("../play").default; - play$(fakeMediaElement).subscribe(() => { - done(); - }); - }); - - it("should throw through an Observable if the `play` promise is rejected", (done) => { - const notAllowedError = new Error("NotAllowedError: Can't play"); - const mockPlay = jest.fn(() => { - return Promise.reject(notAllowedError); - }); - const fakeMediaElement = { play: mockPlay }; - - const play$ = jest.requireActual("../play").default; - play$(fakeMediaElement).subscribe(() => null, (err: unknown) => { - expect(err).toBe(notAllowedError); - done(); - }); - }); - - it("should throw through an Observable if `play` throws", (done) => { - const notAllowedError = new Error("NotAllowedError: Can't play"); - const mockPlay = jest.fn(() => { - throw notAllowedError; - }); - const fakeMediaElement = { play: mockPlay }; - - const play$ = jest.requireActual("../play").default; - play$(fakeMediaElement).subscribe(() => null, (err: unknown) => { - expect(err).toBe(notAllowedError); - done(); - }); - }); -}); diff --git a/src/compat/__tests__/set_element_src.test.ts b/src/compat/__tests__/set_element_src.test.ts deleted file mode 100644 index f6a455ce07..0000000000 --- a/src/compat/__tests__/set_element_src.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ - -import { map } from "rxjs"; - -describe("compat - setElementSrc", () => { - beforeEach(() => { - jest.resetModules(); - }); - - it("should set element src and clear it when unsubscribe", (done) => { - const fakeMediaElement = { - src: "", - removeAttribute: () => null, - }; - - const mockLogInfo = jest.fn((message) => message); - jest.mock("../../log", () => ({ - __esModule: true as const, - default: { - info: mockLogInfo, - }, - })); - const mockClearElementSrc = jest.fn(() => { - fakeMediaElement.src = ""; - }); - jest.mock("../clear_element_src", () => ({ - __esModule: true as const, - default: mockClearElementSrc, - })); - const fakeURL = "blob:http://fakeURL"; - - const setElementSrc = jest.requireActual("../set_element_src").default; - - const setElementSrc$ = setElementSrc(fakeMediaElement, fakeURL); - const subscribe = setElementSrc$.pipe( - map(() => { - expect(mockLogInfo).toHaveBeenCalledTimes(1); - expect(mockLogInfo) - .toHaveBeenCalledWith("Setting URL to HTMLMediaElement", fakeURL); - expect(fakeMediaElement.src).toBe(fakeURL); - }) - ).subscribe(); - - setTimeout(() => { - subscribe.unsubscribe(); - expect(fakeMediaElement.src).toBe(""); - done(); - }, 200); - }); -}); diff --git a/src/compat/__tests__/should_reload_media_source_on_decipherability_update.test.ts b/src/compat/__tests__/should_reload_media_source_on_decipherability_update.test.ts index f0b5fdbe1d..d5dde4dc60 100644 --- a/src/compat/__tests__/should_reload_media_source_on_decipherability_update.test.ts +++ b/src/compat/__tests__/should_reload_media_source_on_decipherability_update.test.ts @@ -19,7 +19,7 @@ import shouldReloadMediaSourceOnDecipherabilityUpdate from "../should_reload_med describe("Compat - shouldReloadMediaSourceOnDecipherabilityUpdate", () => { it("should return true for an unknown key system", () => { - expect(shouldReloadMediaSourceOnDecipherabilityUpdate(null)).toEqual(true); + expect(shouldReloadMediaSourceOnDecipherabilityUpdate(undefined)).toEqual(true); }); it("should return false for any string containing the string \"widevine\"", () => { diff --git a/src/compat/__tests__/when_loaded_metadata.test.ts b/src/compat/__tests__/when_loaded_metadata.test.ts deleted file mode 100644 index 0cdfbd457f..0000000000 --- a/src/compat/__tests__/when_loaded_metadata.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ - -import { - finalize, - interval as observableInterval, - mapTo, - tap, - timer as observableTimer, -} from "rxjs"; - -describe("compat - whenLoadedMetadata$", () => { - beforeEach(() => { - jest.resetModules(); - }); - - it("should wait for loaded metadata if they are not yet loaded", (done) => { - const fakeMediaElement = { - readyState: 0, - }; - - const mockOnLoadedMetadata$ = jest.fn(() => { - return observableTimer(500).pipe( - tap(() => fakeMediaElement.readyState = 1), - mapTo(null) - ); - }); - - jest.mock("../event_listeners", () => ({ - __esModule: true as const, - onLoadedMetadata$: mockOnLoadedMetadata$, - })); - - const whenLoadedMetadata$ = jest.requireActual("../when_loaded_metadata").default; - whenLoadedMetadata$(fakeMediaElement).subscribe(() => { - expect(fakeMediaElement.readyState).toBe(1); - expect(mockOnLoadedMetadata$).toHaveBeenCalledTimes(1); - done(); - }); - }); - - it("should emit once when metadata is loaded several times", (done) => { - const fakeMediaElement = { - readyState: 0, - }; - - const mockOnLoadedMetadata$ = jest.fn(() => { - return observableInterval(500).pipe( - tap(() => fakeMediaElement.readyState++), - mapTo(null) - ); - }); - - jest.mock("../event_listeners", () => ({ - __esModule: true as const, - onLoadedMetadata$: mockOnLoadedMetadata$, - })); - - const whenLoadedMetadata$ = jest.requireActual("../when_loaded_metadata").default; - whenLoadedMetadata$(fakeMediaElement).pipe( - finalize(() => { - expect(fakeMediaElement.readyState).toBe(1); - expect(mockOnLoadedMetadata$).toHaveBeenCalledTimes(1); - done(); - }) - ).subscribe(); - }); - - it("should emit if metadata is already loaded", (done) => { - const fakeMediaElement = { - readyState: 1, - }; - - const mockOnLoadedMetadata$ = jest.fn(() => null); - - jest.mock("../event_listeners", () => ({ - __esModule: true as const, - onLoadedMetadata$: mockOnLoadedMetadata$, - })); - - const whenLoadedMetadata$ = jest.requireActual("../when_loaded_metadata").default; - whenLoadedMetadata$(fakeMediaElement).subscribe(() => { - expect(fakeMediaElement.readyState).toBe(1); - expect(mockOnLoadedMetadata$).not.toHaveBeenCalled(); - done(); - }); - }); -}); diff --git a/src/compat/browser_detection.ts b/src/compat/browser_detection.ts index f22fd8d85b..87dd321829 100644 --- a/src/compat/browser_detection.ts +++ b/src/compat/browser_detection.ts @@ -24,71 +24,118 @@ interface IIE11Document extends Document { documentMode? : unknown; } -// true on IE11 -// false on Edge and other IEs/browsers. -const isIE11 : boolean = - !isNode && - typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" && - typeof (document as IIE11Document).documentMode !== "undefined"; - -// true for IE / Edge -const isIEOrEdge : boolean = isNode ? - false : - navigator.appName === "Microsoft Internet Explorer" || - navigator.appName === "Netscape" && - /(Trident|Edge)\//.test(navigator.userAgent); - -const isEdgeChromium: boolean = !isNode && - navigator.userAgent.toLowerCase().indexOf("edg/") !== -1; - -const isFirefox : boolean = !isNode && - navigator.userAgent.toLowerCase().indexOf("firefox") !== -1; - -const isSamsungBrowser : boolean = !isNode && - /SamsungBrowser/.test(navigator.userAgent); - -const isTizen : boolean = !isNode && - /Tizen/.test(navigator.userAgent); - -const isWebOs : boolean = !isNode && - navigator.userAgent.indexOf("Web0S") >= 0; - -// Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/ -// Note: even that page doesn't correspond to what we've actually seen in the -// wild -const isWebOs2021 : boolean = isWebOs && - ( - /[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) || - /[Cc]hr[o0]me\/79/.test(navigator.userAgent) - ); -const isWebOs2022 : boolean = isWebOs && - ( - /[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) || - /[Cc]hr[o0]me\/87/.test(navigator.userAgent) - ); +/** Edge Chromium, regardless of the device */ +let isEdgeChromium = false; -interface ISafariWindowObject extends Window { - safari? : { pushNotification? : { toString() : string } }; -} +/** IE11, regardless of the device */ +let isIE11 = false; + +/** IE11 or Edge __Legacy__ (not Edge Chromium), regardless of the device */ +let isIEOrEdge = false; + +/** Firefox, regardless of the device */ +let isFirefox = false; /** `true` on Safari on a PC platform (i.e. not iPhone / iPad etc.) */ -const isSafariDesktop : boolean = - !isNode && ( +let isSafariDesktop = false; + +/** `true` on Safari on an iPhone, iPad & iPod platform */ +let isSafariMobile = false; + +/** Samsung's own browser application */ +let isSamsungBrowser = false; + +/** `true` on devices where Tizen is the OS (e.g. Samsung TVs). */ +let isTizen = false; + +/** `true` on devices where WebOS is the OS (e.g. LG TVs). */ +let isWebOs = false; + +/** `true` specifically for WebOS 2021 version. */ +let isWebOs2021 = false; + +/** `true` specifically for WebOS 2022 version. */ +let isWebOs2022 = false; + +/** `true` for Panasonic devices. */ +let isPanasonic = false; + +((function findCurrentBrowser() : void { + if (isNode) { + return ; + } + + // 1 - Find out browser between IE/Edge Legacy/Edge Chromium/Firefox/Safari + + if (typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" && + typeof (document as IIE11Document).documentMode !== "undefined") + { + isIE11 = true; + isIEOrEdge = true; + } else if ( + navigator.appName === "Microsoft Internet Explorer" || + navigator.appName === "Netscape" && + /(Trident|Edge)\//.test(navigator.userAgent) + ) { + isIEOrEdge = true; + } else if (navigator.userAgent.toLowerCase().indexOf("edg/") !== -1) { + isEdgeChromium = true; + } else if (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1) { + isFirefox = true; + } else if (typeof navigator.platform === "string" && + /iPad|iPhone|iPod/.test(navigator.platform)) + { + isSafariMobile = true; + } else if ( Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") >= 0 || (window as ISafariWindowObject).safari?.pushNotification?.toString() === "[object SafariRemoteNotification]" - ); + ) { + isSafariDesktop = true; + } -/** `true` on Safari on an iPhone, iPad & iPod platform */ -const isSafariMobile : boolean = !isNode && - typeof navigator.platform === "string" && - /iPad|iPhone|iPod/.test(navigator.platform); + // 2 - Find out specific device/platform information + + // Samsung browser e.g. on Android + if (/SamsungBrowser/.test(navigator.userAgent)) { + isSamsungBrowser = true; + } + + if (/Tizen/.test(navigator.userAgent)) { + isTizen = true; + + // Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/ + // Note: even that page doesn't correspond to what we've actually seen in the + // wild + } else if (/[Ww]eb[O0]S/.test(navigator.userAgent)) { + isWebOs = true; + + if ( + /[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) || + /[Cc]hr[o0]me\/87/.test(navigator.userAgent) + ) { + isWebOs2022 = true; + } else if ( + /[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) || + /[Cc]hr[o0]me\/79/.test(navigator.userAgent) + ) { + isWebOs2021 = true; + } + } else if (/[Pp]anasonic/.test(navigator.userAgent)) { + isPanasonic = true; + } +})()); + +interface ISafariWindowObject extends Window { + safari? : { pushNotification? : { toString() : string } }; +} export { isEdgeChromium, isIE11, isIEOrEdge, isFirefox, + isPanasonic, isSafariDesktop, isSafariMobile, isSamsungBrowser, diff --git a/src/compat/can_reuse_media_keys.ts b/src/compat/can_reuse_media_keys.ts index 88eaaab8b6..efa893e5b6 100644 --- a/src/compat/can_reuse_media_keys.ts +++ b/src/compat/can_reuse_media_keys.ts @@ -1,4 +1,7 @@ -import { isWebOs } from "./browser_detection"; +import { + isPanasonic, + isWebOs, +} from "./browser_detection"; /** * Returns `true` if a `MediaKeys` instance (the `Encrypted Media Extension` @@ -13,5 +16,5 @@ import { isWebOs } from "./browser_detection"; * @returns {boolean} */ export default function canReuseMediaKeys() : boolean { - return !isWebOs; + return !isWebOs && !isPanasonic; } diff --git a/src/compat/eme/close_session.ts b/src/compat/eme/close_session.ts index b4aeee2564..d7c8d1f3f7 100644 --- a/src/compat/eme/close_session.ts +++ b/src/compat/eme/close_session.ts @@ -86,7 +86,7 @@ export default function closeSession( try { await session.update(new Uint8Array(1)); } catch (err) { - if (timeoutCanceller.isUsed) { // Reminder: cancelled == session closed + if (timeoutCanceller.isUsed()) { // Reminder: cancelled == session closed return; } @@ -102,7 +102,7 @@ export default function closeSession( await cancellableSleep(1000, timeoutCanceller.signal); } - if (timeoutCanceller.isUsed) { // Reminder: cancelled == session closed + if (timeoutCanceller.isUsed()) { // Reminder: cancelled == session closed return; } diff --git a/src/compat/eme/custom_media_keys/ie11_media_keys.ts b/src/compat/eme/custom_media_keys/ie11_media_keys.ts index b40a8704db..372290b557 100644 --- a/src/compat/eme/custom_media_keys/ie11_media_keys.ts +++ b/src/compat/eme/custom_media_keys/ie11_media_keys.ts @@ -14,12 +14,8 @@ * limitations under the License. */ -import { - merge as observableMerge, - Subject, - takeUntil, -} from "rxjs"; import EventEmitter from "../../../utils/event_emitter"; +import TaskCanceller from "../../../utils/task_canceller"; import { ICompatHTMLMediaElement } from "../../browser_compatibility_types"; import * as events from "../../event_listeners"; import { @@ -43,16 +39,16 @@ class IE11MediaKeySession public expiration: number; public keyStatuses: ICustomMediaKeyStatusMap; private readonly _mk: MSMediaKeys; - private readonly _closeSession$: Subject; + private readonly _sessionClosingCanceller: TaskCanceller; private _ss: MSMediaKeySession | undefined; constructor(mk: MSMediaKeys) { super(); this.expiration = NaN; this.keyStatuses = new Map(); this._mk = mk; - this._closeSession$ = new Subject(); + this._sessionClosingCanceller = new TaskCanceller(); this.closed = new Promise((resolve) => { - this._closeSession$.subscribe(resolve); + this._sessionClosingCanceller.signal.register(() => resolve()); }); this.update = (license: Uint8Array) => { return new Promise((resolve, reject) => { @@ -79,11 +75,15 @@ class IE11MediaKeySession initData instanceof ArrayBuffer ? new Uint8Array(initData) : new Uint8Array(initData.buffer); this._ss = this._mk.createSession("video/mp4", initDataU8); - observableMerge(events.onKeyMessage$(this._ss), - events.onKeyAdded$(this._ss), - events.onKeyError$(this._ss) - ).pipe(takeUntil(this._closeSession$)) - .subscribe((evt: Event) => this.trigger(evt.type, evt)); + events.onKeyMessage(this._ss, (evt) => { + this.trigger((evt as Event).type ?? "message", evt as Event); + }, this._sessionClosingCanceller.signal); + events.onKeyAdded(this._ss, (evt) => { + this.trigger((evt as Event).type ?? "keyadded", evt as Event); + }, this._sessionClosingCanceller.signal); + events.onKeyError(this._ss, (evt) => { + this.trigger((evt as Event).type ?? "keyerror", evt as Event); + }, this._sessionClosingCanceller.signal); resolve(); }); } @@ -93,8 +93,7 @@ class IE11MediaKeySession this._ss.close(); this._ss = undefined; } - this._closeSession$.next(); - this._closeSession$.complete(); + this._sessionClosingCanceller.cancel(); resolve(); }); } diff --git a/src/compat/event_listeners.ts b/src/compat/event_listeners.ts index 9d3ccfbe12..f8f778bc6e 100644 --- a/src/compat/event_listeners.ts +++ b/src/compat/event_listeners.ts @@ -14,17 +14,6 @@ * limitations under the License. */ -/** - * This file provides browser-agnostic event listeners under the form of - * RxJS Observables - */ - -import { - NEVER, - Observable, - fromEvent as observableFromEvent, - merge as observableMerge, -} from "rxjs"; import config from "../config"; import log from "../log"; import { IEventEmitter } from "../utils/event_emitter"; @@ -93,8 +82,8 @@ function findSupportedEvent( */ function eventPrefixed(eventNames : string[], prefixes? : string[]) : string[] { return eventNames.reduce((parent : string[], name : string) => - parent.concat((prefixes == null ? BROWSER_PREFIXES : - prefixes) + parent.concat((prefixes === undefined ? BROWSER_PREFIXES : + prefixes) .map((p) => p + name)), []); } @@ -112,11 +101,15 @@ export type IEventTargetLike = HTMLElement | * optionally automatically adding browser prefixes if needed. * @param {Array.} eventNames - The event(s) to listen to. If multiple * events are set, the event listener will be triggered when any of them emits. + * @param {Array.|undefined} [prefixes] - Optional vendor prefixes with + * which the event might also be sent. If not defined, default prefixes might be + * tested. * @returns {Function} - Returns function allowing to easily add a callback to * be triggered when that event is emitted on a given event target. */ function createCompatibleEventListener( - eventNames : string[] + eventNames : string[], + prefixes? : string[] ) : ( element : IEventTargetLike, @@ -125,14 +118,14 @@ function createCompatibleEventListener( ) => void { let mem : string|undefined; - const prefixedEvents = eventPrefixed(eventNames); + const prefixedEvents = eventPrefixed(eventNames, prefixes); return ( element : IEventTargetLike, listener: (event? : unknown) => void, cancelSignal: CancellationSignal ) => { - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } @@ -161,53 +154,32 @@ function createCompatibleEventListener( } prefixedEvents.forEach(eventName => { - (element as IEventEmitterLike).addEventListener(eventName, listener); + let hasSetOnFn = false; + if (typeof element.addEventListener === "function") { + (element as IEventEmitterLike).addEventListener(eventName, listener); + } else { + hasSetOnFn = true; + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + (element as any)["on" + eventName] = listener; + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ + } cancelSignal.register(() => { - (element as IEventEmitterLike).removeEventListener(eventName, listener); + if (typeof element.removeEventListener === "function") { + (element as IEventEmitterLike).removeEventListener(eventName, listener); + } + if (hasSetOnFn) { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + delete (element as any)["on" + eventName]; + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ + } }); }); }; } -/** - * @param {Array.} eventNames - * @param {Array.|undefined} prefixes - * @returns {Observable} - */ -function compatibleListener( - eventNames : string[], - prefixes? : string[] -) : (element : IEventTargetLike) => Observable { - let mem : string|undefined; - const prefixedEvents = eventPrefixed(eventNames, prefixes); - return (element : IEventTargetLike) => { - // if the element is a HTMLElement we can detect - // the supported event, and memoize it in `mem` - if (element instanceof HTMLElement) { - if (typeof mem === "undefined") { - mem = findSupportedEvent(element, prefixedEvents); - } - - if (isNonEmptyString(mem)) { - return observableFromEvent(element, mem) as Observable; - } else { - if (__ENVIRONMENT__.CURRENT_ENV === __ENVIRONMENT__.DEV as number) { - log.warn(`compat: element ${element.tagName}` + - " does not support any of these events: " + - prefixedEvents.join(", ")); - } - return NEVER; - } - } - - // otherwise, we need to listen to all the events - // and merge them into one observable sequence - return observableMerge(...prefixedEvents.map(eventName => - observableFromEvent(element, eventName))); - }; -} - /** * Returns a reference: * - set to `true` when the document is visible @@ -238,17 +210,13 @@ function getDocumentVisibilityRef( "visibilitychange"; const isHidden = document[hidden as "hidden"]; - const ref = createSharedReference(!isHidden); + const ref = createSharedReference(!isHidden, stopListening); addEventListener(document, visibilityChangeEvent, () => { const isVisible = !(document[hidden as "hidden"]); ref.setValueIfChanged(isVisible); }, stopListening); - stopListening.register(() => { - ref.finish(); - }); - return ref; } @@ -265,11 +233,10 @@ function getPageActivityRef( ) : IReadOnlySharedReference { const isDocVisibleRef = getDocumentVisibilityRef(stopListening); let currentTimeout : number | undefined; - const ref = createSharedReference(true); + const ref = createSharedReference(true, stopListening); stopListening.register(() => { clearTimeout(currentTimeout); currentTimeout = undefined; - ref.finish(); }); isDocVisibleRef.onUpdate(function onDocVisibilityChange(isVisible : boolean) : void { @@ -312,7 +279,7 @@ export interface IPictureInPictureEvent { * Emit when video enters and leaves Picture-In-Picture mode. * @param {HTMLMediaElement} elt * @param {Object} stopListening - * @returns {Observable} + * @returns {Object} */ function getPictureOnPictureStateRef( elt: HTMLMediaElement, @@ -327,14 +294,11 @@ function getPictureOnPictureStateRef( const ref = createSharedReference({ isEnabled: isWebKitPIPEnabled, pipWindow: null, - }); + }, stopListening); addEventListener(mediaElement, "webkitpresentationmodechanged", () => { const isEnabled = mediaElement.webkitPresentationMode === "picture-in-picture"; ref.setValue({ isEnabled, pipWindow: null }); }, stopListening); - stopListening.register(() => { - ref.finish(); - }); return ref; } @@ -342,7 +306,8 @@ function getPictureOnPictureStateRef( (document as ICompatDocument).pictureInPictureElement === mediaElement ); const ref = createSharedReference({ isEnabled: isPIPEnabled, - pipWindow: null }); + pipWindow: null }, + stopListening); addEventListener(mediaElement, "enterpictureinpicture", (evt) => { ref.setValue({ isEnabled: true, @@ -354,9 +319,6 @@ function getPictureOnPictureStateRef( addEventListener(mediaElement, "leavepictureinpicture", () => { ref.setValue({ isEnabled: false, pipWindow: null }); }, stopListening); - stopListening.register(() => { - ref.finish(); - }); return ref; } @@ -368,7 +330,7 @@ function getPictureOnPictureStateRef( * @param {Object} pipStatus * @param {Object} stopListening - `CancellationSignal` allowing to free the * resources reserved to listen to video visibility change. - * @returns {Observable} + * @returns {Object} */ function getVideoVisibilityRef( pipStatus : IReadOnlySharedReference, @@ -376,11 +338,10 @@ function getVideoVisibilityRef( ) : IReadOnlySharedReference { const isDocVisibleRef = getDocumentVisibilityRef(stopListening); let currentTimeout : number | undefined; - const ref = createSharedReference(true); + const ref = createSharedReference(true, stopListening); stopListening.register(() => { clearTimeout(currentTimeout); currentTimeout = undefined; - ref.finish(); }); isDocVisibleRef.onUpdate(checkCurrentVisibility, @@ -417,7 +378,8 @@ function getVideoWidthRef( pipStatusRef : IReadOnlySharedReference, stopListening : CancellationSignal ) : IReadOnlySharedReference { - const ref = createSharedReference(mediaElement.clientWidth * pixelRatio); + const ref = createSharedReference(mediaElement.clientWidth * pixelRatio, + stopListening); let clearPreviousEventListener = noop; pipStatusRef.onUpdate(checkVideoWidth, { clearSignal: stopListening }); addEventListener(window, "resize", checkVideoWidth, stopListening); @@ -428,7 +390,6 @@ function getVideoWidthRef( stopListening.register(function stopUpdatingVideoWidthRef() { clearPreviousEventListener(); clearInterval(interval); - ref.finish(); }); return ref; @@ -459,39 +420,18 @@ function getVideoWidthRef( /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -const onLoadedMetadata$ = compatibleListener(["loadedmetadata"]); - -/** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ -const onSeeking$ = compatibleListener(["seeking"]); +const onLoadedMetadata = createCompatibleEventListener(["loadedmetadata"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ -const onSeeked$ = compatibleListener(["seeked"]); - -/** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -const onEnded$ = compatibleListener(["ended"]); - -/** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -const onTimeUpdate$ = compatibleListener(["timeupdate"]); +const onTimeUpdate = createCompatibleEventListener(["timeupdate"]); /** * @param {HTMLElement} element - * @returns {Observable} */ -const onFullscreenChange$ = compatibleListener( +const onFullscreenChange = createCompatibleEventListener( ["fullscreenchange", "FullscreenChange"], // On IE11, fullscreen change events is called MSFullscreenChange @@ -499,91 +439,89 @@ const onFullscreenChange$ = compatibleListener( ); /** - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {TextTrackList} textTrackList */ -const onTextTrackChanges$ = - (textTrackList : TextTrackList) : Observable => - observableMerge(compatibleListener(["addtrack"])(textTrackList), - compatibleListener(["removetrack"])(textTrackList)); +const onTextTrackAdded = createCompatibleEventListener(["addtrack"]); /** - * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {TextTrackList} textTrackList */ -const onSourceOpen$ = compatibleListener(["sourceopen", "webkitsourceopen"]); +const onTextTrackRemoved = createCompatibleEventListener(["removetrack"]); /** * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {Function} listener + * @param {Object} cancelSignal */ -const onSourceClose$ = compatibleListener(["sourceclose", "webkitsourceclose"]); +const onSourceOpen = createCompatibleEventListener(["sourceopen", "webkitsourceopen"]); /** * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {Function} listener + * @param {Object} cancelSignal */ -const onSourceEnded$ = compatibleListener(["sourceended", "webkitsourceended"]); +const onSourceClose = createCompatibleEventListener(["sourceclose", "webkitsourceclose"]); /** - * @param {SourceBuffer} sourceBuffer - * @returns {Observable} + * @param {MediaSource} mediaSource + * @param {Function} listener + * @param {Object} cancelSignal */ -const onUpdate$ = compatibleListener(["update"]); +const onSourceEnded = createCompatibleEventListener(["sourceended", "webkitsourceended"]); /** * @param {MediaSource} mediaSource - * @returns {Observable} + * @param {Function} listener + * @param {Object} cancelSignal + */ +const onSourceBufferUpdate = createCompatibleEventListener(["update"]); + +/** + * @param {SourceBufferList} sourceBuffers + * @param {Function} listener + * @param {Object} cancelSignal */ -const onRemoveSourceBuffers$ = compatibleListener(["onremovesourcebuffer"]); +const onRemoveSourceBuffers = createCompatibleEventListener(["removesourcebuffer"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ -const onEncrypted$ = compatibleListener( +const onEncrypted = createCompatibleEventListener( shouldFavourCustomSafariEME() ? ["needkey"] : ["encrypted", "needkey"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -const onKeyMessage$ = compatibleListener(["keymessage", "message"]); +const onKeyMessage = createCompatibleEventListener(["keymessage", "message"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -const onKeyAdded$ = compatibleListener(["keyadded", "ready"]); +const onKeyAdded = createCompatibleEventListener(["keyadded", "ready"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -const onKeyError$ = compatibleListener(["keyerror", "error"]); +const onKeyError = createCompatibleEventListener(["keyerror", "error"]); /** * @param {MediaKeySession} mediaKeySession - * @returns {Observable} */ -const onKeyStatusesChange$ = compatibleListener(["keystatuseschange"]); +const onKeyStatusesChange = createCompatibleEventListener(["keystatuseschange"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ const onSeeking = createCompatibleEventListener(["seeking"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ const onSeeked = createCompatibleEventListener(["seeked"]); /** * @param {HTMLMediaElement} mediaElement - * @returns {Observable} */ const onEnded = createCompatibleEventListener(["ended"]); @@ -615,24 +553,22 @@ export { getPictureOnPictureStateRef, getVideoVisibilityRef, getVideoWidthRef, - onEncrypted$, + onEncrypted, onEnded, - onEnded$, - onFullscreenChange$, - onKeyAdded$, - onKeyError$, - onKeyMessage$, - onKeyStatusesChange$, - onLoadedMetadata$, - onRemoveSourceBuffers$, + onFullscreenChange, + onKeyAdded, + onKeyError, + onKeyMessage, + onKeyStatusesChange, + onLoadedMetadata, + onRemoveSourceBuffers, onSeeked, - onSeeked$, onSeeking, - onSeeking$, - onSourceClose$, - onSourceEnded$, - onSourceOpen$, - onTextTrackChanges$, - onTimeUpdate$, - onUpdate$, + onSourceClose, + onSourceEnded, + onSourceOpen, + onTimeUpdate, + onSourceBufferUpdate, + onTextTrackAdded, + onTextTrackRemoved, }; diff --git a/src/compat/index.ts b/src/compat/index.ts index b04c9bb3b6..96f805402c 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -55,15 +55,12 @@ import isVTTCue from "./is_vtt_cue"; import makeVTTCue from "./make_vtt_cue"; import onHeightWidthChange from "./on_height_width_change"; import patchWebkitSourceBuffer from "./patch_webkit_source_buffer"; -import play from "./play"; -import setElementSrc$ from "./set_element_src"; // eslint-disable-next-line max-len import shouldReloadMediaSourceOnDecipherabilityUpdate from "./should_reload_media_source_on_decipherability_update"; import shouldRenewMediaKeySystemAccess from "./should_renew_media_key_system_access"; import shouldUnsetMediaKeys from "./should_unset_media_keys"; import shouldValidateMetadata from "./should_validate_metadata"; import shouldWaitForDataBeforeLoaded from "./should_wait_for_data_before_loaded"; -import whenLoadedMetadata$ from "./when_loaded_metadata"; // TODO To remove. This seems to be the only side-effect done on import, which // we would prefer to disallow (both for the understandability of the code and @@ -100,10 +97,8 @@ export { makeVTTCue, MediaSource_, onHeightWidthChange, - play, requestFullscreen, requestMediaKeySystemAccess, - setElementSrc$, setMediaKeys, shouldReloadMediaSourceOnDecipherabilityUpdate, shouldRenewMediaKeySystemAccess, @@ -111,5 +106,4 @@ export { shouldValidateMetadata, shouldWaitForDataBeforeLoaded, tryToChangeSourceBufferType, - whenLoadedMetadata$, }; diff --git a/src/compat/on_height_width_change.ts b/src/compat/on_height_width_change.ts index b4d8435a62..1fd4c44d8b 100644 --- a/src/compat/on_height_width_change.ts +++ b/src/compat/on_height_width_change.ts @@ -66,7 +66,7 @@ const _ResizeObserver : IResizeObserverConstructor | * milliseconds at which we should query that element's size. * @param {HTMLElement} element * @param {number} interval - * @returns {Observable} + * @returns {Object} */ export default function onHeightWidthChange( element : HTMLElement, @@ -77,7 +77,7 @@ export default function onHeightWidthChange( const ref = createSharedReference({ height: initHeight, width: initWidth, - }); + }, cancellationSignal); let lastHeight : number = initHeight; let lastWidth : number = initWidth; diff --git a/src/compat/play.ts b/src/compat/play.ts deleted file mode 100644 index 1f48790334..0000000000 --- a/src/compat/play.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - defer as observableDefer, - Observable, -} from "rxjs"; -import castToObservable from "../utils/cast_to_observable"; -import tryCatch from "../utils/rx-try_catch"; - -/** - * Call play on the media element on subscription and return the response as an - * observable. - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -export default function play(mediaElement : HTMLMediaElement) : Observable { - return observableDefer(() => - // mediaElement.play is not always a Promise. In the improbable case it - // throws, I prefer still to catch to return the error wrapped in an - // Observable - tryCatch(() => castToObservable(mediaElement.play()), undefined) - ); -} diff --git a/src/compat/set_element_src.ts b/src/compat/set_element_src.ts deleted file mode 100644 index 0ef3e6716d..0000000000 --- a/src/compat/set_element_src.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Observable, - Observer, -} from "rxjs"; -import log from "../log"; -import clearElementSrc from "./clear_element_src"; - -/** - * Set an URL to the element's src. - * Emit ``undefined`` when done. - * Unlink src on unsubscription. - * - * @param {HTMLMediaElement} mediaElement - * @param {string} url - * @returns {Observable} - */ -export default function setElementSrc$( - mediaElement : HTMLMediaElement, - url : string -) : Observable { - return new Observable((observer : Observer) => { - log.info("Setting URL to HTMLMediaElement", url); - - mediaElement.src = url; - - observer.next(undefined); - return () => { - clearElementSrc(mediaElement); - }; - }); -} diff --git a/src/compat/should_reload_media_source_on_decipherability_update.ts b/src/compat/should_reload_media_source_on_decipherability_update.ts index 91c943c908..780f81f14c 100644 --- a/src/compat/should_reload_media_source_on_decipherability_update.ts +++ b/src/compat/should_reload_media_source_on_decipherability_update.ts @@ -21,12 +21,12 @@ * We found that on all Widevine targets tested, a simple seek is sufficient. * As widevine clients make a good chunk of users, we can make a difference * between them and others as it is for the better. - * @param {string|null} currentKeySystem + * @param {string|undefined} currentKeySystem * @returns {Boolean} */ export default function shouldReloadMediaSourceOnDecipherabilityUpdate( - currentKeySystem : string | null + currentKeySystem : string | undefined ) : boolean { - return currentKeySystem === null || + return currentKeySystem === undefined || currentKeySystem.indexOf("widevine") < 0; } diff --git a/src/compat/when_loaded_metadata.ts b/src/compat/when_loaded_metadata.ts deleted file mode 100644 index 2fad2cec22..0000000000 --- a/src/compat/when_loaded_metadata.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Observable, - of as observableOf, - take, -} from "rxjs"; -import { READY_STATES } from "./browser_compatibility_types"; -import { onLoadedMetadata$ } from "./event_listeners"; - -/** - * Returns an observable emitting a single time, as soon as a seek is possible - * (the metadata are loaded). - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -export default function whenLoadedMetadata$( - mediaElement : HTMLMediaElement -) : Observable { - if (mediaElement.readyState >= READY_STATES.HAVE_METADATA) { - return observableOf(null); - } else { - return onLoadedMetadata$(mediaElement) - .pipe(take(1)); - } -} diff --git a/src/core/README.md b/src/core/README.md index 7f8f4acbd4..1a0a441c63 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -11,7 +11,7 @@ Those modules are: implementing it. - - __the `Init` (_./init)__ + - __the `ContentInitializer` (_./init)__ Initialize playback and connects different modules between one another. diff --git a/src/core/adaptive/adaptive_representation_selector.ts b/src/core/adaptive/adaptive_representation_selector.ts index bb25b68928..d3fb1175ec 100644 --- a/src/core/adaptive/adaptive_representation_selector.ts +++ b/src/core/adaptive/adaptive_representation_selector.ts @@ -49,6 +49,23 @@ import PendingRequestsStore, { import RepresentationScoreCalculator from "./utils/representation_score_calculator"; import selectOptimalRepresentation from "./utils/select_optimal_representation"; +// Create default shared references + +const manualBitrateDefaultRef = createSharedReference(-1); +manualBitrateDefaultRef.finish(); + +const minAutoBitrateDefaultRef = createSharedReference(0); +minAutoBitrateDefaultRef.finish(); + +const maxAutoBitrateDefaultRef = createSharedReference(Infinity); +maxAutoBitrateDefaultRef.finish(); + +const limitWidthDefaultRef = createSharedReference(undefined); +limitWidthDefaultRef.finish(); + +const throttleBitrateDefaultRef = createSharedReference(Infinity); +throttleBitrateDefaultRef.finish(); + /** * Select the most adapted Representation according to the network and buffer * metrics it receives. @@ -99,22 +116,22 @@ export default function createAdaptiveRepresentationSelector( const bandwidthEstimator = _getBandwidthEstimator(type); const manualBitrate = takeFirstSet>( manualBitrates[type], - createSharedReference(-1)); + manualBitrateDefaultRef); const minAutoBitrate = takeFirstSet>( minAutoBitrates[type], - createSharedReference(0)); + minAutoBitrateDefaultRef); const maxAutoBitrate = takeFirstSet>( maxAutoBitrates[type], - createSharedReference(Infinity)); + maxAutoBitrateDefaultRef); const initialBitrate = takeFirstSet(initialBitrates[type], 0); const filters = { limitWidth: takeFirstSet>( throttlers.limitWidth[type], - createSharedReference(undefined)), + limitWidthDefaultRef), throttleBitrate: takeFirstSet>( throttlers.throttleBitrate[type], throttlers.throttle[type], - createSharedReference(Infinity)), + throttleBitrateDefaultRef), }; return getEstimateReference({ bandwidthEstimator, context, @@ -198,7 +215,8 @@ function getEstimateReference( * This TaskCanceller is used both for restarting estimates with a new * configuration and to cancel them altogether. */ - let currentEstimatesCanceller = new TaskCanceller({ cancelOn: stopAllEstimates }); + let currentEstimatesCanceller = new TaskCanceller(); + currentEstimatesCanceller.linkToSignal(stopAllEstimates); // Create `ISharedReference` on which estimates will be emitted. const estimateRef = createEstimateReference(manualBitrate.getValue(), @@ -217,6 +235,16 @@ function getEstimateReference( representations : Representation[], innerCancellationSignal : CancellationSignal ) : ISharedReference { + if (representations.length === 0) { + // No Representation given, return `null` as documented + return createSharedReference({ + representation: null, + bitrate: undefined, + knownStableBitrate: undefined, + manual: false, + urgent: true, + }); + } if (manualBitrateVal >= 0) { // A manual bitrate has been set. Just choose Representation according to it. const manualRepresentation = selectOptimalRepresentation(representations, @@ -274,7 +302,7 @@ function getEstimateReference( /** Reference through which estimates are emitted. */ const innerEstimateRef = createSharedReference(getCurrentEstimate()); - // subscribe to subsequent playback observations + // Listen to playback observations playbackObserver.listen((obs) => { lastPlaybackObservation = obs; updateEstimate(); @@ -464,7 +492,8 @@ function getEstimateReference( const manualBitrateVal = manualBitrate.getValue(); const representations = representationsRef.getValue(); currentEstimatesCanceller.cancel(); - currentEstimatesCanceller = new TaskCanceller({ cancelOn: stopAllEstimates }); + currentEstimatesCanceller = new TaskCanceller(); + currentEstimatesCanceller.linkToSignal(stopAllEstimates); const newRef = createEstimateReference( manualBitrateVal, representations, @@ -566,8 +595,10 @@ export interface IABREstimate { /** * The Representation considered as the most adapted to the current network * and playback conditions. + * `null` in the rare occurence where there is no `Representation` to choose + * from. */ - representation: Representation; + representation: Representation | null; /** * If `true`, the current `representation` suggested should be switched to as * soon as possible. For example, you might want to interrupt all pending diff --git a/src/core/api/README.md b/src/core/api/README.md index 8ad5b841d4..b43fff56b2 100644 --- a/src/core/api/README.md +++ b/src/core/api/README.md @@ -9,39 +9,6 @@ As such, its main roles are to: - redirecting events to the user - -## `public_api.ts`: the largest file here ###################################### - -`public_api.ts` is at the time of writing by far the longest file in all the -RxPlayer, with more than 2000 lines. - -One of reason is that the API needs to have a considerable state because most of -the other modules rely on Observables. - -I'll explain: -The API can't just interogate at any time the concerned module as if it was a -class with methods. Here most modules are functions which send events. - -The problem is that a library user might want to have an information at any -given moment (for example, the current bitrate), which internally is only -sent as an event by some module. -It is thus the role of the API to store that information when it receives -this event to then communicate it back to the user. - - Also, as the API is a single class with a huge private state, being able - to see those state mutations in a single file allows us to better think about - how it all works. - - Another huge part of that file is actually the entire public API, as small - functions. - - Still, we did some efforts to reduce the size of that file. For example, some - long argument-parsing code has been moved out of this file, into - `core/api/option_utils`. We might find other ways to reduce that size in the - future, but that's not a main concern for now. - - - ## Subparts #################################################################### To facilitate those actions, the API relies on multiple building blocks: diff --git a/src/core/api/debug/buffer_graph.ts b/src/core/api/debug/buffer_graph.ts new file mode 100644 index 0000000000..44e5da72e4 --- /dev/null +++ b/src/core/api/debug/buffer_graph.ts @@ -0,0 +1,247 @@ +import { Representation } from "../../../manifest"; +import { IBufferedChunk } from "../../segment_buffers"; + +const BUFFER_WIDTH_IN_SECONDS = 10000; + +const COLORS = [ + "#2ab7ca", + "#fed766", + "#4dd248", + "#a22c28", + "#556b2f", // darkolivegreen + "#add8e6", // lightblue + "#90ee90", // lightgreen + "#444444", + "#40bfc1", + "#57557e", + "#fbe555", +]; + +export interface ISegmentBufferGrapUpdateData { + currentTime : number; + inventory : IBufferedChunk[]; + width : number; + height : number; + minimumPosition : number | undefined; + maximumPosition : number | undefined; +} + +export default class SegmentBufferGraph { + /** Link buffered Representation to their corresponding color. */ + private readonly _colorMap : WeakMap; + + /** Current amount of colors chosen to represent the various Representation. */ + private _currNbColors : number; + + /** Canvas that will contain the buffer graph itself. */ + private readonly _canvasElt : HTMLCanvasElement; + + private readonly _canvasCtxt : CanvasRenderingContext2D | null; + + constructor(canvasElt : HTMLCanvasElement) { + this._colorMap = new WeakMap(); + this._currNbColors = 0; + this._canvasElt = canvasElt; + this._canvasCtxt = this._canvasElt.getContext("2d"); + this.clear(); + } + + public clear() { + if (this._canvasCtxt !== null) { + this._canvasCtxt.clearRect(0, 0, this._canvasElt.width, this._canvasElt.height); + } + } + + public update(data : ISegmentBufferGrapUpdateData) : void { + if (this._canvasCtxt === null) { + return; + } + const { + inventory, + currentTime, + width, + height, + } = data; + this._canvasElt.style.width = `${width}px`; + this._canvasElt.style.height = `${height}px`; + this._canvasElt.width = width; + this._canvasElt.height = height; + this.clear(); + let minimumPoint : number; + if (data.minimumPosition !== undefined) { + if (inventory.length > 0) { + minimumPoint = Math.min(data.minimumPosition, inventory[0].start); + } else { + minimumPoint = data.minimumPosition; + } + } else { + minimumPoint = inventory[0]?.start ?? 0; + } + let maximumPoint : number; + if (data.maximumPosition !== undefined) { + if (inventory.length > 0) { + maximumPoint = Math.max(data.maximumPosition, + inventory[inventory.length - 1].end); + } else { + maximumPoint = data.maximumPosition; + } + } else { + maximumPoint = inventory[inventory.length - 1]?.end ?? + 1000; + } + minimumPoint = Math.min(currentTime, minimumPoint); + maximumPoint = Math.max(currentTime, maximumPoint); + + let minimumPosition; + let maximumPosition; + if (maximumPoint - minimumPoint > BUFFER_WIDTH_IN_SECONDS) { + if (currentTime === undefined) { + minimumPosition = minimumPoint; + maximumPosition = maximumPoint; + } else if (maximumPoint - currentTime < BUFFER_WIDTH_IN_SECONDS / 2) { + maximumPosition = maximumPoint; + minimumPosition = maximumPoint - BUFFER_WIDTH_IN_SECONDS; + } else if (currentTime - minimumPoint < BUFFER_WIDTH_IN_SECONDS / 2) { + minimumPosition = minimumPoint; + maximumPosition = minimumPoint + BUFFER_WIDTH_IN_SECONDS; + } else { + minimumPosition = currentTime - BUFFER_WIDTH_IN_SECONDS / 2; + maximumPosition = currentTime + BUFFER_WIDTH_IN_SECONDS / 2; + } + } else { + minimumPosition = minimumPoint; + maximumPosition = maximumPoint; + } + if (minimumPosition >= maximumPosition) { + this.clear(); + return; + } + + const currentRangesScaled = scaleSegments(inventory, + minimumPosition, + maximumPosition); + + for (let i = 0; i < currentRangesScaled.length; i++) { + this._paintRange(currentRangesScaled[i], width, height); + } + + if (currentTime !== undefined) { + paintCurrentPosition(currentTime, + minimumPosition, + maximumPosition, + this._canvasCtxt, + width, + height); + } + } + + /** + * Paint a given range in the canvas + * @param {Object} rangeScaled - Buffered segment information with added + * "scaling" information to know where it fits in the canvas. + */ + private _paintRange( + rangeScaled : IScaledChunk, + width : number, + height : number + ) : void { + if (this._canvasCtxt === null) { + return; + } + const startX = rangeScaled.scaledStart * width; + const endX = rangeScaled.scaledEnd * width; + this._canvasCtxt.fillStyle = this._getColorForRepresentation( + rangeScaled.info.infos.representation + ); + this._canvasCtxt.fillRect( + Math.ceil(startX), + 0, + Math.ceil(endX - startX), + height + ); + } + + private _getColorForRepresentation( + representation : Representation + ) : string { + const color = this._colorMap.get(representation); + if (color !== undefined) { + return color; + } + const newColor = COLORS[this._currNbColors % COLORS.length]; + this._currNbColors++; + this._colorMap.set(representation, newColor); + return newColor; + } +} + +/** + * Represent the current position in the canvas. + * @param {number|undefined} position - The current position + * @param {number} minimumPosition - minimum possible position represented in + * the canvas. + * @param {number} maximumPosition - maximum possible position represented in + * the canvas. + * @param {Object} canvasCtx - The canvas' 2D context + */ +function paintCurrentPosition( + position : number, + minimumPosition : number, + maximumPosition : number, + canvasCtx : CanvasRenderingContext2D, + width : number, + height : number +) { + if (typeof position === "number" && + position >= minimumPosition && + position < maximumPosition) + { + const lengthCanvas = maximumPosition - minimumPosition; + canvasCtx.fillStyle = "#FF0000"; + canvasCtx.fillRect(Math.ceil((position - minimumPosition) / + lengthCanvas * width) - 1, + 5, + 5, + height); + } +} + +/** + * Scale given bufferedData in terms of percentage between the minimum and + * maximum position. Filter out ranges which are not part of it. + * @param {Array.} bufferedData + * @param {number} minimumPosition + * @param {number} maximumPosition + * @returns {Array.} + */ +function scaleSegments( + bufferedData : IBufferedChunk[], + minimumPosition : number, + maximumPosition : number +) : IScaledChunk[] { + const scaledSegments = []; + const wholeDuration = maximumPosition - minimumPosition; + for (let i = 0; i < bufferedData.length; i++) { + const info = bufferedData[i]; + const start = info.bufferedStart === undefined ? + info.start : + info.bufferedStart; + const end = info.bufferedEnd === undefined ? + info.end : + info.bufferedEnd; + if (end > minimumPosition && start < maximumPosition) { + const startPoint = Math.max(start - minimumPosition, 0); + const endPoint = Math.min(end - minimumPosition, maximumPosition); + const scaledStart = startPoint / wholeDuration; + const scaledEnd = endPoint / wholeDuration; + scaledSegments.push({ scaledStart, scaledEnd, info }); + } + } + return scaledSegments; +} + +interface IScaledChunk { + scaledStart: number; + scaledEnd: number; + info: IBufferedChunk; +} diff --git a/src/core/api/debug/buffer_size_graph.ts b/src/core/api/debug/buffer_size_graph.ts new file mode 100644 index 0000000000..fcdf5045c7 --- /dev/null +++ b/src/core/api/debug/buffer_size_graph.ts @@ -0,0 +1,130 @@ +/** + * Maximum history of the buffer size that will be displayed, in milliseconds. + * For example, a value of `3000` indicates that we will just show at most the + * buffer size evolution during the last 3 seconds. + */ +const TIME_SAMPLES_MS = 30000; + +/** + * At minimum, that value will be taken in the chart as a maximum buffer size, + * in seconds. + * If samples go higher than this size, the chart will adapt automatically to + * a higher scale. + * However if values go below that value, the chart won't scale down more than + * this. + */ +const MINIMUM_MAX_BUFFER_SIZE = 20; + +export default class BufferSizeGraph { + private _history : IHistoryItem[]; + + /** Canvas that will contain the buffer size graph itself. */ + private readonly _canvasElt : HTMLCanvasElement; + + private readonly _canvasCtxt : CanvasRenderingContext2D | null; + + constructor(canvasElt : HTMLCanvasElement) { + this._canvasElt = canvasElt; + this._canvasCtxt = this._canvasElt.getContext("2d"); + this._history = []; + } + + public pushBufferSize(bufferSize : number) : void { + const now = performance.now(); + this._history.push({ timestamp: now, bufferSize }); + if (this._history.length > 0) { + const minimumTime = now - TIME_SAMPLES_MS; + let i; + for (i = this._history.length - 1; i >= 1; i--) { + if (this._history[i].timestamp <= minimumTime) { + break; + } + } + this._history = this._history.slice(i); + } else { + this._history = []; + } + } + + public clear() { + if (this._canvasCtxt !== null) { + this._canvasCtxt.clearRect(0, 0, this._canvasElt.width, this._canvasElt.height); + } + } + + public reRender(width : number, height : number) : void { + this._canvasElt.style.width = `${width}px`; + this._canvasElt.style.height = `${height}px`; + this._canvasElt.width = width; + this._canvasElt.height = height; + this.clear(); + const history = this._history; + const canvasCtx = this._canvasCtxt; + + if (history.length === 0) { + return; + } + + const currentMaxSize = getNewMaxBufferSize(); + const minDate = history[0].timestamp; + + const gridHeight = height / currentMaxSize; + const gridWidth = width / TIME_SAMPLES_MS; + + drawData(); + + /** + * Get more appropriate maximum buffer size to put on top of the graph + * according to current history. + */ + function getNewMaxBufferSize() { + const maxPoint = Math.max(...history.map(d => d.bufferSize)); + return Math.max(maxPoint + 5, MINIMUM_MAX_BUFFER_SIZE); + } + + /** + * Draw all data contained in `history` in the canvas given. + */ + function drawData() { + if (canvasCtx === null) { + return; + } + canvasCtx.beginPath(); + canvasCtx.fillStyle = "rgb(200, 100, 200)"; + for (let i = 1; i < history.length; i++) { + const diff = dateToX(history[i].timestamp) - + dateToX(history[i - 1].timestamp); + const y = height - bufferValueToHeight(history[i].bufferSize); + canvasCtx.fillRect( + dateToX(history[i - 1].timestamp), + y, + diff, + height); + } + canvasCtx.stroke(); + } + + /** + * Convert a value of a given data point, to a u coordinate in the canvas. + * @param {number} bufferVal - Value to convert + * @returns {number} - y coordinate + */ + function bufferValueToHeight(bufferVal : number) : number { + return height - (currentMaxSize - bufferVal) * gridHeight; + } + + /** + * Convert a date of a given data point, to a x coordinate in the canvas. + * @param {number} date - Date to convert, in milliseconds + * @returns {number} - x coordinate + */ + function dateToX(date : number) : number { + return (date - minDate) * gridWidth; + } + } +} + +interface IHistoryItem { + timestamp : number; + bufferSize : number; +} diff --git a/src/core/api/debug/constants.ts b/src/core/api/debug/constants.ts new file mode 100644 index 0000000000..bfcc9f6dcc --- /dev/null +++ b/src/core/api/debug/constants.ts @@ -0,0 +1,2 @@ +/** Interval at which the various debug metrics will be refreshed. */ +export const DEFAULT_REFRESH_INTERVAL = 1000; diff --git a/src/core/api/debug/index.ts b/src/core/api/debug/index.ts new file mode 100644 index 0000000000..2a72e10256 --- /dev/null +++ b/src/core/api/debug/index.ts @@ -0,0 +1,3 @@ +import renderDebugElement from "./render"; + +export default renderDebugElement; diff --git a/src/core/api/debug/modules/general_info.ts b/src/core/api/debug/modules/general_info.ts new file mode 100644 index 0000000000..cd8491fa1d --- /dev/null +++ b/src/core/api/debug/modules/general_info.ts @@ -0,0 +1,204 @@ +import { CancellationSignal } from "../../../../utils/task_canceller"; +import RxPlayer from "../../public_api"; +import { DEFAULT_REFRESH_INTERVAL } from "../constants"; +import { + createCompositeElement, + createElement, + createMetricTitle, + isExtendedMode, +} from "../utils"; + +export default function constructDebugGeneralInfo( + instance : RxPlayer, + parentElt : HTMLElement, + cancelSignal : CancellationSignal +) : HTMLElement { + const generalInfoElt = createElement("div"); + const adaptationsElt = createElement("div"); + const representationsElt = createElement("div"); + updateGeneralInfo(); + const generalInfoItv = setInterval(() => { + updateGeneralInfo(); + }, DEFAULT_REFRESH_INTERVAL); + cancelSignal.register(() => { + clearInterval(generalInfoItv); + }); + + return createCompositeElement("div", [ + generalInfoElt, + adaptationsElt, + representationsElt, + ]); + function updateGeneralInfo() { + const videoElement = instance.getVideoElement(); + if (videoElement === null) { + // disposed player. Clean-up everything + generalInfoElt.innerHTML = ""; + adaptationsElt.innerHTML = ""; + representationsElt.innerHTML = ""; + clearInterval(generalInfoItv); + return; + } else { + const currentTime = instance.getPosition(); + const bufferGap = instance.getVideoBufferGap(); + const bufferGapStr = bufferGap === Infinity ? "0" : bufferGap.toFixed(2); + const valuesLine1 : Array<[string, string]> = [ + ["ct", currentTime.toFixed(2)], + ["bg", bufferGapStr], + ["rs", String(videoElement.readyState)], + ["pr", String(videoElement.playbackRate)], + ["sp", String(instance.getPlaybackRate())], + ["pa", String(videoElement.paused ? 1 : 0)], + ["en", String(videoElement.ended ? 1 : 0)], + ["li", String(instance.isLive() ? 1 : 0)], + ["wba", String(instance.getWantedBufferAhead())], + ["st", `"${instance.getPlayerState()}"`], + ]; + + const valuesLine2 : Array<[string, string]> = []; + const ks = instance.getKeySystemConfiguration(); + if (ks !== null) { + valuesLine2.push(["ks", ks.keySystem]); + } + const mbb = instance.getMaxBufferBehind(); + if (mbb !== Infinity) { + valuesLine2.push(["mbb", String(mbb)]); + } + const mba = instance.getMaxBufferAhead(); + if (mba !== Infinity) { + valuesLine2.push(["mba", String(mba)]); + } + const mia = instance.getMinAudioBitrate(); + if (mia !== 0) { + valuesLine2.push(["mia", String(mia)]); + } + const miv = instance.getMinVideoBitrate(); + if (miv !== 0) { + valuesLine2.push(["miv", String(miv)]); + } + const maa = instance.getMaxAudioBitrate(); + if (maa !== Infinity) { + valuesLine2.push(["maa", String(maa)]); + } + const mav = instance.getMaxVideoBitrate(); + if (mav !== Infinity) { + valuesLine2.push(["mav", String(mav)]); + } + const fab = instance.getManualAudioBitrate(); + if (fab >= 0) { + valuesLine2.push(["fab", String(fab)]); + } + const fvb = instance.getManualVideoBitrate(); + if (fvb >= 0) { + valuesLine2.push(["fvb", String(fvb)]); + } + const mbs = instance.getMaxVideoBufferSize(); + if (mbs !== Infinity) { + valuesLine2.push(["mbs", String(mbs)]); + } + const minPos = instance.getMinimumPosition(); + if (minPos !== null) { + valuesLine1.push(["mip", minPos.toFixed(2)]); + valuesLine2.push(["dmi", (currentTime - minPos).toFixed(2)]); + } + const maxPos = instance.getMaximumPosition(); + if (maxPos !== null) { + valuesLine1.push(["map", maxPos.toFixed(2)]); + valuesLine2.push(["dma", (maxPos - currentTime).toFixed(2)]); + } + const valuesLine3 : Array<[string, string]> = []; + const error = instance.getError(); + if (error !== null) { + valuesLine3.push(["er", `"${String(error)}"`]); + } + generalInfoElt.innerHTML = ""; + for (const valueSet of [valuesLine1, valuesLine2, valuesLine3]) { + if (valueSet.length > 0) { + const lineInfoElt = createElement("div"); + for (const value of valueSet) { + lineInfoElt.appendChild(createMetricTitle(value[0])); + lineInfoElt.appendChild(createElement("span", { + textContent: value[1] + " ", + })); + } + generalInfoElt.appendChild(lineInfoElt); + } + } + if (isExtendedMode(parentElt)) { + const url = instance.getUrl(); + if (url !== undefined) { + const reducedUrl = url.length > 100 ? + url.substring(0, 99) + "…" : + url; + + generalInfoElt.appendChild(createCompositeElement("div", [ + createMetricTitle("url"), + createElement("span", { + textContent: reducedUrl, + }), + ])); + } + } + } + if (isExtendedMode(parentElt)) { + const videoId = instance.getAvailableVideoTracks().map(({ id, active }) => + active ? `*${id}` : id); + const audioId = instance.getAvailableAudioTracks().map(({ id, active }) => + active ? `*${id}` : id); + const textId = instance.getAvailableTextTracks().map(({ id, active }) => + active ? `*${id}` : id); + adaptationsElt.innerHTML = ""; + if (videoId.length > 0) { + let textContent = `${videoId.length}:${videoId.join(" ")} `; + if (textContent.length > 100) { + textContent = textContent.substring(0, 98) + "… "; + } + const videoAdaps = createCompositeElement("div", [ + createMetricTitle("vt"), + createElement("span", { textContent }), + ]); + adaptationsElt.appendChild(videoAdaps); + } + if (audioId.length > 0) { + let textContent = `${audioId.length}:${audioId.join(" ")} `; + if (textContent.length > 100) { + textContent = textContent.substring(0, 98) + "… "; + } + const audioAdaps = createCompositeElement("div", [ + createMetricTitle("at"), + createElement("span", { textContent }), + ]); + adaptationsElt.appendChild(audioAdaps); + } + if (textId.length > 0) { + let textContent = `${textId.length}:${textId.join(" ")} `; + if (textContent.length > 100) { + textContent = textContent.substring(0, 98) + "… "; + } + const textAdaps = createCompositeElement("div", [ + createMetricTitle("tt"), + createElement("span", { textContent }), + ]); + adaptationsElt.appendChild(textAdaps); + } + const videoBitrates = instance.getAvailableVideoBitrates(); + const audioBitrates = instance.getAvailableAudioBitrates(); + representationsElt.innerHTML = ""; + if (videoBitrates.length > 0) { + representationsElt.appendChild(createMetricTitle("vb")); + representationsElt.appendChild(createElement("span", { + textContent: videoBitrates.join(" ") + " ", + })); + } + if (audioBitrates.length > 0) { + representationsElt.appendChild(createMetricTitle("ab")); + representationsElt.appendChild(createElement("span", { + textContent: audioBitrates.join(" ") + " ", + })); + } + } else { + adaptationsElt.innerHTML = ""; + representationsElt.innerHTML = ""; + } + } +} diff --git a/src/core/api/debug/modules/segment_buffer_content.ts b/src/core/api/debug/modules/segment_buffer_content.ts new file mode 100644 index 0000000000..65c3f2a8d0 --- /dev/null +++ b/src/core/api/debug/modules/segment_buffer_content.ts @@ -0,0 +1,155 @@ +import { + IAdaptation, + IPeriod, + IRepresentation, +} from "../../../../public_types"; +import isNullOrUndefined from "../../../../utils/is_null_or_undefined"; +import { CancellationSignal } from "../../../../utils/task_canceller"; +import { IBufferType } from "../../../segment_buffers"; +import RxPlayer from "../../public_api"; +import SegmentBufferGraph from "../buffer_graph"; +import { DEFAULT_REFRESH_INTERVAL } from "../constants"; +import { + createElement, + createGraphCanvas, + createMetricTitle, + isExtendedMode, +} from "../utils"; + +export default function createSegmentBufferGraph( + instance : RxPlayer, + bufferType : IBufferType, + title : string, + parentElt : HTMLElement, + cancelSignal : CancellationSignal +) : HTMLElement { + const bufferGraphWrapper = createElement("div"); + const bufferTitle = createMetricTitle(title); + const canvasElt = createGraphCanvas(); + const currentRangeRepInfoElt = createElement("div"); + const loadingRangeRepInfoElt = createElement("div"); + const bufferGraph = new SegmentBufferGraph(canvasElt); + const intervalId = setInterval(update, DEFAULT_REFRESH_INTERVAL); + cancelSignal.register(() => { + clearInterval(intervalId); + }); + bufferGraphWrapper.appendChild(bufferTitle); + bufferGraphWrapper.appendChild(canvasElt); + bufferGraphWrapper.appendChild(currentRangeRepInfoElt); + bufferGraphWrapper.appendChild(loadingRangeRepInfoElt); + bufferGraphWrapper.style.padding = "5px 0px"; + update(); + return bufferGraphWrapper; + + function update() { + if (instance.getVideoElement() === null) { + // disposed player. Clean-up everything + bufferGraphWrapper.style.display = "none"; + bufferGraphWrapper.innerHTML = ""; + clearInterval(intervalId); + return; + } + const showAllInfo = isExtendedMode(parentElt); + const inventory = instance.__priv_getSegmentBufferContent(bufferType); + if (inventory === null) { + bufferGraphWrapper.style.display = "none"; + currentRangeRepInfoElt.innerHTML = ""; + loadingRangeRepInfoElt.innerHTML = ""; + } else { + bufferGraphWrapper.style.display = "block"; + const currentTime = instance.getPosition(); + const width = Math.min(parentElt.clientWidth - 150, 600); + bufferGraph.update({ + currentTime, + minimumPosition: instance.getMinimumPosition() ?? undefined, + maximumPosition: instance.getMaximumPosition() ?? undefined, + inventory, + width, + height: 10, + }); + + if (!showAllInfo) { + currentRangeRepInfoElt.innerHTML = ""; + loadingRangeRepInfoElt.innerHTML = ""; + return; + } + + currentRangeRepInfoElt.innerHTML = ""; + for (let i = 0; i < inventory.length; i++) { + const rangeInfo = inventory[i]; + const { bufferedStart, bufferedEnd, infos } = rangeInfo; + if (bufferedStart !== undefined && bufferedEnd !== undefined && + currentTime >= bufferedStart && currentTime < bufferedEnd) + { + currentRangeRepInfoElt.appendChild(createMetricTitle("play")); + currentRangeRepInfoElt.appendChild(createElement("span", { + textContent: constructRepresentationInfo(infos), + })); + break; + } + } + + loadingRangeRepInfoElt.innerHTML = ""; + const rep = instance.getCurrentRepresentations()?.[bufferType]; + const adap = instance.getCurrentAdaptations()?.[bufferType]; + const manifest = instance.getManifest(); + if (manifest !== null && !isNullOrUndefined(rep) && !isNullOrUndefined(adap)) { + const period = manifest.getPeriodForTime(currentTime); + if (period !== undefined) { + loadingRangeRepInfoElt.appendChild(createMetricTitle("load")); + loadingRangeRepInfoElt.appendChild(createElement("span", { + textContent: constructRepresentationInfo({ + period, + adaptation: adap, + representation: rep, + }), + })); + } + } + } + } +} + +function constructRepresentationInfo( + content : { + period : IPeriod; + adaptation : IAdaptation; + representation : IRepresentation; + } +) : string { + const period = content.period; + const { language, + isAudioDescription, + isClosedCaption, + isTrickModeTrack, + isSignInterpreted, + type: bufferType } = content.adaptation; + const { id, height, width, bitrate, codec } = content.representation; + let representationInfo = `"${id}" `; + if (height !== undefined && width !== undefined) { + representationInfo += `${width}x${height} `; + } + if (bitrate !== undefined) { + representationInfo += `(${(bitrate / 1000).toFixed(0)}kbps) `; + } + if (codec !== undefined) { + representationInfo += `c:"${codec}" `; + } + if (language !== undefined) { + representationInfo += `l:"${language}" `; + } + if (bufferType === "video" && typeof isSignInterpreted === "boolean") { + representationInfo += `si:${isSignInterpreted ? 1 : 0} `; + } + if (bufferType === "video" && typeof isTrickModeTrack === "boolean") { + representationInfo += `tm:${isTrickModeTrack ? 1 : 0} `; + } + if (bufferType === "audio" && typeof isAudioDescription === "boolean") { + representationInfo += `ad:${isAudioDescription ? 1 : 0} `; + } + if (bufferType === "text" && typeof isClosedCaption === "boolean") { + representationInfo += `cc:${isClosedCaption ? 1 : 0} `; + } + representationInfo += `p:${period.start}-${period.end ?? "?"}`; + return representationInfo; +} diff --git a/src/core/api/debug/modules/segment_buffer_size.ts b/src/core/api/debug/modules/segment_buffer_size.ts new file mode 100644 index 0000000000..b7af4e0a15 --- /dev/null +++ b/src/core/api/debug/modules/segment_buffer_size.ts @@ -0,0 +1,48 @@ +import { CancellationSignal } from "../../../../utils/task_canceller"; +import RxPlayer from "../../public_api"; +import BufferSizeGraph from "../buffer_size_graph"; +import { DEFAULT_REFRESH_INTERVAL } from "../constants"; +import { + createElement, + createGraphCanvas, + createMetricTitle, +} from "../utils"; + +export default function createSegmentBufferSizeGraph( + instance : RxPlayer, + parentElt : HTMLElement, + cancelSignal : CancellationSignal +) : HTMLElement { + const bufferSizeGraphWrapperElt = createElement("div"); + const bufferSizeTitle = createMetricTitle("bgap"); + const canvasElt = createGraphCanvas(); + + const bufferSizeGraph = new BufferSizeGraph(canvasElt); + const intervalId = setInterval(addBufferSize, DEFAULT_REFRESH_INTERVAL); + cancelSignal.register(() => { + clearInterval(intervalId); + }); + bufferSizeGraphWrapperElt.appendChild(bufferSizeTitle); + bufferSizeGraphWrapperElt.appendChild(canvasElt); + bufferSizeGraphWrapperElt.style.padding = "7px 0px"; + addBufferSize(); + return bufferSizeGraphWrapperElt; + + function addBufferSize() { + if (instance.getVideoElement() === null) { + // disposed player. Clean-up everything + bufferSizeGraphWrapperElt.innerHTML = ""; + clearInterval(intervalId); + return; + } + const bufferGap = instance.getVideoBufferGap(); + if (bufferGap === Infinity) { + bufferSizeGraph.pushBufferSize(0); + } else { + bufferSizeGraph.pushBufferSize(bufferGap); + } + const width = Math.min(parentElt.clientWidth - 150, 600); + bufferSizeGraph.reRender(width, 10); + } +} + diff --git a/src/core/api/debug/render.ts b/src/core/api/debug/render.ts new file mode 100644 index 0000000000..d7826c1659 --- /dev/null +++ b/src/core/api/debug/render.ts @@ -0,0 +1,40 @@ +import { CancellationSignal } from "../../../utils/task_canceller"; +import RxPlayer from "../public_api"; +import constructDebugGeneralInfo from "./modules/general_info"; +import createSegmentBufferGraph from "./modules/segment_buffer_content"; +import createSegmentBufferSizeGraph from "./modules/segment_buffer_size"; +import { createCompositeElement, createElement } from "./utils"; + +export default function renderDebugElement( + parentElt : HTMLElement, + instance : RxPlayer, + cancelSignal : CancellationSignal +) : void { + const debugElementTitleElt = createElement("div", { + textContent: "RxPlayer Debug Information", + }); + debugElementTitleElt.style.fontWeight = "bold"; + debugElementTitleElt.style.borderBottom = "1px solid white"; + debugElementTitleElt.style.marginBottom = "5px"; + debugElementTitleElt.style.fontStyle = "italic"; + + const debugWrapperElt = createCompositeElement("div", [ + debugElementTitleElt, + constructDebugGeneralInfo(instance, parentElt, cancelSignal), + createSegmentBufferGraph(instance, "video", "vbuf", parentElt, cancelSignal), + createSegmentBufferGraph(instance, "audio", "abuf", parentElt, cancelSignal), + createSegmentBufferGraph(instance, "text", "tbuf", parentElt, cancelSignal), + createSegmentBufferSizeGraph(instance, parentElt, cancelSignal), + ]); + debugWrapperElt.style.backgroundColor = "#00000099"; + debugWrapperElt.style.padding = "7px"; + debugWrapperElt.style.fontSize = "13px"; + debugWrapperElt.style.fontFamily = "mono"; + debugWrapperElt.style.color = "white"; + debugWrapperElt.style.display = "inline-block"; + debugWrapperElt.style.bottom = "0px"; + parentElt.appendChild(debugWrapperElt); + cancelSignal.register(() => { + parentElt.removeChild(debugWrapperElt); + }); +} diff --git a/src/core/api/debug/utils.ts b/src/core/api/debug/utils.ts new file mode 100644 index 0000000000..8b944d6157 --- /dev/null +++ b/src/core/api/debug/utils.ts @@ -0,0 +1,103 @@ +/** + * Create an HTML element. + * @param {string} elementName - The element's name, like `"div"` for example. + * @param {Object} [options={}] - Optional attributes for the element. + * @param {string} [options.textContent] - Inner text for that element. + * @param {string} [options.className] - Value for a `class` attribute + * associated to this element. + * @param {string} [options.href] - Value for a `href` attribute + * associated to this element. + * @returns {HTMLElement} + */ +export function createElement( + elementName : "input", + opts? : CreateElementOptions | undefined +) : HTMLInputElement; +export function createElement( + elementName : "button", + opts? : CreateElementOptions | undefined +) : HTMLButtonElement; +export function createElement( + elementName : "a", + opts? : CreateElementOptions | undefined +) : HTMLLinkElement; +export function createElement( + elementName : "canvas", + opts? : CreateElementOptions | undefined +) : HTMLCanvasElement; +export function createElement( + elementName : string, + opts? : CreateElementOptions | undefined +) : HTMLElement; +export function createElement( + elementName : string, + { + textContent, + className, + } : CreateElementOptions | undefined = {} +) : HTMLElement { + const elt = document.createElement(elementName); + if (className !== undefined) { + elt.className = className; + } + if (textContent !== undefined) { + elt.textContent = textContent; + } + return elt; +} + +interface CreateElementOptions { + textContent? : string | undefined; + className? : string | undefined; +} + +/** + * Create an HTML element which may contain mutiple HTML sub-elements. + * @param {string} rootElementName - The element's name, like `"div"` for + * example. + * @param {Array.} parts - The HTML sub-elements, in order. + * Those can also just be strings, in which case only text nodes (and no actual + * HTMLElement) will be added at this place. + * @param {Object} [options={}] - Optional attributes for the element. + * @param {string} [options.className] - Value for a `class` attribute + * associated to this element. + * @returns {HTMLElement} + */ +export function createCompositeElement( + rootElementName : string, + parts : Array, + { className } : { className? : string } | undefined = {} +) : HTMLElement { + const elt = document.createElement(rootElementName); + if (className !== undefined) { + elt.className = className; + } + for (const subElt of parts) { + if (typeof subElt === "string") { + elt.appendChild(document.createTextNode(subElt)); + } else { + elt.appendChild(subElt); + } + } + return elt; +} + +export function isExtendedMode(parentElt : HTMLElement) : boolean { + return parentElt.clientHeight > 400; +} + +export function createMetricTitle(title : string) : HTMLElement { + const elt = createElement("span", { + textContent: title + "/", + }); + elt.style.fontWeight = "bold"; + return elt; +} + +export function createGraphCanvas() : HTMLCanvasElement { + const canvasElt = createElement("canvas"); + canvasElt.style.border = "1px solid white"; + canvasElt.style.height = "15px"; + canvasElt.style.marginLeft = "2px"; + return canvasElt; +} diff --git a/src/core/api/option_utils.ts b/src/core/api/option_utils.ts index 9711c4a155..abbc4cfca7 100644 --- a/src/core/api/option_utils.ts +++ b/src/core/api/option_utils.ts @@ -469,6 +469,12 @@ function parseLoadVideoOptions( transport = String(options.transport); } + if (!isNullOrUndefined(options.transportOptions?.aggressiveMode)) { + warnOnce("`transportOptions.aggressiveMode` is deprecated and won't " + + "be present in the next major version. " + + "Please open an issue if you still need this."); + } + const autoPlay = isNullOrUndefined(options.autoPlay) ? DEFAULT_AUTO_PLAY : !!options.autoPlay; @@ -484,6 +490,16 @@ function parseLoadVideoOptions( throw new Error("Invalid key system given: Missing type string or " + "getLicense callback"); } + if (!isNullOrUndefined(keySystem.onKeyStatusesChange)) { + warnOnce("`keySystems[].onKeyStatusesChange` is deprecated and won't " + + "be present in the next major version. " + + "Please open an issue if you still need this."); + } + if (!isNullOrUndefined(keySystem.throwOnLicenseExpiration)) { + warnOnce("`keySystems[].throwOnLicenseExpiration` is deprecated and won't " + + "be present in the next major version. " + + "Please open an issue if you still need this."); + } } } diff --git a/src/core/api/playback_observer.ts b/src/core/api/playback_observer.ts index b12b23400c..c84ebeb691 100644 --- a/src/core/api/playback_observer.ts +++ b/src/core/api/playback_observer.ts @@ -48,8 +48,8 @@ const SCANNED_MEDIA_ELEMENTS_EVENTS : IPlaybackObserverEventType[] = [ "canplay" * `PlaybackObserver` to know the current state of the media being played. * * You can use the PlaybackObserver to either get the last observation - * performed, get the current media state or subscribe to an Observable emitting - * regularly media conditions. + * performed, get the current media state or listen to media observation sent + * at a regular interval. * * @class {PlaybackObserver} */ @@ -116,8 +116,7 @@ export default class PlaybackObserver { /** * Stop the `PlaybackObserver` from emitting playback observations and free all - * resources reserved to emitting them such as event listeners, intervals and - * subscribing callbacks. + * resources reserved to emitting them such as event listeners and intervals. * * Once `stop` is called, no new playback observation will ever be emitted. * @@ -192,7 +191,7 @@ export default class PlaybackObserver { * produced by the `PlaybackObserver` and updated each time a new one is * produced. * - * This value can then be for example subscribed to to be notified of future + * This value can then be for example listened to to be notified of future * playback observations. * * @returns {Object} @@ -211,11 +210,14 @@ export default class PlaybackObserver { * CancellationSignal emits. */ public listen( - cb : (observation : IPlaybackObservation) => void, + cb : ( + observation : IPlaybackObservation, + stopListening : () => void + ) => void, options? : { includeLastObservation? : boolean | undefined; clearSignal? : CancellationSignal | undefined; } ) { - if (this._canceller.isUsed || options?.clearSignal?.isCancelled === true) { + if (this._canceller.isUsed() || options?.clearSignal?.isCancelled() === true) { return noop; } this._observationRef.onUpdate(cb, { @@ -250,7 +252,7 @@ export default class PlaybackObserver { /** * Creates the `IReadOnlySharedReference` that will generate playback * observations. - * @returns {Observable} + * @returns {Object} */ private _createSharedReference() : IReadOnlySharedReference { if (this._observationRef !== undefined) { @@ -312,7 +314,8 @@ export default class PlaybackObserver { return timings; }; - const returnedSharedReference = createSharedReference(getCurrentObservation("init")); + const returnedSharedReference = createSharedReference(getCurrentObservation("init"), + this._canceller.signal); const generateObservationForEvent = (event : IPlaybackObserverEventType) => { const newObservation = getCurrentObservation(event); @@ -529,7 +532,7 @@ export interface IReadOnlyPlaybackObserver { * produced by the `IReadOnlyPlaybackObserver` and updated each time a new one * is produced. * - * This value can then be for example subscribed to to be notified of future + * This value can then be for example listened to to be notified of future * playback observations. * * @returns {Object} @@ -546,7 +549,10 @@ export interface IReadOnlyPlaybackObserver { * @returns {Function} - Allows to easily unregister the callback */ listen( - cb : (observation : TObservationType) => void, + cb : ( + observation : TObservationType, + stopListening : () => void + ) => void, options? : { includeLastObservation? : boolean | undefined; clearSignal? : CancellationSignal | undefined; } ) : void; @@ -958,11 +964,16 @@ function generateReadOnlyObserver( return mappedRef; }, listen( - cb : (observation : TDest) => void, + cb : ( + observation : TDest, + stopListening : () => void + ) => void, options? : { includeLastObservation? : boolean | undefined; clearSignal? : CancellationSignal | undefined; } ) : void { - if (cancellationSignal.isCancelled || options?.clearSignal?.isCancelled === true) { + if (cancellationSignal.isCancelled() || + options?.clearSignal?.isCancelled() === true) + { return ; } mappedRef.onUpdate(cb, { diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts index 68d537276f..641abcb402 100644 --- a/src/core/api/public_api.ts +++ b/src/core/api/public_api.ts @@ -19,30 +19,6 @@ * It also starts the different sub-parts of the player on various API calls. */ -import { - combineLatest as observableCombineLatest, - Connectable, - concat as observableConcat, - connectable, - distinctUntilChanged, - EMPTY, - filter, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - share, - shareReplay, - skipWhile, - startWith, - Subject, - Subscription, - switchMap, - take, - takeUntil, - tap, -} from "rxjs"; import { events, exitFullscreen, @@ -79,6 +55,7 @@ import { IBitrateEstimate, IConstructorOptions, IDecipherabilityUpdateContent, + IKeySystemConfigurationOutput, ILoadVideoOptions, IPeriod, IPlayerError, @@ -92,9 +69,12 @@ import { IVideoTrackPreference, } from "../../public_types"; import areArraysOfNumbersEqual from "../../utils/are_arrays_of_numbers_equal"; +import assert from "../../utils/assert"; import EventEmitter, { + IEventPayload, IListener, } from "../../utils/event_emitter"; +import idGenerator from "../../utils/id_generator"; import isNullOrUndefined from "../../utils/is_null_or_undefined"; import Logger from "../../utils/logger"; import objectAssign from "../../utils/object_assign"; @@ -108,25 +88,19 @@ import createSharedReference, { IReadOnlySharedReference, ISharedReference, } from "../../utils/reference"; -import TaskCanceller from "../../utils/task_canceller"; +import TaskCanceller, { + CancellationSignal, +} from "../../utils/task_canceller"; import warnOnce from "../../utils/warn_once"; import { IABRThrottlers } from "../adaptive"; import { clearOnStop, disposeDecryptionResources, + getKeySystemConfiguration, getCurrentKeySystem, } from "../decrypt"; -import { - IManifestFetcherParsedResult, - IManifestFetcherWarningEvent, - ManifestFetcher, -} from "../fetchers"; -import initializeMediaSourcePlayback, { - IInitEvent, - ILoadedEvent, - IReloadingMediaSourceEvent, - IStalledEvent, -} from "../init"; +import { ContentInitializer } from "../init"; +import MediaSourceContentInitializer from "../init/media_source_content_initializer"; import SegmentBuffersStore, { IBufferedChunk, IBufferType, @@ -146,20 +120,24 @@ import PlaybackObserver, { import MediaElementTrackChoiceManager from "./tracks_management/media_element_track_choice_manager"; import TrackChoiceManager from "./tracks_management/track_choice_manager"; import { + constructPlayerStateReference, emitSeekEvents, - getLoadedContentState, + isLoadedState, + // emitSeekEvents, PLAYER_STATES, } from "./utils"; /* eslint-disable @typescript-eslint/naming-convention */ +const generateContentId = idGenerator(); const { getPageActivityRef, getPictureOnPictureStateRef, getVideoVisibilityRef, getVideoWidthRef, - onFullscreenChange$, - onTextTrackChanges$ } = events; + onFullscreenChange, + onTextTrackAdded, + onTextTrackRemoved } = events; /** * @class Player @@ -184,7 +162,7 @@ class Player extends EventEmitter { /** * Current state of the RxPlayer. - * Please use `getLoadedContentState()` instead. + * Please use `getPlayerState()` instead. */ public state : IPlayerState; @@ -193,7 +171,7 @@ class Player extends EventEmitter { * used for its normal functionment can be freed. * The player will be unusable after that. */ - private readonly _priv_destroy$ : Subject; + private readonly _destroyCanceller : TaskCanceller; /** * Contains `true` when the previous content is cleaning-up, `false` when it's @@ -254,67 +232,7 @@ class Player extends EventEmitter { * Information about the current content being played. * `null` when no content is currently loading or loaded. */ - private _priv_contentInfos : null | { - /** - * URL of the Manifest (or just of the content for DirectFile contents) - * currently being played. - */ - url : string | undefined; - - /** TaskCanceller triggered when it's time to stop the current content. */ - currentContentCanceller : TaskCanceller; - - /** - * `true` if the current content is in DirectFile mode. - * `false` is the current content has a transport protocol (Smooth/DASH...). - */ - isDirectFile : boolean; - - /** - * Current Image Track Data associated to the content. - * `null` if the current content has no image playlist linked to it. - * @deprecated - */ - thumbnails : IBifThumbnail[]|null; - - /** - * Manifest linked to the current content. - * `null` if the current content loaded has no manifest or if the content is - * not yet loaded. - */ - manifest : Manifest|null; - - /** - * Current Period being played. - * `null` if no Period is being played. - */ - currentPeriod : Period|null; - - /** - * Store currently considered adaptations, per active period. - * `null` if no Adaptation is active - */ - activeAdaptations : { - [periodId : string] : Partial>; - } | null; - - /** - * Store currently considered representations, per active period. - * `null` if no Representation is active - */ - activeRepresentations : { - [periodId : string] : Partial>; - } | null; - - /** Store starting audio track if one. */ - initialAudioTrack : undefined|IAudioTrackPreference; - - /** Store starting text track if one. */ - initialTextTrack : undefined|ITextTrackPreference; - - /** Keep information on the active SegmentBuffers. */ - segmentBuffersStore : SegmentBuffersStore | null; - }; + private _priv_contentInfos : IPublicApiContentInfos | null; /** List of favorite audio tracks, in preference order. */ private _priv_preferredAudioTracks : IAudioTrackPreference[]; @@ -328,20 +246,6 @@ class Player extends EventEmitter { /** If `true` trickMode video tracks will be chosen if available. */ private _priv_preferTrickModeTracks : boolean; - /** - * TrackChoiceManager instance linked to the current content. - * `null` if no content has been loaded or if the current content loaded - * has no TrackChoiceManager. - */ - private _priv_trackChoiceManager : TrackChoiceManager|null; - - /** - * MediaElementTrackChoiceManager instance linked to the current content. - * `null` if no content has been loaded or if the current content loaded - * has no MediaElementTrackChoiceManager. - */ - private _priv_mediaElementTrackChoiceManager : MediaElementTrackChoiceManager|null; - /** Refer to last picture in picture event received. */ private _priv_pictureInPictureRef : IReadOnlySharedReference< events.IPictureInPictureEvent @@ -460,79 +364,102 @@ class Player extends EventEmitter { // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624 videoElement.preload = "auto"; - this.version = /* PLAYER_VERSION */"3.29.0"; + this.version = /* PLAYER_VERSION */"3.30.0"; this.log = log; this.state = "STOPPED"; this.videoElement = videoElement; const destroyCanceller = new TaskCanceller(); - this._priv_destroy$ = new Subject(); // TODO Remove the need for this Subject - this._priv_destroy$.pipe(take(1)).subscribe(() => { - destroyCanceller.cancel(); - }); + this._destroyCanceller = destroyCanceller; this._priv_pictureInPictureRef = getPictureOnPictureStateRef(videoElement, destroyCanceller.signal); /** @deprecated */ - onFullscreenChange$(videoElement) - .pipe(takeUntil(this._priv_destroy$)) + onFullscreenChange(videoElement, () => { /* eslint-disable import/no-deprecated */ - .subscribe(() => this.trigger("fullscreenChange", this.isFullscreen())); + this.trigger("fullscreenChange", this.isFullscreen()); /* eslint-enable import/no-deprecated */ + }, destroyCanceller.signal); + + /** Store last known TextTrack array linked to the media element. */ + let prevTextTracks : TextTrack[] = [] ; + for (let i = 0; i < videoElement.textTracks?.length; i++) { + const textTrack = videoElement.textTracks?.[i]; + if (!isNullOrUndefined(textTrack)) { + prevTextTracks.push(textTrack); + } + } - /** @deprecated */ - onTextTrackChanges$(videoElement.textTracks) - .pipe( - takeUntil(this._priv_destroy$), - map((evt : Event) => { // prepare TextTrack array - const target = evt.target as TextTrackList; - const arr : TextTrack[] = []; - for (let i = 0; i < target.length; i++) { - const textTrack = target[i]; - arr.push(textTrack); - } - return arr; - }), - - // We can have two consecutive textTrackChanges with the exact same - // payload when we perform multiple texttrack operations before the event - // loop is freed. - // In that case we only want to fire one time the observable. - distinctUntilChanged((textTracksA, textTracksB) => { - if (textTracksA.length !== textTracksB.length) { - return false; - } - for (let i = 0; i < textTracksA.length; i++) { - if (textTracksA[i] !== textTracksB[i]) { - return false; - } - } - return true; - }) - ) - .subscribe((x : TextTrack[]) => this._priv_onNativeTextTracksNext(x)); + /** Callback called when a TextTrack element is added or removed. */ + const onTextTrackChanges = (_evt : unknown) => { + const evt = _evt as Event; + const target = evt.target as TextTrackList; + const textTrackArr : TextTrack[] = []; + for (let i = 0; i < target.length; i++) { + const textTrack = target[i]; + textTrackArr.push(textTrack); + } + + const oldTextTracks = prevTextTracks; + prevTextTracks = textTrackArr; + + // We can have two consecutive textTrackChanges with the exact same + // payload when we perform multiple texttrack operations before the event + // loop is freed. + if (oldTextTracks.length !== textTrackArr.length) { + this._priv_onNativeTextTracksNext(textTrackArr); + return; + } + for (let i = 0; i < oldTextTracks.length; i++) { + if (oldTextTracks[i] !== textTrackArr[i]) { + this._priv_onNativeTextTracksNext(textTrackArr); + return ; + } + } + return ; + }; + + if (!isNullOrUndefined(videoElement.textTracks)) { + onTextTrackAdded(videoElement.textTracks, + onTextTrackChanges, + destroyCanceller.signal); + onTextTrackRemoved(videoElement.textTracks, + onTextTrackChanges, + destroyCanceller.signal); + } - this._priv_speed = createSharedReference(videoElement.playbackRate); + this._priv_speed = createSharedReference(videoElement.playbackRate, + this._destroyCanceller.signal); this._priv_preferTrickModeTracks = false; - this._priv_contentLock = createSharedReference(false); + this._priv_contentLock = createSharedReference( + false, + this._destroyCanceller.signal); this._priv_bufferOptions = { - wantedBufferAhead: createSharedReference(wantedBufferAhead), - maxBufferAhead: createSharedReference(maxBufferAhead), - maxBufferBehind: createSharedReference(maxBufferBehind), - maxVideoBufferSize: createSharedReference(maxVideoBufferSize), + wantedBufferAhead: createSharedReference(wantedBufferAhead, + this._destroyCanceller.signal), + maxBufferAhead: createSharedReference(maxBufferAhead, + this._destroyCanceller.signal), + maxBufferBehind: createSharedReference(maxBufferBehind, + this._destroyCanceller.signal), + maxVideoBufferSize: createSharedReference(maxVideoBufferSize, + this._destroyCanceller.signal), }; this._priv_bitrateInfos = { lastBitrates: { audio: initialAudioBitrate, video: initialVideoBitrate }, - minAutoBitrates: { audio: createSharedReference(minAudioBitrate), - video: createSharedReference(minVideoBitrate) }, - maxAutoBitrates: { audio: createSharedReference(maxAudioBitrate), - video: createSharedReference(maxVideoBitrate) }, - manualBitrates: { audio: createSharedReference(-1), - video: createSharedReference(-1) }, + minAutoBitrates: { audio: createSharedReference(minAudioBitrate, + this._destroyCanceller.signal), + video: createSharedReference(minVideoBitrate, + this._destroyCanceller.signal) }, + maxAutoBitrates: { audio: createSharedReference(maxAudioBitrate, + this._destroyCanceller.signal), + video: createSharedReference(maxVideoBitrate, + this._destroyCanceller.signal) }, + manualBitrates: { audio: createSharedReference(-1, this._destroyCanceller.signal), + video: createSharedReference(-1, this._destroyCanceller.signal) }, }; this._priv_throttleWhenHidden = throttleWhenHidden; @@ -540,8 +467,6 @@ class Player extends EventEmitter { this._priv_limitVideoWidth = limitVideoWidth; this._priv_mutedMemory = DEFAULT_UNMUTED_VOLUME; - this._priv_trackChoiceManager = null; - this._priv_mediaElementTrackChoiceManager = null; this._priv_currentError = null; this._priv_contentInfos = null; @@ -608,23 +533,8 @@ class Player extends EventEmitter { }); } - // free Observables linked to the Player instance - this._priv_destroy$.next(); - this._priv_destroy$.complete(); - - // Complete all subjects and references - this._priv_speed.finish(); - this._priv_contentLock.finish(); - this._priv_bufferOptions.wantedBufferAhead.finish(); - this._priv_bufferOptions.maxVideoBufferSize.finish(); - this._priv_bufferOptions.maxBufferAhead.finish(); - this._priv_bufferOptions.maxBufferBehind.finish(); - this._priv_bitrateInfos.manualBitrates.video.finish(); - this._priv_bitrateInfos.manualBitrates.audio.finish(); - this._priv_bitrateInfos.minAutoBitrates.video.finish(); - this._priv_bitrateInfos.minAutoBitrates.audio.finish(); - this._priv_bitrateInfos.maxAutoBitrates.video.finish(); - this._priv_bitrateInfos.maxAutoBitrates.audio.finish(); + // free resources linked to the Player instance + this._destroyCanceller.cancel(); this._priv_reloadingMetadata = {}; @@ -692,6 +602,21 @@ class Player extends EventEmitter { this._priv_initializeContentPlayback(newOptions); } + public createDebugElement(element : HTMLElement) : { + dispose() : void; + } { + if (features.createDebugElement === null) { + throw new Error("Feature `DEBUG_ELEMENT` not added to the RxPlayer"); + } + const canceller = new TaskCanceller() ; + features.createDebugElement(element, this, canceller.signal); + return { + dispose() { + canceller.cancel(); + }, + }; + } + /** * From given options, initialize content playback. * @param {Object} options @@ -721,48 +646,15 @@ class Player extends EventEmitter { const isDirectFile = transport === "directfile"; - /** Subject which will emit to stop the current content. */ + /** Emit to stop the current content. */ const currentContentCanceller = new TaskCanceller(); - // Some logic needs the equivalent of the `currentContentCanceller` under - // an Observable form - // TODO remove the need for `stoppedContent$` - const stoppedContent$ = new Observable((obs) => { - currentContentCanceller.signal.register(() => { - obs.next(); - obs.complete(); - }); - }); - - /** Future `this._priv_contentInfos` related to this content. */ - const contentInfos = { url, - currentContentCanceller, - isDirectFile, - segmentBuffersStore: null, - thumbnails: null, - manifest: null, - currentPeriod: null, - activeAdaptations: null, - activeRepresentations: null, - initialAudioTrack: defaultAudioTrack, - initialTextTrack: defaultTextTrack }; - - const videoElement = this.videoElement; - /** Global "playback observer" which will emit playback conditions */ - const playbackObserver = new PlaybackObserver(videoElement, { - withMediaSource: !isDirectFile, - lowLatencyMode, - }); - - currentContentCanceller.signal.register(() => { - playbackObserver.stop(); - }); - - /** Emit playback events. */ - let playback$ : Connectable; + let initializer : ContentInitializer; + let mediaElementTrackChoiceManager : MediaElementTrackChoiceManager | null = + null; if (!isDirectFile) { const transportFn = features.transports[transport]; if (typeof transportFn !== "function") { @@ -781,44 +673,12 @@ class Player extends EventEmitter { segmentRequestTimeout } = networkConfig; /** Interface used to load and refresh the Manifest. */ - const manifestFetcher = new ManifestFetcher( - url, - transportPipelines, - { lowLatencyMode, - maxRetryRegular: manifestRetry, - maxRetryOffline: offlineRetry, - requestTimeout: manifestRequestTimeout }); - - /** Observable emitting the initial Manifest */ - let manifest$ : Observable; - - if (initialManifest instanceof Manifest) { - manifest$ = observableOf({ type: "parsed", - manifest: initialManifest }); - } else if (initialManifest !== undefined) { - manifest$ = manifestFetcher.parse(initialManifest, { previousManifest: null, - unsafeMode: false }); - } else { - manifest$ = manifestFetcher.fetch(url).pipe( - mergeMap((response) => response.type === "warning" ? - observableOf(response) : // bubble-up warnings - response.parse({ previousManifest: null, unsafeMode: false }))); - } - - // Load the Manifest right now and share it with every subscriber until - // the content is stopped - manifest$ = manifest$.pipe(takeUntil(stoppedContent$), - shareReplay()); - manifest$.subscribe(); - - // now that the Manifest is loading, stop previous content and reset state - // This is done after fetching the Manifest as `stop` could technically - // take time. - this.stop(); - - this._priv_currentError = null; - this._priv_contentInfos = contentInfos; + const manifestRequestSettings = { lowLatencyMode, + maxRetryRegular: manifestRetry, + maxRetryOffline: offlineRetry, + requestTimeout: manifestRequestTimeout, + minimumManifestUpdateInterval, + initialManifest }; const relyOnVideoVisibilityAndSize = canRelyOnVideoVisibilityAndSize(); const throttlers : IABRThrottlers = { throttle: {}, @@ -888,183 +748,178 @@ class Player extends EventEmitter { onCodecSwitch }, this._priv_bufferOptions); - const segmentRequestOptions = { regularError: segmentRetry, + const segmentRequestOptions = { lowLatencyMode, + maxRetryRegular: segmentRetry, requestTimeout: segmentRequestTimeout, - offlineError: offlineRetry }; - - // We've every options set up. Start everything now - const init$ = initializeMediaSourcePlayback({ adaptiveOptions, - autoPlay, - bufferOptions, - playbackObserver, - keySystems, - lowLatencyMode, - manifest$, - manifestFetcher, - mediaElement: videoElement, - minimumManifestUpdateInterval, - segmentRequestOptions, - speed: this._priv_speed, - startAt, - transport: transportPipelines, - textTrackOptions }) - .pipe(takeUntil(stoppedContent$)); - - playback$ = connectable(init$, { connector: () => new Subject(), - resetOnDisconnect: false }); + maxRetryOffline: offlineRetry }; + + initializer = new MediaSourceContentInitializer({ + adaptiveOptions, + autoPlay, + bufferOptions, + keySystems, + lowLatencyMode, + manifestRequestSettings, + transport: transportPipelines, + segmentRequestOptions, + speed: this._priv_speed, + startAt, + textTrackOptions, + url, + }); } else { - // Stop previous content and reset its state - this.stop(); - this._priv_currentError = null; if (features.directfile === null) { + this.stop(); + this._priv_currentError = null; throw new Error("DirectFile feature not activated in your build."); } - this._priv_contentInfos = contentInfos; - - this._priv_mediaElementTrackChoiceManager = - new features.directfile.mediaElementTrackChoiceManager(this.videoElement); - - const preferredAudioTracks = defaultAudioTrack === undefined ? - this._priv_preferredAudioTracks : - [defaultAudioTrack]; - this._priv_mediaElementTrackChoiceManager - .setPreferredAudioTracks(preferredAudioTracks, true); - - const preferredTextTracks = defaultTextTrack === undefined ? - this._priv_preferredTextTracks : - [defaultTextTrack]; - this._priv_mediaElementTrackChoiceManager - .setPreferredTextTracks(preferredTextTracks, true); - - this._priv_mediaElementTrackChoiceManager - .setPreferredVideoTracks(this._priv_preferredVideoTracks, true); - - this.trigger("availableAudioTracksChange", - this._priv_mediaElementTrackChoiceManager.getAvailableAudioTracks()); - this.trigger("availableVideoTracksChange", - this._priv_mediaElementTrackChoiceManager.getAvailableVideoTracks()); - this.trigger("availableTextTracksChange", - this._priv_mediaElementTrackChoiceManager.getAvailableTextTracks()); - - this.trigger("audioTrackChange", - this._priv_mediaElementTrackChoiceManager.getChosenAudioTrack() - ?? null); - this.trigger("textTrackChange", - this._priv_mediaElementTrackChoiceManager.getChosenTextTrack() - ?? null); - this.trigger("videoTrackChange", - this._priv_mediaElementTrackChoiceManager.getChosenVideoTrack() - ?? null); - - this._priv_mediaElementTrackChoiceManager - .addEventListener("availableVideoTracksChange", (val) => - this.trigger("availableVideoTracksChange", val)); - this._priv_mediaElementTrackChoiceManager - .addEventListener("availableAudioTracksChange", (val) => - this.trigger("availableAudioTracksChange", val)); - this._priv_mediaElementTrackChoiceManager - .addEventListener("availableTextTracksChange", (val) => - this.trigger("availableTextTracksChange", val)); - - this._priv_mediaElementTrackChoiceManager - .addEventListener("audioTrackChange", (val) => - this.trigger("audioTrackChange", val)); - this._priv_mediaElementTrackChoiceManager - .addEventListener("videoTrackChange", (val) => - this.trigger("videoTrackChange", val)); - this._priv_mediaElementTrackChoiceManager - .addEventListener("textTrackChange", (val) => - this.trigger("textTrackChange", val)); - - const directfileInit$ = - features.directfile.initDirectFile({ autoPlay, - keySystems, - mediaElement: videoElement, - speed: this._priv_speed, - playbackObserver, - startAt, - url }) - .pipe(takeUntil(stoppedContent$)); - - playback$ = connectable(directfileInit$, { connector: () => new Subject(), - resetOnDisconnect: false }); - } - - /** Emit an object when the player "stalls" and null when it un-stalls */ - const stalled$ = playback$.pipe( - filter((evt) : evt is IStalledEvent => evt.type === "stalled" || - evt.type === "unstalled"), - map(x => x.value), - distinctUntilChanged((prevStallReason, currStallReason) => { - return prevStallReason === null && currStallReason === null || - (prevStallReason !== null && currStallReason !== null && - prevStallReason === currStallReason); - })); - - /** Emit when the content is considered "loaded". */ - const loaded$ = playback$.pipe( - filter((evt) : evt is ILoadedEvent => evt.type === "loaded"), - share() - ); - - /** Emit when we will "reload" the MediaSource. */ - const reloading$ = playback$ - .pipe(filter((evt) : evt is IReloadingMediaSourceEvent => - evt.type === "reloading-media-source" - ), - share()); - - /** Emit when the media element emits a "seeking" event. */ - const observation$ = playbackObserver.getReference().asObservable(); - - const stateChangingEvent$ = observation$.pipe(filter(o => { - return o.event === "seeking" || o.event === "ended" || - o.event === "play" || o.event === "pause"; - })); - - /** Emit state updates once the content is considered "loaded". */ - const loadedStateUpdates$ = observableCombineLatest([ - stalled$.pipe(startWith(null)), - stateChangingEvent$.pipe(startWith(null)), - ]).pipe( - takeUntil(stoppedContent$), - map(([stalledStatus]) => - getLoadedContentState(videoElement, stalledStatus) - ) - ); - - /** Emit all player "state" updates. */ - const playerState$ = observableConcat( - observableOf(PLAYER_STATES.LOADING), // Begin with LOADING - - loaded$.pipe(switchMap((_, i) => { - const isFirstLoad = i === 0; - return observableMerge( - // Purposely subscribed first so a RELOADING triggered synchronously - // after a LOADED state is catched. - reloading$.pipe(map(() => PLAYER_STATES.RELOADING)), - // Only switch to LOADED state for the first (i.e. non-RELOADING) load - isFirstLoad ? observableOf(PLAYER_STATES.LOADED) : - EMPTY, - // Purposely put last so any other state change happens after we've - // already switched to LOADED - loadedStateUpdates$.pipe( - takeUntil(reloading$), - // For the first load, we prefer staying at the LOADED state over - // PAUSED when autoPlay is disabled. - // For consecutive loads however, there's no LOADED state. - skipWhile(state => isFirstLoad && state === PLAYER_STATES.PAUSED) - ) + mediaElementTrackChoiceManager = + this._priv_initializeMediaElementTrackChoiceManager( + defaultAudioTrack, + defaultTextTrack, + currentContentCanceller.signal ); - })) - ).pipe(distinctUntilChanged()); + if (currentContentCanceller.isUsed()) { + return; + } + initializer = new features.directfile.initDirectFile({ autoPlay, + keySystems, + speed: this._priv_speed, + startAt, + url }); + } + + /** Future `this._priv_contentInfos` related to this content. */ + const contentInfos : IPublicApiContentInfos = { + contentId: generateContentId(), + originalUrl: url, + currentContentCanceller, + initializer, + isDirectFile, + segmentBuffersStore: null, + thumbnails: null, + manifest: null, + currentPeriod: null, + activeAdaptations: null, + activeRepresentations: null, + initialAudioTrack: defaultAudioTrack, + initialTextTrack: defaultTextTrack, + trackChoiceManager: null, + mediaElementTrackChoiceManager, + }; + + // Bind events + initializer.addEventListener("error", (error) => { + const formattedError = formatError(error, { + defaultCode: "NONE", + defaultReason: "An unknown error stopped content playback.", + }); + formattedError.fatal = true; + + contentInfos.currentContentCanceller.cancel(); + this._priv_cleanUpCurrentContentState(); + this._priv_currentError = formattedError; + log.error("API: The player stopped because of an error", + error instanceof Error ? error : ""); + this._priv_setPlayerState(PLAYER_STATES.STOPPED); - let playbackSubscription : Subscription|undefined; - stoppedContent$.subscribe(() => { - if (playbackSubscription !== undefined) { - playbackSubscription.unsubscribe(); + // TODO This condition is here because the eventual callback called when the + // player state is updated can launch a new content, thus the error will not + // be here anymore, in which case triggering the "error" event is unwanted. + // This is very ugly though, and we should probable have a better solution + if (this._priv_currentError === formattedError) { + this.trigger("error", formattedError); + } + }); + initializer.addEventListener("warning", (error) => { + const formattedError = formatError(error, { + defaultCode: "NONE", + defaultReason: "An unknown error happened.", + }); + log.warn("API: Sending warning:", formattedError); + this.trigger("warning", formattedError); + }); + initializer.addEventListener("reloadingMediaSource", () => { + contentInfos.segmentBuffersStore = null; + if (contentInfos.trackChoiceManager !== null) { + contentInfos.trackChoiceManager.resetPeriods(); } }); + initializer.addEventListener("inbandEvents", (inbandEvents) => + this.trigger("inbandEvents", inbandEvents)); + initializer.addEventListener("streamEvent", (streamEvent) => + this.trigger("streamEvent", streamEvent)); + initializer.addEventListener("streamEventSkip", (streamEventSkip) => + this.trigger("streamEventSkip", streamEventSkip)); + initializer.addEventListener("decipherabilityUpdate", (decipherabilityUpdate) => + this.trigger("decipherabilityUpdate", decipherabilityUpdate)); + initializer.addEventListener("activePeriodChanged", (periodInfo) => + this._priv_onActivePeriodChanged(contentInfos, periodInfo)); + initializer.addEventListener("periodStreamReady", (periodReadyInfo) => + this._priv_onPeriodStreamReady(contentInfos, periodReadyInfo)); + initializer.addEventListener("periodStreamCleared", (periodClearedInfo) => + this._priv_onPeriodStreamCleared(contentInfos, periodClearedInfo)); + initializer.addEventListener("representationChange", (representationInfo) => + this._priv_onRepresentationChange(contentInfos, representationInfo)); + initializer.addEventListener("adaptationChange", (adaptationInfo) => + this._priv_onAdaptationChange(contentInfos, adaptationInfo)); + initializer.addEventListener("bitrateEstimationChange", (bitrateEstimationInfo) => + this._priv_onBitrateEstimationChange(bitrateEstimationInfo)); + initializer.addEventListener("manifestReady", (manifest) => + this._priv_onManifestReady(contentInfos, manifest)); + initializer.addEventListener("loaded", (evt) => { + contentInfos.segmentBuffersStore = evt.segmentBuffersStore; + }); + initializer.addEventListener("addedSegment", (evt) => { + // Manage image tracks + // @deprecated + const { content, segmentData } = evt; + if (content.adaptation.type === "image") { + if (!isNullOrUndefined(segmentData) && + (segmentData as { type : string }).type === "bif") + { + const imageData = (segmentData as { data : IBifThumbnail[] }).data; + /* eslint-disable import/no-deprecated */ + contentInfos.thumbnails = imageData; + this.trigger("imageTrackUpdate", + { data: contentInfos.thumbnails }); + /* eslint-enable import/no-deprecated */ + } + } + }); + + // Now, that most events are linked, prepare the next content. + initializer.prepare(); + + // Now that the content is prepared, stop previous content and reset state + // This is done after content preparation as `stop` could technically have + // a long and synchronous blocking time. + // Note that this call is done **synchronously** after all events linking. + // This is **VERY** important so: + // - the `STOPPED` state is switched to synchronously after loading a new + // content. + // - we can avoid involontarily catching events linked to the previous + // content. + this.stop(); + + /** Global "playback observer" which will emit playback conditions */ + const playbackObserver = new PlaybackObserver(videoElement, { + withMediaSource: !isDirectFile, + lowLatencyMode, + }); + + currentContentCanceller.signal.register(() => { + playbackObserver.stop(); + }); + + // Update the RxPlayer's state at the right events + const playerStateRef = constructPlayerStateReference(initializer, + videoElement, + playbackObserver, + currentContentCanceller.signal); + currentContentCanceller.signal.register(() => { + initializer.dispose(); + }); /** * Function updating `this._priv_reloadingMetadata` in function of the @@ -1091,61 +946,66 @@ class Player extends EventEmitter { } }; - playerState$.pipe( - tap(newState => { - updateReloadingMetadata(newState); - this._priv_setPlayerState(newState); + /** + * `TaskCanceller` allowing to stop emitting `"seeking"` and `"seeked"` + * events. + * `null` when such events are not emitted currently. + */ + let seekEventsCanceller : TaskCanceller | null = null; + + // React to player state change + playerStateRef.onUpdate((newState : IPlayerState) => { + updateReloadingMetadata(newState); + this._priv_setPlayerState(newState); - // Previous call could have performed all kind of side-effects, thus, - // we re-check the current state associated to the RxPlayer - if (this.state === "ENDED" && this._priv_stopAtEnd) { - currentContentCanceller.cancel(); + if (currentContentCanceller.isUsed()) { + return; + } + + if (seekEventsCanceller !== null) { + if (!isLoadedState(this.state)) { + seekEventsCanceller.cancel(); + seekEventsCanceller = null; } - }), - map(state => state !== "RELOADING" && state !== "STOPPED"), - distinctUntilChanged(), - switchMap(canSendObservation => canSendObservation ? observation$ : - EMPTY), - takeUntil(stoppedContent$) - ).subscribe(o => { + } else if (isLoadedState(this.state)) { + seekEventsCanceller = new TaskCanceller(); + seekEventsCanceller.linkToSignal(currentContentCanceller.signal); + emitSeekEvents(videoElement, + playbackObserver, + () => this.trigger("seeking", null), + () => this.trigger("seeked", null), + seekEventsCanceller.signal); + } + + // Previous call could have performed all kind of side-effects, thus, + // we re-check the current state associated to the RxPlayer + if (this.state === PLAYER_STATES.ENDED && this._priv_stopAtEnd) { + this.stop(); + } + }, { emitCurrentValue: true, clearSignal: currentContentCanceller.signal }); + + // React to playback conditions change + playbackObserver.listen((observation) => { updateReloadingMetadata(this.state); - this._priv_triggerPositionUpdate(o); - }); + this._priv_triggerPositionUpdate(contentInfos, observation); + }, { clearSignal: currentContentCanceller.signal }); - // Link "seeking" and "seeked" events (once the content is loaded) - loaded$.pipe( - switchMap(() => emitSeekEvents(this.videoElement, observation$)), - takeUntil(stoppedContent$) - ).subscribe((evt : "seeking" | "seeked") => { - log.info(`API: Triggering "${evt}" event`); - this.trigger(evt, null); - }); + this._priv_currentError = null; + this._priv_contentInfos = contentInfos; - // Link playback events to the corresponding callbacks - playback$.subscribe({ - next: (x) => this._priv_onPlaybackEvent(x), - error: (err : Error) => this._priv_onPlaybackError(err), - complete: () => { - if (!contentInfos.currentContentCanceller.isUsed) { - log.info("API: Previous playback finished. Stopping and cleaning-up..."); - contentInfos.currentContentCanceller.cancel(); - this._priv_cleanUpCurrentContentState(); - this._priv_setPlayerState(PLAYER_STATES.STOPPED); - } - }, + currentContentCanceller.signal.register(() => { + initializer.removeEventListener(); }); // initialize the content only when the lock is inactive - this._priv_contentLock.asObservable() - .pipe( - filter((isLocked) => !isLocked), - take(1), - takeUntil(stoppedContent$) - ) - .subscribe(() => { + this._priv_contentLock.onUpdate((isLocked, stopListeningToLock) => { + if (!isLocked) { + stopListeningToLock(); + // start playback! - playbackSubscription = playback$.connect(); - }); + initializer.start(videoElement, playbackObserver); + } + }, { emitCurrentValue: true, clearSignal: currentContentCanceller.signal }); } /** @@ -1277,7 +1137,8 @@ class Player extends EventEmitter { } /** - * Returns the url of the content's manifest + * Returns the url of the currently considered Manifest, or of the content for + * directfile content. * @returns {string|undefined} - Current URL. `undefined` if not known or no * URL yet. */ @@ -1285,9 +1146,9 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return undefined; } - const { isDirectFile, manifest, url } = this._priv_contentInfos; + const { isDirectFile, manifest, originalUrl } = this._priv_contentInfos; if (isDirectFile) { - return url; + return originalUrl; } if (manifest !== null) { return manifest.getUrl(); @@ -1295,6 +1156,25 @@ class Player extends EventEmitter { return undefined; } + /** + * Update URL of the content currently being played (e.g. DASH's MPD). + * @param {Array.|undefined} urls - URLs to reach that content / + * Manifest from the most prioritized URL to the least prioritized URL. + * @param {Object|undefined} [params] + * @param {boolean} params.refresh - If `true` the resource in question + * (e.g. DASH's MPD) will be refreshed immediately. + */ + public updateContentUrls( + urls : string[] | undefined, + params? : { refresh?: boolean } | undefined + ) : void { + if (this._priv_contentInfos === null) { + throw new Error("No content loaded"); + } + const refreshNow = params?.refresh === true; + this._priv_contentInfos.initializer.updateContentUrls(urls, refreshNow); + } + /** * Returns the video duration, in seconds. * NaN if no video is playing. @@ -1328,6 +1208,8 @@ class Player extends EventEmitter { * @returns {Number} */ getVideoLoadedTime() : number { + warnOnce("`getVideoLoadedTime` is deprecated and won't be present in the " + + "next major version"); if (this.videoElement === null) { throw new Error("Disposed player"); } @@ -1342,6 +1224,8 @@ class Player extends EventEmitter { * @returns {Number} */ getVideoPlayedTime() : number { + warnOnce("`getVideoPlayedTime` is deprecated and won't be present in the " + + "next major version"); if (this.videoElement === null) { throw new Error("Disposed player"); } @@ -1481,15 +1365,12 @@ class Player extends EventEmitter { return; } this._priv_preferTrickModeTracks = preferTrickModeTracks; - if (this._priv_trackChoiceManager !== null) { - if (preferTrickModeTracks && - !this._priv_trackChoiceManager.isTrickModeEnabled()) - { - this._priv_trackChoiceManager.enableVideoTrickModeTracks(); - } else if (!preferTrickModeTracks && - this._priv_trackChoiceManager.isTrickModeEnabled()) - { - this._priv_trackChoiceManager.disableVideoTrickModeTracks(); + const trackChoiceManager = this._priv_contentInfos?.trackChoiceManager; + if (!isNullOrUndefined(trackChoiceManager)) { + if (preferTrickModeTracks && !trackChoiceManager.isTrickModeEnabled()) { + trackChoiceManager.enableVideoTrickModeTracks(); + } else if (!preferTrickModeTracks && trackChoiceManager.isTrickModeEnabled()) { + trackChoiceManager.disableVideoTrickModeTracks(); } } } @@ -1946,15 +1827,37 @@ class Player extends EventEmitter { /** * Returns type of current keysystem (e.g. playready, widevine) if the content * is encrypted. null otherwise. + * @deprecated * @returns {string|null} */ getCurrentKeySystem() : string|null { + warnOnce("`getCurrentKeySystem` is deprecated." + + "Please use the `getKeySystemConfiguration` method instead."); if (this.videoElement === null) { throw new Error("Disposed player"); } return getCurrentKeySystem(this.videoElement); } + /** + * Returns both the name of the key system (e.g. `"com.widevine.alpha"`) and + * the `MediaKeySystemConfiguration` currently associated to the + * HTMLMediaElement linked to the RxPlayer. + * + * Returns `null` if no such capabilities is associated or if unknown. + * @returns {Object|null} + */ + getKeySystemConfiguration() : IKeySystemConfigurationOutput | null { + if (this.videoElement === null) { + throw new Error("Disposed player"); + } + const values = getKeySystemConfiguration(this.videoElement); + if (values === null) { + return null; + } + return { keySystem: values[0], configuration: values[1] }; + } + /** * Returns every available audio tracks for the current Period. * @returns {Array.|null} @@ -1963,14 +1866,17 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return []; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { - return this._priv_mediaElementTrackChoiceManager?.getAvailableAudioTracks() ?? []; + return mediaElementTrackChoiceManager?.getAvailableAudioTracks() ?? []; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return []; } - return this._priv_trackChoiceManager.getAvailableAudioTracks(currentPeriod); + return trackChoiceManager.getAvailableAudioTracks(currentPeriod); } /** @@ -1981,14 +1887,17 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return []; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { - return this._priv_mediaElementTrackChoiceManager?.getAvailableTextTracks() ?? []; + return mediaElementTrackChoiceManager?.getAvailableTextTracks() ?? []; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return []; } - return this._priv_trackChoiceManager.getAvailableTextTracks(currentPeriod); + return trackChoiceManager.getAvailableTextTracks(currentPeriod); } /** @@ -1999,14 +1908,17 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return []; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { - return this._priv_mediaElementTrackChoiceManager?.getAvailableVideoTracks() ?? []; + return mediaElementTrackChoiceManager?.getAvailableVideoTracks() ?? []; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return []; } - return this._priv_trackChoiceManager.getAvailableVideoTracks(currentPeriod); + return trackChoiceManager.getAvailableVideoTracks(currentPeriod); } /** @@ -2017,17 +1929,20 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return undefined; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { - if (this._priv_mediaElementTrackChoiceManager === null) { + if (mediaElementTrackChoiceManager === null) { return undefined; } - return this._priv_mediaElementTrackChoiceManager.getChosenAudioTrack(); + return mediaElementTrackChoiceManager.getChosenAudioTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return undefined; } - return this._priv_trackChoiceManager.getChosenAudioTrack(currentPeriod); + return trackChoiceManager.getChosenAudioTrack(currentPeriod); } /** @@ -2038,17 +1953,20 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return undefined; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { - if (this._priv_mediaElementTrackChoiceManager === null) { + if (mediaElementTrackChoiceManager === null) { return undefined; } - return this._priv_mediaElementTrackChoiceManager.getChosenTextTrack(); + return mediaElementTrackChoiceManager.getChosenTextTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return undefined; } - return this._priv_trackChoiceManager.getChosenTextTrack(currentPeriod); + return trackChoiceManager.getChosenTextTrack(currentPeriod); } /** @@ -2059,17 +1977,20 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return undefined; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { - if (this._priv_mediaElementTrackChoiceManager === null) { + if (mediaElementTrackChoiceManager === null) { return undefined; } - return this._priv_mediaElementTrackChoiceManager.getChosenVideoTrack(); + return mediaElementTrackChoiceManager.getChosenVideoTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return undefined; } - return this._priv_trackChoiceManager.getChosenVideoTrack(currentPeriod); + return trackChoiceManager.getChosenVideoTrack(currentPeriod); } /** @@ -2082,20 +2003,23 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { throw new Error("No content loaded"); } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { try { - this._priv_mediaElementTrackChoiceManager?.setAudioTrackById(audioId); + mediaElementTrackChoiceManager?.setAudioTrackById(audioId); return; } catch (e) { throw new Error("player: unknown audio track"); } } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { throw new Error("No compatible content launched."); } try { - this._priv_trackChoiceManager.setAudioTrackByID(currentPeriod, audioId); + trackChoiceManager.setAudioTrackByID(currentPeriod, audioId); } catch (e) { throw new Error("player: unknown audio track"); @@ -2112,20 +2036,23 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { throw new Error("No content loaded"); } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { try { - this._priv_mediaElementTrackChoiceManager?.setTextTrackById(textId); + mediaElementTrackChoiceManager?.setTextTrackById(textId); return; } catch (e) { throw new Error("player: unknown text track"); } } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { throw new Error("No compatible content launched."); } try { - this._priv_trackChoiceManager.setTextTrackByID(currentPeriod, textId); + trackChoiceManager.setTextTrackByID(currentPeriod, textId); } catch (e) { throw new Error("player: unknown text track"); @@ -2139,15 +2066,18 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { - this._priv_mediaElementTrackChoiceManager?.disableTextTrack(); + mediaElementTrackChoiceManager?.disableTextTrack(); return; } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return; } - return this._priv_trackChoiceManager.disableTextTrack(currentPeriod); + return trackChoiceManager.disableTextTrack(currentPeriod); } /** @@ -2160,20 +2090,23 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { throw new Error("No content loaded"); } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; if (isDirectFile) { try { - this._priv_mediaElementTrackChoiceManager?.setVideoTrackById(videoId); + mediaElementTrackChoiceManager?.setVideoTrackById(videoId); return; } catch (e) { throw new Error("player: unknown video track"); } } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { throw new Error("No compatible content launched."); } try { - this._priv_trackChoiceManager.setVideoTrackByID(currentPeriod, videoId); + trackChoiceManager.setVideoTrackByID(currentPeriod, videoId); } catch (e) { throw new Error("player: unknown video track"); @@ -2187,14 +2120,17 @@ class Player extends EventEmitter { if (this._priv_contentInfos === null) { return; } - const { currentPeriod, isDirectFile } = this._priv_contentInfos; - if (isDirectFile && this._priv_mediaElementTrackChoiceManager !== null) { - return this._priv_mediaElementTrackChoiceManager.disableVideoTrack(); + const { currentPeriod, + isDirectFile, + trackChoiceManager, + mediaElementTrackChoiceManager } = this._priv_contentInfos; + if (isDirectFile && mediaElementTrackChoiceManager !== null) { + return mediaElementTrackChoiceManager.disableVideoTrack(); } - if (this._priv_trackChoiceManager === null || currentPeriod === null) { + if (trackChoiceManager === null || currentPeriod === null) { return; } - return this._priv_trackChoiceManager.disableVideoTrack(currentPeriod); + return trackChoiceManager.disableVideoTrack(currentPeriod); } /** @@ -2237,11 +2173,12 @@ class Player extends EventEmitter { "Should have been an Array."); } this._priv_preferredAudioTracks = tracks; - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.setPreferredAudioTracks(tracks, shouldApply); - } else if (this._priv_mediaElementTrackChoiceManager !== null) { - this._priv_mediaElementTrackChoiceManager.setPreferredAudioTracks(tracks, - shouldApply); + const contentInfos = this._priv_contentInfos; + if (!isNullOrUndefined(contentInfos?.trackChoiceManager)) { + contentInfos?.trackChoiceManager.setPreferredAudioTracks(tracks, shouldApply); + } else if (!isNullOrUndefined(contentInfos?.mediaElementTrackChoiceManager)) { + contentInfos?.mediaElementTrackChoiceManager.setPreferredAudioTracks(tracks, + shouldApply); } } @@ -2261,11 +2198,12 @@ class Player extends EventEmitter { "Should have been an Array."); } this._priv_preferredTextTracks = tracks; - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.setPreferredTextTracks(tracks, shouldApply); - } else if (this._priv_mediaElementTrackChoiceManager !== null) { - this._priv_mediaElementTrackChoiceManager.setPreferredTextTracks(tracks, - shouldApply); + const contentInfos = this._priv_contentInfos; + if (!isNullOrUndefined(contentInfos?.trackChoiceManager)) { + contentInfos?.trackChoiceManager.setPreferredTextTracks(tracks, shouldApply); + } else if (!isNullOrUndefined(contentInfos?.mediaElementTrackChoiceManager)) { + contentInfos?.mediaElementTrackChoiceManager.setPreferredTextTracks(tracks, + shouldApply); } } @@ -2285,11 +2223,12 @@ class Player extends EventEmitter { "Should have been an Array."); } this._priv_preferredVideoTracks = tracks; - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.setPreferredVideoTracks(tracks, shouldApply); - } else if (this._priv_mediaElementTrackChoiceManager !== null) { - this._priv_mediaElementTrackChoiceManager.setPreferredVideoTracks(tracks, - shouldApply); + const contentInfos = this._priv_contentInfos; + if (!isNullOrUndefined(contentInfos?.trackChoiceManager)) { + contentInfos?.trackChoiceManager.setPreferredVideoTracks(tracks, shouldApply); + } else if (!isNullOrUndefined(contentInfos?.mediaElementTrackChoiceManager)) { + contentInfos?.mediaElementTrackChoiceManager.setPreferredVideoTracks(tracks, + shouldApply); } } @@ -2386,10 +2325,8 @@ class Player extends EventEmitter { // lock playback of new contents while cleaning up is pending this._priv_contentLock.setValue(true); + this._priv_contentInfos?.mediaElementTrackChoiceManager?.dispose(); this._priv_contentInfos = null; - this._priv_trackChoiceManager = null; - this._priv_mediaElementTrackChoiceManager?.dispose(); - this._priv_mediaElementTrackChoiceManager = null; this._priv_contentEventsMemory = {}; @@ -2418,165 +2355,72 @@ class Player extends EventEmitter { } } - /** - * Triggered each time the playback Observable emits. - * - * React to various events. - * - * @param {Object} event - payload emitted - */ - private _priv_onPlaybackEvent(event : IInitEvent) : void { - switch (event.type) { - case "inband-events": - const inbandEvents = event.value; - this.trigger("inbandEvents", inbandEvents); - return; - case "stream-event": - this.trigger("streamEvent", event.value); - break; - case "stream-event-skip": - this.trigger("streamEventSkip", event.value); - break; - case "activePeriodChanged": - this._priv_onActivePeriodChanged(event.value); - break; - case "periodStreamReady": - this._priv_onPeriodStreamReady(event.value); - break; - case "periodStreamCleared": - this._priv_onPeriodStreamCleared(event.value); - break; - case "reloading-media-source": - this._priv_onReloadingMediaSource(); - break; - case "representationChange": - this._priv_onRepresentationChange(event.value); - break; - case "adaptationChange": - this._priv_onAdaptationChange(event.value); - break; - case "bitrateEstimationChange": - this._priv_onBitrateEstimationChange(event.value); - break; - case "manifestReady": - this._priv_onManifestReady(event.value); - break; - case "warning": - this._priv_onPlaybackWarning(event.value); - break; - case "loaded": - if (this._priv_contentInfos === null) { - log.error("API: Loaded event while no content is loaded"); - return; - } - this._priv_contentInfos.segmentBuffersStore = event.value.segmentBuffersStore; - break; - case "decipherabilityUpdate": - this.trigger("decipherabilityUpdate", event.value); - break; - case "added-segment": - if (this._priv_contentInfos === null) { - log.error("API: Added segment while no content is loaded"); - return; - } - - // Manage image tracks - // @deprecated - const { content, segmentData } = event.value; - if (content.adaptation.type === "image") { - if (!isNullOrUndefined(segmentData) && - (segmentData as { type : string }).type === "bif") - { - const imageData = (segmentData as { data : IBifThumbnail[] }).data; - /* eslint-disable import/no-deprecated */ - this._priv_contentInfos.thumbnails = imageData; - this.trigger("imageTrackUpdate", - { data: this._priv_contentInfos.thumbnails }); - /* eslint-enable import/no-deprecated */ - } - } - } - } - - /** - * Triggered when we received a fatal error. - * Clean-up ressources and signal that the content has stopped on error. - * @param {Error} error - */ - private _priv_onPlaybackError(error : unknown) : void { - const formattedError = formatError(error, { - defaultCode: "NONE", - defaultReason: "An unknown error stopped content playback.", - }); - formattedError.fatal = true; - - if (this._priv_contentInfos !== null) { - this._priv_contentInfos.currentContentCanceller.cancel(); - } - this._priv_cleanUpCurrentContentState(); - this._priv_currentError = formattedError; - log.error("API: The player stopped because of an error", - error instanceof Error ? error : ""); - this._priv_setPlayerState(PLAYER_STATES.STOPPED); - - // TODO This condition is here because the eventual callback called when the - // player state is updated can launch a new content, thus the error will not - // be here anymore, in which case triggering the "error" event is unwanted. - // This is very ugly though, and we should probable have a better solution - if (this._priv_currentError === formattedError) { - this.trigger("error", formattedError); - } - } - - /** - * Triggered when we received a warning event during playback. - * Trigger the right API event. - * @param {Error} error - */ - private _priv_onPlaybackWarning(error : IPlayerError) : void { - const formattedError = formatError(error, { - defaultCode: "NONE", - defaultReason: "An unknown error happened.", - }); - log.warn("API: Sending warning:", formattedError); - this.trigger("warning", formattedError); - } - /** * Triggered when the Manifest has been loaded for the current content. * Initialize various private properties and emit initial event. - * @param {Object} value + * @param {Object} contentInfos + * @param {Object} manifest */ - private _priv_onManifestReady({ manifest } : { manifest : Manifest }) : void { - const contentInfos = this._priv_contentInfos; - if (contentInfos === null) { - log.error("API: The manifest is loaded but no content is."); - return; + private _priv_onManifestReady( + contentInfos : IPublicApiContentInfos, + manifest : Manifest + ) : void { + if (contentInfos.contentId !== this._priv_contentInfos?.contentId) { + return; // Event for another content } contentInfos.manifest = manifest; + const cancelSignal = contentInfos.currentContentCanceller.signal; this._priv_reloadingMetadata.manifest = manifest; const { initialAudioTrack, initialTextTrack } = contentInfos; - this._priv_trackChoiceManager = new TrackChoiceManager({ + contentInfos.trackChoiceManager = new TrackChoiceManager({ preferTrickModeTracks: this._priv_preferTrickModeTracks, }); const preferredAudioTracks = initialAudioTrack === undefined ? this._priv_preferredAudioTracks : [initialAudioTrack]; - this._priv_trackChoiceManager.setPreferredAudioTracks(preferredAudioTracks, true); + contentInfos.trackChoiceManager.setPreferredAudioTracks(preferredAudioTracks, true); const preferredTextTracks = initialTextTrack === undefined ? this._priv_preferredTextTracks : [initialTextTrack]; - this._priv_trackChoiceManager.setPreferredTextTracks(preferredTextTracks, true); + contentInfos.trackChoiceManager.setPreferredTextTracks(preferredTextTracks, true); - this._priv_trackChoiceManager.setPreferredVideoTracks(this._priv_preferredVideoTracks, - true); - manifest.addEventListener("manifestUpdate", () => { + contentInfos.trackChoiceManager + .setPreferredVideoTracks(this._priv_preferredVideoTracks, + true); + manifest.addEventListener("manifestUpdate", (updates) => { // Update the tracks chosen if it changed - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.update(); + if (contentInfos.trackChoiceManager !== null) { + contentInfos.trackChoiceManager.update(); + } + const currentPeriod = this._priv_contentInfos?.currentPeriod ?? undefined; + const trackChoiceManager = this._priv_contentInfos?.trackChoiceManager; + if (currentPeriod === undefined || isNullOrUndefined(trackChoiceManager)) { + return; + } + for (const update of updates.updatedPeriods) { + if (update.period.id === currentPeriod.id) { + if (update.result.addedAdaptations.length > 0 || + update.result.removedAdaptations.length > 0) + { + // We might have new (or less) tracks, send events just to be sure + const audioTracks = trackChoiceManager.getAvailableAudioTracks(currentPeriod); + this._priv_triggerEventIfNotStopped("availableAudioTracksChange", + audioTracks ?? [], + cancelSignal); + const textTracks = trackChoiceManager.getAvailableTextTracks(currentPeriod); + this._priv_triggerEventIfNotStopped("availableTextTracksChange", + textTracks ?? [], + cancelSignal); + const videoTracks = trackChoiceManager.getAvailableVideoTracks(currentPeriod); + this._priv_triggerEventIfNotStopped("availableVideoTracksChange", + videoTracks ?? [], + cancelSignal); + } + } + return; } }, contentInfos.currentContentCanceller.signal); } @@ -2585,101 +2429,140 @@ class Player extends EventEmitter { * Triggered each times the current Period Changed. * Store and emit initial state for the Period. * - * @param {Object} value + * @param {Object} contentInfos + * @param {Object} periodInfo */ - private _priv_onActivePeriodChanged({ period } : { period : Period }) : void { - if (this._priv_contentInfos === null) { - log.error("API: The active period changed but no content is loaded"); - return; + private _priv_onActivePeriodChanged( + contentInfos : IPublicApiContentInfos, + { period } : { period : Period } + ) : void { + if (contentInfos.contentId !== this._priv_contentInfos?.contentId) { + return; // Event for another content } - this._priv_contentInfos.currentPeriod = period; + contentInfos.currentPeriod = period; + const cancelSignal = contentInfos.currentContentCanceller.signal; if (this._priv_contentEventsMemory.periodChange !== period) { this._priv_contentEventsMemory.periodChange = period; - this.trigger("periodChange", period); + this._priv_triggerEventIfNotStopped("periodChange", period, cancelSignal); } - this.trigger("availableAudioTracksChange", this.getAvailableAudioTracks()); - this.trigger("availableTextTracksChange", this.getAvailableTextTracks()); - this.trigger("availableVideoTracksChange", this.getAvailableVideoTracks()); + this._priv_triggerEventIfNotStopped("availableAudioTracksChange", + this.getAvailableAudioTracks(), + cancelSignal); + this._priv_triggerEventIfNotStopped("availableTextTracksChange", + this.getAvailableTextTracks(), + cancelSignal); + this._priv_triggerEventIfNotStopped("availableVideoTracksChange", + this.getAvailableVideoTracks(), + cancelSignal); + + const trackChoiceManager = this._priv_contentInfos?.trackChoiceManager; // Emit intial events for the Period - if (this._priv_trackChoiceManager !== null) { - const audioTrack = this._priv_trackChoiceManager.getChosenAudioTrack(period); - const textTrack = this._priv_trackChoiceManager.getChosenTextTrack(period); - const videoTrack = this._priv_trackChoiceManager.getChosenVideoTrack(period); - - this.trigger("audioTrackChange", audioTrack); - this.trigger("textTrackChange", textTrack); - this.trigger("videoTrackChange", videoTrack); + if (!isNullOrUndefined(trackChoiceManager)) { + const audioTrack = trackChoiceManager.getChosenAudioTrack(period); + this._priv_triggerEventIfNotStopped("audioTrackChange", + audioTrack, + cancelSignal); + const textTrack = trackChoiceManager.getChosenTextTrack(period); + this._priv_triggerEventIfNotStopped("textTrackChange", + textTrack, + cancelSignal); + const videoTrack = trackChoiceManager.getChosenVideoTrack(period); + this._priv_triggerEventIfNotStopped("videoTrackChange", + videoTrack, + cancelSignal); } else { - this.trigger("audioTrackChange", null); - this.trigger("textTrackChange", null); - this.trigger("videoTrackChange", null); + this._priv_triggerEventIfNotStopped("audioTrackChange", null, cancelSignal); + this._priv_triggerEventIfNotStopped("textTrackChange", null, cancelSignal); + this._priv_triggerEventIfNotStopped("videoTrackChange", null, cancelSignal); } this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange", - this.getAvailableAudioBitrates()); + this.getAvailableAudioBitrates(), + cancelSignal); + if (contentInfos.currentContentCanceller.isUsed()) { + return; + } this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange", - this.getAvailableVideoBitrates()); - + this.getAvailableVideoBitrates(), + cancelSignal); + if (contentInfos.currentContentCanceller.isUsed()) { + return; + } const audioBitrate = this._priv_getCurrentRepresentations()?.audio?.bitrate ?? -1; - this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", audioBitrate); + this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", + audioBitrate, + cancelSignal); + if (contentInfos.currentContentCanceller.isUsed()) { + return; + } const videoBitrate = this._priv_getCurrentRepresentations()?.video?.bitrate ?? -1; - this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", videoBitrate); + this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", + videoBitrate, + cancelSignal); } /** * Triggered each times a new "PeriodStream" is ready. * Choose the right Adaptation for the Period and emit it. + * @param {Object} contentInfos * @param {Object} value */ - private _priv_onPeriodStreamReady(value : { - type : IBufferType; - period : Period; - adaptation$ : Subject; - }) : void { - const { type, period, adaptation$ } = value; + private _priv_onPeriodStreamReady( + contentInfos : IPublicApiContentInfos, + value : { + type : IBufferType; + period : Period; + adaptationRef : ISharedReference; + } + ) : void { + if (contentInfos.contentId !== this._priv_contentInfos?.contentId) { + return; // Event for another content + } + const { type, period, adaptationRef } = value; + const trackChoiceManager = contentInfos.trackChoiceManager; switch (type) { case "video": - if (this._priv_trackChoiceManager === null) { + if (isNullOrUndefined(trackChoiceManager)) { log.error("API: TrackChoiceManager not instanciated for a new video period"); - adaptation$.next(null); + adaptationRef.setValue(null); } else { - this._priv_trackChoiceManager.addPeriod(type, period, adaptation$); - this._priv_trackChoiceManager.setInitialVideoTrack(period); + trackChoiceManager.addPeriod(type, period, adaptationRef); + trackChoiceManager.setInitialVideoTrack(period); } break; case "audio": - if (this._priv_trackChoiceManager === null) { + if (isNullOrUndefined(trackChoiceManager)) { log.error(`API: TrackChoiceManager not instanciated for a new ${type} period`); - adaptation$.next(null); + adaptationRef.setValue(null); } else { - this._priv_trackChoiceManager.addPeriod(type, period, adaptation$); - this._priv_trackChoiceManager.setInitialAudioTrack(period); + trackChoiceManager.addPeriod(type, period, adaptationRef); + trackChoiceManager.setInitialAudioTrack(period); } break; case "text": - if (this._priv_trackChoiceManager === null) { + if (isNullOrUndefined(trackChoiceManager)) { log.error(`API: TrackChoiceManager not instanciated for a new ${type} period`); - adaptation$.next(null); + adaptationRef.setValue(null); } else { - this._priv_trackChoiceManager.addPeriod(type, period, adaptation$); - this._priv_trackChoiceManager.setInitialTextTrack(period); + trackChoiceManager.addPeriod(type, period, adaptationRef); + trackChoiceManager.setInitialTextTrack(period); } break; default: const adaptations = period.adaptations[type]; if (!isNullOrUndefined(adaptations) && adaptations.length > 0) { - adaptation$.next(adaptations[0]); + adaptationRef.setValue(adaptations[0]); } else { - adaptation$.next(null); + adaptationRef.setValue(null); } break; } @@ -2687,30 +2570,32 @@ class Player extends EventEmitter { /** * Triggered each times we "remove" a PeriodStream. + * @param {Object} contentInfos * @param {Object} value */ - private _priv_onPeriodStreamCleared(value : { - type : IBufferType; - period : Period; - }) : void { + private _priv_onPeriodStreamCleared( + contentInfos : IPublicApiContentInfos, + value : { type : IBufferType; period : Period } + ) : void { + if (contentInfos.contentId !== this._priv_contentInfos?.contentId) { + return; // Event for another content + } const { type, period } = value; + const trackChoiceManager = contentInfos.trackChoiceManager; // Clean-up track choice from TrackChoiceManager switch (type) { case "audio": case "text": case "video": - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.removePeriod(type, period); + if (!isNullOrUndefined(trackChoiceManager)) { + trackChoiceManager.removePeriod(type, period); } break; } // Clean-up stored Representation and Adaptation information - if (this._priv_contentInfos === null) { - return ; - } - const { activeAdaptations, activeRepresentations } = this._priv_contentInfos; + const { activeAdaptations, activeRepresentations } = contentInfos; if (!isNullOrUndefined(activeAdaptations) && !isNullOrUndefined(activeAdaptations[period.id])) { @@ -2732,44 +2617,29 @@ class Player extends EventEmitter { } } - /** - * Triggered each time the content is re-loaded on the MediaSource. - */ - private _priv_onReloadingMediaSource() { - if (this._priv_contentInfos !== null) { - this._priv_contentInfos.segmentBuffersStore = null; - } - if (this._priv_trackChoiceManager !== null) { - this._priv_trackChoiceManager.resetPeriods(); - } - } - /** * Triggered each times a new Adaptation is considered for the current * content. * Store given Adaptation and emit it if from the current Period. + * @param {Object} contentInfos * @param {Object} value */ - private _priv_onAdaptationChange({ - type, - adaptation, - period, - } : { - type : IBufferType; - adaptation : Adaptation|null; - period : Period; - }) : void { - if (this._priv_contentInfos === null) { - log.error("API: The adaptations changed but no content is loaded"); - return; + private _priv_onAdaptationChange( + contentInfos : IPublicApiContentInfos, + { type, adaptation, period } : { type : IBufferType; + adaptation : Adaptation|null; + period : Period; } + ) : void { + if (contentInfos.contentId !== this._priv_contentInfos?.contentId) { + return; // Event for another content } - // lazily create this._priv_contentInfos.activeAdaptations - if (this._priv_contentInfos.activeAdaptations === null) { - this._priv_contentInfos.activeAdaptations = {}; + // lazily create contentInfos.activeAdaptations + if (contentInfos.activeAdaptations === null) { + contentInfos.activeAdaptations = {}; } - const { activeAdaptations, currentPeriod } = this._priv_contentInfos; + const { activeAdaptations, currentPeriod } = contentInfos; const activePeriodAdaptations = activeAdaptations[period.id]; if (isNullOrUndefined(activePeriodAdaptations)) { activeAdaptations[period.id] = { [type]: adaptation }; @@ -2777,33 +2647,38 @@ class Player extends EventEmitter { activePeriodAdaptations[type] = adaptation; } - if (this._priv_trackChoiceManager !== null && + const { trackChoiceManager } = contentInfos; + const cancelSignal = contentInfos.currentContentCanceller.signal; + if (trackChoiceManager !== null && currentPeriod !== null && !isNullOrUndefined(period) && period.id === currentPeriod.id) { switch (type) { case "audio": - const audioTrack = this._priv_trackChoiceManager - .getChosenAudioTrack(currentPeriod); - this.trigger("audioTrackChange", audioTrack); + const audioTrack = trackChoiceManager.getChosenAudioTrack(currentPeriod); + this._priv_triggerEventIfNotStopped("audioTrackChange", + audioTrack, + cancelSignal); const availableAudioBitrates = this.getAvailableAudioBitrates(); this._priv_triggerAvailableBitratesChangeEvent("availableAudioBitratesChange", - availableAudioBitrates); + availableAudioBitrates, + cancelSignal); break; case "text": - const textTrack = this._priv_trackChoiceManager - .getChosenTextTrack(currentPeriod); - this.trigger("textTrackChange", textTrack); + const textTrack = trackChoiceManager.getChosenTextTrack(currentPeriod); + this._priv_triggerEventIfNotStopped("textTrackChange", textTrack, cancelSignal); break; case "video": - const videoTrack = this._priv_trackChoiceManager - .getChosenVideoTrack(currentPeriod); - this.trigger("videoTrackChange", videoTrack); + const videoTrack = trackChoiceManager.getChosenVideoTrack(currentPeriod); + this._priv_triggerEventIfNotStopped("videoTrackChange", + videoTrack, + cancelSignal); const availableVideoBitrates = this.getAvailableVideoBitrates(); this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange", - availableVideoBitrates); + availableVideoBitrates, + cancelSignal); break; } } @@ -2814,28 +2689,25 @@ class Player extends EventEmitter { * * Store given Representation and emit it if from the current Period. * + * @param {Object} contentInfos * @param {Object} obj */ - private _priv_onRepresentationChange({ - type, - period, - representation, - }: { - type : IBufferType; - period : Period; - representation : Representation|null; - }) : void { - if (this._priv_contentInfos === null) { - log.error("API: The representations changed but no content is loaded"); - return; + private _priv_onRepresentationChange( + contentInfos : IPublicApiContentInfos, + { type, period, representation }: { type : IBufferType; + period : Period; + representation : Representation|null; } + ) : void { + if (contentInfos.contentId !== this._priv_contentInfos?.contentId) { + return; // Event for another content } - // lazily create this._priv_contentInfos.activeRepresentations - if (this._priv_contentInfos.activeRepresentations === null) { - this._priv_contentInfos.activeRepresentations = {}; + // lazily create contentInfos.activeRepresentations + if (contentInfos.activeRepresentations === null) { + contentInfos.activeRepresentations = {}; } - const { activeRepresentations, currentPeriod } = this._priv_contentInfos; + const { activeRepresentations, currentPeriod } = contentInfos; const activePeriodRepresentations = activeRepresentations[period.id]; if (isNullOrUndefined(activePeriodRepresentations)) { @@ -2849,10 +2721,15 @@ class Player extends EventEmitter { currentPeriod !== null && currentPeriod.id === period.id) { + const cancelSignal = this._priv_contentInfos.currentContentCanceller.signal; if (type === "video") { - this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", bitrate); + this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", + bitrate, + cancelSignal); } else if (type === "audio") { - this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", bitrate); + this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", + bitrate, + cancelSignal); } } } @@ -2907,15 +2784,18 @@ class Player extends EventEmitter { * * Trigger the right Player Event * + * @param {Object} contentInfos * @param {Object} observation */ - private _priv_triggerPositionUpdate(observation : IPlaybackObservation) : void { - if (this._priv_contentInfos === null) { - log.warn("API: Cannot perform time update: no content loaded."); - return; + private _priv_triggerPositionUpdate( + contentInfos : IPublicApiContentInfos, + observation : IPlaybackObservation + ) : void { + if (contentInfos.contentId !== this._priv_contentInfos?.contentId) { + return; // Event for another content } - const { isDirectFile, manifest } = this._priv_contentInfos; + const { isDirectFile, manifest } = contentInfos; if ((!isDirectFile && manifest === null) || isNullOrUndefined(observation)) { return; } @@ -2959,13 +2839,17 @@ class Player extends EventEmitter { * the previously stored value. * @param {string} event * @param {Array.} newVal + * @param {Object} currentContentCancelSignal */ private _priv_triggerAvailableBitratesChangeEvent( event : "availableAudioBitratesChange" | "availableVideoBitratesChange", - newVal : number[] + newVal : number[], + currentContentCancelSignal : CancellationSignal ) : void { const prevVal = this._priv_contentEventsMemory[event]; - if (prevVal === undefined || !areArraysOfNumbersEqual(newVal, prevVal)) { + if (!currentContentCancelSignal.isCancelled() && + (prevVal === undefined || !areArraysOfNumbersEqual(newVal, prevVal))) + { this._priv_contentEventsMemory[event] = newVal; this.trigger(event, newVal); } @@ -2976,12 +2860,16 @@ class Player extends EventEmitter { * previously stored value. * @param {string} event * @param {number} newVal + * @param {Object} currentContentCancelSignal */ private _priv_triggerCurrentBitrateChangeEvent( event : "audioBitrateChange" | "videoBitrateChange", - newVal : number + newVal : number, + currentContentCancelSignal : CancellationSignal ) : void { - if (newVal !== this._priv_contentEventsMemory[event]) { + if (!currentContentCancelSignal.isCancelled() && + newVal !== this._priv_contentEventsMemory[event]) + { this._priv_contentEventsMemory[event] = newVal; this.trigger(event, newVal); } @@ -3001,8 +2889,103 @@ class Player extends EventEmitter { } return activeRepresentations[currentPeriod.id]; } + + /** + * @param {string} evt + * @param {*} arg + * @param {Object} currentContentCancelSignal + */ + private _priv_triggerEventIfNotStopped( + evt : TEventName, + arg : IEventPayload, + currentContentCancelSignal : CancellationSignal + ) { + if (!currentContentCancelSignal.isCancelled()) { + this.trigger(evt, arg); + } + } + + /** + * @param {Object} defaultAudioTrack + * @param {Object} defaultTextTrack + * @param {Object} cancelSignal + * @returns {Object} + */ + private _priv_initializeMediaElementTrackChoiceManager( + defaultAudioTrack : IAudioTrackPreference | null | undefined, + defaultTextTrack : ITextTrackPreference | null | undefined, + cancelSignal : CancellationSignal + ) : MediaElementTrackChoiceManager { + assert(features.directfile !== null, + "Initializing `MediaElementTrackChoiceManager` without Directfile feature"); + assert(this.videoElement !== null, + "Initializing `MediaElementTrackChoiceManager` on a disposed RxPlayer"); + + const mediaElementTrackChoiceManager = + new features.directfile.mediaElementTrackChoiceManager(this.videoElement); + + const preferredAudioTracks = defaultAudioTrack === undefined ? + this._priv_preferredAudioTracks : + [defaultAudioTrack]; + mediaElementTrackChoiceManager.setPreferredAudioTracks(preferredAudioTracks, true); + + const preferredTextTracks = defaultTextTrack === undefined ? + this._priv_preferredTextTracks : + [defaultTextTrack]; + mediaElementTrackChoiceManager.setPreferredTextTracks(preferredTextTracks, true); + + mediaElementTrackChoiceManager + .setPreferredVideoTracks(this._priv_preferredVideoTracks, true); + + this._priv_triggerEventIfNotStopped( + "availableAudioTracksChange", + mediaElementTrackChoiceManager.getAvailableAudioTracks(), + cancelSignal); + this._priv_triggerEventIfNotStopped( + "availableVideoTracksChange", + mediaElementTrackChoiceManager.getAvailableVideoTracks(), + cancelSignal); + this._priv_triggerEventIfNotStopped( + "availableTextTracksChange", + mediaElementTrackChoiceManager.getAvailableTextTracks(), + cancelSignal); + this._priv_triggerEventIfNotStopped( + "audioTrackChange", + mediaElementTrackChoiceManager.getChosenAudioTrack() ?? null, + cancelSignal); + this._priv_triggerEventIfNotStopped( + "textTrackChange", + mediaElementTrackChoiceManager.getChosenTextTrack() ?? null, + cancelSignal); + this._priv_triggerEventIfNotStopped( + "videoTrackChange", + mediaElementTrackChoiceManager.getChosenVideoTrack() ?? null, + cancelSignal); + + mediaElementTrackChoiceManager + .addEventListener("availableVideoTracksChange", (val) => + this.trigger("availableVideoTracksChange", val)); + mediaElementTrackChoiceManager + .addEventListener("availableAudioTracksChange", (val) => + this.trigger("availableAudioTracksChange", val)); + mediaElementTrackChoiceManager + .addEventListener("availableTextTracksChange", (val) => + this.trigger("availableTextTracksChange", val)); + + mediaElementTrackChoiceManager + .addEventListener("audioTrackChange", (val) => + this.trigger("audioTrackChange", val)); + mediaElementTrackChoiceManager + .addEventListener("videoTrackChange", (val) => + this.trigger("videoTrackChange", val)); + mediaElementTrackChoiceManager + .addEventListener("textTrackChange", (val) => + this.trigger("textTrackChange", val)); + + return mediaElementTrackChoiceManager; + } } -Player.version = /* PLAYER_VERSION */"3.29.0"; +Player.version = /* PLAYER_VERSION */"3.30.0"; /** Every events sent by the RxPlayer's public API. */ interface IPublicAPIEvent { @@ -3034,4 +3017,74 @@ interface IPublicAPIEvent { inbandEvents : IInbandEvent[]; } +/** State linked to a particular contents loaded by the public API. */ +interface IPublicApiContentInfos { + /** + * Unique identifier for this `IPublicApiContentInfos` object. + * Allows to identify and thus compare this `contentInfos` object with another + * one. + */ + contentId : string; + /** Original URL set to load the content. */ + originalUrl : string | undefined; + /** `ContentInitializer` used to load the content. */ + initializer : ContentInitializer; + /** TaskCanceller triggered when it's time to stop the current content. */ + currentContentCanceller : TaskCanceller; + /** + * `true` if the current content is in DirectFile mode. + * `false` is the current content has a transport protocol (Smooth/DASH...). + */ + isDirectFile : boolean; + /** + * Current Image Track Data associated to the content. + * `null` if the current content has no image playlist linked to it. + * @deprecated + */ + thumbnails : IBifThumbnail[]|null; + /** + * Manifest linked to the current content. + * `null` if the current content loaded has no manifest or if the content is + * not yet loaded. + */ + manifest : Manifest|null; + /** + * Current Period being played. + * `null` if no Period is being played. + */ + currentPeriod : Period|null; + /** + * Store currently considered adaptations, per active period. + * `null` if no Adaptation is active + */ + activeAdaptations : { + [periodId : string] : Partial>; + } | null; + /** + * Store currently considered representations, per active period. + * `null` if no Representation is active + */ + activeRepresentations : { + [periodId : string] : Partial>; + } | null; + /** Store starting audio track if one. */ + initialAudioTrack : undefined|IAudioTrackPreference; + /** Store starting text track if one. */ + initialTextTrack : undefined|ITextTrackPreference; + /** Keep information on the active SegmentBuffers. */ + segmentBuffersStore : SegmentBuffersStore | null; + /** + * TrackChoiceManager instance linked to the current content. + * `null` if no content has been loaded or if the current content loaded + * has no TrackChoiceManager. + */ + trackChoiceManager : TrackChoiceManager|null; + /** + * MediaElementTrackChoiceManager instance linked to the current content. + * `null` if no content has been loaded or if the current content loaded + * has no MediaElementTrackChoiceManager. + */ + mediaElementTrackChoiceManager : MediaElementTrackChoiceManager|null; +} + export default Player; diff --git a/src/core/api/tracks_management/track_choice_manager.ts b/src/core/api/tracks_management/track_choice_manager.ts index 51f3c1ab7a..d8e90573be 100644 --- a/src/core/api/tracks_management/track_choice_manager.ts +++ b/src/core/api/tracks_management/track_choice_manager.ts @@ -19,7 +19,6 @@ * switching for an easier API management. */ -import { Subject } from "rxjs"; import log from "../../../log"; import { Adaptation, @@ -43,26 +42,35 @@ import arrayFind from "../../../utils/array_find"; import arrayIncludes from "../../../utils/array_includes"; import isNullOrUndefined from "../../../utils/is_null_or_undefined"; import normalizeLanguage from "../../../utils/languages"; +import { ISharedReference } from "../../../utils/reference"; import SortedList from "../../../utils/sorted_list"; import takeFirstSet from "../../../utils/take_first_set"; /** Audio information stored for a single Period. */ -interface ITMPeriodAudioInfos { adaptations : Adaptation[]; - adaptation$ : Subject; } +interface ITMPeriodAudioInfos { + adaptations : Adaptation[]; + adaptationRef : ISharedReference; +} /** Text information stored for a single Period. */ -interface ITMPeriodTextInfos { adaptations : Adaptation[]; - adaptation$ : Subject; } +interface ITMPeriodTextInfos { + adaptations : Adaptation[]; + adaptationRef : ISharedReference; +} /** Video information stored for a single Period. */ -interface ITMPeriodVideoInfos { adaptations : Adaptation[]; - adaptation$ : Subject; } +interface ITMPeriodVideoInfos { + adaptations : Adaptation[]; + adaptationRef : ISharedReference; +} /** Every information stored for a single Period. */ -interface ITMPeriodInfos { period : Period; - audio? : ITMPeriodAudioInfos; - text? : ITMPeriodTextInfos; - video? : ITMPeriodVideoInfos; } +interface ITMPeriodInfos { + period : Period; + audio? : ITMPeriodAudioInfos; + text? : ITMPeriodTextInfos; + video? : ITMPeriodVideoInfos; +} /** Audio track preference once normalized by the TrackChoiceManager. */ type INormalizedPreferredAudioTrack = null | @@ -84,6 +92,7 @@ type INormalizedPreferredTextTrack = null | /** Text track preference when it is not set to `null`. */ interface INormalizedPreferredTextTrackObject { normalized : string; + forced : boolean | undefined; closedCaption : boolean; } @@ -116,6 +125,7 @@ function normalizeTextTracks( return tracks.map(t => t === null ? t : { normalized: normalizeLanguage(t.language), + forced: t.forced, closedCaption: t.closedCaption }); } @@ -247,16 +257,15 @@ export default class TrackChoiceManager { } /** - * Add Subject to choose Adaptation for new "audio" or "text" Period. + * Add shared reference to choose Adaptation for new "audio" or "text" Period. * @param {string} bufferType - The concerned buffer type * @param {Period} period - The concerned Period. - * @param {Subject.} adaptation$ - A subject through which the - * choice will be given + * @param {Object} adaptationRef */ public addPeriod( bufferType : "audio" | "text"| "video", period : Period, - adaptation$ : Subject + adaptationRef : ISharedReference ) : void { const periodItem = getPeriodItem(this._periods, period); const adaptations = period.getSupportedAdaptations(bufferType); @@ -266,17 +275,17 @@ export default class TrackChoiceManager { period.start); return; } else { - periodItem[bufferType] = { adaptations, adaptation$ }; + periodItem[bufferType] = { adaptations, adaptationRef }; } } else { this._periods.add({ period, - [bufferType]: { adaptations, adaptation$ } }); + [bufferType]: { adaptations, adaptationRef } }); } } /** - * Remove Subject to choose an "audio", "video" or "text" Adaptation for a - * Period. + * Remove shared reference to choose an "audio", "video" or "text" Adaptation + * for a Period. * @param {string} bufferType - The concerned buffer type * @param {Period} period - The concerned Period. */ @@ -324,7 +333,7 @@ export default class TrackChoiceManager { } /** - * Emit initial audio Adaptation through the given Subject based on: + * Emit initial audio Adaptation through the given shared reference based on: * - the preferred audio tracks * - the last choice for this period, if one * @param {Period} period - The concerned Period. @@ -342,7 +351,7 @@ export default class TrackChoiceManager { if (chosenAudioAdaptation === null) { // If the Period was previously without audio, keep it that way - audioInfos.adaptation$.next(null); + audioInfos.adaptationRef.setValue(null); } else if (chosenAudioAdaptation === undefined || !arrayIncludes(audioAdaptations, chosenAudioAdaptation) ) { @@ -353,14 +362,14 @@ export default class TrackChoiceManager { normalizedPref); this._audioChoiceMemory.set(period, optimalAdaptation); - audioInfos.adaptation$.next(optimalAdaptation); + audioInfos.adaptationRef.setValue(optimalAdaptation); } else { - audioInfos.adaptation$.next(chosenAudioAdaptation); // set last one + audioInfos.adaptationRef.setValue(chosenAudioAdaptation); // set last one } } /** - * Emit initial text Adaptation through the given Subject based on: + * Emit initial text Adaptation through the given shared reference based on: * - the preferred text tracks * - the last choice for this period, if one * @param {Period} period - The concerned Period. @@ -377,24 +386,26 @@ export default class TrackChoiceManager { const chosenTextAdaptation = this._textChoiceMemory.get(period); if (chosenTextAdaptation === null) { // If the Period was previously without text, keep it that way - textInfos.adaptation$.next(null); + textInfos.adaptationRef.setValue(null); } else if (chosenTextAdaptation === undefined || !arrayIncludes(textAdaptations, chosenTextAdaptation) ) { // Find the optimal text Adaptation const preferredTextTracks = this._preferredTextTracks; const normalizedPref = normalizeTextTracks(preferredTextTracks); - const optimalAdaptation = findFirstOptimalTextAdaptation(textAdaptations, - normalizedPref); + const optimalAdaptation = findFirstOptimalTextAdaptation( + textAdaptations, + normalizedPref, + this._audioChoiceMemory.get(period)); this._textChoiceMemory.set(period, optimalAdaptation); - textInfos.adaptation$.next(optimalAdaptation); + textInfos.adaptationRef.setValue(optimalAdaptation); } else { - textInfos.adaptation$.next(chosenTextAdaptation); // set last one + textInfos.adaptationRef.setValue(chosenTextAdaptation); // set last one } } /** - * Emit initial video Adaptation through the given Subject based on: + * Emit initial video Adaptation through the given shared reference based on: * - the preferred video tracks * - the last choice for this period, if one * @param {Period} period - The concerned Period. @@ -429,7 +440,7 @@ export default class TrackChoiceManager { if (newBaseAdaptation === null) { this._videoChoiceMemory.set(period, null); - videoInfos.adaptation$.next(null); + videoInfos.adaptationRef.setValue(null); return; } @@ -437,7 +448,7 @@ export default class TrackChoiceManager { this.trickModeTrackEnabled); this._videoChoiceMemory.set(period, { baseAdaptation: newBaseAdaptation, adaptation: newVideoAdaptation }); - videoInfos.adaptation$.next(newVideoAdaptation); + videoInfos.adaptationRef.setValue(newVideoAdaptation); } /** @@ -465,7 +476,7 @@ export default class TrackChoiceManager { } this._audioChoiceMemory.set(period, wantedAdaptation); - audioInfos.adaptation$.next(wantedAdaptation); + audioInfos.adaptationRef.setValue(wantedAdaptation); } /** @@ -492,7 +503,7 @@ export default class TrackChoiceManager { } this._textChoiceMemory.set(period, wantedAdaptation); - textInfos.adaptation$.next(wantedAdaptation); + textInfos.adaptationRef.setValue(wantedAdaptation); } /** @@ -523,7 +534,7 @@ export default class TrackChoiceManager { this.trickModeTrackEnabled); this._videoChoiceMemory.set(period, { baseAdaptation: wantedBaseAdaptation, adaptation: newVideoAdaptation }); - videoInfos.adaptation$.next(newVideoAdaptation); + videoInfos.adaptationRef.setValue(newVideoAdaptation); } /** @@ -546,7 +557,7 @@ export default class TrackChoiceManager { } this._textChoiceMemory.set(period, null); - textInfos.adaptation$.next(null); + textInfos.adaptationRef.setValue(null); } /** @@ -565,7 +576,7 @@ export default class TrackChoiceManager { return; } this._videoChoiceMemory.set(period, null); - videoInfos.adaptation$.next(null); + videoInfos.adaptationRef.setValue(null); } public disableVideoTrickModeTracks(): void { @@ -645,13 +656,17 @@ export default class TrackChoiceManager { return null; } - return { + const formatted : ITextTrack = { language: takeFirstSet(chosenTextAdaptation.language, ""), normalized: takeFirstSet(chosenTextAdaptation.normalizedLanguage, ""), closedCaption: chosenTextAdaptation.isClosedCaption === true, id: chosenTextAdaptation.id, label: chosenTextAdaptation.label, }; + if (chosenTextAdaptation.isForcedSubtitles !== undefined) { + formatted.forced = chosenTextAdaptation.isForcedSubtitles; + } + return formatted; } /** @@ -768,15 +783,21 @@ export default class TrackChoiceManager { null; return textInfos.adaptations - .map((adaptation) => ({ - language: takeFirstSet(adaptation.language, ""), - normalized: takeFirstSet(adaptation.normalizedLanguage, ""), - closedCaption: adaptation.isClosedCaption === true, - id: adaptation.id, - active: currentId === null ? false : - currentId === adaptation.id, - label: adaptation.label, - })); + .map((adaptation) => { + const formatted : IAvailableTextTrack = { + language: takeFirstSet(adaptation.language, ""), + normalized: takeFirstSet(adaptation.normalizedLanguage, ""), + closedCaption: adaptation.isClosedCaption === true, + id: adaptation.id, + active: currentId === null ? false : + currentId === adaptation.id, + label: adaptation.label, + }; + if (adaptation.isForcedSubtitles !== undefined) { + formatted.forced = adaptation.isForcedSubtitles; + } + return formatted; + }); } /** @@ -906,7 +927,7 @@ export default class TrackChoiceManager { normalizedPref); this._audioChoiceMemory.set(period, optimalAdaptation); - audioItem.adaptation$.next(optimalAdaptation); + audioItem.adaptationRef.setValue(optimalAdaptation); // previous "next" call could have changed everything, start over recursiveUpdateAudioTrack(0); @@ -956,11 +977,13 @@ export default class TrackChoiceManager { return; } - const optimalAdaptation = findFirstOptimalTextAdaptation(textAdaptations, - normalizedPref); + const optimalAdaptation = findFirstOptimalTextAdaptation( + textAdaptations, + normalizedPref, + this._audioChoiceMemory.get(period)); this._textChoiceMemory.set(period, optimalAdaptation); - textItem.adaptation$.next(optimalAdaptation); + textItem.adaptationRef.setValue(optimalAdaptation); // previous "next" call could have changed everything, start over recursiveUpdateTextTrack(0); @@ -1020,7 +1043,7 @@ export default class TrackChoiceManager { baseAdaptation: chosenVideoAdaptation.baseAdaptation, adaptation: wantedVideoAdaptation, }); - videoItem.adaptation$.next(wantedVideoAdaptation); + videoItem.adaptationRef.setValue(wantedVideoAdaptation); // previous "next" call could have changed everything, start over return recursiveUpdateVideoTrack(0); @@ -1031,7 +1054,7 @@ export default class TrackChoiceManager { preferredVideoTracks); if (optimalAdaptation === null) { this._videoChoiceMemory.set(period, null); - videoItem.adaptation$.next(null); + videoItem.adaptationRef.setValue(null); // previous "next" call could have changed everything, start over return recursiveUpdateVideoTrack(0); } @@ -1040,7 +1063,7 @@ export default class TrackChoiceManager { this.trickModeTrackEnabled); this._videoChoiceMemory.set(period, { baseAdaptation: optimalAdaptation, adaptation: newVideoAdaptation }); - videoItem.adaptation$.next(newVideoAdaptation); + videoItem.adaptationRef.setValue(newVideoAdaptation); // previous "next" call could have changed everything, start over return recursiveUpdateVideoTrack(0); @@ -1162,7 +1185,9 @@ function createTextPreferenceMatcher( return takeFirstSet(textAdaptation.normalizedLanguage, "") === preferredTextTrack.normalized && (preferredTextTrack.closedCaption ? textAdaptation.isClosedCaption === true : - textAdaptation.isClosedCaption !== true); + textAdaptation.isClosedCaption !== true) && + (preferredTextTrack.forced === true ? textAdaptation.isForcedSubtitles === true : + textAdaptation.isForcedSubtitles !== true); }; } @@ -1173,12 +1198,14 @@ function createTextPreferenceMatcher( * `null` if the most optimal text adaptation is no text adaptation. * @param {Array.} textAdaptations * @param {Array.} preferredTextTracks + * @param {Object|null|undefined} chosenAudioAdaptation * @returns {Adaptation|null} */ function findFirstOptimalTextAdaptation( textAdaptations : Adaptation[], - preferredTextTracks : INormalizedPreferredTextTrack[] -) : Adaptation|null { + preferredTextTracks : INormalizedPreferredTextTrack[], + chosenAudioAdaptation : Adaptation | null | undefined +) : Adaptation | null { if (textAdaptations.length === 0) { return null; } @@ -1198,6 +1225,19 @@ function findFirstOptimalTextAdaptation( } } + const forcedSubtitles = textAdaptations.filter((ad) => ad.isForcedSubtitles === true); + if (forcedSubtitles.length > 0) { + if (chosenAudioAdaptation !== null && chosenAudioAdaptation !== undefined) { + const sameLanguage = arrayFind(forcedSubtitles, (f) => + f.normalizedLanguage === chosenAudioAdaptation.normalizedLanguage); + if (sameLanguage !== undefined) { + return sameLanguage; + } + } + return arrayFind(forcedSubtitles, (f) => f.normalizedLanguage === undefined) ?? + null; + } + // no optimal adaptation return null; } diff --git a/src/core/api/utils.ts b/src/core/api/utils.ts index 1b3052c780..643dbe17dd 100644 --- a/src/core/api/utils.ts +++ b/src/core/api/utils.ts @@ -14,58 +14,60 @@ * limitations under the License. */ -import { - defer as observableDefer, - EMPTY, - filter, - map, - merge as observableMerge, - Observable, - startWith, - switchMap, - take, -} from "rxjs"; import config from "../../config"; import { IPlayerState } from "../../public_types"; -import { IStallingSituation } from "../init"; -import { IPlaybackObservation } from "./playback_observer"; +import arrayIncludes from "../../utils/array_includes"; +import createSharedReference, { + IReadOnlySharedReference, +} from "../../utils/reference"; +import { CancellationSignal } from "../../utils/task_canceller"; +import { + ContentInitializer, + IStallingSituation, +} from "../init"; +import { + IPlaybackObservation, + IReadOnlyPlaybackObserver, +} from "./playback_observer"; /** - * Returns Observable which will emit: - * - `"seeking"` when we are seeking in the given mediaElement - * - `"seeked"` when a seek is considered as finished by the given observation$ - * Observable. * @param {HTMLMediaElement} mediaElement - * @param {Observable} observation$ - * @returns {Observable} + * @param {Object} playbackObserver - Observes playback conditions on + * `mediaElement`. + * @param {function} onSeeking - Callback called when a seeking operation starts + * on `mediaElement`. + * @param {function} onSeeked - Callback called when a seeking operation ends + * on `mediaElement`. + * @param {Object} cancelSignal - When triggered, stop calling callbacks and + * remove all listeners this function has registered. */ export function emitSeekEvents( mediaElement : HTMLMediaElement | null, - observation$ : Observable -) : Observable<"seeking" | "seeked"> { - return observableDefer(() => { - if (mediaElement === null) { - return EMPTY; - } - - let isSeeking$ = observation$.pipe( - filter((observation : IPlaybackObservation) => observation.event === "seeking"), - map(() => "seeking" as const) - ); + playbackObserver : IReadOnlyPlaybackObserver, + onSeeking: () => void, + onSeeked: () => void, + cancelSignal : CancellationSignal +) : void { + if (cancelSignal.isCancelled() || mediaElement === null) { + return ; + } - if (mediaElement.seeking) { - isSeeking$ = isSeeking$.pipe(startWith("seeking" as const)); + let wasSeeking = playbackObserver.getReference().getValue().seeking; + if (wasSeeking) { + onSeeking(); + if (cancelSignal.isCancelled()) { + return; } - - const hasSeeked$ = isSeeking$.pipe( - switchMap(() => - observation$.pipe( - filter((observation : IPlaybackObservation) => observation.event === "seeked"), - map(() => "seeked" as const), - take(1))) - ); - return observableMerge(isSeeking$, hasSeeked$); - }); + } + playbackObserver.listen((obs) => { + if (obs.event === "seeking") { + wasSeeking = true; + onSeeking(); + } else if (wasSeeking && obs.event === "seeked") { + wasSeeking = false; + onSeeked(); + } + }, { includeLastObservation: true, clearSignal: cancelSignal }); } /** Player state dictionnary. */ @@ -81,6 +83,68 @@ export const enum PLAYER_STATES { RELOADING = "RELOADING", } +export function constructPlayerStateReference( + initializer : ContentInitializer, + mediaElement : HTMLMediaElement, + playbackObserver : IReadOnlyPlaybackObserver, + cancelSignal : CancellationSignal +) : IReadOnlySharedReference { + const playerStateRef = createSharedReference(PLAYER_STATES.LOADING, + cancelSignal); + initializer.addEventListener("loaded", () => { + if (playerStateRef.getValue() === PLAYER_STATES.LOADING) { + playerStateRef.setValue(PLAYER_STATES.LOADED); + if (!cancelSignal.isCancelled()) { + const newState = getLoadedContentState(mediaElement, null); + if (newState !== PLAYER_STATES.PAUSED) { + playerStateRef.setValue(newState); + } + } + } else { + playerStateRef.setValueIfChanged(getLoadedContentState(mediaElement, null)); + } + }, cancelSignal); + + initializer.addEventListener("reloadingMediaSource", () => { + if (isLoadedState(playerStateRef.getValue())) { + playerStateRef.setValueIfChanged(PLAYER_STATES.RELOADING); + } + }, cancelSignal); + + /** + * Keep track of the last known stalling situation. + * `null` if playback is not stalled. + */ + let prevStallReason : IStallingSituation | null = null; + initializer.addEventListener("stalled", (s) => { + if (s !== prevStallReason) { + if (isLoadedState(playerStateRef.getValue())) { + playerStateRef.setValueIfChanged(getLoadedContentState(mediaElement, s)); + } + prevStallReason = s; + } + }, cancelSignal); + initializer.addEventListener("unstalled", () => { + if (prevStallReason !== null) { + if (isLoadedState(playerStateRef.getValue())) { + playerStateRef.setValueIfChanged(getLoadedContentState(mediaElement, null)); + } + prevStallReason = null; + } + }, cancelSignal); + + playbackObserver.listen((observation) => { + if (isLoadedState(playerStateRef.getValue()) && + arrayIncludes(["seeking", "ended", "play", "pause"], observation.event)) + { + playerStateRef.setValueIfChanged( + getLoadedContentState(mediaElement, prevStallReason) + ); + } + }, { clearSignal: cancelSignal }); + return playerStateRef; +} + /** * Get state string for a _loaded_ content. * @param {HTMLMediaElement} mediaElement @@ -118,3 +182,9 @@ export function getLoadedContentState( return mediaElement.paused ? PLAYER_STATES.PAUSED : PLAYER_STATES.PLAYING; } + +export function isLoadedState(state : IPlayerState) : boolean { + return state !== PLAYER_STATES.LOADING && + state !== PLAYER_STATES.RELOADING && + state !== PLAYER_STATES.STOPPED; +} diff --git a/src/core/decrypt/__tests__/__global__/get_license.test.ts b/src/core/decrypt/__tests__/__global__/get_license.test.ts index e4d7b286e7..9a26a9cb8e 100644 --- a/src/core/decrypt/__tests__/__global__/get_license.test.ts +++ b/src/core/decrypt/__tests__/__global__/get_license.test.ts @@ -180,16 +180,16 @@ describe("core - decrypt - global tests - getLicense", () => { it("should fail after first failure when getLicenseConfig.retry is set to `0`", async () => { await checkGetLicense({ isGetLicensePromiseBased: true, - configuredRetries: 1, + configuredRetries: 0, configuredTimeout: undefined, getTimeout: () => undefined, - nbRetries: 0, + nbRetries: 1, ignoreLicenseRequests: false }); }); it("should fail after two failures when getLicenseConfig.retry is set to `1`", async () => { await checkGetLicense({ isGetLicensePromiseBased: true, - configuredRetries: 2, + configuredRetries: 1, configuredTimeout: undefined, getTimeout: () => undefined, nbRetries: 2, @@ -198,28 +198,28 @@ describe("core - decrypt - global tests - getLicense", () => { it("should not fail after one failure when getLicenseConfig.retry is set to `1`", async () => { await checkGetLicense({ isGetLicensePromiseBased: true, - configuredRetries: 2, + configuredRetries: 1, configuredTimeout: undefined, getTimeout: () => undefined, nbRetries: 1, ignoreLicenseRequests: false }); }); - it("should not fail after 5 failures when getLicenseConfig.retry is set to `6`", async () => { + it("should not fail after 6 failures when getLicenseConfig.retry is set to `6`", async () => { await checkGetLicense({ isGetLicensePromiseBased: true, configuredRetries: 6, configuredTimeout: undefined, getTimeout: () => undefined, - nbRetries: 5, + nbRetries: 6, ignoreLicenseRequests: false }); }, 15000); - it("should fail after 6 failures when getLicenseConfig.retry is set to `6`", async () => { + it("should fail after 7 failures when getLicenseConfig.retry is set to `6`", async () => { await checkGetLicense({ isGetLicensePromiseBased: true, configuredRetries: 6, configuredTimeout: undefined, getTimeout: () => undefined, - nbRetries: 6, + nbRetries: 7, ignoreLicenseRequests: false }); }, 25000); @@ -269,8 +269,8 @@ function checkGetLicense( configuredTimeout : number | undefined; /** * Nb of times getLicense should fail in a row. - * If put at a higher value or equal to `configuredRetries`, no license - * will be obtained. + * If put at a higher value than `configuredRetries`, no license will be + * obtained. */ nbRetries : number; /** @@ -317,9 +317,9 @@ function checkGetLicense( // == vars == /** Default keySystems configuration used in our tests. */ - const maxRetries = configuredRetries === undefined ? 3 : + const maxRetries = configuredRetries === undefined ? 2 : configuredRetries; - const shouldFail = nbRetries >= maxRetries; + const shouldFail = nbRetries > maxRetries; let warningsLeft = nbRetries; const ksConfig = [{ type: "com.widevine.alpha", getLicense: mockGetLicense, @@ -360,8 +360,10 @@ function checkGetLicense( if (shouldFail) { try { checkKeyLoadError(error); - expect(mockGetLicense).toHaveBeenCalledTimes(maxRetries); - expect(mockGetLicense).toHaveBeenNthCalledWith(maxRetries, challenge, "license-request"); + expect(mockGetLicense).toHaveBeenCalledTimes(maxRetries + 1); + for (let i = 1; i <= maxRetries + 1; i++) { + expect(mockGetLicense).toHaveBeenNthCalledWith(i, challenge, "license-request"); + } expect(mockUpdate).toHaveBeenCalledTimes(0); res(); } catch (e) { @@ -389,30 +391,32 @@ function checkGetLicense( contentDecryptor.onInitializationData(initDataEvent); - const timeout = nbRetries === 0 ? 100 : - nbRetries === 1 ? 300 : - nbRetries === 2 ? 800 : - nbRetries === 3 ? 1200 : - nbRetries === 4 ? 3000 : - 10000; - setTimeout(() => { - try { - if (ignoreLicenseRequests) { - expect(mockUpdate).toHaveBeenCalledTimes(0); - } else { - const license = concat(challenge, challenge); - expect(mockUpdate).toHaveBeenCalledTimes(1); - expect(mockUpdate).toHaveBeenCalledWith(license); - } - expect(mockGetLicense).toHaveBeenCalledTimes(nbRetries + 1); - for (let i = 1; i <= nbRetries + 1; i++) { - expect(mockGetLicense).toHaveBeenNthCalledWith(i, challenge, "license-request"); + if (!shouldFail) { + const timeout = nbRetries === 0 ? 100 : + nbRetries === 1 ? 300 : + nbRetries === 2 ? 800 : + nbRetries === 3 ? 1200 : + nbRetries === 4 ? 3000 : + 10000; + setTimeout(() => { + try { + if (ignoreLicenseRequests) { + expect(mockUpdate).toHaveBeenCalledTimes(0); + } else { + const license = concat(challenge, challenge); + expect(mockUpdate).toHaveBeenCalledTimes(1); + expect(mockUpdate).toHaveBeenCalledWith(license); + } + expect(mockGetLicense).toHaveBeenCalledTimes(nbRetries + 1); + for (let i = 1; i <= nbRetries + 1; i++) { + expect(mockGetLicense).toHaveBeenNthCalledWith(i, challenge, "license-request"); + } + contentDecryptor.dispose(); + res(); + } catch (e) { + rej(e); } - contentDecryptor.dispose(); - res(); - } catch (e) { - rej(e); - } - }, timeout); + }, timeout); + } }); } diff --git a/src/core/decrypt/__tests__/__global__/init_data.test.ts b/src/core/decrypt/__tests__/__global__/init_data.test.ts index fd874c3510..34d7723acc 100644 --- a/src/core/decrypt/__tests__/__global__/init_data.test.ts +++ b/src/core/decrypt/__tests__/__global__/init_data.test.ts @@ -313,7 +313,6 @@ describe("core - decrypt - global tests - init data", () => { return new Promise((res, rej) => { // == mocks == const { mockGenerateKeyRequest, eventTriggers, mockGetInitData } = mockCompat(); - const { triggerEncrypted } = eventTriggers; const mediaKeySession = new MediaKeySessionImpl(); const mockCreateSession = jest.spyOn(MediaKeysImpl.prototype, "createSession") .mockReturnValue(mediaKeySession); @@ -336,7 +335,7 @@ describe("core - decrypt - global tests - init data", () => { type: "cenc", values: [ { systemId: "15", data: initData } ], }; - triggerEncrypted.next(initDataEvent); + eventTriggers.triggerEncrypted(videoElt, initDataEvent); setTimeout(() => { try { expect(mockGetInitData).toHaveBeenCalledTimes(1); @@ -363,7 +362,6 @@ describe("core - decrypt - global tests - init data", () => { return new Promise((res, rej) => { // == mocks == const { mockGenerateKeyRequest, eventTriggers, mockGetInitData } = mockCompat(); - const { triggerEncrypted } = eventTriggers; const mediaKeySession = new MediaKeySessionImpl(); const mockCreateSession = jest.spyOn(MediaKeysImpl.prototype, "createSession") .mockReturnValue(mediaKeySession); @@ -386,10 +384,10 @@ describe("core - decrypt - global tests - init data", () => { type: "cenc", values: [ { systemId: "15", data: initData } ], }; - triggerEncrypted.next(initDataEvent); - triggerEncrypted.next(initDataEvent); + eventTriggers.triggerEncrypted(videoElt, initDataEvent); + eventTriggers.triggerEncrypted(videoElt, initDataEvent); setTimeout(() => { - triggerEncrypted.next(initDataEvent); + eventTriggers.triggerEncrypted(videoElt, initDataEvent); }, 5); setTimeout(() => { try { @@ -424,7 +422,6 @@ describe("core - decrypt - global tests - init data", () => { return new Promise((res, rej) => { // == mocks == const { mockGenerateKeyRequest, eventTriggers, mockGetInitData } = mockCompat(); - const { triggerEncrypted } = eventTriggers; const mediaKeySessions = [ new MediaKeySessionImpl(), new MediaKeySessionImpl(), new MediaKeySessionImpl() ]; @@ -456,14 +453,14 @@ describe("core - decrypt - global tests - init data", () => { contentDecryptor.removeEventListener("stateChange"); contentDecryptor.attach(); }); - triggerEncrypted.next(initDataEvents[0]); - triggerEncrypted.next(initDataEvents[1]); - triggerEncrypted.next(initDataEvents[0]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[0]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[1]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[0]); setTimeout(() => { - triggerEncrypted.next(initDataEvents[2]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[2]); }); setTimeout(() => { - triggerEncrypted.next(initDataEvents[1]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[1]); }, 5); setTimeout(() => { try { @@ -519,7 +516,6 @@ describe("core - decrypt - global tests - init data", () => { return new Promise((res, rej) => { // == mocks == const { mockGenerateKeyRequest, eventTriggers, mockGetInitData } = mockCompat(); - const { triggerEncrypted } = eventTriggers; const mediaKeySessions = [ new MediaKeySessionImpl(), new MediaKeySessionImpl() ]; let createSessionCallIdx = 0; @@ -545,8 +541,8 @@ describe("core - decrypt - global tests - init data", () => { contentDecryptor.removeEventListener("stateChange"); contentDecryptor.attach(); }); - triggerEncrypted.next(initDataEvents[0]); - triggerEncrypted.next(initDataEvents[1]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[0]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[1]); setTimeout(() => { try { expect(mockGetInitData).toHaveBeenCalledTimes(2); @@ -584,7 +580,6 @@ describe("core - decrypt - global tests - init data", () => { return new Promise((res, rej) => { // == mocks == const { mockGenerateKeyRequest, eventTriggers, mockGetInitData } = mockCompat(); - const { triggerEncrypted } = eventTriggers; const mediaKeySessions = [ new MediaKeySessionImpl(), new MediaKeySessionImpl(), new MediaKeySessionImpl() ]; @@ -616,13 +611,13 @@ describe("core - decrypt - global tests - init data", () => { contentDecryptor.removeEventListener("stateChange"); contentDecryptor.attach(); }); - triggerEncrypted.next(initDataEvents[0]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[0]); contentDecryptor.onInitializationData(initDataEvents[1]); - triggerEncrypted.next(initDataEvents[1]); - triggerEncrypted.next(initDataEvents[0]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[1]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[0]); setTimeout(() => { contentDecryptor.onInitializationData(initDataEvents[0]); - triggerEncrypted.next(initDataEvents[2]); + eventTriggers.triggerEncrypted(videoElt, initDataEvents[2]); }); setTimeout(() => { try { diff --git a/src/core/decrypt/__tests__/__global__/utils.ts b/src/core/decrypt/__tests__/__global__/utils.ts index b3ffdc54a2..1e9afde54a 100644 --- a/src/core/decrypt/__tests__/__global__/utils.ts +++ b/src/core/decrypt/__tests__/__global__/utils.ts @@ -26,62 +26,82 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable no-restricted-properties */ -import { Subject } from "rxjs"; import { IEncryptedEventData } from "../../../../compat/eme"; import { base64ToBytes, bytesToBase64, } from "../../../../utils/base64"; -import EventEmitter, { fromEvent } from "../../../../utils/event_emitter"; +import EventEmitter from "../../../../utils/event_emitter"; import flatMap from "../../../../utils/flat_map"; import { strToUtf8, utf8ToStr, } from "../../../../utils/string_parsing"; +import { CancellationSignal } from "../../../../utils/task_canceller"; /** Default MediaKeySystemAccess configuration used by the RxPlayer. */ -export const defaultKSConfig = [{ - audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, - { contentType: "audio/webm;codecs=opus" } ], - distinctiveIdentifier: "optional" as const, - initDataTypes: ["cenc"] as const, - persistentState: "optional" as const, - sessionTypes: ["temporary"] as const, - videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, - { contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, - { contentType: "video/webm;codecs=\"vp8\"" } ], -}]; +export const defaultKSConfig = [ + { + audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, + { contentType: "audio/webm;codecs=opus" } ], + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, + { contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, + { contentType: "video/webm;codecs=\"vp8\"" } ], + }, + { + audioCapabilities: undefined, + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: undefined, + }, +]; /** * Default "com.microsoft.playready.recommendation" MediaKeySystemAccess * configuration used by the RxPlayer. */ -export const defaultPRRecommendationKSConfig = [{ - audioCapabilities: [ { robustness: "3000", - contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, - { robustness: "3000", - contentType: "audio/webm;codecs=opus" }, - { robustness: "2000", - contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, - { robustness: "2000", - contentType: "audio/webm;codecs=opus" } ], - distinctiveIdentifier: "optional" as const, - initDataTypes: ["cenc"] as const, - persistentState: "optional" as const, - sessionTypes: ["temporary"] as const, - videoCapabilities: [ { robustness: "3000", - contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, - { robustness: "3000", - contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, - { robustness: "3000", - contentType: "video/webm;codecs=\"vp8\"" }, - { robustness: "2000", - contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, - { robustness: "2000", - contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, - { robustness: "2000", - contentType: "video/webm;codecs=\"vp8\"" } ], -}]; +export const defaultPRRecommendationKSConfig = [ + { + audioCapabilities: [ { robustness: "3000", + contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, + { robustness: "3000", + contentType: "audio/webm;codecs=opus" }, + { robustness: "2000", + contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, + { robustness: "2000", + contentType: "audio/webm;codecs=opus" } ], + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: [ { robustness: "3000", + contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, + { robustness: "3000", + contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, + { robustness: "3000", + contentType: "video/webm;codecs=\"vp8\"" }, + { robustness: "2000", + contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, + { robustness: "2000", + contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, + { robustness: "2000", + contentType: "video/webm;codecs=\"vp8\"" } ], + }, + { + audioCapabilities: undefined, + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: undefined, + }, +]; /** Default Widevine MediaKeySystemAccess configuration used by the RxPlayer. */ export const defaultWidevineConfig = (() => { @@ -104,9 +124,10 @@ export const defaultWidevineConfig = (() => { { contentType: "audio/webm;codecs=opus", robustness } ]; }); - return defaultKSConfig.map(conf => { - return { ...conf, audioCapabilities, videoCapabilities }; - }); + return [ + { ...defaultKSConfig[0], audioCapabilities, videoCapabilities }, + defaultKSConfig[1], + ]; })(); /** @@ -274,25 +295,83 @@ export function requestMediaKeySystemAccessImpl( return Promise.resolve(new MediaKeySystemAccessImpl(keySystem, config)); } +class MockedDecryptorEventEmitter extends EventEmitter<{ + encrypted : { elt : HTMLMediaElement; value : unknown }; + keymessage : { session : MediaKeySessionImpl; value : unknown }; + keyerror : { session : MediaKeySessionImpl; value : unknown }; + keystatuseschange : { session : MediaKeySessionImpl; value : unknown }; +}> { + public triggerEncrypted(elt : HTMLMediaElement, value : unknown) { + this.trigger("encrypted", { elt, value }); + } + public triggerKeyError(session : MediaKeySessionImpl, value : unknown) { + this.trigger("keyerror", { session, value }); + } + public triggerKeyStatusesChange(session : MediaKeySessionImpl, value : unknown) { + this.trigger("keystatuseschange", { session, value }); + } + public triggerKeyMessage(session : MediaKeySessionImpl, value : unknown) { + this.trigger("keymessage", { session, value }); + } +} + /** * Mock functions coming from the compat directory. */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function mockCompat(exportedFunctions = {}) { - const triggerEncrypted = new Subject(); - const triggerKeyMessage = new Subject(); - const triggerKeyError = new Subject(); - const triggerKeyStatusesChange = new Subject(); + const ee = new MockedDecryptorEventEmitter(); const mockEvents : Record = { - onEncrypted$: jest.fn(() => triggerEncrypted), - onKeyMessage$: jest.fn((mediaKeySession : MediaKeySessionImpl) => { - return fromEvent(mediaKeySession, "message"); + onEncrypted: jest.fn(( + elt : HTMLMediaElement, + fn : (x : unknown) => void, + signal : CancellationSignal + ) => { + elt.addEventListener("encrypted", fn); + signal.register(() => { + elt.removeEventListener("encrypted", fn); + }); + ee.addEventListener("encrypted", (evt) => { + if (evt.elt === elt) { + fn(evt.value); + } + }, signal); + }), + onKeyMessage: jest.fn(( + elt : MediaKeySessionImpl, + fn : (x : unknown) => void, + signal : CancellationSignal + ) => { + elt.addEventListener("message", fn, signal); + ee.addEventListener("keymessage", (evt) => { + if (evt.session === elt) { + fn(evt.value); + } + }, signal); }), - onKeyError$: jest.fn((mediaKeySession : MediaKeySessionImpl) => { - return fromEvent(mediaKeySession, "error"); + onKeyError: jest.fn(( + elt : MediaKeySessionImpl, + fn : (x : unknown) => void, + signal : CancellationSignal + ) => { + elt.addEventListener("error", fn, signal); + ee.addEventListener("keyerror", (evt) => { + if (evt.session === elt) { + fn(evt.value); + } + }, signal); }), - onKeyStatusesChange$: jest.fn((mediaKeySession : MediaKeySessionImpl) => { - return fromEvent(mediaKeySession, "keyStatusesChange"); + onKeyStatusesChange: jest.fn(( + elt : MediaKeySessionImpl, + fn : (x : unknown) => void, + signal : CancellationSignal + ) => { + elt.addEventListener("keystatuseschange", fn, signal); + ee.addEventListener("keystatuseschange", (evt) => { + if (evt.session === elt) { + fn(evt.value); + } + }, signal); }), }; @@ -322,10 +401,20 @@ export function mockCompat(exportedFunctions = {}) { ...exportedFunctions })); return { mockEvents, - eventTriggers: { triggerEncrypted, - triggerKeyMessage, - triggerKeyError, - triggerKeyStatusesChange }, + eventTriggers: { + triggerEncrypted(elt : HTMLMediaElement, value : unknown) { + ee.triggerEncrypted(elt, value); + }, + triggerKeyMessage(session : MediaKeySessionImpl, value : unknown) { + ee.triggerKeyMessage(session, value); + }, + triggerKeyError(session : MediaKeySessionImpl, value : unknown) { + ee.triggerKeyError(session, value); + }, + triggerKeyStatusesChange(session : MediaKeySessionImpl, value : unknown) { + ee.triggerKeyStatusesChange(session, value); + }, + }, mockRequestMediaKeySystemAccess: mockRmksa, mockGetInitData, mockSetMediaKeys, diff --git a/src/core/decrypt/attach_media_keys.ts b/src/core/decrypt/attach_media_keys.ts index 9a5d897e6c..fb3388dc28 100644 --- a/src/core/decrypt/attach_media_keys.ts +++ b/src/core/decrypt/attach_media_keys.ts @@ -39,9 +39,10 @@ export function disableMediaKeys(mediaElement : HTMLMediaElement): void { * Attach MediaKeys and its associated state to an HTMLMediaElement. * * /!\ Mutates heavily MediaKeysInfosStore - * @param {Object} mediaKeysInfos * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {Object} mediaKeysInfos + * @param {Object} cancelSignal + * @returns {Promise} */ export default async function attachMediaKeys( mediaElement : HTMLMediaElement, @@ -61,7 +62,7 @@ export default async function attachMediaKeys( // If this task has been cancelled while we were closing previous sessions, // stop now (and thus avoid setting the new media keys); - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { throw cancelSignal.cancellationError; } diff --git a/src/core/decrypt/clear_on_stop.ts b/src/core/decrypt/clear_on_stop.ts index fba9700052..a845d84139 100644 --- a/src/core/decrypt/clear_on_stop.ts +++ b/src/core/decrypt/clear_on_stop.ts @@ -23,7 +23,7 @@ import MediaKeysInfosStore from "./utils/media_keys_infos_store"; * Clear DRM-related resources that should be cleared when the current content * stops its playback. * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @returns {Promise} */ export default function clearOnStop( mediaElement : HTMLMediaElement diff --git a/src/core/decrypt/content_decryptor.ts b/src/core/decrypt/content_decryptor.ts index 5571af3e5e..fefe8f2796 100644 --- a/src/core/decrypt/content_decryptor.ts +++ b/src/core/decrypt/content_decryptor.ts @@ -45,7 +45,7 @@ import createOrLoadSession from "./create_or_load_session"; import { IMediaKeysInfos } from "./get_media_keys"; import initMediaKeys from "./init_media_keys"; import SessionEventsListener, { - BlacklistedSessionError, + BlacklistedSessionError, IKeyUpdateValue, } from "./session_events_listener"; import setServerCertificate from "./set_server_certificate"; import { @@ -66,7 +66,7 @@ import { } from "./utils/key_id_comparison"; import KeySessionRecord from "./utils/key_session_record"; -const { onEncrypted$ } = events; +const { onEncrypted } = events; /** * Module communicating with the Content Decryption Module (or CDM) to be able @@ -172,16 +172,13 @@ export default class ContentDecryptor extends EventEmitter { + onEncrypted(mediaElement, evt => { log.debug("DRM: Encrypted event received from media element."); - const initData = getInitData(evt); + const initData = getInitData(evt as MediaEncryptedEvent); if (initData !== null) { this.onInitializationData(initData); } - }); - canceller.signal.register(() => { - listenerSub.unsubscribe(); - }); + }, canceller.signal); initMediaKeys(mediaElement, ksOptions, canceller.signal) .then((mediaKeysInfo) => { @@ -481,24 +478,19 @@ export default class ContentDecryptor extends EventEmitter { - if (evt.type === "warning") { - this.trigger("warning", evt.value); - return; - } - + SessionEventsListener( + mediaKeySession, + options, + mediaKeySystemAccess.keySystem, + { + onKeyUpdate: (value : IKeyUpdateValue) : void => { const linkedKeys = getKeyIdsLinkedToSession( initializationData, sessionInfo.record, options.singleLicensePer, sessionInfo.source === MediaKeySessionLoadingType.Created, - evt.value.whitelistedKeyIds, - evt.value.blacklistedKeyIds); + value.whitelistedKeyIds, + value.blacklistedKeyIds); sessionInfo.record.associateKeyIds(linkedKeys.whitelisted); sessionInfo.record.associateKeyIds(linkedKeys.blacklisted); @@ -529,8 +521,10 @@ export default class ContentDecryptor extends EventEmitter { + onWarning: (value : IPlayerError) : void => { + this.trigger("warning", value); + }, + onError: (err : unknown) : void => { if (err instanceof DecommissionedSessionError) { log.warn("DRM: A session's closing condition has been triggered"); this._lockInitDataQueue(); @@ -577,10 +571,8 @@ export default class ContentDecryptor extends EventEmitter { - sub.unsubscribe(); - }); + }, + this._canceller.signal); if (options.singleLicensePer === undefined || options.singleLicensePer === "init-data") @@ -735,7 +727,7 @@ export default class ContentDecryptor extends EventEmitter} */ async function recreatePersistentSession() : Promise { if (cancelSignal.cancellationError !== null) { diff --git a/src/core/decrypt/find_key_system.ts b/src/core/decrypt/find_key_system.ts index 2914c96eab..85bffb986f 100644 --- a/src/core/decrypt/find_key_system.ts +++ b/src/core/decrypt/find_key_system.ts @@ -155,7 +155,9 @@ function buildKeySystemConfigurations( if (keySystem.distinctiveIdentifierRequired === true) { distinctiveIdentifier = "required"; } - const { EME_DEFAULT_WIDEVINE_ROBUSTNESSES, + const { EME_DEFAULT_AUDIO_CODECS, + EME_DEFAULT_VIDEO_CODECS, + EME_DEFAULT_WIDEVINE_ROBUSTNESSES, EME_DEFAULT_PLAYREADY_ROBUSTNESSES } = config.getCurrent(); // Set robustness, in order of consideration: @@ -206,42 +208,41 @@ function buildKeySystemConfigurations( // https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent const videoCapabilities: IMediaCapability[] = - flatMap(videoRobustnesses, (robustness) => - ["video/mp4;codecs=\"avc1.4d401e\"", - "video/mp4;codecs=\"avc1.42e01e\"", - "video/webm;codecs=\"vp8\""].map(contentType => { - return robustness !== undefined ? { contentType, robustness } : - { contentType }; - })); + flatMap(videoRobustnesses, (robustness) => { + return EME_DEFAULT_VIDEO_CODECS.map(contentType => { + return robustness === undefined ? { contentType } : + { contentType, robustness }; + }); + }); const audioCapabilities: IMediaCapability[] = - flatMap(audioRobustnesses, (robustness) => - ["audio/mp4;codecs=\"mp4a.40.2\"", - "audio/webm;codecs=opus"].map(contentType => { - return robustness !== undefined ? { contentType, robustness } : - { contentType }; - })); - - // TODO Re-test with a set contentType but an undefined robustness on the - // STBs on which this problem was found. - // - // add another with no {audio,video}Capabilities for some legacy browsers. - // As of today's spec, this should return NotSupported but the first - // candidate configuration should be good, so we should have no downside - // doing that. - // initDataTypes: ["cenc"], - // videoCapabilities: undefined, - // audioCapabilities: undefined, - // distinctiveIdentifier, - // persistentState, - // sessionTypes, - - return [{ initDataTypes: ["cenc"], - videoCapabilities, - audioCapabilities, - distinctiveIdentifier, - persistentState, - sessionTypes }]; + flatMap(audioRobustnesses, (robustness) => { + return EME_DEFAULT_AUDIO_CODECS.map(contentType => { + return robustness === undefined ? { contentType } : + { contentType, robustness }; + }); + }); + + const wantedMediaKeySystemConfiguration : MediaKeySystemConfiguration = { + initDataTypes: ["cenc"], + videoCapabilities, + audioCapabilities, + distinctiveIdentifier, + persistentState, + sessionTypes, + }; + + return [ + wantedMediaKeySystemConfiguration, + + // Some legacy implementations have issues with `audioCapabilities` and + // `videoCapabilities`, so we're including a supplementary + // `MediaKeySystemConfiguration` without those properties. + { ...wantedMediaKeySystemConfiguration, + audioCapabilities: undefined , + videoCapabilities: undefined, + } as unknown as MediaKeySystemConfiguration, + ]; } /** diff --git a/src/core/decrypt/get_current_key_system.ts b/src/core/decrypt/get_key_system_configuration.ts similarity index 56% rename from src/core/decrypt/get_current_key_system.ts rename to src/core/decrypt/get_key_system_configuration.ts index 57fac4b397..be3444bb86 100644 --- a/src/core/decrypt/get_current_key_system.ts +++ b/src/core/decrypt/get_key_system_configuration.ts @@ -17,11 +17,32 @@ import MediaKeysInfosStore from "./utils/media_keys_infos_store"; /** - * Returns the name of the current key system used. + * Returns the name of the current key system used as well as its configuration, + * as reported by the `MediaKeySystemAccess` itself. + * @param {HTMLMediaElement} mediaElement + * @returns {Array|null} + */ +export default function getKeySystemConfiguration( + mediaElement : HTMLMediaElement +) : [string, MediaKeySystemConfiguration] | null { + const currentState = MediaKeysInfosStore.getState(mediaElement); + if (currentState === null) { + return null; + } + return [ + currentState.mediaKeySystemAccess.keySystem, + currentState.mediaKeySystemAccess.getConfiguration(), + ]; +} + +/** + * Returns the name of the current key system used, as originally indicated by + * the user. + * @deprecated * @param {HTMLMediaElement} mediaElement * @returns {string|null} */ -export default function getCurrentKeySystem( +export function getCurrentKeySystem( mediaElement : HTMLMediaElement ) : string | null { const currentState = MediaKeysInfosStore.getState(mediaElement); diff --git a/src/core/decrypt/get_media_keys.ts b/src/core/decrypt/get_media_keys.ts index d1af7cf17e..c0acab5963 100644 --- a/src/core/decrypt/get_media_keys.ts +++ b/src/core/decrypt/get_media_keys.ts @@ -130,7 +130,7 @@ export default async function getMediaKeysInfos( * Create `MediaKeys` from the `MediaKeySystemAccess` given. * Throws the right formatted error if it fails. * @param {MediaKeySystemAccess} mediaKeySystemAccess - * @returns {Observable.} + * @returns {Promise.} */ async function createMediaKeys( mediaKeySystemAccess : MediaKeySystemAccess | ICustomMediaKeySystemAccess diff --git a/src/core/decrypt/index.ts b/src/core/decrypt/index.ts index f029992ab1..286184a657 100644 --- a/src/core/decrypt/index.ts +++ b/src/core/decrypt/index.ts @@ -25,7 +25,9 @@ import ContentDecryptor, { IContentDecryptorEvent, } from "./content_decryptor"; import disposeDecryptionResources from "./dispose_decryption_resources"; -import getCurrentKeySystem from "./get_current_key_system"; +import getKeySystemConfiguration, { + getCurrentKeySystem, +} from "./get_key_system_configuration"; export * from "./types"; export default ContentDecryptor; @@ -33,6 +35,7 @@ export { clearOnStop, ContentDecryptorState, disposeDecryptionResources, + getKeySystemConfiguration, getCurrentKeySystem, IContentDecryptorEvent, }; diff --git a/src/core/decrypt/session_events_listener.ts b/src/core/decrypt/session_events_listener.ts index 9a8226a135..98e263ca65 100644 --- a/src/core/decrypt/session_events_listener.ts +++ b/src/core/decrypt/session_events_listener.ts @@ -14,25 +14,6 @@ * limitations under the License. */ -import { - catchError, - concat as observableConcat, - concatMap, - defer as observableDefer, - EMPTY, - identity, - ignoreElements, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - Subject, - tap, - takeUntil, - timeout, - TimeoutError, -} from "rxjs"; import { events, ICustomMediaKeySession, @@ -43,182 +24,257 @@ import { IKeySystemOption, IPlayerError, } from "../../public_types"; -import castToObservable from "../../utils/cast_to_observable"; import isNonEmptyString from "../../utils/is_non_empty_string"; import isNullOrUndefined from "../../utils/is_null_or_undefined"; -import retryObsWithBackoff, { +import retryPromiseWithBackoff, { IBackoffOptions, -} from "../../utils/rx-retry_with_backoff"; -import tryCatch from "../../utils/rx-try_catch"; -import { - IEMEWarningEvent, - ILicense, -} from "./types"; -import checkKeyStatuses, { - IKeyStatusesCheckingOptions, -} from "./utils/check_key_statuses"; +} from "../../utils/retry_promise_with_backoff"; +import TaskCanceller, { + CancellationSignal, +} from "../../utils/task_canceller"; +import checkKeyStatuses from "./utils/check_key_statuses"; -const { onKeyError$, - onKeyMessage$, - onKeyStatusesChange$ } = events; +const { onKeyError, + onKeyMessage, + onKeyStatusesChange } = events; /** - * Error thrown when the MediaKeySession is blacklisted. - * Such MediaKeySession should not be re-used but other MediaKeySession for the - * same content can still be used. - * @class BlacklistedSessionError - * @extends Error - */ -export class BlacklistedSessionError extends Error { - public sessionError : IPlayerError; - constructor(sessionError : IPlayerError) { - super(); - // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class - Object.setPrototypeOf(this, BlacklistedSessionError.prototype); - this.sessionError = sessionError; - } -} - -/** - * listen to various events from a MediaKeySession and react accordingly + * Listen to various events from a MediaKeySession and react accordingly * depending on the configuration given. * @param {MediaKeySession} session - The MediaKeySession concerned. * @param {Object} keySystemOptions - The key system options. * @param {String} keySystem - The configuration keySystem used for deciphering - * @returns {Observable} + * @param {Object} callbacks + * @param {Object} cancelSignal */ export default function SessionEventsListener( - session: MediaKeySession | ICustomMediaKeySession, - keySystemOptions: IKeySystemOption, - keySystem: string -) : Observable { + session : MediaKeySession | ICustomMediaKeySession, + keySystemOptions : IKeySystemOption, + keySystem : string, + callbacks : ISessionEventListenerCallbacks, + cancelSignal : CancellationSignal +) : void { log.info("DRM: Binding session events", session.sessionId); - const sessionWarningSubject$ = new Subject(); const { getLicenseConfig = {} } = keySystemOptions; - const keyErrors = onKeyError$(session).pipe(map((error) : never => { - throw new EncryptedMediaError("KEY_ERROR", error.type); - })); + /** Allows to manually cancel everything the `SessionEventsListener` is doing. */ + const manualCanceller = new TaskCanceller(); + manualCanceller.linkToSignal(cancelSignal); - const keyStatusesChange$ = onKeyStatusesChange$(session) - .pipe(mergeMap((keyStatusesEvent: Event) => - handleKeyStatusesChangeEvent(session, - keySystemOptions, - keySystem, - keyStatusesEvent))); + if (!isNullOrUndefined(session.closed)) { + session.closed + .then(() => manualCanceller.cancel()) + .catch((err) => { // Should never happen + if (cancelSignal.isCancelled()) { + return; + } + manualCanceller.cancel(); + callbacks.onError(err); + }); + } - const keyMessages$ : Observable = - onKeyMessage$(session).pipe(mergeMap((messageEvent: MediaKeyMessageEvent) => { - const message = new Uint8Array(messageEvent.message); - const messageType = isNonEmptyString(messageEvent.messageType) ? - messageEvent.messageType : - "license-request"; + onKeyError(session, (evt) => { + manualCanceller.cancel(); + callbacks.onError(new EncryptedMediaError("KEY_ERROR", (evt as Event).type)); + }, manualCanceller.signal); - log.info(`DRM: Received message event, type ${messageType}`, - session.sessionId); - const getLicense$ = observableDefer(() => { - const getLicense = keySystemOptions.getLicense(message, messageType); - const getLicenseTimeout = isNullOrUndefined(getLicenseConfig.timeout) ? - 10 * 1000 : - getLicenseConfig.timeout; - return castToObservable(getLicense) - .pipe(getLicenseTimeout >= 0 ? timeout(getLicenseTimeout) : - identity /* noop */); - }); + onKeyStatusesChange(session, (keyStatusesEvent) => { + handleKeyStatusesChangeEvent(keyStatusesEvent as Event).catch(error => { + if (cancelSignal.isCancelled() || + (manualCanceller.isUsed() && error instanceof CancellationSignal)) + { + return; + } + manualCanceller.cancel(); + callbacks.onError(error); + }); + }, manualCanceller.signal); - const backoffOptions = getLicenseBackoffOptions(sessionWarningSubject$, - getLicenseConfig.retry); - return retryObsWithBackoff(getLicense$, backoffOptions).pipe( - map(licenseObject => ({ - type: "key-message-handled" as const, - value : { session, license: licenseObject }, - })), + onKeyMessage(session, (evt) => { + const messageEvent = evt as MediaKeyMessageEvent; + const message = new Uint8Array(messageEvent.message); + const messageType = isNonEmptyString(messageEvent.messageType) ? + messageEvent.messageType : + "license-request"; - catchError((err : unknown) => { - const formattedError = formatGetLicenseError(err); + log.info(`DRM: Received message event, type ${messageType}`, + session.sessionId); - if (!isNullOrUndefined(err)) { - const { fallbackOnLastTry } = (err as { fallbackOnLastTry? : boolean }); - if (fallbackOnLastTry === true) { - log.warn("DRM: Last `getLicense` attempt failed. " + - "Blacklisting the current session."); - throw new BlacklistedSessionError(formattedError); - } - } - throw formattedError; - })); - })); + const backoffOptions = getLicenseBackoffOptions(getLicenseConfig.retry); + retryPromiseWithBackoff(() => runGetLicense(message, messageType), + backoffOptions, + manualCanceller.signal) + .then((licenseObject) => { + if (manualCanceller.isUsed()) { + return Promise.resolve(); + } + if (isNullOrUndefined(licenseObject)) { + log.info("DRM: No license given, skipping session.update"); + } else { + return updateSessionWithMessage(session, licenseObject); + } + }) + .catch((err : unknown) => { + if (manualCanceller.isUsed()) { + return; + } + manualCanceller.cancel(); + const formattedError = formatGetLicenseError(err); - const sessionUpdates = observableMerge(keyMessages$, keyStatusesChange$) - .pipe(concatMap(( - evt : IEMEWarningEvent | - IKeyMessageHandledEvent | - IKeysUpdateEvent | - IKeyStatusChangeHandledEvent - ) : Observable< IEMEWarningEvent | - IKeysUpdateEvent > => { - switch (evt.type) { - case "key-message-handled": - case "key-status-change-handled": - if (isNullOrUndefined(evt.value.license)) { - log.info("DRM: No message given, skipping session.update"); - return EMPTY; + if (!isNullOrUndefined(err)) { + const { fallbackOnLastTry } = (err as { fallbackOnLastTry? : boolean }); + if (fallbackOnLastTry === true) { + log.warn("DRM: Last `getLicense` attempt failed. " + + "Blacklisting the current session."); + callbacks.onError(new BlacklistedSessionError(formattedError)); + return; } + } + callbacks.onError(formattedError); + }); + }, manualCanceller.signal); - return updateSessionWithMessage(session, evt.value.license); - default: - return observableOf(evt); - } - })); + checkAndHandleCurrentKeyStatuses(); + return; + /** + * @param {Event} keyStatusesEvent + * @returns {Promise} + */ + async function handleKeyStatusesChangeEvent( + keyStatusesEvent : Event + ) : Promise { + log.info("DRM: keystatuseschange event received", session.sessionId); - const sessionEvents = observableMerge( - getKeyStatusesEvents(session, keySystemOptions, keySystem), - sessionUpdates, - keyErrors, - sessionWarningSubject$); + await Promise.all([ + runOnKeyStatusesChangeCallback(), + Promise.resolve(checkAndHandleCurrentKeyStatuses()), + ]); - return !isNullOrUndefined(session.closed) ? - sessionEvents - // TODO There is a subtle TypeScript issue there that made casting - // to a type-compatible type mandatory. If a more elegant solution - // can be found, it should be preffered. - .pipe(takeUntil(castToObservable(session.closed as Promise))) : - sessionEvents; -} + async function runOnKeyStatusesChangeCallback() { + if (manualCanceller.isUsed()) { + return; + } + if (typeof keySystemOptions.onKeyStatusesChange === "function") { + let ret; + try { + ret = await keySystemOptions.onKeyStatusesChange(keyStatusesEvent, session); + if (manualCanceller.isUsed()) { + return; + } + } catch (error) { + if (cancelSignal.isCancelled()) { + return; + } + const err = new EncryptedMediaError("KEY_STATUS_CHANGE_ERROR", + "Unknown `onKeyStatusesChange` error"); + if (!isNullOrUndefined(error) && + isNonEmptyString((error as { message? : unknown }).message)) + { + err.message = (error as { message : string }).message; + } + throw err; + } + if (isNullOrUndefined(ret)) { + log.info("DRM: No license given, skipping session.update"); + } else { + await updateSessionWithMessage(session, ret); + } + } + } + } -/** - * Check current MediaKeyStatus for each key in the given MediaKeySession and - * return an Observable which either: - * - throw if at least one status is a non-recoverable error - * - emit warning events for recoverable errors - * - emit blacklist-keys events for key IDs that are not decipherable - * @param {MediaKeySession} session - The MediaKeySession concerned. - * @param {Object} options - Options related to key statuses checks. - * @param {String} keySystem - The name of the key system used for deciphering - * @returns {Observable} - */ -function getKeyStatusesEvents( - session : MediaKeySession | ICustomMediaKeySession, - options : IKeyStatusesCheckingOptions, - keySystem : string -) : Observable { - return observableDefer(() => { - if (session.keyStatuses.size === 0) { - return EMPTY; + /** + * Check current MediaKeyStatus for each key in the given MediaKeySession and: + * - throw if at least one status is a non-recoverable error + * - call warning callback for recoverable errors + * - call onKeyUpdate callback when the MediaKeyStatus of any key is updated + */ + function checkAndHandleCurrentKeyStatuses() : void { + if (manualCanceller.isUsed() || session.keyStatuses.size === 0) { + return ; } - const { warning, - blacklistedKeyIds, - whitelistedKeyIds } = checkKeyStatuses(session, options, keySystem); - const keysUpdate$ = observableOf({ type : "keys-update" as const, - value : { whitelistedKeyIds, - blacklistedKeyIds } }); + const { warning, blacklistedKeyIds, whitelistedKeyIds } = + checkKeyStatuses(session, keySystemOptions, keySystem); if (warning !== undefined) { - return observableConcat(observableOf({ type: "warning" as const, value: warning }), - keysUpdate$); + callbacks.onWarning(warning); + if (manualCanceller.isUsed()) { + return; + } } - return keysUpdate$; - }); + callbacks.onKeyUpdate({ whitelistedKeyIds, blacklistedKeyIds }); + } + + function runGetLicense( + message : Uint8Array, + messageType : MediaKeyMessageType + ) : Promise { + let timeoutId : number | undefined; + return new Promise((res, rej) => { + try { + log.debug("DRM: Calling `getLicense`", messageType); + const getLicense = keySystemOptions.getLicense(message, messageType); + const getLicenseTimeout = isNullOrUndefined(getLicenseConfig.timeout) ? + 10 * 1000 : + getLicenseConfig.timeout; + + if (getLicenseTimeout >= 0) { + timeoutId = setTimeout(() => { + rej(new GetLicenseTimeoutError( + `"getLicense" timeout exceeded (${getLicenseTimeout} ms)` + )); + }, getLicenseTimeout) as unknown as number; + } + Promise.resolve(getLicense) + .then(clearTimeoutAndResolve, clearTimeoutAndReject); + } catch (err) { + clearTimeoutAndReject(err); + } + function clearTimeoutAndResolve(data : BufferSource | null) { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + res(data); + } + function clearTimeoutAndReject(err : unknown) { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + rej(err); + } + }); + } + + /** + * Construct backoff options for the getLicense call. + * @param {number|undefined} numberOfRetry - Maximum of amount retried. + * Equal to `2` if not defined. + * @returns {Object} + */ + function getLicenseBackoffOptions( + numberOfRetry : number | undefined + ) : IBackoffOptions { + return { + totalRetry: numberOfRetry ?? 2, + baseDelay: 200, + maxDelay: 3000, + shouldRetry: (error : unknown) => + error instanceof GetLicenseTimeoutError || + isNullOrUndefined(error) || + (error as { noRetry? : boolean }).noRetry !== true, + onRetry: (error : unknown) => + callbacks.onWarning(formatGetLicenseError(error)), + }; + } +} + +export interface ISessionEventListenerCallbacks { + /** + * Some key ids related to the current MediaKeySession have updated their + * statuses. + */ + onKeyUpdate : (val : IKeyUpdateValue) => void; + onWarning : (val : IPlayerError) => void; + onError : (val : unknown | BlacklistedSessionError) => void; } /** @@ -228,7 +284,7 @@ function getKeyStatusesEvents( * @returns {Error} */ function formatGetLicenseError(error: unknown) : IPlayerError { - if (error instanceof TimeoutError) { + if (error instanceof GetLicenseTimeoutError) { return new EncryptedMediaError("KEY_LOAD_TIMEOUT", "The license server took too much time to " + "respond."); @@ -246,111 +302,23 @@ function formatGetLicenseError(error: unknown) : IPlayerError { /** * Call MediaKeySession.update with the given `message`, if defined. - * Returns the right event depending on the action taken. * @param {MediaKeySession} session * @param {ArrayBuffer|TypedArray|null} message - * @returns {Observable} + * @returns {Promise} */ -function updateSessionWithMessage( +async function updateSessionWithMessage( session : MediaKeySession | ICustomMediaKeySession, message : BufferSource -) : Observable { +) : Promise { log.info("DRM: Updating MediaKeySession with message"); - return castToObservable(session.update(message)).pipe( - catchError((error: unknown) => { - const reason = error instanceof Error ? error.toString() : - "`session.update` failed"; - throw new EncryptedMediaError("KEY_UPDATE_ERROR", reason); - }), - tap(() => { log.info("DRM: MediaKeySession update succeeded."); }), - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - ignoreElements()); -} - -/** - * @param {MediaKeySession} session - * @param {Object} keySystemOptions - * @param {string} keySystem - * @param {Event} keyStatusesEvent - * @returns {Observable} - */ -function handleKeyStatusesChangeEvent( - session : MediaKeySession | ICustomMediaKeySession, - keySystemOptions : IKeySystemOption, - keySystem : string, - keyStatusesEvent : Event -) : Observable { - log.info("DRM: keystatuseschange event received", session.sessionId); - const callback$ = observableDefer(() => { - return tryCatch(() => { - if (typeof keySystemOptions.onKeyStatusesChange !== "function") { - return EMPTY; - } - return castToObservable(keySystemOptions.onKeyStatusesChange(keyStatusesEvent, - session)); - }, undefined); - }).pipe( - map(licenseObject => ({ type: "key-status-change-handled" as const, - value : { session, license: licenseObject } })), - catchError((error: unknown) => { - const err = new EncryptedMediaError("KEY_STATUS_CHANGE_ERROR", - "Unknown `onKeyStatusesChange` error"); - if (!isNullOrUndefined(error) && - isNonEmptyString((error as { message? : unknown }).message)) - { - err.message = (error as { message : string }).message; - } - throw err; - }) - ); - return observableMerge(getKeyStatusesEvents(session, keySystemOptions, keySystem), - callback$); -} - -/** - * Construct backoff options for the getLicense call. - * @param {Subject} sessionWarningSubject$ - Subject through which retry - * warnings will be sent. - * @param {number|undefined} numberOfRetry - Maximum of amount retried. - * Equal to `2` if not defined. - * @returns {Object} - */ -function getLicenseBackoffOptions( - sessionWarningSubject$ : Subject, - numberOfRetry : number | undefined -) : IBackoffOptions { - return { - totalRetry: numberOfRetry ?? 2, - baseDelay: 200, - maxDelay: 3000, - shouldRetry: (error : unknown) => error instanceof TimeoutError || - isNullOrUndefined(error) || - (error as { noRetry? : boolean }).noRetry !== true, - onRetry: (error : unknown) => - sessionWarningSubject$.next({ type: "warning", - value: formatGetLicenseError(error) }), - }; -} - -/** - * Some key ids related to the current MediaKeySession have updated their - * statuses. - * - * Note that each `IKeysUpdateEvent` is independent of any other. - * - * A new `IKeysUpdateEvent` does not completely replace a previously emitted - * one, as it can for example be linked to a whole other decryption session. - * - * However, if a key id is encountered in both an older and a newer - * `IKeysUpdateEvent`, only the newer, updated, status should be considered. - */ -export interface IKeysUpdateEvent { - type: "keys-update"; - value: IKeyUpdateValue; + try { + await session.update(message); + } catch (error) { + const reason = error instanceof Error ? error.toString() : + "`session.update` failed"; + throw new EncryptedMediaError("KEY_UPDATE_ERROR", reason); + } + log.info("DRM: MediaKeySession update succeeded."); } /** Information on key ids linked to a MediaKeySession. */ @@ -382,18 +350,33 @@ export interface IKeyUpdateValue { whitelistedKeyIds : Uint8Array[]; } -/** Emitted after the `onKeyStatusesChange` callback has been called. */ -interface IKeyStatusChangeHandledEvent { - type: "key-status-change-handled"; - value: { session: MediaKeySession | - ICustomMediaKeySession; - license: ILicense|null; }; +/** + * Error thrown when the MediaKeySession is blacklisted. + * Such MediaKeySession should not be re-used but other MediaKeySession for the + * same content can still be used. + * @class BlacklistedSessionError + * @extends Error + */ +export class BlacklistedSessionError extends Error { + public sessionError : IPlayerError; + constructor(sessionError : IPlayerError) { + super(); + // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class + Object.setPrototypeOf(this, BlacklistedSessionError.prototype); + this.sessionError = sessionError; + } } -/** Emitted after the `getLicense` callback has been called */ -interface IKeyMessageHandledEvent { - type: "key-message-handled"; - value: { session: MediaKeySession | - ICustomMediaKeySession; - license: ILicense|null; }; +/** + * Error thrown when a `getLicense` call timeouts. + * @class GetLicenseTimeoutError + * @extends Error + */ +export class GetLicenseTimeoutError extends Error { + constructor(message : string) { + super(); + // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class + Object.setPrototypeOf(this, BlacklistedSessionError.prototype); + this.message = message; + } } diff --git a/src/core/decrypt/set_server_certificate.ts b/src/core/decrypt/set_server_certificate.ts index 5940d3d298..ec8068c431 100644 --- a/src/core/decrypt/set_server_certificate.ts +++ b/src/core/decrypt/set_server_certificate.ts @@ -61,7 +61,7 @@ async function setServerCertificate( * and complete. * @param {MediaKeys} mediaKeys * @param {ArrayBuffer} serverCertificate - * @returns {Observable} + * @returns {Promise.} */ export default async function trySettingServerCertificate( mediaKeys : ICustomMediaKeys|MediaKeys, diff --git a/src/core/decrypt/types.ts b/src/core/decrypt/types.ts index ac31433be0..e07f38b335 100644 --- a/src/core/decrypt/types.ts +++ b/src/core/decrypt/types.ts @@ -19,7 +19,6 @@ import Manifest, { Period, Representation, } from "../../manifest"; -import { IPlayerError } from "../../public_types"; import InitDataValuesContainer from "./utils/init_data_values_container"; import LoadedSessionsStore from "./utils/loaded_sessions_store"; import PersistentSessionsStore from "./utils/persistent_sessions_store"; @@ -74,10 +73,6 @@ export interface IInitDataValue { data: Uint8Array; } -/** Event emitted when a minor - recoverable - error happened. */ -export interface IEMEWarningEvent { type : "warning"; - value : IPlayerError; } - export type ILicense = BufferSource | ArrayBuffer; diff --git a/src/core/decrypt/utils/check_key_statuses.ts b/src/core/decrypt/utils/check_key_statuses.ts index 0b4580d012..7906bab272 100644 --- a/src/core/decrypt/utils/check_key_statuses.ts +++ b/src/core/decrypt/utils/check_key_statuses.ts @@ -18,6 +18,7 @@ import { ICustomMediaKeySession } from "../../../compat"; /* eslint-disable-next-line max-len */ import getUUIDKidFromKeyStatusKID from "../../../compat/eme/get_uuid_kid_from_keystatus_kid"; import { EncryptedMediaError } from "../../../errors"; +import log from "../../../log"; import { IEncryptedMediaErrorKeyStatusObject, IKeySystemOption, @@ -109,6 +110,11 @@ export default function checkKeyStatuses( new Uint8Array(keyStatusKeyId)); const keyStatusObj = { keyId: keyId.buffer, keyStatus }; + + if (log.hasLevel("DEBUG")) { + log.debug(`DRM: key status update (${bytesToHex(keyId)}): ${keyStatus}`); + } + switch (keyStatus) { case KEY_STATUSES.EXPIRED: { const error = new EncryptedMediaError( diff --git a/src/core/decrypt/utils/clean_old_loaded_sessions.ts b/src/core/decrypt/utils/clean_old_loaded_sessions.ts index b4d9c07625..3e489b82b8 100644 --- a/src/core/decrypt/utils/clean_old_loaded_sessions.ts +++ b/src/core/decrypt/utils/clean_old_loaded_sessions.ts @@ -23,7 +23,7 @@ import LoadedSessionsStore from "./loaded_sessions_store"; * Emit event when a MediaKeySession begin to be closed and another when the * MediaKeySession is closed. * @param {Object} loadedSessionsStore - * @returns {Observable} + * @returns {Promise} */ export default async function cleanOldLoadedSessions( loadedSessionsStore : LoadedSessionsStore, diff --git a/src/core/decrypt/utils/loaded_sessions_store.ts b/src/core/decrypt/utils/loaded_sessions_store.ts index 4b77e21931..1f1aa5643f 100644 --- a/src/core/decrypt/utils/loaded_sessions_store.ts +++ b/src/core/decrypt/utils/loaded_sessions_store.ts @@ -479,7 +479,7 @@ export interface IStoredSessionEntry { * Close a MediaKeySession and just log an error if it fails (while resolving). * Emits then complete when done. * @param {MediaKeySession} mediaKeySession - * @returns {Observable} + * @returns {Promise} */ async function safelyCloseMediaKeySession( mediaKeySession : MediaKeySession | ICustomMediaKeySession diff --git a/src/core/fetchers/README.md b/src/core/fetchers/README.md index 5b2b77b153..17e57d6caa 100644 --- a/src/core/fetchers/README.md +++ b/src/core/fetchers/README.md @@ -21,6 +21,9 @@ protocol-agnostic. This is the part of the code that interacts with `transports` to perform the request and parsing of the Manifest file. +It also regularly refreshes the Manifest, based on its attributes and other +criteria, like performances when doing that. + ## The SegmentFetcherCreator ################################################### diff --git a/src/core/fetchers/index.ts b/src/core/fetchers/index.ts index ab5097f9b4..d7f95d3c23 100644 --- a/src/core/fetchers/index.ts +++ b/src/core/fetchers/index.ts @@ -15,9 +15,9 @@ */ import ManifestFetcher, { - IManifestFetcherParsedResult, - IManifestFetcherParserOptions, - IManifestFetcherWarningEvent, + IManifestFetcherSettings, + IManifestFetcherEvent, + IManifestRefreshSettings, } from "./manifest"; import SegmentFetcherCreator, { IPrioritizedSegmentFetcher, @@ -28,9 +28,9 @@ export { ManifestFetcher, SegmentFetcherCreator, - IManifestFetcherParserOptions, - IManifestFetcherParsedResult, - IManifestFetcherWarningEvent, + IManifestFetcherSettings, + IManifestFetcherEvent, + IManifestRefreshSettings, IPrioritizedSegmentFetcher, diff --git a/src/core/fetchers/manifest/index.ts b/src/core/fetchers/manifest/index.ts index e7bd6693ae..5394a7db8e 100644 --- a/src/core/fetchers/manifest/index.ts +++ b/src/core/fetchers/manifest/index.ts @@ -15,14 +15,14 @@ */ import ManifestFetcher, { - IManifestFetcherParsedResult, - IManifestFetcherParserOptions, - IManifestFetcherWarningEvent, + IManifestFetcherSettings, + IManifestFetcherEvent, + IManifestRefreshSettings, } from "./manifest_fetcher"; -export default ManifestFetcher; export { - IManifestFetcherParsedResult, - IManifestFetcherParserOptions, - IManifestFetcherWarningEvent, + IManifestFetcherSettings, + IManifestFetcherEvent, + IManifestRefreshSettings, }; +export default ManifestFetcher; diff --git a/src/core/fetchers/manifest/manifest_fetcher.ts b/src/core/fetchers/manifest/manifest_fetcher.ts index f0221e78b2..98a19cbd51 100644 --- a/src/core/fetchers/manifest/manifest_fetcher.ts +++ b/src/core/fetchers/manifest/manifest_fetcher.ts @@ -14,21 +14,24 @@ * limitations under the License. */ -import { - Observable, -} from "rxjs"; import config from "../../../config"; import { formatError } from "../../../errors"; import log from "../../../log"; import Manifest from "../../../manifest"; -import { IPlayerError } from "../../../public_types"; +import { + IInitialManifest, + ILoadedManifestFormat, + IPlayerError, +} from "../../../public_types"; import { IRequestedData, ITransportManifestPipeline, ITransportPipelines, } from "../../../transports"; import assert from "../../../utils/assert"; +import EventEmitter from "../../../utils/event_emitter"; import isNullOrUndefined from "../../../utils/is_null_or_undefined"; +import noop from "../../../utils/noop"; import TaskCanceller from "../../../utils/task_canceller"; import errorSelector from "../utils/error_selector"; import { @@ -36,109 +39,55 @@ import { scheduleRequestPromise, } from "../utils/schedule_request"; - -/** What will be sent once parsed. */ -export interface IManifestFetcherParsedResult { - /** To differentiate it from a "warning" event. */ - type : "parsed"; - - /** The resulting Manifest */ - manifest : Manifest; +/** + * Class allowing to facilitate the task of loading and parsing a Manifest, as + * well as automatically refreshing it. + * @class ManifestFetcher + */ +export default class ManifestFetcher extends EventEmitter { /** - * The time (`performance.now()`) at which the request was started (at which - * the JavaScript call was done). + * Allows to manually trigger a Manifest refresh. + * Will only have an effect if the Manifest has been fetched at least once. + * @param {Object} settings - refresh configuration. */ - sendingTime? : number | undefined; - /** The time (`performance.now()`) at which the request was fully received. */ - receivedTime? : number | undefined; - /* The time taken to parse the Manifest through the corresponding parse function. */ - parsingTime? : number | undefined; -} - -/** Emitted when a fetching or parsing minor error happened. */ -export interface IManifestFetcherWarningEvent { - /** To differentiate it from other events. */ - type : "warning"; - - /** The error in question. */ - value : IPlayerError; -} - -/** Response emitted by a Manifest fetcher. */ -export interface IManifestFetcherResponse { - /** To differentiate it from a "warning" event. */ - type : "response"; - - /** Allows to parse a fetched Manifest into a `Manifest` structure. */ - parse(parserOptions : IManifestFetcherParserOptions) : - Observable; -} + public scheduleManualRefresh : (settings : IManifestRefreshSettings) => void; -export interface IManifestFetcherParserOptions { + /** `ManifestFetcher` configuration. */ + private _settings : IManifestFetcherSettings; + /** URLs through which the Manifest may be reached, by order of priority. */ + private _manifestUrls : string[] | undefined; /** - * If set, offset to add to `performance.now()` to obtain the current - * server's time. + * Manifest loading and parsing pipelines linked to the current transport + * protocol used. */ - externalClockOffset? : number | undefined; - /** The previous value of the Manifest (when updating). */ - previousManifest : Manifest | null; + private _pipelines : ITransportManifestPipeline; /** - * If set to `true`, the Manifest parser can perform advanced optimizations - * to speed-up the parsing process. Those optimizations might lead to a - * de-synchronization with what is actually on the server, hence the "unsafe" - * part. - * To use with moderation and only when needed. + * `TaskCanceller` called when this `ManifestFetcher` is disposed, to clean + * resources. */ - unsafeMode : boolean; -} - -/** Options used by `createManifestFetcher`. */ -export interface IManifestFetcherSettings { + private _canceller : TaskCanceller; /** - * Whether the content is played in a low-latency mode. - * This has an impact on default backoff delays. + * Set to `true` once the Manifest has been fetched at least once through this + * `ManifestFetcher`. */ - lowLatencyMode : boolean; - /** Maximum number of time a request on error will be retried. */ - maxRetryRegular : number | undefined; - /** Maximum number of time a request be retried when the user is offline. */ - maxRetryOffline : number | undefined; + private _isStarted : boolean; /** - * Timeout after which request are aborted and, depending on other options, - * retried. - * To set to `-1` for no timeout. - * `undefined` will lead to a default, large, timeout being used. + * Set to `true` when a Manifest refresh is currently pending. + * Allows to avoid doing multiple concurrent Manifest refresh, as this is + * most of the time unnecessary. */ - requestTimeout : number | undefined; -} - -/** - * Class allowing to facilitate the task of loading and parsing a Manifest. - * @class ManifestFetcher - * @example - * ```js - * const manifestFetcher = new ManifestFetcher(manifestUrl, pipelines, options); - * manifestFetcher.fetch().pipe( - * // Filter only responses (might also receive warning events) - * filter((evt) => evt.type === "response"); - * // Parse the Manifest - * mergeMap(res => res.parse({ externalClockOffset })) - * // (again) - * filter((evt) => evt.type === "parsed"); - * ).subscribe(({ value }) => { - * console.log("Manifest:", value.manifest); - * }); - * ``` - */ -export default class ManifestFetcher { - private _settings : IManifestFetcherSettings; - private _manifestUrl : string | undefined; - private _pipelines : ITransportManifestPipeline; + private _isRefreshPending; + /** Number of consecutive times the Manifest parsing has been done in `unsafeMode`. */ + private _consecutiveUnsafeMode; + /** + * If set to a string or `undefined`, the given URL should be prioritized on + * the next Manifest fetching operation, it can then be reset to `null`. + */ + private _prioritizedContentUrl : string | undefined | null; /** * Construct a new ManifestFetcher. - * @param {string | undefined} url - Default Manifest url, will be used when + * @param {Array. | undefined} urls - Manifest URLs, will be used when * no URL is provided to the `fetch` function. * `undefined` if unknown or if a Manifest should be retrieved through other * means than an HTTP request. @@ -147,13 +96,88 @@ export default class ManifestFetcher { * @param {Object} settings - Configure the `ManifestFetcher`. */ constructor( - url : string | undefined, + urls : string[] | undefined, pipelines : ITransportPipelines, settings : IManifestFetcherSettings ) { - this._manifestUrl = url; + super(); + this.scheduleManualRefresh = noop; + this._manifestUrls = urls; this._pipelines = pipelines.manifest; this._settings = settings; + this._canceller = new TaskCanceller(); + this._isStarted = false; + this._isRefreshPending = false; + this._consecutiveUnsafeMode = 0; + this._prioritizedContentUrl = null; + } + + /** + * Free resources and stop refresh mechanism from happening. + * + * Once `dispose` has been called. This `ManifestFetcher` cannot be relied on + * anymore. + */ + public dispose() { + this._canceller.cancel(); + this.removeEventListener(); + } + + /** + * Start requesting the Manifest as well as the Manifest refreshing logic, if + * needed. + * + * Once `start` has been called, this mechanism can only be stopped by calling + * `dispose`. + */ + public start() : void { + if (this._isStarted) { + return; + } + this._isStarted = true; + + let manifestProm : Promise; + + const initialManifest = this._settings.initialManifest; + if (initialManifest instanceof Manifest) { + manifestProm = Promise.resolve({ manifest: initialManifest }); + } else if (initialManifest !== undefined) { + manifestProm = this.parse(initialManifest, + { previousManifest: null, unsafeMode: false }, + undefined); + } else { + manifestProm = this._fetchManifest(undefined) + .then((val) => { + return val.parse({ previousManifest: null, unsafeMode: false }); + }); + } + + manifestProm + .then((val : IManifestFetcherParsedResult) => { + this.trigger("manifestReady", val.manifest); + if (!this._canceller.isUsed()) { + this._recursivelyRefreshManifest(val.manifest, val); + } + }) + .catch((err : unknown) => this._onFatalError(err)); + } + + /** + * Update URL of the fetched Manifest. + * @param {Array. | undefined} urls - New Manifest URLs by order of + * priority or `undefined` if there's now no URL. + * @param {boolean} refreshNow - If set to `true`, the next Manifest refresh + * will be triggered immediately. + */ + public updateContentUrls(urls : string[] | undefined, refreshNow : boolean) : void { + this._prioritizedContentUrl = urls?.[0] ?? undefined; + if (refreshNow) { + this.scheduleManualRefresh({ + enablePartialRefresh: false, + delay: 0, + canUseUnsafeMode: false, + }); + } } /** @@ -165,94 +189,80 @@ export default class ManifestFetcher { * If not set, the regular Manifest url - defined on the `ManifestFetcher` * instanciation - will be used instead. * - * @param {string} [url] - * @returns {Observable} + * @param {string | undefined} url + * @returns {Promise} */ - public fetch(url? : string) : Observable - { - return new Observable((obs) => { - const settings = this._settings; - const pipelines = this._pipelines; - const requestUrl = url ?? this._manifestUrl; - - /** `true` if the loading pipeline is already completely executed. */ - let hasFinishedLoading = false; - - /** Allows to cancel the loading operation. */ - const canceller = new TaskCanceller(); - - const backoffSettings = this._getBackoffSetting((err) => { - obs.next({ type: "warning", value: errorSelector(err) }); - }); - - const loadingPromise = pipelines.resolveManifestUrl === undefined ? - callLoaderWithRetries(requestUrl) : - callResolverWithRetries(requestUrl).then(callLoaderWithRetries); - - loadingPromise - .then(response => { - hasFinishedLoading = true; - obs.next({ - type: "response", - parse: (parserOptions : IManifestFetcherParserOptions) => { - return this._parseLoadedManifest(response, parserOptions); - }, - }); - obs.complete(); - }) - .catch((err : unknown) => { - if (canceller.isUsed) { - // Cancellation has already been handled by RxJS - return; - } - hasFinishedLoading = true; - obs.error(errorSelector(err)); - }); + private async _fetchManifest( + url : string | undefined + ) : Promise { + const cancelSignal = this._canceller.signal; + const settings = this._settings; + const pipelines = this._pipelines; + + // TODO Better handle multiple Manifest URLs + const requestUrl = url ?? this._manifestUrls?.[0]; + + const backoffSettings = this._getBackoffSetting((err) => { + this.trigger("warning", errorSelector(err)); + }); - return () => { - if (!hasFinishedLoading) { - canceller.cancel(); - } + const loadingPromise = pipelines.resolveManifestUrl === undefined ? + callLoaderWithRetries(requestUrl) : + callResolverWithRetries(requestUrl).then(callLoaderWithRetries); + + try { + const response = await loadingPromise; + return { + parse: (parserOptions : IManifestFetcherParserOptions) => { + return this._parseLoadedManifest(response, + parserOptions, + requestUrl); + }, }; - - /** - * Call the resolver part of the pipeline, retrying if it fails according - * to the current settings. - * Returns the Promise of the last attempt. - * /!\ This pipeline should have a `resolveManifestUrl` function defined. - * @param {string | undefined} resolverUrl - * @returns {Promise} - */ - function callResolverWithRetries(resolverUrl : string | undefined) { - const { resolveManifestUrl } = pipelines; - assert(resolveManifestUrl !== undefined); - const callResolver = () => resolveManifestUrl(resolverUrl, canceller.signal); - return scheduleRequestPromise(callResolver, backoffSettings, canceller.signal); - } - - /** - * Call the loader part of the pipeline, retrying if it fails according - * to the current settings. - * Returns the Promise of the last attempt. - * @param {string | undefined} manifestUrl - * @returns {Promise} - */ - function callLoaderWithRetries(manifestUrl : string | undefined) { - const { loadManifest } = pipelines; - let requestTimeout : number | undefined = - isNullOrUndefined(settings.requestTimeout) ? - config.getCurrent().DEFAULT_REQUEST_TIMEOUT : - settings.requestTimeout; - if (requestTimeout < 0) { - requestTimeout = undefined; - } - const callLoader = () => loadManifest(manifestUrl, - { timeout: requestTimeout }, - canceller.signal); - return scheduleRequestPromise(callLoader, backoffSettings, canceller.signal); + } catch (err) { + throw errorSelector(err); + } + + /** + * Call the resolver part of the pipeline, retrying if it fails according + * to the current settings. + * Returns the Promise of the last attempt. + * /!\ This pipeline should have a `resolveManifestUrl` function defined. + * @param {string | undefined} resolverUrl + * @returns {Promise} + */ + function callResolverWithRetries( + resolverUrl : string | undefined + ) : Promise { + const { resolveManifestUrl } = pipelines; + assert(resolveManifestUrl !== undefined); + const callResolver = () => resolveManifestUrl(resolverUrl, cancelSignal); + return scheduleRequestPromise(callResolver, backoffSettings, cancelSignal); + } + + /** + * Call the loader part of the pipeline, retrying if it fails according + * to the current settings. + * Returns the Promise of the last attempt. + * @param {string | undefined} manifestUrl + * @returns {Promise} + */ + function callLoaderWithRetries( + manifestUrl : string | undefined + ) : Promise> { + const { loadManifest } = pipelines; + let requestTimeout : number | undefined = + isNullOrUndefined(settings.requestTimeout) ? + config.getCurrent().DEFAULT_REQUEST_TIMEOUT : + settings.requestTimeout; + if (requestTimeout < 0) { + requestTimeout = undefined; } - }); + const callLoader = () => loadManifest(manifestUrl, + { timeout: requestTimeout }, + cancelSignal); + return scheduleRequestPromise(callLoader, backoffSettings, cancelSignal); + } } /** @@ -264,17 +274,19 @@ export default class ManifestFetcher { * information on the request can be used by the parsing process. * @param {*} manifest * @param {Object} parserOptions - * @returns {Observable} + * @param {string | undefined} originalUrl + * @returns {Promise} */ - public parse( + private parse( manifest : unknown, - parserOptions : IManifestFetcherParserOptions - ) : Observable { + parserOptions : IManifestFetcherParserOptions, + originalUrl : string | undefined + ) : Promise { return this._parseLoadedManifest({ responseData: manifest, size: undefined, requestDuration: undefined }, - parserOptions); + parserOptions, + originalUrl); } @@ -284,127 +296,98 @@ export default class ManifestFetcher { * @param {Object} loaded - Information about the loaded Manifest as well as * about the corresponding request. * @param {Object} parserOptions - Options used when parsing the Manifest. - * @returns {Observable} + * @param {string | undefined} requestUrl + * @returns {Promise} */ - private _parseLoadedManifest( + private async _parseLoadedManifest( loaded : IRequestedData, - parserOptions : IManifestFetcherParserOptions - ) : Observable - { - return new Observable(obs => { - const parsingTimeStart = performance.now(); - const canceller = new TaskCanceller(); - const { sendingTime, receivedTime } = loaded; - const backoffSettings = this._getBackoffSetting((err) => { - obs.next({ type: "warning", value: errorSelector(err) }); - }); + parserOptions : IManifestFetcherParserOptions, + requestUrl : string | undefined + ) : Promise { + const parsingTimeStart = performance.now(); + const cancelSignal = this._canceller.signal; + const trigger = this.trigger.bind(this); + const { sendingTime, receivedTime } = loaded; + const backoffSettings = this._getBackoffSetting((err) => { + this.trigger("warning", errorSelector(err)); + }); - const opts = { externalClockOffset: parserOptions.externalClockOffset, - unsafeMode: parserOptions.unsafeMode, - previousManifest: parserOptions.previousManifest, - originalUrl: this._manifestUrl }; + const originalUrl = requestUrl ?? this._manifestUrls?.[0]; + const opts = { externalClockOffset: parserOptions.externalClockOffset, + unsafeMode: parserOptions.unsafeMode, + previousManifest: parserOptions.previousManifest, + originalUrl }; + try { + const res = this._pipelines.parseManifest(loaded, + opts, + onWarnings, + cancelSignal, + scheduleRequest); + if (!isPromise(res)) { + return finish(res.manifest); + } else { + const { manifest } = await res; + return finish(manifest); + } + } catch (err) { + const formattedError = formatError(err, { + defaultCode: "PIPELINE_PARSE_ERROR", + defaultReason: "Unknown error when parsing the Manifest", + }); + throw formattedError; + } + + /** + * Perform a request with the same retry mechanisms and error handling + * than for a Manifest loader. + * @param {Function} performRequest + * @returns {Function} + */ + async function scheduleRequest( + performRequest : () => Promise + ) : Promise { try { - const res = this._pipelines.parseManifest(loaded, - opts, - onWarnings, - canceller.signal, - scheduleRequest); - if (!isPromise(res)) { - emitManifestAndComplete(res.manifest); - } else { - res - .then(({ manifest }) => emitManifestAndComplete(manifest)) - .catch((err) => { - if (canceller.isUsed) { - // Cancellation is already handled by RxJS - return; - } - emitError(err, true); - }); - } + const data = await scheduleRequestPromise(performRequest, + backoffSettings, + cancelSignal); + return data; } catch (err) { - if (canceller.isUsed) { - // Cancellation is already handled by RxJS - return undefined; - } - emitError(err, true); + throw errorSelector(err); } - - return () => { - canceller.cancel(); - }; - - /** - * Perform a request with the same retry mechanisms and error handling - * than for a Manifest loader. - * @param {Function} performRequest - * @returns {Function} - */ - async function scheduleRequest( - performRequest : () => Promise - ) : Promise { - try { - const data = await scheduleRequestPromise(performRequest, - backoffSettings, - canceller.signal); - return data; - } catch (err) { - throw errorSelector(err); + } + + /** + * Handle minor errors encountered by a Manifest parser. + * @param {Array.} warnings + */ + function onWarnings(warnings : Error[]) : void { + for (const warning of warnings) { + if (cancelSignal.isCancelled()) { + return; } - } - - /** - * Handle minor errors encountered by a Manifest parser. - * @param {Array.} warnings - */ - function onWarnings(warnings : Error[]) : void { - for (const warning of warnings) { - if (canceller.isUsed) { - return; - } - emitError(warning, false); - } - } - - /** - * Emit a formatted "parsed" event through `obs`. - * To call once the Manifest has been parsed. - * @param {Object} manifest - */ - function emitManifestAndComplete(manifest : Manifest) : void { - onWarnings(manifest.contentWarnings); - const parsingTime = performance.now() - parsingTimeStart; - log.info(`MF: Manifest parsed in ${parsingTime}ms`); - - obs.next({ type: "parsed" as const, - manifest, - sendingTime, - receivedTime, - parsingTime }); - obs.complete(); - } - - /** - * Format the given Error and emit it through `obs`. - * Either through a `"warning"` event, if `isFatal` is `false`, or through - * a fatal Observable error, if `isFatal` is set to `true`. - * @param {*} err - * @param {boolean} isFatal - */ - function emitError(err : unknown, isFatal : boolean) : void { - const formattedError = formatError(err, { + const formattedError = formatError(warning, { defaultCode: "PIPELINE_PARSE_ERROR", defaultReason: "Unknown error when parsing the Manifest", }); - if (isFatal) { - obs.error(formattedError); - } else { - obs.next({ type: "warning" as const, - value: formattedError }); - } + trigger("warning", formattedError); } - }); + } + + /** + * Emit a formatted "parsed" event through `obs`. + * To call once the Manifest has been parsed. + * @param {Object} manifest + */ + function finish(manifest : Manifest) : IManifestFetcherParsedResult { + onWarnings(manifest.contentWarnings); + const parsingTime = performance.now() - parsingTimeStart; + log.info(`MF: Manifest parsed in ${parsingTime}ms`); + + return { manifest, + sendingTime, + receivedTime, + parsingTime }; + } } /** @@ -433,6 +416,266 @@ export default class ManifestFetcher { maxRetryRegular, maxRetryOffline }; } + + /** + * Performs Manifest refresh (recursively) when it judges it is time to do so. + * @param {Object} manifest + * @param {Object} manifestRequestInfos - Various information linked to the + * last Manifest loading and parsing operations. + */ + private _recursivelyRefreshManifest ( + manifest : Manifest, + { sendingTime, parsingTime, updatingTime } : { sendingTime?: number | undefined; + parsingTime? : number | undefined; + updatingTime? : number | undefined; } + ) : void { + const { MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE, + MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE } = config.getCurrent(); + + /** + * Total time taken to fully update the last Manifest, in milliseconds. + * Note: this time also includes possible requests done by the parsers. + */ + const totalUpdateTime = parsingTime !== undefined ? + parsingTime + (updatingTime ?? 0) : + undefined; + + /** + * "unsafeMode" is a mode where we unlock advanced Manifest parsing + * optimizations with the added risk to lose some information. + * `unsafeModeEnabled` is set to `true` when the `unsafeMode` is enabled. + * + * Only perform parsing in `unsafeMode` when the last full parsing took a + * lot of time and do not go higher than the maximum consecutive time. + */ + + const unsafeModeEnabled = this._consecutiveUnsafeMode > 0 ? + this._consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE : + totalUpdateTime !== undefined ? + (totalUpdateTime >= MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE) : + false; + + /** Time elapsed since the beginning of the Manifest request, in milliseconds. */ + const timeSinceRequest = sendingTime === undefined ? + 0 : + performance.now() - sendingTime; + + /** Minimum update delay we should not go below, in milliseconds. */ + const minInterval = Math.max(this._settings.minimumManifestUpdateInterval - + timeSinceRequest, + 0); + + /** + * Multiple refresh trigger are scheduled here, but only the first one should + * be effectively considered. + * `nextRefreshCanceller` will allow to cancel every other when one is triggered. + */ + const nextRefreshCanceller = new TaskCanceller(); + nextRefreshCanceller.linkToSignal(this._canceller.signal); + + /* Function to manually schedule a Manifest refresh */ + this.scheduleManualRefresh = (settings : IManifestRefreshSettings) => { + const { enablePartialRefresh, delay, canUseUnsafeMode } = settings; + const unsafeMode = canUseUnsafeMode && unsafeModeEnabled; + // The value allows to set a delay relatively to the last Manifest refresh + // (to avoid asking for it too often). + const timeSinceLastRefresh = sendingTime === undefined ? + 0 : + performance.now() - sendingTime; + const _minInterval = Math.max(this._settings.minimumManifestUpdateInterval - + timeSinceLastRefresh, + 0); + const timeoutId = setTimeout(() => { + nextRefreshCanceller.cancel(); + this._triggerNextManifestRefresh(manifest, { enablePartialRefresh, unsafeMode }); + }, Math.max((delay ?? 0) - timeSinceLastRefresh, _minInterval)); + nextRefreshCanceller.signal.register(() => { + clearTimeout(timeoutId); + }); + }; + + /* Handle Manifest expiration. */ + if (manifest.expired !== null) { + const timeoutId = setTimeout(() => { + manifest.expired?.then(() => { + nextRefreshCanceller.cancel(); + this._triggerNextManifestRefresh(manifest, { enablePartialRefresh: false, + unsafeMode: unsafeModeEnabled }); + }, noop /* `expired` should not reject */); + }, minInterval); + nextRefreshCanceller.signal.register(() => { + clearTimeout(timeoutId); + }); + } + + /* + * Trigger Manifest refresh when the Manifest needs to be refreshed + * according to the Manifest's internal properties (parsing time is also + * taken into account in this operation to avoid refreshing too often). + */ + if (manifest.lifetime !== undefined && manifest.lifetime >= 0) { + /** Regular refresh delay as asked by the Manifest. */ + const regularRefreshDelay = manifest.lifetime * 1000 - timeSinceRequest; + + /** Actually choosen delay to refresh the Manifest. */ + let actualRefreshInterval : number; + + if (totalUpdateTime === undefined) { + actualRefreshInterval = regularRefreshDelay; + } else if (manifest.lifetime < 3 && totalUpdateTime >= 100) { + // If Manifest update is very frequent and we take time to update it, + // postpone it. + actualRefreshInterval = Math.min( + Math.max( + // Take 3 seconds as a default safe value for a base interval. + 3000 - timeSinceRequest, + // Add update time to the original interval. + Math.max(regularRefreshDelay, 0) + totalUpdateTime + ), + + // Limit the postponment's higher bound to a very high value relative + // to `regularRefreshDelay`. + // This avoid perpetually postponing a Manifest update when + // performance seems to have been abysmal one time. + regularRefreshDelay * 6 + ); + log.info("MUS: Manifest update rythm is too frequent. Postponing next request.", + regularRefreshDelay, + actualRefreshInterval); + } else if (totalUpdateTime >= (manifest.lifetime * 1000) / 10) { + // If Manifest updating time is very long relative to its lifetime, + // postpone it: + actualRefreshInterval = Math.min( + // Just add the update time to the original waiting time + Math.max(regularRefreshDelay, 0) + totalUpdateTime, + + // Limit the postponment's higher bound to a very high value relative + // to `regularRefreshDelay`. + // This avoid perpetually postponing a Manifest update when + // performance seems to have been abysmal one time. + regularRefreshDelay * 6); + log.info("MUS: Manifest took too long to parse. Postponing next request", + actualRefreshInterval, + actualRefreshInterval); + } else { + actualRefreshInterval = regularRefreshDelay; + } + const timeoutId = setTimeout(() => { + nextRefreshCanceller.cancel(); + this._triggerNextManifestRefresh(manifest, { enablePartialRefresh: false, + unsafeMode: unsafeModeEnabled }); + }, Math.max(actualRefreshInterval, minInterval)); + nextRefreshCanceller.signal.register(() => { + clearTimeout(timeoutId); + }); + } + } + + /** + * Refresh the Manifest, performing a full update if a partial update failed. + * Also re-call `recursivelyRefreshManifest` to schedule the next refresh + * trigger. + * @param {Object} manifest + * @param {Object} refreshInformation + */ + private _triggerNextManifestRefresh( + manifest : Manifest, + { enablePartialRefresh, + unsafeMode } : { enablePartialRefresh : boolean; + unsafeMode : boolean; } + ) { + const manifestUpdateUrl = manifest.updateUrl; + let fullRefresh : boolean; + let refreshURL : string | undefined; + if (this._prioritizedContentUrl !== null) { + fullRefresh = true; + refreshURL = this._prioritizedContentUrl; + this._prioritizedContentUrl = null; + } else { + fullRefresh = !enablePartialRefresh || manifestUpdateUrl === undefined; + refreshURL = fullRefresh ? manifest.getUrl() : + manifestUpdateUrl; + } + const externalClockOffset = manifest.clockOffset; + + if (unsafeMode) { + this._consecutiveUnsafeMode += 1; + log.info("Init: Refreshing the Manifest in \"unsafeMode\" for the " + + String(this._consecutiveUnsafeMode) + " consecutive time."); + } else if (this._consecutiveUnsafeMode > 0) { + log.info("Init: Not parsing the Manifest in \"unsafeMode\" anymore after " + + String(this._consecutiveUnsafeMode) + " consecutive times."); + this._consecutiveUnsafeMode = 0; + } + + if (this._isRefreshPending) { + return; + } + this._isRefreshPending = true; + this._fetchManifest(refreshURL) + .then(res => res.parse({ externalClockOffset, + previousManifest: manifest, + unsafeMode })) + .then(res => { + this._isRefreshPending = false; + const { manifest: newManifest, + sendingTime: newSendingTime, + parsingTime } = res; + const updateTimeStart = performance.now(); + + if (fullRefresh) { + manifest.replace(newManifest); + } else { + try { + manifest.update(newManifest); + } catch (e) { + const message = e instanceof Error ? e.message : + "unknown error"; + log.warn(`MUS: Attempt to update Manifest failed: ${message}`, + "Re-downloading the Manifest fully"); + const { FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY } = config.getCurrent(); + + // The value allows to set a delay relatively to the last Manifest refresh + // (to avoid asking for it too often). + const timeSinceLastRefresh = newSendingTime === undefined ? + 0 : + performance.now() - newSendingTime; + const _minInterval = Math.max(this._settings.minimumManifestUpdateInterval - + timeSinceLastRefresh, + 0); + let unregisterCanceller = noop; + const timeoutId = setTimeout(() => { + unregisterCanceller(); + this._triggerNextManifestRefresh(manifest, + { enablePartialRefresh: false, + unsafeMode: false }); + }, Math.max(FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY - + timeSinceLastRefresh, + _minInterval)); + unregisterCanceller = this._canceller.signal.register(() => { + clearTimeout(timeoutId); + }); + return; + } + } + const updatingTime = performance.now() - updateTimeStart; + this._recursivelyRefreshManifest(manifest, { sendingTime: newSendingTime, + parsingTime, + updatingTime }); + }) + .catch((err) => { + this._isRefreshPending = false; + this._onFatalError(err); + }); + } + + private _onFatalError(err : unknown) : void { + if (this._canceller.isUsed()) { + return; + } + this.trigger("error", err); + this.dispose(); + } } /** @@ -444,3 +687,112 @@ export default class ManifestFetcher { function isPromise(val : T | Promise) : val is Promise { return val instanceof Promise; } + +/** What will be sent once parsed. */ +interface IManifestFetcherParsedResult { + /** The resulting Manifest */ + manifest : Manifest; + /** + * The time (`performance.now()`) at which the request was started (at which + * the JavaScript call was done). + */ + sendingTime? : number | undefined; + /** The time (`performance.now()`) at which the request was fully received. */ + receivedTime? : number | undefined; + /* The time taken to parse the Manifest through the corresponding parse function. */ + parsingTime? : number | undefined; +} + +/** Response emitted by a Manifest fetcher. */ +interface IManifestFetcherResponse { + /** Allows to parse a fetched Manifest into a `Manifest` structure. */ + parse(parserOptions : IManifestFetcherParserOptions) : + Promise; +} + +interface IManifestFetcherParserOptions { + /** + * If set, offset to add to `performance.now()` to obtain the current + * server's time. + */ + externalClockOffset? : number | undefined; + /** The previous value of the Manifest (when updating). */ + previousManifest : Manifest | null; + /** + * If set to `true`, the Manifest parser can perform advanced optimizations + * to speed-up the parsing process. Those optimizations might lead to a + * de-synchronization with what is actually on the server, hence the "unsafe" + * part. + * To use with moderation and only when needed. + */ + unsafeMode : boolean; +} + +/** Options used by `createManifestFetcher`. */ +export interface IManifestFetcherSettings { + /** + * Whether the content is played in a low-latency mode. + * This has an impact on default backoff delays. + */ + lowLatencyMode : boolean; + /** Maximum number of time a request on error will be retried. */ + maxRetryRegular : number | undefined; + /** Maximum number of time a request be retried when the user is offline. */ + maxRetryOffline : number | undefined; + /** + * Timeout after which request are aborted and, depending on other options, + * retried. + * To set to `-1` for no timeout. + * `undefined` will lead to a default, large, timeout being used. + */ + requestTimeout : number | undefined; + /** Limit the frequency of Manifest updates. */ + minimumManifestUpdateInterval : number; + /** + * Potential first Manifest to rely on, allowing to skip the initial Manifest + * request. + */ + initialManifest : IInitialManifest | undefined; +} + +/** Event sent by the `ManifestFetcher`. */ +export interface IManifestFetcherEvent { + /** Event sent by the `ManifestFetcher` when a minor error has been encountered. */ + warning : IPlayerError; + /** + * Event sent by the `ManifestFetcher` when a major error has been encountered, + * leading to the `ManifestFetcher` being disposed. + */ + error : unknown; + /** Event sent after the Manifest has first been fetched. */ + manifestReady : Manifest; +} + +/** Argument defined when forcing a Manifest refresh. */ +export interface IManifestRefreshSettings { + /** + * if `false`, the Manifest should be fully updated. + * if `true`, a shorter version with just the added information can be loaded + * instead. + * + * Basically can be set to `true` in most updates to improve performances, but + * should be set to `false` if you suspect some iregularities in the Manifest, + * so a complete and thorough refresh is performed. + * + * Note that this optimization is only possible when a shorter version of the + * Manifest is available. + * In other cases, setting this value to `true` won't have any effect. + */ + enablePartialRefresh : boolean; + /** + * Optional wanted refresh delay, which is the minimum time you want to wait + * before updating the Manifest + */ + delay? : number | undefined; + /** + * Whether the parsing can be done in the more efficient "unsafeMode". + * This mode is extremely fast but can lead to de-synchronisation with the + * server. + */ + canUseUnsafeMode : boolean; +} diff --git a/src/core/fetchers/segment/segment_fetcher.ts b/src/core/fetchers/segment/segment_fetcher.ts index 956081e015..5d67c4486b 100644 --- a/src/core/fetchers/segment/segment_fetcher.ts +++ b/src/core/fetchers/segment/segment_fetcher.ts @@ -196,14 +196,7 @@ export default function createSegmentFetcher( id: requestId, content }); - cancellationSignal.register(() => { - if (requestInfo !== undefined) { - return; // Request already terminated - } - log.debug("SF: Segment request cancelled", segmentIdString); - requestInfo = null; - lifecycleCallbacks.onRequestEnd?.({ id: requestId }); - }); + cancellationSignal.register(onCancellation); try { const res = await scheduleRequestWithCdns(content.representation.cdnMetadata, @@ -232,22 +225,34 @@ export default function createSegmentFetcher( requestInfo = null; } - if (!cancellationSignal.isCancelled) { + if (!cancellationSignal.isCancelled()) { // The current task could have been canceled as a result of one // of the previous callbacks call. In that case, we don't want to send // a "requestEnd" again as it has already been sent on cancellation. lifecycleCallbacks.onRequestEnd?.({ id: requestId }); } + cancellationSignal.deregister(onCancellation); } catch (err) { + cancellationSignal.deregister(onCancellation); requestInfo = null; if (err instanceof CancellationError) { log.debug("SF: Segment request aborted", segmentIdString); throw err; } log.debug("SF: Segment request failed", segmentIdString); + lifecycleCallbacks.onRequestEnd?.({ id: requestId }); throw errorSelector(err); } + function onCancellation() { + if (requestInfo !== undefined) { + return; // Request already terminated + } + log.debug("SF: Segment request cancelled", segmentIdString); + requestInfo = null; + lifecycleCallbacks.onRequestEnd?.({ id: requestId }); + } + /** * Call a segment loader for the given URL with the right arguments. * @param {Object|null} cdnMetadata diff --git a/src/core/fetchers/segment/segment_fetcher_creator.ts b/src/core/fetchers/segment/segment_fetcher_creator.ts index dd0fb785b8..a92692b047 100644 --- a/src/core/fetchers/segment/segment_fetcher_creator.ts +++ b/src/core/fetchers/segment/segment_fetcher_creator.ts @@ -37,30 +37,6 @@ import TaskPrioritizer from "./task_prioritizer"; * priority. * * @class SegmentFetcherCreator - * - * @example - * ```js - * const creator = new SegmentFetcherCreator(transport, { - * lowLatencyMode: false, - * maxRetryRegular: Infinity, - * maxRetryOffline: Infinity, - * }); - * - * // 2 - create a new fetcher with its backoff options - * const fetcher = creator.createSegmentFetcher("audio", { - * // ... (lifecycle callbacks if wanted) - * }); - * - * // 3 - load a segment with a given priority - * fetcher.createRequest(myContent, 1) - * // 4 - parse it - * .pipe( - * filter(evt => evt.type === "chunk"), - * mergeMap(response => response.parse()); - * ) - * // 5 - use it - * .subscribe((res) => console.log("audio chunk downloaded:", res)); - * ``` */ export default class SegmentFetcherCreator { /** diff --git a/src/core/fetchers/segment/task_prioritizer.ts b/src/core/fetchers/segment/task_prioritizer.ts index 6c0f4eb58f..f1d20ff397 100644 --- a/src/core/fetchers/segment/task_prioritizer.ts +++ b/src/core/fetchers/segment/task_prioritizer.ts @@ -1,5 +1,6 @@ import log from "../../../log"; import arrayFindIndex from "../../../utils/array_find_index"; +import createCancellablePromise from "../../../utils/create_cancellable_promise"; import TaskCanceller, { CancellationError, CancellationSignal, @@ -64,19 +65,34 @@ export default class TaskPrioritizer { cancelSignal: CancellationSignal ): Promise { let newTask: IPrioritizerTask; - - return new Promise((resolve, reject) => { + return createCancellablePromise(cancelSignal, (resolve, reject) => { /** Function allowing to start the underlying Promise. */ const trigger = () => { if (newTask.hasEnded) { - unregisterCancelSignal(); return; } - const interrupter = new TaskCanceller({ cancelOn: cancelSignal }); + const finishTask = () => { + unlinkInterrupter(); + this._endTask(newTask); + }; + + const onResolve = (value: T) => { + callbacks.beforeEnded(); + finishTask(); + resolve(value); + }; + + const onReject = (err: unknown) => { + finishTask(); + reject(err); + }; + + const interrupter = new TaskCanceller(); + const unlinkInterrupter = interrupter.linkToSignal(cancelSignal); newTask.interrupter = interrupter; interrupter.signal.register(() => { newTask.interrupter = null; - if (!cancelSignal.isCancelled) { + if (!cancelSignal.isCancelled()) { callbacks.beforeInterrupted(); } }); @@ -89,8 +105,8 @@ export default class TaskPrioritizer { newTask.taskFn(interrupter.signal) .then(onResolve) .catch((err) => { - if (!cancelSignal.isCancelled && - interrupter.isUsed && + if (!cancelSignal.isCancelled() && + interrupter.isUsed() && err instanceof CancellationError) { return; @@ -99,29 +115,6 @@ export default class TaskPrioritizer { }); }; - const unregisterCancelSignal = cancelSignal.register( - (cancellationError: CancellationError) => { - this._endTask(newTask); - reject(cancellationError); - } - ); - - const finishTask = () => { - unregisterCancelSignal(); - this._endTask(newTask); - }; - - const onResolve = (value: T) => { - callbacks.beforeEnded(); - finishTask(); - resolve(value); - }; - - const onReject = (err: unknown) => { - finishTask(); - reject(err); - }; - newTask = { hasEnded: false, priority, @@ -146,6 +139,8 @@ export default class TaskPrioritizer { this._interruptCancellableTasks(); } } + + return () => this._endTask(newTask); }); } diff --git a/src/core/fetchers/utils/schedule_request.ts b/src/core/fetchers/utils/schedule_request.ts index e67024060f..5c49a91aa4 100644 --- a/src/core/fetchers/utils/schedule_request.ts +++ b/src/core/fetchers/utils/schedule_request.ts @@ -315,7 +315,7 @@ export async function scheduleRequestWithCdns( async function retryWithNextCdn(prevRequestError : unknown) : Promise { const nextCdn = getCdnToRequest(); - if (cancellationSignal.isCancelled) { + if (cancellationSignal.isCancelled()) { throw cancellationSignal.cancellationError; } @@ -324,7 +324,7 @@ export async function scheduleRequestWithCdns( } onRetry(prevRequestError); - if (cancellationSignal.isCancelled) { + if (cancellationSignal.isCancelled()) { throw cancellationSignal.cancellationError; } @@ -358,26 +358,37 @@ export async function scheduleRequestWithCdns( return requestCdn(nextWantedCdn); } - const canceller = new TaskCanceller({ cancelOn: cancellationSignal }); + const canceller = new TaskCanceller(); + const unlinkCanceller = canceller.linkToSignal(cancellationSignal); return new Promise((res, rej) => { /* eslint-disable-next-line @typescript-eslint/no-misused-promises */ cdnPrioritizer?.addEventListener("priorityChange", () => { const updatedPrioritaryCdn = getCdnToRequest(); - if (cancellationSignal.isCancelled) { + if (cancellationSignal.isCancelled()) { throw cancellationSignal.cancellationError; } if (updatedPrioritaryCdn === undefined) { - return rej(prevRequestError); + return cleanAndReject(prevRequestError); } if (updatedPrioritaryCdn !== nextWantedCdn) { canceller.cancel(); waitPotentialBackoffAndRequest(updatedPrioritaryCdn, prevRequestError) - .then(res, rej); + .then(cleanAndResolve, cleanAndReject); } }, canceller.signal); cancellableSleep(blockedFor, canceller.signal) - .then(() => requestCdn(nextWantedCdn).then(res, rej), noop); + .then(() => requestCdn(nextWantedCdn) + .then(cleanAndResolve, cleanAndReject), noop); + + function cleanAndResolve(response : T) { + unlinkCanceller(); + res(response); + } + function cleanAndReject(err : unknown) { + unlinkCanceller(); + rej(err); + } }); } diff --git a/src/core/init/README.md b/src/core/init/README.md index 85b9872dc2..489d4194ff 100644 --- a/src/core/init/README.md +++ b/src/core/init/README.md @@ -1,30 +1,33 @@ -# The `Init` ################################################################### +# The `ContentInitializer` ##################################################### -The Init is the part of the code starting the logic behind playing a content. +The ContentInitializer is the part of the code actually starting and running the +logic behind playing a content. -Its code is written in the ``src/core/init`` directory. +Its code is written in the `src/core/init` directory. -Every time you're calling the API to load a new video, the init is called by it -(with multiple options). +Every time you're calling the API to load a new video, a ContentInitializer is +called by it (with multiple options). -The Init then starts loading the content and communicate back its progress to +The ContentInitializer then starts loading the content and communicate back its progress to the API through events. ``` +-----------+ - 1. LOAD VIDEO | | 2. CALLS + 1. loadVideo | | 2. Instanciate ---------------> | API | -------------------+ | | | +-----------+ | ^ v - | +--------------+ - | 3. EMIT EVENTS | | - +------------------- | Init | - | | - +--------------+ + | +--------------------+ + | 3. Emit events | | + +------------------- | ContentInitializer | + | | + +--------------------+ ``` -During the various events happening on content playback, the Init will create / -destroy / update various player blocks. Example of such blocks are: +During the various events happening on content playback, the ContentInitializer will +create / destroy / update various player submodules. + +Example of such submodules are: - Adaptive streaming management - DRM handling - Manifest loading, parsing and refreshing @@ -35,56 +38,39 @@ destroy / update various player blocks. Example of such blocks are: ## Usage ####################################################################### -Concretely, the Init is a function which returns an Observable. -This Observable: - - - will automatically load the described content on subscription - - will automatically stop and clean-up infos related to the content on - unsubscription - - communicate on various streaming events through emitted notifications - - throw in the case of a fatal error (i.e. an error interrupting playback) - - -### Communication between the API and the Init ################################# +Concretely, the ContentInitializer is a class respecting a given interface, +allowing to: -Objects emitted by the Observable is the only way the Init should be able to -communicate with the API. + - prepare a future content for future play - without influencing a potentially + already-playing content (e.g. by pre-loading the next content's Manifest). -The API is then able to communicate back to the Init, either: - - by Observable provided by the API as arguments when the Init function was - called - - by emitting through Subject provided by the Init, as a payload of one of - its event + - start loading the content on a given media element -Thus, there is three ways the API and Init can communicate: - - API -> Init: When the Init function is called (so a single time) - - Init -> API: Through events emitted by the returned Observable - - API -> Init: Through Observables/Subjects the Init function is in possession - of. + - communicate about various playback events ### Emitted Events ############################################################# -Events allows the Init to reports milestones of the content playback, such as -when the content is ready to play. +Events allows the ContentInitializer to reports milestones of the content +playback, such as when the content is ready to play. -It's also a way for the Init to communicate information about the content and -give some controls to the user. +It's also a way for the `ContentInitializer` to communicate information about +the content and give some controls to the user. For example, as available audio languages are only known after the manifest has been downloaded and parsed, and as it is most of all a user preference, the -Init can emit to the API, RxJS Subjects allowing the API to "choose" at any -time the wanted language. +ContentInitializer can emit to the API, objects allowing the API to "choose" at +any time the wanted language. ### Playback rate management ################################################### -The playback rate (or speed) is updated by the Init. +The playback rate (or speed) is updated by the ContentInitializer. There can be three occasions for these updates: - - the API set a new Speed (``speed$`` Observable). + - the API set a new speed - the content needs to build its buffer. diff --git a/src/core/init/content_time_boundaries_observer.ts b/src/core/init/content_time_boundaries_observer.ts deleted file mode 100644 index d7264e6811..0000000000 --- a/src/core/init/content_time_boundaries_observer.ts +++ /dev/null @@ -1,382 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - distinctUntilChanged, - ignoreElements, - map, - merge as observableMerge, - Observable, - skipWhile, - startWith, - tap, -} from "rxjs"; -import { MediaError } from "../../errors"; -import Manifest, { - Adaptation, - IRepresentationIndex, -} from "../../manifest"; -import { fromEvent } from "../../utils/event_emitter"; -import filterMap from "../../utils/filter_map"; -import isNullOrUndefined from "../../utils/is_null_or_undefined"; -import createSharedReference, { - IReadOnlySharedReference, -} from "../../utils/reference"; -import { IReadOnlyPlaybackObserver } from "../api"; -import { - IAdaptationChangeEvent, - IStreamOrchestratorPlaybackObservation, -} from "../stream"; -import EVENTS from "./events_generators"; -import { IWarningEvent } from "./types"; - -// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default -// first type parameter as `any` instead of the perfectly fine `unknown`, -// leading to linter issues, as it forbids the usage of `any`. -// This is why we're disabling the eslint rule. -/* eslint-disable @typescript-eslint/no-unsafe-argument */ - -/** - * Observes the position and Adaptations being played and deduce various events - * related to the available time boundaries: - * - Emit when the theoretical duration of the content becomes known or when it - * changes. - * - Emit warnings when the duration goes out of what is currently - * theoretically playable. - * - * @param {Object} manifest - * @param {Object} lastAdaptationChange - * @param {Object} playbackObserver - * @returns {Observable} - */ -export default function ContentTimeBoundariesObserver( - manifest : Manifest, - lastAdaptationChange : IReadOnlySharedReference, - playbackObserver : IReadOnlyPlaybackObserver - -) : Observable { - /** - * Allows to calculate the minimum and maximum playable position on the - * whole content. - */ - const maximumPositionCalculator = new MaximumPositionCalculator(manifest); - - // trigger warnings when the wanted time is before or after the manifest's - // segments - const outOfManifest$ = playbackObserver.getReference().asObservable().pipe( - filterMap(( - { position } - ) => { - const wantedPosition = position.pending ?? position.last; - if ( - wantedPosition < manifest.getMinimumSafePosition() - ) { - const warning = new MediaError("MEDIA_TIME_BEFORE_MANIFEST", - "The current position is behind the " + - "earliest time announced in the Manifest."); - return EVENTS.warning(warning); - } else if ( - wantedPosition > maximumPositionCalculator.getMaximumAvailablePosition() - ) { - const warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST", - "The current position is after the latest " + - "time announced in the Manifest."); - return EVENTS.warning(warning); - } - return null; - }, null)); - - /** - * Contains the content duration according to the last audio and video - * Adaptation chosen for the last Period. - * `undefined` if unknown yet. - */ - const contentDuration = createSharedReference(undefined); - - const updateDurationOnManifestUpdate$ = fromEvent(manifest, "manifestUpdate").pipe( - startWith(null), - tap(() => { - const duration = manifest.isDynamic ? - maximumPositionCalculator.getEndingPosition() : - maximumPositionCalculator.getMaximumAvailablePosition(); - contentDuration.setValue(duration); - }), - ignoreElements() - ); - - const updateDurationAndTimeBoundsOnTrackChange$ = lastAdaptationChange - .asObservable().pipe( - tap((message) => { - if (message === null || !manifest.isLastPeriodKnown) { - return; - } - const lastPeriod = manifest.periods[manifest.periods.length - 1]; - if (message.value.period.id === lastPeriod?.id) { - if (message.value.type === "audio" || message.value.type === "video") { - if (message.value.type === "audio") { - maximumPositionCalculator - .updateLastAudioAdaptation(message.value.adaptation); - } else { - maximumPositionCalculator - .updateLastVideoAdaptation(message.value.adaptation); - } - const newDuration = manifest.isDynamic ? - maximumPositionCalculator.getMaximumAvailablePosition() : - maximumPositionCalculator.getEndingPosition(); - contentDuration.setValue(newDuration); - } - } - }), - ignoreElements()); - - return observableMerge( - updateDurationOnManifestUpdate$, - updateDurationAndTimeBoundsOnTrackChange$, - outOfManifest$, - contentDuration.asObservable().pipe( - skipWhile((val) => val === undefined), - distinctUntilChanged(), - map(value => ({ type: "contentDurationUpdate" as const, value })) - )); -} - -/** - * Calculate the last position from the last chosen audio and video Adaptations - * for the last Period (or a default one, if no Adaptations has been chosen). - * @class MaximumPositionCalculator - */ -class MaximumPositionCalculator { - private _manifest : Manifest; - - // TODO replicate for the minimum position ? - private _lastAudioAdaptation : Adaptation | undefined | null; - private _lastVideoAdaptation : Adaptation | undefined | null; - - /** - * @param {Object} manifest - */ - constructor(manifest : Manifest) { - this._manifest = manifest; - this._lastAudioAdaptation = undefined; - this._lastVideoAdaptation = undefined; - } - - /** - * Update the last known audio Adaptation for the last Period. - * If no Adaptation has been set, it should be set to `null`. - * - * Allows to calculate the maximum position more precizely in - * `getMaximumAvailablePosition` and `getEndingPosition`. - * @param {Object|null} adaptation - */ - public updateLastAudioAdaptation(adaptation : Adaptation | null) : void { - this._lastAudioAdaptation = adaptation; - } - - /** - * Update the last known video Adaptation for the last Period. - * If no Adaptation has been set, it should be set to `null`. - * - * Allows to calculate the maximum position more precizely in - * `getMaximumAvailablePosition` and `getEndingPosition`. - * @param {Object|null} adaptation - */ - public updateLastVideoAdaptation(adaptation : Adaptation | null) : void { - this._lastVideoAdaptation = adaptation; - } - -/** - * Returns an estimate of the maximum position currently reachable (i.e. - * segments are available) under the current circumstances. - * @returns {number} - */ - public getMaximumAvailablePosition() : number { - if (this._manifest.isDynamic) { - return this._manifest.getLivePosition() ?? - this._manifest.getMaximumSafePosition(); - } - if (this._lastVideoAdaptation === undefined || - this._lastAudioAdaptation === undefined) - { - return this._manifest.getMaximumSafePosition(); - } else if (this._lastAudioAdaptation === null) { - if (this._lastVideoAdaptation === null) { - return this._manifest.getMaximumSafePosition(); - } else { - const lastVideoPosition = - getLastAvailablePositionFromAdaptation(this._lastVideoAdaptation); - if (typeof lastVideoPosition !== "number") { - return this._manifest.getMaximumSafePosition(); - } - return lastVideoPosition; - } - } else if (this._lastVideoAdaptation === null) { - const lastAudioPosition = - getLastAvailablePositionFromAdaptation(this._lastAudioAdaptation); - if (typeof lastAudioPosition !== "number") { - return this._manifest.getMaximumSafePosition(); - } - return lastAudioPosition; - } else { - const lastAudioPosition = getLastAvailablePositionFromAdaptation( - this._lastAudioAdaptation - ); - const lastVideoPosition = getLastAvailablePositionFromAdaptation( - this._lastVideoAdaptation - ); - if (typeof lastAudioPosition !== "number" || - typeof lastVideoPosition !== "number") - { - return this._manifest.getMaximumSafePosition(); - } else { - return Math.min(lastAudioPosition, lastVideoPosition); - } - } - } - -/** - * Returns an estimate of the actual ending position once - * the full content is available. - * Returns `undefined` if that could not be determined, for various reasons. - * @returns {number|undefined} - */ - public getEndingPosition() : number | undefined { - if (!this._manifest.isDynamic) { - return this.getMaximumAvailablePosition(); - } - if (this._lastVideoAdaptation === undefined || - this._lastAudioAdaptation === undefined) - { - return undefined; - } else if (this._lastAudioAdaptation === null) { - if (this._lastVideoAdaptation === null) { - return undefined; - } else { - return getEndingPositionFromAdaptation(this._lastVideoAdaptation) ?? - undefined; - } - } else if (this._lastVideoAdaptation === null) { - return getEndingPositionFromAdaptation(this._lastAudioAdaptation) ?? - undefined; - } else { - const lastAudioPosition = - getEndingPositionFromAdaptation(this._lastAudioAdaptation); - const lastVideoPosition = - getEndingPositionFromAdaptation(this._lastVideoAdaptation); - if (typeof lastAudioPosition !== "number" || - typeof lastVideoPosition !== "number") - { - return undefined; - } else { - return Math.min(lastAudioPosition, lastVideoPosition); - } - } - } -} - -/** - * Returns last currently available position from the Adaptation given. - * `undefined` if a time could not be found. - * `null` if the Adaptation has no segments (it could be that it didn't started or - * that it already finished for example). - * - * We consider the earliest last available position from every Representation - * in the given Adaptation. - * @param {Object} adaptation - * @returns {Number|undefined|null} - */ -function getLastAvailablePositionFromAdaptation( - adaptation: Adaptation -) : number | undefined | null { - const { representations } = adaptation; - let min : null | number = null; - - /** - * Some Manifest parsers use the exact same `IRepresentationIndex` reference - * for each Representation of a given Adaptation, because in the actual source - * Manifest file, indexing data is often defined at Adaptation-level. - * This variable allows to optimize the logic here when this is the case. - */ - let lastIndex : IRepresentationIndex | undefined; - for (let i = 0; i < representations.length; i++) { - if (representations[i].index !== lastIndex) { - lastIndex = representations[i].index; - const lastPosition = representations[i].index.getLastAvailablePosition(); - if (lastPosition === undefined) { // we cannot tell - return undefined; - } - if (lastPosition !== null) { - min = isNullOrUndefined(min) ? lastPosition : - Math.min(min, lastPosition); - } - } - } - return min; -} - -/** - * Returns ending time from the Adaptation given, once all its segments are - * available. - * `undefined` if a time could not be found. - * `null` if the Adaptation has no segments (it could be that it already - * finished for example). - * - * We consider the earliest ending time from every Representation in the given - * Adaptation. - * @param {Object} adaptation - * @returns {Number|undefined|null} - */ -function getEndingPositionFromAdaptation( - adaptation: Adaptation -) : number | undefined | null { - const { representations } = adaptation; - let min : null | number = null; - - /** - * Some Manifest parsers use the exact same `IRepresentationIndex` reference - * for each Representation of a given Adaptation, because in the actual source - * Manifest file, indexing data is often defined at Adaptation-level. - * This variable allows to optimize the logic here when this is the case. - */ - let lastIndex : IRepresentationIndex | undefined; - for (let i = 0; i < representations.length; i++) { - if (representations[i].index !== lastIndex) { - lastIndex = representations[i].index; - const lastPosition = representations[i].index.getEnd(); - if (lastPosition === undefined) { // we cannot tell - return undefined; - } - if (lastPosition !== null) { - min = isNullOrUndefined(min) ? lastPosition : - Math.min(min, lastPosition); - } - } - } - return min; -} - -/** - * Emitted when the duration of the full content (== the last playable position) - * has changed. - */ -export interface IContentDurationUpdateEvent { - type: "contentDurationUpdate"; - /** The new theoretical duration, `undefined` if unknown, */ - value : number | undefined; -} - -export type IContentTimeObserverPlaybackObservation = - Pick; diff --git a/src/core/init/directfile_content_initializer.ts b/src/core/init/directfile_content_initializer.ts new file mode 100644 index 0000000000..78fb588b19 --- /dev/null +++ b/src/core/init/directfile_content_initializer.ts @@ -0,0 +1,229 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * /!\ This file is feature-switchable. + * It always should be imported through the `features` object. + */ + +import { clearElementSrc } from "../../compat"; +import { MediaError } from "../../errors"; +import log from "../../log"; +import { + IKeySystemOption, + IPlayerError, +} from "../../public_types"; +import createSharedReference, { + IReadOnlySharedReference, +} from "../../utils/reference"; +import TaskCanceller from "../../utils/task_canceller"; +import { PlaybackObserver } from "../api"; +import { ContentInitializer } from "./types"; +import { IInitialTimeOptions } from "./utils/get_initial_time"; +import getLoadedReference from "./utils/get_loaded_reference"; +import performInitialSeekAndPlay from "./utils/initial_seek_and_play"; +import initializeContentDecryption from "./utils/initialize_content_decryption"; +import RebufferingController from "./utils/rebuffering_controller"; +import listenToMediaError from "./utils/throw_on_media_error"; + +export default class DirectFileContentInitializer extends ContentInitializer { + private _settings : IDirectFileOptions; + private _initCanceller : TaskCanceller; + + constructor(settings : IDirectFileOptions) { + super(); + this._settings = settings; + this._initCanceller = new TaskCanceller(); + } + + public prepare(): void { + return; // Directfile contents do not have any preparation + } + + public start( + mediaElement : HTMLMediaElement, + playbackObserver : PlaybackObserver + ): void { + const cancelSignal = this._initCanceller.signal; + const { keySystems, speed, url } = this._settings; + + clearElementSrc(mediaElement); + + if (url == null) { + throw new Error("No URL for a DirectFile content"); + } + + const decryptionRef = createSharedReference(null); + decryptionRef.finish(); + const drmInitRef = + initializeContentDecryption(mediaElement, keySystems, decryptionRef, { + onError: (err) => this._onFatalError(err), + onWarning: (err : IPlayerError) => this.trigger("warning", err), + }, cancelSignal); + + /** Translate errors coming from the media element into RxPlayer errors. */ + listenToMediaError(mediaElement, + (error : MediaError) => this._onFatalError(error), + cancelSignal); + + /** + * Class trying to avoid various stalling situations, emitting "stalled" + * events when it cannot, as well as "unstalled" events when it get out of one. + */ + const rebufferingController = new RebufferingController(playbackObserver, + null, + speed); + rebufferingController.addEventListener("stalled", (evt) => + this.trigger("stalled", evt)); + rebufferingController.addEventListener("unstalled", () => + this.trigger("unstalled", null)); + rebufferingController.addEventListener("warning", (err) => + this.trigger("warning", err)); + cancelSignal.register(() => { + rebufferingController.destroy(); + }); + rebufferingController.start(); + + drmInitRef.onUpdate((evt, stopListeningToDrmUpdates) => { + if (evt.initializationState.type === "uninitialized") { + return; + } + stopListeningToDrmUpdates(); + + // Start everything! (Just put the URL in the element's src). + log.info("Setting URL to HTMLMediaElement", url); + mediaElement.src = url; + cancelSignal.register(() => { + clearElementSrc(mediaElement); + }); + if (evt.initializationState.type === "awaiting-media-link") { + evt.initializationState.value.isMediaLinked.setValue(true); + drmInitRef.onUpdate((newDrmStatus, stopListeningToDrmUpdatesAgain) => { + if (newDrmStatus.initializationState.type === "initialized") { + stopListeningToDrmUpdatesAgain(); + this._seekAndPlay(mediaElement, playbackObserver); + return; + } + }, { emitCurrentValue: true, clearSignal: cancelSignal }); + } else { + this._seekAndPlay(mediaElement, playbackObserver); + return; + } + }, { emitCurrentValue: true, clearSignal: cancelSignal }); + } + + public updateContentUrls(_urls : string[] | undefined, _refreshNow : boolean) : void { + throw new Error("Cannot update content URL of directfile contents"); + } + + public dispose(): void { + this._initCanceller.cancel(); + } + + private _onFatalError(err : unknown) { + this._initCanceller.cancel(); + this.trigger("error", err); + } + + private _seekAndPlay( + mediaElement : HTMLMediaElement, + playbackObserver : PlaybackObserver + ) { + const cancelSignal = this._initCanceller.signal; + const { autoPlay, startAt } = this._settings; + const initialTime = () => { + log.debug("Init: Calculating initial time"); + const initTime = getDirectFileInitialTime(mediaElement, startAt); + log.debug("Init: Initial time calculated:", initTime); + return initTime; + }; + performInitialSeekAndPlay( + mediaElement, + playbackObserver, + initialTime, + autoPlay, + (err) => this.trigger("warning", err), + cancelSignal + ).autoPlayResult + .then(() => + getLoadedReference(playbackObserver, mediaElement, true, cancelSignal) + .onUpdate((isLoaded, stopListening) => { + if (isLoaded) { + stopListening(); + this.trigger("loaded", { segmentBuffersStore: null }); + } + }, { emitCurrentValue: true, clearSignal: cancelSignal })) + .catch((err) => { + if (!cancelSignal.isCancelled()) { + this._onFatalError(err); + } + }); + } +} + +/** + * calculate initial time as a position in seconds. + * @param {HTMLMediaElement} mediaElement + * @param {Object|undefined} startAt + * @returns {number} + */ +function getDirectFileInitialTime( + mediaElement : HTMLMediaElement, + startAt? : IInitialTimeOptions +) : number { + if (startAt == null) { + return 0; + } + + if (startAt.position != null) { + return startAt.position; + } else if (startAt.wallClockTime != null) { + return startAt.wallClockTime; + } else if (startAt.fromFirstPosition != null) { + return startAt.fromFirstPosition; + } + + const duration = mediaElement.duration; + if (duration == null || !isFinite(duration)) { + log.warn("startAt.fromLastPosition set but no known duration, " + + "beginning at 0."); + return 0; + } + + if (typeof startAt.fromLastPosition === "number") { + return Math.max(0, duration + startAt.fromLastPosition); + } else if (startAt.percentage != null) { + const { percentage } = startAt; + if (percentage >= 100) { + return duration; + } else if (percentage <= 0) { + return 0; + } + const ratio = +percentage / 100; + return duration * ratio; + } + + return 0; +} + +// Argument used by `initializeDirectfileContent` +export interface IDirectFileOptions { + autoPlay : boolean; + keySystems : IKeySystemOption[]; + speed : IReadOnlySharedReference; + startAt? : IInitialTimeOptions | undefined; + url? : string | undefined; +} diff --git a/src/core/init/emit_loaded_event.ts b/src/core/init/emit_loaded_event.ts deleted file mode 100644 index 7fd115b59c..0000000000 --- a/src/core/init/emit_loaded_event.ts +++ /dev/null @@ -1,71 +0,0 @@ - -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Observable, - take, -} from "rxjs"; -import { - shouldValidateMetadata, - shouldWaitForDataBeforeLoaded, -} from "../../compat"; -import filterMap from "../../utils/filter_map"; -import { IPlaybackObservation } from "../api"; -import SegmentBuffersStore from "../segment_buffers"; -import EVENTS from "./events_generators"; -import { ILoadedEvent } from "./types"; - -/** - * Emit a `ILoadedEvent` once the content can be considered as loaded. - * @param {Observable} observation$ - * @param {HTMLMediaElement} mediaElement - * @param {Object|null} segmentBuffersStore - * @param {boolean} isDirectfile - `true` if this is a directfile content - * @returns {Observable} - */ -export default function emitLoadedEvent( - observation$ : Observable, - mediaElement : HTMLMediaElement, - segmentBuffersStore : SegmentBuffersStore | null, - isDirectfile : boolean -) : Observable { - return observation$.pipe( - filterMap((observation) => { - if (observation.rebuffering !== null || - observation.freezing !== null || - observation.readyState === 0) - { - return null; - } - - if (!shouldWaitForDataBeforeLoaded(isDirectfile, - mediaElement.hasAttribute("playsinline"))) - { - return mediaElement.duration > 0 ? EVENTS.loaded(segmentBuffersStore) : - null; - } - - if (observation.readyState >= 3 && observation.currentRange !== null) { - if (!shouldValidateMetadata() || mediaElement.duration > 0) { - return EVENTS.loaded(segmentBuffersStore); - } - return null; - } - return null; - }, null), - take(1)); -} diff --git a/src/core/init/end_of_stream.ts b/src/core/init/end_of_stream.ts deleted file mode 100644 index 4f0ac2fc0b..0000000000 --- a/src/core/init/end_of_stream.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - defer as observableDefer, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - race as observableRace, - startWith, - switchMap, - take, - takeLast, -} from "rxjs"; -import { events } from "../../compat"; -import log from "../../log"; - -const { onRemoveSourceBuffers$, - onSourceOpen$, - onUpdate$ } = events; - -/** - * Get "updating" SourceBuffers from a SourceBufferList. - * @param {SourceBufferList} sourceBuffers - * @returns {Array.} - */ -function getUpdatingSourceBuffers(sourceBuffers : SourceBufferList) : SourceBuffer[] { - const updatingSourceBuffers : SourceBuffer[] = []; - for (let i = 0; i < sourceBuffers.length; i++) { - const SourceBuffer = sourceBuffers[i]; - if (SourceBuffer.updating) { - updatingSourceBuffers.push(SourceBuffer); - } - } - return updatingSourceBuffers; -} - -/** - * Trigger the `endOfStream` method of a MediaSource. - * - * If the MediaSource is ended/closed, do not call this method. - * If SourceBuffers are updating, wait for them to be updated before closing - * it. - * @param {MediaSource} mediaSource - * @returns {Observable} - */ -export default function triggerEndOfStream( - mediaSource : MediaSource -) : Observable { - return observableDefer(() => { - log.debug("Init: Trying to call endOfStream"); - if (mediaSource.readyState !== "open") { - log.debug("Init: MediaSource not open, cancel endOfStream"); - return observableOf(null); - } - - const { sourceBuffers } = mediaSource; - const updatingSourceBuffers = getUpdatingSourceBuffers(sourceBuffers); - - if (updatingSourceBuffers.length === 0) { - log.info("Init: Triggering end of stream"); - mediaSource.endOfStream(); - return observableOf(null); - } - - log.debug("Init: Waiting SourceBuffers to be updated before calling endOfStream."); - const updatedSourceBuffers$ = updatingSourceBuffers - .map((sourceBuffer) => onUpdate$(sourceBuffer).pipe(take(1))); - - return observableRace( - observableMerge(...updatedSourceBuffers$).pipe(takeLast(1)), - onRemoveSourceBuffers$(sourceBuffers).pipe(take(1)) - ).pipe(mergeMap(() => { - return triggerEndOfStream(mediaSource); - })); - }); -} - -/** - * Trigger the `endOfStream` method of a MediaSource each times it opens. - * @see triggerEndOfStream - * @param {MediaSource} mediaSource - * @returns {Observable} - */ -export function maintainEndOfStream(mediaSource : MediaSource) : Observable { - return onSourceOpen$(mediaSource).pipe( - startWith(null), - switchMap(() => triggerEndOfStream(mediaSource)) - ); -} diff --git a/src/core/init/events_generators.ts b/src/core/init/events_generators.ts deleted file mode 100644 index ad3da37448..0000000000 --- a/src/core/init/events_generators.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Manifest, { - Adaptation, - Period, - Representation, -} from "../../manifest"; -import { IPlayerError } from "../../public_types"; -import SegmentBuffersStore, { - IBufferType, -} from "../segment_buffers"; -import { IRepresentationChangeEvent } from "../stream"; -import { - IDecipherabilityUpdateEvent, - ILoadedEvent, - IManifestReadyEvent, - IManifestUpdateEvent, - IReloadingMediaSourceEvent, - IStalledEvent, - IStallingSituation, - IUnstalledEvent, - IWarningEvent, -} from "./types"; - -/** - * Construct a "loaded" event. - * @returns {Object} - */ -function loaded(segmentBuffersStore : SegmentBuffersStore | null) : ILoadedEvent { - return { type: "loaded", value: { segmentBuffersStore } }; -} - -/** - * Construct a "stalled" event. - * @param {Object|null} rebuffering - * @returns {Object} - */ -function stalled(rebuffering : IStallingSituation) : IStalledEvent { - return { type: "stalled", value: rebuffering }; -} - -/** - * Construct a "stalled" event. - * @returns {Object} - */ -function unstalled() : IUnstalledEvent { - return { type: "unstalled", value: null }; -} - -/** - * Construct a "decipherabilityUpdate" event. - * @param {Array.} arg - * @returns {Object} - */ -function decipherabilityUpdate( - arg : Array<{ manifest : Manifest; - period : Period; - adaptation : Adaptation; - representation : Representation; }> -) : IDecipherabilityUpdateEvent { - return { type: "decipherabilityUpdate", value: arg }; -} - -/** - * Construct a "manifestReady" event. - * @param {Object} manifest - * @returns {Object} - */ -function manifestReady( - manifest : Manifest -) : IManifestReadyEvent { - return { type: "manifestReady", value: { manifest } }; -} - -/** - * Construct a "manifestUpdate" event. - * @returns {Object} - */ -function manifestUpdate() : IManifestUpdateEvent { - return { type: "manifestUpdate", value: null }; -} - -/** - * Construct a "representationChange" event. - * @param {string} type - * @param {Object} period - * @returns {Object} - */ -function nullRepresentation( - type : IBufferType, - period : Period -) : IRepresentationChangeEvent { - return { type: "representationChange", - value: { type, - representation: null, - period } }; -} - -/** - * construct a "warning" event. - * @param {error} value - * @returns {object} - */ -function warning(value : IPlayerError) : IWarningEvent { - return { type: "warning", value }; -} - -/** - * construct a "reloading-media-source" event. - * @returns {object} - */ -function reloadingMediaSource() : IReloadingMediaSourceEvent { - return { type: "reloading-media-source", value: undefined }; -} - -const INIT_EVENTS = { loaded, - decipherabilityUpdate, - manifestReady, - manifestUpdate, - nullRepresentation, - reloadingMediaSource, - stalled, - unstalled, - warning }; - -export default INIT_EVENTS; diff --git a/src/core/init/index.ts b/src/core/init/index.ts index b2964766d0..33479d4ac7 100644 --- a/src/core/init/index.ts +++ b/src/core/init/index.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import InitializeOnMediaSource, { +import MediaSourceContentInitializer, { IInitializeArguments, -} from "./initialize_media_source"; +} from "./media_source_content_initializer"; export * from "./types"; -export default InitializeOnMediaSource; +export default MediaSourceContentInitializer; export { IInitializeArguments }; diff --git a/src/core/init/initial_seek_and_play.ts b/src/core/init/initial_seek_and_play.ts deleted file mode 100644 index c0a1db538a..0000000000 --- a/src/core/init/initial_seek_and_play.ts +++ /dev/null @@ -1,208 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - catchError, - concat as observableConcat, - filter, - map, - mergeMap, - Observable, - of as observableOf, - shareReplay, - startWith, - take, - tap, -} from "rxjs"; -import { - play, - shouldValidateMetadata, - whenLoadedMetadata$, -} from "../../compat"; -import { MediaError } from "../../errors"; -import log from "../../log"; -import { - createSharedReference, - IReadOnlySharedReference, -} from "../../utils/reference"; -import { - IPlaybackObservation, - PlaybackObserver, -} from "../api"; -import EVENTS from "./events_generators"; -import { IWarningEvent } from "./types"; - -/** Event emitted when trying to perform the initial `play`. */ -export type IInitialPlayEvent = - /** Autoplay is not enabled, but all required steps to do so are there. */ - { type: "skipped" } | - /** - * Tried to play, but autoplay is blocked by the browser. - * A corresponding warning should have already been sent. - */ - { type: "autoplay-blocked" } | - /** Autoplay was done with success. */ - { type: "autoplay" } | - /** Warnings preventing the initial play from happening normally. */ - IWarningEvent; - -/** - * Emit once as soon as the playback observation announces that the content can - * begin to be played by calling the `play` method. - * - * This depends on browser-defined criteria (e.g. the readyState status) as well - * as RxPlayer-defined ones (e.g.) not rebuffering. - * - * @param {Observable} observation$ - * @returns {Observable.} - */ -export function waitUntilPlayable( - observation$ : Observable -) : Observable { - return observation$.pipe( - filter(({ seeking, rebuffering, readyState }) => !seeking && - rebuffering === null && - readyState >= 1), - take(1), - map(() => undefined) - ); -} - -/** - * Try to play content then handle autoplay errors. - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function autoPlay( - mediaElement: HTMLMediaElement -): Observable<"autoplay"|"autoplay-blocked"> { - return play(mediaElement).pipe( - map(() => "autoplay" as const), - catchError((error : unknown) => { - if (error instanceof Error && error.name === "NotAllowedError") { - // auto-play was probably prevented. - log.warn("Init: Media element can't play." + - " It may be due to browser auto-play policies."); - return observableOf("autoplay-blocked" as const); - } else { - throw error; - } - }) - ); -} - -/** Object returned by `initialSeekAndPlay`. */ -export interface IInitialSeekAndPlayObject { - /** - * Observable which, when subscribed, will try to seek at the initial position - * then play if needed as soon as the HTMLMediaElement's properties are right. - * - * Emits various events relative to the status of this operation. - */ - seekAndPlay$ : Observable; - - /** - * Shared reference whose value becomes `true` once the initial seek has - * been considered / has been done by `seekAndPlay$`. - */ - initialSeekPerformed : IReadOnlySharedReference; - - /** - * Shared reference whose value becomes `true` once the initial play has - * been considered / has been done by `seekAndPlay$`. - */ - initialPlayPerformed: IReadOnlySharedReference; -} - -/** - * Creates an Observable allowing to seek at the initially wanted position and - * to play if autoPlay is wanted. - * @param {Object} args - * @returns {Object} - */ -export default function initialSeekAndPlay( - { mediaElement, - playbackObserver, - startTime, - mustAutoPlay } : { playbackObserver : PlaybackObserver; - mediaElement : HTMLMediaElement; - mustAutoPlay : boolean; - startTime : number|(() => number); } -) : IInitialSeekAndPlayObject { - const initialSeekPerformed = createSharedReference(false); - const initialPlayPerformed = createSharedReference(false); - - const seek$ = whenLoadedMetadata$(mediaElement).pipe( - take(1), - tap(() => { - const initialTime = typeof startTime === "function" ? startTime() : - startTime; - log.info("Init: Set initial time", initialTime); - playbackObserver.setCurrentTime(initialTime); - initialSeekPerformed.setValue(true); - initialSeekPerformed.finish(); - }), - shareReplay({ refCount: true }) - ); - - const seekAndPlay$ = seek$.pipe( - mergeMap(() : Observable => { - if (!shouldValidateMetadata() || mediaElement.duration > 0) { - return waitUntilPlayable(playbackObserver.getReference().asObservable()); - } else { - const error = new MediaError("MEDIA_ERR_NOT_LOADED_METADATA", - "Cannot load automatically: your browser " + - "falsely announced having loaded the content."); - return waitUntilPlayable(playbackObserver.getReference().asObservable()) - .pipe(startWith(EVENTS.warning(error))); - } - }), - - mergeMap((evt) : Observable => { - if (evt !== undefined) { - return observableOf(evt); - } - log.info("Init: Can begin to play content"); - if (!mustAutoPlay) { - if (mediaElement.autoplay) { - log.warn("Init: autoplay is enabled on HTML media element. " + - "Media will play as soon as possible."); - } - initialPlayPerformed.setValue(true); - initialPlayPerformed.finish(); - return observableOf({ type: "skipped" as const }); - } - return autoPlay(mediaElement).pipe(mergeMap((autoplayEvt) => { - initialPlayPerformed.setValue(true); - initialPlayPerformed.finish(); - if (autoplayEvt === "autoplay") { - return observableOf({ type: "autoplay" as const }); - } else { - const error = new MediaError("MEDIA_ERR_BLOCKED_AUTOPLAY", - "Cannot trigger auto-play automatically: " + - "your browser does not allow it."); - return observableConcat( - observableOf(EVENTS.warning(error)), - observableOf({ type: "autoplay-blocked" as const }) - ); - } - })); - }), - shareReplay({ refCount: true }) - ); - - return { seekAndPlay$, initialPlayPerformed, initialSeekPerformed }; -} diff --git a/src/core/init/initialize_directfile.ts b/src/core/init/initialize_directfile.ts deleted file mode 100644 index cfd8da8694..0000000000 --- a/src/core/init/initialize_directfile.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * /!\ This file is feature-switchable. - * It always should be imported through the `features` object. - */ - -import { - EMPTY, - filter, - ignoreElements, - merge as observableMerge, - mergeMap, - switchMap, - Observable, - of as observableOf, - share, - take, -} from "rxjs"; -import { - clearElementSrc, - setElementSrc$, -} from "../../compat"; -import log from "../../log"; -import { IKeySystemOption } from "../../public_types"; -import deferSubscriptions from "../../utils/defer_subscriptions"; -import { IReadOnlySharedReference } from "../../utils/reference"; -import { PlaybackObserver } from "../api"; -import emitLoadedEvent from "./emit_loaded_event"; -import { IInitialTimeOptions } from "./get_initial_time"; -import initialSeekAndPlay from "./initial_seek_and_play"; -import linkDrmAndContent from "./link_drm_and_content"; -import RebufferingController from "./rebuffering_controller"; -import throwOnMediaError from "./throw_on_media_error"; -import { IDirectfileEvent } from "./types"; - -// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default -// first type parameter as `any` instead of the perfectly fine `unknown`, -// leading to linter issues, as it forbids the usage of `any`. -// This is why we're disabling the eslint rule. -/* eslint-disable @typescript-eslint/no-unsafe-argument */ - -/** - * calculate initial time as a position in seconds. - * @param {HTMLMediaElement} mediaElement - * @param {Object|undefined} startAt - * @returns {number} - */ -function getDirectFileInitialTime( - mediaElement : HTMLMediaElement, - startAt? : IInitialTimeOptions -) : number { - if (startAt == null) { - return 0; - } - - if (startAt.position != null) { - return startAt.position; - } else if (startAt.wallClockTime != null) { - return startAt.wallClockTime; - } else if (startAt.fromFirstPosition != null) { - return startAt.fromFirstPosition; - } - - const duration = mediaElement.duration; - if (duration == null || !isFinite(duration)) { - log.warn("startAt.fromLastPosition set but no known duration, " + - "beginning at 0."); - return 0; - } - - if (typeof startAt.fromLastPosition === "number") { - return Math.max(0, duration + startAt.fromLastPosition); - } else if (startAt.percentage != null) { - const { percentage } = startAt; - if (percentage >= 100) { - return duration; - } else if (percentage <= 0) { - return 0; - } - const ratio = +percentage / 100; - return duration * ratio; - } - - return 0; -} - -// Argument used by `initializeDirectfileContent` -export interface IDirectFileOptions { autoPlay : boolean; - keySystems : IKeySystemOption[]; - mediaElement : HTMLMediaElement; - playbackObserver : PlaybackObserver; - speed : IReadOnlySharedReference; - startAt? : IInitialTimeOptions | undefined; - url? : string | undefined; } - -/** - * Launch a content in "Directfile mode". - * @param {Object} directfileOptions - * @returns {Observable} - */ -export default function initializeDirectfileContent({ - autoPlay, - keySystems, - mediaElement, - playbackObserver, - speed, - startAt, - url, -} : IDirectFileOptions) : Observable { - - clearElementSrc(mediaElement); - - if (url == null) { - throw new Error("No URL for a DirectFile content"); - } - - // Start everything! (Just put the URL in the element's src). - const linkURL$ = setElementSrc$(mediaElement, url); - - const initialTime = () => { - log.debug("Init: Calculating initial time"); - const initTime = getDirectFileInitialTime(mediaElement, startAt); - log.debug("Init: Initial time calculated:", initTime); - return initTime; - }; - - const { seekAndPlay$ } = initialSeekAndPlay({ mediaElement, - playbackObserver, - startTime: initialTime, - mustAutoPlay: autoPlay }); - - /** Initialize decryption capabilities and the HTMLMediaElement's src attribute. */ - const drmEvents$ = linkDrmAndContent(mediaElement, - keySystems, - EMPTY, - linkURL$).pipe( - deferSubscriptions(), - share() - ); - - // Translate errors coming from the media element into RxPlayer errors - // through a throwing Observable. - const mediaError$ = throwOnMediaError(mediaElement); - - const observation$ = playbackObserver.getReference().asObservable(); - - /** - * Observable trying to avoid various stalling situations, emitting "stalled" - * events when it cannot, as well as "unstalled" events when it get out of one. - */ - const rebuffer$ = RebufferingController(playbackObserver, null, speed, EMPTY, EMPTY); - - /** - * Emit a "loaded" events once the initial play has been performed and the - * media can begin playback. - * Also emits warning events if issues arise when doing so. - */ - const loadingEvts$ = drmEvents$.pipe( - filter((evt) => evt.type === "decryption-ready" || - evt.type === "decryption-disabled"), - take(1), - mergeMap(() => seekAndPlay$), - switchMap((evt) => { - if (evt.type === "warning") { - return observableOf(evt); - } - return emitLoadedEvent(observation$, mediaElement, null, true); - })); - - return observableMerge(loadingEvts$, - drmEvents$.pipe(ignoreElements()), - mediaError$, - rebuffer$); -} - -export { IDirectfileEvent }; diff --git a/src/core/init/initialize_media_source.ts b/src/core/init/initialize_media_source.ts deleted file mode 100644 index e159f334c8..0000000000 --- a/src/core/init/initialize_media_source.ts +++ /dev/null @@ -1,389 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - combineLatest as observableCombineLatest, - filter, - finalize, - ignoreElements, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - share, - shareReplay, - startWith, - Subject, - switchMap, - take, - takeUntil, -} from "rxjs"; -import { shouldReloadMediaSourceOnDecipherabilityUpdate } from "../../compat"; -import config from "../../config"; -import log from "../../log"; -import { IKeySystemOption } from "../../public_types"; -import { ITransportPipelines } from "../../transports"; -import deferSubscriptions from "../../utils/defer_subscriptions"; -import { fromEvent } from "../../utils/event_emitter"; -import filterMap from "../../utils/filter_map"; -import objectAssign from "../../utils/object_assign"; -import { IReadOnlySharedReference } from "../../utils/reference"; -import TaskCanceller from "../../utils/task_canceller"; -import AdaptiveRepresentationSelector, { - IAdaptiveRepresentationSelectorArguments, -} from "../adaptive"; -import { PlaybackObserver } from "../api"; -import { - getCurrentKeySystem, - IContentProtection, -} from "../decrypt"; -import { - IManifestFetcherParsedResult, - IManifestFetcherWarningEvent, - ManifestFetcher, - SegmentFetcherCreator, -} from "../fetchers"; -import { ITextTrackSegmentBufferOptions } from "../segment_buffers"; -import { IAudioTrackSwitchingMode } from "../stream"; -import openMediaSource from "./create_media_source"; -import EVENTS from "./events_generators"; -import getInitialTime, { - IInitialTimeOptions, -} from "./get_initial_time"; -import linkDrmAndContent, { - IDecryptionDisabledEvent, - IDecryptionReadyEvent, -} from "./link_drm_and_content"; -import createMediaSourceLoader from "./load_on_media_source"; -import manifestUpdateScheduler, { - IManifestRefreshSchedulerEvent, -} from "./manifest_update_scheduler"; -import throwOnMediaError from "./throw_on_media_error"; -import { - IInitEvent, - IMediaSourceLoaderEvent, -} from "./types"; - -// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default -// first type parameter as `any` instead of the perfectly fine `unknown`, -// leading to linter issues, as it forbids the usage of `any`. -// This is why we're disabling the eslint rule. -/* eslint-disable @typescript-eslint/no-unsafe-argument */ - -/** Arguments to give to the `InitializeOnMediaSource` function. */ -export interface IInitializeArguments { - /** Options concerning the ABR logic. */ - adaptiveOptions: IAdaptiveRepresentationSelectorArguments; - /** `true` if we should play when loaded. */ - autoPlay : boolean; - /** Options concerning the media buffers. */ - bufferOptions : { - /** Buffer "goal" at which we stop downloading new segments. */ - wantedBufferAhead : IReadOnlySharedReference; - /** Buffer maximum size in kiloBytes at which we stop downloading */ - maxVideoBufferSize : IReadOnlySharedReference; - /** Max buffer size after the current position, in seconds (we GC further up). */ - maxBufferAhead : IReadOnlySharedReference; - /** Max buffer size before the current position, in seconds (we GC further down). */ - maxBufferBehind : IReadOnlySharedReference; - /** Strategy when switching the current bitrate manually (smooth vs reload). */ - manualBitrateSwitchingMode : "seamless" | "direct"; - /** - * Enable/Disable fastSwitching: allow to replace lower-quality segments by - * higher-quality ones to have a faster transition. - */ - enableFastSwitching : boolean; - /** Strategy when switching of audio track. */ - audioTrackSwitchingMode : IAudioTrackSwitchingMode; - /** Behavior when a new video and/or audio codec is encountered. */ - onCodecSwitch : "continue" | "reload"; - }; - /** Regularly emit current playback conditions. */ - playbackObserver : PlaybackObserver; - /** Every encryption configuration set. */ - keySystems : IKeySystemOption[]; - /** `true` to play low-latency contents optimally. */ - lowLatencyMode : boolean; - /** Initial Manifest value. */ - manifest$ : Observable; - /** Interface allowing to load and refresh the Manifest */ - manifestFetcher : ManifestFetcher; -/** The HTMLMediaElement on which we will play. */ - mediaElement : HTMLMediaElement; - /** Limit the frequency of Manifest updates. */ - minimumManifestUpdateInterval : number; - /** Interface allowing to interact with the transport protocol */ - transport : ITransportPipelines; - /** Configuration for the segment requesting logic. */ - segmentRequestOptions : { - /** Maximum number of time a request on error will be retried. */ - regularError : number | undefined; - /** Maximum number of time a request be retried when the user is offline. */ - offlineError : number | undefined; - /** - * Amount of time after which a request should be aborted. - * `undefined` indicates that a default value is wanted. - * `-1` indicates no timeout. - */ - requestTimeout : number | undefined; - }; - /** Emit the playback rate (speed) set by the user. */ - speed : IReadOnlySharedReference; - /** The configured starting position. */ - startAt? : IInitialTimeOptions | undefined; - /** Configuration specific to the text track. */ - textTrackOptions : ITextTrackSegmentBufferOptions; -} - -/** - * Begin content playback. - * - * Returns an Observable emitting notifications about the content lifecycle. - * On subscription, it will perform every necessary tasks so the content can - * play. Among them: - * - * - Creates a MediaSource on the given `mediaElement` and attach to it the - * necessary SourceBuffer instances. - * - * - download the content's Manifest and handle its refresh logic - * - * - Perform decryption if needed - * - * - ask for the choice of the wanted Adaptation through events (e.g. to - * choose a language) - * - * - requests and push the right segments (according to the Adaptation choice, - * the current position, the network conditions etc.) - * - * This Observable will throw in the case where a fatal error (i.e. which has - * stopped content playback) is encountered, with the corresponding error as a - * payload. - * - * This Observable will never complete, it will always run until it is - * unsubscribed from. - * Unsubscription will stop playback and reset the corresponding state. - * - * @param {Object} args - * @returns {Observable} - */ -export default function InitializeOnMediaSource( - { adaptiveOptions, - autoPlay, - bufferOptions, - keySystems, - lowLatencyMode, - manifest$, - manifestFetcher, - mediaElement, - minimumManifestUpdateInterval, - playbackObserver, - segmentRequestOptions, - speed, - startAt, - transport, - textTrackOptions } : IInitializeArguments -) : Observable { - /** Choose the right "Representation" for a given "Adaptation". */ - const representationEstimator = AdaptiveRepresentationSelector(adaptiveOptions); - - const playbackCanceller = new TaskCanceller(); - - /** - * Create and open a new MediaSource object on the given media element on - * subscription. - * Multiple concurrent subscriptions on this Observable will obtain the same - * created MediaSource. - * The MediaSource will be closed when subscriptions are down to 0. - */ - const openMediaSource$ = openMediaSource(mediaElement).pipe( - shareReplay({ refCount: true }) - ); - - /** Send content protection initialization data. */ - const protectedSegments$ = new Subject(); - - /** Initialize decryption capabilities and MediaSource. */ - const drmEvents$ = linkDrmAndContent(mediaElement, - keySystems, - protectedSegments$, - openMediaSource$) - .pipe( - // Because multiple Observables here depend on this Observable as a source, - // we prefer deferring Subscription until those Observables are themselves - // all subscribed to. - // This is needed because `drmEvents$` might send events synchronously - // on subscription. In that case, it might communicate those events directly - // after the first Subscription is done, making the next subscription miss - // out on those events, even if that second subscription is done - // synchronously after the first one. - // By calling `deferSubscriptions`, we ensure that subscription to - // `drmEvents$` effectively starts after a very short delay, thus - // ensuring that no such race condition can occur. - deferSubscriptions(), - share()); - - /** - * Translate errors coming from the media element into RxPlayer errors - * through a throwing Observable. - */ - const mediaError$ = throwOnMediaError(mediaElement); - - const mediaSourceReady$ = drmEvents$.pipe( - filter((evt) : evt is IDecryptionReadyEvent | - IDecryptionDisabledEvent => - evt.type === "decryption-ready" || evt.type === "decryption-disabled"), - map(e => e.value), - take(1)); - - /** Load and play the content asked. */ - const loadContent$ = observableCombineLatest([manifest$, mediaSourceReady$]).pipe( - mergeMap(([manifestEvt, { drmSystemId, mediaSource: initialMediaSource } ]) => { - if (manifestEvt.type === "warning") { - return observableOf(manifestEvt); - } - const { manifest } = manifestEvt; - - log.debug("Init: Calculating initial time"); - const initialTime = getInitialTime(manifest, lowLatencyMode, startAt); - log.debug("Init: Initial time calculated:", initialTime); - - const requestOptions = { lowLatencyMode, - requestTimeout: segmentRequestOptions.requestTimeout, - maxRetryRegular: segmentRequestOptions.regularError, - maxRetryOffline: segmentRequestOptions.offlineError }; - const segmentFetcherCreator = new SegmentFetcherCreator(transport, - requestOptions, - playbackCanceller.signal); - - const mediaSourceLoader = createMediaSourceLoader({ - bufferOptions: objectAssign({ textTrackOptions, drmSystemId }, - bufferOptions), - manifest, - mediaElement, - playbackObserver, - representationEstimator, - segmentFetcherCreator, - speed, - }); - - // handle initial load and reloads - const recursiveLoad$ = recursivelyLoadOnMediaSource(initialMediaSource, - initialTime, - autoPlay); - - // Emit when we want to manually update the manifest. - const scheduleRefresh$ = new Subject(); - - const manifestUpdate$ = manifestUpdateScheduler({ initialManifest: manifestEvt, - manifestFetcher, - minimumManifestUpdateInterval, - scheduleRefresh$ }); - - const manifestEvents$ = observableMerge( - fromEvent(manifest, "manifestUpdate") - .pipe(map(() => EVENTS.manifestUpdate())), - fromEvent(manifest, "decipherabilityUpdate") - .pipe(map(EVENTS.decipherabilityUpdate))); - - return observableMerge(manifestEvents$, - manifestUpdate$, - recursiveLoad$) - .pipe(startWith(EVENTS.manifestReady(manifest)), - finalize(() => { scheduleRefresh$.complete(); })); - - /** - * Load the content defined by the Manifest in the mediaSource given at the - * given position and playing status. - * This function recursively re-call itself when a MediaSource reload is - * wanted. - * @param {MediaSource} mediaSource - * @param {number} startingPos - * @param {boolean} shouldPlay - * @returns {Observable} - */ - function recursivelyLoadOnMediaSource( - mediaSource : MediaSource, - startingPos : number, - shouldPlay : boolean - ) : Observable { - const reloadMediaSource$ = new Subject<{ position : number; - autoPlay : boolean; }>(); - const mediaSourceLoader$ = mediaSourceLoader(mediaSource, startingPos, shouldPlay) - .pipe(filterMap((evt) => { - switch (evt.type) { - case "needs-manifest-refresh": - scheduleRefresh$.next({ completeRefresh: false, - canUseUnsafeMode: true }); - return null; - case "manifest-might-be-out-of-sync": - const { OUT_OF_SYNC_MANIFEST_REFRESH_DELAY } = config.getCurrent(); - scheduleRefresh$.next({ - completeRefresh: true, - canUseUnsafeMode: false, - delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY, - }); - return null; - case "needs-media-source-reload": - reloadMediaSource$.next(evt.value); - return null; - case "needs-decipherability-flush": - const keySystem = getCurrentKeySystem(mediaElement); - if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem)) { - reloadMediaSource$.next(evt.value); - return null; - } - - // simple seek close to the current position - // to flush the buffers - const { position } = evt.value; - if (position + 0.001 < evt.value.duration) { - playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001); - } else { - playbackObserver.setCurrentTime(position); - } - return null; - case "encryption-data-encountered": - protectedSegments$.next(evt.value); - return null; - case "needs-buffer-flush": - playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001); - return null; - } - return evt; - }, null)); - - const currentLoad$ = - mediaSourceLoader$.pipe(takeUntil(reloadMediaSource$)); - - const handleReloads$ = reloadMediaSource$.pipe( - switchMap((reloadOrder) => { - return openMediaSource(mediaElement).pipe( - mergeMap(newMS => recursivelyLoadOnMediaSource(newMS, - reloadOrder.position, - reloadOrder.autoPlay)), - startWith(EVENTS.reloadingMediaSource()) - ); - })); - - return observableMerge(handleReloads$, currentLoad$); - } - })); - - return observableMerge(loadContent$, mediaError$, drmEvents$.pipe(ignoreElements())) - .pipe(finalize(() => { playbackCanceller.cancel(); })); -} diff --git a/src/core/init/link_drm_and_content.ts b/src/core/init/link_drm_and_content.ts deleted file mode 100644 index 9105d41119..0000000000 --- a/src/core/init/link_drm_and_content.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - map, - merge as observableMerge, - Observable, - Subscription, -} from "rxjs"; -import { - events, - hasEMEAPIs, -} from "../../compat/"; -import { EncryptedMediaError } from "../../errors"; -import features from "../../features"; -import log from "../../log"; -import { IKeySystemOption } from "../../public_types"; -import { - IContentProtection, - ContentDecryptorState, -} from "../decrypt"; -import { IWarningEvent } from "./types"; - -const { onEncrypted$ } = events; - -/** - * @param {HTMLMediaElement} mediaElement - * @param {Array.} keySystems - * @param {Observable} contentProtections$ - * @param {Promise} linkingMedia$ - * @returns {Observable} - */ -export default function linkDrmAndContent( - mediaElement : HTMLMediaElement, - keySystems : IKeySystemOption[], - contentProtections$ : Observable, - linkingMedia$ : Observable -) : Observable> { - const encryptedEvents$ = observableMerge(onEncrypted$(mediaElement), - contentProtections$); - if (features.ContentDecryptor == null) { - return observableMerge( - encryptedEvents$.pipe(map(() => { - log.error("Init: Encrypted event but EME feature not activated"); - throw new EncryptedMediaError("MEDIA_IS_ENCRYPTED_ERROR", - "EME feature not activated."); - })), - linkingMedia$.pipe(map(mediaSource => ({ - type: "decryption-disabled" as const, - value: { drmSystemId: undefined, mediaSource }, - })))); - } - - if (keySystems.length === 0) { - return observableMerge( - encryptedEvents$.pipe(map(() => { - log.error("Init: Ciphered media and no keySystem passed"); - throw new EncryptedMediaError("MEDIA_IS_ENCRYPTED_ERROR", - "Media is encrypted and no `keySystems` given"); - })), - linkingMedia$.pipe(map(mediaSource => ({ - type: "decryption-disabled" as const, - value: { drmSystemId: undefined, mediaSource }, - })))); - } - - if (!hasEMEAPIs()) { - return observableMerge( - encryptedEvents$.pipe(map(() => { - log.error("Init: Encrypted event but no EME API available"); - throw new EncryptedMediaError("MEDIA_IS_ENCRYPTED_ERROR", - "Encryption APIs not found."); - })), - linkingMedia$.pipe(map(mediaSource => ({ - type: "decryption-disabled" as const, - value: { drmSystemId: undefined, mediaSource }, - })))); - } - - log.debug("Init: Creating ContentDecryptor"); - const ContentDecryptor = features.ContentDecryptor; - return new Observable((obs) => { - const contentDecryptor = new ContentDecryptor(mediaElement, keySystems); - - let mediaSub : Subscription | undefined; - contentDecryptor.addEventListener("stateChange", (state) => { - if (state === ContentDecryptorState.WaitingForAttachment) { - contentDecryptor.removeEventListener("stateChange"); - - mediaSub = linkingMedia$.subscribe(mediaSource => { - contentDecryptor.addEventListener("stateChange", (newState) => { - if (newState === ContentDecryptorState.ReadyForContent) { - obs.next({ type: "decryption-ready", - value: { drmSystemId: contentDecryptor.systemId, - mediaSource } }); - contentDecryptor.removeEventListener("stateChange"); - } - }); - - contentDecryptor.attach(); - }); - } - }); - - contentDecryptor.addEventListener("error", (e) => { - obs.error(e); - }); - - contentDecryptor.addEventListener("warning", (w) => { - obs.next({ type: "warning", value: w }); - }); - - const protectionDataSub = contentProtections$.subscribe(data => { - contentDecryptor.onInitializationData(data); - }); - - return () => { - protectionDataSub.unsubscribe(); - mediaSub?.unsubscribe(); - contentDecryptor.dispose(); - }; - }); -} - -export type IContentDecryptorInitEvent = IDecryptionDisabledEvent | - IDecryptionReadyEvent | - IWarningEvent; - -/** - * Event emitted after deciding that no decryption logic will be launched for - * the current content. - */ -export interface IDecryptionDisabledEvent { - type: "decryption-disabled"; - value: { - /** - * Identify the current DRM's system ID. - * Here `undefined` as no decryption capability has been added. - */ - drmSystemId: undefined; - /** The value outputed by the `linkingMedia$` Observable. */ - mediaSource: T; - }; -} - -/** - * Event emitted when decryption capabilities have started and content can - * begin to be pushed on the HTMLMediaElement. - */ -export interface IDecryptionReadyEvent { - type: "decryption-ready"; - value: { - /** - * Identify the current DRM's systemId as an hexadecimal string, so the - * RxPlayer may be able to (optionally) only send the corresponding - * encryption initialization data. - * `undefined` if unknown. - */ - drmSystemId: string | undefined; - /** The value outputed by the `linkingMedia$` Observable. */ - mediaSource: T; - }; -} diff --git a/src/core/init/load_on_media_source.ts b/src/core/init/load_on_media_source.ts deleted file mode 100644 index 8f852bf474..0000000000 --- a/src/core/init/load_on_media_source.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - EMPTY, - filter, - finalize, - ignoreElements, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - Subject, - switchMap, - takeUntil, - throwError, -} from "rxjs"; -import { MediaError } from "../../errors"; -import log from "../../log"; -import Manifest from "../../manifest"; -import createSharedReference, { IReadOnlySharedReference } from "../../utils/reference"; -import { IRepresentationEstimator } from "../adaptive"; -import { PlaybackObserver } from "../api"; -import { SegmentFetcherCreator } from "../fetchers"; -import SegmentBuffersStore from "../segment_buffers"; -import StreamOrchestrator, { - IAdaptationChangeEvent, - IStreamOrchestratorOptions, -} from "../stream"; -import ContentTimeBoundariesObserver from "./content_time_boundaries_observer"; -import createStreamPlaybackObserver from "./create_stream_playback_observer"; -import emitLoadedEvent from "./emit_loaded_event"; -import { maintainEndOfStream } from "./end_of_stream"; -import initialSeekAndPlay from "./initial_seek_and_play"; -import MediaDurationUpdater from "./media_duration_updater"; -import RebufferingController, { - IDiscontinuityEvent, - ILockedStreamEvent, -} from "./rebuffering_controller"; -import streamEventsEmitter from "./stream_events_emitter"; -import { IMediaSourceLoaderEvent } from "./types"; - -// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default -// first type parameter as `any` instead of the perfectly fine `unknown`, -// leading to linter issues, as it forbids the usage of `any`. -// This is why we're disabling the eslint rule. -/* eslint-disable @typescript-eslint/no-unsafe-argument */ - -/** Arguments needed by `createMediaSourceLoader`. */ -export interface IMediaSourceLoaderArguments { - /** Various stream-related options. */ - bufferOptions : IStreamOrchestratorOptions; - /* Manifest of the content we want to play. */ - manifest : Manifest; - /** Media Element on which the content will be played. */ - mediaElement : HTMLMediaElement; - /** Emit playback conditions regularly. */ - playbackObserver : PlaybackObserver; - /** Estimate the right Representation. */ - representationEstimator : IRepresentationEstimator; - /** Module to facilitate segment fetching. */ - segmentFetcherCreator : SegmentFetcherCreator; - /** Last wanted playback rate. */ - speed : IReadOnlySharedReference; -} - -/** - * Returns a function allowing to load or reload the content in arguments into - * a single or multiple MediaSources. - * @param {Object} args - * @returns {Function} - */ -export default function createMediaSourceLoader( - { mediaElement, - manifest, - speed, - bufferOptions, - representationEstimator, - playbackObserver, - segmentFetcherCreator } : IMediaSourceLoaderArguments -) : (mediaSource : MediaSource, initialTime : number, autoPlay : boolean) => - Observable { - /** - * Load the content on the given MediaSource. - * @param {MediaSource} mediaSource - * @param {number} initialTime - * @param {boolean} autoPlay - */ - return function loadContentOnMediaSource( - mediaSource : MediaSource, - initialTime : number, - autoPlay : boolean - ) : Observable { - /** Maintains the MediaSource's duration up-to-date with the Manifest */ - const mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource); - - const initialPeriod = manifest.getPeriodForTime(initialTime) ?? - manifest.getNextPeriod(initialTime); - if (initialPeriod === undefined) { - const error = new MediaError("MEDIA_STARTING_TIME_NOT_FOUND", - "Wanted starting time not found in the Manifest."); - return throwError(() => error); - } - - /** Interface to create media buffers. */ - const segmentBuffersStore = new SegmentBuffersStore(mediaElement, mediaSource); - - const { seekAndPlay$, - initialPlayPerformed, - initialSeekPerformed } = initialSeekAndPlay({ mediaElement, - playbackObserver, - startTime: initialTime, - mustAutoPlay: autoPlay }); - - const observation$ = playbackObserver.getReference().asObservable(); - const streamEvents$ = initialPlayPerformed.asObservable().pipe( - filter((hasPlayed) => hasPlayed), - mergeMap(() => streamEventsEmitter(manifest, mediaElement, observation$))); - - const streamObserver = createStreamPlaybackObserver(manifest, - playbackObserver, - { autoPlay, - initialPlayPerformed, - initialSeekPerformed, - speed, - startTime: initialTime }); - - /** Cancel endOfStream calls when streams become active again. */ - const cancelEndOfStream$ = new Subject(); - - /** Emits discontinuities detected by the StreamOrchestrator. */ - const discontinuityUpdate$ = new Subject(); - - /** Emits event when streams are "locked", meaning they cannot load segments. */ - const lockedStream$ = new Subject(); - - /** Emit each time a new Adaptation is considered by the `StreamOrchestrator`. */ - const lastAdaptationChange = createSharedReference< - IAdaptationChangeEvent | null - >(null); - - // Creates Observable which will manage every Stream for the given Content. - const streams$ = StreamOrchestrator({ manifest, initialPeriod }, - streamObserver, - representationEstimator, - segmentBuffersStore, - segmentFetcherCreator, - bufferOptions - ).pipe( - mergeMap((evt) => { - switch (evt.type) { - case "end-of-stream": - log.debug("Init: end-of-stream order received."); - return maintainEndOfStream(mediaSource).pipe( - ignoreElements(), - takeUntil(cancelEndOfStream$)); - case "resume-stream": - log.debug("Init: resume-stream order received."); - cancelEndOfStream$.next(null); - return EMPTY; - case "stream-status": - const { period, bufferType, imminentDiscontinuity, position } = evt.value; - discontinuityUpdate$.next({ period, - bufferType, - discontinuity: imminentDiscontinuity, - position }); - return EMPTY; - case "locked-stream": - lockedStream$.next(evt.value); - return EMPTY; - case "adaptationChange": - lastAdaptationChange.setValue(evt); - return observableOf(evt); - default: - return observableOf(evt); - } - }) - ); - - const contentTimeObserver = ContentTimeBoundariesObserver(manifest, - lastAdaptationChange, - streamObserver) - .pipe( - mergeMap((evt) => { - if (evt.type === "contentDurationUpdate") { - log.debug("Init: Duration has to be updated.", evt.value); - mediaDurationUpdater.updateKnownDuration(evt.value); - return EMPTY; - } - return observableOf(evt); - })); - - /** - * Observable trying to avoid various stalling situations, emitting "stalled" - * events when it cannot, as well as "unstalled" events when it get out of one. - */ - const rebuffer$ = RebufferingController(playbackObserver, - manifest, - speed, - lockedStream$, - discontinuityUpdate$); - - /** - * Emit a "loaded" events once the initial play has been performed and the - * media can begin playback. - * Also emits warning events if issues arise when doing so. - */ - const loadingEvts$ = seekAndPlay$.pipe(switchMap((evt) => - evt.type === "warning" ? - observableOf(evt) : - emitLoadedEvent(observation$, mediaElement, segmentBuffersStore, false))); - - return observableMerge(loadingEvts$, - rebuffer$, - streams$, - contentTimeObserver, - streamEvents$ - ).pipe(finalize(() => { - mediaDurationUpdater.stop(); - // clean-up every created SegmentBuffers - segmentBuffersStore.disposeAll(); - })); - }; -} diff --git a/src/core/init/manifest_update_scheduler.ts b/src/core/init/manifest_update_scheduler.ts deleted file mode 100644 index 04b091617c..0000000000 --- a/src/core/init/manifest_update_scheduler.ts +++ /dev/null @@ -1,362 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - defer as observableDefer, - EMPTY, - from as observableFrom, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - share, - take, - timer as observableTimer, -} from "rxjs"; -import config from "../../config"; -import log from "../../log"; -import Manifest from "../../manifest"; -import throttle from "../../utils/rx-throttle"; -import { - IManifestFetcherParsedResult, - IManifestFetcherParserOptions, - ManifestFetcher, -} from "../fetchers"; -import { IWarningEvent } from "./types"; - - -/** Arguments to give to the `manifestUpdateScheduler` */ -export interface IManifestUpdateSchedulerArguments { - /** Interface allowing to refresh the Manifest */ - manifestFetcher : ManifestFetcher; - /** Information about the initial load of the manifest */ - initialManifest : { manifest : Manifest; - sendingTime? : number | undefined; - receivedTime? : number | undefined; - parsingTime? : number | undefined; }; - /** Minimum interval to keep between Manifest updates */ - minimumManifestUpdateInterval : number; - /** Allows the rest of the code to ask for a Manifest refresh */ - scheduleRefresh$ : IManifestRefreshScheduler; -} - -/** Function defined to refresh the Manifest */ -export type IManifestFetcher = - (manifestURL : string | undefined, options : IManifestFetcherParserOptions) => - Observable; - -/** Events sent by the `IManifestRefreshScheduler` Observable */ -export interface IManifestRefreshSchedulerEvent { - /** - * if `true`, the Manifest should be fully updated. - * if `false`, a shorter version with just the added information can be loaded - * instead. - */ - completeRefresh : boolean; - /** - * Optional wanted refresh delay, which is the minimum time you want to wait - * before updating the Manifest - */ - delay? : number | undefined; - /** - * Whether the parsing can be done in the more efficient "unsafeMode". - * This mode is extremely fast but can lead to de-synchronisation with the - * server. - */ - canUseUnsafeMode : boolean; -} - -/** Observable to send events related to refresh requests coming from the Player. */ -export type IManifestRefreshScheduler = Observable; - -/** - * Refresh the Manifest at the right time. - * @param {Object} manifestUpdateSchedulerArguments - * @returns {Observable} - */ -export default function manifestUpdateScheduler({ - initialManifest, - manifestFetcher, - minimumManifestUpdateInterval, - scheduleRefresh$, -} : IManifestUpdateSchedulerArguments) : Observable { - /** - * Fetch and parse the manifest from the URL given. - * Throttled to avoid doing multiple simultaneous requests. - */ - const fetchManifest = throttle( - (manifestURL : string | undefined, options : IManifestFetcherParserOptions) - : Observable => - manifestFetcher.fetch(manifestURL).pipe( - mergeMap((response) => response.type === "warning" ? - observableOf(response) : // bubble-up warnings - response.parse(options)), - share())); - - // The Manifest always keeps the same reference - const { manifest } = initialManifest; - - /** Number of consecutive times the parsing has been done in `unsafeMode`. */ - let consecutiveUnsafeMode = 0; - - return observableDefer(() => handleManifestRefresh$(initialManifest)); - - /** - * Performs Manifest refresh (recursively) when it judges it is time to do so. - * @param {Object} manifestRequestInfos - Various information linked to the - * Manifest loading and parsing operations. - * @returns {Observable} - Observable which will automatically refresh the - * Manifest on subscription. Can also emit warnings when minor errors are - * encountered. - */ - function handleManifestRefresh$( - { sendingTime, parsingTime, updatingTime } : { sendingTime?: number | undefined; - parsingTime? : number | undefined; - updatingTime? : number | undefined; } - ) : Observable { - /** - * Total time taken to fully update the last Manifest, in milliseconds. - * Note: this time also includes possible requests done by the parsers. - */ - const totalUpdateTime = parsingTime !== undefined ? - parsingTime + (updatingTime ?? 0) : - undefined; - - const { MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE, - MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE } = config.getCurrent(); - - /** - * "unsafeMode" is a mode where we unlock advanced Manifest parsing - * optimizations with the added risk to lose some information. - * `unsafeModeEnabled` is set to `true` when the `unsafeMode` is enabled. - * - * Only perform parsing in `unsafeMode` when the last full parsing took a - * lot of time and do not go higher than the maximum consecutive time. - */ - - const unsafeModeEnabled = consecutiveUnsafeMode > 0 ? - consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE : - totalUpdateTime !== undefined ? - (totalUpdateTime >= MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE) : - false; - - /** Time elapsed since the beginning of the Manifest request, in milliseconds. */ - const timeSinceRequest = sendingTime === undefined ? 0 : - performance.now() - sendingTime; - - /** Minimum update delay we should not go below, in milliseconds. */ - const minInterval = Math.max(minimumManifestUpdateInterval - timeSinceRequest, 0); - - /** Emit when the RxPlayer determined that a refresh should be done. */ - const internalRefresh$ = scheduleRefresh$ - .pipe(mergeMap(({ completeRefresh, delay, canUseUnsafeMode }) => { - const unsafeMode = canUseUnsafeMode && unsafeModeEnabled; - return startManualRefreshTimer(delay ?? 0, - minimumManifestUpdateInterval, - sendingTime) - .pipe(map(() => ({ completeRefresh, unsafeMode }))); - })); - - /** Emit when the Manifest tells us that it has "expired". */ - const expired$ = manifest.expired === null ? - EMPTY : - observableTimer(minInterval).pipe( - mergeMap(() => - manifest.expired === null ? EMPTY : - observableFrom(manifest.expired)), - map(() => ({ completeRefresh: true, unsafeMode: unsafeModeEnabled }))); - - /** Emit when the Manifest should normally be refreshed. */ - const autoRefresh$ = createAutoRefreshObservable(); - - return observableMerge(autoRefresh$, internalRefresh$, expired$).pipe( - take(1), - mergeMap(({ completeRefresh, - unsafeMode }) => refreshManifest({ completeRefresh, - unsafeMode })), - mergeMap(evt => { - if (evt.type === "warning") { - return observableOf(evt); - } - return handleManifestRefresh$(evt); - })); - - /** - * Create an Observable that will emit when the Manifest needs to be - * refreshed according to the Manifest's internal properties (parsing - * time is also taken into account in this operation to avoid refreshing too - * often). - * @returns {Observable} - */ - function createAutoRefreshObservable() : Observable<{ - completeRefresh: boolean; - unsafeMode: boolean; - }> { - if (manifest.lifetime === undefined || manifest.lifetime < 0) { - return EMPTY; - } - - /** Regular refresh delay as asked by the Manifest. */ - const regularRefreshDelay = manifest.lifetime * 1000 - timeSinceRequest; - - /** Actually choosen delay to refresh the Manifest. */ - let actualRefreshInterval : number; - - if (totalUpdateTime === undefined) { - actualRefreshInterval = regularRefreshDelay; - } else if (manifest.lifetime < 3 && totalUpdateTime >= 100) { - // If Manifest update is very frequent and we take time to update it, - // postpone it. - actualRefreshInterval = Math.min( - Math.max( - // Take 3 seconds as a default safe value for a base interval. - 3000 - timeSinceRequest, - // Add update time to the original interval. - Math.max(regularRefreshDelay, 0) + totalUpdateTime - ), - - // Limit the postponment's higher bound to a very high value relative - // to `regularRefreshDelay`. - // This avoid perpetually postponing a Manifest update when - // performance seems to have been abysmal one time. - regularRefreshDelay * 6 - ); - log.info("MUS: Manifest update rythm is too frequent. Postponing next request.", - regularRefreshDelay, - actualRefreshInterval); - } else if (totalUpdateTime >= (manifest.lifetime * 1000) / 10) { - // If Manifest updating time is very long relative to its lifetime, - // postpone it: - actualRefreshInterval = Math.min( - // Just add the update time to the original waiting time - Math.max(regularRefreshDelay, 0) + totalUpdateTime, - - // Limit the postponment's higher bound to a very high value relative - // to `regularRefreshDelay`. - // This avoid perpetually postponing a Manifest update when - // performance seems to have been abysmal one time. - regularRefreshDelay * 6); - log.info("MUS: Manifest took too long to parse. Postponing next request", - actualRefreshInterval, - actualRefreshInterval); - } else { - actualRefreshInterval = regularRefreshDelay; - } - return observableTimer(Math.max(actualRefreshInterval, minInterval)) - .pipe(map(() => ({ completeRefresh: false, unsafeMode: unsafeModeEnabled }))); - } - } - - /** - * Refresh the Manifest. - * Perform a full update if a partial update failed. - * @param {boolean} completeRefresh - * @returns {Observable} - */ - function refreshManifest( - { completeRefresh, - unsafeMode } : { completeRefresh : boolean; - unsafeMode : boolean; } - ) : Observable { - const manifestUpdateUrl = manifest.updateUrl; - const fullRefresh = completeRefresh || manifestUpdateUrl === undefined; - const refreshURL = fullRefresh ? manifest.getUrl() : - manifestUpdateUrl; - const externalClockOffset = manifest.clockOffset; - - if (unsafeMode) { - consecutiveUnsafeMode += 1; - log.info("Init: Refreshing the Manifest in \"unsafeMode\" for the " + - String(consecutiveUnsafeMode) + " consecutive time."); - } else if (consecutiveUnsafeMode > 0) { - log.info("Init: Not parsing the Manifest in \"unsafeMode\" anymore after " + - String(consecutiveUnsafeMode) + " consecutive times."); - consecutiveUnsafeMode = 0; - } - return fetchManifest(refreshURL, { externalClockOffset, - previousManifest: manifest, - unsafeMode }) - .pipe(mergeMap((value) => { - if (value.type === "warning") { - return observableOf(value); - } - const { manifest: newManifest, - sendingTime: newSendingTime, - receivedTime, - parsingTime } = value; - const updateTimeStart = performance.now(); - - if (fullRefresh) { - manifest.replace(newManifest); - } else { - try { - manifest.update(newManifest); - } catch (e) { - const message = e instanceof Error ? e.message : - "unknown error"; - log.warn(`MUS: Attempt to update Manifest failed: ${message}`, - "Re-downloading the Manifest fully"); - const { FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY } = config.getCurrent(); - return startManualRefreshTimer(FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY, - minimumManifestUpdateInterval, - newSendingTime) - .pipe(mergeMap(() => - refreshManifest({ completeRefresh: true, unsafeMode: false }))); - } - } - return observableOf({ type: "parsed" as const, - manifest, - sendingTime: newSendingTime, - receivedTime, - parsingTime, - updatingTime: performance.now() - updateTimeStart }); - })); - } -} - -/** - * Launch a timer Observable which will emit when it is time to refresh the - * Manifest. - * The timer's delay is calculated from: - * - a target delay (`wantedDelay`), which is the minimum time we want to wait - * in the best scenario - * - the minimum set possible interval between manifest updates - * (`minimumManifestUpdateInterval`) - * - the time at which was done the last Manifest refresh - * (`lastManifestRequestTime`) - * @param {number} wantedDelay - * @param {number} minimumManifestUpdateInterval - * @param {number|undefined} lastManifestRequestTime - * @returns {Observable} - */ -function startManualRefreshTimer( - wantedDelay : number, - minimumManifestUpdateInterval : number, - lastManifestRequestTime : number | undefined -) : Observable { - return observableDefer(() => { - // The value allows to set a delay relatively to the last Manifest refresh - // (to avoid asking for it too often). - const timeSinceLastRefresh = lastManifestRequestTime === undefined ? - 0 : - performance.now() - lastManifestRequestTime; - const _minInterval = Math.max(minimumManifestUpdateInterval - timeSinceLastRefresh, - 0); - return observableTimer(Math.max(wantedDelay - timeSinceLastRefresh, - _minInterval)); - }); -} diff --git a/src/core/init/media_duration_updater.ts b/src/core/init/media_duration_updater.ts deleted file mode 100644 index 1217a19c0c..0000000000 --- a/src/core/init/media_duration_updater.ts +++ /dev/null @@ -1,270 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - combineLatest as observableCombineLatest, - distinctUntilChanged, - EMPTY, - fromEvent as observableFromEvent, - interval as observableInterval, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - startWith, - Subscription, - switchMap, - timer, -} from "rxjs"; -import { - onSourceOpen$, - onSourceClose$, - onSourceEnded$, -} from "../../compat/event_listeners"; -import log from "../../log"; -import Manifest from "../../manifest"; -import { fromEvent } from "../../utils/event_emitter"; -import createSharedReference, { - ISharedReference, -} from "../../utils/reference"; - -/** Number of seconds in a regular year. */ -const YEAR_IN_SECONDS = 365 * 24 * 3600; - -/** - * Keep the MediaSource's duration up-to-date with what is being played. - * @class MediaDurationUpdater - */ -export default class MediaDurationUpdater { - private _subscription : Subscription; - /** - * The last known audio Adaptation (i.e. track) chosen for the last Period. - * Useful to determinate the duration of the current content. - * `undefined` if the audio track for the last Period has never been known yet. - * `null` if there are no chosen audio Adaptation. - */ - private _lastKnownDuration : ISharedReference; - - /** - * Create a new `MediaDurationUpdater` that will keep the given MediaSource's - * duration as soon as possible. - * This duration will be updated until the `stop` method is called. - * @param {Object} manifest - The Manifest currently played. - * For another content, you will have to create another `MediaDurationUpdater`. - * @param {MediaSource} mediaSource - The MediaSource on which the content is - * pushed. - */ - constructor(manifest : Manifest, mediaSource : MediaSource) { - this._lastKnownDuration = createSharedReference(undefined); - this._subscription = isMediaSourceOpened$(mediaSource).pipe( - switchMap((canUpdate) => - canUpdate ? observableCombineLatest([this._lastKnownDuration.asObservable(), - fromEvent(manifest, "manifestUpdate") - .pipe(startWith(null))]) : - EMPTY - ), - switchMap(([lastKnownDuration]) => - areSourceBuffersUpdating$(mediaSource.sourceBuffers).pipe( - switchMap((areSBUpdating) => { - return areSBUpdating ? EMPTY : - recursivelyTryUpdatingDuration(); - function recursivelyTryUpdatingDuration() : Observable { - const res = setMediaSourceDuration(mediaSource, - manifest, - lastKnownDuration); - if (res === MediaSourceDurationUpdateStatus.Success) { - return EMPTY; - } - return timer(2000) - .pipe(mergeMap(() => recursivelyTryUpdatingDuration())); - } - })))).subscribe(); - } - - /** - * By default, the `MediaDurationUpdater` only set a safe estimate for the - * MediaSource's duration. - * A more precize duration can be set by communicating to it a more precize - * media duration through `updateKnownDuration`. - * If the duration becomes unknown, `undefined` can be given to it so the - * `MediaDurationUpdater` goes back to a safe estimate. - * @param {number | undefined} newDuration - */ - public updateKnownDuration( - newDuration : number | undefined - ) : void { - this._lastKnownDuration.setValue(newDuration); - } - - /** - * Stop the `MediaDurationUpdater` from updating and free its resources. - * Once stopped, it is not possible to start it again, beside creating another - * `MediaDurationUpdater`. - */ - public stop() { - this._subscription.unsubscribe(); - } -} - -/** - * Checks that duration can be updated on the MediaSource, and then - * sets it. - * - * Returns either: - * - the new duration it has been updated to if it has - * - `null` if it hasn'nt been updated - * - * @param {MediaSource} mediaSource - * @param {Object} manifest - * @returns {string} - */ -function setMediaSourceDuration( - mediaSource: MediaSource, - manifest: Manifest, - knownDuration : number | undefined -) : MediaSourceDurationUpdateStatus { - let newDuration = knownDuration; - - if (newDuration === undefined) { - if (manifest.isDynamic) { - const maxPotentialPos = manifest.getLivePosition() ?? - manifest.getMaximumSafePosition(); - // Some targets poorly support setting a very high number for durations. - // Yet, in dynamic contents, we would prefer setting a value as high as possible - // to still be able to seek anywhere we want to (even ahead of the Manifest if - // we want to). As such, we put it at a safe default value of 2^32 excepted - // when the maximum position is already relatively close to that value, where - // we authorize exceptionally going over it. - newDuration = Math.max(Math.pow(2, 32), maxPotentialPos + YEAR_IN_SECONDS); - } else { - newDuration = manifest.getMaximumSafePosition(); - } - } - - let maxBufferedEnd : number = 0; - for (let i = 0; i < mediaSource.sourceBuffers.length; i++) { - const sourceBuffer = mediaSource.sourceBuffers[i]; - const sbBufferedLen = sourceBuffer.buffered.length; - if (sbBufferedLen > 0) { - maxBufferedEnd = Math.max(sourceBuffer.buffered.end(sbBufferedLen - 1)); - } - } - - if (newDuration === mediaSource.duration) { - return MediaSourceDurationUpdateStatus.Success; - } else if (maxBufferedEnd > newDuration) { - // We already buffered further than the duration we want to set. - // Keep the duration that was set at that time as a security. - if (maxBufferedEnd < mediaSource.duration) { - try { - log.info("Init: Updating duration to what is currently buffered", maxBufferedEnd); - mediaSource.duration = newDuration; - } catch (err) { - log.warn("Duration Updater: Can't update duration on the MediaSource.", - err instanceof Error ? err : ""); - return MediaSourceDurationUpdateStatus.Failed; - } - } - return MediaSourceDurationUpdateStatus.Partial; - } else { - const oldDuration = mediaSource.duration; - try { - log.info("Init: Updating duration", newDuration); - mediaSource.duration = newDuration; - } catch (err) { - log.warn("Duration Updater: Can't update duration on the MediaSource.", - err instanceof Error ? err : ""); - return MediaSourceDurationUpdateStatus.Failed; - } - const deltaToExpected = Math.abs(mediaSource.duration - newDuration); - if (deltaToExpected >= 0.1) { - const deltaToBefore = Math.abs(mediaSource.duration - oldDuration); - return deltaToExpected < deltaToBefore ? MediaSourceDurationUpdateStatus.Partial : - MediaSourceDurationUpdateStatus.Failed; - } - return MediaSourceDurationUpdateStatus.Success; - } -} - -/** - * String describing the result of the process of updating a MediaSource's - * duration. - */ -const enum MediaSourceDurationUpdateStatus { - /** The MediaSource's duration has been updated to the asked duration. */ - Success = "success", - /** - * The MediaSource's duration has been updated and is now closer to the asked - * duration but is not yet the actually asked duration. - */ - Partial = "partial", - /** - * The MediaSource's duration could not have been updated due to an issue or - * has been updated but to a value actually further from the asked duration - * from what it was before. - */ - Failed = "failed", -} - -/** - * Returns an Observable which will emit only when all the SourceBuffers ended - * all pending updates. - * @param {SourceBufferList} sourceBuffers - * @returns {Observable} - */ -function areSourceBuffersUpdating$( - sourceBuffers: SourceBufferList -) : Observable { - if (sourceBuffers.length === 0) { - return observableOf(false); - } - const sourceBufferUpdatingStatuses : Array> = []; - - for (let i = 0; i < sourceBuffers.length; i++) { - const sourceBuffer = sourceBuffers[i]; - sourceBufferUpdatingStatuses.push( - observableMerge( - observableFromEvent(sourceBuffer, "updatestart").pipe(map(() => true)), - observableFromEvent(sourceBuffer, "update").pipe(map(() => false)), - observableInterval(500).pipe(map(() => sourceBuffer.updating)) - ).pipe( - startWith(sourceBuffer.updating), - distinctUntilChanged() - ) - ); - } - return observableCombineLatest(sourceBufferUpdatingStatuses).pipe( - map((areUpdating) => { - return areUpdating.some((isUpdating) => isUpdating); - }), - distinctUntilChanged()); -} - -/** - * Emit a boolean that tells if the media source is opened or not. - * @param {MediaSource} mediaSource - * @returns {Object} - */ -function isMediaSourceOpened$(mediaSource: MediaSource): Observable { - return observableMerge(onSourceOpen$(mediaSource).pipe(map(() => true)), - onSourceEnded$(mediaSource).pipe(map(() => false)), - onSourceClose$(mediaSource).pipe(map(() => false)) - ).pipe( - startWith(mediaSource.readyState === "open"), - distinctUntilChanged() - ); -} diff --git a/src/core/init/media_source_content_initializer.ts b/src/core/init/media_source_content_initializer.ts new file mode 100644 index 0000000000..bbfe365e15 --- /dev/null +++ b/src/core/init/media_source_content_initializer.ts @@ -0,0 +1,815 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { shouldReloadMediaSourceOnDecipherabilityUpdate } from "../../compat"; +import config from "../../config"; +import { MediaError } from "../../errors"; +import log from "../../log"; +import Manifest from "../../manifest"; +import { + IKeySystemOption, + IPlayerError, +} from "../../public_types"; +import { ITransportPipelines } from "../../transports"; +import assert from "../../utils/assert"; +import createCancellablePromise from "../../utils/create_cancellable_promise"; +import objectAssign from "../../utils/object_assign"; +import createSharedReference, { + IReadOnlySharedReference, + ISharedReference, +} from "../../utils/reference"; +import TaskCanceller, { + CancellationSignal, +} from "../../utils/task_canceller"; +import AdaptiveRepresentationSelector, { + IAdaptiveRepresentationSelectorArguments, + IRepresentationEstimator, +} from "../adaptive"; +import { IReadOnlyPlaybackObserver, PlaybackObserver } from "../api"; +import { + getKeySystemConfiguration, + IContentProtection, +} from "../decrypt"; +import { + ManifestFetcher, + SegmentFetcherCreator, +} from "../fetchers"; +import { IManifestFetcherSettings } from "../fetchers/manifest/manifest_fetcher"; +import SegmentBuffersStore, { + ITextTrackSegmentBufferOptions, +} from "../segment_buffers"; +import StreamOrchestrator, { + IAudioTrackSwitchingMode, + IStreamOrchestratorOptions, + IStreamOrchestratorCallbacks, + IStreamOrchestratorPlaybackObservation, +} from "../stream"; +import { ContentInitializer } from "./types"; +import ContentTimeBoundariesObserver from "./utils/content_time_boundaries_observer"; +import openMediaSource from "./utils/create_media_source"; +import createStreamPlaybackObserver from "./utils/create_stream_playback_observer"; +import { maintainEndOfStream } from "./utils/end_of_stream"; +import getInitialTime, { + IInitialTimeOptions, +} from "./utils/get_initial_time"; +import getLoadedReference from "./utils/get_loaded_reference"; +import performInitialSeekAndPlay from "./utils/initial_seek_and_play"; +import initializeContentDecryption from "./utils/initialize_content_decryption"; +import MediaDurationUpdater from "./utils/media_duration_updater"; +import RebufferingController from "./utils/rebuffering_controller"; +import streamEventsEmitter from "./utils/stream_events_emitter"; +import listenToMediaError from "./utils/throw_on_media_error"; + +/** + * Allows to load a new content thanks to the MediaSource Extensions (a.k.a. MSE) + * Web APIs. + * + * Through this `ContentInitializer`, a Manifest will be fetched (and depending + * on the situation, refreshed), a `MediaSource` instance will be linked to the + * wanted `HTMLMediaElement` and chunks of media data, called segments, will be + * pushed on buffers associated to this `MediaSource` instance. + * + * @class MediaSourceContentInitializer + */ +export default class MediaSourceContentInitializer extends ContentInitializer { + /** Constructor settings associated to this `MediaSourceContentInitializer`. */ + private _settings : IInitializeArguments; + /** + * `TaskCanceller` allowing to abort everything that the + * `MediaSourceContentInitializer` is doing. + */ + private _initCanceller : TaskCanceller; + /** Interface allowing to fetch and refresh the Manifest. */ + private _manifestFetcher : ManifestFetcher; + /** + * Promise resolving with the Manifest once it has been initially loaded. + * `null` if the load task has not started yet. + */ + private _initialManifestProm : Promise | null; + + /** + * Create a new `MediaSourceContentInitializer`, associated to the given + * settings. + * @param {Object} settings + */ + constructor(settings : IInitializeArguments) { + super(); + this._settings = settings; + this._initCanceller = new TaskCanceller(); + this._initialManifestProm = null; + const urls = settings.url === undefined ? undefined : + [settings.url]; + this._manifestFetcher = new ManifestFetcher(urls, + settings.transport, + settings.manifestRequestSettings); + } + + /** + * Perform non-destructive preparation steps, to prepare a future content. + * For now, this mainly mean loading the Manifest document. + */ + public prepare(): void { + if (this._initialManifestProm !== null) { + return; + } + this._initialManifestProm = createCancellablePromise( + this._initCanceller.signal, + (res, rej) => { + this._manifestFetcher.addEventListener("warning", (err : IPlayerError) => + this.trigger("warning", err)); + this._manifestFetcher.addEventListener("error", (err : unknown) => { + this.trigger("error", err); + rej(err); + }); + this._manifestFetcher.addEventListener("manifestReady", (manifest) => { + res(manifest); + }); + }); + this._manifestFetcher.start(); + this._initCanceller.signal.register(() => { + this._manifestFetcher.dispose(); + }); + } + + /** + * @param {HTMLMediaElement} mediaElement + * @param {Object} playbackObserver + */ + public start( + mediaElement : HTMLMediaElement, + playbackObserver : PlaybackObserver + ): void { + this.prepare(); // Load Manifest if not already done + + /** Translate errors coming from the media element into RxPlayer errors. */ + listenToMediaError(mediaElement, + (error : MediaError) => this._onFatalError(error), + this._initCanceller.signal); + + /** Send content protection initialization data to the decryption logic. */ + const protectionRef = createSharedReference( + null, + this._initCanceller.signal + ); + + this._initializeMediaSourceAndDecryption(mediaElement, protectionRef) + .then(initResult => this._onInitialMediaSourceReady(mediaElement, + initResult.mediaSource, + playbackObserver, + initResult.drmSystemId, + protectionRef, + initResult.unlinkMediaSource)) + .catch((err) => { + this._onFatalError(err); + }); + } + + /** + * Update URL of the Manifest. + * @param {Array.|undefined} urls - URLs to reach that Manifest from + * the most prioritized URL to the least prioritized URL. + * @param {boolean} refreshNow - If `true` the resource in question (e.g. + * DASH's MPD) will be refreshed immediately. + */ + public updateContentUrls(urls : string[] | undefined, refreshNow : boolean) : void { + this._manifestFetcher.updateContentUrls(urls, refreshNow); + } + + public dispose(): void { + this._initCanceller.cancel(); + } + + private _onFatalError(err : unknown) { + if (this._initCanceller.isUsed()) { + return; + } + this._initCanceller.cancel(); + this.trigger("error", err); + } + + private _initializeMediaSourceAndDecryption( + mediaElement : HTMLMediaElement, + protectionRef : IReadOnlySharedReference + ) : Promise<{ mediaSource : MediaSource; + drmSystemId : string | undefined; + unlinkMediaSource : TaskCanceller; }> + { + const initCanceller = this._initCanceller; + return createCancellablePromise(initCanceller.signal, (resolve) => { + const { keySystems } = this._settings; + + /** Initialize decryption capabilities. */ + const drmInitRef = + initializeContentDecryption(mediaElement, keySystems, protectionRef, { + onWarning: (err : IPlayerError) => this.trigger("warning", err), + onError: (err : Error) => this._onFatalError(err), + }, initCanceller.signal); + + drmInitRef.onUpdate((drmStatus, stopListeningToDrmUpdates) => { + if (drmStatus.initializationState.type === "uninitialized") { + return; + } + stopListeningToDrmUpdates(); + + const mediaSourceCanceller = new TaskCanceller(); + mediaSourceCanceller.linkToSignal(initCanceller.signal); + openMediaSource(mediaElement, mediaSourceCanceller.signal) + .then((mediaSource) => { + const lastDrmStatus = drmInitRef.getValue(); + if (lastDrmStatus.initializationState.type === "awaiting-media-link") { + lastDrmStatus.initializationState.value.isMediaLinked.setValue(true); + drmInitRef.onUpdate((newDrmStatus, stopListeningToDrmUpdatesAgain) => { + if (newDrmStatus.initializationState.type === "initialized") { + stopListeningToDrmUpdatesAgain(); + resolve({ mediaSource, + drmSystemId: newDrmStatus.drmSystemId, + unlinkMediaSource: mediaSourceCanceller }); + return; + } + }, { emitCurrentValue: true, clearSignal: initCanceller.signal }); + } else if (drmStatus.initializationState.type === "initialized") { + resolve({ mediaSource, + drmSystemId: drmStatus.drmSystemId, + unlinkMediaSource: mediaSourceCanceller }); + return; + } + }) + .catch((err) => { + if (mediaSourceCanceller.isUsed()) { + return; + } + this._onFatalError(err); + }); + }, { emitCurrentValue: true, clearSignal: initCanceller.signal }); + }); + } + + private async _onInitialMediaSourceReady( + mediaElement : HTMLMediaElement, + initialMediaSource : MediaSource, + playbackObserver : PlaybackObserver, + drmSystemId : string | undefined, + protectionRef : ISharedReference, + initialMediaSourceCanceller : TaskCanceller + ) : Promise { + const { adaptiveOptions, + autoPlay, + bufferOptions, + lowLatencyMode, + segmentRequestOptions, + speed, + startAt, + textTrackOptions, + transport } = this._settings; + const initCanceller = this._initCanceller; + assert(this._initialManifestProm !== null); + const manifestProm = this._initialManifestProm; + let manifest : Manifest; + try { + manifest = await manifestProm; + } catch (_e) { + return ; // The error should already have been processed through an event listener + } + + manifest.addEventListener("manifestUpdate", () => { + this.trigger("manifestUpdate", null); + }, initCanceller.signal); + manifest.addEventListener("decipherabilityUpdate", (args) => { + this.trigger("decipherabilityUpdate", args); + }, initCanceller.signal); + + log.debug("Init: Calculating initial time"); + const initialTime = getInitialTime(manifest, lowLatencyMode, startAt); + log.debug("Init: Initial time calculated:", initialTime); + + /** Choose the right "Representation" for a given "Adaptation". */ + const representationEstimator = AdaptiveRepresentationSelector(adaptiveOptions); + const subBufferOptions = objectAssign({ textTrackOptions, drmSystemId }, + bufferOptions); + + const segmentFetcherCreator = new SegmentFetcherCreator(transport, + segmentRequestOptions, + initCanceller.signal); + + this.trigger("manifestReady", manifest); + if (initCanceller.isUsed()) { + return ; + } + + const bufferOnMediaSource = this._startBufferingOnMediaSource.bind(this); + const triggerEvent = this.trigger.bind(this); + const onFatalError = this._onFatalError.bind(this); + + // handle initial load and reloads + recursivelyLoadOnMediaSource(initialMediaSource, + initialTime, + autoPlay, + initialMediaSourceCanceller); + + /** + * Load the content defined by the Manifest in the mediaSource given at the + * given position and playing status. + * This function recursively re-call itself when a MediaSource reload is + * wanted. + * @param {MediaSource} mediaSource + * @param {number} startingPos + * @param {Object} currentCanceller + * @param {boolean} shouldPlay + */ + function recursivelyLoadOnMediaSource( + mediaSource : MediaSource, + startingPos : number, + shouldPlay : boolean, + currentCanceller : TaskCanceller + ) : void { + const opts = { mediaElement, + playbackObserver, + mediaSource, + initialTime: startingPos, + autoPlay: shouldPlay, + manifest, + representationEstimator, + segmentFetcherCreator, + speed, + protectionRef, + bufferOptions: subBufferOptions }; + bufferOnMediaSource(opts, onReloadMediaSource, currentCanceller.signal); + + function onReloadMediaSource( + reloadOrder : { position : number; + autoPlay : boolean; } + ) : void { + currentCanceller.cancel(); + if (initCanceller.isUsed()) { + return; + } + triggerEvent("reloadingMediaSource", null); + if (initCanceller.isUsed()) { + return; + } + + const newCanceller = new TaskCanceller(); + newCanceller.linkToSignal(initCanceller.signal); + openMediaSource(mediaElement, newCanceller.signal) + .then(newMediaSource => { + recursivelyLoadOnMediaSource(newMediaSource, + reloadOrder.position, + reloadOrder.autoPlay, + newCanceller); + }) + .catch((err) => { + if (newCanceller.isUsed()) { + return; + } + onFatalError(err); + }); + } + } + } + + /** + * Buffer the content on the given MediaSource. + * @param {Object} args + * @param {function} onReloadOrder + * @param {Object} cancelSignal + */ + private _startBufferingOnMediaSource( + args : IBufferingMediaSettings, + onReloadOrder: (reloadOrder : { position: number; + autoPlay : boolean; }) => void, + cancelSignal : CancellationSignal + ) : void { + const { autoPlay, + bufferOptions, + initialTime, + manifest, + mediaElement, + mediaSource, + playbackObserver, + protectionRef, + representationEstimator, + segmentFetcherCreator, + speed } = args; + + const initialPeriod = manifest.getPeriodForTime(initialTime) ?? + manifest.getNextPeriod(initialTime); + if (initialPeriod === undefined) { + const error = new MediaError("MEDIA_STARTING_TIME_NOT_FOUND", + "Wanted starting time not found in the Manifest."); + return this._onFatalError(error); + } + + /** Interface to create media buffers. */ + const segmentBuffersStore = new SegmentBuffersStore(mediaElement, mediaSource); + cancelSignal.register(() => { + segmentBuffersStore.disposeAll(); + }); + + const { autoPlayResult, initialPlayPerformed, initialSeekPerformed } = + performInitialSeekAndPlay(mediaElement, + playbackObserver, + initialTime, + autoPlay, + (err) => this.trigger("warning", err), + cancelSignal); + + if (cancelSignal.isCancelled()) { + return; + } + + initialPlayPerformed.onUpdate((isPerformed, stopListening) => { + if (isPerformed) { + stopListening(); + streamEventsEmitter(manifest, + mediaElement, + playbackObserver, + (evt) => this.trigger("streamEvent", evt), + (evt) => this.trigger("streamEventSkip", evt), + cancelSignal); + } + }, { clearSignal: cancelSignal, emitCurrentValue: true }); + + const streamObserver = createStreamPlaybackObserver(manifest, + playbackObserver, + { autoPlay, + initialPlayPerformed, + initialSeekPerformed, + speed, + startTime: initialTime }); + + const rebufferingController = this._createRebufferingController(playbackObserver, + manifest, + speed, + cancelSignal); + + const contentTimeBoundariesObserver = this + ._createContentTimeBoundariesObserver(manifest, + mediaSource, + streamObserver, + segmentBuffersStore, + cancelSignal); + + /** + * Emit a "loaded" events once the initial play has been performed and the + * media can begin playback. + * Also emits warning events if issues arise when doing so. + */ + autoPlayResult + .then(() => { + getLoadedReference(playbackObserver, mediaElement, false, cancelSignal) + .onUpdate((isLoaded, stopListening) => { + if (isLoaded) { + stopListening(); + this.trigger("loaded", { segmentBuffersStore }); + } + }, { emitCurrentValue: true, clearSignal: cancelSignal }); + }) + .catch((err) => { + if (cancelSignal.isCancelled()) { + return; // Current loading cancelled, no need to trigger the error + } + this._onFatalError(err); + }); + + /* eslint-disable-next-line @typescript-eslint/no-this-alias */ + const self = this; + StreamOrchestrator({ manifest, initialPeriod }, + streamObserver, + representationEstimator, + segmentBuffersStore, + segmentFetcherCreator, + bufferOptions, + handleStreamOrchestratorCallbacks(), + cancelSignal); + + /** + * Returns Object handling the callbacks from a `StreamOrchestrator`, which + * are basically how it communicates about events. + * @returns {Object} + */ + function handleStreamOrchestratorCallbacks() : IStreamOrchestratorCallbacks { + return { + needsBufferFlush: () => + playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001), + + streamStatusUpdate(value) { + // Announce discontinuities if found + const { period, bufferType, imminentDiscontinuity, position } = value; + rebufferingController.updateDiscontinuityInfo({ + period, + bufferType, + discontinuity: imminentDiscontinuity, + position, + }); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + + // If the status for the last Period indicates that segments are all loaded + // or on the contrary that the loading resumed, announce it to the + // ContentTimeBoundariesObserver. + if (manifest.isLastPeriodKnown && + value.period.id === manifest.periods[manifest.periods.length - 1].id) + { + const hasFinishedLoadingLastPeriod = value.hasFinishedLoading || + value.isEmptyStream; + if (hasFinishedLoadingLastPeriod) { + contentTimeBoundariesObserver + .onLastSegmentFinishedLoading(value.bufferType); + } else { + contentTimeBoundariesObserver + .onLastSegmentLoadingResume(value.bufferType); + } + } + }, + + needsManifestRefresh: () => self._manifestFetcher.scheduleManualRefresh({ + enablePartialRefresh: true, + canUseUnsafeMode: true, + }), + + manifestMightBeOufOfSync: () => { + const { OUT_OF_SYNC_MANIFEST_REFRESH_DELAY } = config.getCurrent(); + self._manifestFetcher.scheduleManualRefresh({ + enablePartialRefresh: false, + canUseUnsafeMode: false, + delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY, + }); + }, + + lockedStream: (value) => + rebufferingController.onLockedStream(value.bufferType, value.period), + + adaptationChange: (value) => { + self.trigger("adaptationChange", value); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + contentTimeBoundariesObserver.onAdaptationChange(value.type, + value.period, + value.adaptation); + }, + + representationChange: (value) => { + self.trigger("representationChange", value); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + contentTimeBoundariesObserver.onRepresentationChange(value.type, value.period); + }, + + inbandEvent: (value) => self.trigger("inbandEvents", value), + + warning: (value) => self.trigger("warning", value), + + periodStreamReady: (value) => self.trigger("periodStreamReady", value), + + periodStreamCleared: (value) => { + contentTimeBoundariesObserver.onPeriodCleared(value.type, value.period); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + self.trigger("periodStreamCleared", value); + }, + + bitrateEstimationChange: (value) => + self.trigger("bitrateEstimationChange", value), + + addedSegment: (value) => self.trigger("addedSegment", value), + + needsMediaSourceReload: (value) => onReloadOrder(value), + + needsDecipherabilityFlush(value) { + const keySystem = getKeySystemConfiguration(mediaElement); + if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem?.[0])) { + onReloadOrder(value); + } else { + // simple seek close to the current position + // to flush the buffers + if (value.position + 0.001 < value.duration) { + playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001); + } else { + playbackObserver.setCurrentTime(value.position); + } + } + }, + + encryptionDataEncountered: (value) => { + for (const protectionData of value) { + protectionRef.setValue(protectionData); + if (cancelSignal.isCancelled()) { + return; // Previous call has stopped streams due to a side-effect + } + } + }, + + error: (err) => self._onFatalError(err), + }; + } + } + + /** + * Creates a `ContentTimeBoundariesObserver`, a class indicating various + * events related to media time (such as duration updates, period changes, + * warnings about being out of the Manifest time boundaries or "endOfStream" + * management), handle those events and returns the class. + * + * Various methods from that class need then to be called at various events + * (see `ContentTimeBoundariesObserver`). + * @param {Object} manifest + * @param {MediaSource} mediaSource + * @param {Object} streamObserver + * @param {Object} segmentBuffersStore + * @param {Object} cancelSignal + * @returns {Object} + */ + private _createContentTimeBoundariesObserver( + manifest : Manifest, + mediaSource : MediaSource, + streamObserver : IReadOnlyPlaybackObserver, + segmentBuffersStore : SegmentBuffersStore, + cancelSignal : CancellationSignal + ) : ContentTimeBoundariesObserver { + /** Maintains the MediaSource's duration up-to-date with the Manifest */ + const mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource); + cancelSignal.register(() => { + mediaDurationUpdater.stop(); + }); + /** Allows to cancel a pending `end-of-stream` operation. */ + let endOfStreamCanceller : TaskCanceller | null = null; + const contentTimeBoundariesObserver = new ContentTimeBoundariesObserver( + manifest, + streamObserver, + segmentBuffersStore.getBufferTypes() + ); + cancelSignal.register(() => { + contentTimeBoundariesObserver.dispose(); + }); + contentTimeBoundariesObserver.addEventListener("warning", (err) => + this.trigger("warning", err)); + contentTimeBoundariesObserver.addEventListener("periodChange", (period) => { + this.trigger("activePeriodChanged", { period }); + }); + contentTimeBoundariesObserver.addEventListener("durationUpdate", (newDuration) => { + log.debug("Init: Duration has to be updated.", newDuration); + mediaDurationUpdater.updateKnownDuration(newDuration); + }); + contentTimeBoundariesObserver.addEventListener("endOfStream", () => { + if (endOfStreamCanceller === null) { + endOfStreamCanceller = new TaskCanceller(); + endOfStreamCanceller.linkToSignal(cancelSignal); + log.debug("Init: end-of-stream order received."); + maintainEndOfStream(mediaSource, endOfStreamCanceller.signal); + } + }); + contentTimeBoundariesObserver.addEventListener("resumeStream", () => { + if (endOfStreamCanceller !== null) { + log.debug("Init: resume-stream order received."); + endOfStreamCanceller.cancel(); + endOfStreamCanceller = null; + } + }); + return contentTimeBoundariesObserver; + } + + /** + * Creates a `RebufferingController`, a class trying to avoid various stalling + * situations (such as rebuffering periods), and returns it. + * + * Various methods from that class need then to be called at various events + * (see `RebufferingController` definition). + * + * This function also handles the `RebufferingController`'s events: + * - emit "stalled" events when stalling situations cannot be prevented, + * - emit "unstalled" events when we could get out of one, + * - emit "warning" on various rebuffering-related minor issues + * like discontinuity skipping. + * @param {Object} playbackObserver + * @param {Object} manifest + * @param {Object} speed + * @param {Object} cancelSignal + * @returns {Object} + */ + private _createRebufferingController( + playbackObserver : PlaybackObserver, + manifest : Manifest, + speed : IReadOnlySharedReference, + cancelSignal : CancellationSignal + ) : RebufferingController { + const rebufferingController = new RebufferingController(playbackObserver, + manifest, + speed); + // Bubble-up events + rebufferingController.addEventListener("stalled", + (evt) => this.trigger("stalled", evt)); + rebufferingController.addEventListener("unstalled", + () => this.trigger("unstalled", null)); + rebufferingController.addEventListener("warning", + (err) => this.trigger("warning", err)); + cancelSignal.register(() => rebufferingController.destroy()); + rebufferingController.start(); + return rebufferingController; + } +} + +/** Arguments to give to the `InitializeOnMediaSource` function. */ +export interface IInitializeArguments { + /** Options concerning the ABR logic. */ + adaptiveOptions: IAdaptiveRepresentationSelectorArguments; + /** `true` if we should play when loaded. */ + autoPlay : boolean; + /** Options concerning the media buffers. */ + bufferOptions : { + /** Buffer "goal" at which we stop downloading new segments. */ + wantedBufferAhead : IReadOnlySharedReference; + /** Buffer maximum size in kiloBytes at which we stop downloading */ + maxVideoBufferSize : IReadOnlySharedReference; + /** Max buffer size after the current position, in seconds (we GC further up). */ + maxBufferAhead : IReadOnlySharedReference; + /** Max buffer size before the current position, in seconds (we GC further down). */ + maxBufferBehind : IReadOnlySharedReference; + /** Strategy when switching the current bitrate manually (smooth vs reload). */ + manualBitrateSwitchingMode : "seamless" | "direct"; + /** + * Enable/Disable fastSwitching: allow to replace lower-quality segments by + * higher-quality ones to have a faster transition. + */ + enableFastSwitching : boolean; + /** Strategy when switching of audio track. */ + audioTrackSwitchingMode : IAudioTrackSwitchingMode; + /** Behavior when a new video and/or audio codec is encountered. */ + onCodecSwitch : "continue" | "reload"; + }; + /** Every encryption configuration set. */ + keySystems : IKeySystemOption[]; + /** `true` to play low-latency contents optimally. */ + lowLatencyMode : boolean; + /** Settings linked to Manifest requests. */ + manifestRequestSettings : IManifestFetcherSettings; + /** Logic linked Manifest and segment loading and parsing. */ + transport : ITransportPipelines; + /** Configuration for the segment requesting logic. */ + segmentRequestOptions : { + lowLatencyMode : boolean; + /** + * Amount of time after which a request should be aborted. + * `undefined` indicates that a default value is wanted. + * `-1` indicates no timeout. + */ + requestTimeout : number | undefined; + /** Maximum number of time a request on error will be retried. */ + maxRetryRegular : number | undefined; + /** Maximum number of time a request be retried when the user is offline. */ + maxRetryOffline : number | undefined; + }; + /** Emit the playback rate (speed) set by the user. */ + speed : IReadOnlySharedReference; + /** The configured starting position. */ + startAt? : IInitialTimeOptions | undefined; + /** Configuration specific to the text track. */ + textTrackOptions : ITextTrackSegmentBufferOptions; + /** URL of the Manifest. `undefined` if unknown or not pertinent. */ + url : string | undefined; +} + +/** Arguments needed when starting to buffer media on a specific MediaSource. */ +interface IBufferingMediaSettings { + /** Various stream-related options. */ + bufferOptions : IStreamOrchestratorOptions; + /* Manifest of the content we want to play. */ + manifest : Manifest; + /** Media Element on which the content will be played. */ + mediaElement : HTMLMediaElement; + /** Emit playback conditions regularly. */ + playbackObserver : PlaybackObserver; + /** Estimate the right Representation. */ + representationEstimator : IRepresentationEstimator; + /** Module to facilitate segment fetching. */ + segmentFetcherCreator : SegmentFetcherCreator; + /** Last wanted playback rate. */ + speed : IReadOnlySharedReference; + /** + * Reference through which decryption initialization information can be + * communicated. + */ + protectionRef : ISharedReference; + /** `MediaSource` element on which the media will be buffered. */ + mediaSource : MediaSource; + /** The initial position to seek to in media time, in seconds. */ + initialTime : number; + /** If `true` it should automatically play once enough data is loaded. */ + autoPlay : boolean; +} diff --git a/src/core/init/stream_events_emitter/stream_events_emitter.ts b/src/core/init/stream_events_emitter/stream_events_emitter.ts deleted file mode 100644 index d55a351913..0000000000 --- a/src/core/init/stream_events_emitter/stream_events_emitter.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - combineLatest as observableCombineLatest, - concat as observableConcat, - distinctUntilChanged, - EMPTY, - ignoreElements, - interval, - map, - mergeMap, - Observable, - pairwise, - scan, - startWith, - switchMap, - tap, - of as observableOf, -} from "rxjs"; -import config from "../../../config"; -import Manifest from "../../../manifest"; -import { fromEvent } from "../../../utils/event_emitter"; -import { IPlaybackObservation } from "../../api"; -import refreshScheduledEventsList from "./refresh_scheduled_events_list"; -import { - INonFiniteStreamEventPayload, - IPublicStreamEvent, - IStreamEvent, - IStreamEventPayload, -} from "./types"; - - -/** - * Tells if a stream event has a duration - * @param {Object} evt - * @returns {Boolean} - */ -function isFiniteStreamEvent( - evt: IStreamEventPayload|INonFiniteStreamEventPayload -): evt is IStreamEventPayload { - return (evt as IStreamEventPayload).end !== undefined; -} - -/** - * Get events from manifest and emit each time an event has to be emitted - * @param {Object} manifest - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -function streamEventsEmitter(manifest: Manifest, - mediaElement: HTMLMediaElement, - observation$: Observable -): Observable { - const eventsBeingPlayed = - new WeakMap(); - let lastScheduledEvents : Array = []; - const scheduledEvents$ = fromEvent(manifest, "manifestUpdate").pipe( - startWith(null), - scan((oldScheduledEvents) => { - return refreshScheduledEventsList(oldScheduledEvents, manifest); - }, [] as Array) - ); - - /** - * Examine playback situation from playback observations to emit stream events and - * prepare set onExit callbacks if needed. - * @param {Array.} scheduledEvents - * @param {Object} oldObservation - * @param {Object} newObservation - * @returns {Observable} - */ - function emitStreamEvents$( - scheduledEvents: Array, - oldObservation: { currentTime: number; isSeeking: boolean }, - newObservation: { currentTime: number; isSeeking: boolean } - ): Observable { - const { currentTime: previousTime } = oldObservation; - const { isSeeking, currentTime } = newObservation; - const eventsToSend: IStreamEvent[] = []; - const eventsToExit: IPublicStreamEvent[] = []; - - for (let i = 0; i < scheduledEvents.length; i++) { - const event = scheduledEvents[i]; - const start = event.start; - const end = isFiniteStreamEvent(event) ? event.end : - undefined; - const isBeingPlayed = eventsBeingPlayed.has(event); - if (isBeingPlayed) { - if (start > currentTime || - (end !== undefined && currentTime >= end) - ) { - if (isFiniteStreamEvent(event)) { - eventsToExit.push(event.publicEvent); - } - eventsBeingPlayed.delete(event); - } - } else if (start <= currentTime && - end !== undefined && - currentTime < end) { - eventsToSend.push({ type: "stream-event", - value: event.publicEvent }); - eventsBeingPlayed.set(event, true); - } else if (previousTime < start && - currentTime >= (end ?? start)) { - if (isSeeking) { - eventsToSend.push({ type: "stream-event-skip", - value: event.publicEvent }); - } else { - eventsToSend.push({ type: "stream-event", - value: event.publicEvent }); - if (isFiniteStreamEvent(event)) { - eventsToExit.push(event.publicEvent); - } - } - } - } - - return observableConcat( - eventsToSend.length > 0 ? observableOf(...eventsToSend) : - EMPTY, - eventsToExit.length > 0 ? observableOf(...eventsToExit).pipe( - tap((evt) => { - if (typeof evt.onExit === "function") { - evt.onExit(); - } - }), - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - ignoreElements() - ) : EMPTY - ); - } - - /** - * This pipe allows to control wether the polling should occur, if there - * are scheduledEvents, or not. - */ - return scheduledEvents$.pipe( - tap((scheduledEvents) => lastScheduledEvents = scheduledEvents), - map((evt): boolean => evt.length > 0), - distinctUntilChanged(), - switchMap((hasEvents: boolean) => { - if (!hasEvents) { - return EMPTY; - } - - const { STREAM_EVENT_EMITTER_POLL_INTERVAL } = config.getCurrent(); - return observableCombineLatest([ - interval(STREAM_EVENT_EMITTER_POLL_INTERVAL).pipe(startWith(null)), - observation$, - ]).pipe( - map(([_, observation]) => { - const { seeking } = observation; - return { isSeeking: seeking, - currentTime: mediaElement.currentTime }; - }), - pairwise(), - mergeMap(([oldObservation, newObservation]) => - emitStreamEvents$(lastScheduledEvents, oldObservation, newObservation)) - ); - }) - ); -} - -export default streamEventsEmitter; diff --git a/src/core/init/throw_on_media_error.ts b/src/core/init/throw_on_media_error.ts deleted file mode 100644 index 0c789fd8f2..0000000000 --- a/src/core/init/throw_on_media_error.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - fromEvent as observableFromEvent, - mergeMap, - Observable, -} from "rxjs"; -import { MediaError } from "../../errors"; -import isNullOrUndefined from "../../utils/is_null_or_undefined"; - -/** - * Returns an observable which throws the right MediaError as soon an "error" - * event is received through the media element. - * @param {HTMLMediaElement} mediaElement - * @returns {Observable} - */ -export default function throwOnMediaError( - mediaElement : HTMLMediaElement -) : Observable { - return observableFromEvent(mediaElement, "error") - .pipe(mergeMap(() => { - const mediaError = mediaElement.error; - let errorCode : number | undefined; - let errorMessage : string | undefined; - if (!isNullOrUndefined(mediaError)) { - errorCode = mediaError.code; - errorMessage = mediaError.message; - } - - switch (errorCode) { - case 1: - errorMessage = errorMessage ?? - "The fetching of the associated resource was aborted by the user's request."; - throw new MediaError("MEDIA_ERR_ABORTED", errorMessage); - case 2: - errorMessage = errorMessage ?? - "A network error occurred which prevented the media from being " + - "successfully fetched"; - throw new MediaError("MEDIA_ERR_NETWORK", errorMessage); - case 3: - errorMessage = errorMessage ?? - "An error occurred while trying to decode the media resource"; - throw new MediaError("MEDIA_ERR_DECODE", errorMessage); - case 4: - errorMessage = errorMessage ?? - "The media resource has been found to be unsuitable."; - throw new MediaError("MEDIA_ERR_SRC_NOT_SUPPORTED", errorMessage); - default: - errorMessage = errorMessage ?? - "The HTMLMediaElement errored due to an unknown reason."; - throw new MediaError("MEDIA_ERR_UNKNOWN", errorMessage); - } - })); -} diff --git a/src/core/init/types.ts b/src/core/init/types.ts index 7a6a58f2e6..298826e614 100644 --- a/src/core/init/types.ts +++ b/src/core/init/types.ts @@ -16,75 +16,237 @@ import Manifest, { Adaptation, + ISegment, Period, Representation, } from "../../manifest"; import { IPlayerError } from "../../public_types"; -import SegmentBuffersStore from "../segment_buffers"; +import EventEmitter from "../../utils/event_emitter"; +import { ISharedReference } from "../../utils/reference"; +import { PlaybackObserver } from "../api"; +import SegmentBuffersStore, { + IBufferType, +} from "../segment_buffers"; +import { IInbandEvent } from "../stream"; import { - IActivePeriodChangedEvent, - IAdaptationChangeEvent, - IBitrateEstimationChangeEvent, - ICompletedStreamEvent, - IEncryptionDataEncounteredEvent, - IInbandEventsEvent, - INeedsBufferFlushEvent, - INeedsDecipherabilityFlush, - INeedsMediaSourceReload, - IPeriodStreamClearedEvent, - IPeriodStreamReadyEvent, - IRepresentationChangeEvent, - IStreamEventAddedSegment, - IStreamManifestMightBeOutOfSync, - IStreamNeedsManifestRefresh, -} from "../stream"; -import { - IStreamEventEvent, - IStreamEventSkipEvent, -} from "./stream_events_emitter"; - -/** Event sent after the Manifest has been loaded and parsed for the first time. */ -export interface IManifestReadyEvent { - type : "manifestReady"; - value : { - /** The Manifest we just parsed. */ - manifest : Manifest; - }; -} - -/** Event sent after the Manifest has been updated. */ -export interface IManifestUpdateEvent { type: "manifestUpdate"; - value: null; } + IPublicNonFiniteStreamEvent, + IPublicStreamEvent, +} from "./utils/stream_events_emitter"; /** - * Event sent after updating the decipherability status of at least one - * Manifest's Representation. - * This generally means that some Representation(s) were detected to be - * undecipherable on the current device. + * Class allowing to start playing a content on an `HTMLMediaElement`. + * + * The actual constructor arguments depend on the `ContentInitializer` defined, + * but should reflect all potential configuration wanted relative to this + * content's playback. + * + * Various events may be emitted by a `ContentInitializer`. However, no event + * should be emitted before `prepare` or `start` is called and no event should + * be emitted after `dispose` is called. */ -export interface IDecipherabilityUpdateEvent { - type: "decipherabilityUpdate"; - value: Array<{ manifest : Manifest; - period : Period; - adaptation : Adaptation; - representation : Representation; }>; } +export abstract class ContentInitializer extends EventEmitter { + /** + * Prepare the content linked to this `ContentInitializer` in the background, + * without actually trying to play it. + * + * This method may be used for optimization reasons, for example to prepare a + * future content without interrupting the previous one playing on a given + * `HTMLMediaElement`. + */ + public abstract prepare() : void; -/** Event sent when a minor happened. */ -export interface IWarningEvent { type : "warning"; - value : IPlayerError; } + /** + * Actually starts playing the content linked to this `ContentInitializer` on + * the given `mediaElement`. + * + * Only a single call to `start` is expected to be performed on each + * `ContentInitializer`. + * + * A call to `prepare` may or may not have been performed before calling + * `start`. If it was, it may or may not have yet finished the preparation + * phase before `start` is called (the `ContentInitializer` should stay + * resilient in both scenarios). + * + * @param {HTMLMediaElement} mediaElement - `HTMLMediaElement` on which the + * content will play. This is given to `start` (and not sooner) to ensure + * that no prior step influence the `HTMLMediaElement`, on which a previous + * content could have been playing until then. + * + * If a content was already playing on that `HTMLMediaElement`, it will be + * stopped. + * @param {Object} playbackObserver - Interface allowing to poll playback + * information on what's playing on the `HTMLMediaElement` at regular + * intervals. + */ + public abstract start( + mediaElement : HTMLMediaElement, + playbackObserver : PlaybackObserver + ) : void; -/** - * Event sent when we're starting attach a new MediaSource to the media element - * (after removing the previous one). - */ -export interface IReloadingMediaSourceEvent { type: "reloading-media-source"; - value: undefined; } + /** + * Update URL of the content currently being played (e.g. DASH's MPD). + * @param {Array.|undefined} urls - URLs to reach that content / + * Manifest from the most prioritized URL to the least prioritized URL. + * @param {boolean} refreshNow - If `true` the resource in question (e.g. + * DASH's MPD) will be refreshed immediately. + */ + public abstract updateContentUrls( + urls : string[] | undefined, + refreshNow : boolean + ) : void; -/** Event sent after the player stalled. */ -export interface IStalledEvent { - type : "stalled"; - /** The reason behind the stall */ - value : IStallingSituation; + /** + * Stop playing the content linked to this `ContentInitializer` on the + * `HTMLMediaElement` linked to it and dispose of every resources taken while + * trying to do so. + */ + public abstract dispose() : void; +} + +/** Every events emitted by a `ContentInitializer`. */ +export interface IContentInitializerEvents { + /** Event sent when a minor happened. */ + warning : IPlayerError; + /** A fatal error occured, leading to the current content being stopped. */ + error : unknown; + /** Event sent after the Manifest has been loaded and parsed for the first time. */ + manifestReady : Manifest; + /** Event sent after the Manifest has been updated. */ + manifestUpdate: null; + /** + * Event sent when we're starting attach a new MediaSource to the media element + * (after removing the previous one). + */ + reloadingMediaSource: null; + /** Event sent after the player stalled. */ + stalled : IStallingSituation; + /** Event sent when the player goes out of a stalling situation. */ + unstalled : null; + /** + * Event sent just as the content is considered as "loaded". + * From this point on, the user can reliably play/pause/resume the stream. + */ + loaded : { segmentBuffersStore: SegmentBuffersStore | null }; + /** + * Event sent after updating the decipherability status of at least one + * Manifest's Representation. + * This generally means that some Representation(s) were detected to be + * undecipherable on the current device. + */ + decipherabilityUpdate: Array<{ manifest : Manifest; + period : Period; + adaptation : Adaptation; + representation : Representation; }>; + /** Event emitted when a stream event is encountered. */ + streamEvent: IPublicStreamEvent | + IPublicNonFiniteStreamEvent; + streamEventSkip: IPublicStreamEvent | + IPublicNonFiniteStreamEvent; + /** Emitted when a new `Period` is currently playing. */ + activePeriodChanged: { + /** The Period we're now playing. */ + period: Period; + }; + /** + * A new `PeriodStream` is ready to start but needs an Adaptation (i.e. track) + * to be chosen first. + */ + periodStreamReady: { + /** The type of buffer linked to the `PeriodStream` we want to create. */ + type : IBufferType; + /** The `Period` linked to the `PeriodStream` we have created. */ + period : Period; + /** + * The Reference through which any Adaptation (i.e. track) choice should be + * emitted for that `PeriodStream`. + * + * The `PeriodStream` will not do anything until this Reference has emitted + * at least one to give its initial choice. + * You can send `null` through it to tell this `PeriodStream` that you don't + * want any `Adaptation`. + * It is set to `undefined` by default, you SHOULD NOT set it to `undefined` + * yourself. + */ + adaptationRef : ISharedReference; + }; + /** + * A `PeriodStream` has been removed. + * This event can be used for clean-up purposes. For example, you are free to + * remove from scope the shared reference that you used to choose a track for + * that `PeriodStream`. + */ + periodStreamCleared: { + /** + * The type of buffer linked to the `PeriodStream` we just removed. + * + * The combination of this and `Period` should give you enough information + * about which `PeriodStream` has been removed. + */ + type : IBufferType; + /** + * The `Period` linked to the `PeriodStream` we just removed. + * + * The combination of this and `Period` should give you enough information + * about which `PeriodStream` has been removed. + */ + period : Period; + }; + /** Emitted when a new `Adaptation` is being considered. */ + adaptationChange: IAdaptationChangeEventPayload; + /** Emitted as new bitrate estimates are done. */ + bitrateEstimationChange: { + /** The type of buffer for which the estimation is done. */ + type : IBufferType; + /** + * The bitrate estimate, in bits per seconds. `undefined` when no bitrate + * estimate is currently available. + */ + bitrate : number|undefined; + }; + /** Emitted when a new `Representation` is being considered. */ + representationChange: { + /** The type of buffer linked to that `RepresentationStream`. */ + type : IBufferType; + /** The `Period` linked to the `RepresentationStream` we're creating. */ + period : Period; + /** + * The `Representation` linked to the `RepresentationStream` we're creating. + * `null` when we're choosing no Representation at all. + */ + representation : Representation | + null; + }; + /** Emitted after a new segment has been succesfully added to the SegmentBuffer */ + addedSegment: { + /** Context about the content that has been added. */ + content: { period : Period; + adaptation : Adaptation; + representation : Representation; }; + /** The concerned Segment. */ + segment : ISegment; + /** TimeRanges of the concerned SegmentBuffer after the segment was pushed. */ + buffered : TimeRanges; + /* The data pushed */ + segmentData : unknown; + }; + /** + * Event emitted when one or multiple inband events (i.e. events inside a + * given segment) have been encountered. + */ + inbandEvents : IInbandEvent[]; +} + +export interface IAdaptationChangeEventPayload { + /** The type of buffer for which the Representation is changing. */ + type : IBufferType; + /** The `Period` linked to the `RepresentationStream` we're creating. */ + period : Period; + /** + * The `Adaptation` linked to the `AdaptationStream` we're creating. + * `null` when we're choosing no Adaptation at all. + */ + adaptation : Adaptation | + null; } export type IStallingSituation = @@ -94,77 +256,3 @@ export type IStallingSituation = "buffering" | // Other rebuffering cases "freezing"; // stalled for an unknown reason (might be waiting for // a decryption key) - -/** Event sent when the player goes out of a stalling situation. */ -export interface IUnstalledEvent { type : "unstalled"; - value : null; } - -/** - * Event sent just as the content is considered as "loaded". - * From this point on, the user can reliably play/pause/resume the stream. - */ -export interface ILoadedEvent { type : "loaded"; - value : { - segmentBuffersStore: SegmentBuffersStore | null; - }; } - -export { IRepresentationChangeEvent }; - -/** Events emitted by a `MediaSourceLoader`. */ -export type IMediaSourceLoaderEvent = IStalledEvent | - IUnstalledEvent | - ILoadedEvent | - IWarningEvent | - IStreamEventEvent | - IStreamEventSkipEvent | - - // Coming from the StreamOrchestrator - - IActivePeriodChangedEvent | - IPeriodStreamClearedEvent | - ICompletedStreamEvent | - IPeriodStreamReadyEvent | - INeedsMediaSourceReload | - INeedsBufferFlushEvent | - IAdaptationChangeEvent | - IBitrateEstimationChangeEvent | - INeedsDecipherabilityFlush | - IRepresentationChangeEvent | - IStreamEventAddedSegment | - IEncryptionDataEncounteredEvent | - IStreamManifestMightBeOutOfSync | - IStreamNeedsManifestRefresh | - IInbandEventsEvent; - -/** Every events emitted by the `Init` module. */ -export type IInitEvent = IManifestReadyEvent | - IManifestUpdateEvent | - IReloadingMediaSourceEvent | - IDecipherabilityUpdateEvent | - IWarningEvent | - - // Coming from the `MediaSourceLoader` - - IStalledEvent | - IUnstalledEvent | - ILoadedEvent | - IStreamEventEvent | - IStreamEventSkipEvent | - - // Coming from the `StreamOrchestrator` - - IActivePeriodChangedEvent | - IPeriodStreamClearedEvent | - ICompletedStreamEvent | - IPeriodStreamReadyEvent | - IAdaptationChangeEvent | - IBitrateEstimationChangeEvent | - IRepresentationChangeEvent | - IStreamEventAddedSegment | - IInbandEventsEvent; - -/** Events emitted by the `Init` module for directfile contents. */ -export type IDirectfileEvent = IStalledEvent | - IUnstalledEvent | - ILoadedEvent | - IWarningEvent; diff --git a/src/core/init/update_playback_rate.ts b/src/core/init/update_playback_rate.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/core/init/__tests__/are_same_stream_events.test.ts b/src/core/init/utils/__tests__/are_same_stream_events.test.ts similarity index 100% rename from src/core/init/__tests__/are_same_stream_events.test.ts rename to src/core/init/utils/__tests__/are_same_stream_events.test.ts diff --git a/src/core/init/__tests__/refresh_scheduled_events_list.test.ts b/src/core/init/utils/__tests__/refresh_scheduled_events_list.test.ts similarity index 100% rename from src/core/init/__tests__/refresh_scheduled_events_list.test.ts rename to src/core/init/utils/__tests__/refresh_scheduled_events_list.test.ts diff --git a/src/core/init/utils/content_time_boundaries_observer.ts b/src/core/init/utils/content_time_boundaries_observer.ts new file mode 100644 index 0000000000..09a43f0adc --- /dev/null +++ b/src/core/init/utils/content_time_boundaries_observer.ts @@ -0,0 +1,609 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MediaError } from "../../../errors"; +import Manifest, { + Adaptation, + IRepresentationIndex, + Period, +} from "../../../manifest"; +import { IPlayerError } from "../../../public_types"; +import EventEmitter from "../../../utils/event_emitter"; +import isNullOrUndefined from "../../../utils/is_null_or_undefined"; +import SortedList from "../../../utils/sorted_list"; +import TaskCanceller from "../../../utils/task_canceller"; +import { IReadOnlyPlaybackObserver } from "../../api"; +import { IBufferType } from "../../segment_buffers"; +import { IStreamOrchestratorPlaybackObservation } from "../../stream"; + +/** + * Observes what's being played and take care of media events relating to time + * boundaries: + * - Emits a `durationUpdate` when the duration of the current content is + * known and every time it changes. + * - Emits `endOfStream` API once segments have been pushed until the end and + * `resumeStream` if downloads starts back. + * - Emits a `periodChange` event when the currently-playing Period seemed to + * have changed. + * - emit "warning" events when what is being played is outside of the + * Manifest range. + * @class ContentTimeBoundariesObserver + */ +export default class ContentTimeBoundariesObserver + extends EventEmitter { + + /** Allows to interrupt everything the `ContentTimeBoundariesObserver` is doing. */ + private _canceller : TaskCanceller; + + /** Store information on every created "Streams". */ + private _activeStreams : Map; + + /** The `Manifest` object linked to the current content. */ + private _manifest : Manifest; + + /** Allows to calculate at any time maximum positions of the content */ + private _maximumPositionCalculator : MaximumPositionCalculator; + + /** Enumerate all possible buffer types in the current content. */ + private _allBufferTypes : IBufferType[]; + + /** + * Stores the `id` property of the last Period for which a `periodChange` + * event has been sent. + * Allows to avoid multiple times in a row `periodChange` for the same + * Period. + */ + private _lastCurrentPeriodId : string | null; + + /** + * @param {Object} manifest + * @param {Object} playbackObserver + */ + constructor( + manifest : Manifest, + playbackObserver : IReadOnlyPlaybackObserver, + bufferTypes : IBufferType[] + ) { + super(); + + this._canceller = new TaskCanceller(); + this._manifest = manifest; + this._activeStreams = new Map(); + this._allBufferTypes = bufferTypes; + this._lastCurrentPeriodId = null; + + /** + * Allows to calculate the minimum and maximum playable position on the + * whole content. + */ + const maximumPositionCalculator = new MaximumPositionCalculator(manifest); + this._maximumPositionCalculator = maximumPositionCalculator; + + const cancelSignal = this._canceller.signal; + playbackObserver.listen(({ position }) => { + const wantedPosition = position.pending ?? position.last; + if (wantedPosition < manifest.getMinimumSafePosition()) { + const warning = new MediaError("MEDIA_TIME_BEFORE_MANIFEST", + "The current position is behind the " + + "earliest time announced in the Manifest."); + this.trigger("warning", warning); + } else if ( + wantedPosition > maximumPositionCalculator.getMaximumAvailablePosition() + ) { + const warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST", + "The current position is after the latest " + + "time announced in the Manifest."); + this.trigger("warning", warning); + } + }, { includeLastObservation: true, clearSignal: cancelSignal }); + + manifest.addEventListener("manifestUpdate", () => { + this.trigger("durationUpdate", getManifestDuration()); + if (cancelSignal.isCancelled()) { + return; + } + this._checkEndOfStream(); + }, cancelSignal); + + function getManifestDuration() : number | undefined { + return manifest.isDynamic ? + maximumPositionCalculator.getMaximumAvailablePosition() : + maximumPositionCalculator.getEndingPosition(); + } + } + + /** + * Method to call any time an Adaptation has been selected. + * + * That Adaptation switch will be considered as active until the + * `onPeriodCleared` method has been called for the same `bufferType` and + * `Period`, or until `dispose` is called. + * @param {string} bufferType - The type of buffer concerned by the Adaptation + * switch + * @param {Object} period - The Period concerned by the Adaptation switch + * @param {Object|null} adaptation - The Adaptation selected. `null` if the + * absence of `Adaptation` has been explicitely selected for this Period and + * buffer type (e.g. no video). + */ + public onAdaptationChange( + bufferType : IBufferType, + period : Period, + adaptation : Adaptation | null + ) : void { + if (this._manifest.isLastPeriodKnown) { + const lastPeriod = this._manifest.periods[this._manifest.periods.length - 1]; + if (period.id === lastPeriod?.id) { + if (bufferType === "audio" || bufferType === "video") { + if (bufferType === "audio") { + this._maximumPositionCalculator + .updateLastAudioAdaptation(adaptation); + } else { + this._maximumPositionCalculator + .updateLastVideoAdaptation(adaptation); + } + const newDuration = this._manifest.isDynamic ? + this._maximumPositionCalculator.getMaximumAvailablePosition() : + this._maximumPositionCalculator.getEndingPosition(); + this.trigger("durationUpdate", newDuration); + } + } + } + if (this._canceller.isUsed()) { + return; + } + if (adaptation === null) { + this._addActivelyLoadedPeriod(period, bufferType); + } + } + + /** + * Method to call any time a Representation has been selected. + * + * That Representation switch will be considered as active until the + * `onPeriodCleared` method has been called for the same `bufferType` and + * `Period`, or until `dispose` is called. + * @param {string} bufferType - The type of buffer concerned by the + * Representation switch + * @param {Object} period - The Period concerned by the Representation switch + */ + public onRepresentationChange( + bufferType : IBufferType, + period : Period + ) : void { + this._addActivelyLoadedPeriod(period, bufferType); + } + + /** + * Method to call any time a Period and type combination is not considered + * anymore. + * + * Calling this method allows to signal that a previous Adaptation and/or + * Representation change respectively indicated by an `onAdaptationChange` and + * an `onRepresentationChange` call, are not active anymore. + * @param {string} bufferType - The type of buffer concerned + * @param {Object} period - The Period concerned + */ + public onPeriodCleared( + bufferType : IBufferType, + period : Period + ) : void { + this._removeActivelyLoadedPeriod(period, bufferType); + } + + /** + * Method to call when the last chronological segment for a given buffer type + * is known to have been loaded and is either pushed or in the process of + * being pushed to the corresponding MSE `SourceBuffer` or equivalent. + * + * This method can even be called multiple times in a row as long as the + * aforementioned condition is true, if it simplify your code's management. + * @param {string} bufferType + */ + public onLastSegmentFinishedLoading( + bufferType : IBufferType + ) : void { + const streamInfo = this._lazilyCreateActiveStreamInfo(bufferType); + if (!streamInfo.hasFinishedLoadingLastPeriod) { + streamInfo.hasFinishedLoadingLastPeriod = true; + this._checkEndOfStream(); + } + } + + /** + * Method to call to "cancel" a previous call to + * `onLastSegmentFinishedLoading`. + * + * That is, calling this method indicates that the last chronological segment + * of a given buffer type is now either not loaded or it is not known. + * + * This method can even be called multiple times in a row as long as the + * aforementioned condition is true, if it simplify your code's management. + * @param {string} bufferType + */ + public onLastSegmentLoadingResume(bufferType : IBufferType) : void { + const streamInfo = this._lazilyCreateActiveStreamInfo(bufferType); + if (streamInfo.hasFinishedLoadingLastPeriod) { + streamInfo.hasFinishedLoadingLastPeriod = false; + this._checkEndOfStream(); + } + } + + /** + * Free all resources used by the `ContentTimeBoundariesObserver` and cancels + * all recurring processes it performs. + */ + public dispose() { + this.removeEventListener(); + this._canceller.cancel(); + } + + private _addActivelyLoadedPeriod(period : Period, bufferType : IBufferType) : void { + const streamInfo = this._lazilyCreateActiveStreamInfo(bufferType); + if (!streamInfo.activePeriods.has(period)) { + streamInfo.activePeriods.add(period); + this._checkCurrentPeriod(); + } + } + + private _removeActivelyLoadedPeriod( + period : Period, + bufferType : IBufferType + ) : void { + const streamInfo = this._activeStreams.get(bufferType); + if (streamInfo === undefined) { + return; + } + if (streamInfo.activePeriods.has(period)) { + streamInfo.activePeriods.removeElement(period); + this._checkCurrentPeriod(); + } + } + + private _checkCurrentPeriod() : void { + if (this._allBufferTypes.length === 0) { + return; + } + + const streamInfo = this._activeStreams.get(this._allBufferTypes[0]); + if (streamInfo === undefined) { + return; + } + for (const period of streamInfo.activePeriods.toArray()) { + let wasFoundInAllTypes = true; + for (let i = 1; i < this._allBufferTypes.length; i++) { + const streamInfo2 = this._activeStreams.get(this._allBufferTypes[i]); + if (streamInfo2 === undefined) { + return; + } + + const activePeriods = streamInfo2.activePeriods.toArray(); + const hasPeriod = activePeriods.some((p) => p.id === period.id); + if (!hasPeriod) { + wasFoundInAllTypes = false; + break; + } + } + if (wasFoundInAllTypes) { + if (this._lastCurrentPeriodId !== period.id) { + this._lastCurrentPeriodId = period.id; + this.trigger("periodChange", period); + } + return; + } + } + } + + private _lazilyCreateActiveStreamInfo(bufferType : IBufferType) : IActiveStreamsInfo { + let streamInfo = this._activeStreams.get(bufferType); + if (streamInfo === undefined) { + streamInfo = { + activePeriods: new SortedList((a, b) => a.start - b.start), + hasFinishedLoadingLastPeriod: false, + }; + this._activeStreams.set(bufferType, streamInfo); + } + return streamInfo; + } + + private _checkEndOfStream() : void { + if (!this._manifest.isLastPeriodKnown) { + return; + } + const everyBufferTypeLoaded = this._allBufferTypes.every((bt) => { + const streamInfo = this._activeStreams.get(bt); + return streamInfo !== undefined && streamInfo.hasFinishedLoadingLastPeriod; + }); + if (everyBufferTypeLoaded) { + this.trigger("endOfStream", null); + } else { + this.trigger("resumeStream", null); + } + } +} + +/** + * Events triggered by a `ContentTimeBoundariesObserver` where the keys are the + * event names and the value is the payload of those events. + */ +export interface IContentTimeBoundariesObserverEvent { + /** Triggered when a minor error is encountered. */ + warning : IPlayerError; + /** Triggered when a new `Period` is currently playing. */ + periodChange : Period; + /** + * Triggered when the duration of the currently-playing content became known + * or changed. + */ + durationUpdate : number | undefined; + /** + * Triggered when the last possible chronological segment for all types of + * buffers has either been pushed or is being pushed to the corresponding + * MSE `SourceBuffer` or equivalent. + * As such, the `endOfStream` MSE API might from now be able to be called. + * + * Note that it is possible to receive this event even if `endOfStream` has + * already been called and even if an "endOfStream" event has already been + * triggered. + */ + endOfStream : null; + /** + * Triggered when the last possible chronological segment for all types of + * buffers have NOT been pushed, or if it is not known whether is has been + * pushed, and as such any potential pending `endOfStream` MSE API call + * need to be cancelled. + * + * Note that it is possible to receive this event even if `endOfStream` has + * not been called and even if an "resumeStream" event has already been + * triggered. + */ + resumeStream : null; +} + +/** + * Calculate the last position from the last chosen audio and video Adaptations + * for the last Period (or a default one, if no Adaptations has been chosen). + * @class MaximumPositionCalculator + */ +class MaximumPositionCalculator { + private _manifest : Manifest; + + // TODO replicate for the minimum position ? + private _lastAudioAdaptation : Adaptation | undefined | null; + private _lastVideoAdaptation : Adaptation | undefined | null; + + /** + * @param {Object} manifest + */ + constructor(manifest : Manifest) { + this._manifest = manifest; + this._lastAudioAdaptation = undefined; + this._lastVideoAdaptation = undefined; + } + + /** + * Update the last known audio Adaptation for the last Period. + * If no Adaptation has been set, it should be set to `null`. + * + * Allows to calculate the maximum position more precizely in + * `getMaximumAvailablePosition` and `getEndingPosition`. + * @param {Object|null} adaptation + */ + public updateLastAudioAdaptation(adaptation : Adaptation | null) : void { + this._lastAudioAdaptation = adaptation; + } + + /** + * Update the last known video Adaptation for the last Period. + * If no Adaptation has been set, it should be set to `null`. + * + * Allows to calculate the maximum position more precizely in + * `getMaximumAvailablePosition` and `getEndingPosition`. + * @param {Object|null} adaptation + */ + public updateLastVideoAdaptation(adaptation : Adaptation | null) : void { + this._lastVideoAdaptation = adaptation; + } + +/** + * Returns an estimate of the maximum position currently reachable (i.e. + * segments are available) under the current circumstances. + * @returns {number} + */ + public getMaximumAvailablePosition() : number { + if (this._manifest.isDynamic) { + return this._manifest.getLivePosition() ?? + this._manifest.getMaximumSafePosition(); + } + if (this._lastVideoAdaptation === undefined || + this._lastAudioAdaptation === undefined) + { + return this._manifest.getMaximumSafePosition(); + } else if (this._lastAudioAdaptation === null) { + if (this._lastVideoAdaptation === null) { + return this._manifest.getMaximumSafePosition(); + } else { + const lastVideoPosition = + getLastAvailablePositionFromAdaptation(this._lastVideoAdaptation); + if (typeof lastVideoPosition !== "number") { + return this._manifest.getMaximumSafePosition(); + } + return lastVideoPosition; + } + } else if (this._lastVideoAdaptation === null) { + const lastAudioPosition = + getLastAvailablePositionFromAdaptation(this._lastAudioAdaptation); + if (typeof lastAudioPosition !== "number") { + return this._manifest.getMaximumSafePosition(); + } + return lastAudioPosition; + } else { + const lastAudioPosition = getLastAvailablePositionFromAdaptation( + this._lastAudioAdaptation + ); + const lastVideoPosition = getLastAvailablePositionFromAdaptation( + this._lastVideoAdaptation + ); + if (typeof lastAudioPosition !== "number" || + typeof lastVideoPosition !== "number") + { + return this._manifest.getMaximumSafePosition(); + } else { + return Math.min(lastAudioPosition, lastVideoPosition); + } + } + } + +/** + * Returns an estimate of the actual ending position once + * the full content is available. + * Returns `undefined` if that could not be determined, for various reasons. + * @returns {number|undefined} + */ + public getEndingPosition() : number | undefined { + if (!this._manifest.isDynamic) { + return this.getMaximumAvailablePosition(); + } + if (this._lastVideoAdaptation === undefined || + this._lastAudioAdaptation === undefined) + { + return undefined; + } else if (this._lastAudioAdaptation === null) { + if (this._lastVideoAdaptation === null) { + return undefined; + } else { + return getEndingPositionFromAdaptation(this._lastVideoAdaptation) ?? + undefined; + } + } else if (this._lastVideoAdaptation === null) { + return getEndingPositionFromAdaptation(this._lastAudioAdaptation) ?? + undefined; + } else { + const lastAudioPosition = + getEndingPositionFromAdaptation(this._lastAudioAdaptation); + const lastVideoPosition = + getEndingPositionFromAdaptation(this._lastVideoAdaptation); + if (typeof lastAudioPosition !== "number" || + typeof lastVideoPosition !== "number") + { + return undefined; + } else { + return Math.min(lastAudioPosition, lastVideoPosition); + } + } + } +} + +/** + * Returns last currently available position from the Adaptation given. + * `undefined` if a time could not be found. + * `null` if the Adaptation has no segments (it could be that it didn't started or + * that it already finished for example). + * + * We consider the earliest last available position from every Representation + * in the given Adaptation. + * @param {Object} adaptation + * @returns {Number|undefined|null} + */ +function getLastAvailablePositionFromAdaptation( + adaptation: Adaptation +) : number | undefined | null { + const { representations } = adaptation; + let min : null | number = null; + + /** + * Some Manifest parsers use the exact same `IRepresentationIndex` reference + * for each Representation of a given Adaptation, because in the actual source + * Manifest file, indexing data is often defined at Adaptation-level. + * This variable allows to optimize the logic here when this is the case. + */ + let lastIndex : IRepresentationIndex | undefined; + for (let i = 0; i < representations.length; i++) { + if (representations[i].index !== lastIndex) { + lastIndex = representations[i].index; + const lastPosition = representations[i].index.getLastAvailablePosition(); + if (lastPosition === undefined) { // we cannot tell + return undefined; + } + if (lastPosition !== null) { + min = isNullOrUndefined(min) ? lastPosition : + Math.min(min, lastPosition); + } + } + } + return min; +} + +/** + * Returns ending time from the Adaptation given, once all its segments are + * available. + * `undefined` if a time could not be found. + * `null` if the Adaptation has no segments (it could be that it already + * finished for example). + * + * We consider the earliest ending time from every Representation in the given + * Adaptation. + * @param {Object} adaptation + * @returns {Number|undefined|null} + */ +function getEndingPositionFromAdaptation( + adaptation: Adaptation +) : number | undefined | null { + const { representations } = adaptation; + let min : null | number = null; + + /** + * Some Manifest parsers use the exact same `IRepresentationIndex` reference + * for each Representation of a given Adaptation, because in the actual source + * Manifest file, indexing data is often defined at Adaptation-level. + * This variable allows to optimize the logic here when this is the case. + */ + let lastIndex : IRepresentationIndex | undefined; + for (let i = 0; i < representations.length; i++) { + if (representations[i].index !== lastIndex) { + lastIndex = representations[i].index; + const lastPosition = representations[i].index.getEnd(); + if (lastPosition === undefined) { // we cannot tell + return undefined; + } + if (lastPosition !== null) { + min = isNullOrUndefined(min) ? lastPosition : + Math.min(min, lastPosition); + } + } + } + return min; +} + +interface IActiveStreamsInfo { + /** + * Active Periods being currently actively loaded by the "Streams". + * That is: either this Period's corresponding `Representation` has been + * selected or we didn't chose any `Adaptation` for that type), in + * chronological order. + * + * The first chronological Period in that list is the active one for + * the current type. + */ + activePeriods : SortedList; + /** + * If `true` the last segment for the last currently known Period has been + * pushed for the current Adaptation and Representation choice. + */ + hasFinishedLoadingLastPeriod : boolean; +} + +export type IContentTimeObserverPlaybackObservation = + Pick; diff --git a/src/core/init/create_media_source.ts b/src/core/init/utils/create_media_source.ts similarity index 52% rename from src/core/init/create_media_source.ts rename to src/core/init/utils/create_media_source.ts index 554fe9b132..b1792511cd 100644 --- a/src/core/init/create_media_source.ts +++ b/src/core/init/utils/create_media_source.ts @@ -14,23 +14,16 @@ * limitations under the License. */ -import { - map, - mergeMap, - Observable, - Observer, - take, -} from "rxjs"; import { clearElementSrc, events, MediaSource_, -} from "../../compat"; -import { MediaError } from "../../errors"; -import log from "../../log"; -import isNonEmptyString from "../../utils/is_non_empty_string"; - -const { onSourceOpen$ } = events; +} from "../../../compat"; +import { MediaError } from "../../../errors"; +import log from "../../../log"; +import createCancellablePromise from "../../../utils/create_cancellable_promise"; +import isNonEmptyString from "../../../utils/is_non_empty_string"; +import { CancellationSignal } from "../../../utils/task_canceller"; /** * Dispose of ressources taken by the MediaSource: @@ -81,63 +74,63 @@ export function resetMediaSource( } /** - * Create, on subscription, a MediaSource instance and attach it to the given - * mediaElement element's src attribute. - * - * Returns an Observable which emits the MediaSource when created and attached - * to the mediaElement element. - * This Observable never completes. It can throw if MediaSource is not - * available in the current environment. + * Create a MediaSource instance and attach it to the given mediaElement element's + * src attribute. * - * On unsubscription, the mediaElement.src is cleaned, MediaSource SourceBuffers - * are aborted and some minor cleaning is done. + * Returns a Promise which resolves with the MediaSource when created and attached + * to the `mediaElement` element. * + * When the given `unlinkSignal` emits, mediaElement.src is cleaned, MediaSource + * SourceBuffers are aborted and some minor cleaning is done. * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {Object} unlinkSignal + * @returns {MediaSource} */ function createMediaSource( - mediaElement : HTMLMediaElement -) : Observable { - return new Observable((observer : Observer) => { - if (MediaSource_ == null) { - throw new MediaError("MEDIA_SOURCE_NOT_SUPPORTED", - "No MediaSource Object was found in the current browser."); - } + mediaElement : HTMLMediaElement, + unlinkSignal : CancellationSignal +) : MediaSource { + if (MediaSource_ == null) { + throw new MediaError("MEDIA_SOURCE_NOT_SUPPORTED", + "No MediaSource Object was found in the current browser."); + } - // make sure the media has been correctly reset - const oldSrc = isNonEmptyString(mediaElement.src) ? mediaElement.src : - null; - resetMediaSource(mediaElement, null, oldSrc); + // make sure the media has been correctly reset + const oldSrc = isNonEmptyString(mediaElement.src) ? mediaElement.src : + null; + resetMediaSource(mediaElement, null, oldSrc); - log.info("Init: Creating MediaSource"); - const mediaSource = new MediaSource_(); - const objectURL = URL.createObjectURL(mediaSource); + log.info("Init: Creating MediaSource"); + const mediaSource = new MediaSource_(); + const objectURL = URL.createObjectURL(mediaSource); - log.info("Init: Attaching MediaSource URL to the media element", objectURL); - mediaElement.src = objectURL; + log.info("Init: Attaching MediaSource URL to the media element", objectURL); + mediaElement.src = objectURL; - observer.next(mediaSource); - return () => { - resetMediaSource(mediaElement, mediaSource, objectURL); - }; + unlinkSignal.register(() => { + resetMediaSource(mediaElement, mediaSource, objectURL); }); + return mediaSource; } /** * Create and open a new MediaSource object on the given media element. - * Emit the MediaSource when done. + * Resolves with the MediaSource when done. + * + * When the given `unlinkSignal` emits, mediaElement.src is cleaned, MediaSource + * SourceBuffers are aborted and some minor cleaning is done. * @param {HTMLMediaElement} mediaElement - * @returns {Observable} + * @param {Object} unlinkSignal + * @returns {Promise} */ export default function openMediaSource( - mediaElement : HTMLMediaElement -) : Observable { - return createMediaSource(mediaElement).pipe( - mergeMap(mediaSource => { - return onSourceOpen$(mediaSource).pipe( - take(1), - map(() => mediaSource) - ); - }) - ); + mediaElement : HTMLMediaElement, + unlinkSignal : CancellationSignal +) : Promise { + return createCancellablePromise(unlinkSignal, (resolve) => { + const mediaSource = createMediaSource(mediaElement, unlinkSignal); + events.onSourceOpen(mediaSource, () => { + resolve(mediaSource); + }, unlinkSignal); + }); } diff --git a/src/core/init/create_stream_playback_observer.ts b/src/core/init/utils/create_stream_playback_observer.ts similarity index 93% rename from src/core/init/create_stream_playback_observer.ts rename to src/core/init/utils/create_stream_playback_observer.ts index 225e668f05..263dd092ff 100644 --- a/src/core/init/create_stream_playback_observer.ts +++ b/src/core/init/utils/create_stream_playback_observer.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import Manifest from "../../manifest"; +import Manifest from "../../../manifest"; import createSharedReference, { IReadOnlySharedReference, -} from "../../utils/reference"; -import { CancellationSignal } from "../../utils/task_canceller"; +} from "../../../utils/reference"; +import { CancellationSignal } from "../../../utils/task_canceller"; import { IPlaybackObservation, IReadOnlyPlaybackObserver, PlaybackObserver, -} from "../api"; -import { IStreamOrchestratorPlaybackObservation } from "../stream"; +} from "../../api"; +import { IStreamOrchestratorPlaybackObservation } from "../../stream"; /** Arguments needed to create the Stream's version of the PlaybackObserver. */ export interface IStreamPlaybackObserverArguments { @@ -60,7 +60,8 @@ export default function createStreamPlaybackObserver( observationRef : IReadOnlySharedReference, cancellationSignal : CancellationSignal ) : IReadOnlySharedReference { - const newRef = createSharedReference(constructStreamPlaybackObservation()); + const newRef = createSharedReference(constructStreamPlaybackObservation(), + cancellationSignal); speed.onUpdate(emitStreamPlaybackObservation, { clearSignal: cancellationSignal, @@ -71,11 +72,6 @@ export default function createStreamPlaybackObserver( clearSignal: cancellationSignal, emitCurrentValue: false, }); - - cancellationSignal.register(() => { - newRef.finish(); - }); - return newRef; function constructStreamPlaybackObservation() { diff --git a/src/core/init/utils/end_of_stream.ts b/src/core/init/utils/end_of_stream.ts new file mode 100644 index 0000000000..62abb5cdd9 --- /dev/null +++ b/src/core/init/utils/end_of_stream.ts @@ -0,0 +1,107 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { events } from "../../../compat"; +import log from "../../../log"; +import TaskCanceller, { + CancellationSignal, +} from "../../../utils/task_canceller"; + +const { onRemoveSourceBuffers, + onSourceOpen, + onSourceBufferUpdate } = events; + +/** + * Get "updating" SourceBuffers from a SourceBufferList. + * @param {SourceBufferList} sourceBuffers + * @returns {Array.} + */ +function getUpdatingSourceBuffers(sourceBuffers : SourceBufferList) : SourceBuffer[] { + const updatingSourceBuffers : SourceBuffer[] = []; + for (let i = 0; i < sourceBuffers.length; i++) { + const SourceBuffer = sourceBuffers[i]; + if (SourceBuffer.updating) { + updatingSourceBuffers.push(SourceBuffer); + } + } + return updatingSourceBuffers; +} + +/** + * Trigger the `endOfStream` method of a MediaSource. + * + * If the MediaSource is ended/closed, do not call this method. + * If SourceBuffers are updating, wait for them to be updated before closing + * it. + * @param {MediaSource} mediaSource + * @param {Object} cancelSignal + */ +export default function triggerEndOfStream( + mediaSource : MediaSource, + cancelSignal : CancellationSignal +) : void { + log.debug("Init: Trying to call endOfStream"); + if (mediaSource.readyState !== "open") { + log.debug("Init: MediaSource not open, cancel endOfStream"); + return ; + } + + const { sourceBuffers } = mediaSource; + const updatingSourceBuffers = getUpdatingSourceBuffers(sourceBuffers); + + if (updatingSourceBuffers.length === 0) { + log.info("Init: Triggering end of stream"); + mediaSource.endOfStream(); + return ; + } + + log.debug("Init: Waiting SourceBuffers to be updated before calling endOfStream."); + + const innerCanceller = new TaskCanceller(); + innerCanceller.linkToSignal(cancelSignal); + for (const sourceBuffer of updatingSourceBuffers) { + onSourceBufferUpdate(sourceBuffer, () => { + innerCanceller.cancel(); + triggerEndOfStream(mediaSource, cancelSignal); + }, innerCanceller.signal); + } + + onRemoveSourceBuffers(sourceBuffers, () => { + innerCanceller.cancel(); + triggerEndOfStream(mediaSource, cancelSignal); + }, innerCanceller.signal); +} + +/** + * Trigger the `endOfStream` method of a MediaSource each times it opens. + * @see triggerEndOfStream + * @param {MediaSource} mediaSource + * @param {Object} cancelSignal + */ +export function maintainEndOfStream( + mediaSource : MediaSource, + cancelSignal : CancellationSignal +) : void { + let endOfStreamCanceller = new TaskCanceller(); + endOfStreamCanceller.linkToSignal(cancelSignal); + onSourceOpen(mediaSource, () => { + endOfStreamCanceller.cancel(); + endOfStreamCanceller = new TaskCanceller(); + endOfStreamCanceller.linkToSignal(cancelSignal); + triggerEndOfStream(mediaSource, endOfStreamCanceller.signal); + }, cancelSignal); + triggerEndOfStream(mediaSource, endOfStreamCanceller.signal); +} diff --git a/src/core/init/get_initial_time.ts b/src/core/init/utils/get_initial_time.ts similarity index 96% rename from src/core/init/get_initial_time.ts rename to src/core/init/utils/get_initial_time.ts index f5274793c7..1c682937c6 100644 --- a/src/core/init/get_initial_time.ts +++ b/src/core/init/utils/get_initial_time.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import config from "../../config"; -import log from "../../log"; -import Manifest from "../../manifest"; -import isNullOrUndefined from "../../utils/is_null_or_undefined"; +import config from "../../../config"; +import log from "../../../log"; +import Manifest from "../../../manifest"; +import isNullOrUndefined from "../../../utils/is_null_or_undefined"; /** * All possible initial time options that can be set. diff --git a/src/core/init/utils/get_loaded_reference.ts b/src/core/init/utils/get_loaded_reference.ts new file mode 100644 index 0000000000..f800e4da53 --- /dev/null +++ b/src/core/init/utils/get_loaded_reference.ts @@ -0,0 +1,77 @@ + +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + shouldValidateMetadata, + shouldWaitForDataBeforeLoaded, +} from "../../../compat"; +import createSharedReference, { + IReadOnlySharedReference, +} from "../../../utils/reference"; +import TaskCanceller, { CancellationSignal } from "../../../utils/task_canceller"; +import { + IPlaybackObservation, + IReadOnlyPlaybackObserver, +} from "../../api"; + +/** + * Returns an `IReadOnlySharedReference` that switches to `true` once the + * content is considered loaded (i.e. once it can begin to be played). + * @param {Object} playbackObserver + * @param {HTMLMediaElement} mediaElement + * @param {boolean} isDirectfile - `true` if this is a directfile content + * @param {Object} cancelSignal + * @returns {Object} + */ +export default function getLoadedReference( + playbackObserver : IReadOnlyPlaybackObserver, + mediaElement : HTMLMediaElement, + isDirectfile : boolean, + cancelSignal : CancellationSignal +) : IReadOnlySharedReference { + const listenCanceller = new TaskCanceller(); + listenCanceller.linkToSignal(cancelSignal); + const isLoaded = createSharedReference(false, listenCanceller.signal); + playbackObserver.listen((observation) => { + if (observation.rebuffering !== null || + observation.freezing !== null || + observation.readyState === 0) + { + return ; + } + + if (!shouldWaitForDataBeforeLoaded(isDirectfile, + mediaElement.hasAttribute("playsinline"))) + { + if (mediaElement.duration > 0) { + isLoaded.setValue(true); + listenCanceller.cancel(); + return; + } + } + + if (observation.readyState >= 3 && observation.currentRange !== null) { + if (!shouldValidateMetadata() || mediaElement.duration > 0) { + isLoaded.setValue(true); + listenCanceller.cancel(); + return; + } + } + }, { includeLastObservation: true, clearSignal: listenCanceller.signal }); + + return isLoaded; +} diff --git a/src/core/init/utils/initial_seek_and_play.ts b/src/core/init/utils/initial_seek_and_play.ts new file mode 100644 index 0000000000..630ca739af --- /dev/null +++ b/src/core/init/utils/initial_seek_and_play.ts @@ -0,0 +1,183 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { shouldValidateMetadata } from "../../../compat"; +import { READY_STATES } from "../../../compat/browser_compatibility_types"; +import { MediaError } from "../../../errors"; +import log from "../../../log"; +import { IPlayerError } from "../../../public_types"; +import { + createSharedReference, + IReadOnlySharedReference, +} from "../../../utils/reference"; +import { CancellationError, CancellationSignal } from "../../../utils/task_canceller"; +import { PlaybackObserver } from "../../api"; + +/** Event emitted when trying to perform the initial `play`. */ +export type IInitialPlayEvent = + /** Autoplay is not enabled, but all required steps to do so are there. */ + { type: "skipped" } | + /** + * Tried to play, but autoplay is blocked by the browser. + * A corresponding warning should have already been sent. + */ + { type: "autoplay-blocked" } | + /** Autoplay was done with success. */ + { type: "autoplay" }; + +/** Object returned by `initialSeekAndPlay`. */ +export interface IInitialSeekAndPlayObject { + /** Emit the result of the auto-play operation, once performed. */ + autoPlayResult : Promise; + + /** + * Shared reference whose value becomes `true` once the initial seek has + * been considered / has been done by `performInitialSeekAndPlay`. + */ + initialSeekPerformed : IReadOnlySharedReference; + + /** + * Shared reference whose value becomes `true` once the initial play has + * been considered / has been done by `performInitialSeekAndPlay`. + */ + initialPlayPerformed: IReadOnlySharedReference; +} + +/** + * Seek as soon as possible at the initially wanted position and play if + * autoPlay is wanted. + * @param {HTMLMediaElement} mediaElement + * @param {Object} playbackObserver + * @param {number|Function} startTime + * @param {boolean} mustAutoPlay + * @param {Function} onWarning + * @param {Object} cancelSignal + * @returns {Object} + */ +export default function performInitialSeekAndPlay( + mediaElement : HTMLMediaElement, + playbackObserver : PlaybackObserver, + startTime : number|(() => number), + mustAutoPlay : boolean, + onWarning : (err : IPlayerError) => void, + cancelSignal : CancellationSignal +) : IInitialSeekAndPlayObject { + let resolveAutoPlay : (x : IInitialPlayEvent) => void; + let rejectAutoPlay : (x : unknown) => void; + const autoPlayResult = new Promise((res, rej) => { + resolveAutoPlay = res; + rejectAutoPlay = rej; + }); + + const initialSeekPerformed = createSharedReference(false, cancelSignal); + const initialPlayPerformed = createSharedReference(false, cancelSignal); + + mediaElement.addEventListener("loadedmetadata", onLoadedMetadata); + if (mediaElement.readyState >= READY_STATES.HAVE_METADATA) { + onLoadedMetadata(); + } + + const deregisterCancellation = cancelSignal.register((err : CancellationError) => { + mediaElement.removeEventListener("loadedmetadata", onLoadedMetadata); + rejectAutoPlay(err); + }); + + return { autoPlayResult, initialPlayPerformed, initialSeekPerformed }; + + function onLoadedMetadata() { + mediaElement.removeEventListener("loadedmetadata", onLoadedMetadata); + const initialTime = typeof startTime === "function" ? startTime() : + startTime; + log.info("Init: Set initial time", initialTime); + playbackObserver.setCurrentTime(initialTime); + initialSeekPerformed.setValue(true); + initialSeekPerformed.finish(); + + if (shouldValidateMetadata() && mediaElement.duration === 0) { + const error = new MediaError("MEDIA_ERR_NOT_LOADED_METADATA", + "Cannot load automatically: your browser " + + "falsely announced having loaded the content."); + onWarning(error); + } + if (cancelSignal.isCancelled()) { + return ; + } + + playbackObserver.listen((observation, stopListening) => { + if (!observation.seeking && + observation.rebuffering === null && + observation.readyState >= 1) + { + stopListening(); + onPlayable(); + } + }, { includeLastObservation: true, clearSignal: cancelSignal }); + } + + function onPlayable() { + log.info("Init: Can begin to play content"); + if (!mustAutoPlay) { + if (mediaElement.autoplay) { + log.warn("Init: autoplay is enabled on HTML media element. " + + "Media will play as soon as possible."); + } + initialPlayPerformed.setValue(true); + initialPlayPerformed.finish(); + deregisterCancellation(); + return resolveAutoPlay({ type: "skipped" as const }); + } + + let playResult : Promise; + try { + playResult = mediaElement.play() ?? Promise.resolve(); + } catch (playError) { + deregisterCancellation(); + return rejectAutoPlay(playError); + } + playResult + .then(() => { + if (cancelSignal.isCancelled()) { + return; + } + initialPlayPerformed.setValue(true); + initialPlayPerformed.finish(); + deregisterCancellation(); + return resolveAutoPlay({ type: "autoplay" as const }); + }) + .catch((playError : unknown) => { + deregisterCancellation(); + if (cancelSignal.isCancelled()) { + return; + } + if (playError instanceof Error && playError.name === "NotAllowedError") { + // auto-play was probably prevented. + log.warn("Init: Media element can't play." + + " It may be due to browser auto-play policies."); + + const error = new MediaError("MEDIA_ERR_BLOCKED_AUTOPLAY", + "Cannot trigger auto-play automatically: " + + "your browser does not allow it."); + onWarning(error); + if (cancelSignal.isCancelled()) { + return; + } + return resolveAutoPlay({ type: "autoplay-blocked" as const }); + } else { + rejectAutoPlay(playError); + } + }); + } +} diff --git a/src/core/init/utils/initialize_content_decryption.ts b/src/core/init/utils/initialize_content_decryption.ts new file mode 100644 index 0000000000..263948b8c1 --- /dev/null +++ b/src/core/init/utils/initialize_content_decryption.ts @@ -0,0 +1,179 @@ +import { hasEMEAPIs } from "../../../compat"; +import { EncryptedMediaError } from "../../../errors"; +import log from "../../../log"; +import { + IKeySystemOption, + IPlayerError, +} from "../../../public_types"; +import createSharedReference, { + IReadOnlySharedReference, + ISharedReference, +} from "../../../utils/reference"; +import TaskCanceller, { + CancellationSignal, +} from "../../../utils/task_canceller"; +import ContentDecryptor, { + ContentDecryptorState, + IContentProtection, +} from "../../decrypt"; + +/** + * Initialize content decryption capabilities on the given `HTMLMediaElement`. + * + * You can call this function even if you don't want decrytpion capabilities, in + * which case you can just set the `keySystems` option as an empty array. + * In this situation, the returned object will directly correspond to an + * "`initialized`" state and the `onError` callback will be triggered as soon + * as protection information is received. + * + * @param {HTMLMediaElement} mediaElement - `HTMLMediaElement` on which content + * decryption may be wanted. + * @param {Array.} keySystems - Key system configuration(s) wanted + * Empty array if no content decryption capability is wanted. + * @param {Object} protectionRef - Reference through which content + * protection initialization data will be sent through. + * @param {Object} callbacks - Callbacks called at various decryption-related + * events. + * @param {Object} cancelSignal - When that signal emits, this function will + * stop listening to various events as well as items sent through the + * `protectionRef` parameter. + * @returns {Object} - Reference emitting the current status regarding DRM + * initialization. + */ +export default function initializeContentDecryption( + mediaElement : HTMLMediaElement, + keySystems : IKeySystemOption[], + protectionRef : IReadOnlySharedReference, + callbacks : { onWarning : (err : IPlayerError) => void; + onError : (err : Error) => void; }, + cancelSignal : CancellationSignal +) : IReadOnlySharedReference { + if (keySystems.length === 0) { + protectionRef.onUpdate((data, stopListening) => { + if (data === null) { // initial value + return; + } + stopListening(); + log.error("Init: Encrypted event but EME feature not activated"); + const err = new EncryptedMediaError("MEDIA_IS_ENCRYPTED_ERROR", + "EME feature not activated."); + callbacks.onError(err); + }, { clearSignal: cancelSignal }); + const ref = createSharedReference({ + initializationState: { type: "initialized" as const, value: null }, + drmSystemId: undefined }); + ref.finish(); // We know that no new value will be triggered + return ref; + } else if (!hasEMEAPIs()) { + protectionRef.onUpdate((data, stopListening) => { + if (data === null) { // initial value + return; + } + stopListening(); + log.error("Init: Encrypted event but no EME API available"); + const err = new EncryptedMediaError("MEDIA_IS_ENCRYPTED_ERROR", + "Encryption APIs not found."); + callbacks.onError(err); + }, { clearSignal: cancelSignal }); + const ref = createSharedReference({ + initializationState: { type: "initialized" as const, value: null }, + drmSystemId: undefined }); + ref.finish(); // We know that no new value will be triggered + return ref; + } + + const decryptorCanceller = new TaskCanceller(); + decryptorCanceller.linkToSignal(cancelSignal); + const drmStatusRef = createSharedReference({ + initializationState: { type: "uninitialized", value: null }, + drmSystemId: undefined, + }, cancelSignal); + + log.debug("Init: Creating ContentDecryptor"); + const contentDecryptor = new ContentDecryptor(mediaElement, keySystems); + + contentDecryptor.addEventListener("stateChange", (state) => { + if (state === ContentDecryptorState.WaitingForAttachment) { + + const isMediaLinked = createSharedReference(false); + isMediaLinked.onUpdate((isAttached, stopListening) => { + if (isAttached) { + stopListening(); + if (state === ContentDecryptorState.WaitingForAttachment) { + contentDecryptor.attach(); + } + } + }, { clearSignal: decryptorCanceller.signal }); + drmStatusRef.setValue({ initializationState: { type: "awaiting-media-link", + value: { isMediaLinked } }, + drmSystemId: contentDecryptor.systemId }); + } else if (state === ContentDecryptorState.ReadyForContent) { + drmStatusRef.setValue({ initializationState: { type: "initialized", + value: null }, + drmSystemId: contentDecryptor.systemId }); + contentDecryptor.removeEventListener("stateChange"); + } + }); + + contentDecryptor.addEventListener("error", (error) => { + decryptorCanceller.cancel(); + callbacks.onError(error); + }); + + contentDecryptor.addEventListener("warning", (error) => { + callbacks.onWarning(error); + }); + + protectionRef.onUpdate((data) => { + if (data === null) { + return; + } + contentDecryptor.onInitializationData(data); + }, { clearSignal: decryptorCanceller.signal }); + + decryptorCanceller.signal.register(() => { + contentDecryptor.dispose(); + }); + + return drmStatusRef; +} + +/** Status of content decryption initialization. */ +interface IDrmInitializationStatus { + /** Current initialization state the decryption logic is in. */ + initializationState : IDecryptionInitializationState; + /** + * If set, corresponds to the hex string describing the current key system + * used. + * `undefined` if unknown or if it does not apply. + */ + drmSystemId : string | undefined; +} + +/** Initialization steps to add decryption capabilities to an `HTMLMediaElement`. */ +type IDecryptionInitializationState = + /** + * Decryption capabilities have not been initialized yet. + * You should wait before performing any action on the concerned + * `HTMLMediaElement` (such as linking a content / `MediaSource` to it). + */ + { type: "uninitialized"; value: null } | + /** + * The `MediaSource` or media url has to be linked to the `HTMLMediaElement` + * before continuing. + * Once it has been linked with success (e.g. the `MediaSource` has "opened"), + * the `isMediaLinked` `ISharedReference` should be set to `true`. + * + * In the `MediaSource` case, you should wait until the `"initialized"` + * state before pushing segment. + * + * Note that the `"awaiting-media-link"` is an optional state. It can be + * skipped to directly `"initialized"` instead. + */ + { type: "awaiting-media-link"; + value: { isMediaLinked : ISharedReference }; } | + /** + * The `MediaSource` or media url can be linked AND segments can be pushed to + * the `HTMLMediaElement` on which decryption capabilities were wanted. + */ + { type: "initialized"; value: null }; diff --git a/src/core/init/utils/media_duration_updater.ts b/src/core/init/utils/media_duration_updater.ts new file mode 100644 index 0000000000..11039e066b --- /dev/null +++ b/src/core/init/utils/media_duration_updater.ts @@ -0,0 +1,357 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + onSourceOpen, + onSourceEnded, + onSourceClose, +} from "../../../compat/event_listeners"; +import log from "../../../log"; +import Manifest from "../../../manifest"; +import createSharedReference, { + IReadOnlySharedReference, + ISharedReference, +} from "../../../utils/reference"; +import TaskCanceller, { + CancellationSignal, +} from "../../../utils/task_canceller"; + +/** Number of seconds in a regular year. */ +const YEAR_IN_SECONDS = 365 * 24 * 3600; + +/** + * Keep the MediaSource's duration up-to-date with what is being played. + * @class MediaDurationUpdater + */ +export default class MediaDurationUpdater { + private _canceller : TaskCanceller; + + /** + * The last known audio Adaptation (i.e. track) chosen for the last Period. + * Useful to determinate the duration of the current content. + * `undefined` if the audio track for the last Period has never been known yet. + * `null` if there are no chosen audio Adaptation. + */ + private _currentKnownDuration : ISharedReference; + + /** + * Create a new `MediaDurationUpdater` that will keep the given MediaSource's + * duration as soon as possible. + * This duration will be updated until the `stop` method is called. + * @param {Object} manifest - The Manifest currently played. + * For another content, you will have to create another `MediaDurationUpdater`. + * @param {MediaSource} mediaSource - The MediaSource on which the content is + * pushed. + */ + constructor(manifest : Manifest, mediaSource : MediaSource) { + const canceller = new TaskCanceller(); + const currentKnownDuration = createSharedReference(undefined, canceller.signal); + + this._canceller = canceller; + this._currentKnownDuration = currentKnownDuration; + + const isMediaSourceOpened = createMediaSourceOpenReference(mediaSource, + this._canceller.signal); + + /** TaskCanceller triggered each time the MediaSource open status changes. */ + let msUpdateCanceller = new TaskCanceller(); + msUpdateCanceller.linkToSignal(this._canceller.signal); + + isMediaSourceOpened.onUpdate(onMediaSourceOpenedStatusChanged, + { emitCurrentValue: true, + clearSignal: this._canceller.signal }); + + + function onMediaSourceOpenedStatusChanged() { + msUpdateCanceller.cancel(); + if (!isMediaSourceOpened.getValue()) { + return; + } + msUpdateCanceller = new TaskCanceller(); + msUpdateCanceller.linkToSignal(canceller.signal); + + /** TaskCanceller triggered each time the content's duration may have changed */ + let durationChangeCanceller = new TaskCanceller(); + durationChangeCanceller.linkToSignal(msUpdateCanceller.signal); + + const reSetDuration = () => { + durationChangeCanceller.cancel(); + durationChangeCanceller = new TaskCanceller(); + durationChangeCanceller.linkToSignal(msUpdateCanceller.signal); + onDurationMayHaveChanged(durationChangeCanceller.signal); + }; + + currentKnownDuration.onUpdate(reSetDuration, + { emitCurrentValue: false, + clearSignal: msUpdateCanceller.signal }); + + manifest.addEventListener("manifestUpdate", + reSetDuration, + msUpdateCanceller.signal); + + onDurationMayHaveChanged(durationChangeCanceller.signal); + } + + function onDurationMayHaveChanged(cancelSignal : CancellationSignal) { + const areSourceBuffersUpdating = createSourceBuffersUpdatingReference( + mediaSource.sourceBuffers, + cancelSignal + ); + + /** TaskCanceller triggered each time SourceBuffers' updating status changes */ + let sourceBuffersUpdatingCanceller = new TaskCanceller(); + sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal); + return areSourceBuffersUpdating.onUpdate((areUpdating) => { + sourceBuffersUpdatingCanceller.cancel(); + sourceBuffersUpdatingCanceller = new TaskCanceller(); + sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal); + if (areUpdating) { + return; + } + recursivelyForceDurationUpdate(mediaSource, + manifest, + currentKnownDuration.getValue(), + cancelSignal); + }, { clearSignal: cancelSignal, emitCurrentValue: true }); + } + } + + /** + * By default, the `MediaDurationUpdater` only set a safe estimate for the + * MediaSource's duration. + * A more precize duration can be set by communicating to it a more precize + * media duration through `updateKnownDuration`. + * If the duration becomes unknown, `undefined` can be given to it so the + * `MediaDurationUpdater` goes back to a safe estimate. + * @param {number | undefined} newDuration + */ + public updateKnownDuration( + newDuration : number | undefined + ) : void { + this._currentKnownDuration.setValueIfChanged(newDuration); + } + + /** + * Stop the `MediaDurationUpdater` from updating and free its resources. + * Once stopped, it is not possible to start it again, beside creating another + * `MediaDurationUpdater`. + */ + public stop() { + this._canceller.cancel(); + } +} + +/** + * Checks that duration can be updated on the MediaSource, and then + * sets it. + * + * Returns either: + * - the new duration it has been updated to if it has + * - `null` if it hasn'nt been updated + * + * @param {MediaSource} mediaSource + * @param {Object} manifest + * @returns {string} + */ +function setMediaSourceDuration( + mediaSource: MediaSource, + manifest: Manifest, + knownDuration : number | undefined +) : MediaSourceDurationUpdateStatus { + let newDuration = knownDuration; + + if (newDuration === undefined) { + if (manifest.isDynamic) { + newDuration = manifest.getLivePosition() ?? + manifest.getMaximumSafePosition(); + } else { + newDuration = manifest.getMaximumSafePosition(); + } + } + + if (manifest.isDynamic) { + // Some targets poorly support setting a very high number for durations. + // Yet, in dynamic contents, we would prefer setting a value as high as possible + // to still be able to seek anywhere we want to (even ahead of the Manifest if + // we want to). As such, we put it at a safe default value of 2^32 excepted + // when the maximum position is already relatively close to that value, where + // we authorize exceptionally going over it. + newDuration = Math.max(Math.pow(2, 32), newDuration + YEAR_IN_SECONDS); + } + + let maxBufferedEnd : number = 0; + for (let i = 0; i < mediaSource.sourceBuffers.length; i++) { + const sourceBuffer = mediaSource.sourceBuffers[i]; + const sbBufferedLen = sourceBuffer.buffered.length; + if (sbBufferedLen > 0) { + maxBufferedEnd = Math.max(sourceBuffer.buffered.end(sbBufferedLen - 1)); + } + } + + if (newDuration === mediaSource.duration) { + return MediaSourceDurationUpdateStatus.Success; + } else if (maxBufferedEnd > newDuration) { + // We already buffered further than the duration we want to set. + // Keep the duration that was set at that time as a security. + if (maxBufferedEnd < mediaSource.duration) { + try { + log.info("Init: Updating duration to what is currently buffered", maxBufferedEnd); + mediaSource.duration = maxBufferedEnd; + } catch (err) { + log.warn("Duration Updater: Can't update duration on the MediaSource.", + err instanceof Error ? err : ""); + return MediaSourceDurationUpdateStatus.Failed; + } + } + return MediaSourceDurationUpdateStatus.Partial; + } else { + const oldDuration = mediaSource.duration; + try { + log.info("Init: Updating duration", newDuration); + mediaSource.duration = newDuration; + } catch (err) { + log.warn("Duration Updater: Can't update duration on the MediaSource.", + err instanceof Error ? err : ""); + return MediaSourceDurationUpdateStatus.Failed; + } + const deltaToExpected = Math.abs(mediaSource.duration - newDuration); + if (deltaToExpected >= 0.1) { + const deltaToBefore = Math.abs(mediaSource.duration - oldDuration); + return deltaToExpected < deltaToBefore ? MediaSourceDurationUpdateStatus.Partial : + MediaSourceDurationUpdateStatus.Failed; + } + return MediaSourceDurationUpdateStatus.Success; + } +} + +/** + * String describing the result of the process of updating a MediaSource's + * duration. + */ +const enum MediaSourceDurationUpdateStatus { + /** The MediaSource's duration has been updated to the asked duration. */ + Success = "success", + /** + * The MediaSource's duration has been updated and is now closer to the asked + * duration but is not yet the actually asked duration. + */ + Partial = "partial", + /** + * The MediaSource's duration could not have been updated due to an issue or + * has been updated but to a value actually further from the asked duration + * from what it was before. + */ + Failed = "failed", +} + +/** + * Returns an `ISharedReference` wrapping a boolean that tells if all the + * SourceBuffers ended all pending updates. + * @param {SourceBufferList} sourceBuffers + * @param {Object} cancelSignal + * @returns {Object} + */ +function createSourceBuffersUpdatingReference( + sourceBuffers : SourceBufferList, + cancelSignal : CancellationSignal +) : IReadOnlySharedReference { + if (sourceBuffers.length === 0) { + const notOpenedRef = createSharedReference(false); + notOpenedRef.finish(); + return notOpenedRef; + } + + const areUpdatingRef = createSharedReference(false, cancelSignal); + reCheck(); + + for (let i = 0; i < sourceBuffers.length; i++) { + const sourceBuffer = sourceBuffers[i]; + sourceBuffer.addEventListener("updatestart", reCheck); + sourceBuffer.addEventListener("update", reCheck); + cancelSignal.register(() => { + sourceBuffer.removeEventListener("updatestart", reCheck); + sourceBuffer.removeEventListener("update", reCheck); + }); + } + + return areUpdatingRef; + + function reCheck() { + for (let i = 0; i < sourceBuffers.length; i++) { + const sourceBuffer = sourceBuffers[i]; + if (sourceBuffer.updating) { + areUpdatingRef.setValueIfChanged(true); + return; + } + } + areUpdatingRef.setValueIfChanged(false); + } +} + +/** + * Returns an `ISharedReference` wrapping a boolean that tells if the media + * source is opened or not. + * @param {MediaSource} mediaSource + * @param {Object} cancelSignal + * @returns {Object} + */ +function createMediaSourceOpenReference( + mediaSource : MediaSource, + cancelSignal : CancellationSignal +): IReadOnlySharedReference { + const isMediaSourceOpen = createSharedReference(mediaSource.readyState === "open", + cancelSignal); + onSourceOpen(mediaSource, () => { + isMediaSourceOpen.setValueIfChanged(true); + }, cancelSignal); + onSourceEnded(mediaSource, () => { + isMediaSourceOpen.setValueIfChanged(false); + }, cancelSignal); + onSourceClose(mediaSource, () => { + isMediaSourceOpen.setValueIfChanged(false); + }, cancelSignal); + return isMediaSourceOpen; +} + +/** + * Immediately tries to set the MediaSource's duration to the most appropriate + * one according to the Manifest and duration given. + * + * If it fails, wait 2 seconds and retries. + * + * @param {MediaSource} mediaSource + * @param {Object} manifest + * @param {number|undefined} duration + * @param {Object} cancelSignal + */ +function recursivelyForceDurationUpdate( + mediaSource : MediaSource, + manifest : Manifest, + duration : number | undefined, + cancelSignal : CancellationSignal +) : void { + const res = setMediaSourceDuration(mediaSource, manifest, duration); + if (res === MediaSourceDurationUpdateStatus.Success) { + return ; + } + const timeoutId = setTimeout(() => { + unregisterClear(); + recursivelyForceDurationUpdate(mediaSource, manifest, duration, cancelSignal); + }, 2000); + const unregisterClear = cancelSignal.register(() => { + clearTimeout(timeoutId); + }); +} diff --git a/src/core/init/rebuffering_controller.ts b/src/core/init/utils/rebuffering_controller.ts similarity index 66% rename from src/core/init/rebuffering_controller.ts rename to src/core/init/utils/rebuffering_controller.ts index d91cf1cba6..f1753b8c2b 100644 --- a/src/core/init/rebuffering_controller.ts +++ b/src/core/init/utils/rebuffering_controller.ts @@ -14,38 +14,24 @@ * limitations under the License. */ -import { - finalize, - ignoreElements, - map, - merge as observableMerge, - Observable, - scan, - tap, - withLatestFrom, -} from "rxjs"; -import isSeekingApproximate from "../../compat/is_seeking_approximate"; -import config from "../../config"; -import { MediaError } from "../../errors"; -import log from "../../log"; +import isSeekingApproximate from "../../../compat/is_seeking_approximate"; +import config from "../../../config"; +import { MediaError } from "../../../errors"; +import log from "../../../log"; import Manifest, { Period, -} from "../../manifest"; -import { getNextRangeGap } from "../../utils/ranges"; -import { IReadOnlySharedReference } from "../../utils/reference"; -import TaskCanceller from "../../utils/task_canceller"; +} from "../../../manifest"; +import { IPlayerError } from "../../../public_types"; +import EventEmitter from "../../../utils/event_emitter"; +import { getNextRangeGap } from "../../../utils/ranges"; +import { IReadOnlySharedReference } from "../../../utils/reference"; +import TaskCanceller from "../../../utils/task_canceller"; import { IPlaybackObservation, PlaybackObserver, -} from "../api"; -import { IBufferType } from "../segment_buffers"; -import EVENTS from "../stream/events_generators"; -import { - IStalledEvent, - IStallingSituation, - IUnstalledEvent, - IWarningEvent, -} from "./types"; +} from "../../api"; +import { IBufferType } from "../../segment_buffers"; +import { IStallingSituation } from "../types"; /** @@ -54,70 +40,6 @@ import { */ const EPSILON = 1 / 60; -export interface ILockedStreamEvent { - /** Buffer type for which no segment will currently load. */ - bufferType : IBufferType; - /** Period for which no segment will currently load. */ - period : Period; -} - -/** - * Event indicating that a discontinuity has been found. - * Each event for a `bufferType` and `period` combination replaces the previous - * one. - */ -export interface IDiscontinuityEvent { - /** Buffer type concerned by the discontinuity. */ - bufferType : IBufferType; - /** Period concerned by the discontinuity. */ - period : Period; - /** - * Close discontinuity time information. - * `null` if no discontinuity has been detected currently for that buffer - * type and Period. - */ - discontinuity : IDiscontinuityTimeInfo | null; - /** - * Position at which the discontinuity was found. - * Can be important for when a current discontinuity's start is unknown. - */ - position : number; -} - -/** Information on a found discontinuity. */ -export interface IDiscontinuityTimeInfo { - /** - * Start time of the discontinuity. - * `undefined` for when the start is unknown but the discontinuity was - * currently encountered at the position we were in when this event was - * created. - */ - start : number | undefined; - /** - * End time of the discontinuity, in seconds. - * If `null`, no further segment can be loaded for the corresponding Period. - */ - end : number | null; -} - -/** - * Internally stored information about a known discontinuity in the audio or - * video buffer. - */ -interface IDiscontinuityStoredInfo { - /** Buffer type concerned by the discontinuity. */ - bufferType : IBufferType; - /** Period concerned by the discontinuity. */ - period : Period; - /** Discontinuity time information. */ - discontinuity : IDiscontinuityTimeInfo; - /** - * Position at which the discontinuity was found. - * Can be important for when a current discontinuity's start is unknown. - */ - position : number; -} - /** * Monitor playback, trying to avoid stalling situation. * If stopping the player to build buffer is needed, temporarily set the @@ -125,105 +47,86 @@ interface IDiscontinuityStoredInfo { * * Emit "stalled" then "unstalled" respectively when an unavoidable stall is * encountered and exited. - * @param {object} playbackObserver - emit the current playback conditions. - * @param {Object} manifest - The Manifest of the currently-played content. - * @param {Object} speed - The last speed set by the user - * @param {Observable} lockedStream$ - Emit information on currently "locked" - * streams. - * @param {Observable} discontinuityUpdate$ - Observable emitting encountered - * discontinuities for loaded Period and buffer types. - * @returns {Observable} */ -export default function RebufferingController( - playbackObserver : PlaybackObserver, - manifest: Manifest | null, - speed : IReadOnlySharedReference, - lockedStream$ : Observable, - discontinuityUpdate$: Observable -) : Observable { - const initialDiscontinuitiesStore : IDiscontinuityStoredInfo[] = []; +export default class RebufferingController + extends EventEmitter { + + /** Emit the current playback conditions */ + private _playbackObserver : PlaybackObserver; + private _manifest : Manifest | null; + private _speed : IReadOnlySharedReference; + private _isStarted : boolean; /** - * Emit every known audio and video buffer discontinuities in chronological + * Every known audio and video buffer discontinuities in chronological * order (first ordered by Period's start, then by bufferType in any order. */ - const discontinuitiesStore$ = discontinuityUpdate$.pipe( - withLatestFrom(playbackObserver.getReference().asObservable()), - scan( - (discontinuitiesStore, [evt, observation]) => - updateDiscontinuitiesStore(discontinuitiesStore, evt, observation), - initialDiscontinuitiesStore)); + private _discontinuitiesStore : IDiscontinuityStoredInfo[]; - /** - * On some devices (right now only seen on Tizen), seeking through the - * `currentTime` property can lead to the browser re-seeking once the - * segments have been loaded to improve seeking performances (for - * example, by seeking right to an intra video frame). - * In that case, we risk being in a conflict with that behavior: if for - * example we encounter a small discontinuity at the position the browser - * seeks to, we will seek over it, the browser would seek back and so on. - * - * This variable allows to store the last known position we were seeking to - * so we can detect when the browser seeked back (to avoid performing another - * seek after that). When browsers seek back to a position behind a - * discontinuity, they are usually able to skip them without our help. - */ - let lastSeekingPosition : number | null = null; + private _canceller : TaskCanceller; /** - * In some conditions (see `lastSeekingPosition`), we might want to not - * automatically seek over discontinuities because the browser might do it - * itself instead. - * In that case, we still want to perform the seek ourselves if the browser - * doesn't do it after sufficient time. - * This variable allows to store the timestamp at which a discontinuity began - * to be ignored. + * @param {object} playbackObserver - emit the current playback conditions. + * @param {Object} manifest - The Manifest of the currently-played content. + * @param {Object} speed - The last speed set by the user */ - let ignoredStallTimeStamp : number | null = null; + constructor( + playbackObserver : PlaybackObserver, + manifest: Manifest | null, + speed : IReadOnlySharedReference + ) { + super(); + this._playbackObserver = playbackObserver; + this._manifest = manifest; + this._speed = speed; + this._discontinuitiesStore = []; + this._isStarted = false; + this._canceller = new TaskCanceller(); + } - let prevFreezingState : { attemptTimestamp : number } | null; + public start() : void { + if (this._isStarted) { + return; + } + this._isStarted = true; + + /** + * On some devices (right now only seen on Tizen), seeking through the + * `currentTime` property can lead to the browser re-seeking once the + * segments have been loaded to improve seeking performances (for + * example, by seeking right to an intra video frame). + * In that case, we risk being in a conflict with that behavior: if for + * example we encounter a small discontinuity at the position the browser + * seeks to, we will seek over it, the browser would seek back and so on. + * + * This variable allows to store the last known position we were seeking to + * so we can detect when the browser seeked back (to avoid performing another + * seek after that). When browsers seek back to a position behind a + * discontinuity, they are usually able to skip them without our help. + */ + let lastSeekingPosition : number | null; + + /** + * In some conditions (see `lastSeekingPosition`), we might want to not + * automatically seek over discontinuities because the browser might do it + * itself instead. + * In that case, we still want to perform the seek ourselves if the browser + * doesn't do it after sufficient time. + * This variable allows to store the timestamp at which a discontinuity began + * to be ignored. + */ + let ignoredStallTimeStamp : number | null = null; + + const playbackRateUpdater = new PlaybackRateUpdater(this._playbackObserver, + this._speed); + this._canceller.signal.register(() => { + playbackRateUpdater.dispose(); + }); - /** - * If we're rebuffering waiting on data of a "locked stream", seek into the - * Period handled by that stream to unlock the situation. - */ - const unlock$ = lockedStream$.pipe( - withLatestFrom(playbackObserver.getReference().asObservable()), - tap(([lockedStreamEvt, observation]) => { - if ( - !observation.rebuffering || - observation.paused || - speed.getValue() <= 0 || ( - lockedStreamEvt.bufferType !== "audio" && - lockedStreamEvt.bufferType !== "video" - ) - ) { - return; - } - const currPos = observation.position; - const rebufferingPos = observation.rebuffering.position ?? currPos; - const lockedPeriodStart = lockedStreamEvt.period.start; - if (currPos < lockedPeriodStart && - Math.abs(rebufferingPos - lockedPeriodStart) < 1) - { - log.warn("Init: rebuffering because of a future locked stream.\n" + - "Trying to unlock by seeking to the next Period"); - playbackObserver.setCurrentTime(lockedPeriodStart + 0.001); - } - }), - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - ignoreElements() - ); - - const playbackRateUpdater = new PlaybackRateUpdater(playbackObserver, speed); - - const stall$ = playbackObserver.getReference().asObservable().pipe( - withLatestFrom(discontinuitiesStore$), - map(([observation, discontinuitiesStore]) => { + let prevFreezingState : { attemptTimestamp : number } | null = null; + + this._playbackObserver.listen((observation) => { + const discontinuitiesStore = this._discontinuitiesStore; const { buffered, position, readyState, @@ -240,9 +143,8 @@ export default function RebufferingController( !observation.seeking && isSeekingApproximate && ignoredStallTimeStamp === null && - lastSeekingPosition !== null && - observation.position < lastSeekingPosition - ) { + lastSeekingPosition !== null && observation.position < lastSeekingPosition) + { log.debug("Init: the device appeared to have seeked back by itself."); const now = performance.now(); ignoredStallTimeStamp = now; @@ -261,8 +163,8 @@ export default function RebufferingController( if (now - referenceTimestamp > UNFREEZING_SEEK_DELAY) { log.warn("Init: trying to seek to un-freeze player"); - playbackObserver.setCurrentTime( - playbackObserver.getCurrentTime() + UNFREEZING_DELTA_POSITION); + this._playbackObserver.setCurrentTime( + this._playbackObserver.getCurrentTime() + UNFREEZING_DELTA_POSITION); prevFreezingState = { attemptTimestamp: now }; } @@ -272,8 +174,8 @@ export default function RebufferingController( } else { playbackRateUpdater.startRebuffering(); } - return { type: "stalled" as const, - value: "freezing" as const }; + this.trigger("stalled", "freezing"); + return; } } else { prevFreezingState = null; @@ -291,11 +193,11 @@ export default function RebufferingController( } else { reason = "not-ready"; } - return { type: "stalled" as const, - value: reason }; + this.trigger("stalled", reason); + return ; } - return { type: "unstalled" as const, - value: null }; + this.trigger("unstalled", null); + return ; } // We want to separate a stall situation when a seek is due to a seek done @@ -310,8 +212,8 @@ export default function RebufferingController( if (now - ignoredStallTimeStamp < FORCE_DISCONTINUITY_SEEK_DELAY) { playbackRateUpdater.stopRebuffering(); log.debug("Init: letting the device get out of a stall by itself"); - return { type: "stalled" as const, - value: stalledReason }; + this.trigger("stalled", stalledReason); + return ; } else { log.warn("Init: ignored stall for too long, checking discontinuity", now - ignoredStallTimeStamp); @@ -321,9 +223,9 @@ export default function RebufferingController( ignoredStallTimeStamp = null; playbackRateUpdater.startRebuffering(); - if (manifest === null) { - return { type: "stalled" as const, - value: stalledReason }; + if (this._manifest === null) { + this.trigger("stalled", stalledReason); + return ; } /** Position at which data is awaited. */ @@ -331,22 +233,23 @@ export default function RebufferingController( if (stalledPosition !== null && stalledPosition !== undefined && - speed.getValue() > 0) + this._speed.getValue() > 0) { const skippableDiscontinuity = findSeekableDiscontinuity(discontinuitiesStore, - manifest, + this._manifest, stalledPosition); if (skippableDiscontinuity !== null) { const realSeekTime = skippableDiscontinuity + 0.001; - if (realSeekTime <= playbackObserver.getCurrentTime()) { + if (realSeekTime <= this._playbackObserver.getCurrentTime()) { log.info("Init: position to seek already reached, no seeking", - playbackObserver.getCurrentTime(), realSeekTime); + this._playbackObserver.getCurrentTime(), realSeekTime); } else { log.warn("SA: skippable discontinuity found in the stream", position, realSeekTime); - playbackObserver.setCurrentTime(realSeekTime); - return EVENTS.warning(generateDiscontinuityError(stalledPosition, - realSeekTime)); + this._playbackObserver.setCurrentTime(realSeekTime); + this.trigger("warning", generateDiscontinuityError(stalledPosition, + realSeekTime)); + return; } } } @@ -362,44 +265,99 @@ export default function RebufferingController( // case of small discontinuity in the content. const nextBufferRangeGap = getNextRangeGap(buffered, freezePosition); if ( - speed.getValue() > 0 && + this._speed.getValue() > 0 && nextBufferRangeGap < BUFFER_DISCONTINUITY_THRESHOLD ) { const seekTo = (freezePosition + nextBufferRangeGap + EPSILON); - if (playbackObserver.getCurrentTime() < seekTo) { + if (this._playbackObserver.getCurrentTime() < seekTo) { log.warn("Init: discontinuity encountered inferior to the threshold", freezePosition, seekTo, BUFFER_DISCONTINUITY_THRESHOLD); - playbackObserver.setCurrentTime(seekTo); - return EVENTS.warning(generateDiscontinuityError(freezePosition, seekTo)); + this._playbackObserver.setCurrentTime(seekTo); + this.trigger("warning", generateDiscontinuityError(freezePosition, seekTo)); + return; } } // Are we in a discontinuity between periods ? -> Seek at the beginning of the // next period - for (let i = manifest.periods.length - 2; i >= 0; i--) { - const period = manifest.periods[i]; + for (let i = this._manifest.periods.length - 2; i >= 0; i--) { + const period = this._manifest.periods[i]; if (period.end !== undefined && period.end <= freezePosition) { - if (manifest.periods[i + 1].start > freezePosition && - manifest.periods[i + 1].start > playbackObserver.getCurrentTime()) + if (this._manifest.periods[i + 1].start > freezePosition && + this._manifest.periods[i + 1].start > + this._playbackObserver.getCurrentTime()) { - const nextPeriod = manifest.periods[i + 1]; - playbackObserver.setCurrentTime(nextPeriod.start); - return EVENTS.warning(generateDiscontinuityError(freezePosition, - nextPeriod.start)); + const nextPeriod = this._manifest.periods[i + 1]; + this._playbackObserver.setCurrentTime(nextPeriod.start); + this.trigger("warning", generateDiscontinuityError(freezePosition, + nextPeriod.start)); + return; } break; } } - return { type: "stalled" as const, - value: stalledReason }; - })); + this.trigger("stalled", stalledReason); + }, { includeLastObservation: true, clearSignal: this._canceller.signal }); + } - return observableMerge(unlock$, stall$) - .pipe(finalize(() => { - playbackRateUpdater.dispose(); - })); + /** + * Update information on an upcoming discontinuity for a given buffer type and + * Period. + * Each new update for the same Period and type overwrites the previous one. + * @param {Object} evt + */ + public updateDiscontinuityInfo(evt: IDiscontinuityEvent) : void { + if (!this._isStarted) { + this.start(); + } + const lastObservation = this._playbackObserver.getReference().getValue(); + updateDiscontinuitiesStore(this._discontinuitiesStore, evt, lastObservation); + } + + /** + * Function to call when a Stream is currently locked, i.e. we cannot load + * segments for the corresponding Period and buffer type until it is seeked + * to. + * @param {string} bufferType - Buffer type for which no segment will + * currently load. + * @param {Object} period - Period for which no segment will currently load. + */ + public onLockedStream(bufferType : IBufferType, period : Period) : void { + if (!this._isStarted) { + this.start(); + } + const observation = this._playbackObserver.getReference().getValue(); + if ( + !observation.rebuffering || + observation.paused || + this._speed.getValue() <= 0 || ( + bufferType !== "audio" && + bufferType !== "video" + ) + ) { + return; + } + const currPos = observation.position; + const rebufferingPos = observation.rebuffering.position ?? currPos; + const lockedPeriodStart = period.start; + if (currPos < lockedPeriodStart && + Math.abs(rebufferingPos - lockedPeriodStart) < 1) + { + log.warn("Init: rebuffering because of a future locked stream.\n" + + "Trying to unlock by seeking to the next Period"); + this._playbackObserver.setCurrentTime(lockedPeriodStart + 0.001); + } + } + + /** + * Stops the `RebufferingController` from montoring stalling situations, + * forever. + */ + public destroy() : void { + this._canceller.cancel(); + } } /** @@ -483,7 +441,7 @@ function updateDiscontinuitiesStore( discontinuitiesStore : IDiscontinuityStoredInfo[], evt : IDiscontinuityEvent, observation : IPlaybackObservation -) : IDiscontinuityStoredInfo[] { +) : void { // First, perform clean-up of old discontinuities while (discontinuitiesStore.length > 0 && discontinuitiesStore[0].period.end !== undefined && @@ -494,7 +452,7 @@ function updateDiscontinuitiesStore( const { period, bufferType } = evt; if (bufferType !== "audio" && bufferType !== "video") { - return discontinuitiesStore; + return ; } for (let i = 0; i < discontinuitiesStore.length; i++) { @@ -505,19 +463,19 @@ function updateDiscontinuitiesStore( } else { discontinuitiesStore[i] = evt; } - return discontinuitiesStore; + return ; } } else if (discontinuitiesStore[i].period.start > period.start) { if (eventContainsDiscontinuity(evt)) { discontinuitiesStore.splice(i, 0, evt); } - return discontinuitiesStore; + return ; } } if (eventContainsDiscontinuity(evt)) { discontinuitiesStore.push(evt); } - return discontinuitiesStore; + return ; } /** @@ -619,3 +577,66 @@ class PlaybackRateUpdater { }, { clearSignal: this._speedUpdateCanceller.signal, emitCurrentValue: true }); } } + +export interface IRebufferingControllerEvent { + stalled : IStallingSituation; + unstalled : null; + warning : IPlayerError; +} + +/** + * Event indicating that a discontinuity has been found. + * Each event for a `bufferType` and `period` combination replaces the previous + * one. + */ +export interface IDiscontinuityEvent { + /** Buffer type concerned by the discontinuity. */ + bufferType : IBufferType; + /** Period concerned by the discontinuity. */ + period : Period; + /** + * Close discontinuity time information. + * `null` if no discontinuity has been detected currently for that buffer + * type and Period. + */ + discontinuity : IDiscontinuityTimeInfo | null; + /** + * Position at which the discontinuity was found. + * Can be important for when a current discontinuity's start is unknown. + */ + position : number; +} + +/** Information on a found discontinuity. */ +export interface IDiscontinuityTimeInfo { + /** + * Start time of the discontinuity. + * `undefined` for when the start is unknown but the discontinuity was + * currently encountered at the position we were in when this event was + * created. + */ + start : number | undefined; + /** + * End time of the discontinuity, in seconds. + * If `null`, no further segment can be loaded for the corresponding Period. + */ + end : number | null; +} + +/** + * Internally stored information about a known discontinuity in the audio or + * video buffer. + */ +interface IDiscontinuityStoredInfo { + /** Buffer type concerned by the discontinuity. */ + bufferType : IBufferType; + /** Period concerned by the discontinuity. */ + period : Period; + /** Discontinuity time information. */ + discontinuity : IDiscontinuityTimeInfo; + /** + * Position at which the discontinuity was found. + * Can be important for when a current discontinuity's start is unknown. + */ + position : number; +} diff --git a/src/core/init/stream_events_emitter/are_same_stream_events.ts b/src/core/init/utils/stream_events_emitter/are_same_stream_events.ts similarity index 100% rename from src/core/init/stream_events_emitter/are_same_stream_events.ts rename to src/core/init/utils/stream_events_emitter/are_same_stream_events.ts diff --git a/src/core/init/stream_events_emitter/index.ts b/src/core/init/utils/stream_events_emitter/index.ts similarity index 80% rename from src/core/init/stream_events_emitter/index.ts rename to src/core/init/utils/stream_events_emitter/index.ts index b51f44a6ed..9f92f7e510 100644 --- a/src/core/init/stream_events_emitter/index.ts +++ b/src/core/init/utils/stream_events_emitter/index.ts @@ -15,20 +15,8 @@ */ import streamEventsEmitter from "./stream_events_emitter"; -import { - IPublicNonFiniteStreamEvent, - IPublicStreamEvent, - IStreamEvent, - IStreamEventEvent, - IStreamEventSkipEvent, -} from "./types"; - export { - IStreamEvent, IPublicNonFiniteStreamEvent, IPublicStreamEvent, - - IStreamEventEvent, - IStreamEventSkipEvent, -}; +} from "./types"; export default streamEventsEmitter; diff --git a/src/core/init/stream_events_emitter/refresh_scheduled_events_list.ts b/src/core/init/utils/stream_events_emitter/refresh_scheduled_events_list.ts similarity index 98% rename from src/core/init/stream_events_emitter/refresh_scheduled_events_list.ts rename to src/core/init/utils/stream_events_emitter/refresh_scheduled_events_list.ts index e1f0474c57..5c3969cb7b 100644 --- a/src/core/init/stream_events_emitter/refresh_scheduled_events_list.ts +++ b/src/core/init/utils/stream_events_emitter/refresh_scheduled_events_list.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import Manifest from "../../../manifest"; +import Manifest from "../../../../manifest"; import areSameStreamEvents from "./are_same_stream_events"; import { INonFiniteStreamEventPayload, diff --git a/src/core/init/utils/stream_events_emitter/stream_events_emitter.ts b/src/core/init/utils/stream_events_emitter/stream_events_emitter.ts new file mode 100644 index 0000000000..53487aa56c --- /dev/null +++ b/src/core/init/utils/stream_events_emitter/stream_events_emitter.ts @@ -0,0 +1,212 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import config from "../../../../config"; +import Manifest from "../../../../manifest"; +import createSharedReference from "../../../../utils/reference"; +import TaskCanceller, { CancellationSignal } from "../../../../utils/task_canceller"; +import { IPlaybackObservation, IReadOnlyPlaybackObserver } from "../../../api"; +import refreshScheduledEventsList from "./refresh_scheduled_events_list"; +import { + INonFiniteStreamEventPayload, + IPublicNonFiniteStreamEvent, + IPublicStreamEvent, + IStreamEventPayload, +} from "./types"; + +/** + * Get events from manifest and emit each time an event has to be emitted + * @param {Object} manifest + * @param {HTMLMediaElement} mediaElement + * @param {Object} playbackObserver + * @param {Function} onEvent + * @param {Function} onEventSkip + * @param {Object} cancelSignal + * @returns {Object} + */ +export default function streamEventsEmitter( + manifest : Manifest, + mediaElement : HTMLMediaElement, + playbackObserver : IReadOnlyPlaybackObserver, + onEvent : (evt : IPublicStreamEvent | IPublicNonFiniteStreamEvent) => void, + onEventSkip : (evt : IPublicStreamEvent | IPublicNonFiniteStreamEvent) => void, + cancelSignal : CancellationSignal +) : void { + const eventsBeingPlayed = + new WeakMap(); + const scheduledEventsRef = createSharedReference(refreshScheduledEventsList([], + manifest), + cancelSignal); + manifest.addEventListener("manifestUpdate", () => { + const prev = scheduledEventsRef.getValue(); + scheduledEventsRef.setValue(refreshScheduledEventsList(prev, manifest)); + }, cancelSignal); + + let isPollingEvents = false; + let cancelCurrentPolling = new TaskCanceller(); + cancelCurrentPolling.linkToSignal(cancelSignal); + + scheduledEventsRef.onUpdate(({ length: scheduledEventsLength }) => { + if (scheduledEventsLength === 0) { + if (isPollingEvents) { + cancelCurrentPolling.cancel(); + cancelCurrentPolling = new TaskCanceller(); + cancelCurrentPolling.linkToSignal(cancelSignal); + isPollingEvents = false; + } + return; + } else if (isPollingEvents) { + return; + } + isPollingEvents = true; + let oldObservation = constructObservation(); + + const { STREAM_EVENT_EMITTER_POLL_INTERVAL } = config.getCurrent(); + const intervalId = setInterval(checkStreamEvents, + STREAM_EVENT_EMITTER_POLL_INTERVAL); + playbackObserver.listen(checkStreamEvents, + { includeLastObservation: false, + clearSignal: cancelCurrentPolling.signal }); + + cancelCurrentPolling.signal.register(() => { + clearInterval(intervalId); + }); + + function checkStreamEvents() { + const newObservation = constructObservation(); + emitStreamEvents(scheduledEventsRef.getValue(), + oldObservation, + newObservation, + cancelCurrentPolling.signal); + oldObservation = newObservation; + } + + function constructObservation() { + const isSeeking = playbackObserver.getReference().getValue().seeking; + return { currentTime: mediaElement.currentTime, + isSeeking }; + } + }, { emitCurrentValue: true, clearSignal: cancelSignal }); + + /** + * Examine playback situation from playback observations to emit stream events and + * prepare set onExit callbacks if needed. + * @param {Array.} scheduledEvents + * @param {Object} oldObservation + * @param {Object} newObservation + * @param {Object} stopSignal + */ + function emitStreamEvents( + scheduledEvents : Array, + oldObservation : { currentTime: number; isSeeking: boolean }, + newObservation : { currentTime: number; isSeeking: boolean }, + stopSignal : CancellationSignal + ) : void { + const { currentTime: previousTime } = oldObservation; + const { isSeeking, currentTime } = newObservation; + const eventsToSend: IStreamEvent[] = []; + const eventsToExit: IPublicStreamEvent[] = []; + + for (let i = 0; i < scheduledEvents.length; i++) { + const event = scheduledEvents[i]; + const start = event.start; + const end = isFiniteStreamEvent(event) ? event.end : + undefined; + const isBeingPlayed = eventsBeingPlayed.has(event); + if (isBeingPlayed) { + if (start > currentTime || + (end !== undefined && currentTime >= end) + ) { + if (isFiniteStreamEvent(event)) { + eventsToExit.push(event.publicEvent); + } + eventsBeingPlayed.delete(event); + } + } else if (start <= currentTime && + end !== undefined && + currentTime < end) { + eventsToSend.push({ type: "stream-event", + value: event.publicEvent }); + eventsBeingPlayed.set(event, true); + } else if (previousTime < start && + currentTime >= (end ?? start)) { + if (isSeeking) { + eventsToSend.push({ type: "stream-event-skip", + value: event.publicEvent }); + } else { + eventsToSend.push({ type: "stream-event", + value: event.publicEvent }); + if (isFiniteStreamEvent(event)) { + eventsToExit.push(event.publicEvent); + } + } + } + } + + if (eventsToSend.length > 0) { + for (const event of eventsToSend) { + if (event.type === "stream-event") { + onEvent(event.value); + } else { + onEventSkip(event.value); + } + if (stopSignal.isCancelled()) { + return; + } + } + } + + if (eventsToExit.length > 0) { + for (const event of eventsToExit) { + if (typeof event.onExit === "function") { + event.onExit(); + } + if (stopSignal.isCancelled()) { + return; + } + } + } + } +} + +/** + * Tells if a stream event has a duration + * @param {Object} evt + * @returns {Boolean} + */ +function isFiniteStreamEvent( + evt: IStreamEventPayload|INonFiniteStreamEventPayload +): evt is IStreamEventPayload { + return (evt as IStreamEventPayload).end !== undefined; +} + +/** Event emitted when a stream event is encountered. */ +interface IStreamEventEvent { + type: "stream-event"; + value: IPublicStreamEvent | + IPublicNonFiniteStreamEvent; +} + +/** Event emitted when a stream event has just been skipped. */ +interface IStreamEventSkipEvent { + type: "stream-event-skip"; + value: IPublicStreamEvent | + IPublicNonFiniteStreamEvent; +} + +/** Events sent by the `streamEventsEmitter`. */ +type IStreamEvent = IStreamEventEvent | + IStreamEventSkipEvent; diff --git a/src/core/init/stream_events_emitter/types.ts b/src/core/init/utils/stream_events_emitter/types.ts similarity index 68% rename from src/core/init/stream_events_emitter/types.ts rename to src/core/init/utils/stream_events_emitter/types.ts index bb689699d6..29467a5631 100644 --- a/src/core/init/stream_events_emitter/types.ts +++ b/src/core/init/utils/stream_events_emitter/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IStreamEventData } from "../../../public_types"; +import { IStreamEventData } from "../../../../public_types"; export interface IStreamEventPayload { id?: string | undefined; @@ -44,21 +44,3 @@ export interface IPublicStreamEvent { end: number; onExit?: () => void; } - -/** Event emitted when a stream event is encountered. */ -export interface IStreamEventEvent { - type: "stream-event"; - value: IPublicStreamEvent | - IPublicNonFiniteStreamEvent; -} - -/** Event emitted when a stream event has just been skipped. */ -export interface IStreamEventSkipEvent { - type: "stream-event-skip"; - value: IPublicStreamEvent | - IPublicNonFiniteStreamEvent; -} - -/** Events sent by the `streamEventsEmitter`. */ -export type IStreamEvent = IStreamEventEvent | - IStreamEventSkipEvent; diff --git a/src/core/init/utils/throw_on_media_error.ts b/src/core/init/utils/throw_on_media_error.ts new file mode 100644 index 0000000000..47953c806a --- /dev/null +++ b/src/core/init/utils/throw_on_media_error.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MediaError } from "../../../errors"; +import isNullOrUndefined from "../../../utils/is_null_or_undefined"; +import { CancellationSignal } from "../../../utils/task_canceller"; + +/** + * @param {HTMLMediaElement} mediaElement + * @param {Function} onError + * @param {Object} cancelSignal + */ +export default function listenToMediaError( + mediaElement : HTMLMediaElement, + onError : (error : MediaError) => void, + cancelSignal : CancellationSignal +) : void { + if (cancelSignal.isCancelled()) { + return; + } + + mediaElement.addEventListener("error", onMediaError); + + cancelSignal.register(() => { + mediaElement.removeEventListener("error", onMediaError); + }); + + function onMediaError() : void { + const mediaError = mediaElement.error; + let errorCode : number | undefined; + let errorMessage : string | undefined; + if (!isNullOrUndefined(mediaError)) { + errorCode = mediaError.code; + errorMessage = mediaError.message; + } + + switch (errorCode) { + case 1: + errorMessage = errorMessage ?? + "The fetching of the associated resource was aborted by the user's request."; + return onError(new MediaError("MEDIA_ERR_ABORTED", errorMessage)); + case 2: + errorMessage = errorMessage ?? + "A network error occurred which prevented the media from being " + + "successfully fetched"; + return onError(new MediaError("MEDIA_ERR_NETWORK", errorMessage)); + case 3: + errorMessage = errorMessage ?? + "An error occurred while trying to decode the media resource"; + return onError(new MediaError("MEDIA_ERR_DECODE", errorMessage)); + case 4: + errorMessage = errorMessage ?? + "The media resource has been found to be unsuitable."; + return onError(new MediaError("MEDIA_ERR_SRC_NOT_SUPPORTED", errorMessage)); + default: + errorMessage = errorMessage ?? + "The HTMLMediaElement errored due to an unknown reason."; + return onError(new MediaError("MEDIA_ERR_UNKNOWN", errorMessage)); + } + } +} diff --git a/src/core/segment_buffers/README.md b/src/core/segment_buffers/README.md index 8d5b74bfbe..478b8ade3f 100644 --- a/src/core/segment_buffers/README.md +++ b/src/core/segment_buffers/README.md @@ -134,7 +134,7 @@ periodically perform "garbage collection" manually on a given It is based on the following building bricks: - - An observable emitting the current time (in seconds) when the garbage + - A playback observer emitting the current time (in seconds) when the garbage collection task should be performed - The `SegmentBuffer` on which the garbage collection task should run @@ -145,9 +145,10 @@ It is based on the following building bricks: - The maximum time margin authorized for the buffer ahead of the current position -Basically, each times the given Observable emits, the BufferGarbageCollector will -ensure that the volume of data before and ahead of the current position does not -grow into a larger value than what is configured. +Basically, each times the given playback observer emits, the +BufferGarbageCollector will ensure that the volume of data before and ahead +of the current position does not grow into a larger value than what is +configured. For now, its code is completely decoupled for the rest of the code in that directory. This is why it is not included in the schema included on the top of diff --git a/src/core/segment_buffers/garbage_collector.ts b/src/core/segment_buffers/garbage_collector.ts index f16b399d0e..274a70ca4e 100644 --- a/src/core/segment_buffers/garbage_collector.ts +++ b/src/core/segment_buffers/garbage_collector.ts @@ -44,7 +44,6 @@ export interface IGarbageCollectorArgument { * * @param {Object} opt * @param {Object} cancellationSignal - * @returns {Observable} */ export default function BufferGarbageCollector( { segmentBuffer, diff --git a/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts b/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts index 354043132d..60c022a4ca 100644 --- a/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts +++ b/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts @@ -24,6 +24,7 @@ import { getLoggableSegmentId } from "../../../../manifest"; import areArraysOfNumbersEqual from "../../../../utils/are_arrays_of_numbers_equal"; import assertUnreachable from "../../../../utils/assert_unreachable"; import { toUint8Array } from "../../../../utils/byte_parsing"; +import createCancellablePromise from "../../../../utils/create_cancellable_promise"; import hashBuffer from "../../../../utils/hash_buffer"; import noop from "../../../../utils/noop"; import objectAssign from "../../../../utils/object_assign"; @@ -49,9 +50,8 @@ import { * Item added to the AudioVideoSegmentBuffer's queue before being processed into * a task (see `IAVSBPendingTask`). * - * Here we add the `subject` property which will allow the - * AudioVideoSegmentBuffer to emit an event when the corresponding queued - * operation is completely processed. + * Here we add `resolve` and `reject` callbacks to anounce when the task is + * finished. */ type IAVSBQueueItem = ISBOperation & { resolve : () => void; @@ -361,11 +361,6 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer { } /** - * When the returned observable is subscribed: - * 1. Add your operation to the queue. - * 2. Begin the queue if not pending. - * - * Cancel queued operation on unsubscription. * @private * @param {Object} operation * @param {Object} cancellationSignal @@ -375,15 +370,15 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer { operation : ISBOperation, cancellationSignal : CancellationSignal ) : Promise { - return new Promise((resolve, reject) => { - if (cancellationSignal.cancellationError !== null) { - return reject(cancellationSignal.cancellationError); - } + return createCancellablePromise(cancellationSignal, (resolve, reject) => { const shouldRestartQueue = this._queue.length === 0 && this._pendingTask === null; const queueItem = objectAssign({ resolve, reject }, operation); this._queue.push(queueItem); - cancellationSignal.register((error : CancellationError) => { + if (shouldRestartQueue) { + this._flush(); + } + return () => { // Remove the corresponding element from the AudioVideoSegmentBuffer's // queue. // If the operation was a pending task, it should still continue to not @@ -394,12 +389,7 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer { } queueItem.resolve = noop; queueItem.reject = noop; - reject(error); - }); - - if (shouldRestartQueue) { - this._flush(); - } + }; }); } diff --git a/src/core/segment_buffers/implementations/image/image_segment_buffer.ts b/src/core/segment_buffers/implementations/image/image_segment_buffer.ts index e54ecd28de..fe0f08e798 100644 --- a/src/core/segment_buffers/implementations/image/image_segment_buffer.ts +++ b/src/core/segment_buffers/implementations/image/image_segment_buffer.ts @@ -97,9 +97,9 @@ export default class ImageSegmentBuffer extends SegmentBuffer { * Indicate that every chunks from a Segment has been given to pushChunk so * far. * This will update our internal Segment inventory accordingly. - * The returned Observable will emit and complete successively once the whole - * segment has been pushed and this indication is acknowledged. - * @param {Object} infos + * The returned Promise will resolve once the whole segment has been pushed + * and this indication is acknowledged. + * @param {Object} _infos * @returns {Promise} */ public endOfSegment(_infos : IEndOfSegmentInfos) : Promise { diff --git a/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts b/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts index e2d341fd6e..f3a7ecdfe2 100644 --- a/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts +++ b/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts @@ -138,7 +138,7 @@ export default class HTMLTextSegmentBuffer extends SegmentBuffer { } /** - * Push segment on Subscription. + * Push text segment to the HTMLTextSegmentBuffer. * @param {Object} infos * @returns {Promise} */ @@ -353,7 +353,8 @@ export default class HTMLTextSegmentBuffer extends SegmentBuffer { element : HTMLElement; } => cue.resolution !== null); if (proportionalCues.length > 0) { - this._sizeUpdateCanceller = new TaskCanceller({ cancelOn: this._canceller.signal }); + this._sizeUpdateCanceller = new TaskCanceller(); + this._sizeUpdateCanceller.linkToSignal(this._canceller.signal); const { TEXT_TRACK_SIZE_CHECKS_INTERVAL } = config.getCurrent(); // update propertionally-sized elements periodically const heightWidthRef = onHeightWidthChange(this._textTrackElement, @@ -382,7 +383,8 @@ export default class HTMLTextSegmentBuffer extends SegmentBuffer { const startAutoRefresh = () => { stopAutoRefresh(); - autoRefreshCanceller = new TaskCanceller({ cancelOn: cancellationSignal }); + autoRefreshCanceller = new TaskCanceller(); + autoRefreshCanceller.linkToSignal(cancellationSignal); const intervalId = setInterval(() => this.refreshSubtitles(), MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL); autoRefreshCanceller.signal.register(() => { diff --git a/src/core/segment_buffers/segment_buffers_store.ts b/src/core/segment_buffers/segment_buffers_store.ts index cc92ecdacf..3196a3f375 100644 --- a/src/core/segment_buffers/segment_buffers_store.ts +++ b/src/core/segment_buffers/segment_buffers_store.ts @@ -17,10 +17,8 @@ import { MediaError } from "../../errors"; import features from "../../features"; import log from "../../log"; -import { - CancellationError, - CancellationSignal, -} from "../../utils/task_canceller"; +import createCancellablePromise from "../../utils/create_cancellable_promise"; +import { CancellationSignal } from "../../utils/task_canceller"; import { AudioVideoSegmentBuffer, IBufferType, @@ -194,21 +192,26 @@ export default class SegmentBuffersStore { if (this._areNativeBuffersUsable()) { return Promise.resolve(); } - return new Promise((res, rej) => { - const onAddedOrDisabled = () => { + return createCancellablePromise(cancelWaitSignal, (res) => { + /* eslint-disable-next-line prefer-const */ + let onAddedOrDisabled : () => void; + + const removeCallback = () => { + const indexOf = this._onNativeBufferAddedOrDisabled.indexOf(onAddedOrDisabled); + if (indexOf >= 0) { + this._onNativeBufferAddedOrDisabled.splice(indexOf, 1); + } + }; + + onAddedOrDisabled = () => { if (this._areNativeBuffersUsable()) { + removeCallback(); res(); } }; this._onNativeBufferAddedOrDisabled.push(onAddedOrDisabled); - cancelWaitSignal.register((error : CancellationError) => { - const indexOf = this._onNativeBufferAddedOrDisabled.indexOf(onAddedOrDisabled); - if (indexOf >= 0) { - this._onNativeBufferAddedOrDisabled.splice(indexOf, 1); - } - rej(error); - }); + return removeCallback; }); } diff --git a/src/core/stream/README.md b/src/core/stream/README.md index 90edbe896b..32001c9cf6 100644 --- a/src/core/stream/README.md +++ b/src/core/stream/README.md @@ -32,7 +32,7 @@ The ``PeriodStream`` creates and destroys ``AdaptationStream``s for a single Manifest's Period and a single type of buffer (e.g. "audio", "video", "text" etc.). -It does so after asking through an event which Adaptation has to be chosen for +It does so after asking through a callback which Adaptation has to be chosen for that Period and type. It also takes care of creating the right "`SegmentBuffer`" for its associated diff --git a/src/core/stream/adaptation/adaptation_stream.ts b/src/core/stream/adaptation/adaptation_stream.ts index 5235b3927d..695d63c4ca 100644 --- a/src/core/stream/adaptation/adaptation_stream.ts +++ b/src/core/stream/adaptation/adaptation_stream.ts @@ -14,95 +14,81 @@ * limitations under the License. */ -/** - * This file allows to create `AdaptationStream`s. - * - * An `AdaptationStream` downloads and push segment for a single Adaptation - * (e.g. a single audio, video or text track). - * It chooses which Representation to download mainly thanks to the - * IRepresentationEstimator, and orchestrates a RepresentationStream, - * which will download and push segments corresponding to a chosen - * Representation. - */ - -import { - catchError, - concat as observableConcat, - defer as observableDefer, - distinctUntilChanged, - EMPTY, - exhaustMap, - filter, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - share, - Subject, - take, - tap, -} from "rxjs"; +import nextTick from "next-tick"; import config from "../../../config"; import { formatError } from "../../../errors"; import log from "../../../log"; -import Manifest, { - Adaptation, - Period, - Representation, -} from "../../../manifest"; -import deferSubscriptions from "../../../utils/defer_subscriptions"; +import { Representation } from "../../../manifest"; +import cancellableSleep from "../../../utils/cancellable_sleep"; +import noop from "../../../utils/noop"; +import objectAssign from "../../../utils/object_assign"; import { + createMappedReference, createSharedReference, IReadOnlySharedReference, } from "../../../utils/reference"; -import TaskCanceller from "../../../utils/task_canceller"; -import { - IABREstimate, - IRepresentationEstimator, -} from "../../adaptive"; -import { IReadOnlyPlaybackObserver } from "../../api"; -import { SegmentFetcherCreator } from "../../fetchers"; -import { SegmentBuffer } from "../../segment_buffers"; -import EVENTS from "../events_generators"; -import reloadAfterSwitch from "../reload_after_switch"; +import TaskCanceller, { + CancellationSignal, +} from "../../../utils/task_canceller"; import RepresentationStream, { - IRepresentationStreamPlaybackObservation, + IRepresentationStreamCallbacks, ITerminationOrder, } from "../representation"; import { - IAdaptationStreamEvent, - IRepresentationStreamEvent, -} from "../types"; -import createRepresentationEstimator from "./create_representation_estimator"; + IAdaptationStreamArguments, + IAdaptationStreamCallbacks, +} from "./types"; +import createRepresentationEstimator from "./utils/create_representation_estimator"; /** - * Create new AdaptationStream Observable, which task will be to download the - * media data for a given Adaptation (i.e. "track"). + * Create new `AdaptationStream` whose task will be to download the media data + * for a given Adaptation (i.e. "track"). * * It will rely on the IRepresentationEstimator to choose at any time the * best Representation for this Adaptation and then run the logic to download * and push the corresponding segments in the SegmentBuffer. * - * After being subscribed to, it will start running and will emit various events - * to report its current status. + * @param {Object} args - Various arguments allowing the `AdaptationStream` to + * determine which Representation to choose and which segments to load from it. + * You can check the corresponding type for more information. + * @param {Object} callbacks - The `AdaptationStream` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. * - * @param {Object} args - * @returns {Observable} + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `AdaptationStream` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `AdaptationStream` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, or until the `error` callback is called, whichever comes first. + * @param {Object} parentCancelSignal - `CancellationSignal` allowing, when + * triggered, to immediately stop all operations the `AdaptationStream` is + * doing. */ -export default function AdaptationStream({ - playbackObserver, - content, - options, - representationEstimator, - segmentBuffer, - segmentFetcherCreator, - wantedBufferAhead, - maxVideoBufferSize, -} : IAdaptationStreamArguments) : Observable { +export default function AdaptationStream( + { playbackObserver, + content, + options, + representationEstimator, + segmentBuffer, + segmentFetcherCreator, + wantedBufferAhead, + maxVideoBufferSize } : IAdaptationStreamArguments, + callbacks : IAdaptationStreamCallbacks, + parentCancelSignal : CancellationSignal +) : void { const directManualBitrateSwitching = options.manualBitrateSwitchingMode === "direct"; const { manifest, period, adaptation } = content; + /** Allows to cancel everything the `AdaptationStream` is doing. */ + const adapStreamCanceller = new TaskCanceller(); + adapStreamCanceller.linkToSignal(parentCancelSignal); + /** * The buffer goal ratio base itself on the value given by `wantedBufferAhead` * to determine a more dynamic buffer goal for a given Representation. @@ -111,19 +97,28 @@ export default function AdaptationStream({ * buffering and tells us that we should try to bufferize less data : * https://developers.google.com/web/updates/2017/10/quotaexceedederror */ - const bufferGoalRatioMap: Partial> = {}; - const currentRepresentation = createSharedReference(null); + const bufferGoalRatioMap: Map = new Map(); - /** Errors when the adaptive logic fails with an error. */ - const abrErrorSubject = new Subject(); - const adaptiveCanceller = new TaskCanceller(); - const { estimateRef, abrCallbacks } = - createRepresentationEstimator(content, - representationEstimator, - currentRepresentation, - playbackObserver, - (err) => { abrErrorSubject.error(err); }, - adaptiveCanceller.signal); + /** + * Emit the currently chosen `Representation`. + * `null` if no Representation is chosen for now. + */ + const currentRepresentation = createSharedReference( + null, + adapStreamCanceller.signal + ); + + const { estimateRef, abrCallbacks } = createRepresentationEstimator( + content, + representationEstimator, + currentRepresentation, + playbackObserver, + (err) => { + adapStreamCanceller.cancel(); + callbacks.error(err); + }, + adapStreamCanceller.signal + ); /** Allows the `RepresentationStream` to easily fetch media segments. */ const segmentFetcher = segmentFetcherCreator @@ -135,95 +130,104 @@ export default function AdaptationStream({ onMetrics: abrCallbacks.metrics }); /* eslint-enable @typescript-eslint/unbound-method */ - /** - * Stores the last estimate emitted through the `abrEstimate$` Observable, - * starting with `null`. - * This allows to easily rely on that value in inner Observables which might also - * need the last already-considered value. - */ - const lastEstimate = createSharedReference(null); - - /** Emits abr estimates on Subscription. */ - const abrEstimate$ = estimateRef.asObservable().pipe( - tap((estimate) => { lastEstimate.setValue(estimate); }), - deferSubscriptions(), - share()); + /** Stores the last emitted bitrate. */ + let previousBitrate : number | undefined; /** Emit at each bitrate estimate done by the IRepresentationEstimator. */ - const bitrateEstimate$ = abrEstimate$.pipe( - filter(({ bitrate }) => bitrate != null), - distinctUntilChanged((old, current) => old.bitrate === current.bitrate), - map(({ bitrate }) => { - log.debug(`Stream: new ${adaptation.type} bitrate estimate`, bitrate); - return EVENTS.bitrateEstimationChange(adaptation.type, bitrate); - }) - ); + estimateRef.onUpdate(({ bitrate }) => { + if (bitrate === undefined) { + return ; + } - /** Recursively create `RepresentationStream`s according to the last estimate. */ - const representationStreams$ = abrEstimate$ - .pipe(exhaustMap((estimate, i) : Observable => { - return recursivelyCreateRepresentationStreams(estimate, i === 0); - })); + if (bitrate === previousBitrate) { + return ; + } + previousBitrate = bitrate; + log.debug(`Stream: new ${adaptation.type} bitrate estimate`, bitrate); + callbacks.bitrateEstimationChange({ type: adaptation.type, bitrate }); + }, { emitCurrentValue: true, clearSignal: adapStreamCanceller.signal }); - return observableMerge(abrErrorSubject, - representationStreams$, - bitrateEstimate$, - // Cancel adaptive logic on unsubscription - new Observable(() => () => adaptiveCanceller.cancel())); + recursivelyCreateRepresentationStreams(true); /** - * Create `RepresentationStream`s starting with the Representation indicated in - * `fromEstimate` argument. + * Create `RepresentationStream`s starting with the Representation of the last + * estimate performed. * Each time a new estimate is made, this function will create a new * `RepresentationStream` corresponding to that new estimate. - * @param {Object} fromEstimate - The first estimate we should start with * @param {boolean} isFirstEstimate - Whether this is the first time we're - * creating a RepresentationStream in the corresponding `AdaptationStream`. + * creating a `RepresentationStream` in the corresponding `AdaptationStream`. * This is important because manual quality switches might need a full reload * of the MediaSource _except_ if we are talking about the first quality chosen. - * @returns {Observable} */ - function recursivelyCreateRepresentationStreams( - fromEstimate : IABREstimate, - isFirstEstimate : boolean - ) : Observable { - const { representation } = fromEstimate; + function recursivelyCreateRepresentationStreams(isFirstEstimate : boolean) : void { + /** + * `TaskCanceller` triggered when the current `RepresentationStream` is + * terminating and as such the next one might be immediately created + * recursively. + */ + const repStreamTerminatingCanceller = new TaskCanceller(); + repStreamTerminatingCanceller.linkToSignal(adapStreamCanceller.signal); + const { representation, manual } = estimateRef.getValue(); + if (representation === null) { + return; + } // A manual bitrate switch might need an immediate feedback. // To do that properly, we need to reload the MediaSource - if (directManualBitrateSwitching && - fromEstimate.manual && - !isFirstEstimate) - { + if (directManualBitrateSwitching && manual && !isFirstEstimate) { const { DELTA_POSITION_AFTER_RELOAD } = config.getCurrent(); - return reloadAfterSwitch(period, - adaptation.type, - playbackObserver, - DELTA_POSITION_AFTER_RELOAD.bitrateSwitch); + + // We begin by scheduling a micro-task to reduce the possibility of race + // conditions where the inner logic would be called synchronously before + // the next observation (which may reflect very different playback conditions) + // is actually received. + return nextTick(() => { + playbackObserver.listen((observation) => { + const { manual: newManual } = estimateRef.getValue(); + if (!newManual) { + return; + } + const currentTime = playbackObserver.getCurrentTime(); + const pos = currentTime + DELTA_POSITION_AFTER_RELOAD.bitrateSwitch; + + // Bind to Period start and end + const position = Math.min(Math.max(period.start, pos), + period.end ?? Infinity); + const autoPlay = !(observation.paused.pending ?? + playbackObserver.getIsPaused()); + return callbacks.waitingMediaSourceReload({ bufferType: adaptation.type, + period, + position, + autoPlay }); + }, { includeLastObservation: true, + clearSignal: repStreamTerminatingCanceller.signal }); + }); } /** * Emit when the current RepresentationStream should be terminated to make * place for a new one (e.g. when switching quality). */ - const terminateCurrentStream$ = lastEstimate.asObservable().pipe( - filter((newEstimate) => newEstimate === null || - newEstimate.representation.id !== representation.id || - (newEstimate.manual && !fromEstimate.manual)), - take(1), - map((newEstimate) => { - if (newEstimate === null) { - log.info("Stream: urgent Representation termination", adaptation.type); - return ({ urgent: true }); - } - if (newEstimate.urgent) { - log.info("Stream: urgent Representation switch", adaptation.type); - return ({ urgent: true }); - } else { - log.info("Stream: slow Representation switch", adaptation.type); - return ({ urgent: false }); - } - })); + const terminateCurrentStream = createSharedReference( + null, + repStreamTerminatingCanceller.signal + ); + + /** Allows to stop listening to estimateRef on the following line. */ + estimateRef.onUpdate((estimate) => { + if (estimate.representation === null || + estimate.representation.id === representation.id) + { + return; + } + if (estimate.urgent) { + log.info("Stream: urgent Representation switch", adaptation.type); + return terminateCurrentStream.setValue({ urgent: true }); + } else { + log.info("Stream: slow Representation switch", adaptation.type); + return terminateCurrentStream.setValue({ urgent: false }); + } + }, { clearSignal: repStreamTerminatingCanceller.signal, emitCurrentValue: true }); /** * "Fast-switching" is a behavior allowing to replace low-quality segments @@ -236,207 +240,147 @@ export default function AdaptationStream({ * Set to `undefined` to indicate that there's no threshold (anything can be * replaced by higher-quality segments). */ - const fastSwitchThreshold$ = !options.enableFastSwitching ? - observableOf(0) : // Do not fast-switch anything - lastEstimate.asObservable().pipe( - map((estimate) => estimate === null ? undefined : - estimate.knownStableBitrate), - distinctUntilChanged()); + const fastSwitchThreshold = createSharedReference(0); + if (options.enableFastSwitching) { + estimateRef.onUpdate((estimate) => { + fastSwitchThreshold.setValueIfChanged(estimate?.knownStableBitrate); + }, { clearSignal: repStreamTerminatingCanceller.signal, emitCurrentValue: true }); + } - const representationChange$ = - observableOf(EVENTS.representationChange(adaptation.type, - period, - representation)); + const repInfo = { type: adaptation.type, period, representation }; + currentRepresentation.setValue(representation); + if (adapStreamCanceller.isUsed()) { + return ; // previous callback has stopped everything by side-effect + } + callbacks.representationChange(repInfo); + if (adapStreamCanceller.isUsed()) { + return ; // previous callback has stopped everything by side-effect + } - return observableConcat(representationChange$, - createRepresentationStream(representation, - terminateCurrentStream$, - fastSwitchThreshold$)).pipe( - tap((evt) : void => { - if (evt.type === "added-segment") { - abrCallbacks.addedSegment(evt.value); - } - if (evt.type === "representationChange") { - currentRepresentation.setValue(evt.value.representation); + const representationStreamCallbacks : IRepresentationStreamCallbacks = { + streamStatusUpdate: callbacks.streamStatusUpdate, + encryptionDataEncountered: callbacks.encryptionDataEncountered, + manifestMightBeOufOfSync: callbacks.manifestMightBeOufOfSync, + needsManifestRefresh: callbacks.needsManifestRefresh, + inbandEvent: callbacks.inbandEvent, + warning: callbacks.warning, + error(err : unknown) { + adapStreamCanceller.cancel(); + callbacks.error(err); + }, + addedSegment(segmentInfo) { + abrCallbacks.addedSegment(segmentInfo); + if (adapStreamCanceller.isUsed()) { + return; } - }), - mergeMap((evt) => { - if (evt.type === "stream-terminating") { - const estimate = lastEstimate.getValue(); - if (estimate === null) { - return EMPTY; - } - return recursivelyCreateRepresentationStreams(estimate, false); + callbacks.addedSegment(segmentInfo); + }, + terminating() { + if (repStreamTerminatingCanceller.isUsed()) { + return; // Already handled } - return observableOf(evt); - })); + repStreamTerminatingCanceller.cancel(); + return recursivelyCreateRepresentationStreams(false); + }, + }; + + createRepresentationStream(representation, + terminateCurrentStream, + fastSwitchThreshold, + representationStreamCallbacks); } /** - * Create and returns a new RepresentationStream Observable, linked to the + * Create and returns a new `RepresentationStream`, linked to the * given Representation. - * @param {Representation} representation - * @returns {Observable} + * @param {Object} representation + * @param {Object} terminateCurrentStream + * @param {Object} fastSwitchThreshold + * @param {Object} representationStreamCallbacks */ function createRepresentationStream( representation : Representation, - terminateCurrentStream$ : Observable, - fastSwitchThreshold$ : Observable - ) : Observable { - return observableDefer(() => { - const oldBufferGoalRatio = bufferGoalRatioMap[representation.id]; - const bufferGoalRatio = oldBufferGoalRatio != null ? oldBufferGoalRatio : - 1; - bufferGoalRatioMap[representation.id] = bufferGoalRatio; + terminateCurrentStream : IReadOnlySharedReference, + fastSwitchThreshold : IReadOnlySharedReference, + representationStreamCallbacks : IRepresentationStreamCallbacks + ) : void { + /** + * `TaskCanceller` triggered when the `RepresentationStream` calls its + * `terminating` callback. + */ + const terminatingRepStreamCanceller = new TaskCanceller(); + terminatingRepStreamCanceller.linkToSignal(adapStreamCanceller.signal); + const bufferGoal = createMappedReference(wantedBufferAhead, prev => { + return prev * getBufferGoalRatio(representation); + }, terminatingRepStreamCanceller.signal); + const maxBufferSize = adaptation.type === "video" ? + maxVideoBufferSize : + createSharedReference(Infinity); - const bufferGoal$ = wantedBufferAhead.asObservable().pipe( - map((wba) => wba * bufferGoalRatio) - ); - // eslint-disable-next-line max-len - const maxBufferSize$ = adaptation.type === "video" ? maxVideoBufferSize.asObservable() : - observableOf(Infinity); + log.info("Stream: changing representation", + adaptation.type, + representation.id, + representation.bitrate); + const updatedCallbacks = objectAssign({}, representationStreamCallbacks, { + error(err : unknown) { + const formattedError = formatError(err, { + defaultCode: "NONE", + defaultReason: "Unknown `RepresentationStream` error", + }); + if (formattedError.code !== "BUFFER_FULL_ERROR") { + representationStreamCallbacks.error(err); + } else { + const wba = wantedBufferAhead.getValue(); + const lastBufferGoalRatio = bufferGoalRatioMap.get(representation.id) ?? 1; + // 70%, 49%, 34.3%, 24%, 16.81%, 11.76%, 8.24% and 5.76% + const newBufferGoalRatio = lastBufferGoalRatio * 0.7; + if (newBufferGoalRatio <= 0.05 || wba * newBufferGoalRatio <= 2) { + throw formattedError; + } + bufferGoalRatioMap.set(representation.id, newBufferGoalRatio); - log.info("Stream: changing representation", - adaptation.type, - representation.id, - representation.bitrate); - return RepresentationStream({ playbackObserver, - content: { representation, - adaptation, - period, - manifest }, - segmentBuffer, - segmentFetcher, - terminate$: terminateCurrentStream$, - options: { bufferGoal$, - maxBufferSize$, - drmSystemId: options.drmSystemId, - fastSwitchThreshold$ } }) - .pipe(catchError((err : unknown) => { - const formattedError = formatError(err, { - defaultCode: "NONE", - defaultReason: "Unknown `RepresentationStream` error", - }); - if (formattedError.code === "BUFFER_FULL_ERROR") { - const wba = wantedBufferAhead.getValue(); - const lastBufferGoalRatio = bufferGoalRatio; - if (lastBufferGoalRatio <= 0.25 || wba * lastBufferGoalRatio <= 2) { - throw formattedError; - } - bufferGoalRatioMap[representation.id] = lastBufferGoalRatio - 0.25; + // We wait 4 seconds to let the situation evolve by itself before + // retrying loading segments with a lower buffer goal + cancellableSleep(4000, adapStreamCanceller.signal).then(() => { return createRepresentationStream(representation, - terminateCurrentStream$, - fastSwitchThreshold$); - } - throw formattedError; - })); + terminateCurrentStream, + fastSwitchThreshold, + representationStreamCallbacks); + }).catch(noop); + } + }, + terminating() { + terminatingRepStreamCanceller.cancel(); + representationStreamCallbacks.terminating(); + }, }); + RepresentationStream({ playbackObserver, + content: { representation, + adaptation, + period, + manifest }, + segmentBuffer, + segmentFetcher, + terminate: terminateCurrentStream, + options: { bufferGoal, + maxBufferSize, + drmSystemId: options.drmSystemId, + fastSwitchThreshold } }, + updatedCallbacks, + adapStreamCanceller.signal); } -} - -/** Regular playback information needed by the AdaptationStream. */ -export interface IAdaptationStreamPlaybackObservation extends - IRepresentationStreamPlaybackObservation { - /** - * For the current SegmentBuffer, difference in seconds between the next position - * where no segment data is available and the current position. - */ - bufferGap : number; - /** `duration` property of the HTMLMediaElement on which the content plays. */ - duration : number; - /** - * Information on whether the media element was paused at the time of the - * Observation. - */ - paused : IPausedPlaybackObservation; - /** Last "playback rate" asked by the user. */ - speed : number; - /** Theoretical maximum position on the content that can currently be played. */ - maximumPosition : number; - } - -/** Pause-related information linked to an emitted Playback observation. */ -export interface IPausedPlaybackObservation { - /** - * Known paused state at the time the Observation was emitted. - * - * `true` indicating that the HTMLMediaElement was in a paused state. - * - * Note that it might have changed since. If you want truly precize - * information, you should recuperate it from the HTMLMediaElement directly - * through another mean. - */ - last : boolean; - /** - * Actually wanted paused state not yet reached. - * This might for example be set to `false` when the content is currently - * loading (and thus paused) but with autoPlay enabled. - */ - pending : boolean | undefined; -} -/** Arguments given when creating a new `AdaptationStream`. */ -export interface IAdaptationStreamArguments { - /** Regularly emit playback conditions. */ - playbackObserver : IReadOnlyPlaybackObserver; - /** Content you want to create this Stream for. */ - content : { manifest : Manifest; - period : Period; - adaptation : Adaptation; }; - options: IAdaptationStreamOptions; - /** Estimate the right Representation to play. */ - representationEstimator : IRepresentationEstimator; - /** SourceBuffer wrapper - needed to push media segments. */ - segmentBuffer : SegmentBuffer; - /** Module used to fetch the wanted media segments. */ - segmentFetcherCreator : SegmentFetcherCreator; - /** - * "Buffer goal" wanted, or the ideal amount of time ahead of the current - * position in the current SegmentBuffer. When this amount has been reached - * this AdaptationStream won't try to download new segments. - */ - wantedBufferAhead : IReadOnlySharedReference; - maxVideoBufferSize : IReadOnlySharedReference; -} - -/** - * Various specific stream "options" which tweak the behavior of the - * AdaptationStream. - */ -export interface IAdaptationStreamOptions { /** - * Hex-encoded DRM "system ID" as found in: - * https://dashif.org/identifiers/content_protection/ - * - * Allows to identify which DRM system is currently used, to allow potential - * optimizations. - * - * Set to `undefined` in two cases: - * - no DRM system is used (e.g. the content is unencrypted). - * - We don't know which DRM system is currently used. + * @param {Object} representation + * @returns {number} */ - drmSystemId : string | undefined; - /** - * Strategy taken when the user switch manually the current Representation: - * - "seamless": the switch will happen smoothly, with the Representation - * with the new bitrate progressively being pushed alongside the old - * Representation. - * - "direct": hard switch. The Representation switch will be directly - * visible but may necessitate the current MediaSource to be reloaded. - */ - manualBitrateSwitchingMode : "seamless" | "direct"; - /** - * If `true`, the AdaptationStream might replace segments of a lower-quality - * (with a lower bitrate) with segments of a higher quality (with a higher - * bitrate). This allows to have a fast transition when network conditions - * improve. - * If `false`, this strategy will be disabled: segments of a lower-quality - * will not be replaced. - * - * Some targeted devices support poorly segment replacement in a - * SourceBuffer. - * As such, this option can be used to disable that unnecessary behavior on - * those devices. - */ - enableFastSwitching : boolean; + function getBufferGoalRatio(representation : Representation) : number { + const oldBufferGoalRatio = bufferGoalRatioMap.get(representation.id); + const bufferGoalRatio = oldBufferGoalRatio !== undefined ? oldBufferGoalRatio : + 1; + if (oldBufferGoalRatio === undefined) { + bufferGoalRatioMap.set(representation.id, bufferGoalRatio); + } + return bufferGoalRatio; + } } diff --git a/src/core/stream/adaptation/index.ts b/src/core/stream/adaptation/index.ts index 83103e07dc..ed17cf64c3 100644 --- a/src/core/stream/adaptation/index.ts +++ b/src/core/stream/adaptation/index.ts @@ -14,17 +14,7 @@ * limitations under the License. */ -import AdaptationStream, { - IAdaptationStreamArguments, - IAdaptationStreamPlaybackObservation, - IAdaptationStreamOptions, - IPausedPlaybackObservation, -} from "./adaptation_stream"; +import AdaptationStream from "./adaptation_stream"; +export * from "./types"; export default AdaptationStream; -export { - IAdaptationStreamArguments, - IAdaptationStreamPlaybackObservation, - IAdaptationStreamOptions, - IPausedPlaybackObservation, -}; diff --git a/src/core/stream/adaptation/types.ts b/src/core/stream/adaptation/types.ts new file mode 100644 index 0000000000..7cca5cc6fd --- /dev/null +++ b/src/core/stream/adaptation/types.ts @@ -0,0 +1,193 @@ +import Manifest, { + Adaptation, + Period, + Representation, +} from "../../../manifest"; +import { IReadOnlySharedReference } from "../../../utils/reference"; +import { IRepresentationEstimator } from "../../adaptive"; +import { IReadOnlyPlaybackObserver } from "../../api"; +import { SegmentFetcherCreator } from "../../fetchers"; +import { + IBufferType, + SegmentBuffer, +} from "../../segment_buffers"; +import { + IRepresentationStreamCallbacks, + IRepresentationStreamPlaybackObservation, +} from "../representation"; + +/** Callbacks called by the `AdaptationStream` on various events. */ +export interface IAdaptationStreamCallbacks + extends Omit, "terminating"> +{ + /** Called as new bitrate estimates are done. */ + bitrateEstimationChange(payload : IBitrateEstimationChangePayload) : void; + /** + * Called when a new `RepresentationStream` is created to load segments from a + * `Representation`. + */ + representationChange(payload : IRepresentationChangePayload) : void; + /** + * Callback called when a stream cannot go forward loading segments because it + * needs the `MediaSource` to be reloaded first. + */ + waitingMediaSourceReload(payload : IWaitingMediaSourceReloadPayload) : void; +} + +/** Payload for the `bitrateEstimationChange` callback. */ +export interface IBitrateEstimationChangePayload { + /** The type of buffer for which the estimation is done. */ + type : IBufferType; + /** + * The bitrate estimate, in bits per seconds. `undefined` when no bitrate + * estimate is currently available. + */ + bitrate : number|undefined; +} + +/** Payload for the `representationChange` callback. */ +export interface IRepresentationChangePayload { + /** The type of buffer linked to that `RepresentationStream`. */ + type : IBufferType; + /** The `Period` linked to the `RepresentationStream` we're creating. */ + period : Period; + /** + * The `Representation` linked to the `RepresentationStream` we're creating. + * `null` when we're choosing no Representation at all. + */ + representation : Representation | + null; +} + +/** Payload for the `waitingMediaSourceReload` callback. */ +export interface IWaitingMediaSourceReloadPayload { + /** Period concerned. */ + period : Period; + /** Buffer type concerned. */ + bufferType : IBufferType; + /** + * The position in seconds and the time at which the MediaSource should be + * reset once it has been reloaded. + */ + position : number; + /** + * If `true`, we want the HTMLMediaElement to play right after the reload is + * done. + * If `false`, we want to stay in a paused state at that point. + */ + autoPlay : boolean; +} + +/** Regular playback information needed by the AdaptationStream. */ +export interface IAdaptationStreamPlaybackObservation extends + IRepresentationStreamPlaybackObservation { + /** + * For the current SegmentBuffer, difference in seconds between the next position + * where no segment data is available and the current position. + */ + bufferGap : number; + /** `duration` property of the HTMLMediaElement on which the content plays. */ + duration : number; + /** + * Information on whether the media element was paused at the time of the + * Observation. + */ + paused : IPausedPlaybackObservation; + /** Last "playback rate" asked by the user. */ + speed : number; + /** Theoretical maximum position on the content that can currently be played. */ + maximumPosition : number; + } + +/** Pause-related information linked to an emitted Playback observation. */ +export interface IPausedPlaybackObservation { + /** + * Known paused state at the time the Observation was emitted. + * + * `true` indicating that the HTMLMediaElement was in a paused state. + * + * Note that it might have changed since. If you want truly precize + * information, you should recuperate it from the HTMLMediaElement directly + * through another mean. + */ + last : boolean; + /** + * Actually wanted paused state not yet reached. + * This might for example be set to `false` when the content is currently + * loading (and thus paused) but with autoPlay enabled. + */ + pending : boolean | undefined; +} + +/** Arguments given when creating a new `AdaptationStream`. */ +export interface IAdaptationStreamArguments { + /** Regularly emit playback conditions. */ + playbackObserver : IReadOnlyPlaybackObserver; + /** Content you want to create this Stream for. */ + content : { manifest : Manifest; + period : Period; + adaptation : Adaptation; }; + options: IAdaptationStreamOptions; + /** Estimate the right Representation to play. */ + representationEstimator : IRepresentationEstimator; + /** SourceBuffer wrapper - needed to push media segments. */ + segmentBuffer : SegmentBuffer; + /** Module used to fetch the wanted media segments. */ + segmentFetcherCreator : SegmentFetcherCreator; + /** + * "Buffer goal" wanted, or the ideal amount of time ahead of the current + * position in the current SegmentBuffer. When this amount has been reached + * this AdaptationStream won't try to download new segments. + */ + wantedBufferAhead : IReadOnlySharedReference; + /** + * The buffer size limit in memory that we can reach for the video buffer. + * + * Once reached, no segments will be loaded until it goes below that size + * again + */ + maxVideoBufferSize : IReadOnlySharedReference; +} + +/** + * Various specific stream "options" which tweak the behavior of the + * AdaptationStream. + */ +export interface IAdaptationStreamOptions { + /** + * Hex-encoded DRM "system ID" as found in: + * https://dashif.org/identifiers/content_protection/ + * + * Allows to identify which DRM system is currently used, to allow potential + * optimizations. + * + * Set to `undefined` in two cases: + * - no DRM system is used (e.g. the content is unencrypted). + * - We don't know which DRM system is currently used. + */ + drmSystemId : string | undefined; + /** + * Strategy taken when the user switch manually the current Representation: + * - "seamless": the switch will happen smoothly, with the Representation + * with the new bitrate progressively being pushed alongside the old + * Representation. + * - "direct": hard switch. The Representation switch will be directly + * visible but may necessitate the current MediaSource to be reloaded. + */ + manualBitrateSwitchingMode : "seamless" | "direct"; + /** + * If `true`, the AdaptationStream might replace segments of a lower-quality + * (with a lower bitrate) with segments of a higher quality (with a higher + * bitrate). This allows to have a fast transition when network conditions + * improve. + * If `false`, this strategy will be disabled: segments of a lower-quality + * will not be replaced. + * + * Some targeted devices support poorly segment replacement in a + * SourceBuffer. + * As such, this option can be used to disable that unnecessary behavior on + * those devices. + */ + enableFastSwitching : boolean; +} + diff --git a/src/core/stream/adaptation/create_representation_estimator.ts b/src/core/stream/adaptation/utils/create_representation_estimator.ts similarity index 92% rename from src/core/stream/adaptation/create_representation_estimator.ts rename to src/core/stream/adaptation/utils/create_representation_estimator.ts index bf5db29e5c..1885ab74b7 100644 --- a/src/core/stream/adaptation/create_representation_estimator.ts +++ b/src/core/stream/adaptation/utils/create_representation_estimator.ts @@ -14,24 +14,24 @@ * limitations under the License. */ -import { MediaError } from "../../../errors"; +import { MediaError } from "../../../../errors"; import Manifest, { Adaptation, Period, Representation, -} from "../../../manifest"; -import { IPlayerError } from "../../../public_types"; +} from "../../../../manifest"; +import { IPlayerError } from "../../../../public_types"; import createSharedReference, { IReadOnlySharedReference, -} from "../../../utils/reference"; -import { CancellationSignal } from "../../../utils/task_canceller"; +} from "../../../../utils/reference"; +import { CancellationSignal } from "../../../../utils/task_canceller"; import { IABREstimate, IRepresentationEstimatorPlaybackObservation, IRepresentationEstimator, IRepresentationEstimatorCallbacks, -} from "../../adaptive"; -import { IReadOnlyPlaybackObserver } from "../../api"; +} from "../../../adaptive"; +import { IReadOnlyPlaybackObserver } from "../../../api"; /** * Produce estimates to know which Representation should be played. @@ -66,7 +66,9 @@ export default function getRepresentationEstimate( abrCallbacks : IRepresentationEstimatorCallbacks; } { const { manifest, adaptation } = content; - const representations = createSharedReference([]); + const representations = createSharedReference( + [], + cancellationSignal); updateRepresentationsReference(); manifest.addEventListener("decipherabilityUpdate", updateRepresentationsReference); const unregisterCleanUp = cancellationSignal.register(cleanUp); @@ -102,7 +104,6 @@ export default function getRepresentationEstimate( /** Clean-up all resources taken here. */ function cleanUp() : void { manifest.removeEventListener("decipherabilityUpdate", updateRepresentationsReference); - representations.finish(); // check to protect against the case where it is not yet defined. if (typeof unregisterCleanUp !== "undefined") { diff --git a/src/core/stream/events_generators.ts b/src/core/stream/events_generators.ts deleted file mode 100644 index bd1856f6fb..0000000000 --- a/src/core/stream/events_generators.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Subject } from "rxjs"; -import Manifest, { - Adaptation, - ISegment, - Period, - Representation, -} from "../../manifest"; -import { IRepresentationProtectionData } from "../../manifest/representation"; -import { IPlayerError } from "../../public_types"; -import objectAssign from "../../utils/object_assign"; -import { IBufferType } from "../segment_buffers"; -import { - IActivePeriodChangedEvent, - IAdaptationChangeEvent, - IBitrateEstimationChangeEvent, - ICompletedStreamEvent, - IEncryptionDataEncounteredEvent, - IEndOfStreamEvent, - ILockedStreamEvent, - INeedsBufferFlushEvent, - INeedsDecipherabilityFlush, - INeedsMediaSourceReload, - IPeriodStreamClearedEvent, - IPeriodStreamReadyEvent, - IRepresentationChangeEvent, - IResumeStreamEvent, - IStreamEventAddedSegment, - IStreamManifestMightBeOutOfSync, - IStreamNeedsManifestRefresh, - IStreamTerminatingEvent, - IStreamWarningEvent, - IWaitingMediaSourceReloadInternalEvent, -} from "./types"; - -const EVENTS = { - activePeriodChanged(period : Period) : IActivePeriodChangedEvent { - return { type : "activePeriodChanged", - value : { period } }; - }, - - adaptationChange( - bufferType : IBufferType, - adaptation : Adaptation|null, - period : Period - ) : IAdaptationChangeEvent { - return { type: "adaptationChange", - value : { type: bufferType, - adaptation, - period } }; - }, - - addedSegment( - content : { adaptation : Adaptation; - period : Period; - representation : Representation; }, - segment : ISegment, - buffered : TimeRanges, - segmentData : T - ) : IStreamEventAddedSegment { - return { type : "added-segment", - value : { content, - segment, - segmentData, - buffered } }; - }, - - bitrateEstimationChange( - type : IBufferType, - bitrate : number|undefined - ) : IBitrateEstimationChangeEvent { - return { type: "bitrateEstimationChange", - value: { type, bitrate } }; - }, - - streamComplete(bufferType: IBufferType) : ICompletedStreamEvent { - return { type: "complete-stream", - value: { type: bufferType } }; - }, - - endOfStream() : IEndOfStreamEvent { - return { type: "end-of-stream", - value: undefined }; - }, - - needsManifestRefresh() : IStreamNeedsManifestRefresh { - return { type : "needs-manifest-refresh", - value : undefined }; - }, - - manifestMightBeOufOfSync() : IStreamManifestMightBeOutOfSync { - return { type : "manifest-might-be-out-of-sync", - value : undefined }; - }, - - /** - * @param {number} reloadAt - Position at which we should reload - * @param {boolean} reloadOnPause - If `false`, stay on pause after reloading. - * if `true`, automatically play once reloaded. - * @returns {Object} - */ - needsMediaSourceReload( - reloadAt : number, - reloadOnPause : boolean - ) : INeedsMediaSourceReload { - return { type: "needs-media-source-reload", - value: { position : reloadAt, - autoPlay : reloadOnPause } }; - }, - - /** - * @param {string} bufferType - The buffer type for which the stream cannot - * currently load segments. - * @param {Object} period - The Period for which the stream cannot - * currently load segments. - * media source reload is linked. - * @returns {Object} - */ - lockedStream( - bufferType : IBufferType, - period : Period - ) : ILockedStreamEvent { - return { type: "locked-stream", - value: { bufferType, period } }; - }, - - needsBufferFlush(): INeedsBufferFlushEvent { - return { type: "needs-buffer-flush", value: undefined }; - }, - - needsDecipherabilityFlush( - position : number, - autoPlay : boolean, - duration : number - ) : INeedsDecipherabilityFlush { - return { type: "needs-decipherability-flush", - value: { position, autoPlay, duration } }; - }, - - periodStreamReady( - type : IBufferType, - period : Period, - adaptation$ : Subject - ) : IPeriodStreamReadyEvent { - return { type: "periodStreamReady", - value: { type, period, adaptation$ } }; - }, - - periodStreamCleared( - type : IBufferType, - period : Period - ) : IPeriodStreamClearedEvent { - return { type: "periodStreamCleared", - value: { type, period } }; - }, - - encryptionDataEncountered( - reprProtData : IRepresentationProtectionData, - content : { manifest : Manifest; - period : Period; - adaptation : Adaptation; - representation : Representation; } - ) : IEncryptionDataEncounteredEvent { - return { type: "encryption-data-encountered", - value: objectAssign({ content }, reprProtData) }; - }, - - representationChange( - type : IBufferType, - period : Period, - representation : Representation - ) : IRepresentationChangeEvent { - return { type: "representationChange", - value: { type, period, representation } }; - }, - - streamTerminating() : IStreamTerminatingEvent { - return { type: "stream-terminating", - value: undefined }; - }, - - resumeStream() : IResumeStreamEvent { - return { type: "resume-stream", - value: undefined }; - }, - - warning(value : IPlayerError) : IStreamWarningEvent { - return { type: "warning", value }; - }, - - waitingMediaSourceReload( - bufferType : IBufferType, - period : Period, - position : number, - autoPlay : boolean - ) : IWaitingMediaSourceReloadInternalEvent { - return { type: "waiting-media-source-reload", - value: { bufferType, period, position, autoPlay } }; - }, -}; - -export default EVENTS; diff --git a/src/core/stream/index.ts b/src/core/stream/index.ts index ffdc4990e4..cbc86ca6ea 100644 --- a/src/core/stream/index.ts +++ b/src/core/stream/index.ts @@ -17,12 +17,20 @@ import StreamOrchestrator, { IStreamOrchestratorOptions, IStreamOrchestratorPlaybackObservation, + IStreamOrchestratorCallbacks, } from "./orchestrator"; export { IAudioTrackSwitchingMode } from "./period"; -export * from "./types"; +export { + IInbandEvent, + IStreamStatusPayload, +} from "./representation"; +export { + IWaitingMediaSourceReloadPayload, +} from "./adaptation"; export default StreamOrchestrator; export { IStreamOrchestratorPlaybackObservation, IStreamOrchestratorOptions, + IStreamOrchestratorCallbacks, }; diff --git a/src/core/stream/orchestrator/README.md b/src/core/stream/orchestrator/README.md index 986871b4b0..1af507aff3 100644 --- a/src/core/stream/orchestrator/README.md +++ b/src/core/stream/orchestrator/README.md @@ -346,7 +346,4 @@ At the end, we should only have _PeriodStream[s]_ for consecutive Period[s]: Any "Stream" communicates to the API about creations and destructions of _PeriodStreams_ respectively through ``"periodStreamReady"`` and -``"periodStreamCleared"`` events. - -When the currently seen Period changes, an ``activePeriodChanged`` event is -sent. +``"periodStreamCleared"`` callbacks. diff --git a/src/core/stream/orchestrator/active_period_emitter.ts b/src/core/stream/orchestrator/active_period_emitter.ts deleted file mode 100644 index b9dc63f276..0000000000 --- a/src/core/stream/orchestrator/active_period_emitter.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - distinctUntilChanged, - filter, - map, - merge as observableMerge, - Observable, - scan, -} from "rxjs"; -import { Period } from "../../../manifest"; -import { IBufferType } from "../../segment_buffers"; -import { IStreamOrchestratorEvent } from "../types"; - -interface IPeriodObject { period : Period; - buffers: Set; } - -type IPeriodsList = Partial>; - -/** - * Emit the active Period each times it changes. - * - * The active Period is the first Period (in chronological order) which has - * a RepresentationStream associated for every defined BUFFER_TYPES. - * - * Emit null if no Period can be considered active currently. - * - * @example - * For 4 BUFFER_TYPES: "AUDIO", "VIDEO", "TEXT" and "IMAGE": - * ``` - * +-------------+ - * Period 1 | Period 2 | Period 3 - * AUDIO |=========| | |=== | | - * VIDEO | |===== | | - * TEXT |(NO TEXT)| | |(NO TEXT)| | |==== | - * IMAGE |=========| | |= | | - * +-------------+ - * - * The active Period here is Period 2 as Period 1 has no video - * RepresentationStream. - * - * If we are missing a or multiple PeriodStreams in the first chronological - * Period, like that is the case here, it generally means that we are - * currently switching between Periods. - * - * For here we are surely switching from Period 1 to Period 2 beginning by the - * video PeriodStream. As every PeriodStream is ready for Period 2, we can - * already inform that it is the current Period. - * ``` - * - * @param {Array.} buffers$ - * @returns {Observable} - */ -export default function ActivePeriodEmitter( - buffers$: Array> -) : Observable { - const numberOfStreams = buffers$.length; - return observableMerge(...buffers$).pipe( - // not needed to filter, this is an optim - filter(({ type }) => type === "periodStreamCleared" || - type === "adaptationChange" || - type === "representationChange"), - scan((acc, evt) => { - switch (evt.type) { - case "periodStreamCleared": { - const { period, type } = evt.value; - const currentInfos = acc[period.id]; - if (currentInfos !== undefined && currentInfos.buffers.has(type)) { - currentInfos.buffers.delete(type); - if (currentInfos.buffers.size === 0) { - delete acc[period.id]; - } - } - } - break; - - case "adaptationChange": { - // For Adaptations that are not null, we will receive a - // `representationChange` event. We can thus skip this event and only - // listen to the latter. - if (evt.value.adaptation !== null) { - return acc; - } - } - // /!\ fallthrough done on purpose - // Note that we fall-through only when the Adaptation sent through the - // `adaptationChange` event is `null`. This is because in those cases, - // we won't receive any "representationChange" event. We however still - // need to register that Period as active for the current type. - // eslint-disable-next-line no-fallthrough - case "representationChange": { - const { period, type } = evt.value; - const currentInfos = acc[period.id]; - if (currentInfos === undefined) { - const bufferSet = new Set(); - bufferSet.add(type); - acc[period.id] = { period, buffers: bufferSet }; - } else if (!currentInfos.buffers.has(type)) { - currentInfos.buffers.add(type); - } - } - break; - - } - return acc; - }, {}), - - map((list) : Period | null => { - const activePeriodIDs = Object.keys(list); - const completePeriods : Period[] = []; - for (let i = 0; i < activePeriodIDs.length; i++) { - const periodInfos = list[activePeriodIDs[i]]; - if (periodInfos !== undefined && periodInfos.buffers.size === numberOfStreams) { - completePeriods.push(periodInfos.period); - } - } - - return completePeriods.reduce((acc, period) => { - if (acc === null) { - return period; - } - return period.start < acc.start ? period : - acc; - }, null); - }), - - distinctUntilChanged((a, b) => { - return a === null && b === null || - a !== null && b !== null && a.id === b.id; - }) - ); -} diff --git a/src/core/stream/orchestrator/are_streams_complete.ts b/src/core/stream/orchestrator/are_streams_complete.ts deleted file mode 100644 index 49ae11138c..0000000000 --- a/src/core/stream/orchestrator/are_streams_complete.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - combineLatest as observableCombineLatest, - distinctUntilChanged, - filter, - map, - Observable, - startWith, -} from "rxjs"; -import { IStreamOrchestratorEvent } from "../types"; - -/** - * Returns an Observable which emits ``true`` when all PeriodStreams given are - * _complete_. - * Returns false otherwise. - * - * A PeriodStream for a given type is considered _complete_ when both of these - * conditions are true: - * - it is the last PeriodStream in the content for the given type - * - it has finished downloading segments (it is _full_) - * - * Simply put a _complete_ PeriodStream for a given type means that every - * segments needed for this Stream have been downloaded. - * - * When the Observable returned here emits, every Stream are finished. - * @param {...Observable} streams - * @returns {Observable} - */ -export default function areStreamsComplete( - ...streams : Array> -) : Observable { - /** - * Array of Observables linked to the Array of Streams which emit: - * - true when the corresponding Stream is considered _complete_. - * - false when the corresponding Stream is considered _active_. - * @type {Array.} - */ - const isCompleteArray : Array> = streams - .map((stream) => { - return stream.pipe( - filter((evt) => { - return evt.type === "complete-stream" || - (evt.type === "stream-status" && !evt.value.hasFinishedLoading); - }), - map((evt) => evt.type === "complete-stream"), - startWith(false), - distinctUntilChanged() - ); - }); - - return observableCombineLatest(isCompleteArray).pipe( - map((areComplete) => areComplete.every((isComplete) => isComplete)), - distinctUntilChanged() - ); -} diff --git a/src/core/stream/orchestrator/index.ts b/src/core/stream/orchestrator/index.ts index 0b31e55cc8..4fdf56bb09 100644 --- a/src/core/stream/orchestrator/index.ts +++ b/src/core/stream/orchestrator/index.ts @@ -15,12 +15,14 @@ */ import StreamOrchestrator, { + IStreamOrchestratorCallbacks, IStreamOrchestratorOptions, IStreamOrchestratorPlaybackObservation, } from "./stream_orchestrator"; export default StreamOrchestrator; export { + IStreamOrchestratorCallbacks, IStreamOrchestratorOptions, IStreamOrchestratorPlaybackObservation, }; diff --git a/src/core/stream/orchestrator/stream_orchestrator.ts b/src/core/stream/orchestrator/stream_orchestrator.ts index 6b3f3c67d9..a216afe9b7 100644 --- a/src/core/stream/orchestrator/stream_orchestrator.ts +++ b/src/core/stream/orchestrator/stream_orchestrator.ts @@ -14,27 +14,7 @@ * limitations under the License. */ -import { - combineLatest, - concat as observableConcat, - defer as observableDefer, - distinctUntilChanged, - EMPTY, - exhaustMap, - filter, - ignoreElements, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - share, - startWith, - Subject, - take, - takeUntil, - tap, -} from "rxjs"; +import nextTick from "next-tick"; import config from "../../../config"; import { MediaError } from "../../../errors"; import log from "../../../log"; @@ -42,17 +22,14 @@ import Manifest, { IDecipherabilityUpdateElement, Period, } from "../../../manifest"; -import deferSubscriptions from "../../../utils/defer_subscriptions"; -import { fromEvent } from "../../../utils/event_emitter"; -import filterMap from "../../../utils/filter_map"; import { createMappedReference, IReadOnlySharedReference, } from "../../../utils/reference"; -import fromCancellablePromise from "../../../utils/rx-from_cancellable_promise"; -import nextTickObs from "../../../utils/rx-next-tick"; import SortedList from "../../../utils/sorted_list"; -import TaskCanceller from "../../../utils/task_canceller"; +import TaskCanceller, { + CancellationSignal, +} from "../../../utils/task_canceller"; import WeakMapMemory from "../../../utils/weak_map_memory"; import { IRepresentationEstimator } from "../../adaptive"; import type { IReadOnlyPlaybackObserver } from "../../api"; @@ -62,39 +39,18 @@ import SegmentBuffersStore, { IBufferType, SegmentBuffer, } from "../../segment_buffers"; -import EVENTS from "../events_generators"; +import { IWaitingMediaSourceReloadPayload } from "../adaptation"; import PeriodStream, { - IPeriodStreamPlaybackObservation, + IPeriodStreamCallbacks, IPeriodStreamOptions, + IPeriodStreamPlaybackObservation, + IPeriodStreamReadyPayload, } from "../period"; -import { - IMultiplePeriodStreamsEvent, - IPeriodStreamEvent, - IStreamOrchestratorEvent, -} from "../types"; -import ActivePeriodEmitter from "./active_period_emitter"; -import areStreamsComplete from "./are_streams_complete"; +import { IStreamStatusPayload } from "../representation"; import getTimeRangesForContent from "./get_time_ranges_for_content"; -// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default -// first type parameter as `any` instead of the perfectly fine `unknown`, -// leading to linter issues, as it forbids the usage of `any`. -// This is why we're disabling the eslint rule. -/* eslint-disable @typescript-eslint/no-unsafe-argument */ - -export type IStreamOrchestratorPlaybackObservation = IPeriodStreamPlaybackObservation; - - -/** Options tweaking the behavior of the StreamOrchestrator. */ -export type IStreamOrchestratorOptions = - IPeriodStreamOptions & - { wantedBufferAhead : IReadOnlySharedReference; - maxVideoBufferSize : IReadOnlySharedReference; - maxBufferAhead : IReadOnlySharedReference; - maxBufferBehind : IReadOnlySharedReference; }; - /** - * Create and manage the various Stream Observables needed for the content to + * Create and manage the various "Streams" needed for the content to * play: * * - Create or dispose SegmentBuffers depending on the chosen Adaptations. @@ -106,17 +62,34 @@ export type IStreamOrchestratorOptions = * - Concatenate Streams for adaptation from separate Periods at the right * time, to allow smooth transitions between periods. * - * - Emit various events to notify of its health and issues + * - Call various callbacks to notify of its health and issues * * @param {Object} content - * @param {Observable} playbackObserver - Emit position information + * @param {Object} playbackObserver - Emit position information * @param {Object} representationEstimator - Emit bitrate estimates and best * Representation to play. * @param {Object} segmentBuffersStore - Will be used to lazily create * SegmentBuffer instances associated with the current content. * @param {Object} segmentFetcherCreator - Allow to download segments. * @param {Object} options - * @returns {Observable} + * @param {Object} callbacks - The `StreamOrchestrator` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. + * + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `StreamOrchestrator` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `StreamOrchestrator` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, or until the `error` callback is called, whichever comes first. + * @param {Object} orchestratorCancelSignal - `CancellationSignal` allowing, + * when triggered, to immediately stop all operations the `PeriodStream` is + * doing. */ export default function StreamOrchestrator( content : { manifest : Manifest; @@ -125,8 +98,10 @@ export default function StreamOrchestrator( representationEstimator : IRepresentationEstimator, segmentBuffersStore : SegmentBuffersStore, segmentFetcherCreator : SegmentFetcherCreator, - options: IStreamOrchestratorOptions -) : Observable { + options : IStreamOrchestratorOptions, + callbacks : IStreamOrchestratorCallbacks, + orchestratorCancelSignal : CancellationSignal +) : void { const { manifest, initialPeriod } = content; const { maxBufferAhead, maxBufferBehind, @@ -135,6 +110,7 @@ export default function StreamOrchestrator( const { MAXIMUM_MAX_BUFFER_AHEAD, MAXIMUM_MAX_BUFFER_BEHIND } = config.getCurrent(); + // Keep track of a unique BufferGarbageCollector created per // SegmentBuffer. const garbageCollectors = @@ -146,55 +122,27 @@ export default function StreamOrchestrator( const defaultMaxAhead = MAXIMUM_MAX_BUFFER_AHEAD[bufferType] != null ? MAXIMUM_MAX_BUFFER_AHEAD[bufferType] as number : Infinity; - return new Observable(() => { - const canceller = new TaskCanceller(); + return (gcCancelSignal : CancellationSignal) => { BufferGarbageCollector( { segmentBuffer, playbackObserver, maxBufferBehind: createMappedReference(maxBufferBehind, (val) => Math.min(val, defaultMaxBehind), - canceller.signal), + gcCancelSignal), maxBufferAhead: createMappedReference(maxBufferAhead, (val) => Math.min(val, defaultMaxAhead), - canceller.signal) }, - canceller.signal + gcCancelSignal) }, + gcCancelSignal ); - return () => { canceller.cancel(); }; - }); + }; }); - // Every PeriodStreams for every possible types - const streamsArray = segmentBuffersStore.getBufferTypes().map((bufferType) => { - return manageEveryStreams(bufferType, initialPeriod) - .pipe(deferSubscriptions(), share()); - }); - - // Emits the activePeriodChanged events every time the active Period changes. - const activePeriodChanged$ = ActivePeriodEmitter(streamsArray).pipe( - filter((period) : period is Period => period !== null), - map(period => { - log.info("Stream: New active period", period.start); - return EVENTS.activePeriodChanged(period); - })); - - const isLastPeriodKnown$ = fromEvent(manifest, "manifestUpdate").pipe( - map(() => manifest.isLastPeriodKnown), - startWith(manifest.isLastPeriodKnown), - distinctUntilChanged() - ); - - // Emits an "end-of-stream" event once every PeriodStream are complete. - // Emits a 'resume-stream" when it's not - const endOfStream$ = combineLatest([areStreamsComplete(...streamsArray), - isLastPeriodKnown$]) - .pipe(map(([areComplete, isLastPeriodKnown]) => areComplete && isLastPeriodKnown), - distinctUntilChanged(), - map((emitEndOfStream) => - emitEndOfStream ? EVENTS.endOfStream() : EVENTS.resumeStream())); - - return observableMerge(...streamsArray, activePeriodChanged$, endOfStream$); + // Create automatically the right `PeriodStream` for every possible types + for (const bufferType of segmentBuffersStore.getBufferTypes()) { + manageEveryStreams(bufferType, initialPeriod); + } /** * Manage creation and removal of Streams for every Periods for a given type. @@ -204,58 +152,100 @@ export default function StreamOrchestrator( * current position goes out of the bounds of these Streams. * @param {string} bufferType - e.g. "audio" or "video" * @param {Period} basePeriod - Initial Period downloaded. - * @returns {Observable} */ - function manageEveryStreams( - bufferType : IBufferType, - basePeriod : Period - ) : Observable { - // Each Period for which there is currently a Stream, chronologically + function manageEveryStreams(bufferType : IBufferType, basePeriod : Period) : void { + /** Each Period for which there is currently a Stream, chronologically */ const periodList = new SortedList((a, b) => a.start - b.start); - const destroyStreams$ = new Subject(); - // When set to `true`, all the currently active PeriodStream will be destroyed - // and re-created from the new current position if we detect it to be out of - // their bounds. - // This is set to false when we're in the process of creating the first - // PeriodStream, to avoid interferences while no PeriodStream is available. + /** + * When set to `true`, all the currently active PeriodStream will be destroyed + * and re-created from the new current position if we detect it to be out of + * their bounds. + * This is set to false when we're in the process of creating the first + * PeriodStream, to avoid interferences while no PeriodStream is available. + */ let enableOutOfBoundsCheck = false; + /** Cancels currently created `PeriodStream`s. */ + let currentCanceller = new TaskCanceller(); + currentCanceller.linkToSignal(orchestratorCancelSignal); + + // Restart the current Stream when the wanted time is in another period + // than the ones already considered + playbackObserver.listen(({ position }) => { + const time = position.pending ?? position.last; + if (!enableOutOfBoundsCheck || !isOutOfPeriodList(time)) { + return ; + } + + log.info("Stream: Destroying all PeriodStreams due to out of bounds situation", + bufferType, time); + enableOutOfBoundsCheck = false; + while (periodList.length() > 0) { + const period = periodList.get(periodList.length() - 1); + periodList.removeElement(period); + callbacks.periodStreamCleared({ type: bufferType, period }); + } + currentCanceller.cancel(); + currentCanceller = new TaskCanceller(); + currentCanceller.linkToSignal(orchestratorCancelSignal); + + const nextPeriod = manifest.getPeriodForTime(time) ?? + manifest.getNextPeriod(time); + if (nextPeriod === undefined) { + log.warn("Stream: The wanted position is not found in the Manifest."); + return; + } + launchConsecutiveStreamsForPeriod(nextPeriod); + }, { clearSignal: orchestratorCancelSignal, includeLastObservation: true }); + + manifest.addEventListener("decipherabilityUpdate", (evt) => { + onDecipherabilityUpdates(evt).catch(err => { + currentCanceller.cancel(); + callbacks.error(err); + }); + }, orchestratorCancelSignal); + + return launchConsecutiveStreamsForPeriod(basePeriod); + /** * @param {Object} period - * @returns {Observable} */ - function launchConsecutiveStreamsForPeriod( - period : Period - ) : Observable { - return manageConsecutivePeriodStreams(bufferType, period, destroyStreams$).pipe( - map((message) => { - switch (message.type) { - case "waiting-media-source-reload": - // Only reload the MediaSource when the more immediately required - // Period is the one asking for it - const firstPeriod = periodList.head(); - if (firstPeriod === undefined || - firstPeriod.id !== message.value.period.id) - { - return EVENTS.lockedStream(message.value.bufferType, - message.value.period); - } else { - const { position, autoPlay } = message.value; - return EVENTS.needsMediaSourceReload(position, autoPlay); - } - case "periodStreamReady": - enableOutOfBoundsCheck = true; - periodList.add(message.value.period); - break; - case "periodStreamCleared": - periodList.removeElement(message.value.period); - break; + function launchConsecutiveStreamsForPeriod(period : Period) : void { + const consecutivePeriodStreamCb = { + ...callbacks, + waitingMediaSourceReload(payload : IWaitingMediaSourceReloadPayload) : void { + // Only reload the MediaSource when the more immediately required + // Period is the one asking for it + const firstPeriod = periodList.head(); + if (firstPeriod === undefined || + firstPeriod.id !== payload.period.id) + { + callbacks.lockedStream({ bufferType: payload.bufferType, + period: payload.period }); + } else { + const { position, autoPlay } = payload; + callbacks.needsMediaSourceReload({ position, autoPlay }); } - return message; - }), - share() - ); + }, + periodStreamReady(payload : IPeriodStreamReadyPayload) : void { + enableOutOfBoundsCheck = true; + periodList.add(payload.period); + callbacks.periodStreamReady(payload); + }, + periodStreamCleared(payload : IPeriodStreamClearedPayload) : void { + periodList.removeElement(payload.period); + callbacks.periodStreamCleared(payload); + }, + error(err : unknown) : void { + currentCanceller.cancel(); + callbacks.error(err); + }, + }; + manageConsecutivePeriodStreams(bufferType, + period, + consecutivePeriodStreamCb, + currentCanceller.signal); } /** @@ -276,51 +266,14 @@ export default function StreamOrchestrator( last.end) < time; } - // Restart the current Stream when the wanted time is in another period - // than the ones already considered - const observation$ = playbackObserver.getReference().asObservable(); - const restartStreamsWhenOutOfBounds$ = observation$.pipe( - filterMap< - IStreamOrchestratorPlaybackObservation, - Period, - null - >(({ position }) => { - const time = position.pending ?? position.last; - if (!enableOutOfBoundsCheck || !isOutOfPeriodList(time)) { - return null; - } - const nextPeriod = manifest.getPeriodForTime(time) ?? - manifest.getNextPeriod(time); - if (nextPeriod === undefined) { - return null; - } - log.info("SO: Current position out of the bounds of the active periods," + - "re-creating Streams.", - bufferType, - time); - enableOutOfBoundsCheck = false; - destroyStreams$.next(); - return nextPeriod; - }, null), - mergeMap((newInitialPeriod) => { - if (newInitialPeriod == null) { - throw new MediaError("MEDIA_TIME_NOT_FOUND", - "The wanted position is not found in the Manifest."); - } - return launchConsecutiveStreamsForPeriod(newInitialPeriod); - }) - ); - - const handleDecipherabilityUpdate$ = fromEvent(manifest, "decipherabilityUpdate") - .pipe(mergeMap(onDecipherabilityUpdates)); - - return observableMerge(restartStreamsWhenOutOfBounds$, - handleDecipherabilityUpdate$, - launchConsecutiveStreamsForPeriod(basePeriod)); - - function onDecipherabilityUpdates( + /** + * React to a Manifest's decipherability updates. + * @param {Array.} + * @returns {Promise} + */ + async function onDecipherabilityUpdates( updates : IDecipherabilityUpdateElement[] - ) : Observable { + ) : Promise { const segmentBufferStatus = segmentBuffersStore.getStatus(bufferType); const ofCurrentType = updates .filter(update => update.adaptation.type === bufferType); @@ -333,7 +286,7 @@ export default function StreamOrchestrator( ) { // Data won't have to be removed from the buffers, no need to stop the // current Streams. - return EMPTY; + return ; } const segmentBuffer = segmentBufferStatus.value; @@ -363,180 +316,330 @@ export default function StreamOrchestrator( // First close all Stream currently active so they don't continue to // load and push segments. enableOutOfBoundsCheck = false; - destroyStreams$.next(); + + log.info("Stream: Destroying all PeriodStreams for decipherability matters", + bufferType); + while (periodList.length() > 0) { + const period = periodList.get(periodList.length() - 1); + periodList.removeElement(period); + callbacks.periodStreamCleared({ type: bufferType, period }); + } + + currentCanceller.cancel(); + currentCanceller = new TaskCanceller(); + currentCanceller.linkToSignal(orchestratorCancelSignal); /** Remove from the `SegmentBuffer` all the concerned time ranges. */ - const cleanOperations = [...undecipherableRanges, ...rangesToRemove] - .map(({ start, end }) => { - if (start >= end) { - return EMPTY; + for (const { start, end } of [...undecipherableRanges, ...rangesToRemove]) { + if (start < end) { + await segmentBuffer.removeBuffer(start, end, orchestratorCancelSignal); + } + } + + // Schedule micro task before checking the last playback observation + // to reduce the risk of race conditions where the next observation + // was going to be emitted synchronously. + nextTick(() => { + if (orchestratorCancelSignal.isCancelled()) { + return ; + } + const observation = playbackObserver.getReference().getValue(); + if (needsFlushingAfterClean(observation, undecipherableRanges)) { + const shouldAutoPlay = !(observation.paused.pending ?? + playbackObserver.getIsPaused()); + callbacks.needsDecipherabilityFlush({ position: observation.position.last, + autoPlay: shouldAutoPlay, + duration: observation.duration }); + if (orchestratorCancelSignal.isCancelled()) { + return ; } - const canceller = new TaskCanceller(); - return fromCancellablePromise(canceller, () => { - return segmentBuffer.removeBuffer(start, end, canceller.signal); - }).pipe(ignoreElements()); - }); - - return observableConcat( - ...cleanOperations, - - // Schedule micro task before checking the last playback observation - // to reduce the risk of race conditions where the next observation - // was going to be emitted synchronously. - nextTickObs().pipe(ignoreElements()), - playbackObserver.getReference().asObservable().pipe( - take(1), - mergeMap((observation) => { - const restartStream$ = observableDefer(() => { - const lastPosition = observation.position.pending ?? - observation.position.last; - const newInitialPeriod = manifest.getPeriodForTime(lastPosition); - if (newInitialPeriod == null) { - throw new MediaError( - "MEDIA_TIME_NOT_FOUND", - "The wanted position is not found in the Manifest."); - } - return launchConsecutiveStreamsForPeriod(newInitialPeriod); - }); - - - if (needsFlushingAfterClean(observation, undecipherableRanges)) { - const shouldAutoPlay = !(observation.paused.pending ?? - playbackObserver.getIsPaused()); - return observableConcat( - observableOf(EVENTS.needsDecipherabilityFlush(observation.position.last, - shouldAutoPlay, - observation.duration)), - restartStream$); - } else if (needsFlushingAfterClean(observation, rangesToRemove)) { - return observableConcat(observableOf(EVENTS.needsBufferFlush()), - restartStream$); - } - return restartStream$; - }))); + } else if (needsFlushingAfterClean(observation, rangesToRemove)) { + callbacks.needsBufferFlush(); + if (orchestratorCancelSignal.isCancelled()) { + return ; + } + } + + const lastPosition = observation.position.pending ?? + observation.position.last; + const newInitialPeriod = manifest.getPeriodForTime(lastPosition); + if (newInitialPeriod == null) { + callbacks.error( + new MediaError("MEDIA_TIME_NOT_FOUND", + "The wanted position is not found in the Manifest.") + ); + return; + } + launchConsecutiveStreamsForPeriod(newInitialPeriod); + }); } } /** * Create lazily consecutive PeriodStreams: * - * It first creates the PeriodStream for `basePeriod` and - once it becomes + * It first creates the `PeriodStream` for `basePeriod` and - once it becomes * full - automatically creates the next chronological one. - * This process repeats until the PeriodStream linked to the last Period is + * This process repeats until the `PeriodStream` linked to the last Period is * full. * - * If an "old" PeriodStream becomes active again, it destroys all PeriodStream - * coming after it (from the last chronological one to the first). + * If an "old" `PeriodStream` becomes active again, it destroys all + * `PeriodStream` coming after it (from the last chronological one to the + * first). * * To clean-up PeriodStreams, each one of them are also automatically * destroyed once the current position is superior or equal to the end of * the concerned Period. * - * A "periodStreamReady" event is sent each times a new PeriodStream is - * created. The first one (for `basePeriod`) should be sent synchronously on - * subscription. + * The "periodStreamReady" callback is alled each times a new `PeriodStream` + * is created. * - * A "periodStreamCleared" event is sent each times a PeriodStream is - * destroyed. + * The "periodStreamCleared" callback is called each times a PeriodStream is + * destroyed (this callback is though not called if it was destroyed due to + * the given `cancelSignal` emitting or due to a fatal error). * @param {string} bufferType - e.g. "audio" or "video" * @param {Period} basePeriod - Initial Period downloaded. - * @param {Observable} destroy$ - Emit when/if all created Streams from this - * point should be destroyed. - * @returns {Observable} + * @param {Object} consecutivePeriodStreamCb - Callbacks called on various + * events. See type for more information. + * @param {Object} cancelSignal - `CancellationSignal` allowing to stop + * everything that this function was doing. Callbacks in + * `consecutivePeriodStreamCb` might still be sent as a consequence of this + * signal emitting. */ function manageConsecutivePeriodStreams( bufferType : IBufferType, basePeriod : Period, - destroy$ : Observable - ) : Observable { - log.info("SO: Creating new Stream for", bufferType, basePeriod.start); - - // Emits the Period of the next Period Stream when it can be created. - const createNextPeriodStream$ = new Subject(); - - // Emits when the Streams for the next Periods should be destroyed, if - // created. - const destroyNextStreams$ = new Subject(); - - // Emits when the current position goes over the end of the current Stream. - const endOfCurrentStream$ = playbackObserver.getReference().asObservable() - .pipe(filter(({ position }) => - basePeriod.end != null && - (position.pending ?? position.last) >= basePeriod.end)); - - // Create Period Stream for the next Period. - const nextPeriodStream$ = createNextPeriodStream$ - .pipe(exhaustMap((nextPeriod) => - manageConsecutivePeriodStreams(bufferType, nextPeriod, destroyNextStreams$) - )); - - // Allows to destroy each created Stream, from the newest to the oldest, - // once destroy$ emits. - const destroyAll$ = destroy$.pipe( - take(1), - tap(() => { - // first complete createNextStream$ to allow completion of the - // nextPeriodStream$ observable once every further Streams have been - // cleared. - createNextPeriodStream$.complete(); - - // emit destruction signal to the next Stream first - destroyNextStreams$.next(); - destroyNextStreams$.complete(); // we do not need it anymore - }), - share() // share side-effects - ); - - // Will emit when the current Stream should be destroyed. - const killCurrentStream$ = observableMerge(endOfCurrentStream$, destroyAll$); - - const periodStream$ = PeriodStream({ bufferType, - content: { manifest, period: basePeriod }, - garbageCollectors, - maxVideoBufferSize, - segmentFetcherCreator, - segmentBuffersStore, - options, - playbackObserver, - representationEstimator, - wantedBufferAhead } - ).pipe( - mergeMap((evt : IPeriodStreamEvent) : Observable => { - if (evt.type === "stream-status") { - if (evt.value.hasFinishedLoading) { - const nextPeriod = manifest.getPeriodAfter(basePeriod); - if (nextPeriod === null) { - return observableConcat(observableOf(evt), - observableOf(EVENTS.streamComplete(bufferType))); - } + consecutivePeriodStreamCb : IPeriodStreamCallbacks & { + periodStreamCleared(payload : IPeriodStreamClearedPayload) : void; + }, + cancelSignal : CancellationSignal + ) : void { + log.info("Stream: Creating new Stream for", bufferType, basePeriod.start); + /** + * Contains properties linnked to the next chronological `PeriodStream` that + * may be created here. + */ + let nextStreamInfo : { + /** Emits when the `PeriodStreamfor should be destroyed, if created. */ + canceller : TaskCanceller; + /** The `Period` concerned. */ + period : Period; + } | null = null; + + /** Emits when the `PeriodStream` linked to `basePeriod` should be destroyed. */ + const currentStreamCanceller = new TaskCanceller(); + currentStreamCanceller.linkToSignal(cancelSignal); + + // Stop current PeriodStream when the current position goes over the end of + // that Period. + playbackObserver.listen(({ position }, stopListeningObservations) => { + if (basePeriod.end !== undefined && + (position.pending ?? position.last) >= basePeriod.end) + { + log.info("Stream: Destroying PeriodStream as the current playhead moved above it", + bufferType, + basePeriod.start, + position.pending ?? position.last, + basePeriod.end); + stopListeningObservations(); + consecutivePeriodStreamCb.periodStreamCleared({ type: bufferType, + period: basePeriod }); + currentStreamCanceller.cancel(); + } + }, { clearSignal: cancelSignal, includeLastObservation: true }); + + const periodStreamArgs = { bufferType, + content: { manifest, period: basePeriod }, + garbageCollectors, + maxVideoBufferSize, + segmentFetcherCreator, + segmentBuffersStore, + options, + playbackObserver, + representationEstimator, + wantedBufferAhead }; + const periodStreamCallbacks : IPeriodStreamCallbacks = { + ...consecutivePeriodStreamCb, + streamStatusUpdate(value : IStreamStatusPayload) : void { + if (value.hasFinishedLoading) { + const nextPeriod = manifest.getPeriodAfter(basePeriod); + if (nextPeriod !== null) { // current Stream is full, create the next one if not - createNextPeriodStream$.next(nextPeriod); - } else { - // current Stream is active, destroy next Stream if created - destroyNextStreams$.next(); + createNextPeriodStream(nextPeriod); } + } else if (nextStreamInfo !== null) { + // current Stream is active, destroy next Stream if created + log.info("Stream: Destroying next PeriodStream due to current one being active", + bufferType, nextStreamInfo.period.start); + consecutivePeriodStreamCb + .periodStreamCleared({ type: bufferType, period: nextStreamInfo.period }); + nextStreamInfo.canceller.cancel(); + nextStreamInfo = null; } - return observableOf(evt); - }), - share() - ); - - // Stream for the current Period. - const currentStream$ : Observable = - observableConcat( - periodStream$.pipe(takeUntil(killCurrentStream$)), - observableOf(EVENTS.periodStreamCleared(bufferType, basePeriod)) - .pipe(tap(() => { - log.info("SO: Destroying Stream for", bufferType, basePeriod.start); - }))); - - return observableMerge(currentStream$, - nextPeriodStream$, - destroyAll$.pipe(ignoreElements())); + consecutivePeriodStreamCb.streamStatusUpdate(value); + }, + error(err : unknown) : void { + if (nextStreamInfo !== null) { + nextStreamInfo.canceller.cancel(); + nextStreamInfo = null; + } + currentStreamCanceller.cancel(); + consecutivePeriodStreamCb.error(err); + }, + }; + + PeriodStream(periodStreamArgs, periodStreamCallbacks, currentStreamCanceller.signal); + + /** + * Create `PeriodStream` for the next Period, specified under `nextPeriod`. + * @param {Object} nextPeriod + */ + function createNextPeriodStream(nextPeriod : Period) : void { + if (nextStreamInfo !== null) { + log.warn("Stream: Creating next `PeriodStream` while it was already created."); + consecutivePeriodStreamCb.periodStreamCleared({ type: bufferType, + period: nextStreamInfo.period }); + nextStreamInfo.canceller.cancel(); + } + const nextStreamCanceller = new TaskCanceller(); + nextStreamCanceller.linkToSignal(cancelSignal); + nextStreamInfo = { canceller: nextStreamCanceller, + period: nextPeriod }; + manageConsecutivePeriodStreams(bufferType, + nextPeriod, + consecutivePeriodStreamCb, + nextStreamInfo.canceller.signal); + } } } +export type IStreamOrchestratorPlaybackObservation = IPeriodStreamPlaybackObservation; + +/** Options tweaking the behavior of the StreamOrchestrator. */ +export type IStreamOrchestratorOptions = + IPeriodStreamOptions & + { wantedBufferAhead : IReadOnlySharedReference; + maxVideoBufferSize : IReadOnlySharedReference; + maxBufferAhead : IReadOnlySharedReference; + maxBufferBehind : IReadOnlySharedReference; }; + +/** Callbacks called by the `StreamOrchestrator` on various events. */ +export interface IStreamOrchestratorCallbacks + extends Omit +{ + /** + * Called when a `PeriodStream` has been removed. + * This event can be used for clean-up purposes. For example, you are free to + * remove from scope the object used to choose a track for that + * `PeriodStream`. + * + * This callback might not be called when a `PeriodStream` is cleared due to + * an `error` callback or to the `StreamOrchestrator` being cancellated as + * both already indicate implicitly that all `PeriodStream` have been cleared. + */ + periodStreamCleared(payload : IPeriodStreamClearedPayload) : void; + /** + * Called when a situation needs the MediaSource to be reloaded. + * + * Once the MediaSource is reloaded, the `StreamOrchestrator` need to be + * restarted from scratch. + */ + needsMediaSourceReload(payload : INeedsMediaSourceReloadPayload) : void; + /** + * Called when the stream is unable to load segments for a particular Period + * and buffer type until that Period becomes the currently-played Period. + * + * This might be the case for example when a track change happened for an + * upcoming Period, which necessitates the reloading of the media source + * once the Period is the current one. + * Here, the stream might stay in a locked mode for segments linked to that + * Period and buffer type, meaning it will not load any such segment until that + * next Period becomes the current one (in which case it will probably ask to + * reload through the proper callback, `needsMediaSourceReload`). + * + * This callback can be useful when investigating rebuffering situation: one + * might be due to the next Period not loading segment of a certain type + * because of a locked stream. In that case, playing until or seeking at the + * start of the corresponding Period should be enough to "unlock" the stream. + */ + lockedStream(payload : ILockedStreamPayload) : void; + /** + * Called after the SegmentBuffer have been "cleaned" to remove from it + * every non-decipherable segments - usually following an update of the + * decipherability status of some `Representation`(s). + * + * When that event is emitted, the current HTMLMediaElement's buffer might need + * to be "flushed" to continue (e.g. through a little seek operation) or in + * worst cases completely removed and re-created through the "reload" mechanism, + * depending on the platform. + */ + needsDecipherabilityFlush(payload : INeedsDecipherabilityFlushPayload) : void; +} + +/** Payload for the `periodStreamCleared` callback. */ +export interface IPeriodStreamClearedPayload { + /** + * The type of buffer linked to the `PeriodStream` we just removed. + * + * The combination of this and `Period` should give you enough information + * about which `PeriodStream` has been removed. + */ + type : IBufferType; + /** + * The `Period` linked to the `PeriodStream` we just removed. + * + * The combination of this and `Period` should give you enough information + * about which `PeriodStream` has been removed. + */ + period : Period; +} + +/** Payload for the `needsMediaSourceReload` callback. */ +export interface INeedsMediaSourceReloadPayload { + /** + * The position in seconds and the time at which the MediaSource should be + * reset once it has been reloaded. + */ + position : number; + /** + * If `true`, we want the HTMLMediaElement to play right after the reload is + * done. + * If `false`, we want to stay in a paused state at that point. + */ + autoPlay : boolean; +} + +/** Payload for the `lockedStream` callback. */ +export interface ILockedStreamPayload { + /** Period concerned. */ + period : Period; + /** Buffer type concerned. */ + bufferType : IBufferType; +} + +/** Payload for the `needsDecipherabilityFlush` callback. */ +export interface INeedsDecipherabilityFlushPayload { + /** + * Indicated in the case where the MediaSource has to be reloaded, + * in which case the time of the HTMLMediaElement should be reset to that + * position, in seconds, once reloaded. + */ + position : number; + /** + * If `true`, we want the HTMLMediaElement to play right after the flush is + * done. + * If `false`, we want to stay in a paused state at that point. + */ + autoPlay : boolean; + /** + * The duration (maximum seekable position) of the content. + * This is indicated in the case where a seek has to be performed, to avoid + * seeking too far in the content. + */ + duration : number; +} + /** * Returns `true` if low-level buffers have to be "flushed" after the given * `cleanedRanges` time ranges have been removed from an audio or video diff --git a/src/core/stream/period/create_empty_adaptation_stream.ts b/src/core/stream/period/create_empty_adaptation_stream.ts deleted file mode 100644 index 5b874de3b5..0000000000 --- a/src/core/stream/period/create_empty_adaptation_stream.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - combineLatest as observableCombineLatest, - mergeMap, - Observable, - of as observableOf, -} from "rxjs"; -import log from "../../../log"; -import { Period } from "../../../manifest"; -import { IReadOnlySharedReference } from "../../../utils/reference"; -import { IReadOnlyPlaybackObserver } from "../../api"; -import { IBufferType } from "../../segment_buffers"; -import { IStreamStatusEvent } from "../types"; -import { IPeriodStreamPlaybackObservation } from "./period_stream"; - -/** - * Create empty AdaptationStream Observable, linked to a Period. - * - * This observable will never download any segment and just emit a "full" - * event when reaching the end. - * @param {Observable} playbackObserver - * @param {Object} wantedBufferAhead - * @param {string} bufferType - * @param {Object} content - * @returns {Observable} - */ -export default function createEmptyAdaptationStream( - playbackObserver : IReadOnlyPlaybackObserver, - wantedBufferAhead : IReadOnlySharedReference, - bufferType : IBufferType, - content : { period : Period } -) : Observable { - const { period } = content; - let hasFinishedLoading = false; - const wantedBufferAhead$ = wantedBufferAhead.asObservable(); - const observation$ = playbackObserver.getReference().asObservable(); - return observableCombineLatest([observation$, - wantedBufferAhead$]).pipe( - mergeMap(([observation, wba]) => { - const position = observation.position.last; - if (period.end !== undefined && position + wba >= period.end) { - log.debug("Stream: full \"empty\" AdaptationStream", bufferType); - hasFinishedLoading = true; - } - return observableOf({ type: "stream-status" as const, - value: { period, - bufferType, - position, - imminentDiscontinuity: null, - hasFinishedLoading, - neededSegments: [], - shouldRefreshManifest: false } }); - }) - ); -} diff --git a/src/core/stream/period/index.ts b/src/core/stream/period/index.ts index 64f65b84a2..bb9e36b11c 100644 --- a/src/core/stream/period/index.ts +++ b/src/core/stream/period/index.ts @@ -14,17 +14,7 @@ * limitations under the License. */ -export { IAudioTrackSwitchingMode } from "../../../public_types"; -import PeriodStream, { - IPeriodStreamArguments, - IPeriodStreamOptions, - IPeriodStreamPlaybackObservation, -} from "./period_stream"; +import PeriodStream from "./period_stream"; +export * from "./types"; export default PeriodStream; - -export { - IPeriodStreamArguments, - IPeriodStreamOptions, - IPeriodStreamPlaybackObservation, -}; diff --git a/src/core/stream/period/period_stream.ts b/src/core/stream/period/period_stream.ts index f4f65e062f..a06db4fd66 100644 --- a/src/core/stream/period/period_stream.ts +++ b/src/core/stream/period/period_stream.ts @@ -14,312 +14,335 @@ * limitations under the License. */ -import { - catchError, - concat as observableConcat, - defer as observableDefer, - EMPTY, - ignoreElements, - map, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - ReplaySubject, - startWith, - switchMap, -} from "rxjs"; +import nextTick from "next-tick"; import config from "../../../config"; import { formatError, MediaError, } from "../../../errors"; import log from "../../../log"; -import Manifest, { +import { Adaptation, Period, } from "../../../manifest"; -import { IAudioTrackSwitchingMode } from "../../../public_types"; import objectAssign from "../../../utils/object_assign"; import { getLeftSizeOfRange } from "../../../utils/ranges"; import createSharedReference, { IReadOnlySharedReference, } from "../../../utils/reference"; -import fromCancellablePromise from "../../../utils/rx-from_cancellable_promise"; import TaskCanceller, { + CancellationError, CancellationSignal, } from "../../../utils/task_canceller"; -import WeakMapMemory from "../../../utils/weak_map_memory"; -import { IRepresentationEstimator } from "../../adaptive"; import { IReadOnlyPlaybackObserver } from "../../api"; -import { SegmentFetcherCreator } from "../../fetchers"; import SegmentBuffersStore, { IBufferType, ITextTrackSegmentBufferOptions, SegmentBuffer, } from "../../segment_buffers"; import AdaptationStream, { - IAdaptationStreamOptions, + IAdaptationStreamCallbacks, IAdaptationStreamPlaybackObservation, - IPausedPlaybackObservation, } from "../adaptation"; -import EVENTS from "../events_generators"; -import reloadAfterSwitch from "../reload_after_switch"; -import { IPositionPlaybackObservation } from "../representation"; import { - IAdaptationStreamEvent, - IPeriodStreamEvent, - IStreamWarningEvent, -} from "../types"; -import createEmptyStream from "./create_empty_adaptation_stream"; -import getAdaptationSwitchStrategy from "./get_adaptation_switch_strategy"; - - -/** Playback observation required by the `PeriodStream`. */ -export interface IPeriodStreamPlaybackObservation { - /** - * Information on whether the media element was paused at the time of the - * Observation. - */ - paused : IPausedPlaybackObservation; - /** - * Information on the current media position in seconds at the time of the - * Observation. - */ - position : IPositionPlaybackObservation; - /** `duration` property of the HTMLMediaElement. */ - duration : number; - /** `readyState` property of the HTMLMediaElement. */ - readyState : number; - /** Target playback rate at which we want to play the content. */ - speed : number; - /** Theoretical maximum position on the content that can currently be played. */ - maximumPosition : number; -} - -/** Arguments required by the `PeriodStream`. */ -export interface IPeriodStreamArguments { - bufferType : IBufferType; - content : { manifest : Manifest; - period : Period; }; - garbageCollectors : WeakMapMemory>; - segmentFetcherCreator : SegmentFetcherCreator; - segmentBuffersStore : SegmentBuffersStore; - playbackObserver : IReadOnlyPlaybackObserver; - options: IPeriodStreamOptions; - representationEstimator : IRepresentationEstimator; - wantedBufferAhead : IReadOnlySharedReference; - maxVideoBufferSize : IReadOnlySharedReference; -} + IPeriodStreamArguments, + IPeriodStreamCallbacks, + IPeriodStreamPlaybackObservation, +} from "./types"; +import getAdaptationSwitchStrategy from "./utils/get_adaptation_switch_strategy"; -/** Options tweaking the behavior of the PeriodStream. */ -export type IPeriodStreamOptions = - IAdaptationStreamOptions & - { - /** RxPlayer's behavior when switching the audio track. */ - audioTrackSwitchingMode : IAudioTrackSwitchingMode; - /** Behavior when a new video and/or audio codec is encountered. */ - onCodecSwitch : "continue" | "reload"; - /** Options specific to the text SegmentBuffer. */ - textTrackOptions? : ITextTrackSegmentBufferOptions; - }; /** - * Create single PeriodStream Observable: + * Create a single PeriodStream: * - Lazily create (or reuse) a SegmentBuffer for the given type. * - Create a Stream linked to an Adaptation each time it changes, to * download and append the corresponding segments to the SegmentBuffer. * - Announce when the Stream is full or is awaiting new Segments through * events - * @param {Object} args - * @returns {Observable} + * + * @param {Object} args - Various arguments allowing the `PeriodStream` to + * determine which Adaptation and which Representation to choose, as well as + * which segments to load from it. + * You can check the corresponding type for more information. + * @param {Object} callbacks - The `PeriodStream` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. + * + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `AdaptationStream` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `AdaptationStream` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, or until the `error` callback is called, whichever comes first. + * @param {Object} parentCancelSignal - `CancellationSignal` allowing, when + * triggered, to immediately stop all operations the `PeriodStream` is + * doing. */ -export default function PeriodStream({ - bufferType, - content, - garbageCollectors, - playbackObserver, - representationEstimator, - segmentFetcherCreator, - segmentBuffersStore, - options, - wantedBufferAhead, - maxVideoBufferSize, -} : IPeriodStreamArguments) : Observable { +export default function PeriodStream( + { bufferType, + content, + garbageCollectors, + playbackObserver, + representationEstimator, + segmentFetcherCreator, + segmentBuffersStore, + options, + wantedBufferAhead, + maxVideoBufferSize } : IPeriodStreamArguments, + callbacks : IPeriodStreamCallbacks, + parentCancelSignal : CancellationSignal +) : void { const { period } = content; - // Emits the chosen Adaptation for the current type. - // `null` when no Adaptation is chosen (e.g. no subtitles) - const adaptation$ = new ReplaySubject(1); - return adaptation$.pipe( - switchMap(( - adaptation : Adaptation | null, - switchNb : number - ) : Observable => { - /** - * If this is not the first Adaptation choice, we might want to apply a - * delta to the current position so we can re-play back some media in the - * new Adaptation to give some context back. - * This value contains this relative position, in seconds. - * @see reloadAfterSwitch - */ - const { DELTA_POSITION_AFTER_RELOAD } = config.getCurrent(); - const relativePosAfterSwitch = - switchNb === 0 ? 0 : - bufferType === "audio" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.audio : - bufferType === "video" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.video : - DELTA_POSITION_AFTER_RELOAD.trackSwitch.other; + /** + * Emits the chosen Adaptation for the current type. + * `null` when no Adaptation is chosen (e.g. no subtitles) + * `undefined` at the beginning (it can be ignored.). + */ + const adaptationRef = createSharedReference( + undefined, + parentCancelSignal + ); + + callbacks.periodStreamReady({ type: bufferType, period, adaptationRef }); + if (parentCancelSignal.isCancelled()) { + return; + } + + let currentStreamCanceller : TaskCanceller | undefined; + let isFirstAdaptationSwitch = true; + + adaptationRef.onUpdate((adaptation : Adaptation | null | undefined) => { + // As an IIFE to profit from async/await while respecting onUpdate's signature + (async () : Promise => { + if (adaptation === undefined) { + return; + } + const streamCanceller = new TaskCanceller(); + streamCanceller.linkToSignal(parentCancelSignal); + currentStreamCanceller?.cancel(); // Cancel oreviously created stream if one + currentStreamCanceller = streamCanceller; if (adaptation === null) { // Current type is disabled for that Period log.info(`Stream: Set no ${bufferType} Adaptation. P:`, period.start); const segmentBufferStatus = segmentBuffersStore.getStatus(bufferType); - let cleanBuffer$ : Observable; if (segmentBufferStatus.type === "initialized") { log.info(`Stream: Clearing previous ${bufferType} SegmentBuffer`); if (SegmentBuffersStore.isNative(bufferType)) { - return reloadAfterSwitch(period, bufferType, playbackObserver, 0); - } - const canceller = new TaskCanceller(); - cleanBuffer$ = fromCancellablePromise(canceller, () => { - if (period.end === undefined) { - return segmentBufferStatus.value.removeBuffer(period.start, - Infinity, - canceller.signal); - } else if (period.end <= period.start) { - return Promise.resolve(); + return askForMediaSourceReload(0, streamCanceller.signal); + } else { + const periodEnd = period.end ?? Infinity; + if (period.start > periodEnd) { + log.warn("Stream: Can't free buffer: period's start is after its end"); } else { - return segmentBufferStatus.value.removeBuffer(period.start, - period.end, - canceller.signal); + await segmentBufferStatus.value.removeBuffer(period.start, + periodEnd, + streamCanceller.signal); + if (streamCanceller.isUsed()) { + return; // The stream has been cancelled + } } - }); - } else { - if (segmentBufferStatus.type === "uninitialized") { - segmentBuffersStore.disableSegmentBuffer(bufferType); } - cleanBuffer$ = observableOf(null); + } else if (segmentBufferStatus.type === "uninitialized") { + segmentBuffersStore.disableSegmentBuffer(bufferType); + if (streamCanceller.isUsed()) { + return; // The stream has been cancelled + } } - return observableConcat( - cleanBuffer$.pipe(map(() => EVENTS.adaptationChange(bufferType, null, period))), - createEmptyStream(playbackObserver, wantedBufferAhead, bufferType, { period }) - ); + callbacks.adaptationChange({ type: bufferType, adaptation: null, period }); + if (streamCanceller.isUsed()) { + return; // Previous call has provoken Stream cancellation by side-effect + } + + return createEmptyAdaptationStream(playbackObserver, + wantedBufferAhead, + bufferType, + { period }, + callbacks, + streamCanceller.signal); } + /** + * If this is not the first Adaptation choice, we might want to apply a + * delta to the current position so we can re-play back some media in the + * new Adaptation to give some context back. + * This value contains this relative position, in seconds. + * @see askForMediaSourceReload + */ + const { DELTA_POSITION_AFTER_RELOAD } = config.getCurrent(); + const relativePosAfterSwitch = + isFirstAdaptationSwitch ? 0 : + bufferType === "audio" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.audio : + bufferType === "video" ? DELTA_POSITION_AFTER_RELOAD.trackSwitch.video : + DELTA_POSITION_AFTER_RELOAD.trackSwitch.other; + isFirstAdaptationSwitch = false; + if (SegmentBuffersStore.isNative(bufferType) && segmentBuffersStore.getStatus(bufferType).type === "disabled") { - return reloadAfterSwitch(period, - bufferType, - playbackObserver, - relativePosAfterSwitch); + return askForMediaSourceReload(relativePosAfterSwitch, streamCanceller.signal); } log.info(`Stream: Updating ${bufferType} adaptation`, `A: ${adaptation.id}`, `P: ${period.start}`); - const newStream$ = observableDefer(() => { - const readyState = playbackObserver.getReadyState(); - const segmentBuffer = createOrReuseSegmentBuffer(segmentBuffersStore, - bufferType, - adaptation, - options); - const playbackInfos = { currentTime: playbackObserver.getCurrentTime(), - readyState }; - const strategy = getAdaptationSwitchStrategy(segmentBuffer, - period, - adaptation, - playbackInfos, - options); - if (strategy.type === "needs-reload") { - return reloadAfterSwitch(period, - bufferType, - playbackObserver, - relativePosAfterSwitch); + callbacks.adaptationChange({ type: bufferType, adaptation, period }); + if (streamCanceller.isUsed()) { + return; // Previous call has provoken cancellation by side-effect + } + + const readyState = playbackObserver.getReadyState(); + const segmentBuffer = createOrReuseSegmentBuffer(segmentBuffersStore, + bufferType, + adaptation, + options); + const playbackInfos = { currentTime: playbackObserver.getCurrentTime(), + readyState }; + const strategy = getAdaptationSwitchStrategy(segmentBuffer, + period, + adaptation, + playbackInfos, + options); + if (strategy.type === "needs-reload") { + return askForMediaSourceReload(relativePosAfterSwitch, streamCanceller.signal); + } + + await segmentBuffersStore.waitForUsableBuffers(streamCanceller.signal); + if (streamCanceller.isUsed()) { + return; // The Stream has since been cancelled + } + if (strategy.type === "flush-buffer" || strategy.type === "clean-buffer") { + for (const { start, end } of strategy.value) { + await segmentBuffer.removeBuffer(start, end, streamCanceller.signal); + if (streamCanceller.isUsed()) { + return; // The Stream has since been cancelled + } } + if (strategy.type === "flush-buffer") { + callbacks.needsBufferFlush(); + if (streamCanceller.isUsed()) { + return ; // Previous callback cancelled the Stream by side-effect + } + } + } - const needsBufferFlush$ = strategy.type === "flush-buffer" - ? observableOf(EVENTS.needsBufferFlush()) - : EMPTY; - - const cleanBuffer$ = - strategy.type === "clean-buffer" || strategy.type === "flush-buffer" ? - observableConcat(...strategy.value.map(({ start, end }) => { - const canceller = new TaskCanceller(); - return fromCancellablePromise(canceller, () => - segmentBuffer.removeBuffer(start, end, canceller.signal)); - }) - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - ).pipe(ignoreElements()) : EMPTY; - - const bufferGarbageCollector$ = garbageCollectors.get(segmentBuffer); - const adaptationStream$ = createAdaptationStream(adaptation, segmentBuffer); - - const cancelWait = new TaskCanceller(); - return fromCancellablePromise(cancelWait, () => - segmentBuffersStore.waitForUsableBuffers(cancelWait.signal) - ).pipe(mergeMap(() => - observableConcat(cleanBuffer$, - needsBufferFlush$, - observableMerge(adaptationStream$, - bufferGarbageCollector$)))); - }); - - return observableConcat( - observableOf(EVENTS.adaptationChange(bufferType, adaptation, period)), - newStream$ - ); - }), - startWith(EVENTS.periodStreamReady(bufferType, period, adaptation$)) - ); + garbageCollectors.get(segmentBuffer)(streamCanceller.signal); + createAdaptationStream(adaptation, segmentBuffer, streamCanceller.signal); + })().catch((err) => { + if (err instanceof CancellationError) { + return; + } + currentStreamCanceller?.cancel(); + callbacks.error(err); + }); + }, { clearSignal: parentCancelSignal, emitCurrentValue: true }); /** * @param {Object} adaptation * @param {Object} segmentBuffer - * @returns {Observable} + * @param {Object} cancelSignal */ function createAdaptationStream( adaptation : Adaptation, - segmentBuffer : SegmentBuffer - ) : Observable { + segmentBuffer : SegmentBuffer, + cancelSignal : CancellationSignal + ) : void { const { manifest } = content; const adaptationPlaybackObserver = createAdaptationStreamPlaybackObserver(playbackObserver, segmentBuffer); - return AdaptationStream({ content: { manifest, period, adaptation }, - options, - playbackObserver: adaptationPlaybackObserver, - representationEstimator, - segmentBuffer, - segmentFetcherCreator, - wantedBufferAhead, - maxVideoBufferSize }).pipe( - catchError((error : unknown) => { - // Stream linked to a non-native media buffer should not impact the - // stability of the player. ie: if a text buffer sends an error, we want - // to continue playing without any subtitles - if (!SegmentBuffersStore.isNative(bufferType)) { - log.error(`Stream: ${bufferType} Stream crashed. Aborting it.`, - error instanceof Error ? error : ""); - segmentBuffersStore.disposeSegmentBuffer(bufferType); - - const formattedError = formatError(error, { - defaultCode: "NONE", - defaultReason: "Unknown `AdaptationStream` error", - }); - return observableConcat( - observableOf(EVENTS.warning(formattedError)), - createEmptyStream(playbackObserver, wantedBufferAhead, bufferType, { period }) - ); - } - log.error(`Stream: ${bufferType} Stream crashed. Stopping playback.`, + + AdaptationStream({ content: { manifest, period, adaptation }, + options, + playbackObserver: adaptationPlaybackObserver, + representationEstimator, + segmentBuffer, + segmentFetcherCreator, + wantedBufferAhead, + maxVideoBufferSize }, + { ...callbacks, error: onAdaptationStreamError }, + cancelSignal); + + function onAdaptationStreamError(error : unknown) : void { + // Stream linked to a non-native media buffer should not impact the + // stability of the player. ie: if a text buffer sends an error, we want + // to continue playing without any subtitles + if (!SegmentBuffersStore.isNative(bufferType)) { + log.error(`Stream: ${bufferType} Stream crashed. Aborting it.`, error instanceof Error ? error : ""); - throw error; - })); + segmentBuffersStore.disposeSegmentBuffer(bufferType); + + const formattedError = formatError(error, { + defaultCode: "NONE", + defaultReason: "Unknown `AdaptationStream` error", + }); + callbacks.warning(formattedError); + if (cancelSignal.isCancelled()) { + return ; // Previous callback cancelled the Stream by side-effect + } + + return createEmptyAdaptationStream(playbackObserver, + wantedBufferAhead, + bufferType, + { period }, + callbacks, + cancelSignal); + } + log.error(`Stream: ${bufferType} Stream crashed. Stopping playback.`, + error instanceof Error ? error : ""); + callbacks.error(error); + } + } + + /** + * Regularly ask to reload the MediaSource on each playback observation + * performed by the playback observer. + * + * If and only if the Period currently played corresponds to the concerned + * Period, applies an offset to the reloaded position corresponding to + * `deltaPos`. + * This can be useful for example when switching the audio/video tracks, where + * you might want to give back some context if that was the currently played + * track. + * + * @param {number} deltaPos - If the concerned Period is playing at the time + * this function is called, we will add this value, in seconds, to the current + * position to indicate the position we should reload at. + * This value allows to give back context (by replaying some media data) after + * a switch. + * @param {Object} cancelSignal + */ + function askForMediaSourceReload( + deltaPos : number, + cancelSignal : CancellationSignal + ) : void { + // We begin by scheduling a micro-task to reduce the possibility of race + // conditions where `askForMediaSourceReload` would be called synchronously before + // the next observation (which may reflect very different playback conditions) + // is actually received. + // It can happen when `askForMediaSourceReload` is called as a side-effect of + // the same event that triggers the playback observation to be emitted. + nextTick(() => { + playbackObserver.listen((observation) => { + const currentTime = playbackObserver.getCurrentTime(); + const pos = currentTime + deltaPos; + + // Bind to Period start and end + const position = Math.min(Math.max(period.start, pos), + period.end ?? Infinity); + const autoPlay = !(observation.paused.pending ?? playbackObserver.getIsPaused()); + callbacks.waitingMediaSourceReload({ bufferType, + period, + position, + autoPlay }); + }, { includeLastObservation: true, clearSignal: cancelSignal }); + }); } } @@ -377,17 +400,13 @@ function createAdaptationStreamPlaybackObserver( observationRef : IReadOnlySharedReference, cancellationSignal : CancellationSignal ) : IReadOnlySharedReference { - const newRef = createSharedReference(constructAdaptationStreamPlaybackObservation()); + const newRef = createSharedReference(constructAdaptationStreamPlaybackObservation(), + cancellationSignal); observationRef.onUpdate(emitAdaptationStreamPlaybackObservation, { clearSignal: cancellationSignal, emitCurrentValue: false, }); - - cancellationSignal.register(() => { - newRef.finish(); - }); - return newRef; function constructAdaptationStreamPlaybackObservation( @@ -404,3 +423,48 @@ function createAdaptationStreamPlaybackObserver( }); } + +/** + * Create empty AdaptationStream, linked to a Period. + * This AdaptationStream will never download any segment and just emit a "full" + * event when reaching the end. + * @param {Object} playbackObserver + * @param {Object} wantedBufferAhead + * @param {string} bufferType + * @param {Object} content + * @param {Object} callbacks + * @param {Object} cancelSignal + */ +function createEmptyAdaptationStream( + playbackObserver : IReadOnlyPlaybackObserver, + wantedBufferAhead : IReadOnlySharedReference, + bufferType : IBufferType, + content : { period : Period }, + callbacks : Pick, "streamStatusUpdate">, + cancelSignal : CancellationSignal +) : void { + const { period } = content; + let hasFinishedLoading = false; + wantedBufferAhead.onUpdate(sendStatus, + { emitCurrentValue: false, clearSignal: cancelSignal }); + playbackObserver.listen(sendStatus, + { includeLastObservation: false, clearSignal: cancelSignal }); + sendStatus(); + + function sendStatus() : void { + const observation = playbackObserver.getReference().getValue(); + const wba = wantedBufferAhead.getValue(); + const position = observation.position.last; + if (period.end !== undefined && position + wba >= period.end) { + log.debug("Stream: full \"empty\" AdaptationStream", bufferType); + hasFinishedLoading = true; + } + callbacks.streamStatusUpdate({ period, + bufferType, + position, + imminentDiscontinuity: null, + isEmptyStream: true, + hasFinishedLoading, + neededSegments: [] }); + } +} diff --git a/src/core/stream/period/types.ts b/src/core/stream/period/types.ts new file mode 100644 index 0000000000..95d9430a60 --- /dev/null +++ b/src/core/stream/period/types.ts @@ -0,0 +1,132 @@ +import Manifest, { + Adaptation, + Period, +} from "../../../manifest"; +import { IAudioTrackSwitchingMode } from "../../../public_types"; +import { IReadOnlySharedReference, ISharedReference } from "../../../utils/reference"; +import { CancellationSignal } from "../../../utils/task_canceller"; +import WeakMapMemory from "../../../utils/weak_map_memory"; +import { IRepresentationEstimator } from "../../adaptive"; +import { IReadOnlyPlaybackObserver } from "../../api"; +import { SegmentFetcherCreator } from "../../fetchers"; +import SegmentBuffersStore, { + IBufferType, + ITextTrackSegmentBufferOptions, + SegmentBuffer, +} from "../../segment_buffers"; +import { + IAdaptationStreamCallbacks, + IAdaptationStreamOptions, + IPausedPlaybackObservation, +} from "../adaptation"; +import { IPositionPlaybackObservation } from "../representation"; + +/** Callbacks called by the `AdaptationStream` on various events. */ +export interface IPeriodStreamCallbacks extends + IAdaptationStreamCallbacks +{ + /** + * Called when a new `PeriodStream` is ready to start but needs an Adaptation + * (i.e. track) to be chosen first. + */ + periodStreamReady(payload : IPeriodStreamReadyPayload) : void; + /** + * Called when a new `AdaptationStream` is created to load segments from an + * `Adaptation`. + */ + adaptationChange(payload : IAdaptationChangePayload) : void; + /** + * Some situations might require the browser's buffers to be refreshed. + * This callback is called when such situation arised. + * + * Generally flushing/refreshing low-level buffers can be performed simply by + * performing a very small seek. + */ + needsBufferFlush() : void; +} + +/** Payload for the `adaptationChange` callback. */ +export interface IAdaptationChangePayload { + /** The type of buffer for which the Representation is changing. */ + type : IBufferType; + /** The `Period` linked to the `RepresentationStream` we're creating. */ + period : Period; + /** + * The `Adaptation` linked to the `AdaptationStream` we're creating. + * `null` when we're choosing no Adaptation at all. + */ + adaptation : Adaptation | + null; +} + +/** Payload for the `periodStreamReady` callback. */ +export interface IPeriodStreamReadyPayload { + /** The type of buffer linked to the `PeriodStream` we want to create. */ + type : IBufferType; + /** The `Period` linked to the `PeriodStream` we have created. */ + period : Period; + /** + * The reference through which any Adaptation (i.e. track) choice should be + * emitted for that `PeriodStream`. + * + * The `PeriodStream` will not do anything until this Reference has emitted + * at least one to give its initial choice. + * You can send `null` through it to tell this `PeriodStream` that you don't + * want any `Adaptation`. + * It is set to `undefined` by default, you SHOULD NOT set it to `undefined` + * yourself. + */ + adaptationRef : ISharedReference; +} + +/** Playback observation required by the `PeriodStream`. */ +export interface IPeriodStreamPlaybackObservation { + /** + * Information on whether the media element was paused at the time of the + * Observation. + */ + paused : IPausedPlaybackObservation; + /** + * Information on the current media position in seconds at the time of the + * Observation. + */ + position : IPositionPlaybackObservation; + /** `duration` property of the HTMLMediaElement. */ + duration : number; + /** `readyState` property of the HTMLMediaElement. */ + readyState : number; + /** Target playback rate at which we want to play the content. */ + speed : number; + /** Theoretical maximum position on the content that can currently be played. */ + maximumPosition : number; +} + +/** Arguments required by the `PeriodStream`. */ +export interface IPeriodStreamArguments { + bufferType : IBufferType; + content : { manifest : Manifest; + period : Period; }; + garbageCollectors : WeakMapMemory void>; + segmentFetcherCreator : SegmentFetcherCreator; + segmentBuffersStore : SegmentBuffersStore; + playbackObserver : IReadOnlyPlaybackObserver; + options: IPeriodStreamOptions; + representationEstimator : IRepresentationEstimator; + wantedBufferAhead : IReadOnlySharedReference; + maxVideoBufferSize : IReadOnlySharedReference; +} + +/** Options tweaking the behavior of the PeriodStream. */ +export type IPeriodStreamOptions = + IAdaptationStreamOptions & + { + /** RxPlayer's behavior when switching the audio track. */ + audioTrackSwitchingMode : IAudioTrackSwitchingMode; + /** Behavior when a new video and/or audio codec is encountered. */ + onCodecSwitch : "continue" | "reload"; + /** Options specific to the text SegmentBuffer. */ + textTrackOptions? : ITextTrackSegmentBufferOptions; + }; + +export { IAudioTrackSwitchingMode } from "../../../public_types"; diff --git a/src/core/stream/period/get_adaptation_switch_strategy.ts b/src/core/stream/period/utils/get_adaptation_switch_strategy.ts similarity index 96% rename from src/core/stream/period/get_adaptation_switch_strategy.ts rename to src/core/stream/period/utils/get_adaptation_switch_strategy.ts index b1aa2782ff..5d6f09ff6e 100644 --- a/src/core/stream/period/get_adaptation_switch_strategy.ts +++ b/src/core/stream/period/utils/get_adaptation_switch_strategy.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import config from "../../../config"; +import config from "../../../../config"; import { Adaptation, Period, -} from "../../../manifest"; -import { IAudioTrackSwitchingMode } from "../../../public_types"; -import areCodecsCompatible from "../../../utils/are_codecs_compatible"; +} from "../../../../manifest"; +import { IAudioTrackSwitchingMode } from "../../../../public_types"; +import areCodecsCompatible from "../../../../utils/are_codecs_compatible"; import { convertToRanges, excludeFromRanges, @@ -28,11 +28,11 @@ import { isTimeInRange, isTimeInRanges, keepRangeIntersection, -} from "../../../utils/ranges"; +} from "../../../../utils/ranges"; import { IBufferedChunk, SegmentBuffer, -} from "../../segment_buffers"; +} from "../../../segment_buffers"; export type IAdaptationSwitchStrategy = diff --git a/src/core/stream/reload_after_switch.ts b/src/core/stream/reload_after_switch.ts deleted file mode 100644 index bb145d914b..0000000000 --- a/src/core/stream/reload_after_switch.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - map, - mergeMap, - Observable, -} from "rxjs"; -import { Period } from "../../manifest"; -import nextTickObs from "../../utils/rx-next-tick"; -import { IReadOnlyPlaybackObserver } from "../api"; -import { IBufferType } from "../segment_buffers"; -import EVENTS from "./events_generators"; -import { IWaitingMediaSourceReloadInternalEvent } from "./types"; - -/** - * Regularly ask to reload the MediaSource on each playback observation - * performed by the playback observer. - * - * If and only if the Period currently played corresponds to `Period`, applies - * an offset to the reloaded position corresponding to `deltaPos`. - * This can be useful for example when switching the audio/video track, where - * you might want to give back some context if that was the currently played - * track. - * - * @param {Object} period - The Period linked to the Adaptation or - * Representation that you want to switch to. - * @param {Observable} playbackObserver - emit playback conditions. - * Has to emit last playback conditions immediately on subscribe. - * @param {number} deltaPos - If the concerned Period is playing at the time - * this function is called, we will add this value, in seconds, to the current - * position to indicate the position we should reload at. - * This value allows to give back context (by replaying some media data) after - * a switch. - * @returns {Observable} - */ -export default function reloadAfterSwitch( - period : Period, - bufferType : IBufferType, - playbackObserver : IReadOnlyPlaybackObserver<{ - paused : { last: boolean; pending: boolean | undefined }; - }>, - deltaPos : number -) : Observable { - // We begin by scheduling a micro-task to reduce the possibility of race - // conditions where `reloadAfterSwitch` would be called synchronously before - // the next observation (which may reflect very different playback conditions) - // is actually received. - // It can happen when `reloadAfterSwitch` is called as a side-effect of the - // same event that triggers the playback observation to be emitted. - return nextTickObs().pipe( - mergeMap(() => playbackObserver.getReference().asObservable()), - map((observation) => { - const currentTime = playbackObserver.getCurrentTime(); - const pos = currentTime + deltaPos; - - // Bind to Period start and end - const reloadAt = Math.min(Math.max(period.start, pos), - period.end ?? Infinity); - const autoPlay = !(observation.paused.pending ?? playbackObserver.getIsPaused()); - return EVENTS.waitingMediaSourceReload(bufferType, period, reloadAt, autoPlay); - })); -} diff --git a/src/core/stream/representation/README.md b/src/core/stream/representation/README.md index 03665aff8e..e7ed6e685b 100644 --- a/src/core/stream/representation/README.md +++ b/src/core/stream/representation/README.md @@ -11,27 +11,6 @@ playback conditions. It then download and push them to a linked `SegmentBuffer` (the media buffer containing the segments for later decoding). -Multiple `RepresentationStream` observables can be ran on the same -`SegmentBuffer` without problems, as long as they are linked to different -Periods of the Manifest. +Multiple `RepresentationStream` can be ran on the same `SegmentBuffer` without +problems, as long as they are linked to different Periods of the Manifest. This allows for example smooth transitions between multiple periods. - - - -## Return value ################################################################ - -The `RepresentationStream` returns an Observable which emits multiple -notifications depending on what is happening at its core, like: - - - when segments are scheduled for download - - - when segments are pushed to the associated `SegmentBuffer` - - - when the Manifest needs to be refreshed to obtain information on possible - - - whether the `RepresentationStream` finished to load segments until the end - of the current Period. This can for example allow the creation of a - `RepresentationStream` for the next Period for pre-loading purposes. - - - whether there are discontinuities: holes in the stream that won't be filled - by segments and can thus be skipped diff --git a/src/core/stream/representation/downloading_queue.ts b/src/core/stream/representation/downloading_queue.ts deleted file mode 100644 index a9f9f4172f..0000000000 --- a/src/core/stream/representation/downloading_queue.ts +++ /dev/null @@ -1,632 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - defer as observableDefer, - EMPTY, - filter, - finalize, - merge as observableMerge, - Observable, - of as observableOf, - share, - Subscription, - switchMap, -} from "rxjs"; -import log from "../../../log"; -import Manifest, { - Adaptation, - ISegment, - Period, - Representation, -} from "../../../manifest"; -import { IPlayerError } from "../../../public_types"; -import { - ISegmentParserParsedInitChunk, - ISegmentParserParsedMediaChunk, -} from "../../../transports"; -import assert from "../../../utils/assert"; -import objectAssign from "../../../utils/object_assign"; -import createSharedReference, { - IReadOnlySharedReference, - ISharedReference, -} from "../../../utils/reference"; -import TaskCanceller from "../../../utils/task_canceller"; -import { IPrioritizedSegmentFetcher } from "../../fetchers"; -import { IQueuedSegment } from "../types"; - -/** - * Class scheduling segment downloads for a single Representation. - * @class DownloadingQueue - */ -export default class DownloadingQueue { - /** Context of the Representation that will be loaded through this DownloadingQueue. */ - private _content : IDownloadingQueueContext; - /** - * Observable doing segment requests and emitting related events. - * We only can have maximum one at a time. - * `null` when `start` has never been called. - */ - private _currentObs$ : Observable> | null; - /** - * Current queue of segments scheduled for download. - * - * Segments whose request are still pending are still in that queue. Segments - * are only removed from it once their request has succeeded. - */ - private _downloadQueue : IReadOnlySharedReference; - /** - * Pending request for the initialization segment. - * `null` if no request is pending for it. - */ - private _initSegmentRequest : ISegmentRequestObject|null; - /** - * Pending request for a media (i.e. non-initialization) segment. - * `null` if no request is pending for it. - */ - private _mediaSegmentRequest : ISegmentRequestObject|null; - /** Interface used to load segments. */ - private _segmentFetcher : IPrioritizedSegmentFetcher; - - /** - * Emit the timescale anounced in the initialization segment once parsed. - * `undefined` when this is not yet known. - * `null` when no initialization segment or timescale exists. - */ - private _initSegmentInfoRef : ISharedReference; - /** - * Some media segments might have been loaded and are only awaiting for the - * initialization segment to be parsed before being parsed themselves. - * This `Set` will contain the `id` property of all segments that are - * currently awaiting this event. - */ - private _mediaSegmentsAwaitingInitMetadata : Set; - - /** - * Create a new `DownloadingQueue`. - * - * @param {Object} content - The context of the Representation you want to - * load segments for. - * @param {Object} downloadQueue - Queue of segments you want to load. - * @param {Object} segmentFetcher - Interface to facilitate the download of - * segments. - * @param {boolean} hasInitSegment - Declare that an initialization segment - * will need to be downloaded. - * - * A `DownloadingQueue` ALWAYS wait for the initialization segment to be - * loaded and parsed before parsing a media segment. - * - * In cases where no initialization segment exist, this would lead to the - * `DownloadingQueue` waiting indefinitely for it. - * - * By setting that value to `false`, you anounce to the `DownloadingQueue` - * that it should not wait for an initialization segment before parsing a - * media segment. - */ - constructor( - content: IDownloadingQueueContext, - downloadQueue : IReadOnlySharedReference, - segmentFetcher : IPrioritizedSegmentFetcher, - hasInitSegment : boolean - ) { - this._content = content; - this._currentObs$ = null; - this._downloadQueue = downloadQueue; - this._initSegmentRequest = null; - this._mediaSegmentRequest = null; - this._segmentFetcher = segmentFetcher; - this._initSegmentInfoRef = createSharedReference(undefined); - this._mediaSegmentsAwaitingInitMetadata = new Set(); - if (!hasInitSegment) { - this._initSegmentInfoRef.setValue(null); - } - } - - /** - * Returns the initialization segment currently being requested. - * Returns `null` if no initialization segment request is pending. - * @returns {Object | null} - */ - public getRequestedInitSegment() : ISegment | null { - return this._initSegmentRequest === null ? null : - this._initSegmentRequest.segment; - } - - /** - * Returns the media segment currently being requested. - * Returns `null` if no media segment request is pending. - * @returns {Object | null} - */ - public getRequestedMediaSegment() : ISegment | null { - return this._mediaSegmentRequest === null ? null : - this._mediaSegmentRequest.segment; - } - - /** - * Start the current downloading queue, emitting events as it loads and parses - * initialization and media segments. - * - * If it was already started, returns the same - shared - Observable. - * @returns {Observable} - */ - public start() : Observable> { - if (this._currentObs$ !== null) { - return this._currentObs$; - } - const obs = observableDefer(() => { - const mediaQueue$ = this._downloadQueue.asObservable().pipe( - filter(({ segmentQueue }) => { - // First, the first elements of the segmentQueue might be already - // loaded but awaiting the initialization segment to be parsed. - // Filter those out. - let nextSegmentToLoadIdx = 0; - for (; nextSegmentToLoadIdx < segmentQueue.length; nextSegmentToLoadIdx++) { - const nextSegment = segmentQueue[nextSegmentToLoadIdx].segment; - if (!this._mediaSegmentsAwaitingInitMetadata.has(nextSegment.id)) { - break; - } - } - - const currentSegmentRequest = this._mediaSegmentRequest; - if (nextSegmentToLoadIdx >= segmentQueue.length) { - return currentSegmentRequest !== null; - } else if (currentSegmentRequest === null) { - return true; - } - const nextItem = segmentQueue[nextSegmentToLoadIdx]; - if (currentSegmentRequest.segment.id !== nextItem.segment.id) { - return true; - } - if (currentSegmentRequest.priority !== nextItem.priority) { - this._segmentFetcher.updatePriority(currentSegmentRequest.request, - nextItem.priority); - } - return false; - }), - switchMap(({ segmentQueue }) => - segmentQueue.length > 0 ? this._requestMediaSegments() : - EMPTY)); - - const initSegmentPush$ = this._downloadQueue.asObservable().pipe( - filter((next) => { - const initSegmentRequest = this._initSegmentRequest; - if (next.initSegment !== null && initSegmentRequest !== null) { - if (next.initSegment.priority !== initSegmentRequest.priority) { - this._segmentFetcher.updatePriority(initSegmentRequest.request, - next.initSegment.priority); - } - return false; - } else { - return next.initSegment === null || initSegmentRequest === null; - } - }), - switchMap((nextQueue) => { - if (nextQueue.initSegment === null) { - return EMPTY; - } - return this._requestInitSegment(nextQueue.initSegment); - })); - - return observableMerge(initSegmentPush$, mediaQueue$); - }).pipe(share()); - - this._currentObs$ = obs; - - return obs; - } - - /** - * Internal logic performing media segment requests. - * @returns {Observable} - */ - private _requestMediaSegments( - ) : Observable | - IEndOfSegmentEvent> { - - const { segmentQueue } = this._downloadQueue.getValue(); - const currentNeededSegment = segmentQueue[0]; - - /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - const self = this; - - return observableDefer(() => - recursivelyRequestSegments(currentNeededSegment) - ).pipe(finalize(() => { this._mediaSegmentRequest = null; })); - - function recursivelyRequestSegments( - startingSegment : IQueuedSegment | undefined - ) : Observable | - IEndOfSegmentEvent> { - if (startingSegment === undefined) { - return observableOf({ type : "end-of-queue", - value : null }); - } - const { segment, priority } = startingSegment; - const context = objectAssign({ segment }, self._content); - return new Observable< - ILoaderRetryEvent | - IEndOfQueueEvent | - IParsedSegmentEvent | - IEndOfSegmentEvent - >((obs) => { - /** TaskCanceller linked to this Observable's lifecycle. */ - const canceller = new TaskCanceller(); - - /** - * If `true` , the Observable has either errored, completed, or was - * unsubscribed from. - * This only conserves the Observable for the current segment's request, - * not the other recursively-created future ones. - */ - let isComplete = false; - - /** - * Subscription to request the following segment (as this function is - * recursive). - * `undefined` if no following segment has been requested. - */ - let nextSegmentSubscription : Subscription | undefined; - - /** - * If true, we're currently waiting for the initialization segment to be - * parsed before parsing a received chunk. - * - * In that case, the `DownloadingQueue` has to remain careful to only - * send further events and complete the Observable only once the - * initialization segment has been parsed AND the chunk parsing has been - * done (this can be done very simply by listening to the same - * `ISharedReference`, as its callbacks are called in the same order - * than the one in which they are added. - */ - let isWaitingOnInitSegment = false; - - /** Scheduled actual segment request. */ - const request = self._segmentFetcher.createRequest(context, priority, { - - /** - * Callback called when the request has to be retried. - * @param {Error} error - */ - onRetry(error : IPlayerError) : void { - obs.next({ type: "retry" as const, value: { segment, error } }); - }, - - /** - * Callback called when the request has to be interrupted and - * restarted later. - */ - beforeInterrupted() { - log.info("Stream: segment request interrupted temporarly.", - segment.id, - segment.time); - }, - - /** - * Callback called when a decodable chunk of the segment is available. - * @param {Function} parse - Function allowing to parse the segment. - */ - onChunk( - parse : ( - initTimescale : number | undefined - ) => ISegmentParserParsedInitChunk | ISegmentParserParsedMediaChunk - ) : void { - const initTimescale = self._initSegmentInfoRef.getValue(); - if (initTimescale !== undefined) { - emitChunk(parse(initTimescale ?? undefined)); - } else { - isWaitingOnInitSegment = true; - - // We could also technically call `waitUntilDefined` in both cases, - // but I found it globally clearer to segregate the two cases, - // especially to always have a meaningful `isWaitingOnInitSegment` - // boolean which is a very important variable. - self._initSegmentInfoRef.waitUntilDefined((actualTimescale) => { - emitChunk(parse(actualTimescale ?? undefined)); - }, { clearSignal: canceller.signal }); - } - }, - - /** Callback called after all chunks have been sent. */ - onAllChunksReceived() : void { - if (!isWaitingOnInitSegment) { - obs.next({ type: "end-of-segment" as const, - value: { segment } }); - } else { - self._mediaSegmentsAwaitingInitMetadata.add(segment.id); - self._initSegmentInfoRef.waitUntilDefined(() => { - obs.next({ type: "end-of-segment" as const, - value: { segment } }); - self._mediaSegmentsAwaitingInitMetadata.delete(segment.id); - isWaitingOnInitSegment = false; - }, { clearSignal: canceller.signal }); - } - }, - - /** - * Callback called right after the request ended but before the next - * requests are scheduled. It is used to schedule the next segment. - */ - beforeEnded() : void { - self._mediaSegmentRequest = null; - - if (isWaitingOnInitSegment) { - self._initSegmentInfoRef.waitUntilDefined( - continueToNextSegment, { clearSignal: canceller.signal }); - } else { - continueToNextSegment(); - } - }, - - }, canceller.signal); - - request.catch((error : unknown) => { - if (!isComplete) { - isComplete = true; - obs.error(error); - } - }); - - self._mediaSegmentRequest = { segment, priority, request }; - return () => { - self._mediaSegmentsAwaitingInitMetadata.delete(segment.id); - if (nextSegmentSubscription !== undefined) { - nextSegmentSubscription.unsubscribe(); - } - if (isComplete) { - return; - } - isComplete = true; - isWaitingOnInitSegment = false; - canceller.cancel(); - }; - - function emitChunk( - parsed : ISegmentParserParsedInitChunk | - ISegmentParserParsedMediaChunk - ) : void { - assert(parsed.segmentType === "media", - "Should have loaded a media segment."); - obs.next(objectAssign({}, - parsed, - { type: "parsed-media" as const, - segment })); - } - - function continueToNextSegment() : void { - const lastQueue = self._downloadQueue.getValue().segmentQueue; - if (lastQueue.length === 0) { - obs.next({ type : "end-of-queue" as const, - value : null }); - isComplete = true; - obs.complete(); - return; - } else if (lastQueue[0].segment.id === segment.id) { - lastQueue.shift(); - } - isComplete = true; - nextSegmentSubscription = recursivelyRequestSegments(lastQueue[0]) - .subscribe(obs); - } - }); - } - } - - /** - * Internal logic performing initialization segment requests. - * @param {Object} queuedInitSegment - * @returns {Observable} - */ - private _requestInitSegment( - queuedInitSegment : IQueuedSegment | null - ) : Observable | - IEndOfSegmentEvent> { - if (queuedInitSegment === null) { - this._initSegmentRequest = null; - return EMPTY; - } - - /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - const self = this; - return new Observable< - ILoaderRetryEvent | - IParsedInitSegmentEvent | - IEndOfSegmentEvent - >((obs) => { - /** TaskCanceller linked to this Observable's lifecycle. */ - const canceller = new TaskCanceller(); - const { segment, priority } = queuedInitSegment; - const context = objectAssign({ segment }, this._content); - - /** - * If `true` , the Observable has either errored, completed, or was - * unsubscribed from. - */ - let isComplete = false; - - const request = this._segmentFetcher.createRequest(context, priority, { - onRetry(err : IPlayerError) { - obs.next({ type: "retry" as const, - value: { segment, error: err } }); - }, - beforeInterrupted() { - log.info("Stream: init segment request interrupted temporarly.", segment.id); - }, - beforeEnded() { - self._initSegmentRequest = null; - isComplete = true; - obs.complete(); - }, - onChunk(parse : (x : undefined) => ISegmentParserParsedInitChunk | - ISegmentParserParsedMediaChunk) - { - const parsed = parse(undefined); - assert(parsed.segmentType === "init", - "Should have loaded an init segment."); - obs.next(objectAssign({}, - parsed, - { type: "parsed-init" as const, - segment })); - if (parsed.segmentType === "init") { - self._initSegmentInfoRef.setValue(parsed.initTimescale ?? null); - } - - }, - onAllChunksReceived() { - obs.next({ type: "end-of-segment" as const, - value: { segment } }); - }, - }, canceller.signal); - - request.catch((error : unknown) => { - if (!isComplete) { - isComplete = true; - obs.error(error); - } - }); - - this._initSegmentRequest = { segment, priority, request }; - - return () => { - this._initSegmentRequest = null; - if (isComplete) { - return; - } - isComplete = true; - canceller.cancel(); - }; - }); - } -} - -/** Event sent by the DownloadingQueue. */ -export type IDownloadingQueueEvent = IParsedInitSegmentEvent | - IParsedSegmentEvent | - IEndOfSegmentEvent | - ILoaderRetryEvent | - IEndOfQueueEvent; - -/** - * Notify that the initialization segment has been fully loaded and parsed. - * - * You can now push that segment to its corresponding buffer and use its parsed - * metadata. - * - * Only sent if an initialization segment exists (when the `DownloadingQueue`'s - * `hasInitSegment` constructor option has been set to `true`). - * In that case, an `IParsedInitSegmentEvent` will always be sent before any - * `IParsedSegmentEvent` event is sent. - */ -export type IParsedInitSegmentEvent = ISegmentParserParsedInitChunk & - { segment : ISegment; - type : "parsed-init"; }; - -/** - * Notify that a media chunk (decodable sub-part of a media segment) has been - * loaded and parsed. - * - * If an initialization segment exists (when the `DownloadingQueue`'s - * `hasInitSegment` constructor option has been set to `true`), an - * `IParsedSegmentEvent` will always be sent AFTER the `IParsedInitSegmentEvent` - * event. - * - * It can now be pushed to its corresponding buffer. Note that there might be - * multiple `IParsedSegmentEvent` for a single segment, if that segment is - * divided into multiple decodable chunks. - * You will know that all `IParsedSegmentEvent` have been loaded for a given - * segment once you received the `IEndOfSegmentEvent` for that segment. - */ -export type IParsedSegmentEvent = ISegmentParserParsedMediaChunk & - { segment : ISegment; - type : "parsed-media"; }; - -/** Notify that a media or initialization segment has been fully-loaded. */ -export interface IEndOfSegmentEvent { type : "end-of-segment"; - value: { segment : ISegment }; } - -/** - * Notify that a media or initialization segment request is retried. - * This happened most likely because of an HTTP error. - */ -export interface ILoaderRetryEvent { type : "retry"; - value : { segment : ISegment; - error : IPlayerError; }; } - -/** - * Notify that the media segment queue is now empty. - * This can be used to re-check if any segment are now needed. - */ -export interface IEndOfQueueEvent { type : "end-of-queue"; value : null } - -/** - * Structure of the object that has to be emitted through the `downloadQueue` - * Observable, to signal which segments are currently needed. - */ -export interface IDownloadQueueItem { - /** - * A potential initialization segment that needs to be loaded and parsed. - * It will generally be requested in parralel of the first media segments. - * - * Can be set to `null` if you don't need to load the initialization segment - * for now. - * - * If the `DownloadingQueue`'s `hasInitSegment` constructor option has been - * set to `true`, no media segment will be parsed before the initialization - * segment has been loaded and parsed. - */ - initSegment : IQueuedSegment | null; - - /** - * The queue of media segments currently needed for download. - * - * Those will be loaded from the first element in that queue to the last - * element in it. - * - * Note that any media segments in the segment queue will only be parsed once - * either of these is true: - * - An initialization segment has been loaded and parsed by this - * `DownloadingQueue` instance. - * - The `DownloadingQueue`'s `hasInitSegment` constructor option has been - * set to `false`. - */ - segmentQueue : IQueuedSegment[]; -} - -/** Object describing a pending Segment request. */ -interface ISegmentRequestObject { - /** The segment the request is for. */ - segment : ISegment; - /** The request Observable itself. Can be used to update its priority. */ - request: Promise; - /** Last set priority of the segment request (lower number = higher priority). */ - priority : number; -} - -/** Context for segments downloaded through the DownloadingQueue. */ -export interface IDownloadingQueueContext { - /** Adaptation linked to the segments you want to load. */ - adaptation : Adaptation; - /** Manifest linked to the segments you want to load. */ - manifest : Manifest; - /** Period linked to the segments you want to load. */ - period : Period; - /** Representation linked to the segments you want to load. */ - representation : Representation; -} diff --git a/src/core/stream/representation/index.ts b/src/core/stream/representation/index.ts index aa2fbc565d..2f8a69fbbd 100644 --- a/src/core/stream/representation/index.ts +++ b/src/core/stream/representation/index.ts @@ -14,17 +14,7 @@ * limitations under the License. */ -import RepresentationStream, { - IPositionPlaybackObservation, - IRepresentationStreamArguments, - IRepresentationStreamPlaybackObservation, - ITerminationOrder, -} from "./representation_stream"; +import RepresentationStream from "./representation_stream"; +export * from "./types"; export default RepresentationStream; -export { - IPositionPlaybackObservation, - IRepresentationStreamArguments, - IRepresentationStreamPlaybackObservation, - ITerminationOrder, -}; diff --git a/src/core/stream/representation/push_init_segment.ts b/src/core/stream/representation/push_init_segment.ts deleted file mode 100644 index 704a905534..0000000000 --- a/src/core/stream/representation/push_init_segment.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - defer as observableDefer, - EMPTY, - map, - Observable, -} from "rxjs"; -import Manifest, { - Adaptation, - ISegment, - Period, - Representation, -} from "../../../manifest"; -import fromCancellablePromise from "../../../utils/rx-from_cancellable_promise"; -import TaskCanceller from "../../../utils/task_canceller"; -import { IReadOnlyPlaybackObserver } from "../../api"; -import { - IPushedChunkData, - SegmentBuffer, -} from "../../segment_buffers"; -import EVENTS from "../events_generators"; -import { IStreamEventAddedSegment } from "../types"; -import appendSegmentToBuffer from "./append_segment_to_buffer"; -import { IRepresentationStreamPlaybackObservation } from "./representation_stream"; - -/** - * Push the initialization segment to the SegmentBuffer. - * The Observable returned: - * - emit an event once the segment has been pushed. - * - throws on Error. - * @param {Object} args - * @returns {Observable} - */ -export default function pushInitSegment( - { playbackObserver, - content, - segment, - segmentData, - segmentBuffer } : - { playbackObserver : IReadOnlyPlaybackObserver< - IRepresentationStreamPlaybackObservation - >; - content: { adaptation : Adaptation; - manifest : Manifest; - period : Period; - representation : Representation; }; - segmentData : T | null; - segment : ISegment; - segmentBuffer : SegmentBuffer; } -) : Observable< IStreamEventAddedSegment > { - return observableDefer(() => { - if (segmentData === null) { - return EMPTY; - } - const codec = content.representation.getMimeTypeString(); - const data : IPushedChunkData = { initSegment: segmentData, - chunk: null, - timestampOffset: 0, - appendWindow: [ undefined, undefined ], - codec }; - const canceller = new TaskCanceller(); - return fromCancellablePromise(canceller, () => - appendSegmentToBuffer(playbackObserver, - segmentBuffer, - { data, inventoryInfos: null }, - canceller.signal)) - .pipe(map(() => { - const buffered = segmentBuffer.getBufferedRanges(); - return EVENTS.addedSegment(content, segment, buffered, segmentData); - })); - }); -} diff --git a/src/core/stream/representation/push_media_segment.ts b/src/core/stream/representation/push_media_segment.ts deleted file mode 100644 index a3c6a4c83b..0000000000 --- a/src/core/stream/representation/push_media_segment.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - defer as observableDefer, - EMPTY, - map, - Observable, -} from "rxjs"; -import config from "../../../config"; -import Manifest, { - Adaptation, - ISegment, - Period, - Representation, -} from "../../../manifest"; -import { ISegmentParserParsedMediaChunk } from "../../../transports"; -import objectAssign from "../../../utils/object_assign"; -import fromCancellablePromise from "../../../utils/rx-from_cancellable_promise"; -import TaskCanceller from "../../../utils/task_canceller"; -import { IReadOnlyPlaybackObserver } from "../../api"; -import { SegmentBuffer } from "../../segment_buffers"; -import EVENTS from "../events_generators"; -import { IStreamEventAddedSegment } from "../types"; -import appendSegmentToBuffer from "./append_segment_to_buffer"; -import { IRepresentationStreamPlaybackObservation } from "./representation_stream"; - - -/** - * Push a given media segment (non-init segment) to a SegmentBuffer. - * The Observable returned: - * - emit an event once the segment has been pushed. - * - throws on Error. - * @param {Object} args - * @returns {Observable} - */ -export default function pushMediaSegment( - { playbackObserver, - content, - initSegmentData, - parsedSegment, - segment, - segmentBuffer } : - { playbackObserver : IReadOnlyPlaybackObserver< - IRepresentationStreamPlaybackObservation - >; - content: { adaptation : Adaptation; - manifest : Manifest; - period : Period; - representation : Representation; }; - initSegmentData : T | null; - parsedSegment : ISegmentParserParsedMediaChunk; - segment : ISegment; - segmentBuffer : SegmentBuffer; } -) : Observable< IStreamEventAddedSegment > { - return observableDefer(() => { - if (parsedSegment.chunkData === null) { - return EMPTY; - } - const { chunkData, - chunkInfos, - chunkOffset, - chunkSize, - appendWindow } = parsedSegment; - const codec = content.representation.getMimeTypeString(); - const { APPEND_WINDOW_SECURITIES } = config.getCurrent(); - // Cutting exactly at the start or end of the appendWindow can lead to - // cases of infinite rebuffering due to how browser handle such windows. - // To work-around that, we add a small offset before and after those. - const safeAppendWindow : [ number | undefined, number | undefined ] = [ - appendWindow[0] !== undefined ? - Math.max(0, appendWindow[0] - APPEND_WINDOW_SECURITIES.START) : - undefined, - appendWindow[1] !== undefined ? - appendWindow[1] + APPEND_WINDOW_SECURITIES.END : - undefined, - ]; - - const data = { initSegment: initSegmentData, - chunk: chunkData, - timestampOffset: chunkOffset, - appendWindow: safeAppendWindow, - codec }; - - let estimatedStart = chunkInfos?.time ?? segment.time; - const estimatedDuration = chunkInfos?.duration ?? segment.duration; - let estimatedEnd = estimatedStart + estimatedDuration; - if (safeAppendWindow[0] !== undefined) { - estimatedStart = Math.max(estimatedStart, safeAppendWindow[0]); - } - if (safeAppendWindow[1] !== undefined) { - estimatedEnd = Math.min(estimatedEnd, safeAppendWindow[1]); - } - - const inventoryInfos = objectAssign({ segment, - chunkSize, - start: estimatedStart, - end: estimatedEnd }, - content); - const canceller = new TaskCanceller(); - - return fromCancellablePromise(canceller, () => - appendSegmentToBuffer(playbackObserver, - segmentBuffer, - { data, inventoryInfos }, - canceller.signal)) - .pipe(map(() => { - const buffered = segmentBuffer.getBufferedRanges(); - return EVENTS.addedSegment(content, segment, buffered, chunkData); - })); - }); -} diff --git a/src/core/stream/representation/representation_stream.ts b/src/core/stream/representation/representation_stream.ts index 998e63569e..a508ca4ef8 100644 --- a/src/core/stream/representation/representation_stream.ts +++ b/src/core/stream/representation/representation_stream.ts @@ -23,94 +23,91 @@ * position and what is currently buffered. */ -import nextTick from "next-tick"; -import { - combineLatest as observableCombineLatest, - concat as observableConcat, - defer as observableDefer, - EMPTY, - ignoreElements, - merge as observableMerge, - mergeMap, - Observable, - of as observableOf, - share, - startWith, - Subject, - take, - takeWhile, - withLatestFrom, -} from "rxjs"; import config from "../../../config"; import log from "../../../log"; -import Manifest, { - Adaptation, - ISegment, - Period, - Representation, -} from "../../../manifest"; -import assertUnreachable from "../../../utils/assert_unreachable"; +import { ISegment } from "../../../manifest"; import objectAssign from "../../../utils/object_assign"; import { createSharedReference } from "../../../utils/reference"; -import fromCancellablePromise from "../../../utils/rx-from_cancellable_promise"; -import TaskCanceller from "../../../utils/task_canceller"; -import { IReadOnlyPlaybackObserver } from "../../api"; -import { IPrioritizedSegmentFetcher } from "../../fetchers"; -import { SegmentBuffer } from "../../segment_buffers"; -import EVENTS from "../events_generators"; +import TaskCanceller, { + CancellationError, + CancellationSignal, +} from "../../../utils/task_canceller"; import { - IEncryptionDataEncounteredEvent, IQueuedSegment, - IRepresentationStreamEvent, - IStreamEventAddedSegment, - IStreamManifestMightBeOutOfSync, - IStreamNeedsManifestRefresh, - IStreamStatusEvent, - IStreamTerminatingEvent, - IInbandEventsEvent, - IStreamWarningEvent, -} from "../types"; + IRepresentationStreamArguments, + IRepresentationStreamCallbacks, +} from "./types"; import DownloadingQueue, { - IDownloadingQueueEvent, IDownloadQueueItem, - IParsedInitSegmentEvent, - IParsedSegmentEvent, -} from "./downloading_queue"; -import getBufferStatus from "./get_buffer_status"; -import getSegmentPriority from "./get_segment_priority"; -import pushInitSegment from "./push_init_segment"; -import pushMediaSegment from "./push_media_segment"; + IParsedInitSegmentPayload, + IParsedSegmentPayload, +} from "./utils/downloading_queue"; +import getBufferStatus from "./utils/get_buffer_status"; +import getSegmentPriority from "./utils/get_segment_priority"; +import pushInitSegment from "./utils/push_init_segment"; +import pushMediaSegment from "./utils/push_media_segment"; /** - * Build up buffer for a single Representation. + * Perform the logic to load the right segments for the given Representation and + * push them to the given `SegmentBuffer`. * - * Download and push segments linked to the given Representation according - * to what is already in the SegmentBuffer and where the playback currently is. + * In essence, this is the entry point of the core streaming logic of the + * RxPlayer, the one actually responsible for finding which are the current + * right segments to load, loading them, and pushing them so they can be decoded. * - * Multiple RepresentationStream observables can run on the same SegmentBuffer. + * Multiple RepresentationStream can run on the same SegmentBuffer. * This allows for example smooth transitions between multiple periods. * - * @param {Object} args - * @returns {Observable} + * @param {Object} args - Various arguments allowing to know which segments to + * load, loading them and pushing them. + * You can check the corresponding type for more information. + * @param {Object} callbacks - The `RepresentationStream` relies on a system of + * callbacks that it will call on various events. + * + * Depending on the event, the caller may be supposed to perform actions to + * react upon some of them. + * + * This approach is taken instead of a more classical EventEmitter pattern to: + * - Allow callbacks to be called synchronously after the + * `RepresentationStream` is called. + * - Simplify bubbling events up, by just passing through callbacks + * - Force the caller to explicitely handle or not the different events. + * + * Callbacks may start being called immediately after the `RepresentationStream` + * call and may be called until either the `parentCancelSignal` argument is + * triggered, until the `terminating` callback has been triggered AND all loaded + * segments have been pushed, or until the `error` callback is called, whichever + * comes first. + * @param {Object} parentCancelSignal - `CancellationSignal` allowing, when + * triggered, to immediately stop all operations the `RepresentationStream` is + * doing. */ -export default function RepresentationStream({ - content, - options, - playbackObserver, - segmentBuffer, - segmentFetcher, - terminate$, -} : IRepresentationStreamArguments -) : Observable { - const { period, - adaptation, - representation } = content; - const { bufferGoal$, - maxBufferSize$, - drmSystemId, - fastSwitchThreshold$ } = options; +export default function RepresentationStream( + { content, + options, + playbackObserver, + segmentBuffer, + segmentFetcher, + terminate } : IRepresentationStreamArguments, + callbacks : IRepresentationStreamCallbacks, + parentCancelSignal : CancellationSignal +) : void { + const { period, adaptation, representation } = content; + const { bufferGoal, maxBufferSize, drmSystemId, fastSwitchThreshold } = options; const bufferType = adaptation.type; + /** `TaskCanceller` stopping ALL operations performed by the `RepresentationStream` */ + const globalCanceller = new TaskCanceller(); + globalCanceller.linkToSignal(parentCancelSignal); + + /** + * `TaskCanceller` allowing to only stop segment loading and checking operations. + * This allows to stop only tasks linked to network resource usage, which is + * often a limited resource, while still letting buffer operations to finish. + */ + const segmentsLoadingCanceller = new TaskCanceller(); + segmentsLoadingCanceller.linkToSignal(globalCanceller.signal); + /** Saved initialization segment state for this representation. */ const initSegmentState : IInitSegmentState = { segment: representation.index.getInitSegment(), @@ -118,21 +115,14 @@ export default function RepresentationStream({ isLoaded: false, }; - /** Allows to manually re-check which segments are needed. */ - const reCheckNeededSegments$ = new Subject(); - /** Emit the last scheduled downloading queue for segments. */ const lastSegmentQueue = createSharedReference({ initSegment: null, segmentQueue: [], - }); - const hasInitSegment = initSegmentState.segment !== null; + }, segmentsLoadingCanceller.signal); - /** Will load every segments in `lastSegmentQueue` */ - const downloadingQueue = new DownloadingQueue(content, - lastSegmentQueue, - segmentFetcher, - hasInitSegment); + /** If `true`, the current Representation has a linked initialization segment. */ + const hasInitSegment = initSegmentState.segment !== null; if (!hasInitSegment) { initSegmentState.segmentData = null; @@ -145,7 +135,6 @@ export default function RepresentationStream({ * Allows to avoid sending multiple times protection events. */ let hasSentEncryptionData = false; - let encryptionEvent$ : Observable = EMPTY; // If the DRM system id is already known, and if we already have encryption data // for it, we may not need to wait until the initialization segment is loaded to @@ -156,189 +145,181 @@ export default function RepresentationStream({ // If some key ids are not known yet, it may be safer to wait for this initialization // segment to be loaded first if (encryptionData.length > 0 && encryptionData.every(e => e.keyIds !== undefined)) { - encryptionEvent$ = observableOf(...encryptionData.map(d => - EVENTS.encryptionDataEncountered(d, content))); hasSentEncryptionData = true; + callbacks.encryptionDataEncountered( + encryptionData.map(d => objectAssign({ content }, d)) + ); + if (globalCanceller.isUsed()) { + return ; // previous callback has stopped everything by side-effect + } } } - /** Observable loading and pushing segments scheduled through `lastSegmentQueue`. */ - const queue$ = downloadingQueue.start() - .pipe(mergeMap(onQueueEvent)); - - /** Observable emitting the stream "status" and filling `lastSegmentQueue`. */ - const status$ = observableCombineLatest([ - playbackObserver.getReference().asObservable(), - bufferGoal$, - maxBufferSize$, - terminate$.pipe(take(1), - startWith(null)), - reCheckNeededSegments$.pipe(startWith(undefined)), - ]).pipe( - withLatestFrom(fastSwitchThreshold$), - mergeMap(function ( - [ [ observation, bufferGoal, maxBufferSize, terminate ], - fastSwitchThreshold ] - ) : Observable - { - const initialWantedTime = observation.position.pending ?? - observation.position.last; - const status = getBufferStatus(content, - initialWantedTime, - playbackObserver, - fastSwitchThreshold, - bufferGoal, - maxBufferSize, - segmentBuffer); - const { neededSegments } = status; + /** Will load every segments in `lastSegmentQueue` */ + const downloadingQueue = new DownloadingQueue(content, + lastSegmentQueue, + segmentFetcher, + hasInitSegment); + downloadingQueue.addEventListener("error", (err) => { + if (segmentsLoadingCanceller.signal.isCancelled()) { + return; // ignore post requests-cancellation loading-related errors, + } + globalCanceller.cancel(); // Stop every operations + callbacks.error(err); + }); + downloadingQueue.addEventListener("parsedInitSegment", onParsedChunk); + downloadingQueue.addEventListener("parsedMediaSegment", onParsedChunk); + downloadingQueue.addEventListener("emptyQueue", checkStatus); + downloadingQueue.addEventListener("requestRetry", (payload) => { + callbacks.warning(payload.error); + if (segmentsLoadingCanceller.signal.isCancelled()) { + return; // If the previous callback led to loading operations being stopped, skip + } + const retriedSegment = payload.segment; + const { index } = representation; + if (index.isSegmentStillAvailable(retriedSegment) === false) { + checkStatus(); + } else if (index.canBeOutOfSyncError(payload.error, retriedSegment)) { + callbacks.manifestMightBeOufOfSync(); + } + }); + downloadingQueue.addEventListener("fullyLoadedSegment", (segment) => { + segmentBuffer.endOfSegment(objectAssign({ segment }, content), globalCanceller.signal) + .catch(onFatalBufferError); + }); + downloadingQueue.start(); + segmentsLoadingCanceller.signal.register(() => { + downloadingQueue.removeEventListener(); + downloadingQueue.stop(); + }); - let neededInitSegment : IQueuedSegment | null = null; + playbackObserver.listen(checkStatus, { + includeLastObservation: false, + clearSignal: segmentsLoadingCanceller.signal, + }); + bufferGoal.onUpdate(checkStatus, { + emitCurrentValue: false, + clearSignal: segmentsLoadingCanceller.signal , + }); + maxBufferSize.onUpdate(checkStatus, { + emitCurrentValue: false, + clearSignal: segmentsLoadingCanceller.signal, + }); + terminate.onUpdate(checkStatus, { + emitCurrentValue: false, + clearSignal: segmentsLoadingCanceller.signal, + }); + checkStatus(); + return ; - // Add initialization segment if required - if (!representation.index.isInitialized()) { - if (initSegmentState.segment === null) { - log.warn("Stream: Uninitialized index without an initialization segment"); - } else if (initSegmentState.isLoaded) { - log.warn("Stream: Uninitialized index with an already loaded " + - "initialization segment"); - } else { - const wantedStart = observation.position.pending ?? + /** + * Produce a buffer status update synchronously on call, update the list + * of current segments to update and check various buffer and manifest related + * issues at the current time, calling the right callbacks if necessary. + */ + function checkStatus() : void { + if (segmentsLoadingCanceller.isUsed()) { + return ; // Stop all buffer status checking if load operations are stopped + } + const observation = playbackObserver.getReference().getValue(); + const initialWantedTime = observation.position.pending ?? observation.position.last; - neededInitSegment = { segment: initSegmentState.segment, - priority: getSegmentPriority(period.start, - wantedStart) }; - } - } else if (neededSegments.length > 0 && - !initSegmentState.isLoaded && - initSegmentState.segment !== null) - { - const initSegmentPriority = neededSegments[0].priority; + const status = getBufferStatus(content, + initialWantedTime, + playbackObserver, + fastSwitchThreshold.getValue(), + bufferGoal.getValue(), + maxBufferSize.getValue(), + segmentBuffer); + const { neededSegments } = status; + + let neededInitSegment : IQueuedSegment | null = null; + + // Add initialization segment if required + if (!representation.index.isInitialized()) { + if (initSegmentState.segment === null) { + log.warn("Stream: Uninitialized index without an initialization segment"); + } else if (initSegmentState.isLoaded) { + log.warn("Stream: Uninitialized index with an already loaded " + + "initialization segment"); + } else { + const wantedStart = observation.position.pending ?? + observation.position.last; neededInitSegment = { segment: initSegmentState.segment, - priority: initSegmentPriority }; + priority: getSegmentPriority(period.start, + wantedStart) }; } + } else if (neededSegments.length > 0 && + !initSegmentState.isLoaded && + initSegmentState.segment !== null) + { + const initSegmentPriority = neededSegments[0].priority; + neededInitSegment = { segment: initSegmentState.segment, + priority: initSegmentPriority }; + } - if (terminate === null) { - lastSegmentQueue.setValue({ initSegment: neededInitSegment, - segmentQueue: neededSegments }); - } else if (terminate.urgent) { - log.debug("Stream: Urgent switch, terminate now.", bufferType); - lastSegmentQueue.setValue({ initSegment: null, segmentQueue: [] }); + const terminateVal = terminate.getValue(); + if (terminateVal === null) { + lastSegmentQueue.setValue({ initSegment: neededInitSegment, + segmentQueue: neededSegments }); + } else if (terminateVal.urgent) { + log.debug("Stream: Urgent switch, terminate now.", bufferType); + lastSegmentQueue.setValue({ initSegment: null, segmentQueue: [] }); + lastSegmentQueue.finish(); + segmentsLoadingCanceller.cancel(); + callbacks.terminating(); + return; + } else { + // Non-urgent termination wanted: + // End the download of the current media segment if pending and + // terminate once either that request is finished or another segment + // is wanted instead, whichever comes first. + + const mostNeededSegment = neededSegments[0]; + const initSegmentRequest = downloadingQueue.getRequestedInitSegment(); + const currentSegmentRequest = downloadingQueue.getRequestedMediaSegment(); + + const nextQueue = currentSegmentRequest === null || + mostNeededSegment === undefined || + currentSegmentRequest.id !== mostNeededSegment.segment.id ? + [] : + [mostNeededSegment]; + + const nextInit = initSegmentRequest === null ? null : + neededInitSegment; + lastSegmentQueue.setValue({ initSegment: nextInit, + segmentQueue: nextQueue }); + if (nextQueue.length === 0 && nextInit === null) { + log.debug("Stream: No request left, terminate", bufferType); lastSegmentQueue.finish(); - return observableOf(EVENTS.streamTerminating()); - } else { - // Non-urgent termination wanted: - // End the download of the current media segment if pending and - // terminate once either that request is finished or another segment - // is wanted instead, whichever comes first. - - const mostNeededSegment = neededSegments[0]; - const initSegmentRequest = downloadingQueue.getRequestedInitSegment(); - const currentSegmentRequest = downloadingQueue.getRequestedMediaSegment(); - - const nextQueue = currentSegmentRequest === null || - mostNeededSegment === undefined || - currentSegmentRequest.id !== mostNeededSegment.segment.id ? - [] : - [mostNeededSegment]; - - const nextInit = initSegmentRequest === null ? null : - neededInitSegment; - lastSegmentQueue.setValue({ initSegment: nextInit, - segmentQueue: nextQueue }); - if (nextQueue.length === 0 && nextInit === null) { - log.debug("Stream: No request left, terminate", bufferType); - lastSegmentQueue.finish(); - return observableOf(EVENTS.streamTerminating()); - } - } - - const bufferStatusEvt : Observable = - observableOf({ type: "stream-status" as const, - value: { period, - position: observation.position.last, - bufferType, - imminentDiscontinuity: status.imminentDiscontinuity, - hasFinishedLoading: status.hasFinishedLoading, - neededSegments: status.neededSegments } }); - let bufferRemoval = EMPTY; - const { UPTO_CURRENT_POSITION_CLEANUP } = config.getCurrent(); - if (status.isBufferFull) { - const gcedPosition = Math.max( - 0, - initialWantedTime - UPTO_CURRENT_POSITION_CLEANUP); - if (gcedPosition > 0) { - const removalCanceller = new TaskCanceller(); - bufferRemoval = fromCancellablePromise(removalCanceller, () => - segmentBuffer.removeBuffer(0, gcedPosition, removalCanceller.signal) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - ).pipe(ignoreElements()); - } + segmentsLoadingCanceller.cancel(); + callbacks.terminating(); + return; } - return status.shouldRefreshManifest ? - observableConcat(observableOf(EVENTS.needsManifestRefresh()), - bufferStatusEvt, bufferRemoval) : - observableConcat(bufferStatusEvt, bufferRemoval); - }), - takeWhile((e) => e.type !== "stream-terminating", true) - ); - - return observableMerge(status$, queue$, encryptionEvent$).pipe(share()); - - /** - * React to event from the `DownloadingQueue`. - * @param {Object} evt - * @returns {Observable} - */ - function onQueueEvent( - evt : IDownloadingQueueEvent - ) : Observable | - IStreamWarningEvent | - IEncryptionDataEncounteredEvent | - IInbandEventsEvent | - IStreamNeedsManifestRefresh | - IStreamManifestMightBeOutOfSync> - { - switch (evt.type) { - case "retry": - return observableConcat( - observableOf({ type: "warning" as const, value: evt.value.error }), - observableDefer(() => { // better if done after warning is emitted - const retriedSegment = evt.value.segment; - const { index } = representation; - if (index.isSegmentStillAvailable(retriedSegment) === false) { - reCheckNeededSegments$.next(); - } else if (index.canBeOutOfSyncError(evt.value.error, retriedSegment)) { - return observableOf(EVENTS.manifestMightBeOufOfSync()); - } - return EMPTY; // else, ignore. - })); - - case "parsed-init": - case "parsed-media": - return onParsedChunk(evt); + } - case "end-of-segment": { - const { segment } = evt.value; - const endOfSegmentCanceller = new TaskCanceller(); - return fromCancellablePromise(endOfSegmentCanceller, () => - segmentBuffer.endOfSegment(objectAssign({ segment }, content), - endOfSegmentCanceller.signal)) - // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default - // first type parameter as `any` instead of the perfectly fine `unknown`, - // leading to linter issues, as it forbids the usage of `any`. - // This is why we're disabling the eslint rule. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - .pipe(ignoreElements()); + callbacks.streamStatusUpdate({ period, + position: observation.position.last, + bufferType, + imminentDiscontinuity: status.imminentDiscontinuity, + isEmptyStream: false, + hasFinishedLoading: status.hasFinishedLoading, + neededSegments: status.neededSegments }); + if (segmentsLoadingCanceller.signal.isCancelled()) { + return ; // previous callback has stopped loading operations by side-effect + } + const { UPTO_CURRENT_POSITION_CLEANUP } = config.getCurrent(); + if (status.isBufferFull) { + const gcedPosition = Math.max( + 0, + initialWantedTime - UPTO_CURRENT_POSITION_CLEANUP); + if (gcedPosition > 0) { + segmentBuffer.removeBuffer(0, gcedPosition, globalCanceller.signal) + .catch(onFatalBufferError); } - - case "end-of-queue": - reCheckNeededSegments$.next(); - return EMPTY; - - default: - assertUnreachable(evt); + } + if (status.shouldRefreshManifest) { + callbacks.needsManifestRefresh(); } } @@ -346,39 +327,48 @@ export default function RepresentationStream({ * Process a chunk that has just been parsed by pushing it to the * SegmentBuffer and emitting the right events. * @param {Object} evt - * @returns {Observable} */ function onParsedChunk( - evt : IParsedInitSegmentEvent | - IParsedSegmentEvent - ) : Observable | - IEncryptionDataEncounteredEvent | - IInbandEventsEvent | - IStreamNeedsManifestRefresh | - IStreamManifestMightBeOutOfSync> - { + evt : IParsedInitSegmentPayload | + IParsedSegmentPayload + ) : void { + if (globalCanceller.isUsed()) { + // We should not do anything with segments if the `RepresentationStream` + // is not running anymore. + return ; + } if (evt.segmentType === "init") { - nextTick(() => { - reCheckNeededSegments$.next(); - }); initSegmentState.segmentData = evt.initializationData; initSegmentState.isLoaded = true; // Now that the initialization segment has been parsed - which may have // included encryption information - take care of the encryption event // if not already done. - const allEncryptionData = representation.getAllEncryptionData(); - const initEncEvt$ = !hasSentEncryptionData && - allEncryptionData.length > 0 ? - observableOf(...allEncryptionData.map(p => - EVENTS.encryptionDataEncountered(p, content))) : - EMPTY; - const pushEvent$ = pushInitSegment({ playbackObserver, - content, - segment: evt.segment, - segmentData: evt.initializationData, - segmentBuffer }); - return observableMerge(initEncEvt$, pushEvent$); + if (!hasSentEncryptionData) { + const allEncryptionData = representation.getAllEncryptionData(); + if (allEncryptionData.length > 0) { + callbacks.encryptionDataEncountered( + allEncryptionData.map(p => objectAssign({ content }, p)) + ); + } + } + + pushInitSegment({ playbackObserver, + content, + segment: evt.segment, + segmentData: evt.initializationData, + segmentBuffer }, + globalCanceller.signal) + .then((result) => { + if (result !== null) { + callbacks.addedSegment(result); + } + }) + .catch(onFatalBufferError); + + // Sometimes the segment list is only known once the initialization segment + // is parsed. Thus we immediately re-check if there's new segments to load. + checkStatus(); } else { const { inbandEvents, needsManifestRefresh, @@ -386,154 +376,64 @@ export default function RepresentationStream({ // TODO better handle use cases like key rotation by not always grouping // every protection data together? To check. - const segmentEncryptionEvent$ = protectionDataUpdate && - !hasSentEncryptionData ? - observableOf(...representation.getAllEncryptionData().map(p => - EVENTS.encryptionDataEncountered(p, content))) : - EMPTY; + if (!hasSentEncryptionData && protectionDataUpdate) { + const allEncryptionData = representation.getAllEncryptionData(); + if (allEncryptionData.length > 0) { + callbacks.encryptionDataEncountered( + allEncryptionData.map(p => objectAssign({ content }, p)) + ); + if (globalCanceller.isUsed()) { + return ; // previous callback has stopped everything by side-effect + } + } + } - const manifestRefresh$ = needsManifestRefresh === true ? - observableOf(EVENTS.needsManifestRefresh()) : - EMPTY; - const inbandEvents$ = inbandEvents !== undefined && - inbandEvents.length > 0 ? - observableOf({ type: "inband-events" as const, - value: inbandEvents }) : - EMPTY; + if (needsManifestRefresh === true) { + callbacks.needsManifestRefresh(); + if (globalCanceller.isUsed()) { + return ; // previous callback has stopped everything by side-effect + } + } + if (inbandEvents !== undefined && inbandEvents.length > 0) { + callbacks.inbandEvent(inbandEvents); + if (globalCanceller.isUsed()) { + return ; // previous callback has stopped everything by side-effect + } + } const initSegmentData = initSegmentState.segmentData; - const pushMediaSegment$ = pushMediaSegment({ playbackObserver, - content, - initSegmentData, - parsedSegment: evt, - segment: evt.segment, - segmentBuffer }); - return observableConcat(segmentEncryptionEvent$, - manifestRefresh$, - inbandEvents$, - pushMediaSegment$); + pushMediaSegment({ playbackObserver, + content, + initSegmentData, + parsedSegment: evt, + segment: evt.segment, + segmentBuffer }, + globalCanceller.signal) + .then((result) => { + if (result !== null) { + callbacks.addedSegment(result); + } + }) + .catch(onFatalBufferError); } } -} - -/** Object that should be emitted by the given `IReadOnlyPlaybackObserver`. */ -export interface IRepresentationStreamPlaybackObservation { - /** - * Information on the current media position in seconds at the time of a - * Playback Observation. - */ - position : IPositionPlaybackObservation; -} -/** Position-related information linked to an emitted Playback observation. */ -export interface IPositionPlaybackObservation { /** - * Known position at the time the Observation was emitted, in seconds. - * - * Note that it might have changed since. If you want truly precize - * information, you should recuperate it from the HTMLMediaElement directly - * through another mean. + * Handle Buffer-related fatal errors by cancelling everything the + * `RepresentationStream` is doing and calling the error callback with the + * corresponding error. + * @param {*} err */ - last : number; - /** - * Actually wanted position in seconds that is not yet reached. - * - * This might for example be set to the initial position when the content is - * loading (and thus potentially at a `0` position) but which will be seeked - * to a given position once possible. - */ - pending : number | undefined; -} - -/** Item emitted by the `terminate$` Observable given to a RepresentationStream. */ -export interface ITerminationOrder { - /* - * If `true`, the RepresentationStream should interrupt immediately every long - * pending operations such as segment downloads. - * If it is set to `false`, it can continue until those operations are - * finished. - */ - urgent : boolean; -} - -/** Arguments to give to the RepresentationStream. */ -export interface IRepresentationStreamArguments { - /** The context of the Representation you want to load. */ - content: { adaptation : Adaptation; - manifest : Manifest; - period : Period; - representation : Representation; }; - /** The `SegmentBuffer` on which segments will be pushed. */ - segmentBuffer : SegmentBuffer; - /** Interface used to load new segments. */ - segmentFetcher : IPrioritizedSegmentFetcher; - /** - * Observable emitting when the RepresentationStream should "terminate". - * - * When this Observable emits, the RepresentationStream will begin a - * "termination process": it will, depending on the type of termination - * wanted, either stop immediately pending segment requests or wait until they - * are finished before fully terminating (sending the - * `IStreamTerminatingEvent` and then completing the `RepresentationStream` - * Observable once the corresponding segments have been pushed). - */ - terminate$ : Observable; - /** Periodically emits the current playback conditions. */ - playbackObserver : IReadOnlyPlaybackObserver; - /** Supplementary arguments which configure the RepresentationStream's behavior. */ - options: IRepresentationStreamOptions; -} - - -/** - * Various specific stream "options" which tweak the behavior of the - * RepresentationStream. - */ -export interface IRepresentationStreamOptions { - /** - * The buffer size we have to reach in seconds (compared to the current - * position. When that size is reached, no segments will be loaded until it - * goes below that size again. - */ - bufferGoal$ : Observable; - - /** - * The buffer size limit in memory that we can reach. - * Once reached, no segments will be loaded until it - * goes below that size again - */ - maxBufferSize$ : Observable; - - /** - * Hex-encoded DRM "system ID" as found in: - * https://dashif.org/identifiers/content_protection/ - * - * Allows to identify which DRM system is currently used, to allow potential - * optimizations. - * - * Set to `undefined` in two cases: - * - no DRM system is used (e.g. the content is unencrypted). - * - We don't know which DRM system is currently used. - */ - drmSystemId : string | undefined; - /** - * Bitrate threshold from which no "fast-switching" should occur on a segment. - * - * Fast-switching is an optimization allowing to replace segments from a - * low-bitrate Representation by segments from a higher-bitrate - * Representation. This allows the user to see/hear an improvement in quality - * faster, hence "fast-switching". - * - * This Observable allows to limit this behavior to only allow the replacement - * of segments with a bitrate lower than a specific value - the number emitted - * by that Observable. - * - * If set to `undefined`, no threshold is active and any segment can be - * replaced by higher quality segment(s). - * - * `0` can be emitted to disable any kind of fast-switching. - */ - fastSwitchThreshold$: Observable< undefined | number>; + function onFatalBufferError(err : unknown) : void { + if (globalCanceller.isUsed() && err instanceof CancellationError) { + // The error is linked to cancellation AND we explicitely cancelled buffer + // operations. + // We can thus ignore it, it is very unlikely to lead to true buffer issues. + return; + } + globalCanceller.cancel(); + callbacks.error(err); + } } /** diff --git a/src/core/stream/representation/types.ts b/src/core/stream/representation/types.ts new file mode 100644 index 0000000000..817f013ceb --- /dev/null +++ b/src/core/stream/representation/types.ts @@ -0,0 +1,292 @@ +import Manifest, { + Adaptation, + ISegment, + Period, + Representation, +} from "../../../manifest"; +import { IEMSG } from "../../../parsers/containers/isobmff"; +import { IPlayerError } from "../../../public_types"; +import { IReadOnlySharedReference } from "../../../utils/reference"; +import { IReadOnlyPlaybackObserver } from "../../api"; +import { IContentProtection } from "../../decrypt"; +import { IPrioritizedSegmentFetcher } from "../../fetchers"; +import { + IBufferType, + SegmentBuffer, +} from "../../segment_buffers"; + +/** Callbacks called by the `RepresentationStream` on various events. */ +export interface IRepresentationStreamCallbacks { + /** + * Called to announce the current status regarding the buffer for its + * associated Period and type (e.g. "audio", "video", "text" etc.). + * + * Each new `IStreamStatusPayload` event replace the precedent one for the + * same Period and type. + */ + streamStatusUpdate(payload : IStreamStatusPayload) : void; + /** Called after a new segment has been succesfully added to the SegmentBuffer */ + addedSegment(payload : IStreamEventAddedSegmentPayload) : void; + /** Called when a segment with protection information has been encountered. */ + encryptionDataEncountered(payload : IContentProtection[]) : void; + /** + * Called when the Manifest is possibly out-of-sync and needs to be refreshed + * completely. + * + * The Stream made that guess because a segment that should have been available + * is not and because it suspects this is due to a synchronization problem. + */ + manifestMightBeOufOfSync() : void; + /** + * Callback called when a `RepresentationStream` is being terminated: + * + * - it has finished all its segment requests and won't do new ones. + * + * - it has stopped regularly checking for its current status. + * + * - it only waits until all the segments it has loaded have been pushed to the + * SegmentBuffer before actually stopping everything it does. + * + * You can use this call as a hint that a new `RepresentationStream` can be + * created for the same `Period` and type (e.g. to switch quality). + */ + terminating() : void; + /** + * Called when the Manifest needs to be refreshed. + * Note that segment might still be loaded and pushed even after calling + * this callback. + */ + needsManifestRefresh() : void; + /** + * Called when an "inband" event, as found in a media segment, has been + * encountered. + */ + inbandEvent(payload : IInbandEvent[]) : void; + /** + * Called when a minor error has been encountered, that does not interrupt + * the segment loading and pushing operations. + */ + warning(payload : IPlayerError) : void; + /** + * Called when a fatal error has been encountered. + * Such errors have led to all the Stream's operations to be stopped. + */ + error(payload : unknown) : void; +} + +/** Payload for the `streamStatusUpdate` callback. */ +export interface IStreamStatusPayload { + /** Period concerned. */ + period : Period; + /** Buffer type concerned. */ + bufferType : IBufferType; + /** + * Present or future "hole" in the SegmentBuffer's buffer that will not be + * filled by a segment, despite being part of the time period indicated by + * the associated Period. + * + * This value is set to the most imminent of such "discontinuity", which + * can be either: + * + * - current (no segment available at `position` but future segments are + * available), in which case this discontinuity's true beginning might + * be unknown. + * + * - a future hole between two segments in that Period. + * + * - missing media data at the end of the time period associated to that + * Period. + * + * The presence or absence of a discontinuity can evolve during playback + * (because new tracks or qualities might not have the same ones). + * As such, it is advised to only consider the last discontinuity sent + * through a `"stream-status"` event. + */ + imminentDiscontinuity : IBufferDiscontinuity | null; + /** + * If `true`, no segment are left to be loaded to be able to play until the + * end of the Period. + */ + hasFinishedLoading : boolean; + /** + * If `true`, this stream is a placeholder stream which will never load any + * segment. + */ + isEmptyStream : boolean; + /** + * Segments that will be scheduled for download to fill the buffer until + * the buffer goal (first element of that list might already be loading). + */ + neededSegments : IQueuedSegment[]; + /** Position in the content in seconds from which this status was done. */ + position : number; +} + +/** Payload for the `addedSegment` callback. */ +export interface IStreamEventAddedSegmentPayload { + /** Context about the content that has been added. */ + content: { period : Period; + adaptation : Adaptation; + representation : Representation; }; + /** The concerned Segment. */ + segment : ISegment; + /** TimeRanges of the concerned SegmentBuffer after the segment was pushed. */ + buffered : TimeRanges; + /* The data pushed */ + segmentData : T; +} + +/** Structure describing an "inband" event, as found in a media segment. */ +export interface IInbandEvent { + /** Type when the event was foud inside a "emsg` ISOBMFF box */ + type: "emsg"; + /** Value when the event was foud inside a "emsg` ISOBMFF box */ + value: IEMSG; +} + +/** Information about a Segment waiting to be loaded by the Stream. */ +export interface IQueuedSegment { + /** Priority of the segment request (lower number = higher priority). */ + priority : number; + /** Segment wanted. */ + segment : ISegment; +} + +/** Describe an encountered hole in the buffer, called a "discontinuity". */ +export interface IBufferDiscontinuity { + /** + * Start time, in seconds, at which the discontinuity starts. + * + * if set to `undefined`, its true start time is unknown but the current + * position is part of it. It is thus a discontinuity that is currently + * encountered. + */ + start : number | undefined; + /** + * End time, in seconds at which the discontinuity ends (and thus where + * new segments are encountered). + * + * If `null`, no new media segment is available for that Period and + * buffer type until the end of the Period. + */ + end : number | null; +} + +/** Object that should be emitted by the given `IReadOnlyPlaybackObserver`. */ +export interface IRepresentationStreamPlaybackObservation { + /** + * Information on the current media position in seconds at the time of a + * Playback Observation. + */ + position : IPositionPlaybackObservation; +} + +/** Position-related information linked to an emitted Playback observation. */ +export interface IPositionPlaybackObservation { + /** + * Known position at the time the Observation was emitted, in seconds. + * + * Note that it might have changed since. If you want truly precize + * information, you should recuperate it from the HTMLMediaElement directly + * through another mean. + */ + last : number; + /** + * Actually wanted position in seconds that is not yet reached. + * + * This might for example be set to the initial position when the content is + * loading (and thus potentially at a `0` position) but which will be seeked + * to a given position once possible. + */ + pending : number | undefined; +} + +/** Item emitted by the `terminate` reference given to a RepresentationStream. */ +export interface ITerminationOrder { + /* + * If `true`, the RepresentationStream should interrupt immediately every long + * pending operations such as segment downloads. + * If it is set to `false`, it can continue until those operations are + * finished. + */ + urgent : boolean; +} + +/** Arguments to give to the RepresentationStream. */ +export interface IRepresentationStreamArguments { + /** The context of the Representation you want to load. */ + content: { adaptation : Adaptation; + manifest : Manifest; + period : Period; + representation : Representation; }; + /** The `SegmentBuffer` on which segments will be pushed. */ + segmentBuffer : SegmentBuffer; + /** Interface used to load new segments. */ + segmentFetcher : IPrioritizedSegmentFetcher; + /** + * Reference emitting when the RepresentationStream should "terminate". + * + * When this Reference emits an object, the RepresentationStream will begin a + * "termination process": it will, depending on the type of termination + * wanted, either stop immediately pending segment requests or wait until they + * are finished before fully terminating (calling the `terminating` callback + * and stopping all `RepresentationStream` current tasks). + */ + terminate : IReadOnlySharedReference; + /** Periodically emits the current playback conditions. */ + playbackObserver : IReadOnlyPlaybackObserver; + /** Supplementary arguments which configure the RepresentationStream's behavior. */ + options: IRepresentationStreamOptions; +} + + +/** + * Various specific stream "options" which tweak the behavior of the + * RepresentationStream. + */ +export interface IRepresentationStreamOptions { + /** + * The buffer size we have to reach in seconds (compared to the current + * position. When that size is reached, no segments will be loaded until it + * goes below that size again. + */ + bufferGoal : IReadOnlySharedReference; + + /** + * The buffer size limit in memory that we can reach. + * Once reached, no segments will be loaded until it + * goes below that size again + */ + maxBufferSize : IReadOnlySharedReference; + + /** + * Hex-encoded DRM "system ID" as found in: + * https://dashif.org/identifiers/content_protection/ + * + * Allows to identify which DRM system is currently used, to allow potential + * optimizations. + * + * Set to `undefined` in two cases: + * - no DRM system is used (e.g. the content is unencrypted). + * - We don't know which DRM system is currently used. + */ + drmSystemId : string | undefined; + /** + * Bitrate threshold from which no "fast-switching" should occur on a segment. + * + * Fast-switching is an optimization allowing to replace segments from a + * low-bitrate Representation by segments from a higher-bitrate + * Representation. This allows the user to see/hear an improvement in quality + * faster, hence "fast-switching". + * + * This Reference allows to limit this behavior to only allow the replacement + * of segments with a bitrate lower than a specific value - the number emitted + * by that Reference. + * + * If set to `undefined`, no threshold is active and any segment can be + * replaced by higher quality segment(s). + * + * `0` can be emitted to disable any kind of fast-switching. + */ + fastSwitchThreshold: IReadOnlySharedReference< undefined | number>; +} diff --git a/src/core/stream/representation/append_segment_to_buffer.ts b/src/core/stream/representation/utils/append_segment_to_buffer.ts similarity index 79% rename from src/core/stream/representation/append_segment_to_buffer.ts rename to src/core/stream/representation/utils/append_segment_to_buffer.ts index 10b2d7b9d8..ef29535890 100644 --- a/src/core/stream/representation/append_segment_to_buffer.ts +++ b/src/core/stream/representation/utils/append_segment_to_buffer.ts @@ -18,21 +18,21 @@ * This file allows any Stream to push data to a SegmentBuffer. */ -import { MediaError } from "../../../errors"; -import { CancellationSignal } from "../../../utils/task_canceller"; -import { IReadOnlyPlaybackObserver } from "../../api"; +import { MediaError } from "../../../../errors"; +import { CancellationError, CancellationSignal } from "../../../../utils/task_canceller"; +import { IReadOnlyPlaybackObserver } from "../../../api"; import { IPushChunkInfos, SegmentBuffer, -} from "../../segment_buffers"; +} from "../../../segment_buffers"; +import { IRepresentationStreamPlaybackObservation } from "../types"; import forceGarbageCollection from "./force_garbage_collection"; -import { IRepresentationStreamPlaybackObservation } from "./representation_stream"; /** * Append a segment to the given segmentBuffer. * If it leads to a QuotaExceededError, try to run our custom range * _garbage collector_ then retry. - * @param {Observable} playbackObserver + * @param {Object} playbackObserver * @param {Object} segmentBuffer * @param {Object} dataInfos * @param {Object} cancellationSignal @@ -47,7 +47,11 @@ export default async function appendSegmentToBuffer( try { await segmentBuffer.pushChunk(dataInfos, cancellationSignal); } catch (appendError : unknown) { - if (!(appendError instanceof Error) || appendError.name !== "QuotaExceededError") { + if (cancellationSignal.isCancelled() && appendError instanceof CancellationError) { + throw appendError; + } else if (!(appendError instanceof Error) || + appendError.name !== "QuotaExceededError") + { const reason = appendError instanceof Error ? appendError.toString() : "An unknown error happened when pushing content"; diff --git a/src/core/stream/representation/check_for_discontinuity.ts b/src/core/stream/representation/utils/check_for_discontinuity.ts similarity index 98% rename from src/core/stream/representation/check_for_discontinuity.ts rename to src/core/stream/representation/utils/check_for_discontinuity.ts index 2e6a9f78ec..2a1c816341 100644 --- a/src/core/stream/representation/check_for_discontinuity.ts +++ b/src/core/stream/representation/utils/check_for_discontinuity.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import log from "../../../log"; +import log from "../../../../log"; import Manifest, { Adaptation, Period, Representation, -} from "../../../manifest"; -import { IBufferedChunk } from "../../segment_buffers"; +} from "../../../../manifest"; +import { IBufferedChunk } from "../../../segment_buffers"; import { IBufferDiscontinuity } from "../types"; /** diff --git a/src/core/stream/representation/utils/downloading_queue.ts b/src/core/stream/representation/utils/downloading_queue.ts new file mode 100644 index 0000000000..e1c7f2af0b --- /dev/null +++ b/src/core/stream/representation/utils/downloading_queue.ts @@ -0,0 +1,604 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import log from "../../../../log"; +import Manifest, { + Adaptation, + ISegment, + Period, + Representation, +} from "../../../../manifest"; +import { IPlayerError } from "../../../../public_types"; +import { + ISegmentParserParsedInitChunk, + ISegmentParserParsedMediaChunk, +} from "../../../../transports"; +import assert from "../../../../utils/assert"; +import EventEmitter from "../../../../utils/event_emitter"; +import noop from "../../../../utils/noop"; +import objectAssign from "../../../../utils/object_assign"; +import createSharedReference, { + IReadOnlySharedReference, + ISharedReference, +} from "../../../../utils/reference"; +import TaskCanceller from "../../../../utils/task_canceller"; +import { IPrioritizedSegmentFetcher } from "../../../fetchers"; +import { IQueuedSegment } from "../types"; + +/** + * Class scheduling segment downloads for a single Representation. + * + * TODO The request scheduling abstractions might be simplified by integrating + * the `DownloadingQueue` in the segment fetchers code, instead of having it as + * an utilis of the `RepresentationStream` like here. + * @class DownloadingQueue + */ +export default class DownloadingQueue + extends EventEmitter> +{ + /** Context of the Representation that will be loaded through this DownloadingQueue. */ + private _content : IDownloadingQueueContext; + /** + * Current queue of segments scheduled for download. + * + * Segments whose request are still pending are still in that queue. Segments + * are only removed from it once their request has succeeded. + */ + private _downloadQueue : IReadOnlySharedReference; + /** + * Allows to stop listening to queue updates and stop performing requests. + * Set to `null` if the DownloadingQueue is not started right now. + */ + private _currentCanceller : TaskCanceller | null; + /** + * Pending request for the initialization segment. + * `null` if no request is pending for it. + */ + private _initSegmentRequest : ISegmentRequestObject|null; + /** + * Pending request for a media (i.e. non-initialization) segment. + * `null` if no request is pending for it. + */ + private _mediaSegmentRequest : ISegmentRequestObject|null; + /** Interface used to load segments. */ + private _segmentFetcher : IPrioritizedSegmentFetcher; + /** + * Emit the timescale anounced in the initialization segment once parsed. + * `undefined` when this is not yet known. + * `null` when no initialization segment or timescale exists. + */ + private _initSegmentInfoRef : ISharedReference; + /** + * Some media segment might have been loaded and are only awaiting for the + * initialization segment to be parsed before being parsed themselves. + * This string will contain the `id` property of that segment if one exist or + * `null` if no segment is awaiting an init segment. + */ + private _mediaSegmentAwaitingInitMetadata : string | null; + + /** + * Create a new `DownloadingQueue`. + * + * @param {Object} content - The context of the Representation you want to + * load segments for. + * @param {Object} downloadQueue - Queue of segments you want to load. + * @param {Object} segmentFetcher - Interface to facilitate the download of + * segments. + * @param {boolean} hasInitSegment - Declare that an initialization segment + * will need to be downloaded. + * + * A `DownloadingQueue` ALWAYS wait for the initialization segment to be + * loaded and parsed before parsing a media segment. + * + * In cases where no initialization segment exist, this would lead to the + * `DownloadingQueue` waiting indefinitely for it. + * + * By setting that value to `false`, you anounce to the `DownloadingQueue` + * that it should not wait for an initialization segment before parsing a + * media segment. + */ + constructor( + content: IDownloadingQueueContext, + downloadQueue : IReadOnlySharedReference, + segmentFetcher : IPrioritizedSegmentFetcher, + hasInitSegment : boolean + ) { + super(); + this._content = content; + this._currentCanceller = null; + this._downloadQueue = downloadQueue; + this._initSegmentRequest = null; + this._mediaSegmentRequest = null; + this._segmentFetcher = segmentFetcher; + this._initSegmentInfoRef = createSharedReference(undefined); + this._mediaSegmentAwaitingInitMetadata = null; + if (!hasInitSegment) { + this._initSegmentInfoRef.setValue(null); + } + } + + /** + * Returns the initialization segment currently being requested. + * Returns `null` if no initialization segment request is pending. + * @returns {Object | null} + */ + public getRequestedInitSegment() : ISegment | null { + return this._initSegmentRequest === null ? null : + this._initSegmentRequest.segment; + } + + /** + * Returns the media segment currently being requested. + * Returns `null` if no media segment request is pending. + * @returns {Object | null} + */ + public getRequestedMediaSegment() : ISegment | null { + return this._mediaSegmentRequest === null ? null : + this._mediaSegmentRequest.segment; + } + + /** + * Start the current downloading queue, emitting events as it loads and parses + * initialization and media segments. + */ + public start() : void { + if (this._currentCanceller !== null) { + return ; + } + this._currentCanceller = new TaskCanceller(); + + // Listen for asked media segments + this._downloadQueue.onUpdate((queue) => { + const { segmentQueue } = queue; + + if (segmentQueue.length > 0 && + segmentQueue[0].segment.id === this._mediaSegmentAwaitingInitMetadata) + { + // The most needed segment is still the same one, and there's no need to + // update its priority as the request already ended, just quit. + return; + } + + const currentSegmentRequest = this._mediaSegmentRequest; + if (segmentQueue.length === 0) { + if (currentSegmentRequest === null) { + // There's nothing to load but there's already no request pending. + return; + } + log.debug("Stream: no more media segment to request. Cancelling queue.", + this._content.adaptation.type); + this._restartMediaSegmentDownloadingQueue(); + return; + } else if (currentSegmentRequest === null) { + // There's no request although there are needed segments: start requests + log.debug("Stream: Media segments now need to be requested. Starting queue.", + this._content.adaptation.type, segmentQueue.length); + this._restartMediaSegmentDownloadingQueue(); + return; + } else { + const nextItem = segmentQueue[0]; + if (currentSegmentRequest.segment.id !== nextItem.segment.id) { + // The most important request if for another segment, request it + log.debug("Stream: Next media segment changed, cancelling previous", + this._content.adaptation.type); + this._restartMediaSegmentDownloadingQueue(); + return; + } + if (currentSegmentRequest.priority !== nextItem.priority) { + // The priority of the most important request has changed, update it + log.debug("Stream: Priority of next media segment changed, updating", + this._content.adaptation.type, + currentSegmentRequest.priority, + nextItem.priority); + this._segmentFetcher.updatePriority(currentSegmentRequest.request, + nextItem.priority); + } + return; + } + }, { emitCurrentValue: true, clearSignal: this._currentCanceller.signal }); + + // Listen for asked init segment + this._downloadQueue.onUpdate((next) => { + const initSegmentRequest = this._initSegmentRequest; + if (next.initSegment !== null && initSegmentRequest !== null) { + if (next.initSegment.priority !== initSegmentRequest.priority) { + this._segmentFetcher.updatePriority(initSegmentRequest.request, + next.initSegment.priority); + } + return; + } else if (next.initSegment?.segment.id === initSegmentRequest?.segment.id) { + return ; + } + if (next.initSegment === null) { + log.debug("Stream: no more init segment to request. Cancelling queue.", + this._content.adaptation.type); + } + this._restartInitSegmentDownloadingQueue(next.initSegment); + }, { emitCurrentValue: true, clearSignal: this._currentCanceller.signal }); + } + + public stop() { + this._currentCanceller?.cancel(); + this._currentCanceller = null; + } + + /** + * Internal logic performing media segment requests. + */ + private _restartMediaSegmentDownloadingQueue() : void { + if (this._mediaSegmentRequest !== null) { + this._mediaSegmentRequest.canceller.cancel(); + } + const { segmentQueue } = this._downloadQueue.getValue(); + const currentNeededSegment = segmentQueue[0]; + const recursivelyRequestSegments = ( + startingSegment : IQueuedSegment | undefined + ) : void => { + if (this._currentCanceller !== null && this._currentCanceller.isUsed()) { + this._mediaSegmentRequest = null; + return; + } + if (startingSegment === undefined) { + this._mediaSegmentRequest = null; + this.trigger("emptyQueue", null); + return; + } + const canceller = new TaskCanceller(); + const unlinkCanceller = this._currentCanceller === null ? + noop : + canceller.linkToSignal(this._currentCanceller.signal); + + const { segment, priority } = startingSegment; + const context = objectAssign({ segment }, this._content); + + /** + * If `true` , the current task has either errored, finished, or was + * cancelled. + */ + let isComplete = false; + + /** + * If true, we're currently waiting for the initialization segment to be + * parsed before parsing a received chunk. + */ + let isWaitingOnInitSegment = false; + + canceller.signal.register(() => { + this._mediaSegmentRequest = null; + if (isComplete) { + return; + } + if (this._mediaSegmentAwaitingInitMetadata === segment.id) { + this._mediaSegmentAwaitingInitMetadata = null; + } + isComplete = true; + isWaitingOnInitSegment = false; + }); + const emitChunk = ( + parsed : ISegmentParserParsedInitChunk | + ISegmentParserParsedMediaChunk + ) : void => { + assert(parsed.segmentType === "media", "Should have loaded a media segment."); + this.trigger("parsedMediaSegment", objectAssign({}, parsed, { segment })); + }; + + + const continueToNextSegment = () : void => { + const lastQueue = this._downloadQueue.getValue().segmentQueue; + if (lastQueue.length === 0) { + isComplete = true; + this.trigger("emptyQueue", null); + return; + } else if (lastQueue[0].segment.id === segment.id) { + lastQueue.shift(); + } + isComplete = true; + recursivelyRequestSegments(lastQueue[0]); + }; + + /** Scheduled actual segment request. */ + const request = this._segmentFetcher.createRequest(context, priority, { + /** + * Callback called when the request has to be retried. + * @param {Error} error + */ + onRetry: (error : IPlayerError) : void => { + this.trigger("requestRetry", { segment, error }); + }, + + /** + * Callback called when the request has to be interrupted and + * restarted later. + */ + beforeInterrupted() { + log.info("Stream: segment request interrupted temporarly.", + segment.id, + segment.time); + }, + + /** + * Callback called when a decodable chunk of the segment is available. + * @param {Function} parse - Function allowing to parse the segment. + */ + onChunk: ( + parse : ( + initTimescale : number | undefined + ) => ISegmentParserParsedInitChunk | ISegmentParserParsedMediaChunk + ) : void => { + const initTimescale = this._initSegmentInfoRef.getValue(); + if (initTimescale !== undefined) { + emitChunk(parse(initTimescale ?? undefined)); + } else { + isWaitingOnInitSegment = true; + + // We could also technically call `waitUntilDefined` in both cases, + // but I found it globally clearer to segregate the two cases, + // especially to always have a meaningful `isWaitingOnInitSegment` + // boolean which is a very important variable. + this._initSegmentInfoRef.waitUntilDefined((actualTimescale) => { + emitChunk(parse(actualTimescale ?? undefined)); + }, { clearSignal: canceller.signal }); + } + }, + + /** Callback called after all chunks have been sent. */ + onAllChunksReceived: () : void => { + if (!isWaitingOnInitSegment) { + this.trigger("fullyLoadedSegment", segment); + } else { + this._mediaSegmentAwaitingInitMetadata = segment.id; + this._initSegmentInfoRef.waitUntilDefined(() => { + this._mediaSegmentAwaitingInitMetadata = null; + isWaitingOnInitSegment = false; + this.trigger("fullyLoadedSegment", segment); + }, { clearSignal: canceller.signal }); + } + }, + + /** + * Callback called right after the request ended but before the next + * requests are scheduled. It is used to schedule the next segment. + */ + beforeEnded: () : void => { + unlinkCanceller(); + this._mediaSegmentRequest = null; + + if (isWaitingOnInitSegment) { + this._initSegmentInfoRef.waitUntilDefined(continueToNextSegment, + { clearSignal: canceller.signal }); + } else { + continueToNextSegment(); + } + }, + + }, canceller.signal); + + request.catch((error : unknown) => { + unlinkCanceller(); + if (!isComplete) { + isComplete = true; + this.stop(); + this.trigger("error", error); + } + }); + + this._mediaSegmentRequest = { segment, priority, request, canceller }; + }; + recursivelyRequestSegments(currentNeededSegment); + } + + /** + * Internal logic performing initialization segment requests. + * @param {Object} queuedInitSegment + */ + private _restartInitSegmentDownloadingQueue( + queuedInitSegment : IQueuedSegment | null + ) : void { + if (this._currentCanceller !== null && this._currentCanceller.isUsed()) { + return; + } + if (this._initSegmentRequest !== null) { + this._initSegmentRequest.canceller.cancel(); + } + if (queuedInitSegment === null) { + return ; + } + + const canceller = new TaskCanceller(); + const unlinkCanceller = this._currentCanceller === null ? + noop : + canceller.linkToSignal(this._currentCanceller.signal); + const { segment, priority } = queuedInitSegment; + const context = objectAssign({ segment }, this._content); + + /** + * If `true` , the current task has either errored, finished, or was + * cancelled. + */ + let isComplete = false; + + const request = this._segmentFetcher.createRequest(context, priority, { + onRetry: (err : IPlayerError) : void => { + this.trigger("requestRetry", { segment, error: err }); + }, + beforeInterrupted: () => { + log.info("Stream: init segment request interrupted temporarly.", segment.id); + }, + beforeEnded: () => { + unlinkCanceller(); + this._initSegmentRequest = null; + isComplete = true; + }, + onChunk: (parse : (x : undefined) => ISegmentParserParsedInitChunk | + ISegmentParserParsedMediaChunk) : void => { + const parsed = parse(undefined); + assert(parsed.segmentType === "init", "Should have loaded an init segment."); + this.trigger("parsedInitSegment", objectAssign({}, parsed, { segment })); + if (parsed.segmentType === "init") { + this._initSegmentInfoRef.setValue(parsed.initTimescale ?? null); + } + }, + onAllChunksReceived: () : void => { + this.trigger("fullyLoadedSegment", segment); + }, + }, canceller.signal); + + request.catch((error : unknown) => { + unlinkCanceller(); + if (!isComplete) { + isComplete = true; + this.stop(); + this.trigger("error", error); + } + }); + + canceller.signal.register(() => { + this._initSegmentRequest = null; + if (isComplete) { + return; + } + isComplete = true; + }); + + this._initSegmentRequest = { segment, priority, request, canceller }; + } +} + +/** + * Events sent by the `DownloadingQueue`. + * + * The key is the event's name and the value the format of the corresponding + * event's payload. + */ +export interface IDownloadingQueueEvent { + /** + * Notify that the initialization segment has been fully loaded and parsed. + * + * You can now push that segment to its corresponding buffer and use its parsed + * metadata. + * + * Only sent if an initialization segment exists (when the `DownloadingQueue`'s + * `hasInitSegment` constructor option has been set to `true`). + * In that case, an `IParsedInitSegmentEvent` will always be sent before any + * `IParsedSegmentEvent` event is sent. + */ + parsedInitSegment : IParsedInitSegmentPayload; + /** + * Notify that a media chunk (decodable sub-part of a media segment) has been + * loaded and parsed. + * + * If an initialization segment exists (when the `DownloadingQueue`'s + * `hasInitSegment` constructor option has been set to `true`), an + * `IParsedSegmentEvent` will always be sent AFTER the `IParsedInitSegmentEvent` + * event. + * + * It can now be pushed to its corresponding buffer. Note that there might be + * multiple `IParsedSegmentEvent` for a single segment, if that segment is + * divided into multiple decodable chunks. + * You will know that all `IParsedSegmentEvent` have been loaded for a given + * segment once you received the `IEndOfSegmentEvent` for that segment. + */ + parsedMediaSegment : IParsedSegmentPayload; + /** Notify that a media or initialization segment has been fully-loaded. */ + fullyLoadedSegment : ISegment; + /** + * Notify that a media or initialization segment request is retried. + * This happened most likely because of an HTTP error. + */ + requestRetry : IRequestRetryPayload; + /** + * Notify that the media segment queue is now empty. + * This can be used to re-check if any segment are now needed. + */ + emptyQueue : null; + /** + * Notify that a fatal error happened (such as request failures), which has + * completely stopped the downloading queue. + * + * You may still restart the queue after receiving this event. + */ + error : unknown; +} + +/** Payload for a `parsedInitSegment` event. */ +export type IParsedInitSegmentPayload = ISegmentParserParsedInitChunk & + { segment : ISegment }; +/** Payload for a `parsedMediaSegment` event. */ +export type IParsedSegmentPayload = ISegmentParserParsedMediaChunk & + { segment : ISegment }; +/** Payload for a `requestRetry` event. */ +export interface IRequestRetryPayload { + segment : ISegment; + error : IPlayerError; +} + +/** + * Structure of the object that has to be emitted through the `downloadQueue` + * shared reference, to signal which segments are currently needed. + */ +export interface IDownloadQueueItem { + /** + * A potential initialization segment that needs to be loaded and parsed. + * It will generally be requested in parralel of the first media segments. + * + * Can be set to `null` if you don't need to load the initialization segment + * for now. + * + * If the `DownloadingQueue`'s `hasInitSegment` constructor option has been + * set to `true`, no media segment will be parsed before the initialization + * segment has been loaded and parsed. + */ + initSegment : IQueuedSegment | null; + + /** + * The queue of media segments currently needed for download. + * + * Those will be loaded from the first element in that queue to the last + * element in it. + * + * Note that any media segments in the segment queue will only be parsed once + * either of these is true: + * - An initialization segment has been loaded and parsed by this + * `DownloadingQueue` instance. + * - The `DownloadingQueue`'s `hasInitSegment` constructor option has been + * set to `false`. + */ + segmentQueue : IQueuedSegment[]; +} + +/** Object describing a pending Segment request. */ +interface ISegmentRequestObject { + /** The segment the request is for. */ + segment : ISegment; + /** The request itself. Can be used to update its priority. */ + request: Promise; + /** Last set priority of the segment request (lower number = higher priority). */ + priority : number; + /** Allows to cancel that segment from being requested. */ + canceller : TaskCanceller; +} + +/** Context for segments downloaded through the DownloadingQueue. */ +export interface IDownloadingQueueContext { + /** Adaptation linked to the segments you want to load. */ + adaptation : Adaptation; + /** Manifest linked to the segments you want to load. */ + manifest : Manifest; + /** Period linked to the segments you want to load. */ + period : Period; + /** Representation linked to the segments you want to load. */ + representation : Representation; +} diff --git a/src/core/stream/representation/force_garbage_collection.ts b/src/core/stream/representation/utils/force_garbage_collection.ts similarity index 93% rename from src/core/stream/representation/force_garbage_collection.ts rename to src/core/stream/representation/utils/force_garbage_collection.ts index 93e36b4a54..2b86916581 100644 --- a/src/core/stream/representation/force_garbage_collection.ts +++ b/src/core/stream/representation/utils/force_garbage_collection.ts @@ -14,12 +14,11 @@ * limitations under the License. */ -import config from "../../../config"; -import log from "../../../log"; -import { getInnerAndOuterTimeRanges } from "../../../utils/ranges"; -import { CancellationSignal } from "../../../utils/task_canceller"; -import { SegmentBuffer } from "../../segment_buffers"; - +import config from "../../../../config"; +import log from "../../../../log"; +import { getInnerAndOuterTimeRanges } from "../../../../utils/ranges"; +import { CancellationSignal } from "../../../../utils/task_canceller"; +import { SegmentBuffer } from "../../../segment_buffers"; /** * Run the garbage collector. diff --git a/src/core/stream/representation/get_buffer_status.ts b/src/core/stream/representation/utils/get_buffer_status.ts similarity index 98% rename from src/core/stream/representation/get_buffer_status.ts rename to src/core/stream/representation/utils/get_buffer_status.ts index 7e73bec1f9..8b3ea68c8b 100644 --- a/src/core/stream/representation/get_buffer_status.ts +++ b/src/core/stream/representation/utils/get_buffer_status.ts @@ -14,20 +14,20 @@ * limitations under the License. */ -import config from "../../../config"; +import config from "../../../../config"; import Manifest, { Adaptation, Period, Representation, -} from "../../../manifest"; -import isNullOrUndefined from "../../../utils/is_null_or_undefined"; -import { IReadOnlyPlaybackObserver } from "../../api"; +} from "../../../../manifest"; +import isNullOrUndefined from "../../../../utils/is_null_or_undefined"; +import { IReadOnlyPlaybackObserver } from "../../../api"; import { IBufferedChunk, IEndOfSegmentOperation, SegmentBuffer, SegmentBufferOperation, -} from "../../segment_buffers"; +} from "../../../segment_buffers"; import { IBufferDiscontinuity, IQueuedSegment, diff --git a/src/core/stream/representation/get_needed_segments.ts b/src/core/stream/representation/utils/get_needed_segments.ts similarity index 98% rename from src/core/stream/representation/get_needed_segments.ts rename to src/core/stream/representation/utils/get_needed_segments.ts index 1342e9424a..c139532f14 100644 --- a/src/core/stream/representation/get_needed_segments.ts +++ b/src/core/stream/representation/utils/get_needed_segments.ts @@ -14,22 +14,21 @@ * limitations under the License. */ -// eslint-disable-next-line max-len -import config from "../../../config"; -import log from "../../../log"; +import config from "../../../../config"; +import log from "../../../../log"; import Manifest, { Adaptation, areSameContent, ISegment, Period, Representation, -} from "../../../manifest"; -import objectAssign from "../../../utils/object_assign"; -import { IBufferedChunk, IEndOfSegmentInfos } from "../../segment_buffers"; +} from "../../../../manifest"; +import objectAssign from "../../../../utils/object_assign"; +import { IBufferedChunk, IEndOfSegmentInfos } from "../../../segment_buffers"; import { IBufferedHistoryEntry, IChunkContext, -} from "../../segment_buffers/inventory"; +} from "../../../segment_buffers/inventory"; interface IContentContext { diff --git a/src/core/stream/representation/get_segment_priority.ts b/src/core/stream/representation/utils/get_segment_priority.ts similarity index 97% rename from src/core/stream/representation/get_segment_priority.ts rename to src/core/stream/representation/utils/get_segment_priority.ts index f35c1ae5b8..1f6a07a8e8 100644 --- a/src/core/stream/representation/get_segment_priority.ts +++ b/src/core/stream/representation/utils/get_segment_priority.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import config from "../../../config"; - +import config from "../../../../config"; /** * Calculate the priority number for a given segment start time, in function of diff --git a/src/core/stream/representation/utils/push_init_segment.ts b/src/core/stream/representation/utils/push_init_segment.ts new file mode 100644 index 0000000000..bd3c66612a --- /dev/null +++ b/src/core/stream/representation/utils/push_init_segment.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Manifest, { + Adaptation, + ISegment, + Period, + Representation, +} from "../../../../manifest"; +import { CancellationSignal } from "../../../../utils/task_canceller"; +import { IReadOnlyPlaybackObserver } from "../../../api"; +import { + IPushedChunkData, + SegmentBuffer, +} from "../../../segment_buffers"; +import { + IRepresentationStreamPlaybackObservation, + IStreamEventAddedSegmentPayload, +} from "../types"; +import appendSegmentToBuffer from "./append_segment_to_buffer"; + +/** + * Push the initialization segment to the SegmentBuffer. + * @param {Object} args + * @param {Object} cancelSignal + * @returns {Promise} + */ +export default async function pushInitSegment( + { + playbackObserver, + content, + segment, + segmentData, + segmentBuffer, + } : { + playbackObserver : IReadOnlyPlaybackObserver< + IRepresentationStreamPlaybackObservation + >; + content: { adaptation : Adaptation; + manifest : Manifest; + period : Period; + representation : Representation; }; + segmentData : T | null; + segment : ISegment; + segmentBuffer : SegmentBuffer; + }, + cancelSignal : CancellationSignal +) : Promise< IStreamEventAddedSegmentPayload | null > { + if (segmentData === null) { + return null; + } + if (cancelSignal.cancellationError !== null) { + throw cancelSignal.cancellationError; + } + const codec = content.representation.getMimeTypeString(); + const data : IPushedChunkData = { initSegment: segmentData, + chunk: null, + timestampOffset: 0, + appendWindow: [ undefined, undefined ], + codec }; + await appendSegmentToBuffer(playbackObserver, + segmentBuffer, + { data, inventoryInfos: null }, + cancelSignal); + const buffered = segmentBuffer.getBufferedRanges(); + return { content, segment, buffered, segmentData }; +} diff --git a/src/core/stream/representation/utils/push_media_segment.ts b/src/core/stream/representation/utils/push_media_segment.ts new file mode 100644 index 0000000000..396a0b0368 --- /dev/null +++ b/src/core/stream/representation/utils/push_media_segment.ts @@ -0,0 +1,113 @@ +/** + * Copyright 2015 CANAL+ Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import config from "../../../../config"; +import Manifest, { + Adaptation, + ISegment, + Period, + Representation, +} from "../../../../manifest"; +import { ISegmentParserParsedMediaChunk } from "../../../../transports"; +import objectAssign from "../../../../utils/object_assign"; +import { CancellationSignal } from "../../../../utils/task_canceller"; +import { IReadOnlyPlaybackObserver } from "../../../api"; +import { SegmentBuffer } from "../../../segment_buffers"; +import { + IRepresentationStreamPlaybackObservation, + IStreamEventAddedSegmentPayload, +} from "../types"; +import appendSegmentToBuffer from "./append_segment_to_buffer"; + +/** + * Push a given media segment (non-init segment) to a SegmentBuffer. + * @param {Object} args + * @param {Object} cancelSignal + * @returns {Promise} + */ +export default async function pushMediaSegment( + { playbackObserver, + content, + initSegmentData, + parsedSegment, + segment, + segmentBuffer } : + { playbackObserver : IReadOnlyPlaybackObserver< + IRepresentationStreamPlaybackObservation + >; + content: { adaptation : Adaptation; + manifest : Manifest; + period : Period; + representation : Representation; }; + initSegmentData : T | null; + parsedSegment : ISegmentParserParsedMediaChunk; + segment : ISegment; + segmentBuffer : SegmentBuffer; }, + cancelSignal : CancellationSignal +) : Promise< IStreamEventAddedSegmentPayload | null > { + if (parsedSegment.chunkData === null) { + return null; + } + if (cancelSignal.cancellationError !== null) { + throw cancelSignal.cancellationError; + } + const { chunkData, + chunkInfos, + chunkOffset, + chunkSize, + appendWindow } = parsedSegment; + const codec = content.representation.getMimeTypeString(); + const { APPEND_WINDOW_SECURITIES } = config.getCurrent(); + // Cutting exactly at the start or end of the appendWindow can lead to + // cases of infinite rebuffering due to how browser handle such windows. + // To work-around that, we add a small offset before and after those. + const safeAppendWindow : [ number | undefined, number | undefined ] = [ + appendWindow[0] !== undefined ? + Math.max(0, appendWindow[0] - APPEND_WINDOW_SECURITIES.START) : + undefined, + appendWindow[1] !== undefined ? + appendWindow[1] + APPEND_WINDOW_SECURITIES.END : + undefined, + ]; + + const data = { initSegment: initSegmentData, + chunk: chunkData, + timestampOffset: chunkOffset, + appendWindow: safeAppendWindow, + codec }; + + let estimatedStart = chunkInfos?.time ?? segment.time; + const estimatedDuration = chunkInfos?.duration ?? segment.duration; + let estimatedEnd = estimatedStart + estimatedDuration; + if (safeAppendWindow[0] !== undefined) { + estimatedStart = Math.max(estimatedStart, safeAppendWindow[0]); + } + if (safeAppendWindow[1] !== undefined) { + estimatedEnd = Math.min(estimatedEnd, safeAppendWindow[1]); + } + + const inventoryInfos = objectAssign({ segment, + chunkSize, + start: estimatedStart, + end: estimatedEnd }, + content); + await appendSegmentToBuffer(playbackObserver, + segmentBuffer, + { data, inventoryInfos }, + cancelSignal); + const buffered = segmentBuffer.getBufferedRanges(); + return { content, segment, buffered, segmentData: chunkData }; +} diff --git a/src/core/stream/types.ts b/src/core/stream/types.ts deleted file mode 100644 index 70f7f171e4..0000000000 --- a/src/core/stream/types.ts +++ /dev/null @@ -1,558 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Subject } from "rxjs"; -import { - Adaptation, - ISegment, - Period, - Representation, -} from "../../manifest"; -import { IEMSG } from "../../parsers/containers/isobmff"; -import { IPlayerError } from "../../public_types"; -import { IContentProtection } from "../decrypt"; -import { IBufferType } from "../segment_buffers"; - -/** Information about a Segment waiting to be loaded by the Stream. */ -export interface IQueuedSegment { - /** Priority of the segment request (lower number = higher priority). */ - priority : number; - /** Segment wanted. */ - segment : ISegment; -} - -/** Describe an encountered hole in the buffer, called a "discontinuity". */ -export interface IBufferDiscontinuity { - /** - * Start time, in seconds, at which the discontinuity starts. - * - * if set to `undefined`, its true start time is unknown but the current - * position is part of it. It is thus a discontinuity that is currently - * encountered. - */ - start : number | undefined; - /** - * End time, in seconds at which the discontinuity ends (and thus where - * new segments are encountered). - * - * If `null`, no new media segment is available for that Period and - * buffer type until the end of the Period. - */ - end : number | null; -} - -/** - * Event sent by a `RepresentationStream` to announce the current status - * regarding the buffer for its associated Period and type (e.g. "audio", - * "video", "text" etc.). - * - * Each new `IStreamStatusEvent` event replace the precedent one for the - * same Period and type. - */ -export interface IStreamStatusEvent { - type : "stream-status"; - value : { - /** Period concerned. */ - period : Period; - /** Buffer type concerned. */ - bufferType : IBufferType; - /** - * Present or future "hole" in the SegmentBuffer's buffer that will not be - * filled by a segment, despite being part of the time period indicated by - * the associated Period. - * - * This value is set to the most imminent of such "discontinuity", which - * can be either: - * - * - current (no segment available at `position` but future segments are - * available), in which case this discontinuity's true beginning might - * be unknown. - * - * - a future hole between two segments in that Period. - * - * - missing media data at the end of the time period associated to that - * Period. - * - * The presence or absence of a discontinuity can evolve during playback - * (because new tracks or qualities might not have the same ones). - * As such, it is advised to only consider the last discontinuity sent - * through a `"stream-status"` event. - */ - imminentDiscontinuity : IBufferDiscontinuity | null; - /** - * If `true`, no segment are left to be loaded to be able to play until the - * end of the Period. - */ - hasFinishedLoading : boolean; - /** - * Segments that will be scheduled for download to fill the buffer until - * the buffer goal (first element of that list might already be ). - */ - neededSegments : IQueuedSegment[]; - /** Position in the content in seconds from which this status was done. */ - position : number; - }; -} - -/** Event sent when a minor error happened, which doesn't stop playback. */ -export interface IStreamWarningEvent { - type : "warning"; - /** The error corresponding to the warning given. */ - value : IPlayerError; -} - -/** Emitted after a new segment has been succesfully added to the SegmentBuffer */ -export interface IStreamEventAddedSegment { - type : "added-segment"; - value : { - /** Context about the content that has been added. */ - content: { period : Period; - adaptation : Adaptation; - representation : Representation; }; - /** The concerned Segment. */ - segment : ISegment; - /** TimeRanges of the concerned SegmentBuffer after the segment was pushed. */ - buffered : TimeRanges; - /* The data pushed */ - segmentData : T; - }; -} - -/** - * The Manifest needs to be refreshed. - * Note that a `RepresentationStream` might still be active even after sending - * this event: - * It might download and push segments, send any other event etc. - */ -export interface IStreamNeedsManifestRefresh { - type : "needs-manifest-refresh"; - value: undefined; -} - -/** - * The Manifest is possibly out-of-sync and needs to be refreshed completely. - * - * The Stream made that guess because a segment that should have been available - * is not and because it suspects this is due to a synchronization problem. - */ -export interface IStreamManifestMightBeOutOfSync { - type : "manifest-might-be-out-of-sync"; - value : undefined; -} - -/** Emitted when a segment with protection information has been encountered. */ -export interface IEncryptionDataEncounteredEvent { - type : "encryption-data-encountered"; - value : IContentProtection; -} - -/** Structure describing an "inband" event, as found in a media segment. */ -export interface IInbandEvent { - /** Type when the event was foud inside a "emsg` ISOBMFF box */ - type: "emsg"; - /** Value when the event was foud inside a "emsg` ISOBMFF box */ - value: IEMSG; -} - -export interface IInbandEventsEvent { type : "inband-events"; - value : IInbandEvent[]; } - -/** - * Event sent when a `RepresentationStream` is terminating: - * - * - it has finished all its segment requests and won't do new ones. - * - * - it has stopped regularly checking for its current status. - * - * - it only waits until all the segments it has loaded have been pushed to the - * SegmentBuffer before actually completing. - * - * You can use this event as a hint that a new `RepresentationStream` can be - * created. - */ -export interface IStreamTerminatingEvent { - type : "stream-terminating"; - value : undefined; -} - -/** Emitted as new bitrate estimates are done. */ -export interface IBitrateEstimationChangeEvent { - type : "bitrateEstimationChange"; - value : { - /** The type of buffer for which the estimation is done. */ - type : IBufferType; - /** - * The bitrate estimate, in bits per seconds. `undefined` when no bitrate - * estimate is currently available. - */ - bitrate : number|undefined; - }; -} - -/** - * Emitted when a new `RepresentationStream` is created to load segments from a - * `Representation`. - */ -export interface IRepresentationChangeEvent { - type : "representationChange"; - value : { - /** The type of buffer linked to that `RepresentationStream`. */ - type : IBufferType; - /** The `Period` linked to the `RepresentationStream` we're creating. */ - period : Period; - /** - * The `Representation` linked to the `RepresentationStream` we're creating. - * `null` when we're choosing no Representation at all. - */ - representation : Representation | - null; }; -} - -/** - * Emitted when a new `AdaptationStream` is created to load segments from an - * `Adaptation`. - */ -export interface IAdaptationChangeEvent { - type : "adaptationChange"; - value : { - /** The type of buffer for which the Representation is changing. */ - type : IBufferType; - /** The `Period` linked to the `RepresentationStream` we're creating. */ - period : Period; - /** - * The `Adaptation` linked to the `AdaptationStream` we're creating. - * `null` when we're choosing no Adaptation at all. - */ - adaptation : Adaptation | - null; - }; -} - -/** Emitted when a new `Period` is currently playing. */ -export interface IActivePeriodChangedEvent { - type: "activePeriodChanged"; - value : { - /** The Period we're now playing. */ - period: Period; - }; -} - -/** - * A new `PeriodStream` is ready to start but needs an Adaptation (i.e. track) - * to be chosen first. - */ -export interface IPeriodStreamReadyEvent { - type : "periodStreamReady"; - value : { - /** The type of buffer linked to the `PeriodStream` we want to create. */ - type : IBufferType; - /** The `Period` linked to the `PeriodStream` we have created. */ - period : Period; - /** - * The subject through which any Adaptation (i.e. track) choice should be - * emitted for that `PeriodStream`. - * - * The `PeriodStream` will not do anything until this subject has emitted - * at least one to give its initial choice. - * You can send `null` through it to tell this `PeriodStream` that you don't - * want any `Adaptation`. - */ - adaptation$ : Subject; - }; -} - -/** - * A `PeriodStream` has been removed. - * This event can be used for clean-up purposes. For example, you are free to - * remove from scope the subject that you used to choose a track for that - * `PeriodStream`. - */ -export interface IPeriodStreamClearedEvent { - type : "periodStreamCleared"; - value : { - /** - * The type of buffer linked to the `PeriodStream` we just removed. - * - * The combination of this and `Period` should give you enough information - * about which `PeriodStream` has been removed. - */ - type : IBufferType; - /** - * The `Period` linked to the `PeriodStream` we just removed. - * - * The combination of this and `Period` should give you enough information - * about which `PeriodStream` has been removed. - */ - period : Period; - }; -} - -/** - * The last (chronologically) PeriodStreams from every type of buffers are full. - * This means usually that segments for the whole content have been pushed to - * the end. - */ -export interface IEndOfStreamEvent { type: "end-of-stream"; - value: undefined; } - -/** - * At least a single PeriodStream is now pushing segments. - * This event is sent to cancel a previous `IEndOfStreamEvent`. - * - * Note that it also can be send if no `IEndOfStreamEvent` has been sent before. - */ -export interface IResumeStreamEvent { type: "resume-stream"; - value: undefined; } - -/** - * The last (chronologically) `PeriodStream` for a given type has pushed all - * the segments it needs until the end. - */ -export interface ICompletedStreamEvent { type: "complete-stream"; - value : { type: IBufferType }; } - -/** - * A situation needs the MediaSource to be reloaded. - * - * Once the MediaSource is reloaded, the Streams need to be restarted from - * scratch. - */ -export interface INeedsMediaSourceReload { - type: "needs-media-source-reload"; - value: { - /** - * The position in seconds and the time at which the MediaSource should be - * reset once it has been reloaded. - */ - position : number; - /** - * If `true`, we want the HTMLMediaElement to play right after the reload is - * done. - * If `false`, we want to stay in a paused state at that point. - */ - autoPlay : boolean; - }; -} - -/** - * A stream cannot go forward loading segments because it needs the - * `MediaSource` to be reloaded first. - * - * This is a Stream internal event before being translated into either an - * `INeedsMediaSourceReload` event or an `ILockedStreamEvent` depending on if - * the reloading action has to be taken now or when the corresponding Period - * becomes active. - */ -export interface IWaitingMediaSourceReloadInternalEvent { - type: "waiting-media-source-reload"; - value: { - /** Period concerned. */ - period : Period; - /** Buffer type concerned. */ - bufferType : IBufferType; - /** - * The position in seconds and the time at which the MediaSource should be - * reset once it has been reloaded. - */ - position : number; - /** - * If `true`, we want the HTMLMediaElement to play right after the reload is - * done. - * If `false`, we want to stay in a paused state at that point. - */ - autoPlay : boolean; - }; -} - -/** - * The stream is unable to load segments for a particular Period and buffer - * type until that Period becomes the currently-played Period. - * - * This might be the case for example when a track change happened for an - * upcoming Period, which necessitates the reloading of the media source - - * through a `INeedsMediaSourceReload` event once the Period is the current one. - * Here, the stream might stay in a locked mode for segments linked to that - * Period and buffer type, meaning it will not load any such segment until that - * next Period becomes the current one (in which case it will probably ask to - * reload). - * - * This event can be useful when investigating rebuffering situation: one might - * be due to the next Period not loading segment of a certain type because of a - * locked stream. In that case, playing until or seeking at the start of the - * corresponding Period should be enough to "unlock" the stream. - */ -export interface ILockedStreamEvent { - type : "locked-stream"; - value : { - /** Period concerned. */ - period : Period; - /** Buffer type concerned. */ - bufferType : IBufferType; - }; -} - -/** - * Event emitted after the SegmentBuffer have been "cleaned" to remove from it - * every non-decipherable segments - usually following an update of the - * decipherability status of some `Representation`(s). - * - * When that event is emitted, the current HTMLMediaElement's buffer might need - * to be "flushed" to continue (e.g. through a little seek operation) or in - * worst cases completely removed and re-created through the "reload" mechanism, - * depending on the platform. - */ -export interface INeedsDecipherabilityFlush { - type: "needs-decipherability-flush"; - value: { - /** - * Indicated in the case where the MediaSource has to be reloaded, - * in which case the time of the HTMLMediaElement should be reset to that - * position, in seconds, once reloaded. - */ - position : number; - /** - * If `true`, we want the HTMLMediaElement to play right after the flush is - * done. - * If `false`, we want to stay in a paused state at that point. - */ - autoPlay : boolean; - /** - * The duration (maximum seekable position) of the content. - * This is indicated in the case where a seek has to be performed, to avoid - * seeking too far in the content. - */ - duration : number; - }; -} - -/** - * Some situations might require the browser's buffers to be refreshed. - * This event is emitted when such situation arised. - */ -export interface INeedsBufferFlushEvent { - type: "needs-buffer-flush"; - value: undefined; -} - -/** Event sent by a `RepresentationStream`. */ -export type IRepresentationStreamEvent = IStreamStatusEvent | - IStreamEventAddedSegment | - IEncryptionDataEncounteredEvent | - IStreamManifestMightBeOutOfSync | - IStreamTerminatingEvent | - IStreamNeedsManifestRefresh | - IStreamWarningEvent | - IInbandEventsEvent; - -/** Event sent by an `AdaptationStream`. */ -export type IAdaptationStreamEvent = IBitrateEstimationChangeEvent | - INeedsDecipherabilityFlush | - IRepresentationChangeEvent | - INeedsBufferFlushEvent | - IWaitingMediaSourceReloadInternalEvent | - - // From a RepresentationStream - - IStreamStatusEvent | - IStreamEventAddedSegment | - IEncryptionDataEncounteredEvent | - IStreamManifestMightBeOutOfSync | - IStreamNeedsManifestRefresh | - IStreamWarningEvent | - IInbandEventsEvent; - -/** Event sent by a `PeriodStream`. */ -export type IPeriodStreamEvent = IPeriodStreamReadyEvent | - IAdaptationChangeEvent | - IWaitingMediaSourceReloadInternalEvent | - - // From an AdaptationStream - - IBitrateEstimationChangeEvent | - INeedsMediaSourceReload | - INeedsBufferFlushEvent | - INeedsDecipherabilityFlush | - IRepresentationChangeEvent | - - // From a RepresentationStream - - IStreamStatusEvent | - IStreamEventAddedSegment | - IEncryptionDataEncounteredEvent | - IStreamManifestMightBeOutOfSync | - IStreamNeedsManifestRefresh | - IStreamWarningEvent | - IInbandEventsEvent; - -/** Event coming from function(s) managing multiple PeriodStreams. */ -export type IMultiplePeriodStreamsEvent = IPeriodStreamClearedEvent | - ICompletedStreamEvent | - - // From a PeriodStream - - IPeriodStreamReadyEvent | - INeedsBufferFlushEvent | - IAdaptationChangeEvent | - IWaitingMediaSourceReloadInternalEvent | - - // From an AdaptationStream - - IBitrateEstimationChangeEvent | - INeedsMediaSourceReload | - INeedsDecipherabilityFlush | - IRepresentationChangeEvent | - - // From a RepresentationStream - - IStreamStatusEvent | - IStreamEventAddedSegment | - IEncryptionDataEncounteredEvent | - IStreamManifestMightBeOutOfSync | - IStreamNeedsManifestRefresh | - IStreamWarningEvent | - IInbandEventsEvent; - -/** Every event sent by the `StreamOrchestrator`. */ -export type IStreamOrchestratorEvent = IActivePeriodChangedEvent | - IEndOfStreamEvent | - IResumeStreamEvent | - - IPeriodStreamClearedEvent | - ICompletedStreamEvent | - - // From a PeriodStream - - IPeriodStreamReadyEvent | - IAdaptationChangeEvent | - ILockedStreamEvent | - - // From an AdaptationStream - - IBitrateEstimationChangeEvent | - INeedsMediaSourceReload | - INeedsBufferFlushEvent | - INeedsDecipherabilityFlush | - IRepresentationChangeEvent | - - // From a RepresentationStream - - IStreamStatusEvent | - IStreamEventAddedSegment | - IEncryptionDataEncounteredEvent | - IStreamManifestMightBeOutOfSync | - IStreamNeedsManifestRefresh | - IStreamWarningEvent | - IInbandEventsEvent; diff --git a/src/default_config.ts b/src/default_config.ts index 98253ea88f..d198f1e0de 100644 --- a/src/default_config.ts +++ b/src/default_config.ts @@ -923,15 +923,36 @@ const DEFAULT_CONFIG = { */ MIN_CANCELABLE_PRIORITY: 3, // priority number 3 onward can be cancelled - /** - * Robustnesses used in the {audio,video}Capabilities of the - * MediaKeySystemConfiguration (DRM). - * - * Only used for widevine keysystems. - * - * Defined in order of importance (first will be tested first etc.) - * @type {Array.} - */ + /** + * Codecs used in the videoCapabilities of the MediaKeySystemConfiguration + * (DRM). + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ + EME_DEFAULT_VIDEO_CODECS: [ "video/mp4;codecs=\"avc1.4d401e\"", + "video/mp4;codecs=\"avc1.42e01e\"", + "video/webm;codecs=\"vp8\"" ], + + /** + * Codecs used in the audioCapabilities of the MediaKeySystemConfiguration + * (DRM). + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ + EME_DEFAULT_AUDIO_CODECS: [ "audio/mp4;codecs=\"mp4a.40.2\"", + "audio/webm;codecs=opus" ], + + /** + * Robustnesses used in the {audio,video}Capabilities of the + * MediaKeySystemConfiguration (DRM). + * + * Only used for widevine keysystems. + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ EME_DEFAULT_WIDEVINE_ROBUSTNESSES: [ "HW_SECURE_ALL", "HW_SECURE_DECODE", "HW_SECURE_CRYPTO", diff --git a/src/experimental/features/__tests__/debug_element.test.ts b/src/experimental/features/__tests__/debug_element.test.ts new file mode 100644 index 0000000000..b1b8d53084 --- /dev/null +++ b/src/experimental/features/__tests__/debug_element.test.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ + +describe("Features list - DEBUG_ELEMENT", () => { + beforeEach(() => { + jest.resetModules(); + }); + + it("should add DEBUG_ELEMENT in the current features", () => { + const feat = {}; + jest.mock("../../../core/api/debug", () => ({ __esModule: true as const, + default: feat })); + const addFeature = jest.requireActual("../debug_element").default; + + const featureObject : { + createDebugElement? : unknown; + } = {}; + addFeature(featureObject); + expect(featureObject).toEqual({ createDebugElement: {} }); + expect(featureObject.createDebugElement).toBe(feat); + }); +}); diff --git a/src/experimental/features/debug_element.ts b/src/experimental/features/debug_element.ts new file mode 100644 index 0000000000..73434f986c --- /dev/null +++ b/src/experimental/features/debug_element.ts @@ -0,0 +1,13 @@ +import createDebugElement from "../../core/api/debug"; +import { IFeaturesObject } from "../../features/types"; + +/** + * Add ability to parse SAMI text tracks in an HTML textrack mode. + * @param {Object} features + */ +function addDebugElementFeature(features : IFeaturesObject) : void { + features.createDebugElement = createDebugElement; +} + +export { addDebugElementFeature as DEBUG_ELEMENT }; +export default addDebugElementFeature; diff --git a/src/experimental/features/index.ts b/src/experimental/features/index.ts index 3631bbd61b..c13f264cbd 100644 --- a/src/experimental/features/index.ts +++ b/src/experimental/features/index.ts @@ -15,5 +15,6 @@ */ export { DASH_WASM } from "./dash_wasm"; +export { DEBUG_ELEMENT } from "./debug_element"; export { LOCAL_MANIFEST } from "./local"; export { METAPLAYLIST } from "./metaplaylist"; diff --git a/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts b/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts index 712ed7bc35..bdfa21951a 100644 --- a/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts +++ b/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts @@ -15,9 +15,10 @@ */ import { MediaSource_ } from "../../../compat"; -import { resetMediaSource } from "../../../core/init/create_media_source"; +import { resetMediaSource } from "../../../core/init/utils/create_media_source"; import { AudioVideoSegmentBuffer } from "../../../core/segment_buffers/implementations"; import log from "../../../log"; +import createCancellablePromise from "../../../utils/create_cancellable_promise"; import isNonEmptyString from "../../../utils/is_non_empty_string"; import { CancellationSignal } from "../../../utils/task_canceller"; @@ -33,7 +34,7 @@ export default function prepareSourceBuffer( codec: string, cleanUpSignal: CancellationSignal ): Promise { - return new Promise((resolve, reject) => { + return createCancellablePromise(cleanUpSignal, (resolve, reject) => { if (MediaSource_ == null) { throw new Error("No MediaSource Object was found in the current browser."); } @@ -49,15 +50,17 @@ export default function prepareSourceBuffer( log.info("Init: Attaching MediaSource URL to the media element", objectURL); videoElement.src = objectURL; + cleanUpSignal.register(() => { + resetMediaSource(videoElement, mediaSource, objectURL); + }); mediaSource.addEventListener("sourceopen", onSourceOpen); mediaSource.addEventListener("webkitsourceopen", onSourceOpen); - cleanUpSignal.register(() => { + return () => { mediaSource.removeEventListener("sourceopen", onSourceOpen); mediaSource.removeEventListener("webkitsourceopen", onSourceOpen); - resetMediaSource(videoElement, mediaSource, objectURL); - }); + }; function onSourceOpen() { try { diff --git a/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts b/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts index 2f7053efdc..c4c51214f0 100644 --- a/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts +++ b/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts @@ -27,7 +27,7 @@ import { CancellationSignal } from "../../../utils/task_canceller"; * @param {Number} time * @param {Number|undefined} margin * @param {Object} cancelSignal - * @returns {Observable} + * @returns {Promise} */ export default function removeBufferAroundTime( videoElement: HTMLMediaElement, diff --git a/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts b/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts index 0392fa0cda..17d389011d 100644 --- a/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +++ b/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts @@ -243,15 +243,19 @@ export default class VideoThumbnailLoader { if (pending !== undefined) { promises.push(pending.promise); } else { - const requestCanceller = new TaskCanceller({ - cancelOn: lastRepInfo.cleaner.signal, - }); + const requestCanceller = new TaskCanceller(); + const unlinkSignal = requestCanceller + .linkToSignal(lastRepInfo.cleaner.signal); const segmentInfo = objectAssign({ segment }, content); const prom = loadAndPushSegment(segmentInfo, segmentBuffer, lastRepInfo.segmentFetcher, - requestCanceller.signal); + requestCanceller.signal) + .then(unlinkSignal, (err) => { + unlinkSignal(); + throw err; + }); const newReq = { segmentId: segment.id, canceller: requestCanceller, diff --git a/src/experimental/tools/createMetaplaylist/get_duration_from_manifest.ts b/src/experimental/tools/createMetaplaylist/get_duration_from_manifest.ts index d7cc9509bc..c81ad1088f 100644 --- a/src/experimental/tools/createMetaplaylist/get_duration_from_manifest.ts +++ b/src/experimental/tools/createMetaplaylist/get_duration_from_manifest.ts @@ -14,16 +14,10 @@ * limitations under the License. */ -import { - map, - Observable, - throwError, -} from "rxjs"; import config from "../../../config"; import { IMetaPlaylist } from "../../../parsers/manifest/metaplaylist"; import isNonEmptyString from "../../../utils/is_non_empty_string"; import request from "../../../utils/request/xhr"; -import fromCancellablePromise from "../../../utils/rx-from_cancellable_promise"; import TaskCanceller from "../../../utils/task_canceller"; const iso8601Duration = @@ -71,79 +65,77 @@ function parseDuration(val : string) : number | null { * Load manifest and get duration from it. * @param {String} url * @param {String} transport - * @returns {Observable} + * @returns {Promise.} */ -function getDurationFromManifest(url: string, - transport: "dash" | "smooth" | "metaplaylist" -): Observable { +async function getDurationFromManifest( + url: string, + transport: "dash" | "smooth" | "metaplaylist" +): Promise { if (transport !== "dash" && transport !== "smooth" && transport !== "metaplaylist") { - return throwError(() => new Error("createMetaplaylist: Unknown transport type.")); + throw new Error("createMetaplaylist: Unknown transport type."); } - const canceller = new TaskCanceller(); if (transport === "dash" || transport === "smooth") { - return fromCancellablePromise( - canceller, - () => request({ url, - responseType: "document", - timeout: config.getCurrent().DEFAULT_REQUEST_TIMEOUT, - cancelSignal: canceller.signal }) - ).pipe(map((response) => { - const { responseData } = response; - const root = responseData.documentElement; - if (transport === "dash") { - const dashDurationAttribute = root.getAttribute("mediaPresentationDuration"); - if (dashDurationAttribute === null) { - throw new Error("createMetaplaylist: No duration on DASH content."); - } - const periodElements = root.getElementsByTagName("Period"); - const firstDASHStartAttribute = periodElements[0]?.getAttribute("start"); - const firstDASHStart = - firstDASHStartAttribute !== null ? parseDuration(firstDASHStartAttribute) : - 0; - const dashDuration = parseDuration(dashDurationAttribute); - if (firstDASHStart === null || dashDuration === null) { - throw new Error("createMetaplaylist: Cannot parse " + - "the duration from a DASH content."); - } - return dashDuration - firstDASHStart; + const response = await request({ + url, + responseType: "document", + timeout: config.getCurrent().DEFAULT_REQUEST_TIMEOUT, + // We won't cancel + cancelSignal: new TaskCanceller().signal, + }); + const { responseData } = response; + const root = responseData.documentElement; + if (transport === "dash") { + const dashDurationAttribute = root.getAttribute("mediaPresentationDuration"); + if (dashDurationAttribute === null) { + throw new Error("createMetaplaylist: No duration on DASH content."); } - // smooth - const smoothDurationAttribute = root.getAttribute("Duration"); - const smoothTimeScaleAttribute = root.getAttribute("TimeScale"); - if (smoothDurationAttribute === null) { - throw new Error("createMetaplaylist: No duration on smooth content."); + const periodElements = root.getElementsByTagName("Period"); + const firstDASHStartAttribute = periodElements[0]?.getAttribute("start"); + const firstDASHStart = + firstDASHStartAttribute !== null ? parseDuration(firstDASHStartAttribute) : + 0; + const dashDuration = parseDuration(dashDurationAttribute); + if (firstDASHStart === null || dashDuration === null) { + throw new Error("createMetaplaylist: Cannot parse " + + "the duration from a DASH content."); } - const timescale = smoothTimeScaleAttribute !== null ? - parseInt(smoothTimeScaleAttribute, 10) : - 10000000; - return (parseInt(smoothDurationAttribute, 10)) / timescale; - })); + return dashDuration - firstDASHStart; + } + // smooth + const smoothDurationAttribute = root.getAttribute("Duration"); + const smoothTimeScaleAttribute = root.getAttribute("TimeScale"); + if (smoothDurationAttribute === null) { + throw new Error("createMetaplaylist: No duration on smooth content."); + } + const timescale = smoothTimeScaleAttribute !== null ? + parseInt(smoothTimeScaleAttribute, 10) : + 10000000; + return (parseInt(smoothDurationAttribute, 10)) / timescale; } // metaplaylist - return fromCancellablePromise( - canceller, - () => request({ url, - responseType: "text", - timeout: config.getCurrent().DEFAULT_REQUEST_TIMEOUT, - cancelSignal: canceller.signal }) - ).pipe(map((response) => { - const { responseData } = response; - const metaplaylist = JSON.parse(responseData) as IMetaPlaylist; - if (metaplaylist.contents === undefined || - metaplaylist.contents.length === undefined || - metaplaylist.contents.length === 0) { - throw new Error("createMetaplaylist: No duration on Metaplaylist content."); - } - const { contents } = metaplaylist; - const lastEnd = contents[contents.length - 1].endTime; - const firstStart = contents[0].startTime; - return lastEnd - firstStart; - })); + const response = await request({ + url, + responseType: "text", + timeout: config.getCurrent().DEFAULT_REQUEST_TIMEOUT, + // We won't cancel + cancelSignal: new TaskCanceller().signal, + }); + const { responseData } = response; + const metaplaylist = JSON.parse(responseData) as IMetaPlaylist; + if (metaplaylist.contents === undefined || + metaplaylist.contents.length === undefined || + metaplaylist.contents.length === 0) { + throw new Error("createMetaplaylist: No duration on Metaplaylist content."); + } + const { contents } = metaplaylist; + const lastEnd = contents[contents.length - 1].endTime; + const firstStart = contents[0].startTime; + return lastEnd - firstStart; } export default getDurationFromManifest; diff --git a/src/experimental/tools/createMetaplaylist/index.ts b/src/experimental/tools/createMetaplaylist/index.ts index 66bd7857a9..df44c01f7b 100644 --- a/src/experimental/tools/createMetaplaylist/index.ts +++ b/src/experimental/tools/createMetaplaylist/index.ts @@ -14,12 +14,6 @@ * limitations under the License. */ -import { - combineLatest as observableCombineLatest, - lastValueFrom, - map, - of as observableOf, -} from "rxjs"; import { IMetaPlaylist } from "../../../parsers/manifest/metaplaylist"; import getDurationFromManifest from "./get_duration_from_manifest"; @@ -39,43 +33,40 @@ function createMetaplaylist( timeOffset?: number ): Promise { const playlistStartTime = timeOffset ?? 0; - const completeContentsInfos$ = contentsInfos.map((contentInfos) => { + const completeContentsInfoProms = contentsInfos.map((contentInfos) => { const { url, transport, duration } = contentInfos; if (duration !== undefined) { - return observableOf({ url, - transport, - duration }); + return Promise.resolve({ url, + transport, + duration }); } - return getDurationFromManifest(url, transport).pipe( - map((manifestDuration) => { - return { url, - duration: manifestDuration, - transport }; - }) - ); + return getDurationFromManifest(url, transport).then((manifestDuration) => { + return { url, + duration: manifestDuration, + transport }; + }); }); - return lastValueFrom(observableCombineLatest(completeContentsInfos$).pipe( - map((completeContentsInfos) : IMetaPlaylist => { - const contents = completeContentsInfos - .reduce((acc: Array<{ url: string; - transport: "dash" | "smooth" | "metaplaylist"; - startTime: number; - endTime: number; }>, - val) => { - const lastElement = acc[acc.length - 1]; - const startTime = lastElement?.endTime ?? playlistStartTime; - acc.push({ url: val.url, - transport: val.transport, - startTime, - endTime: startTime + val.duration }); - return acc; - }, []); - return { type: "MPL" as const, - version: "0.1", - dynamic: false, - contents }; - }))); + return Promise.all(completeContentsInfoProms).then((completeContentsInfos) => { + const contents = completeContentsInfos + .reduce((acc: Array<{ url: string; + transport: "dash" | "smooth" | "metaplaylist"; + startTime: number; + endTime: number; }>, + val) => { + const lastElement = acc[acc.length - 1]; + const startTime = lastElement?.endTime ?? playlistStartTime; + acc.push({ url: val.url, + transport: val.transport, + startTime, + endTime: startTime + val.duration }); + return acc; + }, []); + return { type: "MPL" as const, + version: "0.1", + dynamic: false, + contents }; + }); } export default createMetaplaylist; diff --git a/src/features/__tests__/initialize_features.test.ts b/src/features/__tests__/initialize_features.test.ts index c95b361908..ebe96dc233 100644 --- a/src/features/__tests__/initialize_features.test.ts +++ b/src/features/__tests__/initialize_features.test.ts @@ -48,6 +48,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 0, NATIVE_TTML: 0, @@ -78,6 +79,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 1, LOCAL_MANIFEST: 1, METAPLAYLIST: 1, + DEBUG_ELEMENT: 1, NATIVE_SAMI: 1, NATIVE_SRT: 1, NATIVE_TTML: 1, @@ -114,8 +116,9 @@ describe("Features - initializeFeaturesObject", () => { wasm: null, }, ContentDecryptor: jest.requireActual("../../core/decrypt/index").default, + createDebugElement: jest.requireActual("../../core/api/debug").default, directfile: { - initDirectFile: jest.requireActual("../../core/init/initialize_directfile").default, + initDirectFile: jest.requireActual("../../core/init/directfile_content_initializer").default, mediaElementTrackChoiceManager: jest.requireActual( "../../core/api/tracks_management/media_element_track_choice_manager" @@ -161,6 +164,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 1, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 0, NATIVE_TTML: 0, @@ -203,6 +207,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 0, NATIVE_TTML: 0, @@ -245,6 +250,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 0, NATIVE_TTML: 0, @@ -287,6 +293,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 0, NATIVE_TTML: 0, @@ -329,6 +336,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 0, NATIVE_TTML: 0, @@ -371,6 +379,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 1, NATIVE_SRT: 0, NATIVE_TTML: 0, @@ -413,6 +422,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 0, NATIVE_TTML: 1, @@ -455,6 +465,7 @@ describe("Features - initializeFeaturesObject", () => { HTML_VTT: 0, LOCAL_MANIFEST: 0, METAPLAYLIST: 0, + DEBUG_ELEMENT: 0, NATIVE_SAMI: 0, NATIVE_SRT: 1, NATIVE_TTML: 0, diff --git a/src/features/features_object.ts b/src/features/features_object.ts index f6d8dea72f..f0103b360c 100644 --- a/src/features/features_object.ts +++ b/src/features/features_object.ts @@ -22,6 +22,7 @@ import { IFeaturesObject } from "./types"; */ const features : IFeaturesObject = { dashParsers: { wasm: null, js: null }, + createDebugElement: null, directfile: null, ContentDecryptor: null, htmlTextTracksBuffer: null, diff --git a/src/features/initialize_features.ts b/src/features/initialize_features.ts index 645ae350f8..caef3da808 100644 --- a/src/features/initialize_features.ts +++ b/src/features/initialize_features.ts @@ -37,10 +37,11 @@ export default function initializeFeaturesObject() : void { } // Feature switching the Native TextTrack implementation - const HAS_NATIVE_MODE = __FEATURES__.NATIVE_VTT || - __FEATURES__.NATIVE_SAMI || - __FEATURES__.NATIVE_TTML || - __FEATURES__.NATIVE_SRT; + const HAS_NATIVE_MODE = + __FEATURES__.NATIVE_VTT === __FEATURES__.IS_ENABLED as number || + __FEATURES__.NATIVE_SAMI === __FEATURES__.IS_ENABLED as number || + __FEATURES__.NATIVE_TTML === __FEATURES__.IS_ENABLED as number || + __FEATURES__.NATIVE_SRT === __FEATURES__.IS_ENABLED as number; if (__FEATURES__.SMOOTH === __FEATURES__.IS_ENABLED as number) { features.transports.smooth = require("../transports/smooth/index.ts").default; @@ -57,8 +58,11 @@ export default function initializeFeaturesObject() : void { features.transports.metaplaylist = require("../transports/metaplaylist/index.ts").default; } + if (__FEATURES__.DEBUG_ELEMENT === __FEATURES__.IS_ENABLED as number) { + features.createDebugElement = require("../core/api/debug/index.ts").default; + } - if (HAS_NATIVE_MODE === __FEATURES__.IS_ENABLED as number) { + if (HAS_NATIVE_MODE) { features.nativeTextTracksBuffer = require("../core/segment_buffers/implementations/text/native/index.ts").default; if (__FEATURES__.NATIVE_VTT === __FEATURES__.IS_ENABLED as number) { @@ -83,12 +87,13 @@ export default function initializeFeaturesObject() : void { } // Feature switching the HTML TextTrack implementation - const HAS_HTML_MODE = __FEATURES__.HTML_VTT || - __FEATURES__.HTML_SAMI || - __FEATURES__.HTML_TTML || - __FEATURES__.HTML_SRT; + const HAS_HTML_MODE = + __FEATURES__.HTML_VTT === __FEATURES__.IS_ENABLED as number || + __FEATURES__.HTML_SAMI === __FEATURES__.IS_ENABLED as number || + __FEATURES__.HTML_TTML === __FEATURES__.IS_ENABLED as number || + __FEATURES__.HTML_SRT === __FEATURES__.IS_ENABLED as number; - if (HAS_HTML_MODE === __FEATURES__.IS_ENABLED as number) { + if (HAS_HTML_MODE) { features.htmlTextTracksBuffer = require("../core/segment_buffers/implementations/text/html/index.ts").default; if (__FEATURES__.HTML_SAMI === __FEATURES__.IS_ENABLED as number) { @@ -113,7 +118,8 @@ export default function initializeFeaturesObject() : void { } if (__FEATURES__.DIRECTFILE === __FEATURES__.IS_ENABLED as number) { - const initDirectFile = require("../core/init/initialize_directfile.ts").default; + const initDirectFile = + require("../core/init/directfile_content_initializer.ts").default; const mediaElementTrackChoiceManager = require("../core/api/tracks_management/media_element_track_choice_manager.ts") .default; diff --git a/src/features/list/__tests__/directfile.test.ts b/src/features/list/__tests__/directfile.test.ts index 9b010bae51..a8305a6afb 100644 --- a/src/features/list/__tests__/directfile.test.ts +++ b/src/features/list/__tests__/directfile.test.ts @@ -19,10 +19,11 @@ // eslint-disable-next-line max-len import mediaElementTrackChoiceManager from "../../../core/api/tracks_management/media_element_track_choice_manager"; -import initDirectFile from "../../../core/init/initialize_directfile"; +import initDirectFile from "../../../core/init/directfile_content_initializer"; import addDirectfileFeature from "../directfile"; -jest.mock("../../../core/init/initialize_directfile", () => ({ +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +jest.mock("../../../core/init/directfile_content_initializer", () => ({ __esModule: true as const, default: jest.fn(), })); diff --git a/src/features/list/directfile.ts b/src/features/list/directfile.ts index a868a42e34..2ae2adc4c6 100644 --- a/src/features/list/directfile.ts +++ b/src/features/list/directfile.ts @@ -16,7 +16,7 @@ // eslint-disable-next-line max-len import mediaElementTrackChoiceManager from "../../core/api/tracks_management/media_element_track_choice_manager"; -import directfile from "../../core/init/initialize_directfile"; +import directfile from "../../core/init/directfile_content_initializer"; import { IFeaturesObject } from "../types"; /** diff --git a/src/features/types.ts b/src/features/types.ts index a614763c0e..3fbeff7fec 100644 --- a/src/features/types.ts +++ b/src/features/types.ts @@ -14,14 +14,11 @@ * limitations under the License. */ -import { Observable } from "rxjs"; +import RxPlayer from "../core/api"; // eslint-disable-next-line max-len import MediaElementTrackChoiceManager from "../core/api/tracks_management/media_element_track_choice_manager"; import type ContentDecryptor from "../core/decrypt"; -import { - IDirectfileEvent, - IDirectFileOptions, -} from "../core/init/initialize_directfile"; +import DirectFileContentInitializer from "../core/init/directfile_content_initializer"; import { SegmentBuffer } from "../core/segment_buffers"; import { IDashParserResponse, @@ -33,9 +30,9 @@ import { INativeTextTracksParserFn, } from "../parsers/texttracks"; import { ITransportFunction } from "../transports"; +import { CancellationSignal } from "../utils/task_canceller"; -export type IDirectFileInit = (args : IDirectFileOptions) => - Observable; +export type IDirectFileInit = typeof DirectFileContentInitializer; export type IContentDecryptorClass = typeof ContentDecryptor; @@ -83,6 +80,13 @@ export interface IFeaturesObject { mediaElementTrackChoiceManager : IMediaElementTrackChoiceManager; } | null; ContentDecryptor : IContentDecryptorClass|null; + createDebugElement : ( + ( + parentElt : HTMLElement, + instance : RxPlayer, + cancelSignal : CancellationSignal + ) => void + ) | null; htmlTextTracksBuffer : IHTMLTextTracksBuffer|null; htmlTextTracksParsers : Partial>; imageBuffer : IImageBuffer|null; diff --git a/src/manifest/__tests__/manifest.test.ts b/src/manifest/__tests__/manifest.test.ts index cda69eb08b..9faaa611ec 100644 --- a/src/manifest/__tests__/manifest.test.ts +++ b/src/manifest/__tests__/manifest.test.ts @@ -353,18 +353,18 @@ describe("Manifest - Manifest", () => { const fakePeriod = jest.fn((period) => ({ ...period, id: `foo${period.id}`, contentWarnings: [new Error(period.id)] })); - const fakeUpdatePeriodInPlace = jest.fn((oldPeriod, newPeriod) => { - Object.keys(oldPeriod).forEach(key => { - delete oldPeriod[key]; - }); - oldPeriod.id = newPeriod.id; - oldPeriod.start = newPeriod.start; - oldPeriod.adaptations = newPeriod.adaptations; - }); + const fakeReplacePeriodsRes = { + updatedPeriods: [], + addedPeriods: [], + removedPeriods: [], + }; + const fakeReplacePeriods = jest.fn(() => fakeReplacePeriodsRes); jest.mock("../period", () => ({ __esModule: true as const, default: fakePeriod })); - jest.mock("../update_period_in_place", () => ({ __esModule: true as const, - default: fakeUpdatePeriodInPlace })); + jest.mock("../update_periods", () => ({ + __esModule: true as const, + replacePeriods: fakeReplacePeriods, + })); const oldManifestArgs = { availabilityStartTime: 5, duration: 12, @@ -391,8 +391,6 @@ describe("Manifest - Manifest", () => { const mockTrigger = jest.spyOn(manifest, "trigger").mockImplementation(jest.fn()); - const [oldPeriod1, oldPeriod2] = manifest.periods; - const newAdaptations = {}; const newPeriod1 = { id: "foo0", start: 4, adaptations: {} }; const newPeriod2 = { id: "foo1", start: 12, adaptations: {} }; @@ -416,364 +414,15 @@ describe("Manifest - Manifest", () => { uris: ["url3", "url4"] }; manifest.replace(newManifest); - expect(manifest.adaptations).toEqual(newAdaptations); - expect(manifest.availabilityStartTime).toEqual(6); - expect(manifest.id).toEqual("fakeId"); - expect(manifest.isDynamic).toEqual(true); - expect(manifest.isLive).toEqual(true); - expect(manifest.lifetime).toEqual(14); - expect(manifest.contentWarnings).toEqual([new Error("c"), new Error("d")]); - expect(manifest.getMinimumSafePosition()).toEqual(40 - 5); - expect(manifest.getMaximumSafePosition()).toEqual(40); - expect(manifest.suggestedPresentationDelay).toEqual(100); - expect(manifest.uris).toEqual(["url3", "url4"]); - - expect(manifest.periods).toEqual([newPeriod1, newPeriod2]); - - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(2); - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledWith(oldPeriod1, newPeriod1, 0); - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledWith(oldPeriod2, newPeriod2, 0); + expect(fakeReplacePeriods).toHaveBeenCalledTimes(1); + expect(fakeReplacePeriods) + .toHaveBeenCalledWith(manifest.periods, newManifest.periods); expect(mockTrigger).toHaveBeenCalledTimes(1); - expect(mockTrigger).toHaveBeenCalledWith("manifestUpdate", null); + expect(mockTrigger).toHaveBeenCalledWith("manifestUpdate", fakeReplacePeriodsRes); expect(fakeIdGenerator).toHaveBeenCalledTimes(2); expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); expect(fakeLogger.info).not.toHaveBeenCalled(); expect(fakeLogger.warn).not.toHaveBeenCalled(); mockTrigger.mockRestore(); }); - - it("should prepend older Periods when calling `replace`", () => { - const oldManifestArgs = { availabilityStartTime: 5, - duration: 12, - id: "man", - isDynamic: false, - isLive: false, - lifetime: 13, - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { - isLinear: false, - maximumSafePosition: 10, - time: 10, - } }, - contentWarnings: [new Error("a"), new Error("b")], - periods: [{ id: "1", start: 4, adaptations: {} }], - suggestedPresentationDelay: 99, - uris: ["url1", "url2"] }; - - const fakePeriod = jest.fn((period) => { - return { - ...period, - id: `foo${period.id}`, - contentWarnings: [new Error(period.id)], - }; - }); - const fakeUpdatePeriodInPlace = jest.fn((oldPeriod, newPeriod) => { - Object.keys(oldPeriod).forEach(key => { - delete oldPeriod[key]; - }); - oldPeriod.id = newPeriod.id; - oldPeriod.start = newPeriod.start; - oldPeriod.adaptations = newPeriod.adaptations; - oldPeriod.contentWarnings = newPeriod.contentWarnings; - }); - jest.mock("../period", () => ({ __esModule: true as const, - default: fakePeriod })); - jest.mock("../update_period_in_place", () => ({ - __esModule: true as const, - default: fakeUpdatePeriodInPlace, - })); - const Manifest = jest.requireActual("../manifest").default; - const manifest = new Manifest(oldManifestArgs, {}); - const [oldPeriod1] = manifest.periods; - - const mockTrigger = jest.spyOn(manifest, "trigger").mockImplementation(jest.fn()); - - const newPeriod1 = { id: "pre0", - start: 0, - adaptations: {}, - contentWarnings: [] }; - const newPeriod2 = { id: "pre1", - start: 2, - adaptations: {}, - contentWarnings: [] }; - const newPeriod3 = { id: "foo1", - start: 4, - adaptations: {}, - contentWarnings: [] }; - const newManifest = { adaptations: {}, - availabilityStartTime: 6, - id: "man2", - isDynamic: false, - isLive: true, - lifetime: 14, - contentWarnings: [ new Error("c"), - new Error("d") ], - suggestedPresentationDelay: 100, - periods: [ newPeriod1, - newPeriod2, - newPeriod3 ], - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { isLinear: false, - maximumSafePosition: 10, - time: 10 } }, - uris: ["url3", "url4"] }; - - manifest.replace(newManifest); - - expect(manifest.periods).toEqual([newPeriod1, newPeriod2, newPeriod3]); - - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(1); - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledWith(oldPeriod1, newPeriod3, 0); - expect(mockTrigger).toHaveBeenCalledTimes(1); - expect(mockTrigger).toHaveBeenCalledWith("manifestUpdate", null); - expect(fakeIdGenerator).toHaveBeenCalledTimes(2); - expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); - // expect(fakeLogger.info).toHaveBeenCalledTimes(2); - // expect(fakeLogger.info).toHaveBeenCalledWith( - // "Manifest: Adding new Period pre0 after update."); - // expect(fakeLogger.info).toHaveBeenCalledWith( - // "Manifest: Adding new Period pre1 after update."); - mockTrigger.mockRestore(); - }); - - it("should append newer Periods when calling `replace`", () => { - const oldManifestArgs = { availabilityStartTime: 5, - duration: 12, - id: "man", - isDynamic: false, - isLive: false, - lifetime: 13, - contentWarnings: [new Error("a"), new Error("b")], - periods: [{ id: "1" }], - suggestedPresentationDelay: 99, - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { - isLinear: false, - maximumSafePosition: 10, - time: 10, - } }, - uris: ["url1", "url2"] }; - - const fakePeriod = jest.fn((period) => { - return { ...period, - id: `foo${period.id}`, - contentWarnings: [new Error(period.id)] }; - }); - const fakeUpdatePeriodInPlace = jest.fn((oldPeriod, newPeriod) => { - Object.keys(oldPeriod).forEach(key => { - delete oldPeriod[key]; - }); - oldPeriod.id = newPeriod.id; - }); - jest.mock("../period", () => ({ __esModule: true as const, - default: fakePeriod })); - jest.mock("../update_period_in_place", () => ({ - __esModule: true as const, - default: fakeUpdatePeriodInPlace, - })); - const Manifest = jest.requireActual("../manifest").default; - const manifest = new Manifest(oldManifestArgs, {}); - const [oldPeriod1] = manifest.periods; - - const mockTrigger = jest.spyOn(manifest, "trigger").mockImplementation(jest.fn()); - - const newPeriod1 = { id: "foo1" }; - const newPeriod2 = { id: "post0" }; - const newPeriod3 = { id: "post1" }; - const newManifest = { adaptations: {}, - availabilityStartTime: 6, - id: "man2", - isDynamic: false, - isLive: true, - lifetime: 14, - contentWarnings: [new Error("c"), new Error("d")], - suggestedPresentationDelay: 100, - periods: [newPeriod1, newPeriod2, newPeriod3], - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { isLinear: false, - maximumSafePosition: 10, - time: 10 } }, - uris: ["url3", "url4"] }; - - manifest.replace(newManifest); - - expect(manifest.periods).toEqual([newPeriod1, newPeriod2, newPeriod3]); - - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(1); - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledWith(oldPeriod1, newPeriod1, 0); - expect(mockTrigger).toHaveBeenCalledTimes(1); - expect(mockTrigger).toHaveBeenCalledWith("manifestUpdate", null); - expect(fakeIdGenerator).toHaveBeenCalledTimes(2); - expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); - // expect(fakeLogger.warn).toHaveBeenCalledTimes(1); - // expect(fakeLogger.warn) - // .toHaveBeenCalledWith("Manifest: Adding new Periods after update."); - mockTrigger.mockRestore(); - }); - - it("should replace different Periods when calling `replace`", () => { - const oldManifestArgs = { availabilityStartTime: 5, - duration: 12, - id: "man", - isDynamic: false, - isLive: false, - lifetime: 13, - contentWarnings: [new Error("a"), new Error("b")], - periods: [{ id: "1" }], - suggestedPresentationDelay: 99, - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { - isLinear: false, - maximumSafePosition: 10, - time: 10, - } }, - uris: ["url1", "url2"] }; - - const fakePeriod = jest.fn((period) => { - return { ...period, - id: `foo${period.id}`, - contentWarnings: [new Error(period.id)] }; - }); - const fakeUpdatePeriodInPlace = jest.fn((oldPeriod, newPeriod) => { - Object.keys(oldPeriod).forEach(key => { - delete oldPeriod[key]; - }); - oldPeriod.id = newPeriod.id; - }); - jest.mock("../period", () => ({ __esModule: true as const, - default: fakePeriod })); - jest.mock("../update_period_in_place", () => ({ - __esModule: true as const, - default: fakeUpdatePeriodInPlace, - })); - const Manifest = jest.requireActual("../manifest").default; - const manifest = new Manifest(oldManifestArgs, {}); - - const mockTrigger = jest.spyOn(manifest, "trigger").mockImplementation(jest.fn()); - - const newPeriod1 = { id: "diff0" }; - const newPeriod2 = { id: "diff1" }; - const newPeriod3 = { id: "diff2" }; - const newManifest = { adaptations: {}, - availabilityStartTime: 6, - id: "man2", - isDynamic: false, - isLive: true, - lifetime: 14, - contentWarnings: [new Error("c"), new Error("d")], - suggestedPresentationDelay: 100, - periods: [newPeriod1, newPeriod2, newPeriod3], - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { isLinear: false, - maximumSafePosition: 10, - time: 10 } }, - uris: ["url3", "url4"] }; - - manifest.replace(newManifest); - - expect(manifest.periods).toEqual([newPeriod1, newPeriod2, newPeriod3]); - - expect(fakeUpdatePeriodInPlace).not.toHaveBeenCalled(); - expect(mockTrigger).toHaveBeenCalledTimes(1); - expect(mockTrigger).toHaveBeenCalledWith("manifestUpdate", null); - expect(fakeIdGenerator).toHaveBeenCalledTimes(2); - expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); - // expect(fakeLogger.info).toHaveBeenCalledTimes(4); - mockTrigger.mockRestore(); - }); - - it("should merge overlapping Periods when calling `replace`", () => { - const oldManifestArgs = { availabilityStartTime: 5, - duration: 12, - id: "man", - isDynamic: false, - isLive: false, - lifetime: 13, - contentWarnings: [new Error("a"), new Error("b")], - periods: [{ id: "1", start: 2 }, - { id: "2", start: 4 }, - { id: "3", start: 6 }], - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { - isLinear: false, - maximumSafePosition: 10, - time: 10, - } }, - suggestedPresentationDelay: 99, - uris: ["url1", "url2"] }; - - const fakePeriod = jest.fn((period) => { - return { ...period, - id: `foo${period.id}`, - contentWarnings: [new Error(period.id)] }; - }); - const fakeUpdatePeriodInPlace = jest.fn((oldPeriod, newPeriod) => { - Object.keys(oldPeriod).forEach(key => { - delete oldPeriod[key]; - }); - oldPeriod.id = newPeriod.id; - oldPeriod.start = newPeriod.start; - }); - jest.mock("../period", () => ({ __esModule: true as const, - default: fakePeriod })); - jest.mock("../update_period_in_place", () => ({ - __esModule: true as const, - default: fakeUpdatePeriodInPlace, - })); - const Manifest = jest.requireActual("../manifest").default; - const manifest = new Manifest(oldManifestArgs, {}); - const [oldPeriod1, oldPeriod2] = manifest.periods; - - const mockTrigger = jest.spyOn(manifest, "trigger").mockImplementation(jest.fn()); - - const newPeriod1 = { id: "pre0", start: 0 }; - const newPeriod2 = { id: "foo1", start: 2 }; - const newPeriod3 = { id: "diff0", start: 3 }; - const newPeriod4 = { id: "foo2", start: 4 }; - const newPeriod5 = { id: "post0", start: 5 }; - const newManifest = { adaptations: {}, - availabilityStartTime: 6, - id: "man2", - isDynamic: false, - isLive: true, - lifetime: 14, - contentWarnings: [new Error("c"), new Error("d")], - suggestedPresentationDelay: 100, - periods: [ newPeriod1, - newPeriod2, - newPeriod3, - newPeriod4, - newPeriod5 ], - timeBounds: { minimumSafePosition: 0, - timeshiftDepth: null, - maximumTimeData: { isLinear: false, - maximumSafePosition: 10, - time: 10 } }, - uris: ["url3", "url4"] }; - - manifest.replace(newManifest); - - expect(manifest.periods).toEqual([ newPeriod1, - newPeriod2, - newPeriod3, - newPeriod4, - newPeriod5 ]); - - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(2); - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledWith(oldPeriod1, newPeriod2, 0); - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledWith(oldPeriod2, newPeriod4, 0); - expect(mockTrigger).toHaveBeenCalledTimes(1); - expect(mockTrigger).toHaveBeenCalledWith("manifestUpdate", null); - expect(fakeIdGenerator).toHaveBeenCalledTimes(2); - expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); - // expect(fakeLogger.info).toHaveBeenCalledTimes(5); - mockTrigger.mockRestore(); - }); }); diff --git a/src/manifest/__tests__/update_period_in_place.test.ts b/src/manifest/__tests__/update_period_in_place.test.ts index f611128c67..d09606c5ed 100644 --- a/src/manifest/__tests__/update_period_in_place.test.ts +++ b/src/manifest/__tests__/update_period_in_place.test.ts @@ -155,14 +155,17 @@ describe("Manifest - updatePeriodInPlace", () => { it("should fully update the first Period given by the second one in a full update", () => { /* eslint-enable max-len */ const oldVideoAdaptation1 = { contentWarnings: [], + type: "video", id: "ada-video-1", representations: [oldVideoRepresentation1, oldVideoRepresentation2] }; const oldVideoAdaptation2 = { contentWarnings: [], + type: "video", id: "ada-video-2", representations: [oldVideoRepresentation3, oldVideoRepresentation4] }; const oldAudioAdaptation = { contentWarnings: [], + type: "audio", id: "ada-audio-1", representations: [oldAudioRepresentation] }; const oldPeriod = { @@ -180,14 +183,17 @@ describe("Manifest - updatePeriodInPlace", () => { }, }; const newVideoAdaptation1 = { contentWarnings: [], + type: "video", id: "ada-video-1", representations: [newVideoRepresentation1, newVideoRepresentation2] }; const newVideoAdaptation2 = { contentWarnings: [], + type: "video", id: "ada-video-2", representations: [newVideoRepresentation3, newVideoRepresentation4] }; const newAudioAdaptation = { contentWarnings: [], + type: "audio", id: "ada-audio-1", representations: [newAudioRepresentation] }; const newPeriod = { @@ -209,9 +215,40 @@ describe("Manifest - updatePeriodInPlace", () => { const newPeriodAdaptations = jest.spyOn(newPeriod, "getAdaptations"); const mockLog = jest.spyOn(log, "warn"); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Full); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Full); + + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + oldVideoRepresentation2, + ], + }, + { + adaptation: oldVideoAdaptation2, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation3, + oldVideoRepresentation4, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); expect(oldPeriod.start).toEqual(500); expect(oldPeriod.end).toEqual(520); @@ -260,7 +297,7 @@ describe("Manifest - updatePeriodInPlace", () => { expect(mockNewVideoRepresentation4Update).not.toHaveBeenCalled(); expect(mockNewAudioRepresentationUpdate).not.toHaveBeenCalled(); - expect(mockLog).not.toHaveBeenCalled(); + expect(mockLog).toHaveBeenCalledTimes(0); mockLog.mockRestore(); }); @@ -269,14 +306,17 @@ describe("Manifest - updatePeriodInPlace", () => { /* eslint-enable max-len */ const oldVideoAdaptation1 = { contentWarnings: [], id: "ada-video-1", + type: "video", representations: [oldVideoRepresentation1, oldVideoRepresentation2] }; const oldVideoAdaptation2 = { contentWarnings: [], id: "ada-video-2", + type: "video", representations: [oldVideoRepresentation3, oldVideoRepresentation4] }; const oldAudioAdaptation = { contentWarnings: [], id: "ada-audio-1", + type: "audio", representations: [oldAudioRepresentation] }; const oldPeriod = { contentWarnings: [], @@ -294,14 +334,17 @@ describe("Manifest - updatePeriodInPlace", () => { }; const newVideoAdaptation1 = { contentWarnings: [], id: "ada-video-1", + type: "video", representations: [newVideoRepresentation1, newVideoRepresentation2] }; const newVideoAdaptation2 = { contentWarnings: [], id: "ada-video-2", + type: "video", representations: [newVideoRepresentation3, newVideoRepresentation4] }; const newAudioAdaptation = { contentWarnings: [], id: "ada-audio-1", + type: "audio", representations: [newAudioRepresentation] }; const newPeriod = { contentWarnings: [], @@ -322,9 +365,39 @@ describe("Manifest - updatePeriodInPlace", () => { const mockNewPeriodGetAdaptations = jest.spyOn(newPeriod, "getAdaptations"); const mockLog = jest.spyOn(log, "warn"); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Partial); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Partial); + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + oldVideoRepresentation2, + ], + }, + { + adaptation: oldVideoAdaptation2, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation3, + oldVideoRepresentation4, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); expect(oldPeriod.start).toEqual(500); expect(oldPeriod.end).toEqual(520); @@ -373,16 +446,18 @@ describe("Manifest - updatePeriodInPlace", () => { expect(mockNewVideoRepresentation4Replace).not.toHaveBeenCalled(); expect(mockNewAudioRepresentationReplace).not.toHaveBeenCalled(); - expect(mockLog).not.toHaveBeenCalled(); + expect(mockLog).toHaveBeenCalledTimes(0); mockLog.mockRestore(); }); - it("should do nothing with new Adaptations", () => { + it("should add new Adaptations in Full mode", () => { const oldVideoAdaptation1 = { contentWarnings: [], + type: "video", id: "ada-video-1", representations: [oldVideoRepresentation1, oldVideoRepresentation2] }; const oldAudioAdaptation = { contentWarnings: [], + type: "audio", id: "ada-audio-1", representations: [oldAudioRepresentation] }; const oldPeriod = { @@ -399,14 +474,17 @@ describe("Manifest - updatePeriodInPlace", () => { }; const newVideoAdaptation1 = { contentWarnings: [], id: "ada-video-1", + type: "video", representations: [newVideoRepresentation1, newVideoRepresentation2] }; const newVideoAdaptation2 = { contentWarnings: [], id: "ada-video-2", + type: "video", representations: [newVideoRepresentation3, newVideoRepresentation4] }; const newAudioAdaptation = { contentWarnings: [], id: "ada-audio-1", + type: "audio", representations: [newAudioRepresentation] }; const newPeriod = { contentWarnings: [], @@ -424,36 +502,144 @@ describe("Manifest - updatePeriodInPlace", () => { }; const mockLog = jest.spyOn(log, "warn"); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Full); - expect(mockLog).not.toHaveBeenCalled(); - expect(oldPeriod.adaptations.video).toHaveLength(1); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Partial); - expect(mockLog).not.toHaveBeenCalled(); - expect(oldPeriod.adaptations.video).toHaveLength(1); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Full); + expect(res).toEqual({ addedAdaptations: [newVideoAdaptation2], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + oldVideoRepresentation2, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); + expect(mockLog).toHaveBeenCalled(); + expect(mockLog).toHaveBeenNthCalledWith( + 1, + "Manifest: 1 new Adaptations found when merging." + ); + expect(oldPeriod.adaptations.video).toHaveLength(2); mockLog.mockRestore(); }); - it("should warn if an old Adaptation is not found", () => { + it("should add new Adaptations in Partial mode", () => { const oldVideoAdaptation1 = { contentWarnings: [], + type: "video", id: "ada-video-1", representations: [oldVideoRepresentation1, oldVideoRepresentation2] }; - const oldVideoAdaptation2 = { contentWarnings: [], - id: "ada-video-2", - representations: [oldVideoRepresentation3, - oldVideoRepresentation4] }; const oldAudioAdaptation = { contentWarnings: [], + type: "audio", id: "ada-audio-1", representations: [oldAudioRepresentation] }; const oldPeriod = { + contentWarnings: [], + start: 5, + end: 15, + duration: 10, + adaptations: { video: [oldVideoAdaptation1], + audio: [oldAudioAdaptation] }, + getAdaptations() { + return [oldVideoAdaptation1, + oldAudioAdaptation]; + }, + }; + const newVideoAdaptation1 = { contentWarnings: [], + id: "ada-video-1", + type: "video", + representations: [newVideoRepresentation1, + newVideoRepresentation2] }; + const newVideoAdaptation2 = { contentWarnings: [], + id: "ada-video-2", + type: "video", + representations: [newVideoRepresentation3, + newVideoRepresentation4] }; + const newAudioAdaptation = { contentWarnings: [], + id: "ada-audio-1", + type: "audio", + representations: [newAudioRepresentation] }; + const newPeriod = { contentWarnings: [], start: 500, end: 520, duration: 20, + adaptations: { video: [newVideoAdaptation1, + newVideoAdaptation2], + audio: [newAudioAdaptation] }, + getAdaptations() { + return [newVideoAdaptation1, + newVideoAdaptation2, + newAudioAdaptation]; + }, + }; + + const mockLog = jest.spyOn(log, "warn"); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Partial); + expect(res).toEqual({ addedAdaptations: [newVideoAdaptation2], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + oldVideoRepresentation2, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); + expect(mockLog).toHaveBeenCalled(); + expect(mockLog).toHaveBeenNthCalledWith( + 1, + "Manifest: 1 new Adaptations found when merging." + ); + expect(oldPeriod.adaptations.video).toHaveLength(2); + mockLog.mockRestore(); + }); + + it("should remove unfound Adaptations in Full mode", () => { + const oldVideoAdaptation1 = { contentWarnings: [], + type: "video", + id: "ada-video-1", + representations: [oldVideoRepresentation1, + oldVideoRepresentation2] }; + const oldVideoAdaptation2 = { contentWarnings: [], + id: "ada-video-2", + type: "video", + representations: [newVideoRepresentation3, + newVideoRepresentation4] }; + const oldAudioAdaptation = { contentWarnings: [], + type: "audio", + id: "ada-audio-1", + representations: [oldAudioRepresentation] }; + const oldPeriod = { + contentWarnings: [], + start: 5, + end: 15, + duration: 10, adaptations: { video: [oldVideoAdaptation1, oldVideoAdaptation2], audio: [oldAudioAdaptation] }, @@ -465,49 +651,223 @@ describe("Manifest - updatePeriodInPlace", () => { }; const newVideoAdaptation1 = { contentWarnings: [], id: "ada-video-1", + type: "video", representations: [newVideoRepresentation1, newVideoRepresentation2] }; const newAudioAdaptation = { contentWarnings: [], id: "ada-audio-1", + type: "audio", representations: [newAudioRepresentation] }; const newPeriod = { + contentWarnings: [], + start: 500, + end: 520, + duration: 20, + adaptations: { video: [newVideoAdaptation1], + audio: [newAudioAdaptation] }, + getAdaptations() { + return [newVideoAdaptation1, + newAudioAdaptation]; + }, + }; + + const mockLog = jest.spyOn(log, "warn"); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Full); + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [oldVideoAdaptation2], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + oldVideoRepresentation2, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); + expect(mockLog).toHaveBeenCalled(); + expect(mockLog).toHaveBeenNthCalledWith( + 1, + "Manifest: Adaptation \"ada-video-2\" not found when merging." + ); + expect(oldPeriod.adaptations.video).toHaveLength(2); + mockLog.mockRestore(); + }); + + it("should remove unfound Adaptations in Partial mode", () => { + const oldVideoAdaptation1 = { contentWarnings: [], + type: "video", + id: "ada-video-1", + representations: [oldVideoRepresentation1, + oldVideoRepresentation2] }; + const oldVideoAdaptation2 = { contentWarnings: [], + id: "ada-video-2", + type: "video", + representations: [newVideoRepresentation3, + newVideoRepresentation4] }; + const oldAudioAdaptation = { contentWarnings: [], + type: "audio", + id: "ada-audio-1", + representations: [oldAudioRepresentation] }; + const oldPeriod = { contentWarnings: [], start: 5, end: 15, duration: 10, + adaptations: { video: [oldVideoAdaptation1, + oldVideoAdaptation2], + audio: [oldAudioAdaptation] }, + getAdaptations() { + return [oldVideoAdaptation1, + oldVideoAdaptation2, + oldAudioAdaptation]; + }, + }; + const newVideoAdaptation1 = { contentWarnings: [], + id: "ada-video-1", + type: "video", + representations: [newVideoRepresentation1, + newVideoRepresentation2] }; + const newAudioAdaptation = { contentWarnings: [], + id: "ada-audio-1", + type: "audio", + representations: [newAudioRepresentation] }; + const newPeriod = { + contentWarnings: [], + start: 500, + end: 520, + duration: 20, adaptations: { video: [newVideoAdaptation1], audio: [newAudioAdaptation] }, getAdaptations() { - return [newVideoAdaptation1, newAudioAdaptation]; + return [newVideoAdaptation1, + newAudioAdaptation]; }, }; const mockLog = jest.spyOn(log, "warn"); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Full); - expect(mockLog).toHaveBeenCalledTimes(1); - expect(mockLog).toHaveBeenCalledWith( + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Partial); + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [oldVideoAdaptation2], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + oldVideoRepresentation2, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); + expect(mockLog).toHaveBeenCalled(); + expect(mockLog).toHaveBeenNthCalledWith( + 1, "Manifest: Adaptation \"ada-video-2\" not found when merging." ); expect(oldPeriod.adaptations.video).toHaveLength(2); - mockLog.mockClear(); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Partial); + mockLog.mockRestore(); + }); + + it("should add new Representations in Full mode", () => { + const oldVideoAdaptation1 = { contentWarnings: [], + type: "video", + id: "ada-video-1", + representations: [oldVideoRepresentation1] }; + const oldAudioAdaptation = { contentWarnings: [], + type: "audio", + id: "ada-audio-1", + representations: [oldAudioRepresentation] }; + const oldPeriod = { contentWarnings: [], + start: 5, + end: 15, + duration: 10, + adaptations: { video: [oldVideoAdaptation1], + audio: [oldAudioAdaptation] }, + getAdaptations() { return [oldVideoAdaptation1, + oldAudioAdaptation]; } }; + + const newVideoAdaptation1 = { contentWarnings: [], + id: "ada-video-1", + type: "video", + representations: [newVideoRepresentation1, + newVideoRepresentation2] }; + const newAudioAdaptation = { contentWarnings: [], + id: "ada-audio-1", + type: "audio", + representations: [newAudioRepresentation] }; + const newPeriod = { + contentWarnings: [], + start: 500, + end: 520, + duration: 20, + adaptations: { video: [newVideoAdaptation1], + audio: [newAudioAdaptation] }, + getAdaptations() { + return [newVideoAdaptation1, newAudioAdaptation]; + }, + }; + + const mockLog = jest.spyOn(log, "warn"); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Full); + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [newVideoRepresentation2], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith( - "Manifest: Adaptation \"ada-video-2\" not found when merging." + "Manifest: 1 new Representations found when merging." ); - expect(oldPeriod.adaptations.video).toHaveLength(2); + expect(oldVideoAdaptation1.representations).toHaveLength(2); mockLog.mockRestore(); }); - it("should do nothing with new Representations", () => { + it("should add new Representations in Partial mode", () => { const oldVideoAdaptation1 = { contentWarnings: [], + type: "video", id: "ada-video-1", representations: [oldVideoRepresentation1] }; const oldAudioAdaptation = { contentWarnings: [], + type: "audio", id: "ada-audio-1", representations: [oldAudioRepresentation] }; const oldPeriod = { contentWarnings: [], @@ -521,10 +881,12 @@ describe("Manifest - updatePeriodInPlace", () => { const newVideoAdaptation1 = { contentWarnings: [], id: "ada-video-1", + type: "video", representations: [newVideoRepresentation1, newVideoRepresentation2] }; const newAudioAdaptation = { contentWarnings: [], id: "ada-audio-1", + type: "audio", representations: [newAudioRepresentation] }; const newPeriod = { contentWarnings: [], @@ -539,26 +901,46 @@ describe("Manifest - updatePeriodInPlace", () => { }; const mockLog = jest.spyOn(log, "warn"); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Full); - expect(mockLog).not.toHaveBeenCalled(); - expect(oldVideoAdaptation1.representations).toHaveLength(1); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Partial); - expect(mockLog).not.toHaveBeenCalled(); - expect(oldVideoAdaptation1.representations).toHaveLength(1); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Partial); + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [newVideoRepresentation2], + removedRepresentations: [], + updatedRepresentations: [ + oldVideoRepresentation1, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); + expect(mockLog).toHaveBeenCalledTimes(1); + expect(mockLog).toHaveBeenCalledWith( + "Manifest: 1 new Representations found when merging." + ); + expect(oldVideoAdaptation1.representations).toHaveLength(2); mockLog.mockRestore(); }); - it("should warn if an old Representation is not found", () => { + it("should remove an old Representation that is not found in Full mode", () => { const oldVideoAdaptation1 = { contentWarnings: [], id: "ada-video-1", + type: "video", representations: [oldVideoRepresentation1, oldVideoRepresentation2] }; const oldAudioAdaptation = { contentWarnings: [], id: "ada-audio-1", + type: "audio", representations: [oldAudioRepresentation] }; const oldPeriod = { contentWarnings: [], start: 500, @@ -570,9 +952,11 @@ describe("Manifest - updatePeriodInPlace", () => { oldAudioAdaptation]; } }; const newVideoAdaptation1 = { contentWarnings: [], id: "ada-video-1", + type: "video", representations: [newVideoRepresentation1] }; const newAudioAdaptation = { contentWarnings: [], id: "ada-audio-1", + type: "audio", representations: [newAudioRepresentation] }; const newPeriod = { contentWarnings: [], start: 5, @@ -587,23 +971,104 @@ describe("Manifest - updatePeriodInPlace", () => { }; const mockLog = jest.spyOn(log, "warn"); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Full); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Full); + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [oldVideoRepresentation2], + updatedRepresentations: [ + oldVideoRepresentation1, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith( "Manifest: Representation \"rep-video-2\" not found when merging." ); - expect(oldVideoAdaptation1.representations).toHaveLength(2); - mockLog.mockClear(); - updatePeriodInPlace(oldPeriod as unknown as Period, - newPeriod as unknown as Period, - MANIFEST_UPDATE_TYPE.Partial); + expect(oldVideoAdaptation1.representations).toHaveLength(1); + mockLog.mockRestore(); + }); + + it("should remove an old Representation that is not found in Partial mode", () => { + const oldVideoAdaptation1 = { contentWarnings: [], + id: "ada-video-1", + type: "video", + representations: [oldVideoRepresentation1, + oldVideoRepresentation2] }; + const oldAudioAdaptation = { contentWarnings: [], + id: "ada-audio-1", + type: "audio", + representations: [oldAudioRepresentation] }; + const oldPeriod = { contentWarnings: [], + start: 500, + end: 520, + duration: 20, + adaptations: { video: [oldVideoAdaptation1], + audio: [oldAudioAdaptation] }, + getAdaptations() { return [oldVideoAdaptation1, + oldAudioAdaptation]; } }; + const newVideoAdaptation1 = { contentWarnings: [], + id: "ada-video-1", + type: "video", + representations: [newVideoRepresentation1] }; + const newAudioAdaptation = { contentWarnings: [], + id: "ada-audio-1", + type: "audio", + representations: [newAudioRepresentation] }; + const newPeriod = { contentWarnings: [], + start: 5, + end: 15, + duration: 10, + adaptations: { video: [newVideoAdaptation1], + audio: [newAudioAdaptation], + }, + getAdaptations() { + return [newVideoAdaptation1, newAudioAdaptation]; + }, + }; + + const mockLog = jest.spyOn(log, "warn"); + const res = updatePeriodInPlace(oldPeriod as unknown as Period, + newPeriod as unknown as Period, + MANIFEST_UPDATE_TYPE.Partial); + expect(res).toEqual({ addedAdaptations: [], + removedAdaptations: [], + updatedAdaptations: [ + { + adaptation: oldVideoAdaptation1, + addedRepresentations: [], + removedRepresentations: [oldVideoRepresentation2], + updatedRepresentations: [ + oldVideoRepresentation1, + ], + }, + { + adaptation: oldAudioAdaptation, + addedRepresentations: [], + removedRepresentations: [], + updatedRepresentations: [ + oldAudioRepresentation, + ], + }, + ] }); expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith( "Manifest: Representation \"rep-video-2\" not found when merging." ); - expect(oldVideoAdaptation1.representations).toHaveLength(2); + expect(oldVideoAdaptation1.representations).toHaveLength(1); mockLog.mockRestore(); }); }); diff --git a/src/manifest/__tests__/update_periods.test.ts b/src/manifest/__tests__/update_periods.test.ts index 10869937d4..c4f0b018c3 100644 --- a/src/manifest/__tests__/update_periods.test.ts +++ b/src/manifest/__tests__/update_periods.test.ts @@ -27,6 +27,12 @@ const MANIFEST_UPDATE_TYPE = { Partial: 1, }; +const fakeUpdatePeriodInPlaceRes = { + updatedAdaptations: [], + removedAdaptations: [], + addedAdaptations: [], +}; + describe("Manifest - replacePeriods", () => { beforeEach(() => { jest.resetModules(); @@ -37,7 +43,9 @@ describe("Manifest - replacePeriods", () => { // old periods : p1, p2 // new periods : p2 it("should remove old period", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -50,7 +58,14 @@ describe("Manifest - replacePeriods", () => { { id: "p2" }, ] as any; const replacePeriods = jest.requireActual("../update_periods").replacePeriods; - replacePeriods(oldPeriods, newPeriods); + const res = replacePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [], + removedPeriods: [{ id: "p1" }], + updatedPeriods: [ + { period: { id: "p2" }, result: fakeUpdatePeriodInPlaceRes }, + ], + }); expect(oldPeriods.length).toBe(1); expect(oldPeriods[0].id).toBe("p2"); expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(1); @@ -65,7 +80,9 @@ describe("Manifest - replacePeriods", () => { // old periods : p1 // new periods : p1, p2 it("should add new period", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -78,7 +95,14 @@ describe("Manifest - replacePeriods", () => { { id: "p3" }, ] as any; const replacePeriods = jest.requireActual("../update_periods").replacePeriods; - replacePeriods(oldPeriods, newPeriods); + const res = replacePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [{ id: "p3" }], + removedPeriods: [], + updatedPeriods: [ + { period: { id: "p2" }, result: fakeUpdatePeriodInPlaceRes }, + ], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p2"); expect(oldPeriods[1].id).toBe("p3"); @@ -94,7 +118,9 @@ describe("Manifest - replacePeriods", () => { // old periods: p1 // new periods: p2 it("should replace period", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -106,7 +132,12 @@ describe("Manifest - replacePeriods", () => { { id: "p2" }, ] as any; const replacePeriods = jest.requireActual("../update_periods").replacePeriods; - replacePeriods(oldPeriods, newPeriods); + const res = replacePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [{ id: "p2" }], + removedPeriods: [{ id: "p1" }], + updatedPeriods: [], + }); expect(oldPeriods.length).toBe(1); expect(oldPeriods[0].id).toBe("p2"); expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(0); @@ -117,7 +148,9 @@ describe("Manifest - replacePeriods", () => { // old periods: p0, p1, p2 // new periods: p1, a, b, p2, p3 it("should handle more complex period replacement", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -135,7 +168,19 @@ describe("Manifest - replacePeriods", () => { { id: "p3" }, ] as any; const replacePeriods = jest.requireActual("../update_periods").replacePeriods; - replacePeriods(oldPeriods, newPeriods); + const res = replacePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + { id: "a" }, + { id: "b" }, + { id: "p3" }, + ], + removedPeriods: [{ id: "p0" }], + updatedPeriods: [ + { period: { id: "p1" }, result: fakeUpdatePeriodInPlaceRes }, + { period: { id: "p2", start: 0 }, result: fakeUpdatePeriodInPlaceRes }, + ], + }); expect(oldPeriods.length).toBe(5); expect(oldPeriods[0].id).toBe("p1"); @@ -159,7 +204,9 @@ describe("Manifest - replacePeriods", () => { // old periods : p2 // new periods : p1, p2 it("should add new period before", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -172,7 +219,16 @@ describe("Manifest - replacePeriods", () => { { id: "p2" }, ] as any; const replacePeriods = jest.requireActual("../update_periods").replacePeriods; - replacePeriods(oldPeriods, newPeriods); + const res = replacePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + { id: "p1" }, + ], + removedPeriods: [], + updatedPeriods: [ + { period: { id: "p2" }, result: fakeUpdatePeriodInPlaceRes }, + ], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p1"); expect(oldPeriods[1].id).toBe("p2"); @@ -188,7 +244,9 @@ describe("Manifest - replacePeriods", () => { // old periods : p1, p2 // new periods : No periods it("should remove all periods", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -199,7 +257,15 @@ describe("Manifest - replacePeriods", () => { ] as any; const newPeriods = [] as any; const replacePeriods = jest.requireActual("../update_periods").replacePeriods; - replacePeriods(oldPeriods, newPeriods); + const res = replacePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [], + removedPeriods: [ + { id: "p1" }, + { id: "p2" }, + ], + updatedPeriods: [], + }); expect(oldPeriods.length).toBe(0); expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(0); }); @@ -209,7 +275,9 @@ describe("Manifest - replacePeriods", () => { // old periods : No periods // new periods : p1, p2 it("should add all periods to empty array", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -220,7 +288,15 @@ describe("Manifest - replacePeriods", () => { { id: "p2" }, ] as any; const replacePeriods = jest.requireActual("../update_periods").replacePeriods; - replacePeriods(oldPeriods, newPeriods); + const res = replacePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + { id: "p1" }, + { id: "p2" }, + ], + removedPeriods: [], + updatedPeriods: [], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p1"); expect(oldPeriods[1].id).toBe("p2"); @@ -228,7 +304,7 @@ describe("Manifest - replacePeriods", () => { }); }); -describe("updatePeriods", () => { +describe("Manifest - updatePeriods", () => { beforeEach(() => { jest.resetModules(); }); @@ -238,7 +314,9 @@ describe("updatePeriods", () => { // old periods : p1, p2 // new periods : p2 it("should not remove old period", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -248,7 +326,14 @@ describe("updatePeriods", () => { const newPeriods = [ { id: "p2", start: 60 } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [], + removedPeriods: [], + updatedPeriods: [ + { period: { id: "p2", start: 60 }, result: fakeUpdatePeriodInPlaceRes }, + ], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p1"); expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(1); @@ -263,7 +348,9 @@ describe("updatePeriods", () => { // old periods : p1 // new periods : p1, p2 it("should add new period", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -272,7 +359,14 @@ describe("updatePeriods", () => { const newPeriods = [ { id: "p2", start: 60, end: 80 }, { id: "p3", start: 80 } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [{ id: "p3", start: 80 }], + removedPeriods: [], + updatedPeriods: [ + { period: { id: "p2", start: 60 }, result: fakeUpdatePeriodInPlaceRes }, + ], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p2"); expect(oldPeriods[1].id).toBe("p3"); @@ -289,7 +383,9 @@ describe("updatePeriods", () => { // old periods: p1 // new periods: p3 it("should throw when encountering two distant Periods", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -326,11 +422,14 @@ describe("updatePeriods", () => { // old periods: p0, p1, p2 // new periods: p1, a, b, p2, p3 it("should handle more complex period replacement", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const oldPeriods = [ { id: "p0", start: 50, end: 60 }, - { id: "p1", start: 60, end: 70 }, + { id: "p1", start: 60, end: 69 }, + { id: "p1.5", start: 69, end: 70 }, { id: "p2", start: 70 } ] as any; const newPeriods = [ { id: "p1", start: 60, end: 65 }, { id: "a", start: 65, end: 68 }, @@ -338,7 +437,27 @@ describe("updatePeriods", () => { { id: "p2", start: 70, end: 80 }, { id: "p3", start: 80 } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + { id: "a", start: 65, end: 68 }, + { id: "b", start: 68, end: 70 }, + { id: "p3", start: 80 }, + ], + removedPeriods: [ + { id: "p1.5", start: 69, end: 70 }, + ], + updatedPeriods: [ + { + period: { id: "p1", start: 60, end: 69 }, + result: fakeUpdatePeriodInPlaceRes, + }, + { + period: { id: "p2", start: 70 }, + result: fakeUpdatePeriodInPlaceRes, + }, + ], + }); expect(oldPeriods.length).toBe(6); expect(oldPeriods[0].id).toBe("p0"); @@ -347,12 +466,17 @@ describe("updatePeriods", () => { expect(oldPeriods[3].id).toBe("b"); expect(oldPeriods[4].id).toBe("p2"); expect(oldPeriods[5].id).toBe("p3"); - expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(1); + expect(fakeUpdatePeriodInPlace).toHaveBeenCalledTimes(2); expect(fakeUpdatePeriodInPlace) .toHaveBeenNthCalledWith(1, - { id: "p1", start: 60, end: 70 }, + { id: "p1", start: 60, end: 69 }, { id: "p1", start: 60, end: 65 }, MANIFEST_UPDATE_TYPE.Partial); + expect(fakeUpdatePeriodInPlace) + .toHaveBeenNthCalledWith(2, + { id: "p2", start: 70 }, + { id: "p2", start: 70, end: 80 }, + MANIFEST_UPDATE_TYPE.Full); }); // Case 5 : @@ -360,7 +484,9 @@ describe("updatePeriods", () => { // old periods : p2 // new periods : p1, p2 it("should throw when the first period is not encountered", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const oldPeriods = [ { id: "p2", start: 70 } ] as any; @@ -397,13 +523,20 @@ describe("updatePeriods", () => { // old periods : p1, p2 // new periods : No periods it("should keep old periods if no new Period is available", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const oldPeriods = [ { id: "p1" }, { id: "p2" } ] as any; const newPeriods = [] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [], + removedPeriods: [], + updatedPeriods: [], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p1"); expect(oldPeriods[1].id).toBe("p2"); @@ -415,13 +548,23 @@ describe("updatePeriods", () => { // old periods : No periods // new periods : p1, p2 it("should set only new Periods if none were available before", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const oldPeriods = [] as any; const newPeriods = [ { id: "p1" }, { id: "p2" } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + { id: "p1" }, + { id: "p2" }, + ], + removedPeriods: [], + updatedPeriods: [], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p1"); expect(oldPeriods[1].id).toBe("p2"); @@ -433,7 +576,9 @@ describe("updatePeriods", () => { // old periods : p0, p1 // new periods : p4, p5 it("should throw if the new periods come strictly after", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const updatePeriods = jest.requireActual("../update_periods").updatePeriods; @@ -470,7 +615,9 @@ describe("updatePeriods", () => { // old periods: p1 // new periods: p2 it("should concatenate consecutive periods", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -479,7 +626,14 @@ describe("updatePeriods", () => { const newPeriods = [ { id: "p2", start: 60, end: 80 } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + { id: "p2", start: 60, end: 80 }, + ], + removedPeriods: [], + updatedPeriods: [], + }); expect(oldPeriods.length).toBe(2); expect(oldPeriods[0].id).toBe("p1"); expect(oldPeriods[1].id).toBe("p2"); @@ -493,7 +647,9 @@ describe("updatePeriods", () => { /* eslint-disable max-len */ it("should throw when encountering two completely different Periods with the same start", () => { /* eslint-enable max-len */ - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace, @@ -530,7 +686,9 @@ describe("updatePeriods", () => { // old periods: p0, p1, p2 // new periods: p1, p2, p3 it("should handle more complex period replacement", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const oldPeriods = [ { id: "p0", start: 50, end: 60 }, @@ -540,7 +698,24 @@ describe("updatePeriods", () => { { id: "p2", start: 65, end: 80 }, { id: "p3", start: 80 } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + { id: "p3", start: 80 }, + ], + removedPeriods: [ + ], + updatedPeriods: [ + { + period: { id: "p1", start: 60, end: 70 }, + result: fakeUpdatePeriodInPlaceRes, + }, + { + period: { id: "p2", start: 70 }, + result: fakeUpdatePeriodInPlaceRes, + }, + ], + }); expect(oldPeriods.length).toBe(4); expect(oldPeriods[0].id).toBe("p0"); @@ -565,7 +740,9 @@ describe("updatePeriods", () => { // old periods: p0, p1, p2, p3 // new periods: p1, p3 it("should handle more complex period replacement", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const oldPeriods = [ { id: "p0", start: 50, end: 60 }, @@ -575,7 +752,24 @@ describe("updatePeriods", () => { const newPeriods = [ { id: "p1", start: 60, end: 70 }, { id: "p3", start: 80 } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + ], + removedPeriods: [ + { id: "p2", start: 70, end: 80 }, + ], + updatedPeriods: [ + { + period: { id: "p1", start: 60, end: 70 }, + result: fakeUpdatePeriodInPlaceRes, + }, + { + period: { id: "p3", start: 80 }, + result: fakeUpdatePeriodInPlaceRes, + }, + ], + }); expect(oldPeriods.length).toBe(3); expect(oldPeriods[0].id).toBe("p0"); @@ -599,7 +793,9 @@ describe("updatePeriods", () => { // old periods: p0, p1, p2, p3, p4 // new periods: p1, p3 it("should remove periods not included in the new Periods", () => { - const fakeUpdatePeriodInPlace = jest.fn(() => { return; }); + const fakeUpdatePeriodInPlace = jest.fn(() => { + return fakeUpdatePeriodInPlaceRes; + }); jest.mock("../update_period_in_place", () => ({ __esModule: true as const, default: fakeUpdatePeriodInPlace })); const oldPeriods = [ { id: "p0", start: 50, end: 60 }, @@ -610,7 +806,25 @@ describe("updatePeriods", () => { const newPeriods = [ { id: "p1", start: 60, end: 70 }, { id: "p3", start: 80, end: 90 } ] as any; const updatePeriods = jest.requireActual("../update_periods").updatePeriods; - updatePeriods(oldPeriods, newPeriods); + const res = updatePeriods(oldPeriods, newPeriods); + expect(res).toEqual({ + addedPeriods: [ + ], + removedPeriods: [ + { id: "p2", start: 70, end: 80 }, + { id: "p4", start: 90 }, + ], + updatedPeriods: [ + { + period: { id: "p1", start: 60, end: 70 }, + result: fakeUpdatePeriodInPlaceRes, + }, + { + period: { id: "p3", start: 80, end: 90 }, + result: fakeUpdatePeriodInPlaceRes, + }, + ], + }); expect(oldPeriods.length).toBe(3); expect(oldPeriods[0].id).toBe("p0"); diff --git a/src/manifest/adaptation.ts b/src/manifest/adaptation.ts index 9ac5665777..128a4a87a5 100644 --- a/src/manifest/adaptation.ts +++ b/src/manifest/adaptation.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import log from "../log"; import { IParsedAdaptation } from "../parsers/manifest"; import { IRepresentationFilter } from "../public_types"; import arrayFind from "../utils/array_find"; @@ -56,6 +57,14 @@ export default class Adaptation { /** Whether this Adaptation contains closed captions for the hard-of-hearing. */ public isClosedCaption? : boolean; + /** + * If `true` this Adaptation are subtitles Meant for display when no other text + * Adaptation is selected. It is used to clarify dialogue, alternate + * languages, texted graphics or location/person IDs that are not otherwise + * covered in the dubbed/localized audio Adaptation. + */ + public isForcedSubtitles? : boolean; + /** If true this Adaptation contains sign interpretation. */ public isSignInterpreted? : boolean; @@ -120,6 +129,9 @@ export default class Adaptation { if (parsedAdaptation.isDub !== undefined) { this.isDub = parsedAdaptation.isDub; } + if (parsedAdaptation.forcedSubtitles !== undefined) { + this.isForcedSubtitles = parsedAdaptation.forcedSubtitles; + } if (parsedAdaptation.isSignInterpreted !== undefined) { this.isSignInterpreted = parsedAdaptation.isSignInterpreted; } @@ -153,6 +165,12 @@ export default class Adaptation { if (!isSupported && representation.isSupported) { isSupported = true; } + } else { + log.debug("Filtering Representation due to representationFilter", + this.type, + `Adaptation: ${this.id}`, + `Representation: ${representation.id}`, + `(${representation.bitrate})`); } } representations.sort((a, b) => a.bitrate - b.bitrate); diff --git a/src/manifest/manifest.ts b/src/manifest/manifest.ts index 0894bd2f71..db80754946 100644 --- a/src/manifest/manifest.ts +++ b/src/manifest/manifest.ts @@ -15,6 +15,7 @@ */ import { MediaError } from "../errors"; +import log from "../log"; import { IParsedManifest } from "../parsers/manifest"; import { IPlayerError, @@ -36,6 +37,7 @@ import { MANIFEST_UPDATE_TYPE, } from "./types"; import { + IPeriodsUpdateResult, replacePeriods, updatePeriods, } from "./update_periods"; @@ -104,7 +106,7 @@ export interface IDecipherabilityUpdateElement { manifest : Manifest; /** Events emitted by a `Manifest` instance */ export interface IManifestEvents { /** The Manifest has been updated */ - manifestUpdate : null; + manifestUpdate : IPeriodsUpdateResult; /** Some Representation's decipherability status has been updated */ decipherabilityUpdate : IDecipherabilityUpdateElement[]; } @@ -726,14 +728,15 @@ export default class Manifest extends EventEmitter { this.transport = newManifest.transport; this.publishTime = newManifest.publishTime; + let updatedPeriodsResult; if (updateType === MANIFEST_UPDATE_TYPE.Full) { this._timeBounds = newManifest._timeBounds; this.uris = newManifest.uris; - replacePeriods(this.periods, newManifest.periods); + updatedPeriodsResult = replacePeriods(this.periods, newManifest.periods); } else { this._timeBounds.maximumTimeData = newManifest._timeBounds.maximumTimeData; this.updateUrl = newManifest.uris[0]; - updatePeriods(this.periods, newManifest.periods); + updatedPeriodsResult = updatePeriods(this.periods, newManifest.periods); // Partial updates do not remove old Periods. // This can become a memory problem when playing a content long enough. @@ -758,7 +761,7 @@ export default class Manifest extends EventEmitter { // Let's trigger events at the end, as those can trigger side-effects. // We do not want the current Manifest object to be incomplete when those // happen. - this.trigger("manifestUpdate", null); + this.trigger("manifestUpdate", updatedPeriodsResult); } } @@ -784,6 +787,9 @@ function updateDeciperability( const result = isDecipherable(representation); if (result !== representation.decipherable) { updates.push({ manifest, period, adaptation, representation }); + log.debug(`Decipherability changed for "${representation.id}"`, + `(${representation.bitrate})`, + String(representation.decipherable)); representation.decipherable = result; } } diff --git a/src/manifest/representation.ts b/src/manifest/representation.ts index df96a8b7a0..ae89e915d3 100644 --- a/src/manifest/representation.ts +++ b/src/manifest/representation.ts @@ -145,10 +145,16 @@ class Representation { this.cdnMetadata = args.cdnMetadata; this.index = args.index; - this.isSupported = opts.type === "audio" || - opts.type === "video" ? - isCodecSupported(this.getMimeTypeString()) : - true; // TODO for other types + if (opts.type === "audio" || opts.type === "video") { + const mimeTypeStr = this.getMimeTypeString(); + const isSupported = isCodecSupported(mimeTypeStr); + if (!isSupported) { + log.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate); + } + this.isSupported = isSupported; + } else { + this.isSupported = true; // TODO for other types + } } /** diff --git a/src/manifest/update_period_in_place.ts b/src/manifest/update_period_in_place.ts index 2cb8889f75..b6ff88b4cf 100644 --- a/src/manifest/update_period_in_place.ts +++ b/src/manifest/update_period_in_place.ts @@ -15,8 +15,10 @@ */ import log from "../log"; -import arrayFind from "../utils/array_find"; +import arrayFindIndex from "../utils/array_find_index"; +import Adaptation from "./adaptation"; import Period from "./period"; +import Representation from "./representation"; import { MANIFEST_UPDATE_TYPE } from "./types"; /** @@ -24,11 +26,19 @@ import { MANIFEST_UPDATE_TYPE } from "./types"; * the Manifest). * @param {Object} oldPeriod * @param {Object} newPeriod + * @param {number} updateType + * @returns {Object} */ -export default function updatePeriodInPlace(oldPeriod : Period, - newPeriod : Period, - updateType : MANIFEST_UPDATE_TYPE) : void -{ +export default function updatePeriodInPlace( + oldPeriod : Period, + newPeriod : Period, + updateType : MANIFEST_UPDATE_TYPE +) : IUpdatedPeriodResult { + const res : IUpdatedPeriodResult = { + updatedAdaptations: [], + removedAdaptations: [], + addedAdaptations: [], + }; oldPeriod.start = newPeriod.start; oldPeriod.end = newPeriod.end; oldPeriod.duration = newPeriod.duration; @@ -39,26 +49,43 @@ export default function updatePeriodInPlace(oldPeriod : Period, for (let j = 0; j < oldAdaptations.length; j++) { const oldAdaptation = oldAdaptations[j]; - const newAdaptation = arrayFind(newAdaptations, - a => a.id === oldAdaptation.id); - if (newAdaptation === undefined) { + const newAdaptationIdx = arrayFindIndex(newAdaptations, + a => a.id === oldAdaptation.id); + + if (newAdaptationIdx === -1) { log.warn("Manifest: Adaptation \"" + oldAdaptations[j].id + "\" not found when merging."); + const [removed] = oldAdaptations.splice(j, 1); + j--; + res.removedAdaptations.push(removed); } else { - const oldRepresentations = oldAdaptations[j].representations; - const newRepresentations = newAdaptation.representations; + const [newAdaptation] = newAdaptations.splice(newAdaptationIdx, 1); + const updatedRepresentations : Representation[] = []; + const addedRepresentations : Representation[] = []; + const removedRepresentations : Representation[] = []; + res.updatedAdaptations.push({ adaptation: oldAdaptation, + updatedRepresentations, + addedRepresentations, + removedRepresentations }); + + const oldRepresentations = oldAdaptation.representations; + const newRepresentations = newAdaptation.representations.slice(); for (let k = 0; k < oldRepresentations.length; k++) { const oldRepresentation = oldRepresentations[k]; - const newRepresentation = - arrayFind(newRepresentations, - representation => representation.id === oldRepresentation.id); + const newRepresentationIdx = arrayFindIndex(newRepresentations, representation => + representation.id === oldRepresentation.id); - if (newRepresentation === undefined) { + if (newRepresentationIdx === -1) { log.warn(`Manifest: Representation "${oldRepresentations[k].id}" ` + "not found when merging."); + const [removed] = oldRepresentations.splice(k, 1); + k--; + removedRepresentations.push(removed); } else { + const [newRepresentation] = newRepresentations.splice(newRepresentationIdx, 1); + updatedRepresentations.push(oldRepresentation); oldRepresentation.cdnMetadata = newRepresentation.cdnMetadata; if (updateType === MANIFEST_UPDATE_TYPE.Full) { oldRepresentation.index._replace(newRepresentation.index); @@ -67,6 +94,49 @@ export default function updatePeriodInPlace(oldPeriod : Period, } } } + + if (newRepresentations.length > 0) { + log.warn(`Manifest: ${newRepresentations.length} new Representations ` + + "found when merging."); + oldAdaptation.representations.push(...newRepresentations); + addedRepresentations.push(...newRepresentations); + } } } + if (newAdaptations.length > 0) { + log.warn(`Manifest: ${newAdaptations.length} new Adaptations ` + + "found when merging."); + for (const adap of newAdaptations) { + const prevAdaps = oldPeriod.adaptations[adap.type]; + if (prevAdaps === undefined) { + oldPeriod.adaptations[adap.type] = [adap]; + } else { + prevAdaps.push(adap); + } + res.addedAdaptations.push(adap); + } + } + return res; +} + +/** + * Object describing the updates performed by `updatePeriodInPlace` on a single + * Period. + */ +export interface IUpdatedPeriodResult { + /** Information on Adaptations that have been updated. */ + updatedAdaptations : Array<{ + /** The concerned Adaptation. */ + adaptation: Adaptation; + /** Representations that have been updated. */ + updatedRepresentations : Representation[]; + /** Representations that have been removed from the Adaptation. */ + removedRepresentations : Representation[]; + /** Representations that have been added to the Adaptation. */ + addedRepresentations : Representation[]; + }>; + /** Adaptation that have been removed from the Period. */ + removedAdaptations : Adaptation[]; + /** Adaptation that have been added to the Period. */ + addedAdaptations : Adaptation[]; } diff --git a/src/manifest/update_periods.ts b/src/manifest/update_periods.ts index 13f8427582..45734e7f03 100644 --- a/src/manifest/update_periods.ts +++ b/src/manifest/update_periods.ts @@ -19,18 +19,26 @@ import log from "../log"; import arrayFindIndex from "../utils/array_find_index"; import Period from "./period"; import { MANIFEST_UPDATE_TYPE } from "./types"; -import updatePeriodInPlace from "./update_period_in_place"; +import updatePeriodInPlace, { + IUpdatedPeriodResult, +} from "./update_period_in_place"; /** * Update old periods by adding new periods and removing * not available ones. * @param {Array.} oldPeriods * @param {Array.} newPeriods + * @returns {Object} */ export function replacePeriods( oldPeriods: Period[], newPeriods: Period[] -) : void { +) : IPeriodsUpdateResult { + const res : IPeriodsUpdateResult = { + updatedPeriods: [], + addedPeriods: [], + removedPeriods: [], + }; let firstUnhandledPeriodIdx = 0; for (let i = 0; i < newPeriods.length; i++) { const newPeriod = newPeriods[i]; @@ -41,29 +49,35 @@ export function replacePeriods( oldPeriod = oldPeriods[j]; } if (oldPeriod != null) { - updatePeriodInPlace(oldPeriod, newPeriod, MANIFEST_UPDATE_TYPE.Full); + const result = updatePeriodInPlace(oldPeriod, newPeriod, MANIFEST_UPDATE_TYPE.Full); + res.updatedPeriods.push({ period: oldPeriod, result }); const periodsToInclude = newPeriods.slice(firstUnhandledPeriodIdx, i); const nbrOfPeriodsToRemove = j - firstUnhandledPeriodIdx; - oldPeriods.splice(firstUnhandledPeriodIdx, - nbrOfPeriodsToRemove, - ...periodsToInclude); + const removed = oldPeriods.splice(firstUnhandledPeriodIdx, + nbrOfPeriodsToRemove, + ...periodsToInclude); + res.removedPeriods.push(...removed); + res.addedPeriods.push(...periodsToInclude); firstUnhandledPeriodIdx = i + 1; } } if (firstUnhandledPeriodIdx > oldPeriods.length) { log.error("Manifest: error when updating Periods"); - return; + return res; } if (firstUnhandledPeriodIdx < oldPeriods.length) { - oldPeriods.splice(firstUnhandledPeriodIdx, - oldPeriods.length - firstUnhandledPeriodIdx); + const removed = oldPeriods.splice(firstUnhandledPeriodIdx, + oldPeriods.length - firstUnhandledPeriodIdx); + res.removedPeriods.push(...removed); } const remainingNewPeriods = newPeriods.slice(firstUnhandledPeriodIdx, newPeriods.length); if (remainingNewPeriods.length > 0) { oldPeriods.push(...remainingNewPeriods); + res.addedPeriods.push(...remainingNewPeriods); } + return res; } /** @@ -71,17 +85,24 @@ export function replacePeriods( * not available ones. * @param {Array.} oldPeriods * @param {Array.} newPeriods + * @returns {Object} */ export function updatePeriods( oldPeriods: Period[], newPeriods: Period[] -) : void { +) : IPeriodsUpdateResult { + const res : IPeriodsUpdateResult = { + updatedPeriods: [], + addedPeriods: [], + removedPeriods: [], + }; if (oldPeriods.length === 0) { oldPeriods.splice(0, 0, ...newPeriods); - return; + res.addedPeriods.push(...newPeriods); + return res; } if (newPeriods.length === 0) { - return; + return res; } const oldLastPeriod = oldPeriods[oldPeriods.length - 1]; if (oldLastPeriod.start < newPeriods[0].start) { @@ -90,8 +111,11 @@ export function updatePeriods( "Cannot perform partial update: not enough data"); } oldPeriods.push(...newPeriods); - return; + res.addedPeriods.push(...newPeriods); + return res; } + + /** Index, in `oldPeriods` of the first element of `newPeriods` */ const indexOfNewFirstPeriod = arrayFindIndex(oldPeriods, ({ id }) => id === newPeriods[0].id); if (indexOfNewFirstPeriod < 0) { @@ -100,10 +124,14 @@ export function updatePeriods( } // The first updated Period can only be a partial part - updatePeriodInPlace(oldPeriods[indexOfNewFirstPeriod], - newPeriods[0], - MANIFEST_UPDATE_TYPE.Partial); + const updateRes = updatePeriodInPlace(oldPeriods[indexOfNewFirstPeriod], + newPeriods[0], + MANIFEST_UPDATE_TYPE.Partial); + res.updatedPeriods.push({ period: oldPeriods[indexOfNewFirstPeriod], + result: updateRes }); + // Search each consecutive elements of `newPeriods` - after the initial one already + // processed - in `oldPeriods`, removing and adding unfound Periods in the process let prevIndexOfNewPeriod = indexOfNewFirstPeriod + 1; for (let i = 1; i < newPeriods.length; i++) { const newPeriod = newPeriods[i]; @@ -114,28 +142,60 @@ export function updatePeriods( break; // end the loop } } - if (indexOfNewPeriod < 0) { - oldPeriods.splice(prevIndexOfNewPeriod, - oldPeriods.length - prevIndexOfNewPeriod, - ...newPeriods.slice(i, newPeriods.length)); - return; - } - if (indexOfNewPeriod > prevIndexOfNewPeriod) { - oldPeriods.splice(prevIndexOfNewPeriod, - indexOfNewPeriod - prevIndexOfNewPeriod); - indexOfNewPeriod = prevIndexOfNewPeriod; - } + if (indexOfNewPeriod < 0) { // Next element of `newPeriods` not found: insert it + let toRemoveUntil = -1; + for (let j = prevIndexOfNewPeriod; j < oldPeriods.length; j++) { + if (newPeriod.start < oldPeriods[j].start) { + toRemoveUntil = j; + break; // end the loop + } + } + const nbElementsToRemove = toRemoveUntil - prevIndexOfNewPeriod; + const removed = oldPeriods.splice(prevIndexOfNewPeriod, + nbElementsToRemove, + newPeriod); + res.addedPeriods.push(newPeriod); + res.removedPeriods.push(...removed); + } else { + if (indexOfNewPeriod > prevIndexOfNewPeriod) { + // Some old periods were not found: remove + log.warn("Manifest: old Periods not found in new when updating, removing"); + const removed = oldPeriods.splice(prevIndexOfNewPeriod, + indexOfNewPeriod - prevIndexOfNewPeriod); + res.removedPeriods.push(...removed); + indexOfNewPeriod = prevIndexOfNewPeriod; + } - // Later Periods can be fully replaced - updatePeriodInPlace(oldPeriods[indexOfNewPeriod], - newPeriod, - MANIFEST_UPDATE_TYPE.Full); + // Later Periods can be fully replaced + const result = updatePeriodInPlace(oldPeriods[indexOfNewPeriod], + newPeriod, + MANIFEST_UPDATE_TYPE.Full); + res.updatedPeriods.push({ period: oldPeriods[indexOfNewPeriod], result }); + } prevIndexOfNewPeriod++; } if (prevIndexOfNewPeriod < oldPeriods.length) { - oldPeriods.splice(prevIndexOfNewPeriod, - oldPeriods.length - prevIndexOfNewPeriod); + log.warn("Manifest: Ending Periods not found in new when updating, removing"); + const removed = oldPeriods.splice(prevIndexOfNewPeriod, + oldPeriods.length - prevIndexOfNewPeriod); + res.removedPeriods.push(...removed); } + return res; +} + +/** Object describing a Manifest update at the Periods level. */ +export interface IPeriodsUpdateResult { + /** Information on Periods that have been updated. */ + updatedPeriods : Array<{ + /** The concerned Period. */ + period : Period; + /** The updates performed. */ + result : IUpdatedPeriodResult; + }>; + /** Periods that have been added. */ + addedPeriods : Period[]; + /** Periods that have been removed. */ + removedPeriods : Period[]; } diff --git a/src/parsers/manifest/dash/common/indexes/base.ts b/src/parsers/manifest/dash/common/indexes/base.ts index 3d40040d15..405bd533cc 100644 --- a/src/parsers/manifest/dash/common/indexes/base.ts +++ b/src/parsers/manifest/dash/common/indexes/base.ts @@ -69,6 +69,8 @@ export interface IBaseIndex { segmentUrlTemplate : string | null; /** Number from which the first segments in this index starts with. */ startNumber? : number | undefined; + /** Number associated to the last segment in this index. */ + endNumber? : number | undefined; /** Every segments defined in this index. */ timeline : IIndexSegment[]; /** @@ -90,6 +92,7 @@ export interface IBaseIndexIndexArgument { indexRange?: [number, number]; initialization?: { media?: string; range?: [number, number] }; startNumber? : number; + endNumber? : number; /** * Offset present in the index to convert from the mediaTime (time declared in * the media segments and in this index) to the presentationTime (time wanted @@ -222,6 +225,7 @@ export default class BaseRepresentationIndex implements IRepresentationIndex { initialization: { url: initializationUrl, range }, segmentUrlTemplate, startNumber: index.startNumber, + endNumber: index.endNumber, timeline: index.timeline ?? [], timescale }; this._scaledPeriodStart = toIndexTime(periodStart, this._index); diff --git a/src/parsers/manifest/dash/common/indexes/get_segments_from_timeline.ts b/src/parsers/manifest/dash/common/indexes/get_segments_from_timeline.ts index bb5c87872a..f0839e7ac4 100644 --- a/src/parsers/manifest/dash/common/indexes/get_segments_from_timeline.ts +++ b/src/parsers/manifest/dash/common/indexes/get_segments_from_timeline.ts @@ -55,6 +55,7 @@ export default function getSegmentsFromTimeline( index : { availabilityTimeComplete? : boolean | undefined; segmentUrlTemplate : string | null; startNumber? : number | undefined; + endNumber? : number | undefined; timeline : IIndexSegment[]; timescale : number; indexTimeOffset : number; }, @@ -65,7 +66,7 @@ export default function getSegmentsFromTimeline( ) : ISegment[] { const scaledUp = toIndexTime(from, index); const scaledTo = toIndexTime(from + durationWanted, index); - const { timeline, timescale, segmentUrlTemplate, startNumber } = index; + const { timeline, timescale, segmentUrlTemplate, startNumber, endNumber } = index; let currentNumber = startNumber ?? 1; const segments : ISegment[] = []; @@ -84,6 +85,9 @@ export default function getSegmentsFromTimeline( let segmentTime = start + segmentNumberInCurrentRange * duration; while (segmentTime < scaledTo && segmentNumberInCurrentRange <= repeat) { const segmentNumber = currentNumber + segmentNumberInCurrentRange; + if (endNumber !== undefined && segmentNumber > endNumber) { + break; + } const detokenizedURL = segmentUrlTemplate === null ? null : @@ -121,6 +125,9 @@ export default function getSegmentsFromTimeline( } currentNumber += repeat + 1; + if (endNumber !== undefined && currentNumber > endNumber) { + return segments; + } } return segments; diff --git a/src/parsers/manifest/dash/common/indexes/template.ts b/src/parsers/manifest/dash/common/indexes/template.ts index 8bf3541cc3..191935e22f 100644 --- a/src/parsers/manifest/dash/common/indexes/template.ts +++ b/src/parsers/manifest/dash/common/indexes/template.ts @@ -20,6 +20,7 @@ import { ISegment, } from "../../../../../manifest"; import assert from "../../../../../utils/assert"; +import isNullOrUndefined from "../../../../../utils/is_null_or_undefined"; import { IEMSG } from "../../../../containers/isobmff"; import ManifestBoundsCalculator from "../manifest_bounds_calculator"; import getInitSegment from "./get_init_segment"; @@ -94,6 +95,8 @@ export interface ITemplateIndex { presentationTimeOffset : number; /** Number from which the first segments in this index starts with. */ startNumber? : number | undefined; + /** Number associated to the last segment in this index. */ + endNumber? : number | undefined; } /** @@ -109,6 +112,7 @@ export interface ITemplateIndexIndexArgument { media? : string | undefined; presentationTimeOffset? : number | undefined; startNumber? : number | undefined; + endNumber? : number | undefined; timescale? : number | undefined; } @@ -213,7 +217,8 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex range: index.initialization.range }, url: segmentUrlTemplate, presentationTimeOffset, - startNumber: index.startNumber }; + startNumber: index.startNumber, + endNumber: index.endNumber }; this._isDynamic = isDynamic; this._periodStart = periodStart; this._scaledRelativePeriodEnd = periodEnd === undefined ? @@ -239,6 +244,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex const index = this._index; const { duration, startNumber, + endNumber, timescale, url } = index; @@ -276,6 +282,9 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex ) { // To obtain the real number, adds the real number from the Period's start const realNumber = numberIndexedToZero + numberOffset; + if (endNumber !== undefined && realNumber > endNumber) { + return segments; + } const realDuration = scaledEnd != null && timeFromPeriodStart + duration > scaledEnd ? @@ -327,15 +336,16 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex */ getLastAvailablePosition() : number|null|undefined { const lastSegmentStart = this._getLastSegmentStart(); - if (lastSegmentStart == null) { + if (isNullOrUndefined(lastSegmentStart)) { // In that case (null or undefined), getLastAvailablePosition should reflect // the result of getLastSegmentStart, as the meaning is the same for // the two functions. So, we return the result of the latter. return lastSegmentStart; } + const scaledRelativeIndexEnd = this._estimateRelativeScaledEnd(); const lastSegmentEnd = Math.min(lastSegmentStart + this._index.duration, - this._scaledRelativePeriodEnd ?? Infinity); + scaledRelativeIndexEnd ?? Infinity); return (lastSegmentEnd / this._index.timescale) + this._periodStart; } @@ -348,13 +358,14 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex if (!this._isDynamic) { return this.getLastAvailablePosition(); } - if (this._scaledRelativePeriodEnd === undefined) { + const scaledRelativeIndexEnd = this._estimateRelativeScaledEnd(); + if (scaledRelativeIndexEnd === undefined) { return undefined; } const { timescale } = this._index; - const absoluteScaledPeriodEnd = (this._scaledRelativePeriodEnd + + const absoluteScaledIndexEnd = (scaledRelativeIndexEnd + this._periodStart * timescale); - return absoluteScaledPeriodEnd / this._index.timescale; + return absoluteScaledIndexEnd / timescale; } /** @@ -379,14 +390,13 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex const segmentTimeRounding = getSegmentTimeRoundingError(timescale); const scaledPeriodStart = this._periodStart * timescale; const scaledRelativeEnd = end * timescale - scaledPeriodStart; - if (this._scaledRelativePeriodEnd === undefined) { + + const relativeScaledIndexEnd = this._estimateRelativeScaledEnd(); + if (relativeScaledIndexEnd === undefined) { return (scaledRelativeEnd + segmentTimeRounding) >= 0; } - - const scaledRelativePeriodEnd = this._scaledRelativePeriodEnd; const scaledRelativeStart = start * timescale - scaledPeriodStart; - return (scaledRelativeStart - segmentTimeRounding) < scaledRelativePeriodEnd && - (scaledRelativeEnd + segmentTimeRounding) >= 0; + return (scaledRelativeStart - segmentTimeRounding) < relativeScaledIndexEnd; } /** @@ -436,13 +446,19 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex } /** + * Returns `true` if the last segments in this index have already been + * generated so that we can freely go to the next period. + * Returns `false` if the index is still waiting on future segments to be + * generated. * @returns {Boolean} */ isFinished() : boolean { if (!this._isDynamic) { return true; } - if (this._scaledRelativePeriodEnd === undefined) { + + const scaledRelativeIndexEnd = this._estimateRelativeScaledEnd(); + if (scaledRelativeIndexEnd === undefined) { return false; } @@ -451,12 +467,12 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex // As last segment start is null if live time is before // current period, consider the index not to be finished. - if (lastSegmentStart == null) { + if (isNullOrUndefined(lastSegmentStart)) { return false; } const lastSegmentEnd = lastSegmentStart + this._index.duration; const segmentTimeRounding = getSegmentTimeRoundingError(timescale); - return (lastSegmentEnd + segmentTimeRounding) >= this._scaledRelativePeriodEnd; + return (lastSegmentEnd + segmentTimeRounding) >= scaledRelativeIndexEnd; } /** @@ -532,7 +548,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex * @returns {number|null|undefined} */ private _getLastSegmentStart() : number | null | undefined { - const { duration, timescale } = this._index; + const { duration, timescale, endNumber, startNumber = 1 } = this._index; if (this._isDynamic) { const lastPos = this._manifestBoundsCalculator.estimateMaximumBound(); @@ -541,13 +557,15 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex } const agressiveModeOffset = this._aggressiveMode ? (duration / timescale) : 0; - if (this._scaledRelativePeriodEnd != null && + if (this._scaledRelativePeriodEnd !== undefined && this._scaledRelativePeriodEnd < (lastPos + agressiveModeOffset - this._periodStart) * this._index.timescale) { - if (this._scaledRelativePeriodEnd < duration) { - return null; + + let numberOfSegments = Math.ceil(this._scaledRelativePeriodEnd / duration); + if (endNumber !== undefined && (endNumber - startNumber + 1) < numberOfSegments) { + numberOfSegments = endNumber - startNumber + 1; } - return (Math.floor(this._scaledRelativePeriodEnd / duration) - 1) * duration; + return (numberOfSegments - 1) * duration; } // /!\ The scaled last position augments continuously and might not // reflect exactly the real server-side value. As segments are @@ -564,28 +582,61 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex ((this._availabilityTimeOffset !== undefined ? this._availabilityTimeOffset : 0) + agressiveModeOffset) * timescale; - const numberOfSegmentsAvailable = + let numberOfSegmentsAvailable = Math.floor((scaledLastPosition + availabilityTimeOffset) / duration); + if (endNumber !== undefined && + (endNumber - startNumber + 1) < numberOfSegmentsAvailable) { + numberOfSegmentsAvailable = endNumber - startNumber + 1; + } return numberOfSegmentsAvailable <= 0 ? null : (numberOfSegmentsAvailable - 1) * duration; } else { const maximumTime = this._scaledRelativePeriodEnd ?? 0; - const numberIndexedToZero = Math.ceil(maximumTime / duration) - 1; - const regularLastSegmentStart = numberIndexedToZero * duration; + let numberOfSegments = Math.ceil(maximumTime / duration); + if (endNumber !== undefined && (endNumber - startNumber + 1) < numberOfSegments) { + numberOfSegments = endNumber - startNumber + 1; + } + + const regularLastSegmentStart = (numberOfSegments - 1) * duration; // In some SegmentTemplate, we could think that there is one more // segment that there actually is due to a very little difference between // the period's duration and a multiple of a segment's duration. // Check that we're within a good margin const minimumDuration = config.getCurrent().MINIMUM_SEGMENT_SIZE * timescale; - if (maximumTime - regularLastSegmentStart > minimumDuration || - numberIndexedToZero === 0) + if (endNumber !== undefined || + maximumTime - regularLastSegmentStart > minimumDuration || + numberOfSegments < 2) { return regularLastSegmentStart; } - return (numberIndexedToZero - 1) * duration; + return (numberOfSegments - 2) * duration; + } + } + + /** + * Returns an estimate of the last available position in this + * `RepresentationIndex` based on attributes such as the Period's end and + * the `endNumber` attribute. + * If the estimate cannot be made (e.g. this Period's segments are still being + * generated and its end is yet unknown), returns `undefined`. + * @returns {number|undefined} + */ + private _estimateRelativeScaledEnd() : number | undefined { + if (this._index.endNumber !== undefined) { + const numberOfSegments = + this._index.endNumber - (this._index.startNumber ?? 1) + 1; + return Math.max(Math.min(numberOfSegments * this._index.duration, + this._scaledRelativePeriodEnd ?? Infinity), + 0); } + + if (this._scaledRelativePeriodEnd === undefined) { + return undefined; + } + + return Math.max(this._scaledRelativePeriodEnd, 0); } } diff --git a/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts b/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts index 32162aeb8a..0d105ee865 100644 --- a/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +++ b/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts @@ -88,6 +88,8 @@ export interface ITimelineIndex { segmentUrlTemplate : string | null ; /** Number from which the first segments in this index starts with. */ startNumber? : number | undefined; + /** Number associated to the last segment in this index. */ + endNumber? : number | undefined; /** * Every segments defined in this index. * `null` at the beginning as this property is parsed lazily (only when first @@ -125,6 +127,7 @@ export interface ITimelineIndexIndexArgument { undefined; media? : string | undefined; startNumber? : number | undefined; + endNumber? : number | undefined; timescale? : number | undefined; /** * Offset present in the index to convert from the mediaTime (time declared in @@ -306,7 +309,12 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex }, segmentUrlTemplate, startNumber: index.startNumber, - timeline: index.timeline ?? null, + endNumber: index.endNumber, + timeline: index.timeline === undefined ? + null : + updateTimelineFromEndNumber(index.timeline, + index.startNumber, + index.endNumber), timescale }; this._scaledPeriodStart = toIndexTime(periodStart, this._index); this._scaledPeriodEnd = periodEnd === undefined ? undefined : @@ -336,11 +344,13 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex // destructuring to please TypeScript const { segmentUrlTemplate, startNumber, + endNumber, timeline, timescale, indexTimeOffset } = this._index; return getSegmentsFromTimeline({ segmentUrlTemplate, startNumber, + endNumber, timeline, timescale, indexTimeOffset }, @@ -536,6 +546,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex if (hasReplaced) { this._index.startNumber = newIndex._index.startNumber; } + this._index.endNumber = newIndex._index.endNumber; this._isDynamic = newIndex._isDynamic; this._scaledPeriodStart = newIndex._scaledPeriodStart; this._scaledPeriodEnd = newIndex._scaledPeriodEnd; @@ -613,6 +624,8 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex scaledFirstPosition); if (this._index.startNumber !== undefined) { this._index.startNumber += nbEltsRemoved; + } else if (this._index.endNumber !== undefined) { + this._index.startNumber = nbEltsRemoved + 1; } } @@ -666,7 +679,9 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex newElements.length < MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY) { // Just completely parse the current timeline - return constructTimelineFromElements(newElements); + return updateTimelineFromEndNumber(constructTimelineFromElements(newElements), + this._index.startNumber, + this._index.endNumber); } // Construct previously parsed timeline if not already done @@ -679,7 +694,50 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex } this._unsafelyBaseOnPreviousIndex = null; // Free memory - return constructTimelineFromPreviousTimeline(newElements, prevTimeline); + return updateTimelineFromEndNumber( + constructTimelineFromPreviousTimeline(newElements, prevTimeline), + this._index.startNumber, + this._index.endNumber); } } + +/** + * Take the original SegmentTimeline's parsed timeline and, if an `endNumber` is + * specified, filter segments which possess a number superior to that number. + * + * This should only be useful in only rare and broken MPDs, but we aim to + * respect the specification even in those cases. + * + * @param {Array.} timeline + * @param {number|undefined} startNumber + * @param {Array.} endNumber + * @returns {number|undefined} + */ +function updateTimelineFromEndNumber( + timeline : IIndexSegment[], + startNumber : number | undefined, + endNumber : number | undefined +) : IIndexSegment[] { + if (endNumber === undefined) { + return timeline; + } + let currNumber = startNumber ?? 1; + for (let idx = 0; idx < timeline.length; idx++) { + const seg = timeline[idx]; + currNumber += seg.repeatCount + 1; + if (currNumber > endNumber) { + if (currNumber === endNumber + 1) { + return timeline.slice(0, idx + 1); + } else { + const newTimeline = timeline.slice(0, idx); + const lastElt = { ...seg }; + const beginningNumber = currNumber - seg.repeatCount - 1; + lastElt.repeatCount = Math.max(0, endNumber - beginningNumber); + newTimeline.push(lastElt); + return newTimeline; + } + } + } + return timeline; +} diff --git a/src/parsers/manifest/dash/common/parse_adaptation_sets.ts b/src/parsers/manifest/dash/common/parse_adaptation_sets.ts index 6f15c7d497..57bfb592a1 100644 --- a/src/parsers/manifest/dash/common/parse_adaptation_sets.ts +++ b/src/parsers/manifest/dash/common/parse_adaptation_sets.ts @@ -107,21 +107,36 @@ function isVisuallyImpaired( /** * Detect if the accessibility given defines an adaptation for the hard of * hearing. - * Based on DVB Document A168 (DVB-DASH). - * @param {Object} accessibility + * Based on DVB Document A168 (DVB-DASH) and DASH specification. + * @param {Array.} accessibilities + * @param {Array.} roles * @returns {Boolean} */ -function isHardOfHearing( - accessibility : { schemeIdUri? : string | undefined; - value? : string | undefined; } | - undefined +function isCaptionning( + accessibilities : Array<{ schemeIdUri? : string | undefined; + value? : string | undefined; }> | + undefined, + roles : Array<{ schemeIdUri? : string | undefined; + value? : string | undefined; }> | + undefined ) : boolean { - if (accessibility === undefined) { - return false; + if (accessibilities !== undefined) { + const hasDvbClosedCaptionSignaling = accessibilities.some(accessibility => + (accessibility.schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && + accessibility.value === "2")); + if (hasDvbClosedCaptionSignaling) { + return true; + } } - - return (accessibility.schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && - accessibility.value === "2"); + if (roles !== undefined) { + const hasDashCaptionSinaling = roles.some(role => + (role.schemeIdUri === "urn:mpeg:dash:role:2011" && + role.value === "caption")); + if (hasDashCaptionSinaling) { + return true; + } + } + return false; } /** @@ -147,14 +162,13 @@ function hasSignLanguageInterpretation( /** * Contruct Adaptation ID from the information we have. * @param {Object} adaptation - * @param {Array.} representations - * @param {Array.} representations * @param {Object} infos * @returns {string} */ function getAdaptationID( adaptation : IAdaptationSetIntermediateRepresentation, infos : { isClosedCaption : boolean | undefined; + isForcedSubtitle : boolean | undefined; isAudioDescription : boolean | undefined; isSignInterpreted : boolean | undefined; isTrickModeTrack: boolean; @@ -165,6 +179,7 @@ function getAdaptationID( } const { isClosedCaption, + isForcedSubtitle, isAudioDescription, isSignInterpreted, isTrickModeTrack, @@ -177,6 +192,9 @@ function getAdaptationID( if (isClosedCaption === true) { idString += "-cc"; } + if (isForcedSubtitle === true) { + idString += "-cc"; + } if (isAudioDescription === true) { idString += "-ad"; } @@ -253,15 +271,6 @@ export default function parseAdaptationSets( const parsedAdaptationsIDs : string[] = []; - /** - * Index of the last parsed Video AdaptationSet with a Role set as "main" in - * `parsedAdaptations.video`. - * `-1` if not yet encountered. - * Used as we merge all main video AdaptationSet due to a comprehension of the - * DASH-IF IOP. - */ - let lastMainVideoAdapIdx = -1; - for (let adaptationIdx = 0; adaptationIdx < adaptationsIR.length; adaptationIdx++) { const adaptation = adaptationsIR[adaptationIdx]; const adaptationChildren = adaptation.children; @@ -298,7 +307,6 @@ export default function parseAdaptationSets( const priority = adaptation.attributes.selectionPriority ?? 1; const originalID = adaptation.attributes.id; - let newID : string; const adaptationSetSwitchingIDs = getAdaptationSetSwitchingIDs(adaptation); const parentSegmentTemplates = []; if (context.segmentTemplate !== undefined) { @@ -338,148 +346,136 @@ export default function parseAdaptationSets( const isTrickModeTrack = trickModeAttachedAdaptationIds !== undefined; - if (type === "video" && - isMainAdaptation && - lastMainVideoAdapIdx >= 0 && - parsedAdaptations.video.length > lastMainVideoAdapIdx && - !isTrickModeTrack) + const { accessibilities } = adaptationChildren; + + let isDub : boolean|undefined; + if (roles !== undefined && + roles.some((role) => role.value === "dub")) { - const videoMainAdaptation = parsedAdaptations.video[lastMainVideoAdapIdx][0]; - reprCtxt.unsafelyBaseOnPreviousAdaptation = context - .unsafelyBaseOnPreviousPeriod?.getAdaptation(videoMainAdaptation.id) ?? null; - const representations = parseRepresentations(representationsIR, - adaptation, - reprCtxt); - videoMainAdaptation.representations.push(...representations); - newID = videoMainAdaptation.id; - } else { - const { accessibilities } = adaptationChildren; + isDub = true; + } - let isDub : boolean|undefined; - if (roles !== undefined && - roles.some((role) => role.value === "dub")) - { - isDub = true; - } + let isClosedCaption; + if (type !== "text") { + isClosedCaption = false; + } else { + isClosedCaption = isCaptionning(accessibilities, roles); + } - let isClosedCaption; - if (type !== "text") { - isClosedCaption = false; - } else if (accessibilities !== undefined) { - isClosedCaption = accessibilities.some(isHardOfHearing); - } + let isForcedSubtitle; + if (type === "text" && + roles !== undefined && + roles.some((role) => role.value === "forced-subtitle" || + role.value === "forced_subtitle")) + { + isForcedSubtitle = true; + } - let isAudioDescription; - if (type !== "audio") { - isAudioDescription = false; - } else if (accessibilities !== undefined) { - isAudioDescription = accessibilities.some(isVisuallyImpaired); - } + let isAudioDescription; + if (type !== "audio") { + isAudioDescription = false; + } else if (accessibilities !== undefined) { + isAudioDescription = accessibilities.some(isVisuallyImpaired); + } - let isSignInterpreted; - if (type !== "video") { - isSignInterpreted = false; - } else if (accessibilities !== undefined) { - isSignInterpreted = accessibilities.some(hasSignLanguageInterpretation); - } + let isSignInterpreted; + if (type !== "video") { + isSignInterpreted = false; + } else if (accessibilities !== undefined) { + isSignInterpreted = accessibilities.some(hasSignLanguageInterpretation); + } - let adaptationID = getAdaptationID(adaptation, - { isAudioDescription, - isClosedCaption, - isSignInterpreted, - isTrickModeTrack, - type }); + let adaptationID = getAdaptationID(adaptation, + { isAudioDescription, + isForcedSubtitle, + isClosedCaption, + isSignInterpreted, + isTrickModeTrack, + type }); + + // Avoid duplicate IDs + while (arrayIncludes(parsedAdaptationsIDs, adaptationID)) { + adaptationID += "-dup"; + } - // Avoid duplicate IDs - while (arrayIncludes(parsedAdaptationsIDs, adaptationID)) { - adaptationID += "-dup"; - } + const newID = adaptationID; + parsedAdaptationsIDs.push(adaptationID); + + reprCtxt.unsafelyBaseOnPreviousAdaptation = context + .unsafelyBaseOnPreviousPeriod?.getAdaptation(adaptationID) ?? null; + + const representations = parseRepresentations(representationsIR, + adaptation, + reprCtxt); + const parsedAdaptationSet : IParsedAdaptation = + { id: adaptationID, + representations, + type, + isTrickModeTrack }; + if (adaptation.attributes.language != null) { + parsedAdaptationSet.language = adaptation.attributes.language; + } + if (isClosedCaption != null) { + parsedAdaptationSet.closedCaption = isClosedCaption; + } + if (isAudioDescription != null) { + parsedAdaptationSet.audioDescription = isAudioDescription; + } + if (isDub === true) { + parsedAdaptationSet.isDub = true; + } + if (isForcedSubtitle !== undefined) { + parsedAdaptationSet.forcedSubtitles = isForcedSubtitle; + } + if (isSignInterpreted === true) { + parsedAdaptationSet.isSignInterpreted = true; + } - newID = adaptationID; - parsedAdaptationsIDs.push(adaptationID); - - reprCtxt.unsafelyBaseOnPreviousAdaptation = context - .unsafelyBaseOnPreviousPeriod?.getAdaptation(adaptationID) ?? null; - - const representations = parseRepresentations(representationsIR, - adaptation, - reprCtxt); - const parsedAdaptationSet : IParsedAdaptation = - { id: adaptationID, - representations, - type, - isTrickModeTrack }; - if (adaptation.attributes.language != null) { - parsedAdaptationSet.language = adaptation.attributes.language; - } - if (isClosedCaption != null) { - parsedAdaptationSet.closedCaption = isClosedCaption; - } - if (isAudioDescription != null) { - parsedAdaptationSet.audioDescription = isAudioDescription; - } - if (isDub === true) { - parsedAdaptationSet.isDub = true; - } - if (isSignInterpreted === true) { - parsedAdaptationSet.isSignInterpreted = true; - } + if (label !== undefined) { + parsedAdaptationSet.label = label; + } - if (label !== undefined) { - parsedAdaptationSet.label = label; - } + if (trickModeAttachedAdaptationIds !== undefined) { + trickModeAdaptations.push({ adaptation: parsedAdaptationSet, + trickModeAttachedAdaptationIds }); + } else { - if (trickModeAttachedAdaptationIds !== undefined) { - trickModeAdaptations.push({ adaptation: parsedAdaptationSet, - trickModeAttachedAdaptationIds }); - } else { - - // look if we have to merge this into another Adaptation - let mergedIntoIdx = -1; - for (const id of adaptationSetSwitchingIDs) { - const switchingInfos = adaptationSwitchingInfos[id]; - if (switchingInfos !== undefined && - switchingInfos.newID !== newID && - arrayIncludes(switchingInfos.adaptationSetSwitchingIDs, originalID)) + // look if we have to merge this into another Adaptation + let mergedIntoIdx = -1; + for (const id of adaptationSetSwitchingIDs) { + const switchingInfos = adaptationSwitchingInfos[id]; + if (switchingInfos !== undefined && + switchingInfos.newID !== newID && + arrayIncludes(switchingInfos.adaptationSetSwitchingIDs, originalID)) + { + mergedIntoIdx = arrayFindIndex(parsedAdaptations[type], + (a) => a[0].id === id); + const mergedInto = parsedAdaptations[type][mergedIntoIdx]; + if (mergedInto !== undefined && + mergedInto[0].audioDescription === + parsedAdaptationSet.audioDescription && + mergedInto[0].closedCaption === + parsedAdaptationSet.closedCaption && + mergedInto[0].language === parsedAdaptationSet.language) { - mergedIntoIdx = arrayFindIndex(parsedAdaptations[type], - (a) => a[0].id === id); - const mergedInto = parsedAdaptations[type][mergedIntoIdx]; - if (mergedInto !== undefined && - mergedInto[0].audioDescription === - parsedAdaptationSet.audioDescription && - mergedInto[0].closedCaption === - parsedAdaptationSet.closedCaption && - mergedInto[0].language === parsedAdaptationSet.language) - { - log.info("DASH Parser: merging \"switchable\" AdaptationSets", - originalID, id); - mergedInto[0].representations.push(...parsedAdaptationSet.representations); - if (type === "video" && - isMainAdaptation && - !mergedInto[1].isMainAdaptation) - { - lastMainVideoAdapIdx = Math.max(lastMainVideoAdapIdx, mergedIntoIdx); - } - mergedInto[1] = { - priority: Math.max(priority, mergedInto[1].priority), - isMainAdaptation: isMainAdaptation || - mergedInto[1].isMainAdaptation, - indexInMpd: Math.min(adaptationIdx, mergedInto[1].indexInMpd), - }; - } + log.info("DASH Parser: merging \"switchable\" AdaptationSets", + originalID, id); + mergedInto[0].representations.push(...parsedAdaptationSet.representations); + mergedInto[1] = { + priority: Math.max(priority, mergedInto[1].priority), + isMainAdaptation: isMainAdaptation || + mergedInto[1].isMainAdaptation, + indexInMpd: Math.min(adaptationIdx, mergedInto[1].indexInMpd), + }; } } + } - if (mergedIntoIdx < 0) { - parsedAdaptations[type].push([ parsedAdaptationSet, - { priority, - isMainAdaptation, - indexInMpd: adaptationIdx }]); - if (type === "video" && isMainAdaptation) { - lastMainVideoAdapIdx = parsedAdaptations.video.length - 1; - } - } + if (mergedIntoIdx < 0) { + parsedAdaptations[type].push([ parsedAdaptationSet, + { priority, + isMainAdaptation, + indexInMpd: adaptationIdx }]); } } diff --git a/src/parsers/manifest/dash/common/parse_representations.ts b/src/parsers/manifest/dash/common/parse_representations.ts index c80b5e59d4..40ca70ca39 100644 --- a/src/parsers/manifest/dash/common/parse_representations.ts +++ b/src/parsers/manifest/dash/common/parse_representations.ts @@ -66,16 +66,17 @@ function combineInbandEventStreams( */ function getHDRInformation( { adaptationProfiles, + essentialProperties, + supplementalProperties, manifestProfiles, codecs, }: { adaptationProfiles?: string | undefined; + essentialProperties? : IScheme[] | undefined; + supplementalProperties? : IScheme[] | undefined; manifestProfiles?: string | undefined; codecs?: string | undefined; } ): undefined | IHDRInformation { const profiles = (adaptationProfiles ?? "") + (manifestProfiles ?? ""); - if (codecs === undefined) { - return undefined; - } if ( profiles.indexOf( "http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10") !== -1 @@ -87,7 +88,20 @@ function getHDRInformation( colorSpace: "rec2020" }; } } - if (/^vp(08|09|10)/.exec(codecs)) { + const transferCharacteristicScheme = arrayFind( + [...(essentialProperties ?? []), ...(supplementalProperties ?? [])], + (p) => p.schemeIdUri === "urn:mpeg:mpegB:cicp:TransferCharacteristics"); + if (transferCharacteristicScheme !== undefined) { + switch (transferCharacteristicScheme.value) { + case "15": + return undefined; // SDR + case "16": + return { eotf: "pq" }; + case "18": + return { eotf: "hlg" }; + } + } + if (codecs !== undefined && /^vp(08|09|10)/.exec(codecs)) { return getWEBMHDRInformation(codecs); } } @@ -273,6 +287,9 @@ export default function parseRepresentations( parsedRepresentation.hdrInfo = getHDRInformation({ adaptationProfiles: adaptation.attributes.profiles, + supplementalProperties: adaptation.children + .supplementalProperties, + essentialProperties: adaptation.children.essentialProperties, manifestProfiles: context.manifestProfiles, codecs }); diff --git a/src/parsers/manifest/dash/js-parser/node_parsers/SegmentBase.ts b/src/parsers/manifest/dash/js-parser/node_parsers/SegmentBase.ts index 99daed51aa..c0f2cd3863 100644 --- a/src/parsers/manifest/dash/js-parser/node_parsers/SegmentBase.ts +++ b/src/parsers/manifest/dash/js-parser/node_parsers/SegmentBase.ts @@ -99,6 +99,12 @@ export default function parseSegmentBase( parser: parseMPDInteger, dashName: "startNumber" }); break; + + case "endNumber": + parseValue(attr.value, { asKey: "endNumber", + parser: parseMPDInteger, + dashName: "endNumber" }); + break; } } diff --git a/src/parsers/manifest/dash/node_parser_types.ts b/src/parsers/manifest/dash/node_parser_types.ts index 7b32016b84..dec2ec3cb7 100644 --- a/src/parsers/manifest/dash/node_parser_types.ts +++ b/src/parsers/manifest/dash/node_parser_types.ts @@ -285,6 +285,7 @@ export interface ISegmentBaseIntermediateRepresentation { media?: string; presentationTimeOffset?: number; startNumber? : number; + endNumber? : number; timescale?: number; } @@ -299,6 +300,7 @@ export interface ISegmentListIntermediateRepresentation { media?: string; presentationTimeOffset?: number; startNumber? : number; + endNumber? : number; timescale?: number; } @@ -348,6 +350,7 @@ export interface ISegmentTemplateIntermediateRepresentation { media? : string | undefined; presentationTimeOffset? : number | undefined; startNumber? : number | undefined; + endNumber? : number | undefined; timescale? : number | undefined; initialization? : { media?: string } | undefined; timeline? : ISegmentTimelineElement[] | undefined; diff --git a/src/parsers/manifest/dash/wasm-parser/rs/events.rs b/src/parsers/manifest/dash/wasm-parser/rs/events.rs index 075470bf9f..c6cf3724bc 100644 --- a/src/parsers/manifest/dash/wasm-parser/rs/events.rs +++ b/src/parsers/manifest/dash/wasm-parser/rs/events.rs @@ -282,6 +282,9 @@ pub enum AttributeName { Label = 71, // String ServiceLocation = 72, // String + + // SegmentTemplate + EndNumber = 76, // f64 } impl TagName { diff --git a/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs b/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs index 886c80c538..00cd0c0385 100644 --- a/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs +++ b/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs @@ -173,6 +173,8 @@ pub fn report_segment_template_attrs(tag_bs : &quick_xml::events::BytesStart) { Duration.try_report_as_u64(&attr), b"startNumber" => StartNumber.try_report_as_u64(&attr), + b"endNumber" => + EndNumber.try_report_as_u64(&attr), b"media" => Media.try_report_as_string(&attr), b"bitstreamSwitching" => BitstreamSwitching.try_report_as_bool(&attr), _ => {}, @@ -201,6 +203,8 @@ pub fn report_segment_base_attrs(tag_bs : &quick_xml::events::BytesStart) { Duration.try_report_as_u64(&attr), b"startNumber" => StartNumber.try_report_as_u64(&attr), + b"endNumber" => + EndNumber.try_report_as_u64(&attr), _ => {}, }, Err(err) => ParsingError::from(err).report_err(), diff --git a/src/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.ts b/src/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.ts index 4e309d0791..5a9135cdb4 100644 --- a/src/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.ts +++ b/src/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.ts @@ -107,9 +107,9 @@ export default class DashWasmParser { * future improvements. */ private _isParsing : boolean; + /** * Create a new `DashWasmParser`. - * @param {object} opts */ constructor() { this._parsersStack = new ParsersStack(); diff --git a/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentBase.ts b/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentBase.ts index 65dddb2aa3..ea662fd52e 100644 --- a/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentBase.ts +++ b/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentBase.ts @@ -104,6 +104,12 @@ export function generateSegmentBaseAttrParser( break; } + case AttributeName.EndNumber: { + const dataView = new DataView(linearMemory.buffer); + segmentBaseAttrs.endNumber = dataView.getFloat64(ptr, true); + break; + } + } }; } diff --git a/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentTemplate.ts b/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentTemplate.ts index de084949d0..1aea7deff0 100644 --- a/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentTemplate.ts +++ b/src/parsers/manifest/dash/wasm-parser/ts/generators/SegmentTemplate.ts @@ -121,6 +121,13 @@ export function generateSegmentTemplateAttrParser( break; } + case AttributeName.EndNumber: { + const dataView = new DataView(linearMemory.buffer); + segmentTemplateAttrs.endNumber = + dataView.getFloat64(ptr, true); + break; + } + } }; } diff --git a/src/parsers/manifest/dash/wasm-parser/ts/types.ts b/src/parsers/manifest/dash/wasm-parser/ts/types.ts index 571bb9a15a..ef0733f47a 100644 --- a/src/parsers/manifest/dash/wasm-parser/ts/types.ts +++ b/src/parsers/manifest/dash/wasm-parser/ts/types.ts @@ -290,4 +290,7 @@ export const enum AttributeName { ProxyServerUrl = 74, // String DefaultServiceLocation = 75, + + // SegmentTemplate + EndNumber = 76, // f64 } diff --git a/src/parsers/manifest/types.ts b/src/parsers/manifest/types.ts index 6b829e4e17..73332dd8bf 100644 --- a/src/parsers/manifest/types.ts +++ b/src/parsers/manifest/types.ts @@ -197,6 +197,13 @@ export interface IParsedAdaptation { * a video track). */ closedCaption? : boolean | undefined; + /** + * If `true` this Adaptation are subtitles Meant for display when no other text + * Adaptation is selected. It is used to clarify dialogue, alternate + * languages, texted graphics or location/person IDs that are not otherwise + * covered in the dubbed/localized audio Adaptation. + */ + forcedSubtitles? : boolean; /** * If true this Adaptation is in a dub: it was recorded in another language * than the original(s) one(s). diff --git a/src/public_types.ts b/src/public_types.ts index e64ae9bd7f..a51ba54a27 100644 --- a/src/public_types.ts +++ b/src/public_types.ts @@ -244,6 +244,7 @@ export interface IAdaptation { normalizedLanguage? : string | undefined; isAudioDescription? : boolean | undefined; isClosedCaption? : boolean | undefined; + isSignInterpreted? : boolean | undefined; isTrickModeTrack? : boolean | undefined; representations : IRepresentation[]; @@ -636,6 +637,7 @@ export type IAudioTrackPreference = null | /** Single preference for a text track Adaptation. */ export type ITextTrackPreference = null | { language : string; + forced? : boolean | undefined; closedCaption : boolean; }; /** Single preference for a video track Adaptation. */ @@ -763,6 +765,7 @@ export interface IAudioTrack { language : string; export interface ITextTrack { language : string; normalized : string; closedCaption : boolean; + forced? : boolean | undefined; label? : string | undefined; id : number|string; } @@ -786,6 +789,14 @@ export interface IVideoTrack { id : number|string; label? : string | undefined; representations: IVideoRepresentation[]; } +/** Output of the `getKeySystemConfiguration` method. */ +export interface IKeySystemConfigurationOutput { + /** Key system string. */ + keySystem : string; + /** `MediaKeySystemConfiguration` actually used by the key system. */ + configuration : MediaKeySystemConfiguration; +} + /** Audio track from a list of audio tracks returned by the RxPlayer. */ export interface IAvailableAudioTrack extends IAudioTrack { active : boolean } diff --git a/src/transports/README.md b/src/transports/README.md index 6b8582c3f7..0f45fa110f 100644 --- a/src/transports/README.md +++ b/src/transports/README.md @@ -94,17 +94,17 @@ Its concept can be illustrated as such: ``` As the wanted resource could be obtained asynchronously (like when an HTTP -request has to be performed), the loader returns an Observable and the resource -is then emitted through it. +request has to be performed), the loader returns a Promise which resolves once +the full resource is loaded. -This Observable will throw on any problem arising during that step, such as an +This Promise will reject on any problem arising during that step, such as an HTTP error. In some specific conditions, the loader can also emit the wanted resource in multiple sub-parts. This allows for example to play a media file while still downloading it and is at the basis of low-latency streaming. To allow such use cases, the segment loaders can also emit the wanted resource -by cutting it into chunks and emitting them through the Observable as they are +by cutting it into chunks and emitting them through a callback as it becomes available. This is better explained in the related chapter below. @@ -133,8 +133,8 @@ Its concept can be illustrated as such: Depending on the type of parser (e.g. Manifest parser or segment parser), that task can be synchronous or asynchronous. -In asynchronous cases, the parser will return an Observable emitting a unique -time the result when done and throwing if an error is encountered. +In asynchronous cases, the parser will return a Promise resolving with +the result when done and rejecting if an error is encountered. In synchronous cases, the parser returns directly the result, and can throw directly when/if an error is encountered. @@ -152,7 +152,7 @@ just for those requests. The Manifest loader is the "loader" downloading the Manifest (or MPD) file. It is a function which receives as argument the URL of the manifest and then -returns an Observable emitting a single time the corresponding Manifest when it +returns a Promise resolving with the corresponding loaded Manifest when it finished downloading it: ``` INPUT: OUTPUT: @@ -178,8 +178,8 @@ allowing it to ask for supplementary requests before completing (e.g. to fetch the current time from an URL or to load sub-parts of the Manifests only known at parse-time). -This function returns an Observable wich emits a single time the parsed -Manifest: +This function returns either the parsed Manifest object directly or wrapped in a +Promise: ``` INPUT: OUTPUT: ------ ------- @@ -208,8 +208,7 @@ It receives information linked to the segment you want to download: - The `Representation` it is linked to - The `Segment` object it is linked to -It then return an Observable which send events as it loads the corresponding -segment. +It then return a Promise resolving when the segment is loaded. ``` INPUT: OUTPUT: @@ -239,50 +238,12 @@ The latter mode is usually active under the following conditions: - the segment is in a CMAF container - the `Fetch` JS API is available -In most other cases, it will be in the regular mode. +In most other cases, it will be in the regular mode, where the segment is fully +communicated as the returned Promise resolves. -You can deduce which mode we are in simply by looking a the events the loader -sends. - -In the regular mode, any of the following events can be sent through the -Observable: - - - `"progress"`: We have new metrics on the current download (e.g. the amount - currently downloaded, the time since the beginning of the request...) - - - `"data-created"`: The segment is available without needing to perform a - network request. This is usually the case when segments are generated like - Smooth Streaming's initialization segments. - The segment's data is also communicated via this event. - - The `"data-created"` event, when sent, is the last event sent from the - loader. The loader will complete just after emitting it. - - - `"data-loaded"`: The segment has been compeletely downloaded from the - network. The segment's data is also communicated via this event. - - Like `"data-created"`, the `"data-loaded"` will be the last event sent by - the loader. - This means that you will either have a single `"data-created"` event or a - single `"data-loaded"` event with the data when the segment has been loaded - succesfully. - -In the low-latency mode, the following events can be sent instead: - - - `"progress"`: We have new metrics on the current download (e.g. the amount - currently downloaded, the time since the beginning of the request...) - - - `"data-chunk"`: A sub-segment (or chunk) of the data is currently available. - The corresponding sub-segment is communicated in the payload of this event. - - This event can be communicated multiple times until a - `"data-chunk-complete"` event is received. - - - `"data-chunk-complete"`: The segment request just finished. All - corresponding data has been sent through `"data-chunk"` events. - - If sent, this is the last event sent by a segment loader. The loader will - complete just after emitting it. +In the low-latency mode, chunks of the data are sent through a callback given +to the segment loaded and the promise only resolves once all chunks have been +communicated that way. diff --git a/src/transports/dash/add_segment_integrity_checks_to_loader.ts b/src/transports/dash/add_segment_integrity_checks_to_loader.ts index 9cf0234b14..d9f37acd81 100644 --- a/src/transports/dash/add_segment_integrity_checks_to_loader.ts +++ b/src/transports/dash/add_segment_integrity_checks_to_loader.ts @@ -31,11 +31,9 @@ export default function addSegmentIntegrityChecks( ) : ISegmentLoader { return (url, content, loaderOptions, initialCancelSignal, callbacks) => { return new Promise((resolve, reject) => { - const requestCanceller = new TaskCanceller({ cancelOn: initialCancelSignal }); - - // Reject the `CancellationError` when `requestCanceller`'s signal emits - // `stopRejectingOnCancel` here is a function allowing to stop this mechanism - const stopRejectingOnCancel = requestCanceller.signal.register(reject); + const requestCanceller = new TaskCanceller(); + const unlinkCanceller = requestCanceller.linkToSignal(initialCancelSignal); + requestCanceller.signal.register(reject); segmentLoader(url, content, loaderOptions, requestCanceller.signal, { ...callbacks, @@ -45,31 +43,42 @@ export default function addSegmentIntegrityChecks( callbacks.onNewChunk(data); } catch (err) { // Do not reject with a `CancellationError` after cancelling the request - stopRejectingOnCancel(); + cleanUpCancellers(); + // Cancel the request requestCanceller.cancel(); + // Reject with thrown error reject(err); } }, - }).then((info) => { - if (requestCanceller.isUsed) { - return; - } - stopRejectingOnCancel(); - if (info.resultType === "segment-loaded") { - try { - trowOnIntegrityError(info.resultData.responseData); - } catch (err) { + }) + .then( + (info) => { + cleanUpCancellers(); + if (requestCanceller.isUsed()) { + return; + } + if (info.resultType === "segment-loaded") { + try { + trowOnIntegrityError(info.resultData.responseData); + } catch (err) { + reject(err); + return; + } + } + resolve(info); + }, + (err : unknown) => { + cleanUpCancellers(); reject(err); - return; } - } - resolve(info); - }, (error : unknown) => { - stopRejectingOnCancel(); - reject(error); - }); + ); + + function cleanUpCancellers() { + requestCanceller.signal.deregister(reject); + unlinkCanceller(); + } }); /** diff --git a/src/transports/dash/low_latency_segment_loader.ts b/src/transports/dash/low_latency_segment_loader.ts index f7997a39cd..e798b347b8 100644 --- a/src/transports/dash/low_latency_segment_loader.ts +++ b/src/transports/dash/low_latency_segment_loader.ts @@ -65,14 +65,14 @@ export default function lowLatencySegmentLoader( partialChunk = res[1]; for (let i = 0; i < completeChunks.length; i++) { callbacks.onNewChunk(completeChunks[i]); - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } } callbacks.onProgress({ duration: info.duration, size: info.size, totalSize: info.totalSize }); - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } } diff --git a/src/transports/dash/manifest_parser.ts b/src/transports/dash/manifest_parser.ts index 5f25143191..efda8abb46 100644 --- a/src/transports/dash/manifest_parser.ts +++ b/src/transports/dash/manifest_parser.ts @@ -115,7 +115,7 @@ export default function generateManifestParser( * Parse the MPD through the default JS-written parser (as opposed to the * WebAssembly one). * If it is not defined, throws. - * @returns {Observable} + * @returns {Object|Promise.} */ function runDefaultJsParser() { if (parsers.js === null) { @@ -130,7 +130,7 @@ export default function generateManifestParser( * Process return of one of the MPD parser. * If it asks for a resource, load it then continue. * @param {Object} parserResponse - Response returned from a MPD parser. - * @returns {Observable} + * @returns {Object|Promise.} */ function processMpdParserResponse( parserResponse : IDashParserResponse | IDashParserResponse @@ -139,7 +139,7 @@ export default function generateManifestParser( if (parserResponse.value.warnings.length > 0) { onWarnings(parserResponse.value.warnings); } - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return Promise.reject(cancelSignal.cancellationError); } const manifest = new Manifest(parserResponse.value.parsed, options); diff --git a/src/transports/dash/segment_loader.ts b/src/transports/dash/segment_loader.ts index 6f450dc19b..3447c769be 100644 --- a/src/transports/dash/segment_loader.ts +++ b/src/transports/dash/segment_loader.ts @@ -107,7 +107,7 @@ export default function generateSegmentLoader( /** * @param {Object|null} wantedCdn - * @returns {Observable} + * @returns {Promise.} */ function segmentLoader( wantedCdn : ICdnMetadata | null, @@ -156,7 +156,7 @@ export default function generateSegmentLoader( size? : number | undefined; duration? : number | undefined; } ) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -172,7 +172,7 @@ export default function generateSegmentLoader( * @param {*} err - The corresponding error encountered */ const reject = (err : unknown) : void => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -198,7 +198,7 @@ export default function generateSegmentLoader( size : number; totalSize? : number | undefined; } ) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } callbacks.onProgress({ duration: _args.duration, @@ -211,7 +211,7 @@ export default function generateSegmentLoader( * the "regular" implementation */ const fallback = () => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; diff --git a/src/transports/dash/text_parser.ts b/src/transports/dash/text_parser.ts index 186b10d432..9e44650359 100644 --- a/src/transports/dash/text_parser.ts +++ b/src/transports/dash/text_parser.ts @@ -55,7 +55,7 @@ import { * @param {boolean} __priv_patchLastSegmentInSidx - Enable ugly Canal+-specific * fix for an issue people on the content-packaging side could not fix. * For more information on that, look at the code using it. - * @returns {Observable.} + * @returns {Object} */ function parseISOBMFFEmbeddedTextTrack( data : ArrayBuffer | Uint8Array | string, @@ -133,7 +133,7 @@ function parseISOBMFFEmbeddedTextTrack( * @param {Object} content - Object describing the context of the given * segment's data: of which segment, `Representation`, `Adaptation`, `Period`, * `Manifest` it is a part of etc. - * @returns {Observable.} + * @returns {Object} */ function parsePlainTextTrack( data : ArrayBuffer | Uint8Array | string, @@ -188,7 +188,7 @@ export default function generateTextTrackParser( * @param {Object} loadedSegment * @param {Object} content * @param {number|undefined} initTimescale - * @returns {Observable.} + * @returns {Object} */ return function textTrackParser( loadedSegment : { data : ArrayBuffer | Uint8Array | string | null; diff --git a/src/transports/local/segment_loader.ts b/src/transports/local/segment_loader.ts index 0175439961..830aea7556 100644 --- a/src/transports/local/segment_loader.ts +++ b/src/transports/local/segment_loader.ts @@ -20,11 +20,9 @@ import { ILocalManifestInitSegmentLoader, ILocalManifestSegmentLoader, } from "../../parsers/manifest/local"; +import createCancellablePromise from "../../utils/create_cancellable_promise"; import isNullOrUndefined from "../../utils/is_null_or_undefined"; -import { - CancellationError, - CancellationSignal, -} from "../../utils/task_canceller"; +import { CancellationSignal } from "../../utils/task_canceller"; import { ISegmentContext, ISegmentLoaderCallbacks, @@ -41,7 +39,7 @@ function loadInitSegment( customSegmentLoader : ILocalManifestInitSegmentLoader, cancelSignal : CancellationSignal ) : Promise> { - return new Promise((res, rej) => { + return createCancellablePromise(cancelSignal, (res, rej) => { /** `true` when the custom segmentLoader should not be active anymore. */ let hasFinished = false; @@ -54,11 +52,10 @@ function loadInitSegment( size? : number; duration? : number; }) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; - cancelSignal.deregister(abortLoader); res({ resultType: "segment-loaded", resultData: { responseData: _args.data, size: _args.size, @@ -70,22 +67,17 @@ function loadInitSegment( * @param {*} err - The corresponding error encountered */ const reject = (err? : Error) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; - cancelSignal.deregister(abortLoader); rej(err); }; const abort = customSegmentLoader({ resolve, reject }); - cancelSignal.register(abortLoader); - /** - * The logic to run when this loader is cancelled while pending. - * @param {Error} err - */ - function abortLoader(err : CancellationError) { + /** The logic to run when this loader is cancelled while pending. */ + return () => { if (hasFinished) { return; } @@ -93,8 +85,7 @@ function loadInitSegment( if (typeof abort === "function") { abort(); } - rej(err); - } + }; }); } @@ -102,14 +93,14 @@ function loadInitSegment( * @param {Object} segment * @param {Function} customSegmentLoader * @param {Object} cancelSignal - * @returns {Observable} + * @returns {Promise.} */ function loadSegment( segment : { time : number; duration : number; timestampOffset? : number }, customSegmentLoader : ILocalManifestSegmentLoader, cancelSignal : CancellationSignal ) : Promise< ISegmentLoaderResultSegmentLoaded> { - return new Promise((res, rej) => { + return createCancellablePromise(cancelSignal, (res, rej) => { /** `true` when the custom segmentLoader should not be active anymore. */ let hasFinished = false; @@ -122,11 +113,10 @@ function loadSegment( size? : number; duration? : number; }) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; - cancelSignal.deregister(abortLoader); res({ resultType: "segment-loaded", resultData: { responseData: _args.data, size: _args.size, @@ -138,11 +128,10 @@ function loadSegment( * @param {*} err - The corresponding error encountered */ const reject = (err? : Error) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; - cancelSignal.deregister(abortLoader); // Format error and send it const castedErr = err as (null | undefined | { message? : string; @@ -161,12 +150,8 @@ function loadSegment( const abort = customSegmentLoader(segment, { resolve, reject }); - cancelSignal.register(abortLoader); - /** - * The logic to run when this loader is cancelled while pending. - * @param {Error} err - */ - function abortLoader(err : CancellationError) { + /** The logic to run when this loader is cancelled while pending. */ + return () => { if (hasFinished) { return; } @@ -174,8 +159,7 @@ function loadSegment( if (typeof abort === "function") { abort(); } - rej(err); - } + }; }); } diff --git a/src/transports/smooth/segment_loader.ts b/src/transports/smooth/segment_loader.ts index b77b0df091..a218f12d9a 100644 --- a/src/transports/smooth/segment_loader.ts +++ b/src/transports/smooth/segment_loader.ts @@ -185,7 +185,7 @@ const generateSegmentLoader = ({ size? : number | undefined; duration? : number | undefined; }) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -213,7 +213,7 @@ const generateSegmentLoader = ({ * @param {*} err - The corresponding error encountered */ const reject = (err : unknown) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -239,7 +239,7 @@ const generateSegmentLoader = ({ size : number; totalSize? : number | undefined; } ) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } callbacks.onProgress({ duration: _args.duration, @@ -248,7 +248,7 @@ const generateSegmentLoader = ({ }; const fallback = () => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; diff --git a/src/transports/utils/call_custom_manifest_loader.ts b/src/transports/utils/call_custom_manifest_loader.ts index 25d28cc2f6..e8f29a73bc 100644 --- a/src/transports/utils/call_custom_manifest_loader.ts +++ b/src/transports/utils/call_custom_manifest_loader.ts @@ -62,7 +62,7 @@ export default function callCustomManifestLoader( receivingTime? : number | undefined; sendingTime? : number | undefined; }) => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -87,7 +87,7 @@ export default function callCustomManifestLoader( * @param {*} err - The corresponding error encountered */ const reject = (err : unknown) : void => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; @@ -113,7 +113,7 @@ export default function callCustomManifestLoader( * the "regular" implementation */ const fallback = () => { - if (hasFinished || cancelSignal.isCancelled) { + if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; diff --git a/src/typings/globals.d.ts b/src/typings/globals.d.ts index 1c71f16743..7384887ec1 100644 --- a/src/typings/globals.d.ts +++ b/src/typings/globals.d.ts @@ -33,6 +33,7 @@ declare const __FEATURES__ : { HTML_VTT : number; LOCAL_MANIFEST : number; METAPLAYLIST : number; + DEBUG_ELEMENT : number; NATIVE_SAMI : number; NATIVE_SRT : number; NATIVE_TTML : number; @@ -54,6 +55,7 @@ declare const enum FEATURES_ENUM { HTML_VTT, LOCAL_MANIFEST, METAPLAYLIST, + DEBUG_ELEMENT, NATIVE_SAMI, NATIVE_SRT, NATIVE_TTML, diff --git a/src/utils/__tests__/cast_to_observable.test.ts b/src/utils/__tests__/cast_to_observable.test.ts deleted file mode 100644 index c266cfdbcc..0000000000 --- a/src/utils/__tests__/cast_to_observable.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/ban-types */ - -import { Observable } from "rxjs"; -import castToObservable from "../cast_to_observable"; - -describe("utils - castToObservable", () => { - it("should return the argument if already an Observable", () => { - const obs = new Observable(); - expect(castToObservable(obs)).toBe(obs); - }); - - it("should convert promise's then to next", (done) => { - let resolve : ((str : string) => void)|undefined; - const emitItem = "je n'ai plus peur de, perdre mes dents"; - const prom = new Promise((res) => { - resolve = res; - }); - - let numberOfItemEmitted = 0; - castToObservable(prom).subscribe({ - next: (x) => { - numberOfItemEmitted++; - expect(x).toBe(emitItem); - }, - complete: () => { - expect(numberOfItemEmitted).toBe(1); - done(); - }, - }); - - if (resolve === undefined) { - throw new Error(); - } - resolve(emitItem); - }); - - it("should convert promise's error to Observable's error", (done) => { - let reject : ((str : string) => void)|undefined; - const errorItem = "je n'ai plus peur de, perdre mon temps"; - const prom = new Promise((_, rej) => { - reject = rej; - }); - - let numberOfItemEmitted = 0; - castToObservable(prom).subscribe({ - next: () => { - numberOfItemEmitted++; - }, - error: (err) => { - expect(numberOfItemEmitted).toBe(0); - expect(err).toBe(errorItem); - done(); - }, - }); - if (reject === undefined) { - throw new Error(); - } - reject(errorItem); - }); - - it("should wrap other values in an rxJS Observable", (done) => { - const err = new Error("TEST"); - const obs = castToObservable(err); - let nextHasBeenCalled = 0; - obs.subscribe({ - next: (e) => { - nextHasBeenCalled++; - expect(e).toBeInstanceOf(Error); - expect(e.message).toBe("TEST"); - }, - complete: () => { - expect(nextHasBeenCalled).toBe(1); - done(); - }, - }); - }); -}); diff --git a/src/utils/__tests__/concat_map_latest.test.ts b/src/utils/__tests__/concat_map_latest.test.ts deleted file mode 100644 index 662de58549..0000000000 --- a/src/utils/__tests__/concat_map_latest.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - concat as observableConcat, - concatMap, - interval, - map, - merge as observableMerge, - Observable, - Subject, - of as observableOf, - take, - tap, - timer, -} from "rxjs"; -import concatMapLatest from "../concat_map_latest"; - -describe("utils - concatMapLatest", () => { - it("should act as a mergeMap for a single value", (done) => { - const counter$ : Observable = observableOf(0); - let itemReceived = false; - counter$.pipe( - concatMapLatest(observableOf) - ).subscribe({ - next(res: number) : void { - expect(res).toBe(0); - itemReceived = true; - }, - complete() { - expect(itemReceived).toBe(true); - done(); - }, - }); - }); - - /* eslint-disable max-len */ - it("should consider all values if precedent inner Observable finished synchronously", (done) => { - /* eslint-enable max-len */ - const innerValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - const innerValuesLength = innerValues.length; - let lastCount: number|undefined; - - const counter$ : Observable = observableOf(...innerValues); - counter$.pipe( - concatMapLatest((i: number, count: number) => { - lastCount = count; - return observableOf(i); - }) - ).subscribe({ - next(res: number) { - const expectedResult = innerValues.shift(); - expect(res).toBe(expectedResult); - }, - complete() { - if (innerValues.length !== 0) { - throw new Error("Not all values were received."); - } - expect(lastCount).toBe(innerValuesLength - 1); - done(); - }, - }); - }); - - /* eslint-disable max-len */ - it("should consider all values if precedent inner Observable had time to finish", (done) => { - /* eslint-enable max-len */ - const innerValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - const innerValuesLength = innerValues.length; - let lastCount: number|undefined; - - const counter$ : Observable = observableOf(...innerValues).pipe( - concatMap((v) => timer(5).pipe(map(() => v))) - ); - counter$.pipe( - concatMapLatest((i: number, count: number) => { - lastCount = count; - return observableOf(i); - }) - ).subscribe({ - next(res: number) { - const expectedResult = innerValues.shift(); - expect(res).toBe(expectedResult); - }, - complete() { - if (innerValues.length !== 0) { - throw new Error("Not all values were received."); - } - expect(lastCount).toBe(innerValuesLength - 1); - done(); - }, - }); - }); - - /* eslint-disable max-len */ - it("should skip all inner values but the last when the inner Observable completes", (done) => { - /* eslint-enable max-len */ - - const counter$ = new Subject(); - let itemEmittedCounter = 0; - let itemProcessedCounter = 0; - let lastCount: number|undefined; - - counter$.pipe( - tap(() => { itemEmittedCounter++; }), - concatMapLatest((i: number, count: number) => { - lastCount = count; - return timer(230).pipe(map(() => i)); - }) - ).subscribe({ - next(result: number) { - switch (itemProcessedCounter++) { - case 0: - expect(result).toBe(0); - counter$.next(3); // should be ignored - counter$.next(4); - break; - case 1: - expect(result).toBe(4); - counter$.complete(); - break; - default: - throw new Error("Should not have emitted that item"); - } - }, - complete() { - expect(itemEmittedCounter).toBe(5); - expect(itemProcessedCounter).toBe(2); - expect(lastCount).toBe(itemProcessedCounter - 1); - done(); - }, - }); - - counter$.next(0); - counter$.next(1); // should be ignored - counter$.next(2); // should be ignored - }); - - /* eslint-disable max-len */ - it("should increment the counter each times the callback is called", (done) => { - /* eslint-enable max-len */ - - let itemProcessed = 0; - let nextCount = 0; - const obs1$ = observableOf(1, 2, 3); - const obs2$ = observableOf(4, 5); - const obs3$ = observableOf(6, 7, 8, 9); - - observableOf( - [0, obs1$] as [number, Observable], - [1, obs2$] as [number, Observable], - [2, obs3$] as [number, Observable] - ).pipe( - concatMapLatest(([wantedCounter, obs$], counter) => { - itemProcessed++; - expect(counter).toBe(wantedCounter); - return obs$; - }) - ).subscribe({ - next() { nextCount++; }, - complete() { - expect(itemProcessed).toBe(3); - expect(nextCount).toBe(3 + 2 + 4); - done(); - }, - }); - }); - - it("should reset the counter for each subscription", async () => { - const base$ = interval(10).pipe(take(10)); - const counter$ = base$.pipe(concatMapLatest((_, i) => observableOf(i))); - - function validateThroughMerge() { - let nextCount = 0; - return new Promise(res => { - observableMerge(counter$, counter$, counter$).subscribe({ - next(item) { - expect(item).toBe(Math.floor(nextCount / 3)); - nextCount++; - }, - complete() { - expect(nextCount).toBe(30); - res(); - }, - }); - }); - } - - function validateThroughConcat() { - let nextCount = 0; - return new Promise(res => { - observableConcat(counter$, counter$, counter$).subscribe({ - next(item) { - expect(item).toBe(nextCount % 10); - nextCount++; - }, - complete() { - expect(nextCount).toBe(30); - res(); - }, - }); - }); - } - - // eslint-disable-next-line no-restricted-properties - await Promise.all([validateThroughConcat(), validateThroughMerge()]); - }); -}); diff --git a/src/utils/__tests__/defer_subscriptions.test.ts b/src/utils/__tests__/defer_subscriptions.test.ts deleted file mode 100644 index 745fcb02cd..0000000000 --- a/src/utils/__tests__/defer_subscriptions.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - map, - share, - startWith, - timer, -} from "rxjs"; -import deferSubscriptions from "../defer_subscriptions"; - -describe("utils - deferSubscriptions", () => { - /* eslint-disable max-len */ - it("should wait until all subscription in the current script are done before emitting", (done) => { - /* eslint-enable max-len */ - let logs = ""; - const myObservableDeferred = timer(5).pipe(map(() => "A"), - startWith("S"), - deferSubscriptions(), - share()); - - myObservableDeferred.subscribe({ - next: x => { logs += `1:${x}-`; }, - error: () => { /* noop */ }, - complete: () => { - expect(logs).toEqual("1:S-2:S-1:A-2:A-3:A-4:A-"); - done(); - }, - }); - myObservableDeferred.subscribe(x => { logs += `2:${x}-`; }); - - setTimeout(() => { - myObservableDeferred.subscribe(x => { logs += `3:${x}-`; }); - myObservableDeferred.subscribe(x => { logs += `4:${x}-`; }); - }, 1); - }); -}); diff --git a/src/utils/__tests__/event_emitter.test.ts b/src/utils/__tests__/event_emitter.test.ts index 527d658a20..b178278317 100644 --- a/src/utils/__tests__/event_emitter.test.ts +++ b/src/utils/__tests__/event_emitter.test.ts @@ -19,11 +19,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { take } from "rxjs"; import log from "../../log"; -import EventEmitter, { - fromEvent, -} from "../event_emitter"; +import EventEmitter from "../event_emitter"; describe("utils - EventEmitter", () => { it("should be able to call synchronously a callback on a given event", () => { @@ -580,75 +577,3 @@ describe("utils - EventEmitter", () => { eventEmitter.removeEventListener(); }); }); - -describe("utils - fromEvent", () => { - it("should subscribe to a given event", (done) => { - let stringItemsReceived = 0; - let numberItemsReceived = 0; - const eventEmitter = new EventEmitter<{ - test: undefined|"a"|{ a: string }; - fooba: undefined|number|"a"|"b"|"c"|{ a: string }; - }>(); - fromEvent(eventEmitter, "fooba") - .pipe(take(6)) - .subscribe({ - next(item) { - if (typeof item === "number") { - numberItemsReceived++; - } else if (typeof item === "string") { - stringItemsReceived++; - } - }, - complete() { - (eventEmitter as any).trigger("fooba", 6); - expect(numberItemsReceived).toBe(2); - expect(stringItemsReceived).toBe(3); - done(); - }, - }); - - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("fooba", undefined); - (eventEmitter as any).trigger("fooba", 5); - (eventEmitter as any).trigger("fooba", "a"); - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("fooba", "b"); - (eventEmitter as any).trigger("fooba", "c"); - (eventEmitter as any).trigger("fooba", 6); - }); - - it("should remove the event listener on unsubscription", () => { - let stringItemsReceived = 0; - let numberItemsReceived = 0; - const eventEmitter = new EventEmitter<{ - test: undefined|"a"|{ a: string }; - fooba: undefined|number|"a"|"b"|"c"|{ a: string }; - }>(); - const subscription = fromEvent(eventEmitter, "fooba") - .pipe(take(6)) - .subscribe((item) => { - if (typeof item === "number") { - numberItemsReceived++; - } else if (typeof item === "string") { - stringItemsReceived++; - } - }); - - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("fooba", undefined); - (eventEmitter as any).trigger("fooba", 5); - (eventEmitter as any).trigger("fooba", "a"); - subscription.unsubscribe(); - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("test", undefined); - (eventEmitter as any).trigger("fooba", "b"); - (eventEmitter as any).trigger("fooba", "c"); - (eventEmitter as any).trigger("fooba", 6); - - expect(stringItemsReceived).toBe(1); - expect(numberItemsReceived).toBe(1); - }); -}); diff --git a/src/utils/__tests__/filter_map.test.ts b/src/utils/__tests__/filter_map.test.ts deleted file mode 100644 index 7b66e3702f..0000000000 --- a/src/utils/__tests__/filter_map.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Observable, - of as observableOf, -} from "rxjs"; -import filterMap from "../filter_map"; - -describe("utils - filterMap", () => { - it("should filter when the token given is mapped", (done) => { - const counter$ : Observable = observableOf(0); - let itemReceived = false; - counter$.pipe( - filterMap((i: number) => i, 0) - ).subscribe({ - next() { itemReceived = true; }, - complete() { - expect(itemReceived).toBe(false); - done(); - }, - }); - }); - - it("should still map all other tokens", (done) => { - const arr = []; - for (let i = 0; i < 10; i++) { - arr.push(i); - } - const counter$ : Observable = observableOf(...arr); - const receivedArr : number[] = []; - counter$.pipe( - filterMap((i: number) => i * 2, 6) - ).subscribe({ - next(val) { - receivedArr.push(val); - }, - complete() { - expect(receivedArr).toHaveLength(10 - 1); - expect(receivedArr[0]).toEqual(0); - expect(receivedArr[1]).toEqual(2); - expect(receivedArr[2]).toEqual(4); - expect(receivedArr[3]).toEqual(8); - expect(receivedArr[4]).toEqual(10); - done(); - }, - }); - }); - - it("should map everything when the token is not found", (done) => { - const arr = []; - for (let i = 0; i < 10; i++) { - arr.push(i); - } - const counter$ : Observable = observableOf(...arr); - const receivedArr : number[] = []; - counter$.pipe( - filterMap((i: number) => i * 2, null) - ).subscribe({ - next(val) { - receivedArr.push(val); - }, - complete() { - expect(receivedArr).toHaveLength(10); - expect(receivedArr[0]).toEqual(0); - expect(receivedArr[1]).toEqual(2); - expect(receivedArr[2]).toEqual(4); - expect(receivedArr[3]).toEqual(6); - expect(receivedArr[4]).toEqual(8); - done(); - }, - }); - }); -}); diff --git a/src/utils/__tests__/rx-throttle.test.ts b/src/utils/__tests__/rx-throttle.test.ts deleted file mode 100644 index 333e2775de..0000000000 --- a/src/utils/__tests__/rx-throttle.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - concat, - Observable, - of as observableOf, - Subject, -} from "rxjs"; -import throttle from "../rx-throttle"; - -describe("utils - throttle (RxJS)", () => { - it("should execute every Observables for synchronous Observables", (done) => { - const obsFunction = (x : number) : Observable => { - return observableOf(x); - }; - const throttledObsFunction = throttle(obsFunction); - const obs1 = throttledObsFunction(1); - const obs2 = throttledObsFunction(2); - - let receivedItemFrom1 = false; - let has1Completed = false; - let receivedItemFrom2 = false; - - obs1.subscribe({ - next() { receivedItemFrom1 = true; }, - complete() { has1Completed = true; }, - }); - obs2.subscribe({ - next() { receivedItemFrom2 = true; }, - complete() { - expect(receivedItemFrom1).toBe(true); - expect(has1Completed).toBe(true); - expect(receivedItemFrom2).toBe(true); - done(); - }, - }); - }); - - it("should complete new Observable if one is already pending", (done) => { - const sub1 = new Subject(); - const sub2 = new Subject(); - const obsFunction = (sub : Subject) : Observable => { - return concat(observableOf(undefined), sub); - }; - const throttledObsFunction = throttle(obsFunction); - const obs1 = throttledObsFunction(sub1); - const obs2 = throttledObsFunction(sub2); - - let itemsReceivedFrom1 = 0; - let itemsReceivedFrom2 = 0; - - let has2Completed = false; - - obs1.subscribe({ - next() { itemsReceivedFrom1++; }, - complete() { - expect(itemsReceivedFrom1).toBe(2); - expect(itemsReceivedFrom2).toBe(0); - done(); - }, - }); - - obs2.subscribe({ - next() { itemsReceivedFrom2++; }, - complete() { has2Completed = true; }, - }); - - expect(itemsReceivedFrom2).toBe(0); - expect(has2Completed).toBe(true); - - sub1.next(); - sub2.complete(); - sub1.complete(); - }); - - it("should execute Observable coming after the previous one has completed", (done) => { - const sub1 = new Subject(); - const sub2 = new Subject(); - const sub3 = new Subject(); - const obsFunction = (sub : Subject) : Observable => { - return concat(observableOf(undefined), sub); - }; - const throttledObsFunction = throttle(obsFunction); - const obs1 = throttledObsFunction(sub1); - const obs2 = throttledObsFunction(sub2); - const obs3 = throttledObsFunction(sub3); - - let itemsReceivedFrom1 = 0; - let itemsReceivedFrom3 = 0; - - let has1Completed = false; - - obs2.subscribe(); - sub2.complete(); - - obs1.subscribe({ - next() { itemsReceivedFrom1++; }, - complete() { has1Completed = true; }, - }); - sub1.complete(); - - obs3.subscribe({ - next() { itemsReceivedFrom3++; }, - complete() { - expect(has1Completed).toBe(true); - expect(itemsReceivedFrom1).toBe(1); - expect(itemsReceivedFrom3).toBe(1); - done(); - }, - }); - - sub3.complete(); - }); - - it("should execute Observable coming after the previous one has errored", (done) => { - const sub1 = new Subject(); - const sub2 = new Subject(); - const sub3 = new Subject(); - const obsFunction = (sub : Subject) : Observable => { - return concat(observableOf(undefined), sub); - }; - const throttledObsFunction = throttle(obsFunction); - const obs1 = throttledObsFunction(sub1); - const obs2 = throttledObsFunction(sub2); - const obs3 = throttledObsFunction(sub3); - const error = new Error("ffo"); - - let itemsReceivedFrom1 = 0; - let itemsReceivedFrom3 = 0; - - let has1Errored = false; - - obs2.subscribe(); - sub2.complete(); - - obs1.subscribe({ - next: () => { itemsReceivedFrom1++; }, - error: (e) => { - expect(e).toBe("titi"); - has1Errored = true; - }, - }); - sub1.error("titi"); - - obs3.subscribe({ - next: () => { itemsReceivedFrom3++; }, - error: (e) => { - expect(e).toBe(error); - expect(has1Errored).toBe(true); - expect(itemsReceivedFrom1).toBe(1); - expect(itemsReceivedFrom3).toBe(1); - done(); - }, - }); - - sub3.error(error); - }); - - /* eslint-disable max-len */ - it("should execute Observable coming after the previous one was unsubscribed", (done) => { - /* eslint-enable max-len */ - const sub1 = new Subject(); - const sub2 = new Subject(); - const sub3 = new Subject(); - const obsFunction = (sub : Subject) : Observable => { - return concat(observableOf(undefined), sub); - }; - const throttledObsFunction = throttle(obsFunction); - const obs1 = throttledObsFunction(sub1); - const obs2 = throttledObsFunction(sub2); - const obs3 = throttledObsFunction(sub3); - - let itemsReceivedFrom1 = 0; - let itemsReceivedFrom3 = 0; - - let has1Completed = false; - - const subscription2 = obs2.subscribe(); - subscription2.unsubscribe(); - - obs1.subscribe({ - next() { itemsReceivedFrom1++; }, - complete() { has1Completed = true; }, - }); - sub1.complete(); - - obs3.subscribe({ - next() { itemsReceivedFrom3++; }, - complete() { - expect(has1Completed).toBe(true); - expect(itemsReceivedFrom1).toBe(1); - expect(itemsReceivedFrom3).toBe(1); - sub2.complete(); - done(); - }, - }); - - sub3.complete(); - }); - - it("should allow multiple throttledObsFunction Observables in parallel", (done) => { - const sub1 = new Subject(); - const sub2 = new Subject(); - const obsFunction = (sub : Subject) : Observable => { - return concat(observableOf(undefined), sub); - }; - const throttledObsFunction1 = throttle(obsFunction); - const throttledObsFunction2 = throttle(obsFunction); - const obs1 = throttledObsFunction1(sub1); - const obs2 = throttledObsFunction2(sub2); - - let itemsReceivedFrom1 = 0; - let itemsReceivedFrom2 = 0; - - let has2Completed = false; - - obs1.subscribe({ - next() { itemsReceivedFrom1++; }, - complete() { - expect(itemsReceivedFrom1).toBe(2); - expect(itemsReceivedFrom2).toBe(1); - done(); - }, - }); - - obs2.subscribe({ - next() { itemsReceivedFrom2++; }, - complete() { has2Completed = true; }, - }); - - expect(itemsReceivedFrom2).toBe(1); - expect(has2Completed).toBe(false); - - sub1.next(); - sub2.complete(); - sub1.complete(); - }); -}); diff --git a/src/utils/__tests__/rx-try_catch.test.ts b/src/utils/__tests__/rx-try_catch.test.ts deleted file mode 100644 index d0efd11934..0000000000 --- a/src/utils/__tests__/rx-try_catch.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - concat, - Observable, - of as observableOf, - throwError as observableThrow, -} from "rxjs"; -import tryCatch from "../rx-try_catch"; - -describe("utils - tryCatch (RxJS)", () => { - it("should return a throwing observable if the function throws", (done) => { - function func() : Observable { - // eslint-disable-next-line no-throw-literal - throw 4; - } - - let itemsReceived = 0; - tryCatch(func, undefined).subscribe({ - next: () => { itemsReceived++; }, - error: (err) => { - expect(itemsReceived).toBe(0); - expect(err).toBe(4); - done(); - }, - }); - }); - - it("should allow giving optional arguments", (done) => { - function func(a : number) : Observable { - expect(a).toBe(4); - throw new Error(); - } - tryCatch(func, 4).subscribe({ error() { done(); } }); - }); - - /* eslint-disable max-len */ - it("should emit when the returned Observable emits and complete when it completes", (done) => { - /* eslint-enable max-len */ - function func() { - return observableOf(1, 2, 3); - } - - let itemsReceived = 0; - tryCatch(func, undefined).subscribe({ - next(i) { - switch (itemsReceived++) { - case 0: - expect(i).toBe(1); - break; - case 1: - expect(i).toBe(2); - break; - case 2: - expect(i).toBe(3); - break; - default: - throw new Error("Too much items emitted"); - } - }, - complete() { - expect(itemsReceived).toBe(3); - done(); - }, - }); - }); - - it("should throw when the returned Observable throws", (done) => { - function func() { - return concat(observableOf(1), observableThrow(() => "a")); - } - - let itemsReceived = 0; - tryCatch(func, undefined).subscribe({ - next: (i) => { - itemsReceived++; - expect(i).toBe(1); - }, - error: (err) => { - expect(itemsReceived).toBe(1); - expect(err).toBe("a"); - done(); - }, - }); - }); -}); diff --git a/src/utils/cancellable_sleep.ts b/src/utils/cancellable_sleep.ts index 840887fa5d..374966313f 100644 --- a/src/utils/cancellable_sleep.ts +++ b/src/utils/cancellable_sleep.ts @@ -14,10 +14,8 @@ * limitations under the License. */ -import { - CancellationError, - CancellationSignal, -} from "./task_canceller"; +import createCancellablePromise from "./create_cancellable_promise"; +import { CancellationSignal } from "./task_canceller"; /** * Wait the given `delay`, resolving the Promise when finished. @@ -36,15 +34,8 @@ export default function cancellableSleep( delay: number, cancellationSignal: CancellationSignal ) : Promise { - return new Promise((res, rej) => { - const timeout = setTimeout(() => { - unregisterCancelSignal(); - res(); - }, delay); - const unregisterCancelSignal = cancellationSignal - .register(function onCancel(cancellationError : CancellationError) { - clearTimeout(timeout); - rej(cancellationError); - }); + return createCancellablePromise(cancellationSignal, (res) => { + const timeout = setTimeout(() => res(), delay); + return () => clearTimeout(timeout); }); } diff --git a/src/utils/cast_to_observable.ts b/src/utils/cast_to_observable.ts deleted file mode 100644 index 903a0f76e0..0000000000 --- a/src/utils/cast_to_observable.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - from as observableFrom, - Observable, - of as observableOf, -} from "rxjs"; -import isNullOrUndefined from "./is_null_or_undefined"; - -/** - * Try to cast the given value into an observable. - * StraightForward - test first for an Observable then for a Promise. - * @param {Observable|Function|*} - * @returns {Observable} - */ -function castToObservable(value : Observable | - Promise | - Exclude>) : Observable { - if (value instanceof Observable) { - return value; - } else if ( - value instanceof Promise || - ( - !isNullOrUndefined(value) && - typeof (value as { then? : unknown }).then === "function") - ) - { - return observableFrom(value as Promise); - } - - return observableOf(value); -} - -export default castToObservable; diff --git a/src/utils/concat_map_latest.ts b/src/utils/concat_map_latest.ts deleted file mode 100644 index 21628d806d..0000000000 --- a/src/utils/concat_map_latest.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - concat as observableConcat, - defer as observableDefer, - EMPTY, - mergeMap, - Observable, - tap, -} from "rxjs"; - -/** - * Same as concatMap, but get last emitted value from source instead of unstack - * inner values. - * @param {function} callback - * @returns {function} - */ -export default function concatMapLatest( - callback: (arg: T, i: number) => Observable -): (source: Observable) => Observable { - return (source: Observable) => observableDefer(() => { - let counter = 0; - let valuePending : T; - let hasValuePending = false; - let isExhausting = false; - function next(value: T): Observable { - return observableDefer(() => { - if (isExhausting) { - valuePending = value; - hasValuePending = true; - return EMPTY; - } - hasValuePending = false; - isExhausting = true; - return callback(value, counter++).pipe( - tap({ complete: () => isExhausting = false }), - (s: Observable) => - observableConcat(s, - observableDefer(() => - hasValuePending ? next(valuePending) : - EMPTY)) - ); - }); - } - return source.pipe(mergeMap(next)); - }); -} diff --git a/src/utils/create_cancellable_promise.ts b/src/utils/create_cancellable_promise.ts new file mode 100644 index 0000000000..bbca83ff96 --- /dev/null +++ b/src/utils/create_cancellable_promise.ts @@ -0,0 +1,69 @@ +import { + CancellationError, + CancellationSignal, +} from "./task_canceller"; + +/** + * Returns a Promise linked to a `CancellationSignal`, which will reject the + * corresponding `CancellationError` if that signal emits before the wanted + * task finishes (either on success or on error). + * + * The given callback mimicks the Promise interface with the added possibility + * of returning a callback which will be called when and if the task is + * cancelled before being either resolved or rejected. + * In that case, that logic will be called just before the Promise is rejected + * with the corresponding `CancellationError`. + * The point of this callback is to implement aborting logic, such as for + * example aborting a request. + * + * @param {Object} cancellationSignal - The `CancellationSignal` the returned + * Promise will be linked to. + * @param {Function} cb - The function implementing the cancellable Promise. Its + * arguments follow Promise's semantics but it can also return a function which + * will be called when and if `cancellationSignal` emits before either arguments + * are called. + * @returns {Promise} - The created Promise, which will resolve when and if the + * first argument to `cb` is called first and reject either if the second + * argument to `cb` is called first or if the given `CancellationSignal` emits + * before either of the two previous conditions. + */ +export default function createCancellablePromise( + cancellationSignal : CancellationSignal, + cb : ( + resolve : (val : T) => void, + reject : (err : unknown) => void, + ) => (() => void) | void +) : Promise { + let abortingLogic : (() => void) | void; + return new Promise((res, rej) => { + if (cancellationSignal.cancellationError !== null) { + // If the signal was already triggered before, do not even call `cb` + return rej(cancellationSignal.cancellationError); + } + + let hasUnregistered = false; + abortingLogic = cb( + function onCancellablePromiseSuccess(val : T) { + cancellationSignal.deregister(onCancellablePromiseCancellation); + hasUnregistered = true; + res(val); + }, + function onCancellablePromiseFailure(err : unknown) { + cancellationSignal.deregister(onCancellablePromiseCancellation); + hasUnregistered = true; + rej(err); + } + ); + + if (!hasUnregistered) { + cancellationSignal.register(onCancellablePromiseCancellation); + } + + function onCancellablePromiseCancellation(error : CancellationError) { + if (abortingLogic !== undefined) { + abortingLogic(); + } + rej(error); + } + }); +} diff --git a/src/utils/defer_subscriptions.ts b/src/utils/defer_subscriptions.ts deleted file mode 100644 index 9920e926ff..0000000000 --- a/src/utils/defer_subscriptions.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - asapScheduler, - Observable, - subscribeOn, -} from "rxjs"; - -/** - * At subscription, instead of "running" the Observable right away, wait until - * the current task has finished executing before actually running this - * Observable. - * - * This can be important for example when you want in a given function to - * exploit the same shared Observable which may send synchronous events directly - * after subscription. - * - * Here, you might be left in a situation where the first element subscribing to - * that Observable will receive those synchronous events immediately on - * subscription. Further subscriptions on that Observable will miss out on those - * events - even if those subscriptions happen synchronously after the first - * one. - * - * Calling `deferSubscriptions` in those cases will make sure that all such - * subscriptions can be registered before the Observable start emitting events - * (as long as such Subscriptions are done synchronously). - * - * @example - * ```js - * const myObservable = rxjs.timer(100).pipe(mapTo("ASYNC MSG"), - * startWith("SYNCHRONOUS MSG"), - * share()); - * - * myObservable.subscribe(x => console.log("Sub1:", x)); - * myObservable.subscribe(x => console.log("Sub2:", x)); - * - * setTimeout(() => { - * myObservable.subscribe(x => console.log("Sub3:", x)); - * }, 50); - * - * // You will get: - * // Sub1: SYNCHRONOUS MSG - * // Sub1: ASYNC MSG - * // Sub2: ASYNC MSG - * // Sub3: ASYNC MSG - * - * // ------------------------------ - * - * const myObservableDeferred = rxjs.timer(100).pipe(mapTo("ASYNC MSG"), - * startWith("SYNCHRONOUS MSG"), - * deferSubscriptions(), - * // NOTE: the order is important here - * share()); - * - * myObservableDeferred.subscribe(x => console.log("Sub1:", x)); - * myObservableDeferred.subscribe(x => console.log("Sub2:", x)); - * - * setTimeout(() => { - * myObservableDeferred.subscribe(x => console.log("Sub3:", x)); - * }, 50); - * - * // You will get: - * // Sub1: SYNCHRONOUS MSG - * // Sub2: SYNCHRONOUS MSG - * // Sub1: ASYNC MSG - * // Sub2: ASYNC MSG - * // Sub3: ASYNC MSG - * ``` - * @returns {function} - */ -export default function deferSubscriptions( -) : (source: Observable) => Observable { - return (source: Observable) => { - // TODO asapScheduler seems to not push the subscription in the microtask - // queue as nextTick does but in a regular event loop queue. - // This means that the subscription will be run even later that we wish for. - // This is not dramatic but it could be better. - // Either this is a problem with RxJS or this was wanted, in which case we - // may need to add our own scheduler. - return source.pipe(subscribeOn(asapScheduler)); - }; -} diff --git a/src/utils/event_emitter.ts b/src/utils/event_emitter.ts index e03a9cbc89..ec4250f040 100644 --- a/src/utils/event_emitter.ts +++ b/src/utils/event_emitter.ts @@ -14,10 +14,6 @@ * limitations under the License. */ -import { - Observable, - Observer, -} from "rxjs"; import log from "../log"; import isNullOrUndefined from "./is_null_or_undefined"; import { CancellationSignal } from "./task_canceller"; @@ -32,12 +28,14 @@ export interface IEventEmitter { } // Type of the argument in the listener's callback -type IArgs = TEventRecord[TEventName]; // Type of the listener function -export type IListener = (args: IArgs) => void; +export type IListener< + TEventRecord, + TEventName extends keyof TEventRecord +> = (args: IEventPayload) => void; type IListeners = { [P in keyof TEventRecord]? : Array> @@ -131,7 +129,7 @@ export default class EventEmitter implements IEventEmitter { */ protected trigger( evt : TEventName, - arg : IArgs + arg : IEventPayload ) : void { const listeners = this._listeners[evt]; if (!Array.isArray(listeners)) { @@ -147,25 +145,3 @@ export default class EventEmitter implements IEventEmitter { }); } } - -/** - * Simple redefinition of the fromEvent from rxjs to also work on our - * implementation of EventEmitter with type-checked strings - * @param {Object} target - * @param {string} eventName - * @returns {Observable} - */ -export function fromEvent( - target : IEventEmitter, - eventName : TEventName -) : Observable> { - return new Observable((obs : Observer>) => { - function handler(event : IArgs) { - obs.next(event); - } - target.addEventListener(eventName, handler); - return () => { - target.removeEventListener(eventName, handler); - }; - }); -} diff --git a/src/utils/filter_map.ts b/src/utils/filter_map.ts deleted file mode 100644 index 7da01507cd..0000000000 --- a/src/utils/filter_map.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - defer as observableDefer, - filter, - map, - Observable, -} from "rxjs"; - -/** - * Special kind of map which will ignore the result when the value emitted - * corresponds to a given token. - * - * This can also be performed through a `mergeMap` (by returning the `EMPTY` - * Observable when we want to ignore events) but using `filterMap` is both more - * straightforward and more performant. - * @param {function} callback - * @param {*} filteringToken - * @returns {function} - */ -export default function filterMap( - callback: (arg: T, i: number) => U | V, - filteringToken : V -): (source: Observable) => Observable { - return (source: Observable) => observableDefer(() => { - return source.pipe(map(callback), - filter((x : U | V) : x is U => x !== filteringToken)); - }); -} diff --git a/src/utils/reference.ts b/src/utils/reference.ts index b85ccc4ba3..35c317a1f3 100644 --- a/src/utils/reference.ts +++ b/src/utils/reference.ts @@ -14,45 +14,39 @@ * limitations under the License. */ -import { - Observable, - Subscriber, -} from "rxjs"; -import TaskCanceller, { - CancellationSignal, -} from "./task_canceller"; +import { CancellationSignal } from "./task_canceller"; /** * A value behind a shared reference, meaning that any update to its value from - * anywhere can be retrieved from any other parts of the code in posession of - * the "same" (i.e. not cloned) `ISharedReference`. + * anywhere can be retrieved from any other parts of the code in possession of + * the same `ISharedReference`. * * @example * ```ts * const myVal = 1; * const myRef : ISharedReference = createSharedReference(1); * - * function DoThingsWithVal(num : number) { + * function setValTo2(num : number) { * num = 2; * } * - * function DoThingsWithRef(num : ISharedReference) { + * function setRefTo2(num : ISharedReference) { * num.setValue(2); * } * - * myRef.asObservable().subscribe((val) => { - * console.log(val); // outputs first `1`, then `2` - * }); - * - * DoThingsWithVal(myVal); + * setValTo2(myVal); * console.log(myVal); // output: 1 * - * DoThingsWithRef(myRef); + * myRef.onUpdate((val) => { + * console.log(val); // outputs first synchronously `1`, then `2` + * }, { emitCurrentValue: true }); + * + * setRefTo2(myRef); * console.log(myRef.getValue()); // output: 2 * - * myRef.asObservable().subscribe((val) => { + * myRef.listen((val) => { * console.log(val); // outputs only `2` - * }); + * }, { emitCurrentValue: true }); * ``` * * This type was added because we found that the usage of an explicit type for @@ -81,27 +75,20 @@ export interface ISharedReference { */ setValueIfChanged(newVal : T) : void; - /** - * Returns an Observable which synchronously emits the current value (unless - * the `skipCurrentValue` argument has been set to `true`) and then each time - * a new value is set. - * @param {boolean} [skipCurrentValue] - * @returns {Observable} - */ - asObservable(skipCurrentValue? : boolean) : Observable; /** * Allows to register a callback to be called each time the value inside the * reference is updated. * @param {Function} cb - Callback to be called each time the reference is - * updated. Takes the new value im argument. + * updated. Takes as first argument its new value and in second argument a + * function allowing to unregister the update listening callback. * @param {Object} [options] * @param {Object} [options.clearSignal] - Allows to provide a * CancellationSignal which will unregister the callback when it emits. * @param {boolean} [options.emitCurrentValue] - If `true`, the callback will - * also be immediately called with the current value. + * also be immediately (synchronously) called with the current value. */ onUpdate( - cb : (val : T) => void, + cb : (val : T, stopListening : () => void) => void, options? : { clearSignal?: CancellationSignal | undefined; emitCurrentValue?: boolean | undefined; @@ -116,8 +103,18 @@ export interface ISharedReference { * * This method can be used as a lighter weight alternative to `onUpdate` when * just waiting that the stored value becomes defined. + * As such, it is an explicit equivalent to something like: + * ```js + * myReference.onUpdate((newVal, stopListening) => { + * if (newVal !== undefined) { + * stopListening(); + * + * // ... do the logic + * } + * }, { emitCurrentValue: true }); + * ``` * @param {Function} cb - Callback to be called each time the reference is - * updated. Takes the new value im argument. + * updated. Takes the new value in argument. * @param {Object} [options] * @param {Object} [options.clearSignal] - Allows to provide a * CancellationSignal which will unregister the callback when it emits. @@ -127,10 +124,11 @@ export interface ISharedReference { options? : { clearSignal?: CancellationSignal | undefined } | undefined, ) : void; + /** * Indicate that no new values will be emitted. - * Allows to automatically close all Observables generated from this shared - * reference. + * Allows to automatically close all listeners listening to this + * `ISharedReference`. */ finish() : void; } @@ -138,6 +136,11 @@ export interface ISharedReference { /** * An `ISharedReference` which can only be read and not updated. * + * Because an `ISharedReference` is structurally compatible to a + * `IReadOnlySharedReference`, and because of TypeScript variance rules, it can + * be upcasted into a `IReadOnlySharedReference` at any time to make it clear in + * the code that some logic is not supposed to update the referenced value. + * * @example * ```ts * const myReference : ISharedReference = createSharedReference(4); @@ -151,49 +154,12 @@ export interface ISharedReference { * myReference.setValue(12); * shouldOnlyReadIt(myReference); // output: "current value: 12" * ``` - * - * Because an `ISharedReference` is structurally compatible to a - * `IReadOnlySharedReference`, and because of TypeScript variance rules, it can - * be upcasted into a `IReadOnlySharedReference` at any time to make it clear in - * the code that some logic is not supposed to update the referenced value. */ -export interface IReadOnlySharedReference { - /** Get the last value set on that reference. */ - getValue() : T; - /** - * Returns an Observable notifying this reference's value each time it is - * updated. - * - * Also emit its current value on subscription unless its argument is set to - * `true`. - */ - asObservable(skipCurrentValue? : boolean) : Observable; - - /** - * Triggers a callback each time this reference's value is updated. - * - * Can be given several options as argument: - * - clearSignal: When the attach `CancellationSignal` emits, the given - * callback will not be called anymore on reference updates. - * - emitCurrentValue: If `true`, the callback will be called directly and - * synchronously on this call with its current value. - * @param {Function} cb - * @param {Object} [options] - */ - onUpdate( - cb : (val : T) => void, - options? : { - clearSignal?: CancellationSignal | undefined; - emitCurrentValue?: boolean | undefined; - } | undefined, - ) : void; - - waitUntilDefined( - cb : (val : Exclude) => void, - options? : { clearSignal?: CancellationSignal | undefined } | - undefined, - ) : void; -} +export type IReadOnlySharedReference = + Pick, + "getValue" | + "onUpdate" | + "waitUntilDefined">; /** * Create an `ISharedReference` object encapsulating the mutable `initialValue` @@ -201,9 +167,18 @@ export interface IReadOnlySharedReference { * * @see ISharedReference * @param {*} initialValue - * @returns {Observable} + * @param {Object|undefined} [cancelSignal] - If set, the created shared + * reference will be automatically "finished" once that signal emits. + * Finished references won't be able to update their value anymore, and will + * also automatically have their listeners (callbacks linked to value change) + * removed - as they cannot be triggered anymore, thus providing a security + * against memory leaks. + * @returns {Object} */ -export default function createSharedReference(initialValue : T) : ISharedReference { +export default function createSharedReference( + initialValue : T, + cancelSignal? : CancellationSignal +) : ISharedReference { /** Current value referenced by this `ISharedReference`. */ let value = initialValue; @@ -216,19 +191,23 @@ export default function createSharedReference(initialValue : T) : ISharedRefe * once it changes * - `complete`: Allows to clean-up the listener, will be called once the * reference is finished. - * - `hasBeenCleared`: becomes `true` when the Observable becomes - * unsubscribed and thus when it is removed from the `cbs` array. + * - `hasBeenCleared`: becomes `true` when the reference is + * removed from the `cbs` array. * Adding this property allows to detect when a previously-added * listener has since been removed e.g. as a side-effect during a * function call. * - `complete`: Callback to call when the current Reference is "finished". */ - const cbs : Array<{ trigger : (a: T) => void; + const cbs : Array<{ trigger : (a: T, stopListening: () => void) => void; complete : () => void; hasBeenCleared : boolean; }> = []; let isFinished = false; + if (cancelSignal !== undefined) { + cancelSignal.register(finish); + } + return { /** * Returns the current value of this shared reference. @@ -259,7 +238,7 @@ export default function createSharedReference(initialValue : T) : ISharedRefe for (const cbObj of clonedCbs) { try { if (!cbObj.hasBeenCleared) { - cbObj.trigger(newVal); + cbObj.trigger(newVal, cbObj.complete); } } catch (_) { /* nothing */ @@ -277,46 +256,12 @@ export default function createSharedReference(initialValue : T) : ISharedRefe } }, - /** - * Returns an Observable which synchronously emits the current value (unless - * the `skipCurrentValue` argument has been set to `true`) and then each - * time a new value is set. - * @param {boolean} [skipCurrentValue] - * @returns {Observable} - */ - asObservable(skipCurrentValue? : boolean) : Observable { - return new Observable((obs : Subscriber) => { - if (skipCurrentValue !== true) { - obs.next(value); - } - if (isFinished) { - obs.complete(); - return undefined; - } - const cbObj = { trigger: obs.next.bind(obs), - complete: obs.complete.bind(obs), - hasBeenCleared: false }; - cbs.push(cbObj); - return () => { - /** - * Code in here can still be running while this is happening. - * Set `hasBeenCleared` to `true` to avoid still using the - * `subscriber` from this object. - */ - cbObj.hasBeenCleared = true; - const indexOf = cbs.indexOf(cbObj); - if (indexOf >= 0) { - cbs.splice(indexOf, 1); - } - }; - }); - }, - /** * Allows to register a callback to be called each time the value inside the * reference is updated. * @param {Function} cb - Callback to be called each time the reference is - * updated. Takes the new value im argument. + * updated. Takes as first argument its new value and in second argument a + * callback allowing to unregister the callback. * @param {Object|undefined} [options] * @param {Object|undefined} [options.clearSignal] - Allows to provide a * CancellationSignal which will unregister the callback when it emits. @@ -324,33 +269,37 @@ export default function createSharedReference(initialValue : T) : ISharedRefe * callback will also be immediately called with the current value. */ onUpdate( - cb : (val : T) => void, + cb : (val : T, stopListening : () => void) => void, options? : { clearSignal?: CancellationSignal | undefined; emitCurrentValue?: boolean | undefined; } | undefined ) : void { - if (options?.emitCurrentValue === true) { - cb(value); - } - if (isFinished) { - return ; - } const cbObj = { trigger: cb, complete: unlisten, hasBeenCleared: false }; cbs.push(cbObj); + + if (options?.emitCurrentValue === true) { + cb(value, unlisten); + } + + if (isFinished || cbObj.hasBeenCleared) { + unlisten(); + return ; + } if (options?.clearSignal === undefined) { return; } options.clearSignal.register(unlisten); function unlisten() : void { - /** - * Code in here can still be running while this is happening. - * Set `hasBeenCleared` to `true` to avoid still using the - * `subscriber` from this object. - */ + if (options?.clearSignal !== undefined) { + options.clearSignal.deregister(unlisten); + } + if (cbObj.hasBeenCleared) { + return; + } cbObj.hasBeenCleared = true; const indexOf = cbs.indexOf(cbObj); if (indexOf >= 0) { @@ -359,53 +308,57 @@ export default function createSharedReference(initialValue : T) : ISharedRefe } }, + /** + * Variant of `onUpdate` which will only call the callback once, once the + * value inside the reference is different from `undefined`. + * The callback is called synchronously if the value already isn't set to + * `undefined`. + * + * This method can be used as a lighter weight alternative to `onUpdate` + * when just waiting that the stored value becomes defined. + * @param {Function} cb - Callback to be called each time the reference is + * updated. Takes the new value in argument. + * @param {Object} [options] + * @param {Object} [options.clearSignal] - Allows to provide a + * CancellationSignal which will unregister the callback when it emits. + */ waitUntilDefined( cb : (val : Exclude) => void, options? : { clearSignal?: CancellationSignal | undefined } | undefined ) : void { - if (value !== undefined) { - cb(value as Exclude); - return; - } - if (isFinished) { - return ; - } - - const childCanceller = new TaskCanceller(); - if (options?.clearSignal !== undefined) { - options.clearSignal.register(() => childCanceller.cancel()); - } - this.onUpdate((val : T) => { + this.onUpdate((val : T, stopListening) => { if (val !== undefined) { - childCanceller.cancel(); + stopListening(); cb(value as Exclude); - return; } - }, { clearSignal: childCanceller.signal }); + }, { clearSignal: options?.clearSignal, emitCurrentValue: true }); }, /** * Indicate that no new values will be emitted. - * Allows to automatically close all Observables generated from this shared - * reference. + * Allows to automatically free all listeners linked to this reference. */ - finish() : void { - isFinished = true; - const clonedCbs = cbs.slice(); - for (const cbObj of clonedCbs) { - try { - if (!cbObj.hasBeenCleared) { - cbObj.complete(); - } + finish, + }; + function finish() { + if (cancelSignal !== undefined) { + cancelSignal.deregister(finish); + } + isFinished = true; + const clonedCbs = cbs.slice(); + for (const cbObj of clonedCbs) { + try { + if (!cbObj.hasBeenCleared) { + cbObj.complete(); cbObj.hasBeenCleared = true; - } catch (_) { - /* nothing */ } + } catch (_) { + /* nothing */ } - cbs.length = 0; - }, - }; + } + cbs.length = 0; + } } /** @@ -415,27 +368,23 @@ export default function createSharedReference(initialValue : T) : ISharedRefe * over. * @param {Function} mappingFn - The mapping function which will receives * `originalRef`'s value and outputs this new reference's value. - * @param {Object | undefined} [cancellationSignal] - Optionally, a - * `CancellationSignal` which will finish that reference when it emits. + * @param {Object} cancellationSignal - Optionally, a `CancellationSignal` which + * will finish that reference when it emits. * @returns {Object} - The new, mapped, reference. */ export function createMappedReference( originalRef : IReadOnlySharedReference, mappingFn : (x : T) => U, - cancellationSignal? : CancellationSignal + cancellationSignal : CancellationSignal ) : IReadOnlySharedReference { - const newRef = createSharedReference(mappingFn(originalRef.getValue())); + const newRef = createSharedReference(mappingFn(originalRef.getValue()), + cancellationSignal); originalRef.onUpdate(function mapOriginalReference(x) { newRef.setValue(mappingFn(x)); }, { clearSignal: cancellationSignal }); // TODO nothing is done if `originalRef` is finished, though the returned // reference could also be finished in that case. To do? - if (cancellationSignal !== undefined) { - cancellationSignal.register(() => { - newRef.finish(); - }); - } return newRef; } diff --git a/src/utils/request/xhr.ts b/src/utils/request/xhr.ts index e99c02bf55..5e1672520e 100644 --- a/src/utils/request/xhr.ts +++ b/src/utils/request/xhr.ts @@ -26,74 +26,9 @@ import { const DEFAULT_RESPONSE_TYPE : XMLHttpRequestResponseType = "json"; /** - * # request function + * Perform an HTTP request, according to the options given. * - * Translate GET requests into Rx.js Observables. - * - * ## Overview - * - * Perform the request on subscription. - * Emit zero, one or more progress event(s) and then the data if the request - * was successful. - * - * Throw if an error happened or if the status code is not in the 200 range at - * the time of the response. - * Complete after emitting the data. - * Abort the xhr on unsubscription. - * - * ## Emitted Objects - * - * The emitted objects are under the following form: - * ``` - * { - * type {string}: the type of event - * value {Object}: the event value - * } - * ``` - * - * The type of event can either be "progress" or "data-loaded". The value is - * under a different form depending on the type. - * - * For "progress" events, the value should be the following object: - * ``` - * { - * url {string}: url on which the request is being done - * sendingTime {Number}: timestamp at which the request was sent. - * currentTime {Number}: timestamp at which the progress event was - * triggered - * size {Number}: current size downloaded, in bytes (without - * overhead) - * totalSize {Number|undefined}: total size to download, in bytes - * (without overhead) - * } - * ``` - * - * For "data-loaded" events, the value should be the following object: - * ``` - * { - * status {Number}: xhr status code - * url {string}: URL on which the request was done (can be different than - * the one given in arguments when we go through - * redirections). - * responseType {string}: the responseType of the request - * (e.g. "json", "document"...). - * sendingTime {Number}: time at which the request was sent, in ms. - * receivedTime {Number}: timest at which the response was received, in ms. - * size {Number}: size of the received data, in bytes. - * responseData {*}: Data in the response. Format depends on the - * responseType. - * } - * ``` - * - * For any successful request you should have 0+ "progress" events and 1 - * "data-loaded" event. - * - * For failing request, you should have 0+ "progress" events and 0 "data-loaded" - * event (the Observable will throw before). - * - * ## Errors - * - * Several errors can be emitted (the Rx.js way). Namely: + * Several errors can be rejected. Namely: * - RequestErrorTypes.TIMEOUT_ERROR: the request timeouted (took too long to * respond). * - RequestErrorTypes.PARSE_ERROR: the browser APIs used to parse the @@ -104,7 +39,7 @@ const DEFAULT_RESPONSE_TYPE : XMLHttpRequestResponseType = "json"; * - RequestErrorTypes.ERROR_EVENT: The XHR had an error event before the * response could be fetched. * @param {Object} options - * @returns {Observable} + * @returns {Promise.} */ export default function request( options : IRequestOptions< undefined | null | "" | "text" > @@ -190,7 +125,7 @@ export default function request( reject(err); }); - if (cancelSignal.isCancelled) { + if (cancelSignal.isCancelled()) { return; } } diff --git a/src/utils/retry_promise_with_backoff.ts b/src/utils/retry_promise_with_backoff.ts new file mode 100644 index 0000000000..584edfffc3 --- /dev/null +++ b/src/utils/retry_promise_with_backoff.ts @@ -0,0 +1,95 @@ +import { IPlayerError } from "../public_types"; +import getFuzzedDelay from "./get_fuzzed_delay"; +import isNullOrUndefined from "./is_null_or_undefined"; +import sleep from "./sleep"; +import { CancellationSignal } from "./task_canceller"; + +/** + * Retry the given Promise (if it rejects) with an exponential + * backoff. + * The backoff behavior can be tweaked through the options given. + * + * @param {Function} runProm + * @param {Object} options - Configuration object. + * This object contains the following properties: + * + * - retryDelay {Number} - The initial delay, in ms. + * This delay will be fuzzed to fall under the range +-30% each time a new + * retry is done. + * Then, this delay will be multiplied by 2^(n-1), n being the counter of + * retry we performed (beginning at 1 for the first retry). + * + * - totalRetry {Number} - The amount of time we should retry. 0 + * means no retry, 1 means a single retry, Infinity means infinite retry + * etc. + * If the Promise still rejects after this number of retry, the error will + * be throwed through the returned Promise. + * + * - shouldRetry {Function|undefined} - Function which will receive the + * error each time it fails, and should return a boolean. If this boolean + * is false, the error will be directly thrown (without anymore retry). + * + * - onRetry {Function|undefined} - Function which will be triggered at + * each retry. Will receive two arguments: + * 1. The error + * 2. The current retry count, beginning at 1 for the first retry + * + * @param {Object} cancelSignal + * @returns {Promise} + * TODO Take errorSelector out. Should probably be entirely managed in the + * calling code via a catch (much simpler to use and to understand). + */ +export default function retryPromiseWithBackoff( + runProm : () => Promise, + options : IBackoffOptions, + cancelSignal : CancellationSignal +) : Promise { + const { baseDelay, + maxDelay, + totalRetry, + shouldRetry, + onRetry } = options; + + let retryCount = 0; + + return iterate(); + async function iterate() : Promise { + if (cancelSignal.cancellationError !== null) { + throw cancelSignal.cancellationError; + } + try { + const res = await runProm(); + return res; + } catch (error) { + if (cancelSignal.cancellationError !== null) { + throw cancelSignal.cancellationError; + } + if ((!isNullOrUndefined(shouldRetry) && !shouldRetry(error)) || + retryCount++ >= totalRetry) + { + throw error; + } + + if (typeof onRetry === "function") { + onRetry(error, retryCount); + } + + const delay = Math.min(baseDelay * Math.pow(2, retryCount - 1), + maxDelay); + + const fuzzedDelay = getFuzzedDelay(delay); + await sleep(fuzzedDelay); + const res = iterate(); + return res; + } + } +} + +export interface IBackoffOptions { + baseDelay : number; + maxDelay : number; + totalRetry : number; + shouldRetry? : (error : unknown) => boolean; + errorSelector? : (error : unknown, retryCount : number) => Error | IPlayerError; + onRetry? : (error : unknown, retryCount : number) => void; +} diff --git a/src/utils/rx-from_cancellable_promise.ts b/src/utils/rx-from_cancellable_promise.ts deleted file mode 100644 index 6f00db557e..0000000000 --- a/src/utils/rx-from_cancellable_promise.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Observable } from "rxjs"; -import TaskCanceller from "./task_canceller"; - -/** - * Transform a Promise that can be cancelled (through the usage of a - * `TaskCanceller`) to an Observable, while keeping the cancellation logic - * between both in sync. - * - * @example - * ```js - * const canceller = new TaskCanceller(); - * fromCancellablePromise( - * canceller, - * () => doSomeCancellableTasks(canceller.signal) - * ).subscribe( - * (i) => console.log("Emitted: ", i); - * (e) => console.log("Error: ", e); - * () => console.log("Complete.") - * ); - * ``` - * @param {Object} canceller - * @param {Function} fn - * @returns {Observable} - */ -export default function fromCancellablePromise( - canceller : TaskCanceller, - fn : () => Promise -) : Observable { - return new Observable((obs) => { - let isUnsubscribedFrom = false; - let isComplete = false; - fn().then( - (i) => { - if (isUnsubscribedFrom) { - return; - } - isComplete = true; - obs.next(i); - obs.complete(); - }, - (err) => { - isComplete = true; - if (isUnsubscribedFrom) { - return; - } - obs.error(err); - }); - return () => { - if (!isComplete) { - isUnsubscribedFrom = true; - canceller.cancel(); - } - }; - }); -} diff --git a/src/utils/rx-next-tick.ts b/src/utils/rx-next-tick.ts deleted file mode 100644 index aa0f270a4c..0000000000 --- a/src/utils/rx-next-tick.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import nextTick from "next-tick"; -import { Observable } from "rxjs"; - -/** - * Create Observable that emits and complete on the next micro-task. - * - * This Observable can be useful to prevent race conditions based on - * synchronous task being performed in the wrong order. - * By awaiting nextTickObs before performing a task, you ensure that all other - * tasks that might have run synchronously either before or after it all already - * ran. - * @returns {Observable} - */ -export default function nextTickObs(): Observable { - return new Observable((obs) => { - let isFinished = false; - nextTick(() => { - if (!isFinished) { - obs.next(); - obs.complete(); - } - }); - return () => { - isFinished = true; - }; - }); -} diff --git a/src/utils/rx-retry_with_backoff.ts b/src/utils/rx-retry_with_backoff.ts deleted file mode 100644 index 6d72f5bf3a..0000000000 --- a/src/utils/rx-retry_with_backoff.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - catchError, - mergeMap, - Observable, - timer as observableTimer, -} from "rxjs"; -import { IPlayerError } from "../public_types"; -import getFuzzedDelay from "./get_fuzzed_delay"; -import isNullOrUndefined from "./is_null_or_undefined"; - -export interface IBackoffOptions { - baseDelay : number; - maxDelay : number; - totalRetry : number; - shouldRetry? : (error : unknown) => boolean; - errorSelector? : (error : unknown, retryCount : number) => Error | IPlayerError; - onRetry? : (error : unknown, retryCount : number) => void; -} - -/** - * Retry the given observable (if it triggers an error) with an exponential - * backoff. - * The backoff behavior can be tweaked through the options given. - * - * @param {Observable} obs$ - * @param {Object} options - Configuration object. - * This object contains the following properties: - * - * - retryDelay {Number} - The initial delay, in ms. - * This delay will be fuzzed to fall under the range +-30% each time a new - * retry is done. - * Then, this delay will be multiplied by 2^(n-1), n being the counter of - * retry we performed (beginning at 1 for the first retry). - * - * - totalRetry {Number} - The amount of time we should retry. 0 - * means no retry, 1 means a single retry, Infinity means infinite retry - * etc. - * If the observable still fails after this number of retry, the error will - * be throwed through this observable. - * - * - shouldRetry {Function|undefined} - Function which will receive the - * observable error each time it fails, and should return a boolean. If this - * boolean is false, the error will be directly thrown (without anymore - * retry). - * - * - onRetry {Function|undefined} - Function which will be triggered at - * each retry. Will receive two arguments: - * 1. The observable error - * 2. The current retry count, beginning at 1 for the first retry - * - * @returns {Observable} - * TODO Take errorSelector out. Should probably be entirely managed in the - * calling code via a catch (much simpler to use and to understand). - */ -export default function retryObsWithBackoff( - obs$ : Observable, - options : IBackoffOptions -) : Observable { - const { baseDelay, - maxDelay, - totalRetry, - shouldRetry, - onRetry } = options; - - let retryCount = 0; - - return obs$.pipe(catchError((error : unknown, source : Observable) => { - if ((!isNullOrUndefined(shouldRetry) && !shouldRetry(error)) || - retryCount++ >= totalRetry) - { - throw error; - } - - if (typeof onRetry === "function") { - onRetry(error, retryCount); - } - - const delay = Math.min(baseDelay * Math.pow(2, retryCount - 1), - maxDelay); - - const fuzzedDelay = getFuzzedDelay(delay); - return observableTimer(fuzzedDelay) - .pipe(mergeMap(() => source)); - })); -} diff --git a/src/utils/rx-throttle.ts b/src/utils/rx-throttle.ts deleted file mode 100644 index d1e96a81ec..0000000000 --- a/src/utils/rx-throttle.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Observable, - Observer, -} from "rxjs"; - -/** - * Throttle an asynchronous function returning an Observable to drop calls done - * before a previous one has finished or failed. - * - * @example - * ```js - * const fn = (time) => Observable.timer(time); - * const throttled = throttle(fn); - * - * const Obs1 = throttled(2000); // -> call fn(2000) and returns its Observable - * const Obs2 = throttled(1000); // -> won't do anything, Obs2 is an empty - * // observable (it directly completes) - * setTimeout(() => { - * const Obs3 = throttled(1000); // -> will call fn(1000) - * }, 2001); - * ``` - * - * @param {Function} func - * @returns {Function} - Function taking in argument the arguments you want - * to give your function, and returning an Observable. - */ -export default function throttle( - func : (arg1: T) => Observable -) : (arg1: T) => Observable; -export default function throttle( - func : (arg1: T, arg2: U) => Observable -) : (arg1: T, arg2: U) => Observable; -export default function throttle( - func : (arg1: T, arg2: U, arg3: V) => Observable -) : (arg1: T, arg2: U, arg3: V) => Observable; -export default function throttle( - func : (...args : Array) => Observable -) : (...args : Array) => Observable { - let isPending = false; - - return (...args : Array) : Observable => { - return new Observable((obs : Observer) => { - if (isPending) { - obs.complete(); - return undefined; - } - - isPending = true; - const funcSubscription = func(...args) - .subscribe({ - next: (i) => { obs.next(i); }, - error: (e) => { - isPending = false; - obs.error(e); - }, - complete: () => { - isPending = false; - obs.complete(); - }, - }); - - return () => { - funcSubscription.unsubscribe(); - isPending = false; - }; - }); - }; -} diff --git a/src/utils/rx-try_catch.ts b/src/utils/rx-try_catch.ts deleted file mode 100644 index 66c30e2585..0000000000 --- a/src/utils/rx-try_catch.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2015 CANAL+ Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Observable, - throwError as observableThrow, -} from "rxjs"; - -/** - * @param {Function} func - A function you want to execute - * @param {*} argsForFunc - The function's argument - * @returns {*} - If it fails, returns a throwing Observable, else the - * function's result (which should be, in most cases, an Observable). - */ -export default function tryCatch( - func : (args : T) => Observable, - argsForFunc : T -) : Observable { - try { - return func(argsForFunc); - } catch (e : unknown) { - return observableThrow(() => e); - } -} diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 0000000000..3f1790e736 --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,14 @@ +/** + * Convert a setTimeout to a Promise. + * + * You can use it to have a much more readable blocking code with async/await + * in some asynchronous tests. + * + * @param {number} timeInMs + * @returns {Promise} + */ +export default function sleep(timeInMs : number) : Promise { + return new Promise((res) => { + setTimeout(res, timeInMs); + }); +} diff --git a/src/utils/sorted_list.ts b/src/utils/sorted_list.ts index 4fd4bef936..bc44b89a47 100644 --- a/src/utils/sorted_list.ts +++ b/src/utils/sorted_list.ts @@ -110,6 +110,10 @@ export default class SortedList { return this._array[index]; } + public toArray() : T[] { + return this._array.slice(); + } + /** * Find the first element corresponding to the given predicate. * diff --git a/src/utils/task_canceller.ts b/src/utils/task_canceller.ts index 8d40191999..51bdd14379 100644 --- a/src/utils/task_canceller.ts +++ b/src/utils/task_canceller.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import log from "../log"; import assert from "./assert"; import noop from "./noop"; @@ -120,12 +121,12 @@ export default class TaskCanceller { * notified that it should be aborted when this `TaskCanceller` is triggered * (through its `cancel` method). */ - public signal : CancellationSignal; + public readonly signal : CancellationSignal; /** * `true` if this `TaskCanceller` has already been triggered. * `false` otherwise. */ - public isUsed : boolean; + private _isUsed : boolean; /** * @private * Internal function called when the `TaskCanceller` is triggered`. @@ -136,28 +137,43 @@ export default class TaskCanceller { * Creates a new `TaskCanceller`, with its own `CancellationSignal` created * as its `signal` provide. * You can then pass this property to async task you wish to be cancellable. - * @param {Object|undefined} options */ - constructor(options? : { - /** - * If set the TaskCanceller created here will automatically be triggered - * when that signal emits. - */ - cancelOn? : CancellationSignal | undefined; - } | undefined) { + constructor() { const [trigger, register] = createCancellationFunctions(); - this.isUsed = false; + this._isUsed = false; this._trigger = trigger; this.signal = new CancellationSignal(register); + } - if (options?.cancelOn !== undefined) { - const unregisterParent = options.cancelOn.register(() => { - this.cancel(); - }); - this.signal.register(unregisterParent); - } + /** + * Returns `true` if this `TaskCanceller` has already been triggered. + * `false` otherwise. + */ + public isUsed() : boolean { + return this._isUsed; } + /** + * Bind this `TaskCanceller` to a `CancellationSignal`, so the former + * is automatically cancelled when the latter is triggered. + * + * Note that this call registers a callback on the given signal, until either + * the current `TaskCanceller` is cancelled or until this given + * `CancellationSignal` is triggered. + * To avoid leaking memory, the returned callback allow to undo this link. + * It should be called if/when that link is not needed anymore, such as when + * there is no need for this `TaskCanceller` anymore. + * + * @param {Object} signal + * @returns {Function} + */ + public linkToSignal(signal : CancellationSignal) : () => void { + const unregister = signal.register(() => { + this.cancel(); + }); + this.signal.register(unregister); + return unregister; + } /** * "Trigger" the `TaskCanceller`, notify through its associated @@ -171,10 +187,10 @@ export default class TaskCanceller { * @param {Error} [srcError] */ public cancel(srcError? : CancellationError) : void { - if (this.isUsed) { + if (this._isUsed) { return ; } - this.isUsed = true; + this._isUsed = true; const cancellationError = srcError ?? new CancellationError(); this._trigger(cancellationError); } @@ -196,11 +212,6 @@ export default class TaskCanceller { * @class */ export class CancellationSignal { - /** - * True when the cancellation order was already triggered, meaning that the - * linked task needs to be aborted. - */ - public isCancelled : boolean; /** * Error associated to the cancellation, only set if the `CancellationSignal` * has been used (which means that the task has been cancelled). @@ -220,6 +231,12 @@ export class CancellationSignal { */ private _listeners : Array<(error : CancellationError) => void>; + /** + * True when the cancellation order was already triggered, meaning that the + * linked task needs to be aborted. + */ + private _isCancelled : boolean; + /** * Creates a new CancellationSignal. * /!\ Note: Only a `TaskCanceller` is supposed to be able to create one. @@ -227,20 +244,35 @@ export class CancellationSignal { * cancelled. */ constructor(registerToSource : (listener: ICancellationListener) => void) { - this.isCancelled = false; + this._isCancelled = false; this.cancellationError = null; this._listeners = []; registerToSource((cancellationError : CancellationError) : void => { this.cancellationError = cancellationError; - this.isCancelled = true; + this._isCancelled = true; while (this._listeners.length > 0) { - const listener = this._listeners.splice(this._listeners.length - 1, 1)[0]; - listener(cancellationError); + try { + const listener = this._listeners.pop(); + listener?.(cancellationError); + } catch (err) { + log.error("Error while calling clean up listener", + err instanceof Error ? err.toString() : + "Unknown error"); + } } }); } + /** + * Returns `true` when the cancellation order was already triggered, meaning + * that the linked task needs to be aborted. + * @returns boolean + */ + public isCancelled() : boolean { + return this._isCancelled; + } + /** * Registers a function that will be called when/if the current task is * cancelled. @@ -263,9 +295,10 @@ export class CancellationSignal { * performed. */ public register(fn : ICancellationListener) : () => void { - if (this.isCancelled) { + if (this._isCancelled) { assert(this.cancellationError !== null); fn(this.cancellationError); + return noop; } this._listeners.push(fn); return () => this.deregister(fn); @@ -280,13 +313,9 @@ export class CancellationSignal { * @param {Function} fn */ public deregister(fn : ICancellationListener) : void { - if (this.isCancelled) { - return; - } - for (let i = 0; i < this._listeners.length; i++) { + for (let i = this._listeners.length - 1; i >= 0; i--) { if (this._listeners[i] === fn) { this._listeners.splice(i, 1); - return; } } } diff --git a/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/end_number.js b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/end_number.js new file mode 100644 index 0000000000..b4240ca5cf --- /dev/null +++ b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/end_number.js @@ -0,0 +1,354 @@ +const BASE_URL = "http://" + + /* eslint-disable no-undef */ + __TEST_CONTENT_SERVER__.URL + ":" + + __TEST_CONTENT_SERVER__.PORT + + /* eslint-enable no-undef */ + "/DASH_static_SegmentTemplate_Multi_Periods/media/"; + +export default { + url: BASE_URL + "end_number.mpd", + transport: "dash", + isDynamic: false, + isLive: false, + duration: 240, + minimumPosition: 0, + maximumPosition: 240, + availabilityStartTime: 0, + periods: [ + { + start: 0, + duration: 120, + adaptations: { + audio: [ + { + id: "audio", + isAudioDescription: undefined, + language: undefined, + normalizedLanguage: undefined, + representations: [ + { + id: "aaclc", + bitrate: 66295, + codec: "mp4a.40.2", + mimeType: "audio/mp4", + index: { + init: { + url: "mp4-live-periods-aaclc-.mp4", + }, + segments: [ + { + time: 0, + duration: 440029 / 44100, + timescale: 1, + url: "mp4-live-periods-aaclc-1.m4s", + }, + { + time: 440029 / 44100, + duration: 440029 / 44100, + timescale: 1, + url: "mp4-live-periods-aaclc-2.m4s", + }, + ], + // ... + }, + }, + ], + }, + ], + video: [ + { + id: "video", + representations: [ + { + id: "h264bl_low", + bitrate: 50842, + height: 180, + width: 320, + frameRate: "25", + codec: "avc1.42c00d", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_low-.mp4", + }, + segments: [ + { + time: 0, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_low-1.m4s", + }, + { + time: 250000 / 25000, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_low-2.m4s", + }, + // ... + ], + }, + }, + + { + id: "h264bl_mid", + bitrate: 194834, + height: 360, + width: 640, + frameRate: "25", + codec: "avc1.42c01e", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_mid-.mp4", + }, + segments: [ + { + time: 0, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_mid-1.m4s", + }, + { + time: 250000 / 25000, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_mid-2.m4s", + }, + // ... + ], + }, + }, + + { + id: "h264bl_hd", + bitrate: 514793, + height: 720, + width: 1280, + frameRate: "25", + codec: "avc1.42c01f", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_hd-.mp4", + }, + segments: [ + { + time: 0, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_hd-1.m4s", + }, + { + time: 250000 / 25000, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_hd-2.m4s", + }, + // ... + ], + }, + }, + + { + id: "h264bl_full", + bitrate: 770663, + height: 1080, + width: 1920, + frameRate: "25", + codec: "avc1.42c028", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_full-.mp4", + }, + segments: [ + { + time: 0, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_full-1.m4s", + }, + { + time: 250000 / 25000, + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_full-2.m4s", + }, + // ... + ], + }, + }, + ], + }, + ], + }, + }, { + start: 120, + duration: 120, + adaptations: { + audio: [ + { + id: "audio", + isAudioDescription: undefined, + language: undefined, + normalizedLanguage: undefined, + representations: [ + { + id: "aaclc", + bitrate: 66295, + codec: "mp4a.40.2", + mimeType: "audio/mp4", + index: { + init: { + url: "mp4-live-periods-aaclc-.mp4", + }, + segments: [ + { + time: 120, + duration: 440029 / 44100, + timescale: 1, + url: "mp4-live-periods-aaclc-13.m4s", + }, + { + time: 120 + (440029 / 44100), + duration: 440029 / 44100, + timescale: 1, + url: "mp4-live-periods-aaclc-14.m4s", + }, + ], + // ... + }, + }, + ], + }, + ], + video: [ + { + id: "video", + representations: [ + { + id: "h264bl_low", + bitrate: 50842, + height: 180, + width: 320, + frameRate: "25", + codec: "avc1.42c00d", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_low-.mp4", + }, + segments: [ + { + time: 12 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_low-13.m4s", + }, + { + time: 13 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_low-14.m4s", + }, + // ... + ], + }, + }, + + { + id: "h264bl_mid", + bitrate: 194834, + height: 360, + width: 640, + frameRate: "25", + codec: "avc1.42c01e", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_mid-.mp4", + }, + segments: [ + { + time: 12 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_mid-13.m4s", + }, + { + time: 13 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_mid-14.m4s", + }, + // ... + ], + }, + }, + + { + id: "h264bl_hd", + bitrate: 514793, + height: 720, + width: 1280, + frameRate: "25", + codec: "avc1.42c01f", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_hd-.mp4", + }, + segments: [ + { + time: 12 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_hd-13.m4s", + }, + { + time: 13 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_hd-14.m4s", + }, + // ... + ], + }, + }, + + { + id: "h264bl_full", + bitrate: 770663, + height: 1080, + width: 1920, + frameRate: "25", + codec: "avc1.42c028", + mimeType: "video/mp4", + index: { + init: { + url: "mp4-live-periods-h264bl_full-.mp4", + }, + segments: [ + { + time: 12 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_full-13.m4s", + }, + { + time: 13 * (250000 / 25000), + duration: 250000 / 25000, + timescale: 1, + url: "mp4-live-periods-h264bl_full-14.m4s", + }, + // ... + ], + }, + }, + ], + }, + ], + }, + }, + ], +}; diff --git a/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/index.js b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/index.js index 445cecfaa6..e9879630cf 100644 --- a/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/index.js +++ b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/index.js @@ -1,9 +1,11 @@ import manifestInfos from "./infos.js"; import discontinuitiesBetweenPeriodsInfos from "./discontinuity_between_periods_infos"; import differentTypesDiscontinuitiesInfos from "./different_types_infos"; +import endNumberManifestInfos from "./end_number"; export { differentTypesDiscontinuitiesInfos, discontinuitiesBetweenPeriodsInfos, manifestInfos, + endNumberManifestInfos, }; diff --git a/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/media/end_number.mpd b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/media/end_number.mpd new file mode 100644 index 0000000000..a3a330b103 --- /dev/null +++ b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/media/end_number.mpd @@ -0,0 +1,33 @@ + + + + + mp4-live-periods-mpd.mpd generated by GPAC + TelecomParisTech(c)2012 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/urls.js b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/urls.js index 438e53b1d8..5c7ab15b9a 100644 --- a/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/urls.js +++ b/tests/contents/DASH_static_SegmentTemplate_Multi_Periods/urls.js @@ -72,6 +72,11 @@ module.exports = [ path: path.join(__dirname, "./media/different_types_discontinuity.mpd"), contentType: "application/dash+xml", }, + { + url: baseURL + "end_number.mpd", + path: path.join(__dirname, "./media/end_number.mpd"), + contentType: "application/dash+xml", + }, ...audioSegments, // remaining audio segments ...videoQualities, // every video segments ]; diff --git a/tests/contents/DASH_static_SegmentTimeline/forced-subtitles.js b/tests/contents/DASH_static_SegmentTimeline/forced-subtitles.js new file mode 100644 index 0000000000..f5eecb24d4 --- /dev/null +++ b/tests/contents/DASH_static_SegmentTimeline/forced-subtitles.js @@ -0,0 +1,23 @@ +const BASE_URL = "http://" + + /* eslint-disable no-undef */ + __TEST_CONTENT_SERVER__.URL + ":" + + __TEST_CONTENT_SERVER__.PORT + + /* eslint-enable no-undef */ + "/DASH_static_SegmentTimeline/media/"; +export default { + url: BASE_URL + "forced-subtitles.mpd", + transport: "dash", + isDynamic: false, + isLive: false, + minimumPosition: 0, + maximumPosition: 101.568367, + duration: 101.568367, + availabilityStartTime: 0, + + /** + * We don't care about that for now. As this content is only tested for track + * preferences. + * TODO still add it to our list of commonly tested contents? + */ + periods: [], +}; diff --git a/tests/contents/DASH_static_SegmentTimeline/index.js b/tests/contents/DASH_static_SegmentTimeline/index.js index faa2e768fa..c9c0ccb3d3 100644 --- a/tests/contents/DASH_static_SegmentTimeline/index.js +++ b/tests/contents/DASH_static_SegmentTimeline/index.js @@ -1,23 +1,27 @@ import manifestInfos from "./infos.js"; -import trickModeInfos from "./trickmode.js"; import discontinuityInfos from "./discontinuity.js"; +import forcedSubtitles from "./forced-subtitles.js"; import multiAdaptationSetsInfos from "./multi-AdaptationSets.js"; import multiPeriodDifferentChoicesInfos from "./multi_period_different_choices"; import multiPeriodSameChoicesInfos from "./multi_period_same_choices"; import notStartingAt0ManifestInfos from "./not_starting_at_0.js"; -import streamEventsInfos from "./event-stream"; import segmentTemplateInheritanceASRep from "./segment_template_inheritance_as_rep"; import segmentTemplateInheritancePeriodAS from "./segment_template_inheritance_period_as"; +import segmentTimelineEndNumber from "./segment_timeline_end_number"; +import streamEventsInfos from "./event-stream"; +import trickModeInfos from "./trickmode.js"; export { manifestInfos, - trickModeInfos, discontinuityInfos, + forcedSubtitles, multiAdaptationSetsInfos, multiPeriodDifferentChoicesInfos, multiPeriodSameChoicesInfos, notStartingAt0ManifestInfos, segmentTemplateInheritanceASRep, segmentTemplateInheritancePeriodAS, + segmentTimelineEndNumber, streamEventsInfos, + trickModeInfos, }; diff --git a/tests/contents/DASH_static_SegmentTimeline/media/forced-subtitles.mpd b/tests/contents/DASH_static_SegmentTimeline/media/forced-subtitles.mpd new file mode 100644 index 0000000000..8373f1b7a5 --- /dev/null +++ b/tests/contents/DASH_static_SegmentTimeline/media/forced-subtitles.mpd @@ -0,0 +1,133 @@ + + + + + dash/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/contents/DASH_static_SegmentTimeline/media/segment_timeline_end_number.mpd b/tests/contents/DASH_static_SegmentTimeline/media/segment_timeline_end_number.mpd new file mode 100644 index 0000000000..cd57925c9b --- /dev/null +++ b/tests/contents/DASH_static_SegmentTimeline/media/segment_timeline_end_number.mpd @@ -0,0 +1,146 @@ + + + + + dash/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/contents/DASH_static_SegmentTimeline/segment_timeline_end_number.js b/tests/contents/DASH_static_SegmentTimeline/segment_timeline_end_number.js new file mode 100644 index 0000000000..a9f5d94356 --- /dev/null +++ b/tests/contents/DASH_static_SegmentTimeline/segment_timeline_end_number.js @@ -0,0 +1,17 @@ +const BASE_URL = "http://" + + /* eslint-disable no-undef */ + __TEST_CONTENT_SERVER__.URL + ":" + + __TEST_CONTENT_SERVER__.PORT + + /* eslint-enable no-undef */ + "/DASH_static_SegmentTimeline/media/"; +export default { + url: BASE_URL + "./segment_timeline_end_number.mpd", + transport: "dash", + isDynamic: false, + isLive: false, + duration: 101.476, + minimumPosition: 0, + maximumPosition: 101.476, + availabilityStartTime: 0, + periods: [], +}; diff --git a/tests/contents/DASH_static_SegmentTimeline/urls.js b/tests/contents/DASH_static_SegmentTimeline/urls.js index cd48ad5f60..3278dd4382 100644 --- a/tests/contents/DASH_static_SegmentTimeline/urls.js +++ b/tests/contents/DASH_static_SegmentTimeline/urls.js @@ -91,6 +91,11 @@ module.exports = [ path: path.join(__dirname, "media/multi-AdaptationSets.mpd"), contentType: "application/dash+xml", }, + { + url: BASE_URL + "forced-subtitles.mpd", + path: path.join(__dirname, "media/forced-subtitles.mpd"), + contentType: "application/dash+xml", + }, { url: BASE_URL + "event-streams.mpd", path: path.join(__dirname, "media/event-streams.mpd"), @@ -106,6 +111,11 @@ module.exports = [ path: path.join(__dirname, "media/segment_template_inheritance_as_rep.mpd"), contentType: "application/dash+xml", }, + { + url: BASE_URL + "segment_timeline_end_number.mpd", + path: path.join(__dirname, "media/segment_timeline_end_number.mpd"), + contentType: "application/dash+xml", + }, { url: BASE_URL + "discontinuity.mpd", path: path.join(__dirname, "media/discontinuity.mpd"), diff --git a/tests/contents/DASH_static_number_based_SegmentTimeline/end_number.js b/tests/contents/DASH_static_number_based_SegmentTimeline/end_number.js new file mode 100644 index 0000000000..30ff26aed9 --- /dev/null +++ b/tests/contents/DASH_static_number_based_SegmentTimeline/end_number.js @@ -0,0 +1,15 @@ +const BASE_URL = "http://" + + /* eslint-disable no-undef */ + __TEST_CONTENT_SERVER__.URL + ":" + + __TEST_CONTENT_SERVER__.PORT + + /* eslint-enable no-undef */ + "/DASH_static_number_based_SegmentTimeline/media/"; + +export default { + url: BASE_URL + "end_number.mpd", + transport: "dash", + isDynamic: false, + isLive: false, + + // TODO ... +}; diff --git a/tests/contents/DASH_static_number_based_SegmentTimeline/index.js b/tests/contents/DASH_static_number_based_SegmentTimeline/index.js index 2fddc33e84..4bd96969f2 100644 --- a/tests/contents/DASH_static_number_based_SegmentTimeline/index.js +++ b/tests/contents/DASH_static_number_based_SegmentTimeline/index.js @@ -1,4 +1,8 @@ import manifestInfos from "./infos.js"; +import manifestInfosEndNumber from "./end_number"; -export { manifestInfos }; +export { + manifestInfos, + manifestInfosEndNumber, +}; diff --git a/tests/contents/DASH_static_number_based_SegmentTimeline/media/end_number.mpd b/tests/contents/DASH_static_number_based_SegmentTimeline/media/end_number.mpd new file mode 100644 index 0000000000..96db6448b1 --- /dev/null +++ b/tests/contents/DASH_static_number_based_SegmentTimeline/media/end_number.mpd @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/contents/DASH_static_number_based_SegmentTimeline/urls.js b/tests/contents/DASH_static_number_based_SegmentTimeline/urls.js index 6211e84506..4f38bbb37d 100644 --- a/tests/contents/DASH_static_number_based_SegmentTimeline/urls.js +++ b/tests/contents/DASH_static_number_based_SegmentTimeline/urls.js @@ -18,5 +18,10 @@ module.exports = [ path: path.join(__dirname, "./media/manifest.mpd"), contentType: "application/dash+xml", }, + { + url: baseURL + "end_number.mpd", + path: path.join(__dirname, "./media/end_number.mpd"), + contentType: "application/dash+xml", + }, ]; diff --git a/tests/generate_test_webpack_config.js b/tests/generate_test_webpack_config.js index 38c517628e..3776456c59 100644 --- a/tests/generate_test_webpack_config.js +++ b/tests/generate_test_webpack_config.js @@ -62,6 +62,7 @@ module.exports = function generateTestWebpackConfig({ HTML_VTT: 1, LOCAL_MANIFEST: 1, METAPLAYLIST: 1, + DEBUG_ELEMENT: 1, NATIVE_SAMI: 1, NATIVE_SRT: 1, NATIVE_TTML: 1, diff --git a/tests/integration/scenarios/dash_forced-subtitles.js b/tests/integration/scenarios/dash_forced-subtitles.js new file mode 100644 index 0000000000..e162ce5e46 --- /dev/null +++ b/tests/integration/scenarios/dash_forced-subtitles.js @@ -0,0 +1,148 @@ +import { expect } from "chai"; +import RxPlayer from "../../../src"; +import { + forcedSubtitles, +} from "../../contents/DASH_static_SegmentTimeline"; +import XHRMock from "../../utils/request_mock"; +import { + waitForLoadedStateAfterLoadVideo, +} from "../../utils/waitForPlayerState"; + +describe("DASH forced-subtitles content (SegmentTimeline)", function () { + let player; + let xhrMock; + + async function loadContent() { + player.loadVideo({ url: forcedSubtitles.url, + transport: forcedSubtitles.transport }); + await waitForLoadedStateAfterLoadVideo(player); + } + + function checkNoTextTrack() { + const currentTextTrack = player.getTextTrack(); + expect(currentTextTrack).to.equal(null); + } + + function checkAudioTrack(language, normalizedLanguage, isAudioDescription) { + const currentAudioTrack = player.getAudioTrack(); + expect(currentAudioTrack).to.not.equal(null); + expect(currentAudioTrack.language).to.equal(language); + expect(currentAudioTrack.normalized).to.equal(normalizedLanguage); + expect(currentAudioTrack.audioDescription).to.equal(isAudioDescription); + } + + function checkTextTrack(language, normalizedLanguage, props) { + const currentTextTrack = player.getTextTrack(); + expect(currentTextTrack).to.not.equal(null); + expect(currentTextTrack.language).to.equal(language); + expect(currentTextTrack.normalized).to.equal(normalizedLanguage); + expect(currentTextTrack.closedCaption).to.equal( + props.closedCaption, + `"closedCaption" not set to "${props.closedCaption}" but ` + + `to "${currentTextTrack.closedCaption}"`); + expect(currentTextTrack.forced).to.equal( + props.forced, + `"forced" not set to "${props.forced}" but ` + + `to "${currentTextTrack.forced}"`); + } + + beforeEach(() => { + player = new RxPlayer(); + player.setWantedBufferAhead(5); // We don't really care + xhrMock = new XHRMock(); + }); + + afterEach(() => { + player.dispose(); + xhrMock.restore(); + }); + + it("should set the forced text track associated to the current audio track", async function () { + player.dispose(); + player = new RxPlayer({ + preferredAudioTracks: [{ + language: "fr", + audioDescription: false, + }], + }); + + await loadContent(); + checkAudioTrack("fr", "fra", false); + checkTextTrack("fr", "fra", { closedCaption: false, forced: true }); + + player.setPreferredAudioTracks([{ language: "de", audioDescription: false }]); + await loadContent(); + checkAudioTrack("de", "deu", false); + checkTextTrack("de", "deu", { closedCaption: false, forced: true }); + }); + + it("should set the forced text track associated to no language if none is linked to the audio track", async function () { + player.dispose(); + player = new RxPlayer({ + preferredAudioTracks: [{ + language: "en", + audioDescription: false, + }], + }); + + await loadContent(); + checkAudioTrack("en", "eng", false); + checkTextTrack("", "", { + closedCaption: false, + forced: true, + }); + }); + + it("should still prefer preferences over forced subtitles", async function () { + player.dispose(); + player = new RxPlayer({ + preferredAudioTracks: [{ + language: "fr", + audioDescription: false, + }], + preferredTextTracks: [{ + language: "fr", + closedCaption: false, + }], + }); + + await loadContent(); + checkAudioTrack("fr", "fra", false); + checkTextTrack("fr", "fra", { closedCaption: false, forced: undefined }); + + player.setPreferredTextTracks([{ language: "fr", closedCaption: true }]); + await loadContent(); + checkAudioTrack("fr", "fra", false); + checkTextTrack("fr", "fra", { closedCaption: true, forced: undefined }); + + player.setPreferredAudioTracks([{ language: "de", audioDescription: undefined }]); + await loadContent(); + checkAudioTrack("de", "deu", false); + checkTextTrack("fr", "fra", { closedCaption: true, forced: undefined }); + + player.setPreferredTextTracks([null]); + await loadContent(); + checkNoTextTrack(); + }); + + it("should fallback to forced subtitles if no preference match", async function () { + player.dispose(); + player = new RxPlayer({ + preferredAudioTracks: [{ + language: "fr", + audioDescription: false, + }], + preferredTextTracks: [{ + language: "swa", + closedCaption: false, + }, { + language: "de", + closedCaption: true, + }], + }); + + await loadContent(); + checkAudioTrack("fr", "fra", false); + checkTextTrack("fr", "fra", { closedCaption: false, forced: true }); + }); +}); diff --git a/tests/integration/scenarios/dash_multi_periods.js b/tests/integration/scenarios/dash_multi_periods.js index 5559e85be2..0ae831cb9d 100644 --- a/tests/integration/scenarios/dash_multi_periods.js +++ b/tests/integration/scenarios/dash_multi_periods.js @@ -125,7 +125,6 @@ describe("DASH multi-Period with different choices", function () { expect(periodChangeEvents).to.have.length(1); expect(periodChangeEvents[0].id).to.equal("1"); - await goToSecondPeriod(); expect(availableAudioTracksChange).to.have.length(2); diff --git a/tests/integration/scenarios/end_number.js b/tests/integration/scenarios/end_number.js new file mode 100644 index 0000000000..12a7ee0165 --- /dev/null +++ b/tests/integration/scenarios/end_number.js @@ -0,0 +1,101 @@ +import { expect } from "chai"; +import XHRMock from "../../utils/request_mock"; +import { + manifestInfosEndNumber as numberBasedManifestInfos, +} from "../../contents/DASH_static_number_based_SegmentTimeline"; +import { + endNumberManifestInfos as templateManifestinfos, +} from "../../contents/DASH_static_SegmentTemplate_Multi_Periods"; +import { + segmentTimelineEndNumber as timeBasedManifestInfos, +} from "../../contents/DASH_static_SegmentTimeline"; +import RxPlayer from "../../../src"; +import sleep from "../../utils/sleep.js"; +import waitForState, { waitForLoadedStateAfterLoadVideo } from "../../utils/waitForPlayerState"; + +let player; + +describe("end number", function () { + let xhrMock; + beforeEach(() => { + player = new RxPlayer({ stopAtEnd: false }); + xhrMock = new XHRMock(); + }); + + afterEach(() => { + player.dispose(); + xhrMock.restore(); + }); + + it("should calculate the right duration according to endNumber on a number-based SegmentTemplate", async function () { + this.timeout(3000); + xhrMock.lock(); + player.setVideoBitrate(0); + player.setWantedBufferAhead(15); + const { url, transport } = templateManifestinfos; + + player.loadVideo({ + url, + transport, + autoPlay: false, + }); + await sleep(50); + expect(xhrMock.getLockedXHR().length).to.equal(1); + await xhrMock.flush(); + await sleep(500); + expect(player.getMinimumPosition()).to.eql(0); + expect(player.getMaximumPosition()).to.eql(120 + 30); + }); + + it("should not load segment later than the end number on a time-based SegmentTimeline", async function () { + this.timeout(15000); + xhrMock.lock(); + player.setVideoBitrate(0); + player.setWantedBufferAhead(15); + const { url, transport } = timeBasedManifestInfos; + + player.loadVideo({ + url, + transport, + autoPlay: true, + }); + await sleep(50); + expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest + await xhrMock.flush(); + await sleep(50); + expect(player.getMaximumPosition()).to.be.closeTo(20, 1); + expect(xhrMock.getLockedXHR().length).to.equal(4); // Init + media of audio + // + video + await xhrMock.flush(); + await waitForLoadedStateAfterLoadVideo(player); + + await sleep(50); + expect(xhrMock.getLockedXHR().length).to.equal(2); // next audio + video + xhrMock.flush(); + player.seekTo(19); + await sleep(50); + expect(xhrMock.getLockedXHR().length).to.equal(2); // last audio + video + xhrMock.flush(); + await waitForState(player, "ENDED", ["BUFFERING", "RELOADING", "PLAYING"]); + expect(player.getPosition()).to.be.closeTo(20, 1); + }); + + it("should calculate the right duration on a number-based SegmentTimeline", async function () { + this.timeout(10000); + xhrMock.lock(); + player.setVideoBitrate(0); + player.setWantedBufferAhead(15); + const { url, transport } = numberBasedManifestInfos; + + player.loadVideo({ + url, + transport, + autoPlay: true, + }); + await sleep(50); + expect(xhrMock.getLockedXHR().length).to.equal(1); + await xhrMock.flush(); + await sleep(50); + expect(player.getMaximumPosition()).to.be.closeTo(20, 1); + }); +}); diff --git a/tests/integration/utils/launch_tests_for_content.js b/tests/integration/utils/launch_tests_for_content.js index 1ea1b6544f..4f96f789db 100644 --- a/tests/integration/utils/launch_tests_for_content.js +++ b/tests/integration/utils/launch_tests_for_content.js @@ -1072,7 +1072,7 @@ export default function launchTestsForContent(manifestInfos) { describe("setPlaybackRate", () => { // TODO handle live contents it("should update the speed accordingly", async function() { - this.timeout(7000); + this.timeout(15000); player.loadVideo({ url: manifestInfos.url, transport, @@ -1081,16 +1081,20 @@ export default function launchTestsForContent(manifestInfos) { await waitForLoadedStateAfterLoadVideo(player); expect(player.getPosition()).to.be.closeTo(minimumPosition, 0.001); player.setPlaybackRate(1); + const before1 = performance.now(); player.play(); - await sleep(3000); + await sleep(2000); + const duration1 = (performance.now() - before1) / 1000; const initialPosition = player.getPosition(); - expect(initialPosition).to.be.closeTo(minimumPosition + 3, 1); + expect(initialPosition).to.be.closeTo(minimumPosition + duration1, 2); + const before2 = performance.now(); player.setPlaybackRate(3); await sleep(2000); + const duration2 = (performance.now() - before2) / 1000; const secondPosition = player.getPosition(); expect(secondPosition).to.be - .closeTo(initialPosition + 3 * 2, 1.5); + .closeTo(initialPosition + duration2 * 3, 2); }); }); diff --git a/tests/memory/index.js b/tests/memory/index.js index 17283c9202..8a32f43db4 100644 --- a/tests/memory/index.js +++ b/tests/memory/index.js @@ -117,7 +117,126 @@ describe("Memory tests", () => { | Initial heap usage (B) | ${initialMemory.usedJSHeapSize} | Difference (B) | ${heapDifference} `); - expect(heapDifference).to.be.below(5e6); + expect(heapDifference).to.be.below(3e6); + }); + + it("should not have a sensible memory leak after 1000 instances of the RxPlayer", async function() { + if (window.performance == null || + window.performance.memory == null || + window.gc == null) + { + // eslint-disable-next-line no-console + console.warn("API not available. Skipping test."); + return; + } + window.gc(); + await sleep(1000); + const initialMemory = window.performance.memory; + this.timeout(5 * 60 * 1000); + for (let i = 0; i < 1000; i++) { + player = new RxPlayer({ initialVideoBitrate: Infinity, + initialAudiobitrate: Infinity, + preferredtexttracks: [{ language: "fra", + closedcaption: true }] }); + player.loadVideo({ url: manifestInfos.url, + transport: manifestInfos.transport, + supplementaryTextTracks: [{ url: textTrackInfos.url, + language: "fra", + mimeType: "application/ttml+xml", + closedCaption: true }], + supplementaryImageTracks: [{ mimeType: "application/bif", + url: imageInfos.url }], + autoPlay: true }); + await waitForLoadedStateAfterLoadVideo(player); + player.dispose(); + } + await sleep(100); + window.gc(); + await sleep(1000); + const newMemory = window.performance.memory; + const heapDifference = newMemory.usedJSHeapSize - + initialMemory.usedJSHeapSize; + + // eslint-disable-next-line no-console + console.log(` + =========================================================== + | Current heap usage (B) | ${newMemory.usedJSHeapSize} + | Initial heap usage (B) | ${initialMemory.usedJSHeapSize} + | Difference (B) | ${heapDifference} + `); + expect(heapDifference).to.be.below(4e6); + }); + + it("should not have a sensible memory leak after many video quality switches", async function() { + if (window.performance == null || + window.performance.memory == null || + window.gc == null) + { + // eslint-disable-next-line no-console + console.warn("API not available. Skipping test."); + return; + } + this.timeout(15 * 60 * 1000); + player = new RxPlayer({ initialVideoBitrate: Infinity, + initialAudiobitrate: Infinity, + preferredtexttracks: [{ language: "fra", + closedcaption: true }] }); + await sleep(1000); + player.setWantedBufferAhead(5); + player.setMaxBufferBehind(5); + player.setMaxBufferAhead(15); + player.loadVideo({ url: manifestInfos.url, + transport: manifestInfos.transport, + supplementaryTextTracks: [{ url: textTrackInfos.url, + language: "fra", + mimeType: "application/ttml+xml", + closedCaption: true }], + supplementaryImageTracks: [{ mimeType: "application/bif", + url: imageInfos.url }], + autoPlay: false }); + await waitForLoadedStateAfterLoadVideo(player); + const videoBitrates = player.getAvailableVideoBitrates(); + if (videoBitrates.length <= 1) { + throw new Error( + "Not enough video bitrates to perform sufficiently pertinent tests" + ); + } + await sleep(1000); + + window.gc(); + const initialMemory = window.performance.memory; + + // Allows to alternate between two positions + let seekToBeginning = false; + for ( + let iterationIdx = 0; + iterationIdx < 500; + iterationIdx++ + ) { + if (seekToBeginning) { + player.seekTo(0); + } else { + player.seekTo(20); + seekToBeginning = true; + } + const bitrateIdx = iterationIdx % videoBitrates.length; + player.setVideoBitrate(videoBitrates[bitrateIdx]); + await sleep(1000); + } + window.gc(); + await sleep(1000); + const newMemory = window.performance.memory; + const heapDifference = newMemory.usedJSHeapSize - + initialMemory.usedJSHeapSize; + + // eslint-disable-next-line no-console + console.log(` + =========================================================== + | Current heap usage (B) | ${newMemory.usedJSHeapSize} + | Initial heap usage (B) | ${initialMemory.usedJSHeapSize} + | Difference (B) | ${heapDifference} + `); + expect(heapDifference).to.be.below(9e6); }); it("should not have a sensible memory leak after 1000 setTime calls of VideoThumbnailLoader", async function() { diff --git a/tests/performance/run.js b/tests/performance/run.js index e3065a7008..48a11c4e30 100644 --- a/tests/performance/run.js +++ b/tests/performance/run.js @@ -408,7 +408,6 @@ function createResultServer() { if (request.method === "OPTIONS") { answerWithCORS(response, 200); response.end(); - return; } else if (request.method == "POST") { let body = ""; request.on("data", function (data) { @@ -459,7 +458,6 @@ function createResultServer() { } else { response.end(); } - return; } } @@ -713,6 +711,7 @@ function createBundle(options) { HTML_VTT: 1, LOCAL_MANIFEST: 1, METAPLAYLIST: 1, + DEBUG_ELEMENT: 1, NATIVE_SAMI: 1, NATIVE_SRT: 1, NATIVE_TTML: 1, @@ -744,14 +743,7 @@ function createBundle(options) { * @returns {Promise} */ function removeFile(fileName) { - return new Promise((res, rej) => { - rimraf(fileName, (err) => { - if (err !== null && err !== undefined) { - rej(err); - } - res(); - }); - }); + return rimraf(fileName); } /** @@ -786,8 +778,7 @@ async function getChromeCmd() { case "win32": { const suffix = "\\Google\\Chrome\\Application\\chrome.exe"; const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]]; - for (let i = 0; i < prefixes.length; i++) { - const prefix = prefixes[i]; + for (const prefix of prefixes) { try { const windowsChromeDirectory = path.join(prefix, suffix); fs.accessSync(windowsChromeDirectory); diff --git a/tests/utils/request_mock/xhr_mock.js b/tests/utils/request_mock/xhr_mock.js index ee8415194d..a4f7a0eff6 100644 --- a/tests/utils/request_mock/xhr_mock.js +++ b/tests/utils/request_mock/xhr_mock.js @@ -164,7 +164,7 @@ export default class XHRMock { Math.min(len, nbrOfRequests) : len; const nbrOfRequestThatStays = len - nbrOfRequestsToFlush; while (this._sendingQueue.length > nbrOfRequestThatStays) { - const { xhr, data } = this._sendingQueue.pop(); + const { xhr, data } = this._sendingQueue.shift(); this.__xhrSend(xhr, data); proms.push(xhr._finished); } diff --git a/tsconfig.json b/tsconfig.json index b822dffe2e..55ee44e313 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "strictPropertyInitialization": true, "noUnusedParameters": true, "noUnusedLocals": true, - "types": ["rxjs", "node", "jest"], + "types": ["node", "jest"], "moduleResolution": "node", "typeRoots": [ "./src/typings/next-tick.d.ts", diff --git a/tsconfig.modules.json b/tsconfig.modules.json index 01c0ca5c45..829f38f886 100644 --- a/tsconfig.modules.json +++ b/tsconfig.modules.json @@ -13,7 +13,7 @@ "strictPropertyInitialization": true, "noUnusedParameters": true, "noUnusedLocals": true, - "types": ["rxjs", "node", "jest"], + "types": ["node", "jest"], "moduleResolution": "node", "module": "es2015", "typeRoots": [ diff --git a/webpack.config.js b/webpack.config.js index 6779d5b6f9..a42dd54a5d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -52,6 +52,8 @@ module.exports = (env) => { METAPLAYLIST: +(process.env.RXP_METAPLAYLIST === "true"), + DEBUG_ELEMENT: +(process.env.RXP_DEBUG_ELEMENT === "true"), + DIRECTFILE: +(isBarebone ? process.env.RXP_DIRECTFILE === "true" : process.env.RXP_DIRECTFILE !== "false"), @@ -133,8 +135,8 @@ module.exports = (env) => { minimizer: shouldMinify ? [new TerserPlugin()] : [], }, performance: { - maxEntrypointSize: shouldMinify ? 540000 : 2500000, - maxAssetSize: shouldMinify ? 540000 : 2500000, + maxEntrypointSize: shouldMinify ? 600000 : 2500000, + maxAssetSize: shouldMinify ? 600000 : 2500000, }, resolve: { extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],