// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 #pragma once #include "core_queue_sink.h" #include "lms_icon.h" #include "queue_path_sink.h" #include "queue_playlist.h" #include "queue_protocol.h" #include "queue_room.h" #include "queue_lms_commands.h" #include "queue_route.h" #include "queue_lazy_item.h" #include "queue_source.h" #include "queue_speaker_changes.h" #include "queue_window_edits.h" #include "transport_source.h" #include "stream_metrics.h" #include "stream_session.h" #include "upnp/queue_coordinator.h" #include "upnp/queue_metadata.h" #include #include #include #include #include #include extern "C" void yeney_queue_stream_handoff(); namespace bridge { namespace queue_room_detail { extern std::atomic_bool roomStop; void stopRoom(int); void logQueue(const std::string &); unsigned pollSetting(); } // namespace queue_room_detail using namespace queue_room_detail; struct TrackFault : std::runtime_error { using std::runtime_error::runtime_error; }; struct QueueRoom::Impl { upnp::Speaker room; std::string host, mac, session; std::function streamUrl; std::shared_ptr speaker; queue_audio::CoreQueueSink queue; QueuePathSink paths; std::atomic_bool selectionDeferred{false}; std::atomic metadataIndex{-1}; int selectedIndex = -1; bool selecting = false, selectionKnown = false, selectedLocal = true, stoppedPlay = true; bool selectedNewPlay = true; yeney::Command selectedCommand{'s', 0, false}, requestedCommand{'s', 0, false}; std::atomic selectionRequest{0}; uint64_t selectedRequest = 0; queue_audio::PlaylistEntry selectedTrack; int selectedSourceIndex = -1; std::map sourceTracks; std::map sourceIndices; std::map sourceTokens; uint64_t selectedToken = 0; queue_audio::QueueWindow window; queue_audio::QueueWindow::Policy publishedWindowPolicy; bool windowDirty = false; bool refreshUpcoming = false; std::chrono::steady_clock::time_point nextWindowPoll{}; std::map flacCounts; std::chrono::steady_clock::time_point handoffAt{}; std::string pathReason = "local-source"; queue_audio::Route route; upnp::QueueCoordinator coordinator; upnp::QueueActions actions; std::set owned; upnp::QueueOwnership ownership; RoomStatus status; std::shared_ptr speakerChanges = std::make_shared(); std::string publishedState, publishedUri; struct Entry { uint64_t stream; std::string uri; std::shared_ptr item; std::string sourceUrl; uint64_t frames; std::string title; uint64_t owner = 0, segmentOffset = 0; std::string didl; unsigned originMs = 0; }; std::vector items; bool lmsNavigation = false; bool navigationSelecting = false; unsigned navigationFrom = 0; uint64_t queueEdits = 0, navigationEdits = 0; std::optional publishedWindowIndex; uint64_t navigationFirst = 0; uint64_t navigationToken = 0; std::optional navigationItem; std::chrono::steady_clock::time_point navigationDeadline{}; std::map> lazyItems; // Worker-owned selections are independent for each LMS index. struct Selection { enum Phase { Queued, Producing, Published } phase; uint64_t token, first; std::optional observed{}; std::optional rewindMs{}; }; std::map selections; std::deque selectionOrder; void advanceSelections(); void selectionPublished(unsigned index); uint64_t lazySerial = 0; void arbitrateSelection(uint64_t token, const std::string &reason); bool acceptSnapshot(queue_audio::PlaylistSnapshot &snapshot); void activateLazy(uint64_t token); std::map replacementReasons; std::map speakerNavCommands; std::map speakerNavFirst; std::map remote; std::mutex mutex, sourceMutex; std::condition_variable changed; std::deque> commands; std::thread worker; bool roomStopped = false; std::deque trackFaults; void roomFault(const std::string &reason, bool member = false); bool closing = false, started = false, faulted = false, releasing = false; uint64_t firstStream = 1, current = 0; queue_audio::PlaylistSnapshot playlist; queue_audio::PlaylistReader reader; std::string state = "STOPPED", fault, transportStatus; std::chrono::steady_clock::time_point playAt{}; bool waitingForPlay = false; bool queueSelectedForAdmission = false; std::string takeoverSource = "unknown"; bool externalReleased = false, stopAcknowledged = false, sourceAway = false; std::atomic resumeEchoUntil{0}; std::atomic_bool resumeFlush{false}, finitePause{false}; struct PodcastFields { std::string title, artist, album; bool complete = false; }; std::map plainRemoteTitles; std::chrono::steady_clock::time_point deferredStop{}; uint64_t deferredNext = 0; queue_audio::PlaylistSnapshot resumeSnapshot; bool pausedFlushRetained = false, remoteNaturalSuccessor = false; static int64_t milliseconds(); std::string expectedState = "STOPPED"; std::chrono::steady_clock::time_point ownUntil{}; uint64_t lastElapsed = 0; std::chrono::steady_clock::time_point lastPositionAt{}; // The only outgoing CLI mutation path: called solely from speaker evidence. void speakerCommand(const std::string &command, const std::string &reason); void externalStop(const std::string &reason); unsigned pollMs = pollSetting(); std::chrono::steady_clock::time_point nextPoll{}, fastUntil{}; std::shared_ptr eventDirty = std::make_shared(false); void fastPoll(); upnp::QueueOwnershipIO ownershipIO(); Impl(upnp::Speaker r, std::shared_ptr http, std::shared_ptr s, std::string h, std::string m, const uint8_t *rawMac, std::function url); std::optional sourceInfo(uint64_t occurrence, std::optional info); void relayEvents(bool enabled); void received(const yeney::Command &c); void beginSelection(uint64_t request); void selectPath(); void handoffComplete(); void publish(); void release(bool stop = false); void releaseWindowParent(uint64_t token, uint64_t before = UINT64_MAX); void command(char c, uint64_t next); void append(); void trimHistory(); void reconcileWindow(); void observe(); void run(); void shutdown(); ~Impl(); }; } // namespace bridge