fix: separate Cast launch readiness, preserve volume intent and reuse seek inputs

This commit is contained in:
2026-10-13 21:40:19 +02:00
parent 697158aa02
commit b9e63cd760
8 changed files with 365 additions and 55 deletions
+55 -11
View File
@@ -146,14 +146,14 @@ struct Input {
}
};
struct Track {
std::unique_ptr<Input> input;
Input *input;
AVStream *stream = nullptr;
AVCodecContext *decoder = nullptr;
Packet pending;
Frame decoded;
bool eof = false, draining = false, remux = false;
double nextTime = 0;
Track(std::unique_ptr<Input> in, AVMediaType type, double start, bool live) : input(std::move(in)) {
Track(Input &in, AVMediaType type, double start, bool live) : input(&in) {
int index = av_find_best_stream(input->format, type, -1, -1, nullptr, 0);
if (index < 0) return;
stream = input->format->streams[index];
@@ -212,7 +212,7 @@ struct Track {
auto p = packet();
input->arm();
for (;;) {
require(!Input::interrupt(input.get()));
require(!Input::interrupt(input));
int result = av_read_frame(input->format, p.get());
if (result == AVERROR_EOF) { eof = true; return; }
check(result);
@@ -229,7 +229,7 @@ struct Track {
void nextFrame() {
decoded.reset(); auto f = frame();
for (;;) {
require(!Input::interrupt(input.get()));
require(!Input::interrupt(input));
int result = avcodec_receive_frame(decoder, f.get());
if (result == 0) {
decoded = std::move(f);
@@ -440,6 +440,7 @@ struct OutputTrack {
}
};
struct Pipeline {
std::unique_ptr<Input> videoInput, audioInput;
std::unique_ptr<Track> video, audio;
std::unique_ptr<OutputTrack> videoOutput, audioOutput;
bool live;
@@ -450,17 +451,32 @@ struct Pipeline {
duration = std::max(source.duration, primary->duration());
bool hasVideo = av_find_best_stream(primary->format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0) >= 0;
bool hasAudio = av_find_best_stream(primary->format, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0) >= 0;
if (hasVideo) video = std::make_unique<Track>(std::move(primary), AVMEDIA_TYPE_VIDEO, start, source.live);
if (hasVideo) videoInput = std::move(primary);
if (!source.audio.empty()) {
audio = std::make_unique<Track>(std::make_unique<Input>(stop, source.audio, source.audioHeaders, false, source.restricted, source.live), AVMEDIA_TYPE_AUDIO, start, source.live);
require(audio->stream != nullptr);
audioInput = std::make_unique<Input>(stop, source.audio, source.audioHeaders, false, source.restricted, source.live);
} else if (hasAudio) {
if (!primary) primary = std::make_unique<Input>(stop, source.video, source.videoHeaders, source.local, source.restricted, source.live);
audio = std::make_unique<Track>(std::move(primary), AVMEDIA_TYPE_AUDIO, start, source.live);
audioInput = std::move(primary);
}
reset(start);
if (!source.audio.empty()) require(audio && audio->stream);
require(video || audio);
if (!source.live) require(std::isfinite(duration) && duration > 0 && duration <= 86400 * 14);
}
void reset(double start) {
videoOutput.reset(); audioOutput.reset();
video.reset(); audio.reset();
// Fresh codecs preserve independent segment/AAC priming; demuxers retain
// their already-probed metadata and seek indexes across random requests.
if (videoInput) {
videoInput->operationDeadline = std::numeric_limits<int64_t>::max();
video = std::make_unique<Track>(*videoInput, AVMEDIA_TYPE_VIDEO, start, live);
}
if (audioInput) {
audioInput->operationDeadline = std::numeric_limits<int64_t>::max();
audio = std::make_unique<Track>(*audioInput, AVMEDIA_TYPE_AUDIO, start, live);
}
}
Segment segment(int64_t id, double start, double end) {
Mux mux;
if (video) {
@@ -507,6 +523,8 @@ struct CastMedia {
Source source;
std::atomic<bool> cancelled{false};
std::mutex lock;
std::mutex vodLock;
std::unique_ptr<Pipeline> vodPipeline;
std::condition_variable wake;
int listener = -1;
std::vector<int> clients;
@@ -607,6 +625,32 @@ struct CastMedia {
if (!source.live) text += "#EXT-X-ENDLIST\n";
return {text.begin(), text.end()};
}
std::shared_ptr<const Segment> vodSegment(int64_t id) {
auto cached = [&]() -> std::shared_ptr<const Segment> {
std::lock_guard guard(lock);
auto found = std::find_if(segments.begin(), segments.end(), [&](const auto &segment) { return segment->id == id; });
return found == segments.end() ? nullptr : *found;
};
if (auto segment = cached()) return segment;
// ponytail: uncached VOD generation is serialized; use a bounded input
// pool only if parallel uncached requests justify duplicate probe state.
std::lock_guard generation(vodLock);
require(!cancelled);
if (auto segment = cached()) return segment;
try {
if (vodPipeline) vodPipeline->reset(id * segmentSeconds);
else vodPipeline = std::make_unique<Pipeline>(source, cancelled, id * segmentSeconds);
auto segment = std::make_shared<const Segment>(vodPipeline->segment(
id, id * segmentSeconds, std::min(duration, (id + 1) * segmentSeconds)));
{ std::lock_guard guard(lock); segments.push_back(segment); while (segments.size() > windowSegments) segments.pop_front(); }
return segment;
} catch (...) {
// A failed demux/decode is never cached or reused. A later receiver
// request can prepare fresh inputs; this request still receives 503.
vodPipeline.reset();
throw;
}
}
void sendAll(int socket, const uint8_t *bytes, size_t size) {
const auto deadline = Clock::now() + std::chrono::seconds(15);
while (size) {
@@ -682,8 +726,8 @@ struct CastMedia {
if (found == segments.end()) status = 404; else retained = std::shared_ptr<const std::vector<uint8_t>>(*found, &(*found)->bytes);
} else if (double(id) * segmentSeconds >= duration) status = 404;
else {
Pipeline pipeline(source, cancelled, id * segmentSeconds);
data = pipeline.segment(id, id * segmentSeconds, std::min(duration, (id + 1) * segmentSeconds)).bytes;
auto segment = vodSegment(id);
retained = std::shared_ptr<const std::vector<uint8_t>>(segment, &segment->bytes);
}
} else status = 404;
response(socket, status, type, retained ? *retained : data, head, range);
@@ -728,7 +772,7 @@ extern "C" bool cast_media_start(CastMedia *media, const char *host, char url[51
std::unique_lock guard(media->lock);
require(media->wake.wait_for(guard, std::chrono::seconds(50), [&] { return media->segments.size() >= 2 || media->liveFailed || media->cancelled; }));
require(media->segments.size() >= 2 && !media->liveFailed && !media->cancelled);
}
} else media->vodPipeline = std::move(pipeline);
for (auto &worker : media->servers) worker = std::thread([media] { media->server(); });
require(media->base.size() + 11 < 512); std::snprintf(url, 512, "%sindex.m3u8", media->base.c_str()); *duration = media->source.live ? 0 : media->duration;
return true;