// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 #pragma once #include "audio_mode.h" #include "queue_item_store.h" #include #include #include #include #include #include #include namespace queue_audio { struct QueueRecord { yeney::TrackDescriptor source; std::chrono::steady_clock::time_point strmAt{}; uint64_t expectedIndex = 0; uint64_t sourceToken = 0, pairedFrom = 0; std::optional landedFrame; std::string seekReason; std::string sourceUrl; unsigned originMs = 0, seekPositionMs = 0; uint64_t parent = 0, segmentOffset = 0; bool estimated = false, productionComplete = false; double duration = 0; uint32_t sourceOriginMs = 0; double strm = -1, decoded = -1, stmd = -1, audible = -1, stms = -1, end = -1; uint64_t first = 0, actual = 0, overlap = 0, prefixFrames = 0; std::optional promised, finalFrame; std::optional finalElapsedMs; std::string continuationDecision; std::string publication = "pending", transition, fault, incomplete; bool storageFault = false; struct LmsSample { double stamp; std::string index, time, title; std::optional matches; double audibleElapsed = -1, timeErrorWall = -1; }; std::vector lms; double mismatchedSeconds = 0, mismatchRun = 0; bool lmsMismatch = false; std::shared_ptr item; }; class CoreQueueSink : public yeney::Sink { struct Job { double seconds; std::function run; }; Store store; const uint64_t continuationLimit; mutable std::mutex mutex; std::condition_variable changed; std::deque jobs; std::thread worker; bool closing = false, busy = false, paused = false; double staged = 0, budget, mismatchSince = -1, clockSpeed; std::map records; std::deque descriptorOrder; std::deque audibleSignals; std::deque speakerStates; bool speakerClock = false; std::atomic_bool queueFailed{false}; std::atomic_bool retainInterruptedFlush{false}; std::function commandHandler; std::optional speakerState; std::optional speakerElapsed; uint64_t describedLast = 0, active = 0, audibleId = 0, audibleFrame = 0, submitted = 0; uint64_t continuationSerial = UINT64_MAX / 2, producingSegment = 0; uint64_t speakerItemFrame = 0; uint64_t lastEnded = 0, nextIndex = 0, audibleSource = 0; unsigned retainedHistory = 0, retainedUpcoming = 1; std::vector> publishedItems; bool prefixPending = false, freshRender = false, cachedReanchor = false; std::atomic_bool refetchRequested{false}, startRequested{false}, cachedStartRequested{false}; std::atomic latestProducer{0}; std::optional nextContent; uint32_t rate = 48000; std::string workerFault; std::chrono::steady_clock::time_point epoch = std::chrono::steady_clock::now(); double now() const; void enqueue(std::function, double seconds = 0); void work(); void append(const std::vector &, bool); uint64_t continuationAfter(uint64_t predecessor); void publishEstimated(uint64_t id, bool force = false); void appendEstimated(const std::vector &, bool); void protectItems(); void commit(uint64_t previous, const yeney::TrackDescriptor &next); void finish(const yeney::ItemBoundary &); public: explicit CoreQueueSink(const std::string &room, Config config, double seconds = 30, double speed = 1); ~CoreQueueSink() override; bool decodeAhead() const override { return true; } uint32_t maxSampleRate() const override { return audioMode() == AudioMode::Legacy ? 44100 : 48000; } bool paced() const override { return false; } void estimate(uint64_t occurrence, double duration, unsigned originMs); void trackDescriptor(const yeney::TrackDescriptor &) override; void sourceIdentity(uint64_t id, uint64_t token, const std::string &url, unsigned originMs); void coreEvent(const std::string &, uint64_t, uint64_t) override; void resolvedItemBoundary(const yeney::ItemBoundary &) override; void trackBoundary(uint64_t, const yeney::Format &, bool) override; void contentPosition(uint64_t frame, bool known) override { nextContent = known ? std::optional(frame) : std::nullopt; } size_t write(const yeney::Frame *p, size_t n) override { return write(p, n, false); } size_t write(const yeney::Frame *, size_t, bool) override; // Explicit audible control, used only by a device controller or the dry-run // clock. void audibleFrom(uint64_t occurrence, uint64_t firstFrame); bool isPaused() const; // Enable before starting the player. Only the controller calls this path, // after speaker identity/position confirmation; never an HTTP reader. void useSpeakerClock(); // Bind before Player::run. The handler enqueues work and must not wait. void onQueueCommand(std::function); void retainInterruptedOnNextFlush() { retainInterruptedFlush = true; } bool queueCommand(char, uint32_t) override; // Before an LMS re-render/seek run: do not carry an old outgoing tail // into the independently selected asset. Cached mixed intros are untouched. void beginRender(); void restartCached(); uint64_t latestOccurrence() const { return latestProducer.load(); } void resumeReceivedStart() { startRequested = true; } bool takeQueueStart() override { return startRequested.exchange(false); } void confirmCachedStart() { refetchRequested = true; cachedStartRequested = true; } bool takeQueueCachedStart() override { return cachedStartRequested.exchange(false); } bool takeQueueRefetch() override { return refetchRequested.exchange(false); } void reanchorSpeaker(uint64_t occurrence, uint32_t sourceOriginMs, uint32_t seekPositionMs); void speakerPosition(uint64_t occurrence, uint64_t renderedFrame, yeney::PlaybackState); bool speakerDriven() const override; std::optional encodedSpool(uint64_t) override; bool queueEndRequiresPlayed(uint64_t) const override; bool queueItemProduced(uint64_t) const override; bool queuePrefixProduced(uint64_t, uint64_t) const override; void failQueueTrack() { queueFailed = true; } bool takeQueueFailure() override { return queueFailed.exchange(false); } bool takeAudibleState(yeney::AudibleState &) override; std::optional audibleElapsedMs(uint64_t occurrence) const override; void audibleEnd(uint64_t occurrence); void recordLms(uint64_t occurrence, QueueRecord::LmsSample sample); void advanceAudible(uint64_t cumulativeFrame); bool takeAudibleStart(yeney::AudibleStart &) override; std::optional committedItemFrames(uint64_t) const override; bool awaitingPublication(uint64_t) const override; uint64_t audibleFrames() const override; bool outputEmpty(uint64_t submitted) const override; uint64_t audibleOccurrence() const; void waitIdle(); // Configure before playback. Parent budgets do not count continuations. void windowRetention(unsigned history, unsigned upcoming); void protectWindow(std::vector>); void releaseParent(uint64_t occurrence); std::vector snapshot() const; double secondsSinceStart() const { return now(); } double wallSecondsSinceStart() const { return now() / clockSpeed; } void pause() override; void resume() override; void stop() override; void flush() override; void volume(uint32_t, uint32_t) override {} void power(bool) override {} }; } // namespace queue_audio