GCC Code Coverage Report


Directory: ./
File: Player.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 1 1 100.0%
Functions: 1 1 100.0%
Branches: 0 0 -%

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