rx-player
Version:
Canal+ HTML5 Video Player
1,361 lines (1,250 loc) • 49.5 kB
text/typescript
/**
* Configuration file for the whole player.
* Feel free to tweak those values if you know what you're doing.
*
* Please not that you will need to re-build the whole project to take these
* modifications into account.
*
* @type {Object}
*/
const DEFAULT_CONFIG = {
/**
* Default time interval after which a request will timeout, in ms.
* @type {Number}
*/
DEFAULT_REQUEST_TIMEOUT: 30 * 1000,
/**
* Default connection time after which a request will timeout, in ms.
* @type {Number}
*/
DEFAULT_CONNECTION_TIMEOUT: 15 * 1000,
/**
* Can be either:
* - "native": Subtitles are all displayed in a <track> element
* - "html": Subtitles are all displayed in a <div> separated from the video
* element. Can be useful to display richer TTML subtitles, for example.
* @type {Object|null}
*/
DEFAULT_TEXT_TRACK_MODE: "native" as "native" | "html",
/**
* Default behavior for the `enableFastSwitching` loadVideo options.
*
* Fast-switching allows to provide quicker transitions from lower quality
* segments to higher quality segments but might be badly supported on some
* devices.
* When enabled, the RxPlayer 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.
* When disabled, segments of a lower-quality will not be replaced.
*/
DEFAULT_ENABLE_FAST_SWITCHING: true,
/**
* In some cases after switching the current track or bitrate, the RxPlayer
* could be led to go into the `"RELOADING"` state, which corresponds to
* visually a black screen (with nothing audible) before restarting playback.
*
* We could want to seek back some milliseconds when doing that.
* For example, when switching the current audio track, it might make sense
* to restart some time before, so the beginning of the sentence can be heard
* again in the new language.
*
* This config property allows to set the relative position the RxPlayer will
* seek to after reloading, in seconds.
*
* For example: a value of `-0.7` means that will seek back 700 milliseconds
* when reloading due to a track or bitrate switch with necessitated a
* reloading.
*/
DELTA_POSITION_AFTER_RELOAD: {
/** Relative position when switching the bitrate */
bitrateSwitch: -0.1,
/**
* Relative position when switching the track.
*
* From tests, I noticed that seeking back was only really "pleasant" when
* switching the audio track.
*
* E.g. switching the video track often means changing the camera angle or
* even totally changing what is being seen and rely much less on temporal
* context than when an audio track is switched.
* As such, I decided to only set a sensible seek-back behavior when
* switching the audio track, and only a minimal one (to still ensure
* nothing was missed) for video.
*
* "Other" mainly concern text track, where seeking back could even be
* annoying, so that behavior has been disabled in that case.
*/
trackSwitch: { audio: 0, video: 0, other: 0 },
},
/**
* Behavior of the RxPlayer when encountering a whole other codec on a already
* existing audio or video SourceBuffer.
*
* Can be either:
*
* - "continue": Segments linked to the new codec will continue to be
* pushed to that same SourceBuffer. The RxPlayer will still try to call
* the `changeType` API on the SourceBuffer before pushing those
* segments but continue even if this call failed.
*
* - "reload": Every time a new incompatible codec is encountered on a
* given SourceBuffer, we will reload the MediaSource.
*/
DEFAULT_CODEC_SWITCHING_BEHAVIOR: "continue" as "continue" | "reload",
/**
* Specifies the behavior when all audio tracks are not playable.
*
* - If set to `"continue"`, the player will proceed to play the content without audio.
* - If set to `"error"`, an error will be thrown to indicate that the audio tracks could not be played.
*
* Note: If neither the audio nor the video tracks are playable, an error will be thrown regardless of this setting.
*/
DEFAULT_AUDIO_TRACKS_NOT_PLAYABLE_BEHAVIOR: "error" as "continue" | "error",
/**
* Specifies the behavior when all video tracks are not playable.
*
* - If set to `"continue"`, the player will proceed to play the content without video.
* - If set to `"error"`, an error will be thrown to indicate that the video tracks could not be played.
*
* Note: If neither the audio nor the video tracks are playable, an error will be thrown regardless of this setting.
*/
DEFAULT_VIDEO_TRACKS_NOT_PLAYABLE_BEHAVIOR: "error" as "continue" | "error",
/**
* If set to true, video through loadVideo will auto play by default
* @type {Boolean}
*/
DEFAULT_AUTO_PLAY: false,
/**
* Default buffer goal in seconds.
* Once enough content has been downloaded to fill the buffer up to
* ``current position + DEFAULT_WANTED_BUFFER_AHEAD", we will stop downloading
* content.
* @type {Number}
*/
DEFAULT_WANTED_BUFFER_AHEAD: 30,
/**
* Default max buffer size ahead of the current position in seconds.
* The buffer _after_ this limit will be garbage collected.
* Set to Infinity for no limit.
* @type {Number}
*/
DEFAULT_MAX_BUFFER_AHEAD: Infinity,
/**
* Default max buffer size ahead of the current position in seconds.
* The buffer _before_ this limit will be garbage collected.
* Set to Infinity for no limit.
* @type {Number}
*/
DEFAULT_MAX_BUFFER_BEHIND: Infinity,
/**
* Default video buffer memory limit in kilobytes.
* Once enough video content has been downloaded to fill the buffer up to
* DEFAULT_MAX_VIDEO_BUFFER_SIZE , we will stop downloading
* content.
* @type {Number}
*/
DEFAULT_MAX_VIDEO_BUFFER_SIZE: Infinity,
/**
* Maximum possible buffer ahead for each type of buffer, to avoid too much
* memory usage when playing for a long time.
* Equal to Infinity if not defined here.
* @type {Object}
*/
MAXIMUM_MAX_BUFFER_AHEAD: {
text: 5 * 60 * 60,
} as Partial<Record<"audio" | "video" | "text", number>>,
/**
* Minimum possible buffer ahead for each type of buffer, to avoid Garbage
* Collecting too much data when it would have adverse effects.
* Equal to `0` if not defined here.
* @type {Object}
*/
MINIMUM_MAX_BUFFER_AHEAD: {
// Text segments are both much lighter on resources and might
// actually be much larger than other types of segments in terms
// of duration. Let's make an exception here by authorizing a
// larger text buffer ahead, to avoid unnecesarily reloading the
// same text track.
text: 2 * 60,
} as Partial<Record<"audio" | "video" | "image" | "text", number>>,
/**
* Maximum possible buffer behind for each type of buffer, to avoid too much
* memory usage when playing for a long time.
* Equal to Infinity if not defined here.
* @type {Object}
*/
MAXIMUM_MAX_BUFFER_BEHIND: {
text: 5 * 60 * 60,
} as Partial<Record<"audio" | "video" | "text", number>>,
/**
* Default bitrate ceils initially set as the first content begins.
*
* If no track is found with a bitrate inferior or equal to the
* bitrate there, the one with the lowest bitrate will be taken instead.
*
* Set to 0 for the lowest bitrate, Infinity for the highest.
*
* These values are only useful for the first content played, as consecutive
* play will always take the last set one.
* @type {Object}
*/
DEFAULT_BASE_BANDWIDTH: 0,
/**
* Delay after which, if the page is hidden, the user is considered inactive
* on the current video.
*
* Allow to enforce specific optimizations when the page is not shown.
* @see DEFAULT_THROTTLE_WHEN_HIDDEN
* @type {Number}
*/
INACTIVITY_DELAY: 60 * 1000,
/**
* If true, if the video is considered in a "hidden" state for a delay specified by
* the INACTIVITY DELAY config property, we throttle automatically to the video
* representation with the lowest bitrate.
* @type {Boolean}
*/
DEFAULT_THROTTLE_VIDEO_BITRATE_WHEN_HIDDEN: false,
/**
* Default video resolution limit behavior.
*
* This option allows for example to throttle the video resolution so it
* does not exceed the screen resolution.
*
* Here set to "none" by default to disable throttling.
* @type {Boolean}
*/
DEFAULT_VIDEO_RESOLUTION_LIMIT: "none" as const,
/**
* Default initial live gap considered if no presentation delay has been
* suggested, in seconds.
* @type {Number}
*/
DEFAULT_LIVE_GAP: {
DEFAULT: 10,
LOW_LATENCY: 3.5,
},
/**
* Maximum time, in seconds, the player should automatically skip when stalled
* because of a current hole in the buffer.
* Bear in mind that this might seek over not-yet-downloaded/pushed segments.
* @type {Number}
*/
BUFFER_DISCONTINUITY_THRESHOLD: 0.2,
/**
* Ratio used to know if an already loaded segment should be re-buffered.
* We re-load the given segment if the current one times that ratio is
* inferior to the new one.
* @type {Number}
*/
BITRATE_REBUFFERING_RATIO: 1.5,
/**
* The default number of times a manifest request will be re-performed
* when loaded/refreshed if the request finishes on an error which
* justify an retry.
*
* Note that some errors do not use this counter:
* - if the error is not due to the xhr, no retry will be peformed
* - if the error is an HTTP error code, but not a 500-smthg or a 404, no
* retry will be performed.
* @type Number
*/
DEFAULT_MAX_MANIFEST_REQUEST_RETRY: 4,
/**
* Default delay, in seconds, during which a CDN will be "downgraded".
*
* For example in case of media content being available on multiple CDNs, the
* RxPlayer may decide that a CDN is less reliable (for example, it returned a
* server error) and should thus be avoided, at least for some time
*
* This value is the amount of time this CDN will be "less considered" than the
* alternatives.
*/
DEFAULT_CDN_DOWNGRADE_TIME: 60,
/**
* The default number of times a segment request will be re-performed when
* on error which justify a retry.
*
* Note that some errors do not use this counter:
* - if the error is not due to the xhr, no retry will be peformed
* - if the error is an HTTP error code, but not a 500-smthg or a 404, no
* retry will be performed.
* @type Number
*/
DEFAULT_MAX_REQUESTS_RETRY_ON_ERROR: 4,
/**
* Initial backoff delay when a segment / manifest download fails, in
* milliseconds.
*
* This delay will then grow exponentally by power of twos (200, 400, 800
* etc.)
*
* Please note that this delay is not exact, as it will be fuzzed.
* @type {Number}
*/
INITIAL_BACKOFF_DELAY_BASE: {
REGULAR: 200,
LOW_LATENCY: 50,
},
/**
* Maximum backoff delay when a segment / manifest download fails, in
* milliseconds.
*
* Please note that this delay is not exact, as it will be fuzzed.
* @type {Number}
*/
MAX_BACKOFF_DELAY_BASE: {
REGULAR: 3000,
LOW_LATENCY: 1000,
},
/**
* Minimum interval at which playback information samples will be taken. This
* variable is for the "regular" mediasource strategy (that is, not for the
* directfile API.
*
* At each of these interval, various different modules in the RxPlayer will
* run based on the information communicated.
*
* Keep in mind this is the minimum interval. This logic will also be
* triggered when various events of the media element are received.
* @type {Number}
*/
SAMPLING_INTERVAL_MEDIASOURCE: 1000,
/**
* Same than SAMPLING_INTERVAL_MEDIASOURCE but for lowLatency mode.
* @type {Number}
*/
SAMPLING_INTERVAL_LOW_LATENCY: 500,
/**
* Same than SAMPLING_INTERVAL_MEDIASOURCE but for the directfile API.
* @type {Number}
*/
SAMPLING_INTERVAL_NO_MEDIASOURCE: 500,
/**
* Amount of buffer to have ahead of the current position before we may
* consider buffer-based adaptive estimates, in seconds.
*
* For example setting it to `10` means that we need to have ten seconds of
* buffer ahead of the current position before relying on buffer-based
* adaptive estimates.
*
* To avoid getting in-and-out of the buffer-based logic all the time, it
* should be set higher than `ABR_EXIT_BUFFER_BASED_ALGO`.
*/
ABR_ENTER_BUFFER_BASED_ALGO: 10,
/**
* Below this amount of buffer ahead of the current position, in seconds, we
* will stop using buffer-based estimate in our adaptive logic to select a
* quality.
*
* For example setting it to `5` means that if we have less than 5 seconds of
* buffer ahead of the current position, we should stop relying on
* buffer-based estimates to choose a quality.
*
* To avoid getting in-and-out of the buffer-based logic all the time, it
* should be set lower than `ABR_ENTER_BUFFER_BASED_ALGO`.
*/
ABR_EXIT_BUFFER_BASED_ALGO: 5,
/**
* Minimum number of bytes sampled before we trust the estimate.
* If we have not sampled much data, our estimate may not be accurate
* enough to trust.
* If the total of bytes sampled is less than this value, we use a
* default estimate.
* This specific value is based on experimentations.
* @type {Number}
*/
ABR_MINIMUM_TOTAL_BYTES: 150e3,
/**
* Minimum number of bytes, under which samples are discarded.
* Our models do not include latency information, so connection startup time
* (time to first byte) is considered part of the download time.
* Because of this, we should ignore very small downloads which would cause
* our estimate to be too low.
* This specific value is based on experimentation.
* @type {Number}
*/
ABR_MINIMUM_CHUNK_SIZE: 16e3,
/**
* Factor with which is multiplied the bandwidth estimate when the ABR is in
* starvation mode.
* @type {Object}
*/
ABR_STARVATION_FACTOR: {
DEFAULT: 0.72,
LOW_LATENCY: 0.72,
},
/**
* Factor with which is multiplied the bandwidth estimate when the ABR is not
* in starvation mode.
* @type {Object}
*/
ABR_REGULAR_FACTOR: {
DEFAULT: 0.72,
LOW_LATENCY: 0.72,
},
/**
* If a media buffer has less than ABR_STARVATION_GAP in seconds ahead of the
* current position in its buffer, the adaptive logic will go into starvation
* mode.
*
* It gets out of starvation mode when the OUT_OF_STARVATION_GAP value is
* reached.
*
* Under this starvation mode:
*
* - the bandwidth considered will be a little lower than the one estimated
*
* - the time the next important request take will be checked
* multiple times to detect when/if it takes too much time.
* If the request is considered too long, the bitrate will be hastily
* re-calculated from this single request.
*
* @type {Object}
*/
ABR_STARVATION_GAP: {
DEFAULT: 5,
LOW_LATENCY: 5,
},
OUT_OF_STARVATION_GAP: {
DEFAULT: 7,
LOW_LATENCY: 7,
},
/**
* This is a security to avoid going into starvation mode when the content is
* ending (@see ABR_STARVATION_GAP).
* Basically, we subtract that value from the global duration of the content
* and we never enter "starvation mode" if the currently available buffer
* (which equals to the current position + the available buffer ahead of it)
* is equal or higher than this value.
* @type {Number}
*/
ABR_STARVATION_DURATION_DELTA: 0.1,
/**
* Half-life, in seconds for a fastly-evolving exponential weighted moving
* average.
* The lower it is, the faster the ABR logic will react to the bandwidth
* falling quickly.
* Should be kept to a lower number than ABR_SLOW_EMA for coherency reasons.
* @type {Number}
*/
ABR_FAST_EMA: 2,
/**
* Half-life, in seconds for a slowly-evolving exponential weighted moving
* average.
* The lower it is, the faster the ABR logic is going to react to recent
* bandwidth variation, on the higher and on the lower side.
* Should be kept to a higher number than ABR_FAST_EMA for coherency reasons.
* @type {Number}
*/
ABR_SLOW_EMA: 10,
/**
* Number of seconds ahead in the buffer after which playback will resume when
* seeking on an unbuffered part of the content.
* @type {Number}
*/
RESUME_GAP_AFTER_SEEKING: {
DEFAULT: 1.5,
LOW_LATENCY: 0.5,
},
/**
* Number of seconds ahead in the buffer after which playback will resume when
* the player was rebuffering due to a low readyState.
* @type {Number}
*/
RESUME_GAP_AFTER_NOT_ENOUGH_DATA: {
DEFAULT: 0.5,
LOW_LATENCY: 0.5,
},
/**
* Number of seconds ahead in the buffer after which playback will resume
* after the player went through a buffering step.
* @type {Number}
*/
RESUME_GAP_AFTER_BUFFERING: {
DEFAULT: 5,
LOW_LATENCY: 0.5,
},
/**
* Maximum number of seconds in the buffer based on which a "rebuffering"
* strategy will be considered:
* The player will pause playback to get enough time building a sufficient
* buffer. This mostly happen when seeking in an unbuffered part or when not
* enough buffer is ahead of the current position.
* @type {Number}
*/
REBUFFERING_GAP: {
DEFAULT: 0.5,
LOW_LATENCY: 0.2,
},
/**
* Amount of time (in seconds) with data ahead of the current position, at
* which we always consider the browser to be able to play.
*
* If the media element has this amount of data in advance or more but
* playback cannot begin, the player will consider it "freezing".
*/
MINIMUM_BUFFER_AMOUNT_BEFORE_FREEZING: 2,
/**
* A media whose position inexplicably does not increment despite playing is
* called as "freezing" in the RxPlayer.
*
* If the media is still "freezing" after waiting for `UNFREEZING_SEEK_DELAY`
* milliseconds, the RxPlayer will try to un-freeze the situation by interacting
* with the media element.
*
* Those interactions can be costly in time before playback continue, so it
* should be set at a sufficiently high value to avoid false positives.
*/
UNFREEZING_SEEK_DELAY: 6000,
/**
* A media whose position inexplicably does not increment despite playing is
* called as "freezing" in the RxPlayer.
*
* A small freezing interval may be normal as the browser may take time before
* playing, e.g. after a seek.
*
* If the media is still "freezing" after waiting for `FREEZING_STALLED_DELAY`
* milliseconds, the RxPlayer will emit a BUFFERING state through its API to
* notify that the player cannot currently advance.
*/
FREEZING_STALLED_DELAY: 600,
/**
* A media whose position inexplicably does not increment despite playing is
* called as "freezing" in the RxPlayer.
*
* If the media is frozen for a sufficiently large time
* (@see UNFREEZING_SEEK_DELAY), the RxPlayer will perform a seek corresponding
* to its current position plus `UNFREEZING_DELTA_POSITION` seconds.
*
* This should be kept short enough as the goal is just to un-freeze lower-level
* buffers.
*/
UNFREEZING_DELTA_POSITION: 0.001,
/**
* `FREEZING` is a situation where the playback does not seem to advance despite
* all web indicators telling us we can.
* Those may be linked to device issues, but sometimes are just linked to
* performance or it may be just decryption negotiations taking more time than
* expected.
*
* Anyway we might in the RxPlayer "flush" the buffer in that situation to
* un-stuck playback (this is usually done by seeking close to the current
* position),
*
* Yet that "flush" attempt may not in the end be succesful.
*
* If a flush was performed more than `FREEZING_FLUSH_FAILURE_DELAY.MINIMUM`
* milliseconds ago and less than `FREEZING_FLUSH_FAILURE_DELAY.MAXIMUM`
* milliseconds ago, yet a `FREEZING` situation at roughly the same playback
* position (deviating from less than
* `FREEZING_FLUSH_FAILURE_DELAY.POSITION_DELTA` seconds from it) is
* encountered again, we will consider that the flushing attempt was unsuccesful
* and try more agressive solutions (such as reloading the content).
*/
FREEZING_FLUSH_FAILURE_DELAY: {
MAXIMUM: 20000,
MINIMUM: 4000,
POSITION_DELTA: 1,
},
/**
* The RxPlayer has a recurring logic which will synchronize the browser's
* buffers' buffered time ranges with its internal representation in the
* RxPlayer to then rely on that internal representation to determine where
* segments are technically present in the browser's buffer.
*
* We found out that when inserting a new segment to the buffer, the browser
* may actually take time before actually considering the full segment in its
* advertised buffered time ranges.
*
* This value thus set an amount of milliseconds we might want to wait before
* being sure that the buffered time ranges should have considered a segment
* that has been pushed.
*/
SEGMENT_SYNCHRONIZATION_DELAY: 1500,
/**
* The `SEGMENT_SYNCHRONIZATION_DELAY` defined in this same configuration
* object only needs to be used if it appears that the current buffered
* time ranges do not reflect the full data of a pushed segment yet.
*
* The `MISSING_DATA_TRIGGER_SYNC_DELAY` value thus allows to define a
* minimum time difference in seconds between what's buffered and what the
* segment's ranges should have been, from which we might consider that we may
* want to wait the `SEGMENT_SYNCHRONIZATION_DELAY` before trusting the buffered
* time ranges for that segment.
* If what's missing from that segment is however less than that value in
* seconds, we can begin to trust the reported buffered time ranges.
*
* Should generally be inferior to `MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT`.
*/
MISSING_DATA_TRIGGER_SYNC_DELAY: 0.1,
/**
* Maximum authorized difference between what we calculated to be the
* beginning or end of the segment in a media buffer and what we
* actually are noticing now.
*
* If the segment seems to have removed more than this size in seconds, we
* will infer that the segment has been garbage collected and we might try to
* re-download it.
* @type {Number}
*/
MAX_TIME_MISSING_FROM_COMPLETE_SEGMENT: 0.15,
/**
* The maximum authorized difference, in seconds, between the real buffered
* time of a given chunk and what the segment information of the Manifest
* tells us.
*
* Setting a value too high can lead to parts of the media buffer being
* linked to the wrong segments and to segments wrongly believed to be still
* complete (instead of garbage collected).
*
* Setting a value too low can lead to parts of the media buffer not being
* linked to the concerned segment and to segments wrongly believed to be
* partly garbage collected (instead of complete segments).
* @type {Number}
*/
MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE: 0.4,
/**
* The maximum authorized difference, in seconds, between the duration a
* segment should have according to the Manifest and the actual duration it
* seems to have once pushed to the media buffer.
*
* Setting a value too high can lead to parts of the media buffer being
* linked to the wrong segments and to segments wrongly believed to be still
* complete (instead of garbage collected).
*
* Setting a value too low can lead to parts of the media buffer not being
* linked to the concerned segment and to segments wrongly believed to be
* partly garbage collected (instead of complete segments). This last point
* could lead to unnecessary segment re-downloading.
* @type {Number}
*/
MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE: 0.3,
/**
* Minimum duration in seconds a segment should be into a buffered range to be
* considered as part of that range.
* Segments which have less than this amount of time "linked" to a buffered
* range will be deleted.
*
* Setting a value too low can lead in worst-case scenarios to segments being
* wrongly linked to the next or previous range it is truly linked too (if
* those ranges are too close).
*
* Setting a value too high can lead to part of the buffer not being assigned
* any segment. It also limits the minimum duration a segment can be.
*
* TODO As of now, this limits the minimum size a complete segment can be. A
* better logic would be to also consider the duration of a segment. Though
* this logic could lead to bugs with the current code.
* @type {Number}
*/
MINIMUM_SEGMENT_SIZE: 0.001,
/**
* Append windows allow to filter media data from segments if they are outside
* a given limit.
* Coded frames with presentation timestamp within this range are allowed to
* be appended to the media buffer while coded frames outside this range are
* filtered out.
*
* Those are often set to be the start and end of the "Period" the segment is
* in.
* However, we noticed that some browsers were too aggressive when the exact
* limits were set: more data than needed was removed, often leading to
* discontinuities.
*
* Those securities are added to the set windows (substracted from the window
* start and added to the window end) to avoid those problems.
* @type {Object}
*/
APPEND_WINDOW_SECURITIES: {
START: 0.2,
END: 0.1,
},
/**
* Maximum interval at which text tracks are refreshed in an "html"
* textTrackMode.
*
* The text tracks are also refreshed on various video events, this interval
* will only trigger a refresh if none of those events was received during
* that timespan.
*
* Note that if the TextTrack cue did not change between two intervals or
* events, the DOM won't be refreshed.
* The TextTrack cues structure is also optimized for fast retrieval.
* We should thus not have much of a performance impact here if we set a low
* interval.
*
* @type {Number}
*/
MAXIMUM_HTML_TEXT_TRACK_UPDATE_INTERVAL: 50,
/**
* On browsers with no ResizeObserver API, this will be the interval in
* milliseconds at which we should check if the text track element has
* changed its size, and updates proportional text-track data accordingly
* (like a proportional font-size).
*
* This is only used:
* - in an "html" textTrackMode
* - when some styling is proportional in the text track data
*
* Putting a value too low will render faster but might use to much proc time.
* Putting a value too high might provoke a re-render too late after the user
* changed the element's size (e.g. when going to fullscreen mode).
*
* @type {Number}
*/
TEXT_TRACK_SIZE_CHECKS_INTERVAL: 250,
/**
* The Buffer padding is a time offset from the current time that affects
* the buffer.
*
* Basically, from a given time, if the current buffer gap number (time
* between the current time and the end of the downloaded buffer) is above
* the padding described here (of the corresponding type), we won't
* reschedule segments for that range.
*
* This is to avoid excessive re-buffering.
*
* Keeping the padding too low would increase the risk of re-bufferings.
*
* Keeping the padding too high would delay visible quality increase.
*
* @type {Object}
*/
BUFFER_PADDING: {
audio: 1, // only "audio" segments
video: 3, // only "video" segments
other: 1, // tracks which are not audio/video (like text).
},
/**
* Segments of different types are downloaded by steps:
*
* - first the audio/video/text Segments which are immediately needed
*
* - then once every of those Segments have been downloaded, less-needed
* Segments
*
* - then once every of those less-needed Segments have been downloaded,
* even less-needed Segments
*
* - etc.
*
* This stepped download strategy allows to make a better use of network
* ressources.
*
* For example, if more than sufficient audio buffer has been downloaded but
* the immediately-needed video Segment is still pending its request, we might
* be in a situation of rebuffering.
* In that case, a better strategy would be to make sure every network
* ressource is allocated for this video Segment before rebuffering happens.
*
* This is where those steps become useful.
*
* --
*
* The numbers defined in this Array describe what the steps are.
*
* Each number is linked to a distance from the current playing position, in
* seconds.
* Distances which will be used as limit points, from which a new step is
* reached (see example).
*
* In the RxPlayer's code, each step is then translated in to a priority
* number.
* The lower is that number, the lower is the step and the lower is the step,
* the higher is the priority.
*
* Note: You can set an empty array to deactivate the steps feature (every
* Segments have the same priority).
*
* @example
*
* let's imagine the following SEGMENT_PRIORITIES_STEPS array:
* [5, 11, 17, 25]
*
* To link each Segments to a corresponding priority number (and thus to a
* specific step), we have to consider the distance between the current
* position and the start time of the Segment.
*
* We have in our example 5 groups, which correspond to the following possible
* distances:
* 1. inferior to 5 => first step (priority number = 0)
* 2. between 5 and 11 => second step (priority number = 1)
* 3. between 11 and 17 => third step (priority number = 2)
* 4. between 17 and 25 => fourth step (priority number = 3)
* 5. superior to 25 => fifth step (priority number = 4)
*
* Segments corresponding to a lower-step will need to all be downloaded
* before Segments of a newer step begin.
*
* @type {Array.<Number>}
*/
SEGMENT_PRIORITIES_STEPS: [
2, // 1st Step (priority number = 0): < 2
4, // 2nd Step (priority number = 1): 2-4
8, // 3rd Step (priority number = 2): 4-8
12, // 4th Step (priority number = 3): 8-12
18, // 5th Step (priority number = 4): 12-18
25,
], // 6th Step (priority number = 5): 18-25
// 7th Step (priority number = 6): >= 25
/**
* Some segment requests are said to be "high priority".
*
* Requests in that category once done will cancel any segment request that
* has a low priority number (see `SEGMENT_PRIORITIES_STEPS`) - meaning a
* priority number equal to `MIN_CANCELABLE_PRIORITY` or more.
*
* Enter here the last priority number that is considered high priority
* (beginning by the first step, which has the priority number `0`).
* @type {number}
*/
MAX_HIGH_PRIORITY_LEVEL: 1, // priority number 1 and lower is high priority
/**
* Enter here the first priority step (see `SEGMENT_PRIORITIES_STEPS`) that
* will be considered as low priority.
*
* Segment requests with a low priority will be cancelled if a high priority
* segment request (see MAX_HIGH_PRIORITY_LEVEL) is scheduled while they are
* pending.
*
* This number should be strictly superior to the value indicated in
* `MAX_HIGH_PRIORITY_LEVEL`.
* @type {number}
*/
MIN_CANCELABLE_PRIORITY: 3, // priority number 3 onward can be cancelled
/**
* Codecs used in the videoCapabilities of the MediaKeySystemConfiguration
* (DRM).
*
* Defined in order of importance (first will be tested first etc.)
* @type {Array.<string>}
*/
EME_DEFAULT_VIDEO_CODECS: [
'video/mp4;codecs="avc1.4d401e"',
'video/mp4;codecs="avc1.42e01e"',
'video/mp4;codecs="hvc1.1.6.L93.B0"',
'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.<string>}
*/
EME_DEFAULT_AUDIO_CODECS: [
'audio/mp4;codecs="mp4a.40.2"',
'audio/webm;codecs="opus"',
'audio/mp4;codecs="ec-3"',
],
/**
* 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.<string>}
*/
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" keysystems.
*
* Defined in order of importance (first will be tested first etc.)
* @type {Array.<string>}
*/
EME_DEFAULT_PLAYREADY_RECOMMENDATION_ROBUSTNESSES: ["3000", "2000"],
/**
* Link canonical key systems names to their respective reverse domain name,
* used in the EME APIs.
* This allows to have a simpler API, where users just need to set "widevine"
* or "playready" as a keySystem.
* @type {Object}
*/
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"],
} as Partial<Record<string, string[]>>,
/**
* The Manifest parsing logic has a notion of "unsafeMode" which allows to
* speed-up this process a lot with a small risk of de-synchronization with
* what actually is on the server.
* Because using that mode is risky, and can lead to all sort of problems, we
* regularly should fall back to a regular "safe" parsing every once in a
* while.
* This value defines how many consecutive time maximum the "unsafeMode"
* parsing can be done.
*/
MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE: 10,
/**
* Minimum time spent parsing the Manifest before we can authorize parsing
* it in an "unsafeMode", to speed-up the process with a little risk.
* Please note that this parsing time also sometimes includes idle time such
* as when the parser is waiting for a request to finish.
*/
MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE: 200,
/**
* Minimum amount of <S> elements in a DASH MPD's <SegmentTimeline> element
* necessary to begin parsing the current SegmentTimeline element in an
* unsafe manner (meaning: with risks of de-synchronization).
* This is only done when the "unsafeMode" parsing mode is enabled.
*/
MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY: 300,
/**
* When we detect that the local Manifest might be out-of-sync with the
* server's one, we schedule a Manifest refresh.
* However, as this "unsynchronization" is only a theory and as we do not want
* to send too many Manifest requests, we keep a delay between the last
* Manifest refresh done and that one.
* This value indicates which delay we want. Note that the Manifest could
* still be refreshed before this delay for other reasons.
* @type {Number}
*/
OUT_OF_SYNC_MANIFEST_REFRESH_DELAY: 3000,
/**
* When a partial Manifest update (that is an update with a partial sub-set
* of the Manifest) fails, we will perform an update with the whole Manifest
* instead.
* To not overload the client - as parsing a Manifest can be resource heavy -
* we set a minimum delay to wait before doing the corresponding request.
* @type {Number}
*/
FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY: 3000,
/**
* DASH Manifest based on a SegmentTimeline should normally have an
* MPD@minimumUpdatePeriod attribute which should be sufficient to
* know when to refresh it.
* However, there is a specific case, for when it is equal to 0.
* As of DASH-IF IOP (valid in v4.3), when a DASH's MPD set a
* MPD@minimumUpdatePeriod to `0`, a client should not refresh the MPD
* unless told to do so through inband events, in the stream.
* In reality however, we found it to not always be the case (even with
* DASH-IF own streams) and moreover to not always be the best thing to do.
* We prefer to refresh in average at a regular interval when we do not have
* this information.
* /!\ This value is expressed in seconds.
*/
DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0: 3,
/**
* Default value for the maximum number of simultaneous MediaKeySessions that
* will be kept in a cache (linked to the MediaKeys instance) to avoid doing
* superfluous license requests.
*
* If this number is reached, any new session creation will close the oldest
* one.
* Another value can be configured through the API, in which case this default
* will be overwritten.
* @type {Number}
*/
EME_DEFAULT_MAX_SIMULTANEOUS_MEDIA_KEY_SESSIONS: 15,
/**
* When playing contents with a persistent license, we will usually store some
* information related to that MediaKeySession, to be able to play it at a
* later time.
*
* Those information are removed once a MediaKeySession is not considered
* as "usable" anymore. But to know that, the RxPlayer has to load it.
*
* But the RxPlayer does not re-load every persisted MediaKeySession every
* time to check each one of them one by one, as this would not be a
* performant thing to do.
*
* So this is only done when and if the corresponding content is encountered
* again and only if it contains the same initialization data.
*
* We have to consider that those "information" contain binary data which can
* be of arbitrary length. Size taken by an array of them can relatively
* rapidly take a lot of space in JS memory.
*
* So to avoid this storage to take too much space (would it be in the chosen
* browser's storage or in JS memory), we now set a higher bound for the
* amount of MediaKeySession information that can be stored at the same time.
*
* I set the value of 1000 here, as it seems big enough to not be considered a
* problem (though it can become one, when contents have a lot of keys per
* content), and still low enough so it should not cause much problem (my
* method to choose that number was to work with power of 10s and choosing the
* amount which seemed the most sensible one).
*
* This wasn't battle-tested however.
*/
EME_MAX_STORED_PERSISTENT_SESSION_INFORMATION: 1000,
/**
* After loading a persistent MediaKeySession, the RxPlayer needs to ensure
* that its keys still allow to decrypt a content.
*
* However on some browsers, the `keyStatuses` property that we used to check
* the keys' satuses linked to that session can be empty for some time after
* the loading operation is done.
*
* This value allows to configure a delay in milliseconds that will be the
* maximum time we will wait after a persistent session is loaded.
* If after that time, the `keyStatuses` property is still empty, we will
* consider that session as not usable.
*/
EME_WAITING_DELAY_LOADED_SESSION_EMPTY_KEYSTATUSES: 100,
/**
* The player relies on browser events and properties to update its status to
* "ENDED".
*
* Sadly in some cases, like in Chrome 54, this event is never triggered on
* some contents probably due to a browser bug.
*
* This threshold resolves this issue by forcing the status to "ENDED" when:
* 1. the player is stalling
* 2. the absolute difference between current playback time and duration is
* under this value
*
* If set to null, this workaround is disabled and the player only relies on
* browser events.
*
* @type {Number|null}
*/
FORCED_ENDED_THRESHOLD: 0.0008,
/**
* Maximum duration from the current position we will let in the buffer when
* switching an Adaptation/Representations of a given type.
*
* For example, if we have ``text: { before: 1, after: 4 }``, it means that
* when switching subtitles, we will let 1 second before and 4 second after
* the current position in the previous language (until the new segments
* overwrite it).
* This is to allow smooth transitions and avoid de-synchronization that
* can happen when removing the content being decoded.
* @type {Object}
*/
ADAP_REP_SWITCH_BUFFER_PADDINGS: {
video: { before: 5, after: 5 },
audio: { before: 2, after: 2.5 },
text: { before: 0, after: 0 }, // not managed natively, so no problem here
},
/**
* Interval, in milliseconds, at which we should manually flush
* SourceBuffers.
* Some browsers (happened with firefox 66) sometimes "forget" to send us
* `update` or `updateend` events.
* In that case, we're completely unable to continue the queue here and
* stay locked in a waiting state.
* This interval is here to check at regular intervals if the underlying
* SourceBuffer is currently updating.
* @type {Number}
*/
SOURCE_BUFFER_FLUSHING_INTERVAL: 500,
/**
* Any already-pushed segment starting before or at the current position +
* CONTENT_REPLACEMENT_PADDING won't be replaced by new segments.
*
* This allows to avoid overwriting segments that are currently being decoded
* as we encountered many decoding issues when doing so.
* @type {Number} - in seconds
*/
CONTENT_REPLACEMENT_PADDING: 1.2,
/**
* For video and audio segments, determines two thresholds below which :
* - The segment is considered as loaded from cache
* - The segment may be loaded from cache depending on the previous request
*/
CACHE_LOAD_DURATION_THRESHOLDS: {
video: 50,
audio: 10,
},
/** Interval we will use to poll for checking if an event shall be emitted */
STREAM_EVENT_EMITTER_POLL_INTERVAL: 250,
/**
* In Javascript, numbers are encoded in a way that a floating number may be
* represented internally with a rounding error. When multiplying times in
* seconds by the timescale, we've encoutered cases were the rounding error
* was amplified by a factor which is about the timescale.
* Example :
* (192797480.641122).toFixed(20) = 192797480.64112201333045959473
* (error is 0.0000000133...)
* 192797480.641122 * 10000000 = 1927974806411220.2 (error is 0.2)
* 192797480.641122 * 10000000 * 4 = 7711899225644881 (error is 1)
* The error is much more significant here, once the timescale has been
* applied.
* Thus, we consider that our max tolerable rounding error is 1ms.
* It is much more than max rounding errors when seen into practice,
* and not significant from the media loss perspective.
*/
DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR: 1 / 1000,
/**
* RxPlayer's media buffers have a linked history registering recent events
* that happened on those.
* The reason is to implement various heuristics in case of weird browser
* behavior.
*
* The `BUFFERED_HISTORY_RETENTION_TIME` is the minimum age an entry of
* that history can have before being removed from the history.
*/
BUFFERED_HISTORY_RETENTION_TIME: 60000,
/**
* RxPlayer's media buffers have a linked history registering recent events
* that happened on those.
* The reason is to implement various heuristics in case of weird browser
* behavior.
*
* The `BUFFERED_HISTORY_RETENTION_TIME` is the maximum number of entries
* there can be in that history.
*/
BUFFERED_HISTORY_MAXIMUM_ENTRIES: 200,
/**
* Minimum buffer in seconds ahead relative to current time
* we should be able to download, even in cases of saturated memory.
*/
MIN_BUFFER_AHEAD: 5,
/**
* Distance in seconds behind the current position
* the player will free up to in the case we agressively free up memory
* It is set to avoid playback issues
*/
UPTO_CURRENT_POSITION_CLEANUP: 5,
/**
* Default "switching mode" used when locking video Representations.
* That is, which behavior the RxPlayer should have by default when
* explicitely and manually switching from a previous set of video
* Representations to a new one.
*/
DEFAULT_VIDEO_REPRESENTATIONS_SWITCHING_MODE: "seamless" as const,
/**
* Default "switching mode" used when locking audio Representations.
* That is, which behavior the RxPlayer should have by default when
* explicitely and manually switching from a previous set of audio
* Representations to a new one.
*/
DEFAULT_AUDIO_REPRESENTATIONS_SWITCHING_MODE: "seamless" as const,
/**
* Default "switching mode" used when switching between video tracks.
* That is, which behavior the RxPlayer should have by default when
* explicitely and manually switching from a previous video track to a new
* one.
*/
DEFAULT_VIDEO_TRACK_SWITCHING_MODE: "reload" as const,
/**
* Default "switching mode" used when switching between audio tracks.
* That is, which behavior the RxPlayer should have by default when
* explicitely and manually switching from a previous audio track to a new
* one.
*/
DEFAULT_AUDIO_TRACK_SWITCHING_MODE: "seamless" as const,
/**
* The default number of times a thumbnail request will be re-performed when
* on error which justify a retry.
*
* Note that some errors do not use this counter:
* - if the error is not due to the xhr, no retry will be peformed
* - if the error is an HTTP error code, but not a 500-smthg or a 404, no
* retry will be performed.
* @type Number
*/
DEFAULT_MAX_THUMBNAIL_REQUESTS_RETRY_ON_ERROR: 1,
/**
* Default time interval after which a thumbnail request will timeout, in ms.
* @type {Number}
*/
DEFAULT_THUMBNAIL_REQUEST_TIMEOUT: 10 * 1000,
/**
* Default connection time after which a thumbnail request conncection will
* timeout, in ms.
* @type {Number}
*/
DEFAULT_THUMBNAIL_CONNECTION_TIMEOUT: 7 * 1000,
// Compatibility toggles:
/**
* If set to `true`, we'll always try to check thoroughly that a
* `MediaKeySystemAccess` can be relied on.
*/
FORCE_CANNOT_RELY_ON_REQUEST_MEDIA_KEY_SYSTEM_ACCESS: false,
/**
* If set to `true`, we'll always re-create a `MediaKeys` instance for each
* encrypted content where we need one.
*/
FORCE_CANNOT_REUSE_MEDIA_KEYS: false,
/**
* If set to `true`, force work-around for devices which have issues with
* `MediaSource` objects with a high `duration` property.
*/
FORCE_HAS_ISSUES_WITH_HIGH_MEDIA_SOURCE_DURATION: false,
/**
* If set to `true`, the device might seek before what we actually tell it to,
* breaking other RxPlayer behaviors in the process.
* Setting it to `true` allows to enable work-arounds.
*/
FORCE_IS_SEEKING_APPROXIMATE: false,
/**
* If set to `true`, the device might fail directly after uncountering
* decipherable data.
*/
FORCE_MEDIA_ELEMENT_FAIL_ON_UNDECIPHERABLE_DATA: false,
/**
* If set to `true` we'll await a `MediaKeys` attachment on a given
* `HTMLMediaElement` before trying to set a new one.
*/
FORCE_SHOULD_AWAIT_SET_MEDIA_KEYS: false,
/** If set to `true` we'll rely on Safari's Webkit flavor of the EME API. */
FORCE_SHOULD_FAVOUR_CUSTOM_SAFARI_EME: false,
/**
* If `true`, we'll reload if unencrypted data is encountered close to
* the current position.
*/
FORCE_SHOULD_RELOAD_MEDIA_SOURCE_ON_DECIPHERABILITY_UPDATE: false,
/** If `true`, we'll for each content re-create a `MediaKeySystemAccess`. */
FORCE_SHOULD_RENEW_MEDIA_KEY_SYSTEM_ACCESS: false,
/** If `true`, we'll unset the `MediaKeys` at each stop. */
FORCE_SHOULD_UNSET_MEDIA_KEYS: false,
/**
* If `true`, we cannot trust that a `loadedmetadata` event from the
* `HTMLMediaElement` is sent after the browser has parsed key metadata such
* as the content's duration.
*/
FORCE_SHOULD_VALIDATE_METADATA: false,
/**
* If `true`, we have to announce the content as loaded even if no data is
* actually loaded, because that target do not preload, meaning a `play` call
* is required.
*/
FORCE_DONT_WAIT_FOR_DATA_BEFORE_LOADED: false,
/**
* If `true`, we have to wait for the `HAVE_ENOUGH_DATA` `readyState` before
* announcing the content as loaded.
*/
FORCE_WAIT_FOR_HAVE_ENOUGH_DATA: false,
};
export type IDefaultConfig = typeof DEFAULT_CONFIG;
export default DEFAULT_CONFIG;
// NOTE Because the config may have to be serialized and shared between several
// environments, we check here that some strict type is respected:
// - only a subset of types are authorized for now, just make it easier to
// reason about.
// - Needs to make sense in JSON: no `function`, no `Date`, no `undefined`...
interface IGenericConfigData {
[key: string]:
| string
| number
| boolean
| number[]
| string[]
| Partial<Record<string, string[]>>
| IGenericConfigData;
}
function checkIsSerializable(_conf: IGenericConfigData): void {
// noop
}
checkIsSerializable(DEFAULT_CONFIG);