#pragma once #include "player.h" #include #include #include #include #include #include #include #include #include namespace playback { // The decoder owns media/snapshot methods; the writer owns the file after construction. class ProgressReporter { public: explicit ProgressReporter(const std::string &path, std::function clock = {}); ~ProgressReporter(); ProgressReporter(const ProgressReporter &) = delete; ProgressReporter &operator=(const ProgressReporter &) = delete; static bool validMediaURL(std::string_view url) noexcept; static bool validMediaIdentity(std::string_view identity) noexcept; void begin(const std::string &mediaURL, bool live = false) noexcept; void observe(const PlayerState &state) noexcept; void closeMedia() noexcept; // Call after the decoder joins; drains queued records and joins the writer. void finish() noexcept; std::string error() const; private: struct Snapshot { double position = 0; double duration = 0; PlayerStatus status = PLAYER_EMPTY; bool eligible = false; } snapshot; std::string mediaURL; std::function clock; bool live = false; bool hasSnapshot = false; bool awaitingReady = false; bool seekPending = false; bool thresholdReported = false; bool finished = false; double lastRecordAt = 0; int descriptor = -1; std::thread writer; mutable std::mutex mutex; std::condition_variable wake; std::deque records; std::array failure{}; std::atomic disabled{false}; bool stopping = false; bool emit(const char *event) noexcept; void disable(const char *message, int osError = 0) noexcept; void writeRecords() noexcept; }; }