#pragma once #include "player.h" #include #include #include #include #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; // Reopen only this session's original private inode, never an arbitrary existing file. void resume(); 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::string path; dev_t device = 0; ino_t inode = 0; 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; }; // Routes decoder-owned snapshots by immutable media identity; only the active writer runs. class ProgressReports { public: explicit ProgressReports(const std::string &path); ProgressReports() = default; void setSingleFile(const std::string &path); void registerFile(const std::string &identity, const std::string &path); void registerFiles(const char *const *identities, const char *const *paths, int count); void begin(const std::string &identity, bool live = false) noexcept; void observe(const PlayerState &state) noexcept; void closeMedia() noexcept; void finish() noexcept; std::string error() const; private: struct Entry { std::string path; std::unique_ptr reporter; }; std::unique_ptr single; std::string singlePath; std::string singleIdentity; std::map entries; std::set paths; ProgressReporter *active = nullptr; mutable std::mutex mutex; std::string failure; }; }