fix: share buffered Cast HLS input and select streams

This commit is contained in:
2026-10-13 22:47:07 +02:00
parent b0636c5af7
commit ad4b2da5c8
6 changed files with 362 additions and 63 deletions
+33 -6
View File
@@ -125,7 +125,9 @@ public:
for (int slot = 0; slot < 2; ++slot) trim(slot);
budget.changed.notify_all();
}
void seek(int streamIndex, int64_t timestamp) {
// Optional audio target gives fresh segment encoders their AAC preroll without
// moving the video cursor away from its own keyframe. Omission preserves player seeks.
void seek(int streamIndex, int64_t timestamp, double audioTarget = NAN) {
std::lock_guard lock(budget.mutex);
const double target = (streamIndex < 0 ? static_cast<double>(timestamp) / AV_TIME_BASE :
static_cast<double>(timestamp) * av_q2d(format->streams[streamIndex]->time_base)) - origin;
@@ -138,7 +140,7 @@ public:
// track can resume without restoring its I/O position through a real seek.
if (halted && !ended && !(std::isfinite(lane.mediaEnd) &&
lane.frontierEnd + lane.tick >= lane.mediaEnd)) cached = false;
else cached = cachedCursor(slot, target, cursors[slot]);
else cached = cachedCursor(slot, slot == 0 && std::isfinite(audioTarget) ? audioTarget : target, cursors[slot]);
}
position = std::max(0.0, target);
if (cached) {
@@ -156,6 +158,11 @@ public:
failure = 0;
seekIndex = streamIndex;
seekTimestamp = timestamp;
if (lanes[0].index >= 0 && !skipAudio && std::isfinite(audioTarget)) {
const double earliest = std::min(target, std::max(0.0, audioTarget - lanes[0].preroll));
seekIndex = -1;
seekTimestamp = static_cast<int64_t>((earliest + origin) * AV_TIME_BASE);
}
seeking = true;
serial.fetch_add(1, std::memory_order_release);
budget.changed.notify_all();
@@ -187,6 +194,20 @@ public:
std::lock_guard lock(budget.mutex);
return failure;
}
// Whether an existing bounded forward fetch can supply a seek's missing packets.
// Past history and targets outside the current ahead window require normal seek handling.
bool readAheadPending(double target, double audioTarget = NAN) const {
std::lock_guard lock(budget.mutex);
if (failure || seeking || ended || halted || waitingForRoom ||
target < position || target > position + aheadSeconds) return false;
for (int slot = 0; slot < 2; ++slot) {
const auto &lane = lanes[slot];
if (lane.index < 0 || (slot == 0 && skipAudio) || trackEnded(lane)) continue;
const double laneTarget = slot == 0 && std::isfinite(audioTarget) ? audioTarget : target;
if (laneTarget > lane.frontierEnd + lane.tick) return true;
}
return false;
}
double bufferedStart(int slot) const {
std::lock_guard lock(budget.mutex);
if (!cacheSeeking || failure || seeking || (slot == 0 && skipAudio)) return NAN;
@@ -253,6 +274,7 @@ private:
std::atomic<int64_t> deadline{std::numeric_limits<int64_t>::max()};
bool seeking = false, ended = false, halted = false, skipAudio = false;
bool cacheSeeking = false, started = false, audioDropped = false;
bool waitingForRoom = false;
int seekIndex = -1, failure = 0;
int64_t seekTimestamp = 0;
double origin = 0, position = 0;
@@ -464,10 +486,15 @@ private:
}
// The demuxer's single in-flight packet is not retained until it fits.
// Byte pressure shortens consumed history, never drops unread packets.
budget.changed.wait(lock, [&] {
return stoppingNow() || serial.load(std::memory_order_acquire) != current ||
(slot == 0 && skipAudio) || makeRoom(lane, bytes);
});
if (!makeRoom(lane, bytes)) {
waitingForRoom = true;
playerWake.notify_one();
budget.changed.wait(lock, [&] {
return stoppingNow() || serial.load(std::memory_order_acquire) != current ||
(slot == 0 && skipAudio) || makeRoom(lane, bytes);
});
waitingForRoom = false;
}
if (stoppingNow()) return;
if (serial.load(std::memory_order_acquire) != current || (slot == 0 && skipAudio)) {
if (slot == 0 && skipAudio) audioDropped = true;