| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | /* | ||
| 3 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 4 | * 2013-2022 by the Falcon Player Developers. | ||
| 5 | * | ||
| 6 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 7 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 8 | * file for descriptions of what files are covered by each license. | ||
| 9 | * | ||
| 10 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 11 | * included LICENSE.LGPL file. | ||
| 12 | */ | ||
| 13 | |||
| 14 | // PLUGIN API: native plugins compile against this header and are loaded with | ||
| 15 | // dlopen(). | ||
| 16 | // - Plugins call Player::INSTANCE methods (IsPlaying, StartPlaylist, GetInfo, | ||
| 17 | // ...) by symbol. | ||
| 18 | // Data layout here is NOT plugin ABI, so members can change freely. Changing | ||
| 19 | // or removing a signature a plugin calls is: keep the old one as an overload | ||
| 20 | // (as Timers::addPeriodicTimer does) or bump FPP_PLUGIN_API_VERSION in | ||
| 21 | // Plugin.h. See .claude/PLUGIN-ABI.md for which plugins use what. | ||
| 22 | |||
| 23 | #include <ctime> | ||
| 24 | #include "fpp-json-fwd.h" | ||
| 25 | #include "fpphttp_types.h" | ||
| 26 | #include <map> | ||
| 27 | #include <memory> | ||
| 28 | #include <mutex> | ||
| 29 | #include <vector> | ||
| 30 | |||
| 31 | #include "AtomicSharedPtr.h" | ||
| 32 | #include "playlist/Playlist.h" | ||
| 33 | |||
| 34 | class Player { | ||
| 35 | public: | ||
| 36 | Player(); | ||
| 37 | ~Player(); | ||
| 38 | |||
| 39 | void Init(); | ||
| 40 | |||
| 41 | int StartPlaylist(const std::string& name, const int repeat = -1, | ||
| 42 | const int startPosition = -1, const int endPosition = -1, | ||
| 43 | const int manualPriority = 1000, const bool allowScheduleOverride = true); | ||
| 44 | int StartScheduledPlaylist(const std::string& name, const int position, | ||
| 45 | const int repeat, const int scheduleEntry, const int scheduledPriority, | ||
| 46 | const time_t sTime, const time_t eTime, const int method); | ||
| 47 | |||
| 48 | // fppd-internal, not plugin API. Held while one playlist is replaced by | ||
| 49 | // another, as the player reads idle in between. While any is held the | ||
| 50 | // main loop doesn't treat that idle as a stop and the scheduler starts, | ||
| 51 | // pre-empts and re-arms nothing (issue #3140), for at most 5 s. | ||
| 52 | class SwitchScope { | ||
| 53 | public: | ||
| 54 | SwitchScope(); | ||
| 55 | ~SwitchScope(); | ||
| 56 | SwitchScope(const SwitchScope&) = delete; | ||
| 57 | SwitchScope& operator=(const SwitchScope&) = delete; | ||
| 58 | }; | ||
| 59 | static bool Switching(); | ||
| 60 | |||
| 61 | int AdjustPlaylistStopTime(const int seconds = 300); | ||
| 62 | |||
| 63 | // wrappers for Playlist class methods we'll just call directly for now | ||
| 64 | void InsertPlaylistAsNext(const std::string& filename, const int startPosition = -1, const int endPos = -1); | ||
| 65 | void InsertPlaylistImmediate(const std::string& filename, const int startPosition = -1, const int endPos = -1); | ||
| 66 | |||
| 67 | int StopNow(int forceStop = 0); | ||
| 68 | // Hard stop of every level that is playing now (an inserted playlist and | ||
| 69 | // the ones it interrupted). Bounded by that depth rather than "until | ||
| 70 | // idle": a PLAYLIST_STOPPED preset may legitimately start a new playlist | ||
| 71 | // as the last level stops, and "until idle" would then stop and restart | ||
| 72 | // it forever on the main loop. | ||
| 73 | void StopNowAllLevels(int forceStop = 0); | ||
| 74 | int StopGracefully(int forceStop = 0, int afterCurrentLoop = 0); | ||
| 75 | |||
| 76 | int Process(); | ||
| 77 | void ProcessMedia(); | ||
| 78 | int IsPlaying(); | ||
| 79 | |||
| 80 | std::string GetPlaylistName(); | ||
| 81 | PlaylistStatus GetStatus(); | ||
| 82 | int GetRepeat(); | ||
| 83 | void SetRepeat(int repeat); | ||
| 84 | std::time_t GetOrigStartTime(); | ||
| 85 | std::time_t GetOrigStopTime(); | ||
| 86 | std::time_t GetStartTime(); | ||
| 87 | std::time_t GetStopTime(); | ||
| 88 | int GetStopMethod(); | ||
| 89 | int GetPosition(); | ||
| 90 | int GetResumePosition(); | ||
| 91 | Json::Value GetInfo(); | ||
| 92 | void ClearForceStopped(); | ||
| 93 | bool GetForceStopped(); | ||
| 94 | std::string GetForceStoppedPlaylist(); | ||
| 95 | int GetPriority(); | ||
| 96 | bool GetAllowScheduleOverride(); | ||
| 97 | int GetScheduleEntry(); | ||
| 98 | uint64_t GetFileTime(); | ||
| 99 | Json::Value GetConfig(); | ||
| 100 | Json::Value GetMqttStatusJSON(); | ||
| 101 | int WasScheduled(); | ||
| 102 | |||
| 103 | int FindPosForMS(uint64_t& ms, bool itemDefinedOnly); //ms will be updated with how far into Pos it would be | ||
| 104 | void GetFilenamesForPos(int pos, std::string& seq, std::string& med); | ||
| 105 | |||
| 106 | int Load(const std::string filename); | ||
| 107 | int Start(); | ||
| 108 | |||
| 109 | void RestartItem(); | ||
| 110 | void NextItem(); | ||
| 111 | void PrevItem(); | ||
| 112 | |||
| 113 | void Pause(); | ||
| 114 | void Resume(); | ||
| 115 | |||
| 116 | int Cleanup(); | ||
| 117 | |||
| 118 | Json::Value GetStatusJSON(); | ||
| 119 | void GetCurrentStatus(Json::Value& result); | ||
| 120 | |||
| 121 | virtual HttpResponsePtr render_GET(const HttpRequestPtr& req); | ||
| 122 | virtual HttpResponsePtr render_POST(const HttpRequestPtr& req); | ||
| 123 | virtual HttpResponsePtr render_PUT(const HttpRequestPtr& req); | ||
| 124 | |||
| 125 | static Player INSTANCE; | ||
| 126 | |||
| 127 | // ──────────────────────────────────────────────────────────────────── | ||
| 128 | // Ownership of the current playlist. | ||
| 129 | // | ||
| 130 | // The player owns every Playlist that can become "the one playing". A | ||
| 131 | // caller on any thread takes a snapshot and calls through it; holding the | ||
| 132 | // shared_ptr keeps that instance alive for the whole call even if the main | ||
| 133 | // loop retires it mid-flight. That is what closes the cross-thread | ||
| 134 | // use-after-free: a status poll from a drogon thread can no longer be | ||
| 135 | // reading an instance the main loop just freed. | ||
| 136 | // | ||
| 137 | // Take ONE snapshot per call, not one per dereference — two snapshots in | ||
| 138 | // one method can straddle a switch and report a torn mix of two playlists. | ||
| 139 | // ──────────────────────────────────────────────────────────────────── | ||
| 140 | 5220960 | std::shared_ptr<Playlist> PlaylistSnapshot() const { return m_playlist.load(); } | |
| 141 | |||
| 142 | // The rest of this API is main-thread only; it is the lifecycle half that | ||
| 143 | // Playlist.cpp's own machinery (PL_CLEANUPS, the m_parent chain, | ||
| 144 | // SwitchToInsertedPlaylist) drives. Raw Playlist* keeps flowing through | ||
| 145 | // that machinery unchanged; these calls are where it meets ownership. | ||
| 146 | |||
| 147 | // Construct a Playlist and take ownership of it. Returns the reference so | ||
| 148 | // the caller can use it before publishing it as current. | ||
| 149 | std::shared_ptr<Playlist> CreatePlaylist(std::shared_ptr<Playlist> parent); | ||
| 150 | |||
| 151 | // Publish an already-owned instance as the current playlist. | ||
| 152 | void SetCurrentPlaylist(Playlist* p); | ||
| 153 | |||
| 154 | // Retire an instance: drop the player's owning reference. The object | ||
| 155 | // itself survives until the last outstanding snapshot releases it. | ||
| 156 | void ReleasePlaylist(Playlist* p); | ||
| 157 | |||
| 158 | // Run the destructors deferred by the shared_ptr deleter. MAIN THREAD | ||
| 159 | // ONLY — ~Playlist parks its entries on an unsynchronized list that only | ||
| 160 | // the main thread drains, so the deleter never destroys inline. | ||
| 161 | void DrainRetiredPlaylists(); | ||
| 162 | |||
| 163 | private: | ||
| 164 | // Set forceStopped/forceStoppedPlaylist for a stop request. | ||
| 165 | void RecordForceStop(int forceStop); | ||
| 166 | |||
| 167 | // Every live instance, keyed by nothing but its own identity: the current | ||
| 168 | // playlist plus every ancestor still on the m_parent chain. Each level | ||
| 169 | // also holds its own parent, so a parent released here lives on until no | ||
| 170 | // level links to it any more. | ||
| 171 | AtomicSharedPtr<Playlist> m_playlist; | ||
| 172 | std::mutex m_ownedPlaylistsLock; | ||
| 173 | std::vector<std::shared_ptr<Playlist>> m_ownedPlaylists; | ||
| 174 | |||
| 175 | std::string playlistName; | ||
| 176 | |||
| 177 | std::time_t lastCheckTime; | ||
| 178 | std::time_t origStartTime; | ||
| 179 | std::time_t origStopTime; | ||
| 180 | std::time_t startTime; | ||
| 181 | std::time_t stopTime; | ||
| 182 | int stopMethod; | ||
| 183 | int priority; | ||
| 184 | bool allowScheduleOverride; | ||
| 185 | |||
| 186 | bool forceStopped; | ||
| 187 | std::string forceStoppedPlaylist; | ||
| 188 | |||
| 189 | // Guards every field above from playlistName down. They are written by | ||
| 190 | // whichever thread starts or stops a playlist (the main loop, an API | ||
| 191 | // thread, MQTT) and read by the scheduler and the status API; two threads | ||
| 192 | // assigning playlistName at once could free its buffer twice. A leaf | ||
| 193 | // lock: never held while calling into a Playlist. | ||
| 194 | mutable std::mutex m_stateLock; | ||
| 195 | }; | ||
| 196 |