mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
fix(isomp4): fix M4A moov-at-end probe failures and streaming fallback (#757)
* fix(stream): defer M4A probe until moov tail or fast-start prefix Ranged moov-at-end M4A started Symphonia format probe as soon as ~384 KiB linear data arrived, before the parallel tail prefetch filled the moov atom — probe hit end of stream and skipped the track. Wait for tail_ready or detect fast-start moov in the prefix; do not arm playback from linear bytes alone when tail prefetch is active. * fix(isomp4): skip EOF-spanning mdat after moov-at-end is parsed Second-pass header scan with moov already loaded still tried to read through mdat→EOF on RangedHttpSource holes, causing format probe end of stream. Re-check play generation after moov wait. * fix(isomp4): fix AtomIterator overread after seek in patched demuxer `AtomIterator::new_root(reader, len)` treats `len` as bytes available from the current `reader.pos()`, not the absolute file length. After `mss.seek(resume_at)` we were passing the absolute `total_len`, so the iterator thought there were `total_len` bytes left and tried to read past EOF on the next iteration, returning "end of stream". Fix: pass `total_len.map(|tl| tl.saturating_sub(resume_at))` (remaining bytes from the new position) in both branches: - `moov.is_none()` (moov-at-end layout, seek to moov offset) - `moov.is_some()` (fast-start layout, skip bounded mdat body) This caused Symphonia to fail probing moov-at-end M4A files read from local disk (hot-cache) and from in-memory buffers — every decode attempt returned "end of stream", analysis fell back to `byte_envelope_no_ebu` (no EBU R128 loudness), and rodio produced distorted audio. Also in this commit: - `resolve_playback_format_hint()` helper to resolve hint from URL, stream suffix, Content-Disposition, or byte sniff - ISO-BMFF diagnostic helpers (`isobmff_buffer_looks_complete`, `log_isobmff_buffer_diagnostic`, `mp4_suspect_zero_holes`) - Probe-fallback path for ranged-stream failures now uses these helpers to decide whether to refetch or wait for the in-flight download * chore(audio): remove redundant hint recomputation and add missing blank line `bytes_hint_for_wait` in the ranged-stream fallback path was an exact duplicate of `effective_hint` already in scope — reuse the existing binding. Also add missing blank line after `wait_for_ranged_mp4_probe_ready` in mod.rs. * chore(release): CHANGELOG and credits for PR #757 Add Fixed entry for M4A moov-at-end probe fix and credits line in settingsCredits.ts under existing cucadmuh contributions. * fix(audio): extract BuildSourceArgs to fix clippy::too_many_arguments `build_playback_source_with_probe_fallback` had 12 parameters, exceeding the clippy limit of 7. Group url/gen/hints/fade/hi-res/duration into `BuildSourceArgs` so the function signature stays at 4 arguments.
This commit is contained in:
@@ -436,12 +436,27 @@ impl FormatReader for IsoMp4Reader {
|
||||
end
|
||||
};
|
||||
mss.seek(SeekFrom::Start(resume_at))?;
|
||||
iter = AtomIterator::new_root(mss, total_len);
|
||||
} else if end < file_len.saturating_sub(8) {
|
||||
// Fast-start: skip a bounded mdat without linear read.
|
||||
// `AtomIterator::new_root` treats `len` as bytes available **from the
|
||||
// current reader position**, not the absolute file length. Passing
|
||||
// `total_len` here makes the iterator think the file is `total_len`
|
||||
// bytes long after the seek, so reading the last atom's trailer
|
||||
// (e.g. a tail `moov`) succeeds, but the next `iter.next()` then keeps
|
||||
// reading past EOF and returns `end of stream`. Pass the **remaining**
|
||||
// length instead so the iterator stops cleanly when the last atom ends.
|
||||
let remaining = total_len.map(|tl| tl.saturating_sub(resume_at));
|
||||
iter = AtomIterator::new_root(mss, remaining);
|
||||
} else if moov.is_some() {
|
||||
// `moov` was already parsed (fast-start layout). Skip the `mdat` body
|
||||
// without linear-reading it (holes in RangedHttpSource). Never seek to
|
||||
// `file_len` — that is one byte past the last valid offset and makes the
|
||||
// next atom read return end-of-stream on in-memory sources.
|
||||
if end >= file_len.saturating_sub(8) {
|
||||
break;
|
||||
}
|
||||
let mut mss = iter.into_inner();
|
||||
mss.seek(SeekFrom::Start(end))?;
|
||||
iter = AtomIterator::new_root(mss, total_len);
|
||||
let remaining = total_len.map(|tl| tl.saturating_sub(end));
|
||||
iter = AtomIterator::new_root(mss, remaining);
|
||||
}
|
||||
}
|
||||
AtomType::Meta => {
|
||||
|
||||
Reference in New Issue
Block a user