Buckets:
| import { adjustSliding } from './level-helper'; | |
| import { logger } from './logger'; | |
| import type { Fragment } from '../loader/fragment'; | |
| import type { LevelDetails } from '../loader/level-details'; | |
| export function findFirstFragWithCC( | |
| fragments: Fragment[], | |
| cc: number, | |
| ): Fragment | null { | |
| for (let i = 0, len = fragments.length; i < len; i++) { | |
| if (fragments[i]?.cc === cc) { | |
| return fragments[i]; | |
| } | |
| } | |
| return null; | |
| } | |
| export function shouldAlignOnDiscontinuities( | |
| refDetails: LevelDetails | undefined, | |
| details: LevelDetails, | |
| ): refDetails is LevelDetails & boolean { | |
| if (refDetails) { | |
| if ( | |
| details.startCC < refDetails.endCC && | |
| details.endCC > refDetails.startCC | |
| ) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| function adjustFragmentStart(frag: Fragment, sliding: number) { | |
| const start = frag.start + sliding; | |
| frag.startPTS = start; | |
| frag.setStart(start); | |
| frag.endPTS = start + frag.duration; | |
| } | |
| export function adjustSlidingStart(sliding: number, details: LevelDetails) { | |
| // Update segments | |
| const fragments = details.fragments; | |
| for (let i = 0, len = fragments.length; i < len; i++) { | |
| adjustFragmentStart(fragments[i], sliding); | |
| } | |
| // Update LL-HLS parts at the end of the playlist | |
| if (details.fragmentHint) { | |
| adjustFragmentStart(details.fragmentHint, sliding); | |
| } | |
| details.alignedSliding = true; | |
| } | |
| /** | |
| * Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a | |
| * contiguous stream with the last fragments. | |
| * The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to | |
| * download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time | |
| * and an extra download. | |
| * @param lastLevel | |
| * @param details | |
| */ | |
| export function alignStream( | |
| switchDetails: LevelDetails | undefined, | |
| details: LevelDetails, | |
| ) { | |
| if (!switchDetails) { | |
| return; | |
| } | |
| alignDiscontinuities(details, switchDetails); | |
| if (!details.alignedSliding) { | |
| // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level. | |
| // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same | |
| // discontinuity sequence. | |
| alignMediaPlaylistByPDT(details, switchDetails); | |
| } | |
| if (!details.alignedSliding && !details.skippedSegments) { | |
| // Try to align on sn so that we pick a better start fragment. | |
| // Do not perform this on playlists with delta updates as this is only to align levels on switch | |
| // and adjustSliding only adjusts fragments after skippedSegments. | |
| adjustSliding(switchDetails, details, false); | |
| } | |
| } | |
| /** | |
| * Ajust the start of fragments in `details` by the difference in time between fragments of the latest | |
| * shared discontinuity sequence change. | |
| * @param lastLevel - The details of the last loaded level | |
| * @param details - The details of the new level | |
| */ | |
| export function alignDiscontinuities( | |
| details: LevelDetails, | |
| refDetails: LevelDetails | undefined, | |
| ) { | |
| if (!shouldAlignOnDiscontinuities(refDetails, details)) { | |
| return; | |
| } | |
| const targetCC = Math.min(refDetails.endCC, details.endCC); | |
| const refFrag = findFirstFragWithCC(refDetails.fragments, targetCC); | |
| const frag = findFirstFragWithCC(details.fragments, targetCC); | |
| if (!refFrag || !frag) { | |
| return; | |
| } | |
| logger.log(`Aligning playlist at start of dicontinuity sequence ${targetCC}`); | |
| const delta = refFrag.start - frag.start; | |
| adjustSlidingStart(delta, details); | |
| } | |
| /** | |
| * Ensures appropriate time-alignment between renditions based on PDT. | |
| * This function assumes the timelines represented in `refDetails` are accurate, including the PDTs | |
| * for the last discontinuity sequence number shared by both playlists when present, | |
| * and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation | |
| * times/timelines of `details` accordingly. | |
| * Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks, | |
| * the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks | |
| * are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should | |
| * be consistent across playlists, per the HLS spec. | |
| * @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition). | |
| * @param refDetails - The details of the reference rendition with start and PDT times for alignment. | |
| */ | |
| export function alignMediaPlaylistByPDT( | |
| details: LevelDetails, | |
| refDetails: LevelDetails, | |
| ) { | |
| if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) { | |
| return; | |
| } | |
| const fragments = details.fragments; | |
| const refFragments = refDetails.fragments; | |
| if (!fragments.length || !refFragments.length) { | |
| return; | |
| } | |
| // Calculate a delta to apply to all fragments according to the delta in PDT times and start times | |
| // of a fragment in the reference details, and a fragment in the target details of the same discontinuity. | |
| // If a fragment of the same discontinuity was not found use the middle fragment of both. | |
| let refFrag: Fragment | null | undefined; | |
| let frag: Fragment | null | undefined; | |
| const targetCC = Math.min(refDetails.endCC, details.endCC); | |
| if (refDetails.startCC < targetCC && details.startCC < targetCC) { | |
| refFrag = findFirstFragWithCC(refFragments, targetCC); | |
| frag = findFirstFragWithCC(fragments, targetCC); | |
| } | |
| if (!refFrag || !frag) { | |
| refFrag = refFragments[Math.floor(refFragments.length / 2)]; | |
| frag = | |
| findFirstFragWithCC(fragments, refFrag.cc) || | |
| fragments[Math.floor(fragments.length / 2)]; | |
| } | |
| const refPDT = refFrag.programDateTime; | |
| const targetPDT = frag.programDateTime; | |
| if (!refPDT || !targetPDT) { | |
| return; | |
| } | |
| const delta = (targetPDT - refPDT) / 1000 - (frag.start - refFrag.start); | |
| adjustSlidingStart(delta, details); | |
| } | |
Xet Storage Details
- Size:
- 6 kB
- Xet hash:
- ef7d5be3817d7daefd4edad3a918bc2ab28ba7bde0bb8c4b5fd8710a3b3f9477
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.