GCC Code Coverage Report


Directory: ./
File: playlist/Playlist.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 1543 1862 82.9%
Functions: 89 100 89.0%
Branches: 1890 3691 51.2%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <sys/wait.h>
18
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <algorithm>
22 #include <cstring> // strnlen -- needed directly for NOPCH builds
23 #include <functional>
24 #include <time.h>
25
26 #include "../Events.h"
27 #include "../Sequence.h"
28 #include "../Warnings.h"
29 #include "../common.h"
30 #include "../log.h"
31 #include "../settings.h"
32
33 #include "../fseq/FSEQFile.h"
34
35 #include "../Player.h"
36 #include "Playlist.h"
37 #include "Plugins.h"
38 #include "fpp.h"
39
40 #include "PlaylistEntryBoth.h"
41 #include "PlaylistEntryBranch.h"
42 #include "PlaylistEntryCommand.h"
43 #include "PlaylistEntryDynamic.h"
44 #include "PlaylistEntryImage.h"
45 #include "PlaylistEntryMedia.h"
46 #include "PlaylistEntryPause.h"
47 #include "PlaylistEntryPlaylist.h"
48 #include "PlaylistEntryRemap.h"
49 #include "PlaylistEntryScript.h"
50 #include "PlaylistEntrySequence.h"
51 #include "PlaylistEntryURL.h"
52 #include "../mediaoutput/StreamSlotManager.h"
53 #include "../util/RegExCache.h"
54
55 // Playlists the player is done with. Retiring is still a two-step deferral:
56 // pushed here at the moment the instance stops being current, and only at the
57 // top of Process() — after the stack has unwound — handed back to the owner.
58 // What changed in stage 2 is the second step: the drain drops the player's
59 // owning reference instead of calling delete, so an instance a reader thread is
60 // still holding a snapshot of survives until that reader is done with it.
61 // Between push and drain the instance is fully alive, which is what SetIdle's
62 // "is this parent condemned" walk below relies on.
63 // Both lists are reached from command threads as well as the main loop --
64 // SetIdle/SwitchToInsertedPlaylist condemn instances from whatever thread runs
65 // the transition, and a Load() on a command thread parks replaced entries -- so
66 // every access holds PL_CLEANUPS_LOCK. Deletes never run under it.
67 static std::mutex PL_CLEANUPS_LOCK;
68 static std::list<Playlist*> PL_CLEANUPS;
69 // Entries deleted while one of their own methods may still be on the
70 // call stack (e.g. a "Start Playlist" command entry that reloads this
71 // playlist from inside StartPlaying) are parked here and freed at the
72 // top of Process(), after the stack has unwound. Mirrors PL_CLEANUPS.
73 static std::list<PlaylistEntryBase*> PL_ENTRY_CLEANUPS;
74
75 // See the façade's comment in Playlist.h. Stateless, so no static
76 // initialization order concerns of its own; the snapshot it hands out comes
77 // from Player::INSTANCE.
78 PlaylistRef playlist;
79
80 ✗ PlaylistHandle PlaylistRef::operator->() const {
81 ✗ return PlaylistHandle{ Player::INSTANCE.PlaylistSnapshot() };
82 }
83
84 ✗ PlaylistRef::operator bool() const {
85 ✗ return Player::INSTANCE.PlaylistSnapshot() != nullptr;
86 }
87
88 // ──────────────────────────────────────────────────────────────────────────
89 // Crash snapshot
90 //
91 // What was playing, and where, at the moment fppd died. The crash handler
92 // already records the sequence file and elapsed ms; the playlist half is the
93 // part that explains *why* a given entry was being started -- most usefully
94 // the three section sizes, since an empty section that a caller believed was
95 // non-empty is the shape of a whole class of bug here.
96 //
97 // Deliberately plain scalars in file statics, never the live Playlist: see
98 // PlaylistDumpCrashState in the header for why the handler cannot lock or
99 // walk containers. Torn reads are accepted.
100 // ──────────────────────────────────────────────────────────────────────────
101 namespace {
102 struct CrashSnapshot {
103 char playlistName[128];
104 char section[24];
105 int sectionPosition;
106 int leadInSize;
107 int mainSize;
108 int leadOutSize;
109 int status;
110 bool everSet;
111 };
112 CrashSnapshot s_crashSnapshot{};
113
114 599474 void copyFixed(char* dst, size_t dstSize, const std::string& src) {
115
1/2
✓ Branch 0 taken 599474 times.
✗ Branch 1 not taken.
599474 size_t n = src.size() < dstSize - 1 ? src.size() : dstSize - 1;
116 599474 memcpy(dst, src.data(), n);
117 599474 dst[n] = '\0';
118 599474 }
119 } // namespace
120
121 299737 void Playlist::UpdateCrashSnapshot(void) {
122 299737 CrashSnapshot& s = s_crashSnapshot;
123 299737 copyFixed(s.playlistName, sizeof(s.playlistName), m_name);
124 299737 copyFixed(s.section, sizeof(s.section), m_currentSectionStr);
125 299737 s.sectionPosition = m_sectionPosition;
126 299737 s.leadInSize = (int)m_leadIn.size();
127 299737 s.mainSize = (int)m_mainPlaylist.size();
128 299737 s.leadOutSize = (int)m_leadOut.size();
129 299737 s.status = (int)m_status;
130 299737 s.everSet = true;
131 299737 }
132
133 18 void PlaylistDumpCrashState(int fd) {
134 18 const CrashSnapshot& s = s_crashSnapshot;
135 char buf[512];
136 int n;
137
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
18 if (!s.everSet) {
138 17 n = snprintf(buf, sizeof(buf), "Playlist state: none (no playlist has run)\n");
139 } else {
140 1 n = snprintf(buf, sizeof(buf),
141 "Playlist state: name='%.127s' section=%.23s pos=%d status=%d "
142 "sizes(leadIn=%d main=%d leadOut=%d)\n",
143 1 s.playlistName, s.section, s.sectionPosition, s.status,
144 1 s.leadInSize, s.mainSize, s.leadOutSize);
145 }
146
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (n > 0) {
147
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 (void)!write(fd, buf, (size_t)(n < (int)sizeof(buf) ? n : (int)sizeof(buf) - 1));
148 }
149 18 }
150
151 // ──────────────────────────────────────────────────────────────────────────
152 // Deferred notifications and deferred re-entrant starts.
153 //
154 // TriggerPreset() and PluginManager::playlistCallback() run arbitrary code
155 // INLINE on the calling thread: preset commands can start/stop playlists,
156 // native plugins can call any FPP API, and script plugins fork and block in
157 // waitpid. Historically these fired from inside Play()/Start()/StopNow()/
158 // SetIdle()/Process() while m_playlistMutex (recursive) was held and while
159 // this playlist's state was mid-transition. The recursion "worked", but:
160 // - PLAYLIST_STARTED presets that start another playlist re-entered
161 // Play() on the same stack; chains (A starts B, B starts A) recursed
162 // without bound until stack overflow.
163 // - Presets/plugins observed and mutated half-updated playlist state
164 // (the PL_CLEANUPS / PL_ENTRY_CLEANUPS / deferred-start machinery is
165 // scar tissue from exactly this).
166 // - The mutex stayed held across fork/waitpid script runs, stalling the
167 // status API.
168 //
169 // The invariant now: a state transition COMPLETES before any preset or
170 // playlist callback runs. Notification sites queue a snapshot-capturing
171 // closure via QueuePlaylistNotification(); every public transition method
172 // holds a PlaylistTransitionGuard, and when the OUTERMOST guard on this
173 // thread unwinds (mutex already released — the guard is declared before
174 // the lock), the queue is drained iteratively. A Play() triggered by a
175 // drained notification runs inline at depth 1 and appends its own
176 // notifications to the same queue, so chains flatten into iteration
177 // instead of recursion; runaway chains hit MAX_NOTIFICATION_CASCADE and
178 // stop with a warning instead of a stack overflow.
179 //
180 // Re-entrant Play() (depth > 1, e.g. a "Start Playlist" COMMAND ENTRY
181 // executing inside its own StartPlaying() frame) is recorded in
182 // s_pendingStart* and replayed by the same drain. This generalizes the
183 // old PlaylistEntryCommand/PlaylistEntryScript dynamic_cast special-case
184 // in Play() to every re-entrant caller.
185 // ──────────────────────────────────────────────────────────────────────────
186 static thread_local int tl_transitionDepth = 0;
187 static thread_local bool tl_drainingNotifications = false;
188 static thread_local std::vector<std::function<void()>> tl_pendingNotifications;
189 // One drain may legitimately fire a handful of chained events (playlist A's
190 // STOPPED preset starts B, etc.). Each start/stop cycle queues ~4-5
191 // notifications, so 20 allows ~4 chained playlist switches — anything past
192 // that is a preset loop (PLAYLIST_STARTED → Start Playlist → ...).
193 static constexpr int MAX_NOTIFICATION_CASCADE = 20;
194
195 // Pending re-entrant start request. File-scope (not per-Playlist members)
196 // because the active Playlist object can be swapped/queued for cleanup
197 // between the defer and the drain (inserted playlists, SetIdle-to-parent) —
198 // the request belongs to "the player", not to one instance.
199 static std::mutex s_pendingStartMutex;
200 static std::string s_pendingStartFilename;
201 static int s_pendingStartPosition = 0;
202 static int s_pendingStartRepeat = 0;
203 static int s_pendingStartScheduleEntry = 0;
204 static int s_pendingStartEndPosition = 0;
205
206 // Set while a PLAYLIST_STOPPED preset runs for an inserted playlist that has
207 // handed the player back to the playlist it interrupted. That playlist has
208 // already stopped; the preset runs after the transition (above), by which
209 // time the parent has resumed, so a Stop in it would stop the parent -- a
210 // show went dark after every Remote Falcon request (#3114). FPP 9 ran the
211 // preset while the inserted playlist was still current, where a Stop did
212 // nothing; Player's stops do nothing here for the same reason.
213 static thread_local int tl_handedBackStoppedPreset = 0;
214
215 813 bool InHandedBackStoppedPreset() {
216 813 return tl_handedBackStoppedPreset > 0;
217 }
218
219 namespace {
220 struct HandedBackStoppedPresetScope {
221 bool on;
222 12 explicit HandedBackStoppedPresetScope(bool b) :
223 12 on(b) {
224
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 11 times.
12 if (on)
225 1 tl_handedBackStoppedPreset++;
226 12 }
227 12 ~HandedBackStoppedPresetScope() {
228
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 11 times.
12 if (on)
229 1 tl_handedBackStoppedPreset--;
230 12 }
231 };
232 }
233
234 36332 static void QueuePlaylistNotification(std::function<void()>&& fn) {
235
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36332 times.
36332 if (tl_transitionDepth == 0) {
236 // Not inside a transition (shouldn't happen — all queue sites are in
237 // guarded methods) — fire directly rather than parking it on this
238 // thread's queue where it would only drain on some later transition.
239 ✗ fn();
240 ✗ return;
241 }
242 36332 tl_pendingNotifications.push_back(std::move(fn));
243 }
244
245 namespace {
246 class PlaylistTransitionGuard {
247 public:
248 362576 PlaylistTransitionGuard() { tl_transitionDepth++; }
249 362576 ~PlaylistTransitionGuard() {
250
6/6
✓ Branch 0 taken 330146 times.
✓ Branch 1 taken 32430 times.
✓ Branch 2 taken 199 times.
✓ Branch 3 taken 329947 times.
✓ Branch 4 taken 32629 times.
✓ Branch 5 taken 329947 times.
362576 if (--tl_transitionDepth > 0 || tl_drainingNotifications) {
251 // Inner frame, or a frame created by the drain itself — the
252 // outermost/draining frame owns the queue.
253 32629 return;
254 }
255 329947 tl_drainingNotifications = true;
256 329947 int fired = 0;
257 while (true) {
258
2/2
✓ Branch 0 taken 36323 times.
✓ Branch 1 taken 329973 times.
366296 if (!tl_pendingNotifications.empty()) {
259
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 36309 times.
36323 if (++fired > MAX_NOTIFICATION_CASCADE) {
260 14 LogErr(VB_PLAYLIST, "Playlist notification cascade exceeded %d events — presets/plugins are starting playlists in a loop (e.g. PLAYLIST_STARTED -> Start Playlist -> PLAYLIST_STARTED), or a playlist command entry keeps restarting its own playlist item. Dropping %d pending notifications.\n",
261 MAX_NOTIFICATION_CASCADE, (int)tl_pendingNotifications.size());
262 42 WarningHolder::AddWarningTimeout(300, 36, "Playlist loop detected: a playlist preset (e.g. PLAYLIST_STARTED) starts a playlist which re-triggers the preset, or a playlist command entry (Restart/Next/Prev Playlist Item) keeps landing on itself. Fix the preset or playlist.");
263 14 tl_pendingNotifications.clear();
264 14 std::lock_guard<std::mutex> l(s_pendingStartMutex);
265 14 s_pendingStartFilename.clear();
266 14 break;
267 14 }
268 36309 std::function<void()> fn = std::move(tl_pendingNotifications.front());
269 36309 tl_pendingNotifications.erase(tl_pendingNotifications.begin());
270 36309 fn();
271 36307 continue;
272 36307 }
273 // Replay a deferred re-entrant start, if one was recorded.
274 329973 std::string nm;
275 int pos, rep, sched, endPos;
276 {
277 329973 std::lock_guard<std::mutex> l(s_pendingStartMutex);
278
2/2
✓ Branch 0 taken 329931 times.
✓ Branch 1 taken 42 times.
329973 if (s_pendingStartFilename.empty())
279 329931 break;
280 42 nm = s_pendingStartFilename;
281 42 s_pendingStartFilename.clear();
282 42 pos = s_pendingStartPosition;
283 42 rep = s_pendingStartRepeat;
284 42 sched = s_pendingStartScheduleEntry;
285 42 endPos = s_pendingStartEndPosition;
286
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 329931 times.
329973 }
287 42 std::shared_ptr<Playlist> cur = Player::INSTANCE.PlaylistSnapshot();
288
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (cur) {
289 // Runs at depth 0 → executes inline; its notifications land
290 // back on this queue and the loop continues.
291 42 cur->Play(nm, pos, rep, sched, endPos);
292 }
293
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 329931 times.
366322 }
294 329945 tl_drainingNotifications = false;
295 32 }
296 };
297 } // namespace
298 // The atomic members replaced plain ones in place; a plugin's inline getter
299 // built against the old header must still find the same bytes.
300 static_assert(sizeof(std::atomic<int>) == sizeof(int) && alignof(std::atomic<int>) == alignof(int));
301 static_assert(sizeof(std::atomic<time_t>) == sizeof(time_t) && alignof(std::atomic<time_t>) == alignof(time_t));
302
303 /*
304 *
305 */
306 2385 Playlist::Playlist(std::shared_ptr<Playlist> parent) :
307 2385 m_parent(parent),
308 2385 m_repeat(0),
309 2385 m_loop(0),
310 2385 m_loopCount(0),
311 2385 m_random(0),
312 2385 m_blankAtEnd(1),
313 2385 m_startTime(0),
314 2385 m_subPlaylistDepth(0),
315 2385 m_scheduleEntry(-1),
316 2385 m_forceStop(0),
317 2385 m_stopAtPos(-1),
318 2385 m_loadStartPos(-1),
319 2385 m_loadEndPos(-1),
320 2385 m_fileTime(0),
321 2385 m_configTime(0),
322 2385 m_currentSection(nullptr),
323
1/2
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
4770 m_currentSectionStr("New"),
324 2385 m_sectionPosition(0),
325 2385 m_startPosition(0),
326 2385 m_globalPauseBetweenSequencesMS(0),
327 2385 m_isInGlobalPause(false),
328 2385 m_shouldStartGlobalPause(false),
329 2385 m_globalPauseStartTime(0),
330
2/4
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
9540 m_status(FPP_STATUS_IDLE) {
331
2/2
✓ Branch 0 taken 1887 times.
✓ Branch 1 taken 498 times.
2385 if (!parent) {
332 // The resident playlist announces the player's idle state at startup.
333 // An inserted playlist must not: it is constructed mid-show, and this
334 // published status=idle and repeat=0 over MQTT and sent plugins a
335 // "stop" while the parent was still playing.
336
1/2
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
1887 SetIdle(false);
337
1/2
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
1887 SetRepeat(0);
338 }
339
340 // The legacy /playlist/*/set MQTT topics are registered once, in
341 // Player::Init(), and dispatched to whichever playlist is current. They
342 // used to be registered here with [this]: every inserted playlist
343 // overwrote the callback with a pointer to itself and never removed it,
344 // so once that playlist finished and was freed, the next message on those
345 // topics called into freed memory.
346 2385 }
347
348 /*
349 *
350 */
351 496 Playlist::~Playlist() {
352 496 Cleanup();
353 496 }
354
355 1745689 PlaylistStatus Playlist::getPlaylistStatus() {
356 1745689 return m_status;
357 }
358
359 // Maps each status to the exact camelCase string the code has always used for
360 // the "status"/"currentState" fields and the "status" event, plus the "paused"
361 // value the paused state never had. This is the ONLY place m_status becomes a
362 // string, which is what keeps the two from drifting. (The space-separated
363 // human names "stopping gracefully" etc. are a separate representation built
364 // from the same enum in httpAPI.cpp's status_name / Variables.cpp; those are
365 // unaffected.) FPP_STATUS_STOPPING_NOW never had a string mirror -- SetIdle()
366 // overwrites the status within the same lock hold in StopNowImpl(), so no
367 // reader observes it -- "stoppingNow" simply completes the mapping.
368 135141 const char* Playlist::PlaylistStatusToString(PlaylistStatus status) {
369
5/7
✓ Branch 0 taken 19573 times.
✓ Branch 1 taken 111275 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4274 times.
✗ Branch 6 not taken.
135141 switch (status) {
370 19573 case FPP_STATUS_IDLE:
371 19573 return "idle";
372 111275 case FPP_STATUS_PLAYLIST_PLAYING:
373 111275 return "playing";
374 14 case FPP_STATUS_STOPPING_GRACEFULLY:
375 14 return "stoppingGracefully";
376 5 case FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP:
377 5 return "stoppingAfterLoop";
378 ✗ case FPP_STATUS_STOPPING_NOW:
379 ✗ return "stoppingNow";
380 4274 case FPP_STATUS_PLAYLIST_PAUSED:
381 4274 return "paused";
382 }
383 ✗ return "idle";
384 }
385
386 /*
387 *
388 */
389 18 int Playlist::LoadJSONIntoPlaylist(std::vector<PlaylistEntryBase*>& playlistPart, const Json::Value& entries, int startPos, int& maxEntries) {
390 18 return LoadSection(playlistPart, entries, startPos, maxEntries, nullptr);
391 }
392
393 2323 int Playlist::LoadSection(std::vector<PlaylistEntryBase*>& playlistPart, const Json::Value& entries, int startPos,
394 int& maxEntries, std::vector<int>* rowStart) {
395 2323 PlaylistEntryBase* plEntry = NULL;
396
397
2/2
✓ Branch 0 taken 2305 times.
✓ Branch 1 taken 18 times.
2323 if (rowStart)
398
2/4
✓ Branch 0 taken 2305 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2305 times.
✗ Branch 3 not taken.
2305 rowStart->assign(entries.size(), -1);
399
400 2323 int c = startPos;
401
7/8
✓ Branch 0 taken 6389 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4071 times.
✓ Branch 3 taken 2318 times.
✓ Branch 4 taken 4066 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 4066 times.
✓ Branch 7 taken 2323 times.
6389 for (; (c < entries.size()) && (maxEntries > 0); c++) {
402
2/2
✓ Branch 0 taken 4049 times.
✓ Branch 1 taken 17 times.
4066 if (rowStart)
403 4049 (*rowStart)[c] = playlistPart.size();
404 // Long-term handle sub-playlists on-demand instead of at load time
405
6/10
✓ Branch 0 taken 4066 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4066 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4066 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4066 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 7 times.
✓ Branch 9 taken 4059 times.
4066 if (entries[c]["type"].asString() == "playlist") {
406 7 m_subPlaylistDepth++;
407
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
7 if (m_subPlaylistDepth < 5) {
408
8/16
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 6 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 6 times.
✗ Branch 15 not taken.
18 std::string filename = FPP_DIR_PLAYLIST("/" + entries[c]["name"].asString() + ".json");
409
410
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 Json::Value subPlaylist = LoadJSON(filename);
411 6 int tmpMax = 999999;
412
413
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (subPlaylist.isMember("leadIn"))
414
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 LoadJSONIntoPlaylist(playlistPart, subPlaylist["leadIn"], 0, tmpMax);
415
416
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (subPlaylist.isMember("mainPlaylist"))
417
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 LoadJSONIntoPlaylist(playlistPart, subPlaylist["mainPlaylist"], 0, tmpMax);
418
419
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (subPlaylist.isMember("leadOut"))
420
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 LoadJSONIntoPlaylist(playlistPart, subPlaylist["leadOut"], 0, tmpMax);
421 6 } else {
422
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 LogErr(VB_PLAYLIST, "Error, recursive playlist. Sub-playlist depth exceeded 5 trying to include '%s'\n", entries[c]["name"].asString().c_str());
423
7/14
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
2 WarningHolder::AddWarningTimeout(60, 24, "Recursive playlist: sub-playlist depth exceeded trying to include '" + entries[c]["name"].asString() + "'");
424 }
425
426 7 m_subPlaylistDepth--;
427 } else {
428
3/6
✓ Branch 0 taken 4059 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4059 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4059 times.
✗ Branch 5 not taken.
4059 plEntry = LoadPlaylistEntry(entries[c]);
429
2/2
✓ Branch 0 taken 4055 times.
✓ Branch 1 taken 4 times.
4059 if (plEntry) {
430
1/2
✓ Branch 0 taken 4055 times.
✗ Branch 1 not taken.
4055 playlistPart.push_back(plEntry);
431 }
432 }
433 // An end position counts rows of the file: a sub-playlist row used not
434 // to count at all, and an entry that failed to load let one more in.
435 4066 maxEntries--;
436 }
437
2/2
✓ Branch 0 taken 2305 times.
✓ Branch 1 taken 18 times.
2323 if (rowStart) {
438 // Rows past an end position start where the section ends.
439
3/4
✓ Branch 0 taken 2311 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 2305 times.
2311 for (; c < entries.size(); c++)
440 6 (*rowStart)[c] = playlistPart.size();
441 }
442
443 2323 return 1;
444 }
445
446 /*
447 *
448 */
449 1702 int Playlist::Load(Json::Value& config) {
450
1/2
✓ Branch 0 taken 1702 times.
✗ Branch 1 not taken.
1702 LogDebug(VB_PLAYLIST, "Playlist::Load(JSON)\n");
451
452 // Everything is parsed into locals and only committed once the whole
453 // config has been read. jsoncpp throws on a wrong-typed value ("repeat":
454 // "1", a section that is an object, ...), and this runs from Process()
455 // when a playing playlist's file is edited (ReloadIfNeeded) with nothing
456 // above it to catch -- that terminated fppd mid-show. Cleaning up first,
457 // as this used to, also meant any failure left a half-loaded playlist in
458 // place of the running one.
459 1702 std::string name;
460 1702 std::string desc;
461 1702 int repeat = 0;
462 1702 int loopCount = 0;
463 1702 int globalPauseMS = 0;
464 1702 int random = 0;
465
1/2
✓ Branch 0 taken 1702 times.
✗ Branch 1 not taken.
1702 Json::Value playlistInfo;
466 1702 std::vector<PlaylistEntryBase*> leadIn;
467 1702 std::vector<PlaylistEntryBase*> mainPlaylist;
468 1702 std::vector<PlaylistEntryBase*> leadOut;
469 1702 std::vector<int> leadInRows;
470 1702 std::vector<int> mainRows;
471 1702 std::vector<int> leadOutRows;
472
473 try {
474
2/4
✓ Branch 0 taken 1702 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1702 times.
✗ Branch 3 not taken.
1702 if (config.isMember("name"))
475
2/4
✓ Branch 0 taken 1702 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1702 times.
✗ Branch 3 not taken.
1702 name = config["name"].asString();
476
477
3/4
✓ Branch 0 taken 1702 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1700 times.
1702 if (config.isMember("desc"))
478
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 desc = config["desc"].asString();
479
480
3/4
✓ Branch 0 taken 1702 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1700 times.
✓ Branch 3 taken 2 times.
1702 repeat = config["repeat"].asInt();
481
2/4
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1700 times.
✗ Branch 3 not taken.
1700 loopCount = config["loopCount"].asInt();
482 1700 m_subPlaylistDepth = 0;
483
484 // Load global pause between sequences configuration
485
3/4
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1695 times.
1700 if (config.isMember("globalPauseBetweenSequencesMS"))
486
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 globalPauseMS = config["globalPauseBetweenSequencesMS"].asInt();
487
488
2/4
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1700 times.
✗ Branch 3 not taken.
1700 playlistInfo = config["playlistInfo"];
489
490 1700 int startPos = 0;
491 1700 int maxEntries = 999999;
492 1700 int origEntryCount = 0;
493
494
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1692 times.
1700 if (m_loadEndPos >= 0) {
495 8 maxEntries = m_loadEndPos + 1 - ((m_loadStartPos >= 0) ? m_loadStartPos : 0);
496 }
497
498 // Start -2 / end 0 asks for one random main-playlist item (Insert
499 // Random Item From Playlist). The lead-in and lead-out are not part of
500 // the pick: the lead-in used to load first and take the single slot, so
501 // a playlist with a lead-in always "randomly" played its first lead-in
502 // item.
503
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1694 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
1700 bool randomSingle = (m_loadStartPos == -2) && (m_loadEndPos == 0);
504
505
11/14
✓ Branch 0 taken 1694 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 1694 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1680 times.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 1680 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1680 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 590 times.
✓ Branch 11 taken 1090 times.
✓ Branch 12 taken 590 times.
✓ Branch 13 taken 1110 times.
1700 if (!randomSingle && config.isMember("leadIn") && config["leadIn"].size()) {
506
1/2
✓ Branch 0 taken 590 times.
✗ Branch 1 not taken.
590 LogDebug(VB_PLAYLIST, "Loading LeadIn:\n");
507
2/4
✓ Branch 0 taken 590 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 590 times.
✗ Branch 3 not taken.
590 const Json::Value leadInCfg = config["leadIn"];
508
509
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 584 times.
590 if (m_loadStartPos >= 0)
510 6 startPos = m_loadStartPos;
511 else
512 584 startPos = 0;
513
514
2/4
✓ Branch 0 taken 590 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 590 times.
✗ Branch 3 not taken.
590 leadInRows.assign(leadInCfg.size(), -1);
515
2/4
✓ Branch 0 taken 590 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 590 times.
✗ Branch 3 not taken.
590 if (startPos < leadInCfg.size())
516
1/2
✓ Branch 0 taken 590 times.
✗ Branch 1 not taken.
590 LoadSection(leadIn, leadInCfg, startPos, maxEntries, &leadInRows);
517
518
1/2
✓ Branch 0 taken 590 times.
✗ Branch 1 not taken.
590 origEntryCount += leadInCfg.size();
519 590 }
520
521
8/12
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1700 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1700 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1700 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1699 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 1699 times.
✓ Branch 11 taken 1 times.
1700 if (config.isMember("mainPlaylist") && config["mainPlaylist"].size()) {
522
1/2
✓ Branch 0 taken 1699 times.
✗ Branch 1 not taken.
1699 LogDebug(VB_PLAYLIST, "Loading MainPlaylist:\n");
523
2/4
✓ Branch 0 taken 1699 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1699 times.
✗ Branch 3 not taken.
1699 const Json::Value playlist = config["mainPlaylist"];
524
525
4/4
✓ Branch 0 taken 146 times.
✓ Branch 1 taken 1553 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 144 times.
1699 if ((m_loadStartPos >= 0) && (m_loadStartPos > origEntryCount)) {
526 2 startPos = m_loadStartPos - origEntryCount;
527
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1691 times.
1697 } else if (randomSingle) {
528 // random single item
529
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 int l = playlist.size();
530
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (l > 1) {
531
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 startPos = FPPrand() % l;
532 5 maxEntries = 1;
533 }
534 } else {
535 1691 startPos = 0;
536 }
537
538
2/4
✓ Branch 0 taken 1699 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1699 times.
✗ Branch 3 not taken.
1699 mainRows.assign(playlist.size(), -1);
539
2/4
✓ Branch 0 taken 1699 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1699 times.
✗ Branch 3 not taken.
1699 if (startPos < playlist.size()) {
540
1/2
✓ Branch 0 taken 1699 times.
✗ Branch 1 not taken.
1699 LoadSection(mainPlaylist, playlist, startPos, maxEntries, &mainRows);
541 }
542
543
1/2
✓ Branch 0 taken 1699 times.
✗ Branch 1 not taken.
1699 origEntryCount += playlist.size();
544 1699 }
545
546
11/14
✓ Branch 0 taken 1694 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 1694 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1680 times.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 1680 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1680 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 16 times.
✓ Branch 11 taken 1664 times.
✓ Branch 12 taken 16 times.
✓ Branch 13 taken 1684 times.
1700 if (!randomSingle && config.isMember("leadOut") && config["leadOut"].size()) {
547
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 LogDebug(VB_PLAYLIST, "Loading LeadOut:\n");
548
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
16 const Json::Value leadOutCfg = config["leadOut"];
549
550
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
16 if ((m_loadStartPos >= 0) && (m_loadStartPos > origEntryCount))
551 ✗ startPos = m_loadStartPos - origEntryCount;
552 else
553 16 startPos = 0;
554
555
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
16 leadOutRows.assign(leadOutCfg.size(), -1);
556
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
16 if (startPos < leadOutCfg.size())
557
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 LoadSection(leadOut, leadOutCfg, startPos, maxEntries, &leadOutRows);
558 16 }
559
560 // Sections skipped above still count their rows.
561
7/8
✓ Branch 0 taken 1110 times.
✓ Branch 1 taken 590 times.
✓ Branch 2 taken 1110 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1096 times.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 1096 times.
✓ Branch 7 taken 604 times.
1700 if (leadInRows.empty() && config.isMember("leadIn"))
562
3/6
✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1096 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1096 times.
✗ Branch 5 not taken.
1096 leadInRows.assign(config["leadIn"].size(), -1);
563
7/8
✓ Branch 0 taken 1684 times.
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 1684 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1670 times.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 1670 times.
✓ Branch 7 taken 30 times.
1700 if (leadOutRows.empty() && config.isMember("leadOut"))
564
3/6
✓ Branch 0 taken 1670 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1670 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1670 times.
✗ Branch 5 not taken.
1670 leadOutRows.assign(config["leadOut"].size(), -1);
565
566
3/4
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 1685 times.
1700 if (config.isMember("random")) {
567
3/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 if (config["random"].isNumeric()) {
568
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 random = config["random"].asInt();
569 ✗ } else if (config["random"].isBool() && config["random"].asBool()) {
570 ✗ random = 1;
571 ✗ } else if (config["random"].isString()) {
572 ✗ random = std::atoi(config["random"].asString().c_str());
573 }
574 }
575
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } catch (const std::exception& ex) {
576
4/8
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 std::string warn = "Playlist " + (name.empty() ? m_filename : name) + " is invalid: " + ex.what();
577
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogErr(VB_PLAYLIST, "%s\n", warn.c_str());
578
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 WarningHolder::AddWarningTimeout(60, 24, warn);
579 // Entries built before the throw were never published; retire them
580 // the same way Cleanup() does (destroyed on the main thread).
581
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
582
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (auto* section : { &leadIn, &mainPlaylist, &leadOut }) {
583
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 for (auto* e : *section) {
584 ✗ PL_ENTRY_CLEANUPS.push_back(e);
585 }
586 }
587 2 return 0;
588 2 }
589
590
1/2
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
1700 Cleanup();
591
592 {
593
1/2
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
1700 std::lock_guard<std::mutex> nl(m_nameLock);
594
1/2
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
1700 m_name = name;
595 1700 }
596
1/2
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
1700 m_desc = desc;
597 1700 m_repeat = repeat;
598 1700 m_loopCount = loopCount;
599 1700 m_globalPauseBetweenSequencesMS = globalPauseMS;
600
1/2
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
1700 m_playlistInfo = playlistInfo;
601 1700 m_random = random;
602 1700 m_leadIn.swap(leadIn);
603 1700 m_mainPlaylist.swap(mainPlaylist);
604 1700 m_leadOut.swap(leadOut);
605 1700 m_leadInRows.swap(leadInRows);
606 1700 m_mainRows.swap(mainRows);
607 1700 m_leadOutRows.swap(leadOutRows);
608
609 // set the positions prior to any randomizations
610 1700 int curPos = 0;
611
2/2
✓ Branch 0 taken 595 times.
✓ Branch 1 taken 1700 times.
2295 for (auto& a : m_leadIn) {
612 595 a->SetPositionInPlaylist(curPos);
613 595 ++curPos;
614 }
615
2/2
✓ Branch 0 taken 3443 times.
✓ Branch 1 taken 1700 times.
5143 for (auto& a : m_mainPlaylist) {
616 3443 a->SetPositionInPlaylist(curPos);
617 3443 ++curPos;
618 }
619
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1700 times.
1717 for (auto& a : m_leadOut) {
620 17 a->SetPositionInPlaylist(curPos);
621 17 ++curPos;
622 }
623
624 1700 m_sectionPosition = 0;
625 1700 m_currentSection = nullptr;
626
627
3/4
✓ Branch 0 taken 1700 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1698 times.
1700 if (WillLog(LOG_DEBUG, VB_PLAYLIST))
628
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Dump();
629
630 1700 return 1;
631 1702 }
632
633 /*
634 *
635 */
636 1696 Json::Value Playlist::LoadJSON(const std::string& filename) {
637
1/2
✓ Branch 0 taken 1696 times.
✗ Branch 1 not taken.
1696 LogDebug(VB_PLAYLIST, "Playlist::LoadJSON(%s)\n", filename.c_str());
638
639
1/2
✓ Branch 0 taken 1696 times.
✗ Branch 1 not taken.
1696 Json::Value root;
640
641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1696 times.
1696 if (filename.empty()) {
642 ✗ LogErr(VB_PLAYLIST, "Playlist::LoadJSON() called with empty filename\n");
643 ✗ return root;
644 }
645
646
3/4
✓ Branch 0 taken 1696 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1695 times.
1696 if (!LoadJsonFromFile(filename, root, JsonRoot::Object)) {
647
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string warn = "Could not load playlist ";
648
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 warn += filename;
649
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 WarningHolder::AddWarningTimeout(30, 24, warn);
650
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_PLAYLIST, "Error loading %s\n", filename.c_str());
651 // Signal load failure with a null root. LoadJsonFromFile normalizes a
652 // failure to an empty object, which is indistinguishable from a valid
653 // but empty playlist -- returning null instead lets ReloadPlaylist tell
654 // "file missing / half-written / unparseable" (null) apart from "a real
655 // playlist that happens to be empty" (a parsed object), so a transient
656 // read failure can't Cleanup() a playing show. Callers that treat this
657 // as an empty playlist (isMember checks, Load()) behave as before: a
658 // null Json::Value reports no members and loads nothing.
659
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 return Json::Value();
660 1 }
661
662
2/2
✓ Branch 0 taken 1693 times.
✓ Branch 1 taken 2 times.
1695 if (m_filename == filename) {
663 // stat() can fail (the file replaced between the read and here); its
664 // st_mtime is then uninitialized, so leave m_fileTime as it was rather
665 // than copying garbage into the reload check.
666 struct stat attr;
667
2/2
✓ Branch 0 taken 1691 times.
✓ Branch 1 taken 2 times.
1693 if (stat(filename.c_str(), &attr) == 0) {
668 char timeBuf[32];
669
1/2
✓ Branch 0 taken 1691 times.
✗ Branch 1 not taken.
1691 LogDebug(VB_PLAYLIST, "Playlist Last Modified: %s\n", ctime_r(&attr.st_mtime, timeBuf));
670
671 1691 m_fileTime = attr.st_mtime;
672 }
673 }
674
675 1695 return root;
676 1696 }
677
678 1 std::string sanitizeMediaName(std::string mediaName) {
679
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "Searching for Media File (%s)\n", mediaName.c_str());
680 // Same regex as PHP's sanitizeFilename
681
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 RegExCache rec("([^\\w\\s\\d\\-_~,;\\[\\]\\(\\).])");
682 // Check raw format for older Music uploads
683
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 std::string tmpMedia = FPP_DIR_MUSIC("/" + mediaName);
684
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string tmpMedialClean = std::regex_replace(mediaName, *rec.regex, "");
685
686
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "SanitizeMedia: Checking Raw Music (%s)\n", tmpMedia.c_str());
687
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (FileExists(tmpMedia)) {
688 ✗ return mediaName;
689 }
690
691 // Try Cleaned Music
692
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 tmpMedia = FPP_DIR_MUSIC("/" + tmpMedialClean);
693
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "SanitizeMedia: Checking Cleaned Music (%s)\n", tmpMedia.c_str());
694
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (FileExists(tmpMedia)) {
695 ✗ return tmpMedialClean;
696 }
697
698 // Try Older (orginal) Video upload
699
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 tmpMedia = FPP_DIR_VIDEO("/" + mediaName);
700
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "SanitizeMedia: Checking Raw Video (%s)\n", tmpMedia.c_str());
701
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (FileExists(tmpMedia)) {
702 ✗ return mediaName;
703 }
704
705 // Try cleaned video file
706
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 tmpMedia = FPP_DIR_VIDEO("/" + tmpMedialClean);
707
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "SanitizeMedia: Checking Clean Video (%s)\n", tmpMedia.c_str());
708
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (FileExists(tmpMedia)) {
709 ✗ return tmpMedialClean;
710 }
711
712
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_PLAYLIST, "SanitizeMedia: Unable to find Media: (%s)\n", mediaName.c_str());
713
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 tmpMedia = "";
714 1 return tmpMedia;
715 1 }
716
717 /*
718 *
719 */
720 1691 int Playlist::Load(const std::string& filename) {
721 1691 LogDebug(VB_PLAYLIST, "Playlist::Load(%s)\n", filename.c_str());
722
723
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1691 times.
1691 if (filename.empty()) {
724 ✗ LogErr(VB_PLAYLIST, "Playlist::Load() called with empty filename\n");
725 ✗ return 0;
726 }
727
728
2/4
✓ Branch 0 taken 1691 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1691 times.
✗ Branch 3 not taken.
1691 Events::Publish("playlist/name/status", filename);
729
730 try {
731
1/2
✓ Branch 0 taken 1691 times.
✗ Branch 1 not taken.
1691 Json::Value root;
732
1/2
✓ Branch 0 taken 1691 times.
✗ Branch 1 not taken.
1691 std::string tmpFilename = filename;
733
734
1/2
✓ Branch 0 taken 1691 times.
✗ Branch 1 not taken.
1691 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
735
736 // Whether this load synthesizes a one-entry playlist from a bare
737 // media file rather than loading a saved .json playlist. Captured
738 // here from the branch taken (a saved playlist could itself be named
739 // "something.fseq", so the filename alone cannot decide below).
740 1691 bool generated = false;
741
4/6
✓ Branch 0 taken 1691 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1691 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 1678 times.
3382 if (endsWith(tmpFilename, ".fseq")) {
742 13 generated = true;
743
4/8
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
13 m_filename = FPP_DIR_SEQUENCE("/" + tmpFilename);
744
745
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 root["name"] = tmpFilename;
746
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 root["repeat"] = 0;
747
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 root["loopCount"] = 0;
748
749 13 std::string mediaName;
750
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 FSEQFile* src = FSEQFile::openFSEQFile(m_filename);
751
6/6
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 3 times.
13 if (src && !src->getVariableHeaders().empty()) {
752
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 10 times.
27 for (auto& head : src->getVariableHeaders()) {
753
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
17 if ((head.code[0] == 'm') && (head.code[1] == 'f')) {
754
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 const auto& data = head.getData();
755
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (data.empty()) {
756 ✗ continue;
757 }
758 1 const char* d = (const char*)data.data();
759 1 size_t len = strnlen(d, data.size());
760
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (len == 0) {
761 ✗ continue;
762 }
763
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string raw(d, len);
764 1 size_t pos = raw.find_last_of("/\\");
765
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (pos == std::string::npos) {
766
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 mediaName = raw;
767 } else {
768 ✗ mediaName = raw.substr(pos + 1);
769 }
770
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 std::string tmpMedia = sanitizeMediaName(mediaName);
771
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (tmpMedia == "") {
772
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 std::string warn = "fseq \"" + tmpFilename + "\" lists a media file of \"" + mediaName + "\" but it can not be found";
773
774
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 WarningHolder::AddWarningTimeout(60, 24, warn);
775
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "%s\n", warn.c_str());
776
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 mediaName = "";
777 1 }
778 // Set the Media to the correct name
779
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 mediaName = tmpMedia;
780 1 }
781 }
782 }
783
784
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
13 if (src)
785
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 delete src;
786
787
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 Json::Value mp(Json::arrayValue);
788
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 Json::Value pe;
789
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (mediaName.empty()) {
790
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 pe["type"] = "sequence";
791
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 LogDebug(VB_PLAYLIST, "Generated an on-the-fly playlist for %s\n", tmpFilename.c_str());
792 } else {
793 ✗ pe["type"] = "both";
794 ✗ pe["mediaName"] = mediaName;
795 ✗ LogDebug(VB_PLAYLIST, "Generated an on-the-fly playlist for %s/%s\n", tmpFilename.c_str(), mediaName.c_str());
796 }
797
798
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 pe["enabled"] = 1;
799
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 pe["playOnce"] = 0;
800
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 pe["sequenceName"] = tmpFilename;
801
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 pe["videoOut"] = "--Default--";
802
803
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 mp.append(pe);
804
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 root["mainPlaylist"] = mp;
805
806 13 } else {
807
9/22
✓ Branch 0 taken 1678 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1678 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1678 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1678 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1678 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 1678 times.
✓ Branch 12 taken 1678 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 1678 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✓ Branch 17 taken 1678 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
1678 if (IsExtensionAudio(GetFileExtension(tmpFilename)) || IsExtensionVideo(GetFileExtension(tmpFilename))) {
808 ✗ generated = true;
809 ✗ if (IsExtensionAudio(GetFileExtension(tmpFilename)))
810 ✗ m_filename = FPP_DIR_MUSIC("/" + filename);
811 else
812 ✗ m_filename = FPP_DIR_VIDEO("/" + filename);
813
814 ✗ root["name"] = tmpFilename;
815 ✗ root["repeat"] = 0;
816 ✗ root["loopCount"] = 0;
817
818 ✗ Json::Value mp(Json::arrayValue);
819 ✗ Json::Value pe;
820
821 ✗ pe["type"] = "media";
822 ✗ pe["mediaName"] = tmpFilename;
823
824 ✗ LogDebug(VB_PLAYLIST, "Generated an on-the-fly playlist for %s\n", tmpFilename.c_str());
825
826 ✗ pe["enabled"] = 1;
827 ✗ pe["playOnce"] = 0;
828 ✗ pe["videoOut"] = "--Default--";
829 ✗ mp.append(pe);
830 ✗ root["mainPlaylist"] = mp;
831
832 ✗ } else {
833
5/10
✓ Branch 0 taken 1678 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1678 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1678 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1678 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1678 times.
✗ Branch 9 not taken.
1678 m_filename = FPP_DIR_PLAYLIST("/" + filename + ".json");
834
1/2
✓ Branch 0 taken 1678 times.
✗ Branch 1 not taken.
1678 root = LoadJSON(m_filename);
835 }
836 }
837
1/2
✓ Branch 0 taken 1691 times.
✗ Branch 1 not taken.
1691 int rc = Load(root);
838 // NB: Load(Json) runs Cleanup() first, which resets
839 // m_generatedOnTheFly, so the flag captured from the branch above
840 // has to be (re)asserted here, after the load.
841 1691 m_generatedOnTheFly = generated;
842 1691 return rc;
843
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
1691 } catch (std::exception& er) {
844 ✗ std::string warn = "Playlist " + GetPlaylistName() + " is invalid: " + er.what();
845 ✗ LogWarn(VB_PLAYLIST, "%s\n", warn.c_str());
846 ✗ WarningHolder::AddWarningTimeout(60, 24, warn);
847 ✗ return 0;
848 ✗ }
849 }
850
851 /*
852 *
853 */
854 4059 PlaylistEntryBase* Playlist::LoadPlaylistEntry(Json::Value entry) {
855 4059 PlaylistEntryBase* result = NULL;
856
857 // jsoncpp throws on a wrong-typed value ("enabled":"1", an entry that is
858 // not an object, ...). Contain it here, the one place every entry is
859 // built: nothing above this catches on the reload-from-Process() path, so
860 // an escaped exception terminated fppd mid-show.
861 try {
862
2/4
✓ Branch 0 taken 4059 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4059 times.
✗ Branch 3 not taken.
4059 std::string type = entry["type"].asString();
863
3/4
✓ Branch 0 taken 4059 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✓ Branch 3 taken 4020 times.
4059 if (type == "both")
864
3/8
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 39 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
39 result = new PlaylistEntryBoth(this);
865
3/4
✓ Branch 0 taken 4020 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 4006 times.
4020 else if (type == "branch")
866
3/8
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
14 result = new PlaylistEntryBranch(this);
867
3/4
✓ Branch 0 taken 4006 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 3991 times.
4006 else if (type == "dynamic")
868
3/8
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
15 result = new PlaylistEntryDynamic(this);
869
3/4
✓ Branch 0 taken 3991 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 342 times.
✓ Branch 3 taken 3649 times.
3991 else if (type == "image")
870
3/8
✓ Branch 0 taken 342 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 342 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 342 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
342 result = new PlaylistEntryImage(this);
871
3/4
✓ Branch 0 taken 3649 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
✓ Branch 3 taken 3593 times.
3649 else if (type == "media")
872
3/8
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 56 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
56 result = new PlaylistEntryMedia(this);
873
3/4
✓ Branch 0 taken 3593 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 773 times.
✓ Branch 3 taken 2820 times.
3593 else if (type == "pause")
874
3/8
✓ Branch 0 taken 773 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 773 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 773 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
773 result = new PlaylistEntryPause(this);
875
2/4
✓ Branch 0 taken 2820 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2820 times.
2820 else if (type == "playlist")
876 ✗ result = new PlaylistEntryPlaylist(this);
877
3/4
✓ Branch 0 taken 2820 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 2816 times.
2820 else if (type == "remap")
878
3/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 result = new PlaylistEntryRemap(this);
879
3/4
✓ Branch 0 taken 2816 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 2791 times.
2816 else if (type == "script")
880
3/8
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 25 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
25 result = new PlaylistEntryScript(this);
881
3/4
✓ Branch 0 taken 2791 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 615 times.
✓ Branch 3 taken 2176 times.
2791 else if (type == "sequence")
882
3/8
✓ Branch 0 taken 615 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 615 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 615 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
615 result = new PlaylistEntrySequence(this);
883
3/4
✓ Branch 0 taken 2176 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 2156 times.
2176 else if (type == "url")
884
3/8
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
20 result = new PlaylistEntryURL(this);
885
3/4
✓ Branch 0 taken 2156 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2154 times.
✓ Branch 3 taken 2 times.
2156 else if (type == "command")
886
3/8
✓ Branch 0 taken 2154 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2154 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2154 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
2154 result = new PlaylistEntryCommand(this);
887 else {
888
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogErr(VB_PLAYLIST, "Unknown Playlist Entry Type: %s\n", type.c_str());
889
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 WarningHolder::AddWarningTimeout(60, 24, "Unknown playlist entry type: " + type);
890 2 return NULL;
891 }
892
893
4/4
✓ Branch 0 taken 4056 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4055 times.
✓ Branch 3 taken 1 times.
4057 if (result->Init(entry))
894 4055 return result;
895
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4057 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
4060 } catch (const std::exception& ex) {
896
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_PLAYLIST, "Invalid playlist entry, skipping it: %s\n", ex.what());
897
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
3 WarningHolder::AddWarningTimeout(60, 24, std::string("Invalid playlist entry skipped: ") + ex.what());
898 1 }
899
900 // Init failed or threw: the entry was never handed to a caller, so this is
901 // its only owner and it must be destroyed here rather than leaked.
902
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 delete result;
903 2 return NULL;
904 }
905
906 /*
907 *
908 */
909 12 int Playlist::ReloadPlaylist(void) {
910
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 LogDebug(VB_PLAYLIST, "Playlist::ReloadPlaylist()\n");
911
912
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (m_filename == "") {
913 ✗ LogErr(VB_PLAYLIST, "Playlist::ReloadPlaylist() called but m_filename is empty\n");
914 ✗ return 0;
915 }
916
917
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
918
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::string currentSectionStr = m_currentSectionStr;
919 12 int repeat = m_repeat;
920 12 int loopCount = m_loopCount;
921 12 long long startTime = m_startTime;
922
923
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 Json::Value root = LoadJSON(m_filename);
924
925 // Invariant: a reload must never replace a playing playlist with nothing.
926 // LoadJSON returns a null root when the file is missing, half-written, or
927 // unparseable (a non-atomic UI save leaves a brief window where a reload
928 // was already triggered). Load() would Cleanup() the running playlist and
929 // then load an empty one, silently stopping the show. Bail here instead so
930 // ReloadIfNeeded takes its "continuing with existing copy" path and keeps
931 // the current playlist. A genuinely empty playlist still parses to an
932 // object (not null), so a real reload -- even to an empty playlist -- works.
933
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 11 times.
12 if (root.isNull()) {
934
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_PLAYLIST, "Playlist::ReloadPlaylist() got no usable playlist from %s; keeping the running playlist\n", m_filename.c_str());
935 1 return 0;
936 }
937
938
3/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 9 times.
11 if (!Load(root))
939 2 return 0;
940
941
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (m_random > 0) {
942 ✗ RandomizeMainPlaylist();
943 }
944
945 9 m_repeat = repeat;
946 9 m_loopCount = loopCount;
947 9 m_startTime = startTime;
948 9 m_sectionPosition = 0;
949
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 m_currentSectionStr = "MainPlaylist";
950 9 m_currentSection = &m_mainPlaylist;
951
952 9 return 1;
953 12 }
954
955 /*
956 *
957 */
958 1238 void Playlist::ReloadIfNeeded(void) {
959
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1226 times.
1238 if (FileHasBeenModified()) {
960 12 LogDebug(VB_PLAYLIST, "Playlist .json file has been modified, reloading playlist\n");
961
962
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (!ReloadPlaylist())
963 3 LogErr(VB_PLAYLIST, "Error reloading playlist, continuing with existing copy.");
964 }
965 1238 }
966
967 /*
968 *
969 */
970 1222 void Playlist::SwitchToMainPlaylist(void) {
971
1/2
✓ Branch 0 taken 1222 times.
✗ Branch 1 not taken.
1222 LogDebug(VB_PLAYLIST, "Switching to MainPlaylist\n");
972
973
1/2
✓ Branch 0 taken 1222 times.
✗ Branch 1 not taken.
1222 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
974
1/2
✓ Branch 0 taken 1222 times.
✗ Branch 1 not taken.
1222 UpdateCrashSnapshot();
975
976 // A caller's "does this section have entries" test can be invalidated
977 // before we get here: ReloadIfNeeded() runs Cleanup(), which pops every
978 // entry into PL_ENTRY_CLEANUPS and then reloads from the file, so a
979 // playlist edited mid-play can come back without this section. The
980 // vector is empty but keeps its capacity, so [0] hands back a stale
981 // pointer to an entry that Process() is about to delete -- the vtable is
982 // still mapped, the virtual call dispatches, and the fault lands inside
983 // StartPlaying(). Check here, at the one place the section is indexed,
984 // rather than at each call site.
985
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1221 times.
1222 if (m_mainPlaylist.empty()) {
986
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "MainPlaylist is empty, switching to idle.\n");
987
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 SetIdle();
988 1 return;
989 }
990
991
1/2
✓ Branch 0 taken 1221 times.
✗ Branch 1 not taken.
1221 m_currentSectionStr = "MainPlaylist";
992 1221 m_currentSection = &m_mainPlaylist;
993 1221 m_sectionPosition = 0;
994
6/8
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 1185 times.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 36 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 36 times.
✓ Branch 7 taken 1185 times.
1221 if (m_loopHoldUntilMS && GetMonotonicTimeMS() < m_loopHoldUntilMS) {
995 // Process() starts it when the hold runs out.
996 36 return;
997 }
998 1185 m_loopHoldUntilMS = 0;
999
1/2
✓ Branch 0 taken 1185 times.
✗ Branch 1 not taken.
1185 m_loopStartMS = GetMonotonicTimeMS();
1000
1/2
✓ Branch 0 taken 1185 times.
✗ Branch 1 not taken.
1185 StartPlayingWithGlobalPause(m_mainPlaylist[0]);
1001
2/2
✓ Branch 0 taken 1185 times.
✓ Branch 1 taken 37 times.
1222 }
1002
1003 /*
1004 *
1005 */
1006 9 void Playlist::SwitchToLeadOut(void) {
1007
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 LogDebug(VB_PLAYLIST, "Switching to LeadOut\n");
1008
1009
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1010
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 UpdateCrashSnapshot();
1011
1012 // See SwitchToMainPlaylist() -- a reload between the caller's size check
1013 // and here can leave this section empty.
1014
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9 if (m_leadOut.empty()) {
1015
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogDebug(VB_PLAYLIST, "LeadOut is empty, switching to idle.\n");
1016
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 SetIdle();
1017 2 return;
1018 }
1019
1020
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 m_currentSectionStr = "LeadOut";
1021 7 m_currentSection = &m_leadOut;
1022 7 m_sectionPosition = 0;
1023 // A hold is for the next main pass only; one still set here would
1024 // restart this lead-out entry when it ran out.
1025 7 m_loopHoldUntilMS = 0;
1026
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 StartPlayingWithGlobalPause(m_leadOut[0]);
1027
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
9 }
1028
1029 /*
1030 *
1031 */
1032 28960 int Playlist::Start(void) {
1033 // Name it here rather than leaving that to the per-entry Init() lines
1034 // below: this is the line the crash-time log ring is read from first, and
1035 // "which playlist" is the question it is read to answer.
1036
2/4
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28960 times.
✗ Branch 3 not taken.
28960 LogDebug(VB_PLAYLIST, "Playlist::Start(%s)\n", GetPlaylistName().c_str());
1037
1038 28960 PlaylistTransitionGuard guard;
1039
1/2
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
28960 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1040
1041
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1240 times.
30200 if ((!m_leadIn.size()) &&
1042
3/6
✓ Branch 0 taken 1240 times.
✓ Branch 1 taken 27720 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28960 times.
30200 (!m_mainPlaylist.size()) &&
1043 ✗ (!m_leadOut.size())) {
1044 ✗ std::string warn = "Playlist " + GetPlaylistName() + " is empty. Nothing to play.";
1045 ✗ WarningHolder::AddWarningTimeout(30, 24, warn);
1046
1047 ✗ SetIdle();
1048 ✗ return 0;
1049 ✗ }
1050
1051 // Capture the pre-start status before we flip to PLAYING: the callback
1052 // below distinguishes a fresh start from a restart of an already-playing
1053 // playlist.
1054 28960 PlaylistStatus origStatus = m_status;
1055 28960 m_status = FPP_STATUS_PLAYLIST_PLAYING;
1056
1057
1/2
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
28960 m_startTime = GetTime();
1058 28960 m_loop = 0;
1059 28960 m_forceStop = 0;
1060 // Also cleared by Cleanup(); repeated for the restart-in-place path,
1061 // which starts again without cleaning up.
1062 28960 m_isInGlobalPause = false;
1063 28960 m_shouldStartGlobalPause = false;
1064 28960 m_loopHoldUntilMS = 0;
1065
1066
1/2
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
28960 LogDebug(VB_PLAYLIST, "============================================================================\n");
1067
1068
2/2
✓ Branch 0 taken 27143 times.
✓ Branch 1 taken 1817 times.
28960 if (m_startPosition > 0) {
1069
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27143 times.
27143 if (m_startPosition >= (m_leadIn.size() + m_mainPlaylist.size() + m_leadOut.size()))
1070 ✗ m_startPosition = 0;
1071
1072
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27143 times.
27143 if (m_startPosition >= (m_leadIn.size() + m_mainPlaylist.size())) {
1073 ✗ m_sectionPosition = m_startPosition - (m_leadIn.size() + m_mainPlaylist.size());
1074 ✗ m_currentSectionStr = "LeadOut";
1075 ✗ m_currentSection = &m_leadOut;
1076
1/2
✓ Branch 0 taken 27143 times.
✗ Branch 1 not taken.
27143 } else if (m_startPosition >= m_leadIn.size()) {
1077 27143 m_sectionPosition = m_startPosition - m_leadIn.size();
1078
1/2
✓ Branch 0 taken 27143 times.
✗ Branch 1 not taken.
27143 m_currentSectionStr = "MainPlaylist";
1079 27143 m_currentSection = &m_mainPlaylist;
1080 } else {
1081 ✗ m_sectionPosition = m_startPosition;
1082 ✗ m_currentSectionStr = "LeadIn";
1083 ✗ m_currentSection = &m_leadIn;
1084 }
1085 } else {
1086
2/2
✓ Branch 0 taken 579 times.
✓ Branch 1 taken 1238 times.
1817 if (m_leadIn.size()) {
1087
1/2
✓ Branch 0 taken 579 times.
✗ Branch 1 not taken.
579 m_currentSectionStr = "LeadIn";
1088 579 m_currentSection = &m_leadIn;
1089
1/2
✓ Branch 0 taken 1238 times.
✗ Branch 1 not taken.
1238 } else if (m_mainPlaylist.size()) {
1090
1/2
✓ Branch 0 taken 1238 times.
✗ Branch 1 not taken.
1238 m_currentSectionStr = "MainPlaylist";
1091 1238 m_currentSection = &m_mainPlaylist;
1092 } else { // must be only lead Out
1093 ✗ m_currentSectionStr = "LeadOut";
1094 ✗ m_currentSection = &m_leadOut;
1095 }
1096
1097 1817 m_sectionPosition = 0;
1098 }
1099
1100 {
1101 // Deferred — plugin callbacks run arbitrary code inline; snapshot the
1102 // state now, deliver after the transition completes (see
1103 // PlaylistTransitionGuard).
1104
1/2
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
28960 Json::Value info = GetInfo();
1105
3/4
✓ Branch 0 taken 27271 times.
✓ Branch 1 taken 1689 times.
✓ Branch 2 taken 28960 times.
✗ Branch 3 not taken.
28990 std::string action = (origStatus == FPP_STATUS_PLAYLIST_PLAYING) ? "playing" : "start";
1106
1/2
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
28960 std::string sec = m_currentSectionStr;
1107 28960 int pos = m_sectionPosition;
1108
5/16
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28960 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28960 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 28960 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 28960 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
28960 QueuePlaylistNotification([info, action, sec, pos]() {
1109 28952 PluginManager::INSTANCE.playlistCallback(info, action, sec, pos);
1110
2/2
✓ Branch 0 taken 1688 times.
✓ Branch 1 taken 27264 times.
28952 if (action == "start") {
1111 // "playing" is sent as each subsequent item begins, so without
1112 // this the first item is the only one never announced that way
1113 // - and a plugin listening for "playing" waits until item two,
1114 // or forever on a single-item playlist. The two are not
1115 // alternatives: "start" means the playlist began, "playing"
1116 // means an item is playing, and at item zero both are true.
1117
2/4
✓ Branch 0 taken 1688 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1688 times.
✗ Branch 3 not taken.
5064 PluginManager::INSTANCE.playlistCallback(info, "playing", sec, pos);
1118 }
1119 28952 });
1120 28960 }
1121
4/8
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28960 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28960 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 28960 times.
✗ Branch 7 not taken.
86910 Events::Publish("status", PlaylistStatusToString(m_status));
1122
2/4
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28960 times.
✗ Branch 3 not taken.
57920 Events::Publish("playlist/section/status", m_currentSectionStr);
1123
2/4
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28960 times.
✗ Branch 3 not taken.
57890 Events::Publish("playlist/sectionPosition/status", m_sectionPosition);
1124
1125
2/4
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28960 times.
✗ Branch 3 not taken.
28960 m_currentSection->at(m_sectionPosition)->StartPlayingOrFinish();
1126
1127
1/2
✓ Branch 0 taken 28960 times.
✗ Branch 1 not taken.
28960 LogDebug(VB_PLAYLIST, "Exiting Playlist::Start()\n");
1128 28960 return 1;
1129 28960 }
1130
1131 /*
1132 *
1133 */
1134 802 int Playlist::StopNow(int forceStop) {
1135 802 PlaylistTransitionGuard guard;
1136
1/2
✓ Branch 0 taken 802 times.
✗ Branch 1 not taken.
802 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1137
3/4
✓ Branch 0 taken 802 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 800 times.
802 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
1138
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 lck.unlock();
1139
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 return cur->StopNow(forceStop);
1140
2/2
✓ Branch 0 taken 800 times.
✓ Branch 1 taken 2 times.
802 }
1141
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 730 times.
800 if (m_status == FPP_STATUS_IDLE) {
1142 70 return 1;
1143 }
1144
1145
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 719 times.
730 if (tl_transitionDepth > 1) {
1146 // Re-entrant stop — e.g. a "Stop Now" (or "Start Playlist At Item",
1147 // which pre-stops) command executing from inside a playlist entry's
1148 // own StartPlaying frame. Running inline would tear the playlist
1149 // stack out from under the frames above us: with inserted (nested)
1150 // playlists, SetIdle() switches the player's current playlist to the
1151 // parent and condemns levels to PL_CLEANUPS while
1152 // SwitchToInsertedPlaylist/Start frames still reference them. Queue
1153 // it; the outermost transition drains it (before any deferred start —
1154 // queue order is preserved), targeting whatever playlist is current at
1155 // that point.
1156
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 LogDebug(VB_PLAYLIST, "Playlist::StopNow(%d) re-entrant (depth %d) — deferring until current transition completes\n",
1157 forceStop, tl_transitionDepth);
1158
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 QueuePlaylistNotification([forceStop]() {
1159
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 std::shared_ptr<Playlist> cur = Player::INSTANCE.PlaylistSnapshot();
1160
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (cur) {
1161
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 cur->StopNowImpl(forceStop);
1162 }
1163 11 });
1164 11 return 1;
1165 }
1166
1/2
✓ Branch 0 taken 719 times.
✗ Branch 1 not taken.
719 return StopNowImpl(forceStop);
1167 802 }
1168
1169 // The body of StopNow(). Called from StopNow() (non-re-entrant), from the
1170 // deferred-stop drain closure, and directly by internal orchestration
1171 // (PlayImpl's stop-before-load, Process()'s invalid-section bail-out) that
1172 // must stop inline as part of its own transition.
1173 873 int Playlist::StopNowImpl(int forceStop) {
1174
1/2
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
873 LogDebug(VB_PLAYLIST, "Playlist::StopNow(%d)\n", forceStop);
1175
1176
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 873 times.
873 if (m_status == FPP_STATUS_IDLE) {
1177 ✗ return 1;
1178 }
1179
1180 873 PlaylistTransitionGuard guard;
1181
1182 // Stop background stream slots (2-5). Slot 1 is stopped by the current
1183 // entry's Stop() below, which sends the MultiSync media stop packet
1184 // BEFORE the (slow) pipeline teardown — stopping slot 1 here first would
1185 // delay that packet by ~0.5s and make remotes stop late (issue #2676).
1186
2/4
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 873 times.
✗ Branch 3 not taken.
873 StreamSlotManager::Instance().StopBackgroundSlots();
1187
1188 873 std::map<std::string, std::string> keywords;
1189
3/6
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 873 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 873 times.
✗ Branch 5 not taken.
2619 keywords["PLAYLIST_NAME"] = GetPlaylistName();
1190
4/6
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 873 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 872 times.
1746 if (CommandManager::INSTANCE.HasPreset("PLAYLIST_STOPPING_NOW")) {
1191 // Deferred — preset commands run inline and can re-enter the
1192 // playlist; fired after this stop completes (PlaylistTransitionGuard).
1193
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 QueuePlaylistNotification([keywords]() mutable {
1194
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 CommandManager::INSTANCE.TriggerPreset("PLAYLIST_STOPPING_NOW", keywords);
1195 1 });
1196 }
1197
1198
1/2
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
873 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1199 873 m_status = FPP_STATUS_STOPPING_NOW;
1200
1201
1/2
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
873 PlaylistEntryBase* entry = CurrentEntry();
1202
6/8
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 873 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 848 times.
✓ Branch 5 taken 25 times.
✓ Branch 6 taken 848 times.
✓ Branch 7 taken 25 times.
873 if (entry && entry->IsPlaying())
1203
1/2
✓ Branch 0 taken 848 times.
✗ Branch 1 not taken.
848 entry->Stop();
1204
1205 873 m_forceStop = forceStop;
1206
1/2
✓ Branch 0 taken 873 times.
✗ Branch 1 not taken.
873 SetIdle();
1207
1208 873 return 1;
1209 873 }
1210
1211 /*
1212 *
1213 */
1214 12 int Playlist::StopGracefully(int forceStop, int afterCurrentLoop) {
1215
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 LogDebug(VB_PLAYLIST, "Playlist::StopGracefully(%d, %d)\n", forceStop, afterCurrentLoop);
1216
1217 12 PlaylistTransitionGuard guard;
1218
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1219
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
1220 ✗ lck.unlock();
1221 ✗ return cur->StopGracefully(forceStop, afterCurrentLoop);
1222
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 }
1223 // Nothing to stop. Going on would set a stopping status on an idle
1224 // playlist, which nothing then clears: fppd reported "stopping
1225 // gracefully" with nothing playing.
1226
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
12 if (m_status == FPP_STATUS_IDLE) {
1227 2 return 1;
1228 }
1229
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 return StopGracefullyImpl(forceStop, afterCurrentLoop);
1230 12 }
1231
1232 // The body of StopGracefully(), minus the is-this-current check: it walks up
1233 // the m_parent chain, and the parents of an inserted playlist are never the
1234 // current one.
1235 11 int Playlist::StopGracefullyImpl(int forceStop, int afterCurrentLoop) {
1236 11 PlaylistTransitionGuard guard;
1237
7/12
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 10 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 11 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
11 if (m_status == FPP_STATUS_PLAYLIST_PAUSED && this == Player::INSTANCE.PlaylistSnapshot().get()) {
1238 ✗ Resume();
1239 }
1240
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1241
1242 11 std::map<std::string, std::string> keywords;
1243
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
22 keywords["PLAYLIST_NAME"] = m_name;
1244
1245
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9 times.
11 if (afterCurrentLoop) {
1246
4/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
4 if (CommandManager::INSTANCE.HasPreset("PLAYLIST_STOPPING_AFTER_LOOP")) {
1247
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 QueuePlaylistNotification([keywords]() mutable {
1248
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 CommandManager::INSTANCE.TriggerPreset("PLAYLIST_STOPPING_AFTER_LOOP", keywords);
1249 1 });
1250 }
1251 2 m_status = FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP;
1252 } else {
1253
4/6
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 8 times.
18 if (CommandManager::INSTANCE.HasPreset("PLAYLIST_STOPPING_GRACEFULLY")) {
1254
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 QueuePlaylistNotification([keywords]() mutable {
1255
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 CommandManager::INSTANCE.TriggerPreset("PLAYLIST_STOPPING_GRACEFULLY", keywords);
1256 1 });
1257 }
1258 9 m_status = FPP_STATUS_STOPPING_GRACEFULLY;
1259 }
1260 11 m_forceStop = forceStop;
1261
3/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 10 times.
11 if (std::shared_ptr<Playlist> par = m_parent.load()) {
1262
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 par->StopGracefullyImpl(forceStop, afterCurrentLoop);
1263 11 }
1264
1265 11 return 1;
1266 11 }
1267
1268 560 void Playlist::Pause() {
1269
2/4
✓ Branch 0 taken 560 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 560 times.
✗ Branch 3 not taken.
560 LogDebug(VB_PLAYLIST, "Playlist::Pause called on %s\n", GetPlaylistName().c_str());
1270 560 PlaylistTransitionGuard guard;
1271
1/2
✓ Branch 0 taken 560 times.
✗ Branch 1 not taken.
560 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1272
2/4
✓ Branch 0 taken 560 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 560 times.
560 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
1273 ✗ lck.unlock();
1274 ✗ return cur->Pause();
1275
1/2
✓ Branch 0 taken 560 times.
✗ Branch 1 not taken.
560 }
1276
3/4
✓ Branch 0 taken 560 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 559 times.
✓ Branch 3 taken 1 times.
560 if (IsPlaying()) {
1277
1/2
✓ Branch 0 taken 559 times.
✗ Branch 1 not taken.
559 PlaylistEntryBase* entry = CurrentEntry();
1278
6/8
✓ Branch 0 taken 559 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 559 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 408 times.
✓ Branch 5 taken 151 times.
✓ Branch 6 taken 408 times.
✓ Branch 7 taken 151 times.
559 if (entry && entry->IsPlaying()) {
1279
1/2
✓ Branch 0 taken 408 times.
✗ Branch 1 not taken.
408 entry->Pause();
1280
6/8
✓ Branch 0 taken 408 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 341 times.
✓ Branch 3 taken 67 times.
✓ Branch 4 taken 341 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 341 times.
✓ Branch 7 taken 67 times.
408 if (entry->IsPaused() && m_status != FPP_STATUS_PLAYLIST_PAUSED) {
1281 341 m_statusBeforePause = m_status;
1282 341 m_status = FPP_STATUS_PLAYLIST_PAUSED;
1283 }
1284 }
1285 }
1286
2/4
✓ Branch 0 taken 560 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 560 times.
✗ Branch 3 not taken.
560 }
1287 362 void Playlist::Resume() {
1288
2/4
✓ Branch 0 taken 362 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 362 times.
✗ Branch 3 not taken.
362 LogDebug(VB_PLAYLIST, "Playlist::Resume called on %s\n", GetPlaylistName().c_str());
1289 362 PlaylistTransitionGuard guard;
1290
1/2
✓ Branch 0 taken 362 times.
✗ Branch 1 not taken.
362 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1291
2/4
✓ Branch 0 taken 362 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 362 times.
362 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
1292 ✗ lck.unlock();
1293 ✗ return cur->Resume();
1294
1/2
✓ Branch 0 taken 362 times.
✗ Branch 1 not taken.
362 }
1295
3/4
✓ Branch 0 taken 362 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 361 times.
✓ Branch 3 taken 1 times.
362 if (IsPlaying()) {
1296 // Two ways to be paused: the playlist status, or -- for a parent that
1297 // was told to stop gracefully while paused under an "insert
1298 // immediate" child -- a STOPPING_* status over an entry that is still
1299 // paused. The second used to be unresumable: the parent sat on its
1300 // paused entry forever, never finishing the stop.
1301
1/2
✓ Branch 0 taken 361 times.
✗ Branch 1 not taken.
361 PlaylistEntryBase* entry = CurrentEntry();
1302 361 bool statusPaused = (m_status == FPP_STATUS_PLAYLIST_PAUSED);
1303
8/10
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 336 times.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 24 times.
✓ Branch 8 taken 337 times.
✓ Branch 9 taken 24 times.
361 if (statusPaused || (entry && entry->IsPaused())) {
1304
1/2
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
337 if (entry) {
1305
1/2
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
337 entry->Resume();
1306 }
1307
2/2
✓ Branch 0 taken 336 times.
✓ Branch 1 taken 1 times.
337 if (statusPaused) {
1308 // Back to what Pause() interrupted, which may be a stop.
1309 336 m_status = m_statusBeforePause;
1310 }
1311
1312 // Notify of current playlists because was likely changed when Paused.
1313
2/4
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 337 times.
✗ Branch 3 not taken.
1009 Events::Publish("playlist/name/status", m_name);
1314
4/8
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 337 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 337 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 337 times.
✗ Branch 7 not taken.
1013 Events::Publish("status", PlaylistStatusToString(m_status));
1315
2/4
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 337 times.
✗ Branch 3 not taken.
674 Events::Publish("playlist/section/status", m_currentSectionStr);
1316
2/4
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 337 times.
✗ Branch 3 not taken.
672 Events::Publish("playlist/sectionPosition/status", m_sectionPosition);
1317 {
1318
1/2
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
337 Json::Value info = GetInfo();
1319
1/2
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
337 std::string sec = m_currentSectionStr;
1320 337 int pos = m_sectionPosition;
1321
4/12
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 337 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 337 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 337 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
337 QueuePlaylistNotification([info, sec, pos]() {
1322
2/4
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 337 times.
✗ Branch 3 not taken.
674 PluginManager::INSTANCE.playlistCallback(info, "playing", sec, pos);
1323 337 });
1324 337 }
1325 }
1326 }
1327
2/4
✓ Branch 0 taken 362 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 362 times.
✗ Branch 3 not taken.
362 }
1328
1329 /*
1330 *
1331 */
1332 1384769 int Playlist::IsPlaying(void) {
1333
2/2
✓ Branch 0 taken 387890 times.
✓ Branch 1 taken 10782 times.
1783441 if ((m_status == FPP_STATUS_PLAYLIST_PLAYING) ||
1334
2/2
✓ Branch 0 taken 383668 times.
✓ Branch 1 taken 4222 times.
786562 (m_status == FPP_STATUS_STOPPING_GRACEFULLY) ||
1335
6/6
✓ Branch 0 taken 398672 times.
✓ Branch 1 taken 986097 times.
✓ Branch 2 taken 18963 times.
✓ Branch 3 taken 364705 times.
✓ Branch 4 taken 1020064 times.
✓ Branch 5 taken 364705 times.
2171331 (m_status == FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP) ||
1336 383668 (m_status == FPP_STATUS_PLAYLIST_PAUSED)) // paused is technically still "running"
1337 1020064 return 1;
1338
1339 364705 return 0;
1340 }
1341
1342 /*
1343 *
1344 */
1345 1238 int Playlist::FileHasBeenModified(void) {
1346
3/10
✓ Branch 0 taken 1238 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1238 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1238 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
3714 if ((endsWith(m_filename, ".fseq")) ||
1347
6/14
✓ Branch 0 taken 1238 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1238 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1238 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1238 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1238 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1238 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
4952 (IsExtensionAudio(GetFileExtension(m_filename))) ||
1348
5/12
✓ Branch 0 taken 1238 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1238 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1238 times.
✓ Branch 6 taken 1238 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1238 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2476 (IsExtensionVideo(GetFileExtension(m_filename)))) {
1349 ✗ return 0;
1350 }
1351
1352 struct stat attr;
1353
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1238 times.
1238 if (stat(m_filename.c_str(), &attr) != 0) {
1354 // The file is momentarily unstat-able -- a non-atomic UI save (write a
1355 // temp then rename, or a slow write) makes it briefly absent. attr is
1356 // uninitialized on failure, so comparing attr.st_mtime would read stack
1357 // garbage and fabricate a spurious "modified", triggering a reload that
1358 // wipes the running playlist. A file we can't stat right now is not a
1359 // reason to reload: report unmodified and let the next tick retry.
1360 ✗ return 0;
1361 }
1362
1363 char timeBuf[32];
1364
1/2
✓ Branch 0 taken 1238 times.
✗ Branch 1 not taken.
1238 LogDebug(VB_PLAYLIST, "Playlist Last Modified: %s\n", ctime_r(&attr.st_mtime, timeBuf));
1365
1366
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1226 times.
1238 if (attr.st_mtime > m_fileTime)
1367 12 return 1;
1368
1369 1226 return 0;
1370 }
1371
1372 // Process() used to re-enter itself on this thread: after switching to an
1373 // inserted playlist it tail-called `return pl->Process()` while the outer frame
1374 // still held its own m_playlistMutex -- and that outer instance may already be
1375 // on PL_CLEANUPS. It no longer does (see the switch sites), but draining only
1376 // in the outermost frame is what makes the PL_CLEANUPS/PL_ENTRY_CLEANUPS
1377 // promise ("freed after the stack has unwound") true, so the depth guard stays
1378 // for any path that still nests.
1379 static thread_local int tl_processDepth = 0;
1380 namespace {
1381 class ProcessDepthGuard {
1382 public:
1383 298513 ProcessDepthGuard() { tl_processDepth++; }
1384 298513 ~ProcessDepthGuard() { tl_processDepth--; }
1385 };
1386 } // namespace
1387
1388 // Release playlists condemned to PL_CLEANUPS, run the destructors the
1389 // shared_ptr deleter deferred, then free retired entries. Main thread only,
1390 // and never with a Process() frame on the stack. Process() does this at its
1391 // outermost depth; the main loop also calls it while the player is idle,
1392 // because Process() only runs while something plays -- a stop used to leave
1393 // the stopped playlist's entries (an image entry's prep thread, a media
1394 // entry's state) alive until the next show started.
1395 445847 void Playlist::ReleaseRetired(void) {
1396 {
1397 // Held across the whole condemned pass (not swap-and-release) so
1398 // SetIdle's condemned-parent walk on another thread sees either the
1399 // full pre-drain list or the post-release state, never a moment
1400 // where a condemned instance has left the list but still looks
1401 // current-able. ReleasePlaylist only nests the owner's registry
1402 // lock inside; nothing takes these two in the other order.
1403
1/2
✓ Branch 0 taken 445847 times.
✗ Branch 1 not taken.
445847 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
1404
2/2
✓ Branch 0 taken 490 times.
✓ Branch 1 taken 445357 times.
445847 if (!PL_CLEANUPS.empty()) {
1405
1/2
✓ Branch 0 taken 490 times.
✗ Branch 1 not taken.
490 PL_CLEANUPS.sort();
1406 490 PL_CLEANUPS.unique();
1407
2/2
✓ Branch 0 taken 497 times.
✓ Branch 1 taken 490 times.
987 while (!PL_CLEANUPS.empty()) {
1408 497 Playlist* p = PL_CLEANUPS.front();
1409 // Drops the player's owning reference rather than deleting.
1410 // If a reader thread still holds a snapshot of p, the
1411 // instance stays alive until that snapshot releases; its
1412 // destructor then runs on this thread in a later
1413 // DrainRetiredPlaylists().
1414
1/2
✓ Branch 0 taken 497 times.
✗ Branch 1 not taken.
497 Player::INSTANCE.ReleasePlaylist(p);
1415 497 PL_CLEANUPS.pop_front();
1416 }
1417 }
1418 445847 }
1419 // Runs the destructors the shared_ptr deleter deferred — here, between
1420 // the two lists, because ~Playlist parks its entries on
1421 // PL_ENTRY_CLEANUPS and those must be freed in the same tick, exactly
1422 // as when the drain above did the delete itself. Not under the lock:
1423 // those destructors push onto PL_ENTRY_CLEANUPS themselves.
1424 445847 Player::INSTANCE.DrainRetiredPlaylists();
1425 // Entry destructors can park further entries (nested sub-entries), so
1426 // swap-and-delete until a pass drains nothing.
1427 while (true) {
1428 447230 std::list<PlaylistEntryBase*> entries;
1429 {
1430
1/2
✓ Branch 0 taken 447230 times.
✗ Branch 1 not taken.
447230 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
1431 447230 entries.swap(PL_ENTRY_CLEANUPS);
1432 447230 }
1433
2/2
✓ Branch 0 taken 445847 times.
✓ Branch 1 taken 1383 times.
447230 if (entries.empty())
1434 445847 break;
1435
2/2
✓ Branch 0 taken 3632 times.
✓ Branch 1 taken 1383 times.
5015 for (auto* e : entries) {
1436
1/2
✓ Branch 0 taken 3632 times.
✗ Branch 1 not taken.
3632 delete e;
1437 }
1438
2/2
✓ Branch 0 taken 1383 times.
✓ Branch 1 taken 445847 times.
448613 }
1439 445847 }
1440
1441 /*
1442 *
1443 */
1444 298513 int Playlist::Process(void) {
1445
3/4
✓ Branch 0 taken 449 times.
✓ Branch 1 taken 298064 times.
✓ Branch 2 taken 449 times.
✗ Branch 3 not taken.
298513 static time_t lastCheckTime = time(nullptr);
1446 298513 time_t procTime = time(nullptr);
1447
1448 298513 PlaylistTransitionGuard guard;
1449 298513 ProcessDepthGuard depthGuard;
1450
1451 // Process any background stream slots (2-5) — fire-and-forget media on secondary streams
1452
2/4
✓ Branch 0 taken 298513 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 298513 times.
✗ Branch 3 not taken.
298513 StreamSlotManager::Instance().ProcessBackgroundSlots();
1453
1454 // Pull sync-enabled slots toward the primary media's position so extra
1455 // streams (a second language on another audio output, a second display)
1456 // stay locked to the show rather than free-running. Hooked here rather
1457 // than in ProcessMedia(), which the main loop only calls in REMOTE_MODE.
1458 float mediaSecs;
1459 {
1460 // A resume on a command thread rewrites these (PlaylistEntryMedia).
1461
1/2
✓ Branch 0 taken 298513 times.
✗ Branch 1 not taken.
298513 std::lock_guard<std::mutex> fieldsLock(MediaOutputStatus::FieldsLock());
1462 298513 mediaSecs = mediaOutputStatus.mediaSeconds;
1463 298513 }
1464
6/6
✓ Branch 0 taken 145632 times.
✓ Branch 1 taken 152881 times.
✓ Branch 2 taken 139345 times.
✓ Branch 3 taken 6287 times.
✓ Branch 4 taken 139345 times.
✓ Branch 5 taken 159168 times.
298513 if (mediaOutputStatus.status == MEDIAOUTPUTSTATUS_PLAYING && mediaSecs > 0.0f) {
1465
2/4
✓ Branch 0 taken 139345 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 139345 times.
✗ Branch 3 not taken.
139345 StreamSlotManager::Instance().SyncSlotsToMaster(mediaSecs);
1466 }
1467
1468 // LogExcess(VB_PLAYLIST, "Playlist::Process: %s, section %s, position: %d\n", m_name.c_str(), m_currentSectionStr.c_str(), m_sectionPosition);
1469
1470
1/2
✓ Branch 0 taken 298513 times.
✗ Branch 1 not taken.
298513 if (tl_processDepth == 1) {
1471
1/2
✓ Branch 0 taken 298513 times.
✗ Branch 1 not taken.
298513 ReleaseRetired();
1472 }
1473
1/2
✓ Branch 0 taken 298513 times.
✗ Branch 1 not taken.
298513 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1474
3/4
✓ Branch 0 taken 298513 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 298506 times.
298513 if (m_currentSectionStr == "New") {
1475 7 return 0;
1476 }
1477
1478
1/2
✓ Branch 0 taken 298506 times.
✗ Branch 1 not taken.
298506 UpdateCrashSnapshot();
1479
1480
3/6
✓ Branch 0 taken 298506 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 298506 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 298506 times.
298506 if (m_currentSection == nullptr || m_sectionPosition >= m_currentSection->size()) {
1481 ✗ LogErr(VB_PLAYLIST, "Section position %d is outside of section %s\n",
1482 m_sectionPosition, m_currentSectionStr.c_str());
1483 ✗ StopNowImpl();
1484 ✗ return 0;
1485 }
1486
1487 // Holding off the next pass of an instant-only repeating playlist.
1488
2/2
✓ Branch 0 taken 2708 times.
✓ Branch 1 taken 295798 times.
298506 if (m_loopHoldUntilMS) {
1489
3/4
✓ Branch 0 taken 2708 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2686 times.
✓ Branch 3 taken 22 times.
2708 if (GetMonotonicTimeMS() < m_loopHoldUntilMS) {
1490 2686 return 1;
1491 }
1492 22 m_loopHoldUntilMS = 0;
1493
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 m_loopStartMS = GetMonotonicTimeMS();
1494
3/6
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 if (m_currentSection && m_sectionPosition < m_currentSection->size()) {
1495
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 StartPlayingWithGlobalPause(m_currentSection->at(m_sectionPosition));
1496 }
1497 22 return 1;
1498 }
1499
1500 // Handle global pause between sequences
1501
2/2
✓ Branch 0 taken 1007 times.
✓ Branch 1 taken 294791 times.
295798 if (m_isInGlobalPause) {
1502
1/2
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
1007 long long currentTime = GetMonotonicTimeMS();
1503
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1002 times.
1007 if ((currentTime - m_globalPauseStartTime) >= m_globalPauseBetweenSequencesMS) {
1504
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 LogDebug(VB_PLAYLIST, "Global pause between sequences completed, starting next item\n");
1505 5 m_isInGlobalPause = false;
1506 // Start the current item (which was delayed by the pause)
1507
3/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 if (m_currentSection && m_sectionPosition < m_currentSection->size()) {
1508
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 m_currentSection->at(m_sectionPosition)->StartPlayingOrFinish();
1509 }
1510 } else {
1511 // Still in pause period, don't process anything
1512 1002 return 1;
1513 }
1514 }
1515
1516
10/14
✓ Branch 0 taken 294796 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 294796 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 291487 times.
✓ Branch 5 taken 3309 times.
✓ Branch 6 taken 291487 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 291487 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 291426 times.
✓ Branch 11 taken 61 times.
✓ Branch 12 taken 291426 times.
✓ Branch 13 taken 3370 times.
294796 if (!m_currentSection->at(m_sectionPosition)->IsPaused() && m_currentSection->at(m_sectionPosition)->IsPlaying()) {
1517
2/4
✓ Branch 0 taken 291426 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 291426 times.
✗ Branch 3 not taken.
291426 m_currentSection->at(m_sectionPosition)->Process();
1518 }
1519
1520 294796 Playlist* pl = nullptr;
1521
8/10
✓ Branch 0 taken 294796 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 294796 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3309 times.
✓ Branch 5 taken 291487 times.
✓ Branch 6 taken 326 times.
✓ Branch 7 taken 2983 times.
✓ Branch 8 taken 326 times.
✓ Branch 9 taken 294470 times.
294796 if (m_currentSection->at(m_sectionPosition)->IsPaused() && !m_insertedPlaylist.empty()) {
1522
2/4
✓ Branch 0 taken 326 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 326 times.
326 if ((pl = SwitchToInsertedPlaylist()) == nullptr) {
1523 // The insert was refused (nesting limit) or failed to start. A
1524 // failed start already resumed us from the child's SetIdle; a
1525 // refusal didn't, and we'd sit paused under nothing. Resume() is a
1526 // no-op if we're not paused.
1527 ✗ if (Player::INSTANCE.PlaylistSnapshot().get() == this) {
1528 ✗ Resume();
1529 }
1530 ✗ return 1;
1531 }
1532 // SwitchToInsertedPlaylist() already started pl (via PlayImpl) and made
1533 // it current; the next tick processes it. Never process the child from
1534 // here: this frame holds our lock, and a thread stopping the child
1535 // takes the child's lock and then ours (StopGracefully forwards to
1536 // m_parent, SetIdle resumes it) -- taking pl's lock here deadlocked
1537 // against that. Processing it inline also recursed once per level of
1538 // a playlist that inserts itself.
1539 326 return 1;
1540 }
1541
1542
4/6
✓ Branch 0 taken 294470 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 294470 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4270 times.
✓ Branch 5 taken 290200 times.
294470 if (m_currentSection->at(m_sectionPosition)->IsFinished()) {
1543
1/2
✓ Branch 0 taken 4270 times.
✗ Branch 1 not taken.
4270 LogDebug(VB_PLAYLIST, "Playlist entry finished\n");
1544
3/4
✓ Branch 0 taken 4270 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4268 times.
4270 if (WillLog(LOG_DEBUG, VB_PLAYLIST))
1545
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 m_currentSection->at(m_sectionPosition)->Dump();
1546
1547
1/2
✓ Branch 0 taken 4270 times.
✗ Branch 1 not taken.
4270 LogDebug(VB_PLAYLIST, "============================================================================\n");
1548
1549 // Check if we should start a global pause before the next item
1550
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4261 times.
4270 if (m_globalPauseBetweenSequencesMS > 0) {
1551
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 bool isStoppingGracefully = (m_status == FPP_STATUS_STOPPING_GRACEFULLY ||
1552 9 m_status == FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP);
1553
1554
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (!isStoppingGracefully) {
1555
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 LogDebug(VB_PLAYLIST, "Marking for global pause between sequences for %d ms\n", m_globalPauseBetweenSequencesMS);
1556 9 m_shouldStartGlobalPause = true;
1557 }
1558 }
1559
1560 // "query_next" stays SYNCHRONOUS by design: it is the decision-point
1561 // hook plugins use to insert the next item (InsertPlaylistAsNext)
1562 // before the SwitchToInsertedPlaylist checks just below — deferring
1563 // it would make the insertion miss this cycle. Re-entrant Play()
1564 // from the callback is safe now (Play() defers it), which was the
1565 // only hazard of calling it inline.
1566
3/6
✓ Branch 0 taken 4270 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4270 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4270 times.
✗ Branch 5 not taken.
8577 PluginManager::INSTANCE.playlistCallback(GetInfo(), "query_next", m_currentSectionStr, m_sectionPosition);
1567
1568
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 4259 times.
4270 if (m_status == FPP_STATUS_STOPPING_GRACEFULLY) {
1569
4/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 2 times.
22 if ((m_currentSectionStr == "LeadIn") ||
1570
3/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 2 times.
11 (m_currentSectionStr == "MainPlaylist")) {
1571
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 ReloadIfNeeded();
1572
1573
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9 if (m_leadOut.size()) {
1574
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogDebug(VB_PLAYLIST, "Stopping Gracefully\n");
1575
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 SwitchToLeadOut();
1576 } else {
1577
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 SetIdle();
1578 }
1579 9 return 1;
1580 }
1581 }
1582
6/8
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4253 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✓ Branch 7 taken 4253 times.
4261 if (m_stopAtPos != -1 && m_stopAtPos <= (GetPosition() - 1)) {
1583
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 if ((pl = SwitchToInsertedPlaylist(true)) != nullptr) {
1584 ✗ return 1; // next tick processes pl; see the paused case above
1585 }
1586
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 if (Player::INSTANCE.PlaylistSnapshot().get() != this) {
1587 // The insert failed to start and handed the player back to our
1588 // parent, condemning us; nothing here is ours to run any more.
1589 ✗ return 1;
1590 }
1591
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 LogDebug(VB_PLAYLIST, "Stopping after end position\n");
1592
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 SetIdle();
1593 8 return 1;
1594 }
1595
4/6
✓ Branch 0 taken 4253 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4253 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 172 times.
✓ Branch 5 taken 4081 times.
4253 if ((pl = SwitchToInsertedPlaylist(WillStopAfterCurrent())) != nullptr) {
1596 172 return 1; // next tick processes pl; see the paused case above
1597 }
1598
2/4
✓ Branch 0 taken 4081 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4081 times.
4081 if (Player::INSTANCE.PlaylistSnapshot().get() != this) {
1599 ✗ return 1; // condemned by a failed insert; see above
1600 }
1601
1602
1/2
✓ Branch 0 taken 4081 times.
✗ Branch 1 not taken.
4081 auto currentEntry = m_currentSection->at(m_sectionPosition);
1603
3/4
✓ Branch 0 taken 4081 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 4070 times.
4081 if (currentEntry->GetNextBranchType() == PlaylistEntryBase::PlaylistBranchType::Index) {
1604
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
11 if (currentEntry->GetNextSection() != "") {
1605
2/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
11 LogDebug(VB_PLAYLIST, "Attempting Switch to %s section.\n",
1606 currentEntry->GetNextSection().c_str());
1607
1608
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
11 if (currentEntry->GetNextSection() == "leadIn") {
1609 ✗ m_currentSectionStr = "LeadIn";
1610 ✗ m_currentSection = &m_leadIn;
1611
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
11 } else if (currentEntry->GetNextSection() == "leadOut") {
1612 ✗ m_currentSectionStr = "LeadOut";
1613 ✗ m_currentSection = &m_leadOut;
1614 } else {
1615
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 m_currentSectionStr = "MainPlaylist";
1616 11 m_currentSection = &m_mainPlaylist;
1617 }
1618
1619 // A Branch's item number is a row of the playlist as the
1620 // editor shows it; map it through the row table, as a
1621 // sub-playlist row expands to several entries.
1622
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 const std::vector<int>& rows = CurrentSectionRows();
1623
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 int rowCount = rows.empty() ? (int)m_currentSection->size() : (int)rows.size();
1624
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 int item = currentEntry->GetNextItem();
1625
2/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
11 if ((item >= 0) && (item < rowCount)) {
1626
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 m_sectionPosition = RowToSectionPosition(rows, item, m_currentSection->size());
1627 } else {
1628 ✗ m_sectionPosition = 0;
1629 }
1630 ✗ } else if (currentEntry->GetNextItem() != -1) {
1631 ✗ m_sectionPosition = RowToSectionPosition(CurrentSectionRows(), currentEntry->GetNextItem(), m_currentSection->size());
1632 } else {
1633 ✗ m_sectionPosition++;
1634 }
1635
3/4
✓ Branch 0 taken 4070 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4068 times.
4070 } else if (currentEntry->GetNextBranchType() == PlaylistEntryBase::PlaylistBranchType::Offset) {
1636 // Offsets count rows too (see the Index case above).
1637
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const std::vector<int>& rows = CurrentSectionRows();
1638
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 int nextRow = SectionPositionToRow(rows, m_sectionPosition) + currentEntry->GetNextItem();
1639
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogDebug(VB_PLAYLIST, "Attempting Offset Branch to section row %d\n", nextRow);
1640
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (nextRow < 0) {
1641 ✗ LogDebug(VB_PLAYLIST, "New position negative, branching to first position in section\n");
1642 ✗ m_sectionPosition = 0;
1643 } else {
1644
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 m_sectionPosition = RowToSectionPosition(rows, nextRow, m_currentSection->size());
1645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (m_sectionPosition >= m_currentSection->size())
1646 ✗ LogDebug(VB_PLAYLIST, "Offset outside current section, ending section\n");
1647 }
1648
2/4
✓ Branch 0 taken 4068 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4068 times.
4068 } else if (currentEntry->GetNextBranchType() == PlaylistEntryBase::PlaylistBranchType::Playlist) {
1649 // handled internally to PlaylistEntryBranch
1650 ✗ m_sectionPosition++;
1651 } else {
1652 4068 m_sectionPosition++;
1653 }
1654
1655
2/2
✓ Branch 0 taken 1692 times.
✓ Branch 1 taken 2389 times.
4081 if (m_sectionPosition >= m_currentSection->size()) {
1656
3/4
✓ Branch 0 taken 1692 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 466 times.
✓ Branch 3 taken 1226 times.
1692 if (m_currentSectionStr == "LeadIn") {
1657
1/2
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
466 LogDebug(VB_PLAYLIST, "At end of leadIn.\n");
1658
1659
1/2
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
466 ReloadIfNeeded();
1660
1661
1/2
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
466 if (m_mainPlaylist.size()) {
1662
1/2
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
466 SwitchToMainPlaylist();
1663 ✗ } else if (m_leadOut.size()) {
1664 ✗ SwitchToLeadOut();
1665 } else {
1666 ✗ LogDebug(VB_PLAYLIST, "No more playlist entries, switching to idle.\n");
1667 ✗ SetIdle();
1668 }
1669
3/4
✓ Branch 0 taken 1226 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1219 times.
✓ Branch 3 taken 7 times.
1226 } else if (m_currentSectionStr == "MainPlaylist") {
1670 1219 m_loop++;
1671
1/2
✓ Branch 0 taken 1219 times.
✗ Branch 1 not taken.
2438 LogDebug(VB_PLAYLIST, "mainPlaylist loop now: %d\n", m_loop.load());
1672
8/8
✓ Branch 0 taken 764 times.
✓ Branch 1 taken 455 times.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 741 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 757 times.
✓ Branch 7 taken 462 times.
1219 if ((m_repeat) && (!m_loopCount || (m_loop < m_loopCount))) {
1673
1/2
✓ Branch 0 taken 757 times.
✗ Branch 1 not taken.
757 ReloadIfNeeded();
1674
1675
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 756 times.
757 if (m_status == FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP) {
1676
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_leadOut.size()) {
1677
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_PLAYLIST, "Stopping Gracefully after loop\n");
1678
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 SwitchToLeadOut();
1679 } else {
1680 ✗ LogDebug(VB_PLAYLIST, "Stopping Gracefully after loop. Empty leadOut, setting to Idle state\n");
1681 ✗ SetIdle();
1682 }
1683
1684 1 return 1;
1685 }
1686
1687
2/2
✓ Branch 0 taken 740 times.
✓ Branch 1 taken 16 times.
756 if (!m_loopCount)
1688
1/2
✓ Branch 0 taken 740 times.
✗ Branch 1 not taken.
740 LogDebug(VB_PLAYLIST, "mainPlaylist repeating for another loop, loopCount == 0\n");
1689 else
1690
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
32 LogDebug(VB_PLAYLIST, "mainPlaylist repeating for another loop, %d <= %d\n", m_loop.load(), m_loopCount);
1691
1692 // A pass this short has nothing in it that takes time;
1693 // repeated as-is it would spin the main loop. Hold the next
1694 // pass until a second after this one began. Any pass with a
1695 // real entry in it (a sequence, a pause, media) is far
1696 // longer and unaffected. Only here, where another pass
1697 // starts: Process() restarts whatever entry is current when
1698 // the hold runs out, so a hold left set on the way to the
1699 // lead-out played its first entry twice.
1700 static constexpr long long MIN_INSTANT_PASS_MS = 50;
1701 static constexpr long long INSTANT_PASS_PERIOD_MS = 1000;
1702
1/2
✓ Branch 0 taken 756 times.
✗ Branch 1 not taken.
756 long long passMS = GetMonotonicTimeMS() - m_loopStartMS;
1703
4/4
✓ Branch 0 taken 732 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 36 times.
✓ Branch 3 taken 696 times.
756 if (m_loopStartMS && passMS < MIN_INSTANT_PASS_MS) {
1704 36 m_loopHoldUntilMS = m_loopStartMS + INSTANT_PASS_PERIOD_MS;
1705
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 27 times.
36 if (!m_warnedInstantLoop) {
1706 9 m_warnedInstantLoop = true;
1707
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 LogWarn(VB_PLAYLIST, "Playlist '%s' repeats with nothing in it that takes time; limiting it to one pass a second\n",
1708 m_name.c_str());
1709 }
1710 }
1711
1712
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 754 times.
756 if (m_random == 2) {
1713
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 RandomizeMainPlaylist();
1714 }
1715
1716 // Goes through SwitchToMainPlaylist() for its empty-section
1717 // guard; we are already in the MainPlaylist section, so the
1718 // section/position it sets are the values this branch wants.
1719 // ReloadIfNeeded() above may have emptied the section.
1720
1/2
✓ Branch 0 taken 756 times.
✗ Branch 1 not taken.
756 SwitchToMainPlaylist();
1721
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 456 times.
462 } else if (m_leadOut.size()) {
1722
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 ReloadIfNeeded();
1723
1724
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 SwitchToLeadOut();
1725 } else {
1726
1/2
✓ Branch 0 taken 456 times.
✗ Branch 1 not taken.
456 LogDebug(VB_PLAYLIST, "No more playlist entries, switching to idle.\n");
1727
1/2
✓ Branch 0 taken 456 times.
✗ Branch 1 not taken.
456 SetIdle();
1728 }
1729 } else {
1730
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 LogDebug(VB_PLAYLIST, "No more playlist entries, switching to idle.\n");
1731
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 SetIdle();
1732 }
1733 } else {
1734 // Start the next item in the current section
1735
2/4
✓ Branch 0 taken 2389 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2389 times.
✗ Branch 3 not taken.
2389 StartPlayingWithGlobalPause(m_currentSection->at(m_sectionPosition));
1736 }
1737
1738 // NOTE: deferred "Start Playlist" requests recorded by re-entrant
1739 // Play() calls are replayed by the outermost PlaylistTransitionGuard
1740 // when this Process() frame unwinds — the drain loop that used to
1741 // live here moved there so it also covers requests deferred during
1742 // idle transitions (Process() stops running once the player goes
1743 // idle, so a drain only here could strand them).
1744
1745 // Only if we are still playing. Several of the branches above end the
1746 // playlist via SetIdle(), which queues the "stop" callback and resets
1747 // the section to "New" - falling through to here then told every plugin
1748 // "playing" immediately after "stop", for a section that isn't running.
1749 // Plugins that key powering hardware up off "playing" turned it right
1750 // back on at the end of a show.
1751
2/2
✓ Branch 0 taken 3614 times.
✓ Branch 1 taken 466 times.
4080 if (m_status != FPP_STATUS_IDLE) {
1752 {
1753
1/2
✓ Branch 0 taken 3614 times.
✗ Branch 1 not taken.
3614 Json::Value info = GetInfo();
1754
1/2
✓ Branch 0 taken 3614 times.
✗ Branch 1 not taken.
3614 std::string sec = m_currentSectionStr;
1755 3614 int pos = m_sectionPosition;
1756
4/12
✓ Branch 0 taken 3614 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3614 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3614 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3614 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
3614 QueuePlaylistNotification([info, sec, pos]() {
1757
2/4
✓ Branch 0 taken 3614 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3614 times.
✗ Branch 3 not taken.
7228 PluginManager::INSTANCE.playlistCallback(info, "playing", sec, pos);
1758 3614 });
1759 3614 }
1760
2/4
✓ Branch 0 taken 3614 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3614 times.
✗ Branch 3 not taken.
7228 Events::Publish("playlist/section/status", m_currentSectionStr);
1761
2/4
✓ Branch 0 taken 3614 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3614 times.
✗ Branch 3 not taken.
10825 Events::Publish("playlist/sectionPosition/status", m_sectionPosition);
1762 }
1763 }
1764
1765 294280 return 1;
1766 298513 }
1767
1768 // Deepest stack of inserted playlists SwitchToInsertedPlaylist will build,
1769 // counting the resident playlist at the bottom.
1770 static constexpr int MAX_INSERTED_PLAYLIST_DEPTH = 10;
1771
1772 // True when this playlist ends once the current entry finishes -- i.e. an
1773 // inserted playlist can take our place instead of nesting under us. Both
1774 // section branches used to be inverted (true exactly when more was left to
1775 // play): an insert queued near the end of a nested playlist dropped its
1776 // remaining sections, while a playlist that really was ending nested the
1777 // insert one level deeper every time -- without bound for a playlist that
1778 // branches back to itself.
1779 4253 bool Playlist::WillStopAfterCurrent() {
1780
2/2
✓ Branch 0 taken 2397 times.
✓ Branch 1 taken 1856 times.
4253 if ((m_sectionPosition + 1) < m_currentSection->size()) {
1781 2397 return false;
1782 }
1783 1856 PlaylistEntryBase* entry = CurrentEntry();
1784
3/6
✓ Branch 0 taken 1856 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1856 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1856 times.
3712 if (entry && (entry->GetNextBranchType() == PlaylistEntryBase::PlaylistBranchType::Index ||
1785
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1856 times.
1856 entry->GetNextBranchType() == PlaylistEntryBase::PlaylistBranchType::Offset)) {
1786 // A last-entry branch can jump back into the playlist.
1787 ✗ return false;
1788 }
1789
2/2
✓ Branch 0 taken 479 times.
✓ Branch 1 taken 1377 times.
1856 if (m_currentSectionStr == "LeadIn") {
1790
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 479 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
479 return m_mainPlaylist.empty() && m_leadOut.empty();
1791
2/2
✓ Branch 0 taken 1370 times.
✓ Branch 1 taken 7 times.
1377 } else if (m_currentSectionStr == "MainPlaylist") {
1792 // Same test Process() applies once m_loop has been incremented.
1793
8/8
✓ Branch 0 taken 764 times.
✓ Branch 1 taken 606 times.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 741 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 756 times.
✓ Branch 7 taken 1 times.
2127 bool loopsAgain = m_repeat && (!m_loopCount || ((m_loop + 1) < m_loopCount)) &&
1794 757 (m_status != FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP);
1795
4/4
✓ Branch 0 taken 614 times.
✓ Branch 1 taken 756 times.
✓ Branch 2 taken 606 times.
✓ Branch 3 taken 8 times.
1370 return !loopsAgain && m_leadOut.empty();
1796 }
1797 7 return true;
1798 }
1799
1800 4587 Playlist* Playlist::SwitchToInsertedPlaylist(bool isStopping) {
1801
2/2
✓ Branch 0 taken 498 times.
✓ Branch 1 taken 4089 times.
4587 if (m_insertedPlaylist != "") {
1802 // We are exiting, so there is no point wasting our memory on the stack
1803 // of playlists: the new playlist takes our place by inheriting our
1804 // parent, and we clean ourselves up once it is known to be running.
1805
7/10
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 348 times.
✓ Branch 2 taken 150 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 148 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 150 times.
✓ Branch 7 taken 348 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
498 bool replaceSelf = isStopping && (m_parent.load() != nullptr);
1806 // The new playlist's parent: ours if it replaces us, else us.
1807
5/8
✓ Branch 0 taken 148 times.
✓ Branch 1 taken 350 times.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 350 times.
✓ Branch 5 taken 148 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
498 std::shared_ptr<Playlist> newParent = replaceSelf ? m_parent.load() : weak_from_this().lock();
1808
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 498 times.
498 if (!newParent) {
1809 // Only an instance the player owns is processed, so this can't
1810 // happen; without an owning reference to link to, don't insert.
1811 ✗ LogErr(VB_PLAYLIST, "Not inserting playlist '%s': the playlist it interrupts is not owned by the player\n",
1812 m_insertedPlaylist.c_str());
1813 ✗ m_insertedPlaylist = "";
1814 ✗ return nullptr;
1815 }
1816
1817 // Bound the stack of inserted playlists. Inline sub-playlists stop at
1818 // 5 levels in LoadJSONIntoPlaylist, but inserts (Insert Playlist
1819 // commands, Branch-to-playlist) had no limit: a playlist that branches
1820 // to itself, or A -> B -> A, nested one level per pass until memory or
1821 // the stack ran out.
1822 498 int depth = 1;
1823
3/4
✓ Branch 0 taken 526 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 526 times.
✓ Branch 3 taken 498 times.
1024 for (std::shared_ptr<Playlist> p = newParent; p; p = p->m_parent.load()) {
1824 526 depth++;
1825 498 }
1826
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 498 times.
498 if (depth > MAX_INSERTED_PLAYLIST_DEPTH) {
1827 ✗ LogErr(VB_PLAYLIST, "Not inserting playlist '%s': inserted playlists are already nested %d deep (does a playlist insert or branch to itself?)\n",
1828 m_insertedPlaylist.c_str(), depth - 1);
1829 ✗ WarningHolder::AddWarningTimeout(60, 24, "Recursive playlist: too many nested inserts trying to start '" + m_insertedPlaylist + "'");
1830 ✗ m_insertedPlaylist = "";
1831 ✗ return nullptr;
1832 }
1833
1834 // The player owns the new instance from birth; plRef only keeps it
1835 // alive across the start attempt below, before it is published.
1836
1/2
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
498 std::shared_ptr<Playlist> plRef = Player::INSTANCE.CreatePlaylist(newParent);
1837 498 Playlist* pl = plRef.get();
1838
1/2
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
498 std::string plname = m_insertedPlaylist;
1839 // PlayImpl, not Play: this runs inside the parent's transition
1840 // (depth > 1) and the child MUST start inline as part of the switch —
1841 // the public Play() would defer it.
1842
2/4
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 498 times.
✗ Branch 3 not taken.
996 pl->PlayImpl(m_insertedPlaylist.c_str(), m_insertedPlaylistPosition, 0, m_scheduleEntry, m_insertedPlaylistEndPosition);
1843
1/2
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
498 m_insertedPlaylist = "";
1844 // An instance is condemned to PL_CLEANUPS only once it is no longer
1845 // the current playlist — the drain drops the owning reference
1846 // unconditionally, so condemning the instance the player is still
1847 // running would leave nothing owning it. That is why the cleanup of
1848 // `this` waits until the replacement is known to be playing.
1849
2/4
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 498 times.
✗ Branch 3 not taken.
498 if (pl->IsPlaying()) {
1850
1/2
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
498 LogDebug(VB_PLAYLIST, "Switching to inserted playlist '%s'\n", plname.c_str());
1851
1/2
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
498 Player::INSTANCE.SetCurrentPlaylist(pl);
1852
2/2
✓ Branch 0 taken 148 times.
✓ Branch 1 taken 350 times.
498 if (replaceSelf) {
1853
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 m_parent = nullptr;
1854
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
1855
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 PL_CLEANUPS.push_back(this);
1856 148 }
1857 498 return pl;
1858 }
1859 {
1860 ✗ std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
1861 ✗ PL_CLEANUPS.push_back(pl);
1862 ✗ }
1863 ✗ if (replaceSelf && Player::INSTANCE.PlaylistSnapshot().get() != this) {
1864 // pl failed to start and its own SetIdle() already handed the
1865 // player on to our parent, so nothing will process us again.
1866 ✗ m_parent = nullptr;
1867 ✗ std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
1868 ✗ PL_CLEANUPS.push_back(this);
1869 ✗ }
1870
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 498 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 498 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 498 times.
1494 }
1871 4089 return nullptr;
1872 }
1873
1874 /*
1875 *
1876 */
1877 11389 void Playlist::ProcessMedia(void) {
1878
1/2
✓ Branch 0 taken 11389 times.
✗ Branch 1 not taken.
11389 std::unique_lock<std::mutex> lock(mediaOutputLock);
1879
1/2
✓ Branch 0 taken 11389 times.
✗ Branch 1 not taken.
11389 if (mediaOutput) {
1880
1/2
✓ Branch 0 taken 11389 times.
✗ Branch 1 not taken.
11389 mediaOutput->Process();
1881 }
1882 11389 }
1883
1884 /*
1885 *
1886 */
1887 3241 void Playlist::SetIdle(bool exit) {
1888 3241 PlaylistTransitionGuard guard;
1889 3241 m_status = FPP_STATUS_IDLE;
1890
1891 3241 std::map<std::string, std::string> keywords;
1892
3/4
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1354 times.
✓ Branch 3 taken 1887 times.
3241 if (m_name != "") {
1893
3/6
✓ Branch 0 taken 1354 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1354 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1354 times.
✗ Branch 5 not taken.
4062 keywords["PLAYLIST_NAME"] = m_name;
1894 }
1895
1/2
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
3241 Cleanup();
1896 // An insert still waiting for the current item to end goes with the
1897 // playlist it was for. Not in Cleanup(), which a reload also runs.
1898
1/2
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
3241 m_insertedPlaylist = "";
1899
1900 3241 bool publishIdle = true;
1901 // Walk up past parents that are already condemned to PL_CLEANUPS — making
1902 // one current again would resurrect an object the next Process() tick
1903 // releases (leaving the player pointing at a corpse), or double-play it. A
1904 // parent can be condemned while we're exiting when a re-entrant stop tore
1905 // down an intermediate level of an inserted-playlist stack.
1906 // par is held, not borrowed: another thread can condemn it once the walk
1907 // lets go of the lock (a second Start Playlist that already sees it as
1908 // current), and the main loop then releases it while this hand-back is
1909 // still resuming it and reading its status.
1910
1/2
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
3241 std::shared_ptr<Playlist> par = m_parent.load();
1911 {
1912
1/2
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
3241 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
1913
5/8
✓ Branch 0 taken 349 times.
✓ Branch 1 taken 2892 times.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 349 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3241 times.
3241 while (par && std::find(PL_CLEANUPS.begin(), PL_CLEANUPS.end(), par.get()) != PL_CLEANUPS.end()) {
1914 ✗ par = par->m_parent.load();
1915 }
1916 3241 }
1917
3/4
✓ Branch 0 taken 349 times.
✓ Branch 1 taken 2892 times.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
3241 bool handingBack = par && exit;
1918
1919 // Both deferred (see PlaylistTransitionGuard): a PLAYLIST_STOPPED preset
1920 // that starts another playlist used to run here, mid-SetIdle — the rest
1921 // of this function then continued tearing down state the preset's Play()
1922 // had just replaced, and the trailing "idle" publishes lied about the
1923 // actual (playing) state. The "stop" callback snapshot keeps the OLD
1924 // playlist's info, which is what the plugin is being told stopped.
1925
2/2
✓ Branch 0 taken 1354 times.
✓ Branch 1 taken 1887 times.
3241 if (!keywords.empty()) {
1926
4/6
✓ Branch 0 taken 1354 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1354 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 1342 times.
2708 if (CommandManager::INSTANCE.HasPreset("PLAYLIST_STOPPED")) {
1927
3/8
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
12 QueuePlaylistNotification([keywords, handingBack]() mutable {
1928 12 HandedBackStoppedPresetScope scope(handingBack);
1929
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
24 CommandManager::INSTANCE.TriggerPreset("PLAYLIST_STOPPED", keywords);
1930 12 });
1931 }
1932 }
1933
1934 {
1935
1/2
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
3241 Json::Value info = GetInfo();
1936
1/2
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
3241 std::string sec = m_currentSectionStr;
1937 3241 int pos = m_sectionPosition;
1938
4/12
✓ Branch 0 taken 3241 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3241 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3241 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3241 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
3241 QueuePlaylistNotification([info, sec, pos]() {
1939
2/4
✓ Branch 0 taken 3240 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3238 times.
✗ Branch 3 not taken.
6480 PluginManager::INSTANCE.playlistCallback(info, "stop", sec, pos);
1940 3238 });
1941 3241 }
1942
1943
2/2
✓ Branch 0 taken 349 times.
✓ Branch 1 taken 2892 times.
3241 if (handingBack) {
1944 // par is still owned: it is on the parent chain and not condemned, so
1945 // the player has never released it.
1946
1/2
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
349 Player::INSTANCE.SetCurrentPlaylist(par.get());
1947 // Unconditional: Resume() is a no-op unless par is paused, and par
1948 // may be STOPPING_* over a paused entry (stopped while we played).
1949
1/2
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
349 par->Resume();
1950 // Resume() only republishes when par was paused (Insert Immediate).
1951 // After an Insert After Current par never paused, so the MQTT name and
1952 // status topics would still describe the finished insert.
1953
2/4
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
349 if (par->IsPlaying()) {
1954
2/4
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
1045 Events::Publish("playlist/name/status", par->m_name);
1955
4/8
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 349 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 349 times.
✗ Branch 7 not taken.
1049 Events::Publish("status", PlaylistStatusToString(par->m_status));
1956
2/4
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
698 Events::Publish("playlist/section/status", par->m_currentSectionStr);
1957
2/4
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
1045 Events::Publish("playlist/sectionPosition/status", par->m_sectionPosition);
1958 }
1959 {
1960
1/2
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
349 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
1961
1/2
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
349 PL_CLEANUPS.push_back(this);
1962 349 }
1963 // Retired: a thread still holding a snapshot of this level must not
1964 // reach the parent (released on a later tick) through us.
1965
1/2
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
349 m_parent = nullptr;
1966
1967
2/4
✓ Branch 0 taken 349 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
349 if (par->getPlaylistStatus() != FPP_STATUS_IDLE)
1968 349 publishIdle = false;
1969
2/2
✓ Branch 0 taken 1005 times.
✓ Branch 1 taken 1887 times.
2892 } else if (exit) {
1970
2/4
✓ Branch 0 taken 1005 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1005 times.
1005 if (m_parent.load()) {
1971 // Whole remaining chain was condemned — we are still the current
1972 // playlist, so we must NOT be condemned too (the PL_CLEANUPS drain
1973 // releases unconditionally, and the player's own reference is the
1974 // only thing that would be left holding us). We become the
1975 // resident idle playlist, like any top-level SetIdle; just drop
1976 // the dangling pointer into the condemned chain.
1977 ✗ m_parent = nullptr;
1978 }
1979
1/2
✓ Branch 0 taken 1005 times.
✗ Branch 1 not taken.
1005 sequence->SendBlankingData();
1980 }
1981
1982
2/2
✓ Branch 0 taken 2892 times.
✓ Branch 1 taken 349 times.
3241 if (publishIdle) {
1983
3/6
✓ Branch 0 taken 2892 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2892 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2892 times.
✗ Branch 5 not taken.
11568 Events::Publish("status", "idle");
1984
3/6
✓ Branch 0 taken 2892 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2892 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2892 times.
✗ Branch 5 not taken.
11568 Events::Publish("playlist/name/status", "");
1985
3/6
✓ Branch 0 taken 2892 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2892 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2892 times.
✗ Branch 5 not taken.
11568 Events::Publish("playlist/section/status", "");
1986
2/4
✓ Branch 0 taken 2892 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2892 times.
✗ Branch 3 not taken.
5784 Events::Publish("playlist/sectionPosition/status", 0);
1987
2/4
✓ Branch 0 taken 2892 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2892 times.
✗ Branch 3 not taken.
5784 Events::Publish("playlist/repeat/status", 0);
1988 }
1989 3241 }
1990
1991 /*
1992 *
1993 */
1994 560 int Playlist::CleanupIfIdle(void) {
1995
1/2
✓ Branch 0 taken 560 times.
✗ Branch 1 not taken.
560 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
1996
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 559 times.
560 if (m_status != FPP_STATUS_IDLE)
1997 1 return 0;
1998
1/2
✓ Branch 0 taken 559 times.
✗ Branch 1 not taken.
559 return Cleanup();
1999 560 }
2000
2001 5996 int Playlist::Cleanup(void) {
2002
1/2
✓ Branch 0 taken 5996 times.
✗ Branch 1 not taken.
5996 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2003
2004 {
2005
1/2
✓ Branch 0 taken 5996 times.
✗ Branch 1 not taken.
5996 std::lock_guard<std::mutex> nl(m_nameLock);
2006
1/2
✓ Branch 0 taken 5996 times.
✗ Branch 1 not taken.
5996 m_name = "";
2007 5996 }
2008
1/2
✓ Branch 0 taken 5996 times.
✗ Branch 1 not taken.
5996 m_desc = "";
2009 5996 m_generatedOnTheFly = false;
2010
1/2
✓ Branch 0 taken 5996 times.
✗ Branch 1 not taken.
5996 m_currentSectionStr = "New";
2011 5996 m_currentSection = nullptr;
2012 5996 m_startPosition = 0;
2013 5996 m_sectionPosition = 0;
2014 5996 m_repeat = 0;
2015 5996 m_loopCount = 0;
2016 5996 m_startTime = 0;
2017 // A stop during the gap between sequences left these set, and the next
2018 // playlist's first Process() "ended the pause" by starting its first
2019 // entry a second time (media restarted, command entries ran twice).
2020 5996 m_isInGlobalPause = false;
2021 5996 m_shouldStartGlobalPause = false;
2022 5996 m_loopHoldUntilMS = 0;
2023 // Here, not in Start(): navigation restarts in place through Start().
2024 5996 m_warnedInstantLoop = false;
2025 5996 m_leadInRows.clear();
2026 5996 m_mainRows.clear();
2027 5996 m_leadOutRows.clear();
2028
2029
2/2
✓ Branch 0 taken 582 times.
✓ Branch 1 taken 5996 times.
6578 while (m_leadIn.size()) {
2030 582 PlaylistEntryBase* entry = m_leadIn.back();
2031 582 m_leadIn.pop_back();
2032 {
2033
1/2
✓ Branch 0 taken 582 times.
✗ Branch 1 not taken.
582 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
2034
1/2
✓ Branch 0 taken 582 times.
✗ Branch 1 not taken.
582 PL_ENTRY_CLEANUPS.push_back(entry);
2035 582 }
2036 }
2037
2038
2/2
✓ Branch 0 taken 3166 times.
✓ Branch 1 taken 5996 times.
9162 while (m_mainPlaylist.size()) {
2039 3166 PlaylistEntryBase* entry = m_mainPlaylist.back();
2040 3166 m_mainPlaylist.pop_back();
2041 {
2042
1/2
✓ Branch 0 taken 3166 times.
✗ Branch 1 not taken.
3166 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
2043
1/2
✓ Branch 0 taken 3166 times.
✗ Branch 1 not taken.
3166 PL_ENTRY_CLEANUPS.push_back(entry);
2044 3166 }
2045 }
2046
2047
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 5996 times.
6011 while (m_leadOut.size()) {
2048 15 PlaylistEntryBase* entry = m_leadOut.back();
2049 15 m_leadOut.pop_back();
2050 {
2051
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 std::lock_guard<std::mutex> lk(PL_CLEANUPS_LOCK);
2052
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 PL_ENTRY_CLEANUPS.push_back(entry);
2053 15 }
2054 }
2055 5996 return 1;
2056 5996 }
2057
2058 9 void Playlist::InsertPlaylistAsNext(const std::string& filename, const int position, int endPosition) {
2059 // Guard before the lock, like every transition: when idle the Play()
2060 // below is then deferred and run after the lock is released, instead of
2061 // draining its presets and plugin callbacks while holding it.
2062 9 PlaylistTransitionGuard guard;
2063
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2064
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
2065 ✗ lck.unlock();
2066 ✗ cur->InsertPlaylistAsNext(filename, position, endPosition);
2067 ✗ return;
2068
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 }
2069
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 PluginManager::INSTANCE.playlistInserted(filename, position, endPosition, false);
2070
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (m_status == FPP_STATUS_IDLE) {
2071 ✗ Play(filename, position, 0, m_scheduleEntry, endPosition);
2072 } else {
2073
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 m_insertedPlaylist = filename;
2074 9 m_insertedPlaylistPosition = position;
2075 9 m_insertedPlaylistEndPosition = endPosition;
2076 }
2077
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 }
2078 582 void Playlist::InsertPlaylistImmediate(const std::string& filename, const int position, int endPosition) {
2079 // Guard before the lock, like every transition: when idle the Play()
2080 // below is then deferred and run after the lock is released, instead of
2081 // draining its presets and plugin callbacks while holding it.
2082 582 PlaylistTransitionGuard guard;
2083
1/2
✓ Branch 0 taken 582 times.
✗ Branch 1 not taken.
582 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2084
2/4
✓ Branch 0 taken 582 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 582 times.
582 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
2085 ✗ lck.unlock();
2086 ✗ cur->InsertPlaylistImmediate(filename, position, endPosition);
2087 ✗ return;
2088
1/2
✓ Branch 0 taken 582 times.
✗ Branch 1 not taken.
582 }
2089
1/2
✓ Branch 0 taken 582 times.
✗ Branch 1 not taken.
582 PluginManager::INSTANCE.playlistInserted(filename, position, endPosition, true);
2090
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 545 times.
582 if (m_status == FPP_STATUS_IDLE) {
2091
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 Play(filename, position, 0, m_scheduleEntry, endPosition);
2092 } else {
2093
1/2
✓ Branch 0 taken 545 times.
✗ Branch 1 not taken.
545 Pause();
2094
1/2
✓ Branch 0 taken 545 times.
✗ Branch 1 not taken.
545 m_insertedPlaylist = filename;
2095 545 m_insertedPlaylistPosition = position;
2096 545 m_insertedPlaylistEndPosition = endPosition;
2097 }
2098
2/4
✓ Branch 0 taken 582 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 582 times.
✗ Branch 3 not taken.
582 }
2099
2100 1279 int Playlist::Play(const std::string& filename, const int position, const int repeat, const int scheduleEntry, const int endPosition) {
2101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1279 times.
1279 if (filename.empty())
2102 ✗ return 0;
2103
2104 1279 PlaylistTransitionGuard guard;
2105
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 1237 times.
1279 if (tl_transitionDepth > 1) {
2106 // Re-entrant call: another transition on THIS thread is still on the
2107 // stack — e.g. a "Start Playlist" command entry executing inside its
2108 // own StartPlaying() frame, or a native plugin callback. Executing
2109 // inline would reload/clobber playlist state that the outer frames
2110 // still reference (m_currentSection, positions), so record the
2111 // request; the outermost PlaylistTransitionGuard replays it after
2112 // the stack unwinds. This replaces (and generalizes) the old
2113 // PlaylistEntryCommand/PlaylistEntryScript dynamic_cast check that
2114 // deferred via the startNewPlaylistFilename members.
2115
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 LogDebug(VB_PLAYLIST, "Playlist::Play('%s') re-entrant (depth %d) — deferring until current transition completes\n",
2116 filename.c_str(), tl_transitionDepth);
2117
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 std::lock_guard<std::mutex> l(s_pendingStartMutex);
2118
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 s_pendingStartFilename = filename;
2119 42 s_pendingStartPosition = position;
2120 42 s_pendingStartRepeat = repeat;
2121 42 s_pendingStartScheduleEntry = scheduleEntry;
2122 42 s_pendingStartEndPosition = endPosition;
2123 42 return 1;
2124 42 }
2125
1/2
✓ Branch 0 taken 1237 times.
✗ Branch 1 not taken.
1237 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2126
3/4
✓ Branch 0 taken 1237 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1236 times.
1237 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
2127 // An idle, condemned level would otherwise load and start the new
2128 // playlist itself, where nothing processes it.
2129
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 lck.unlock();
2130
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 return cur->Play(filename, position, repeat, scheduleEntry, endPosition);
2131
2/2
✓ Branch 0 taken 1236 times.
✓ Branch 1 taken 1 times.
1237 }
2132
1/2
✓ Branch 0 taken 1236 times.
✗ Branch 1 not taken.
1236 return PlayImpl(filename, position, repeat, scheduleEntry, endPosition);
2133 1279 }
2134
2135 // The body of Play(). Called only from Play() (guarded, non-re-entrant) and
2136 // from SwitchToInsertedPlaylist(), which must start the inserted child
2137 // playlist inline as part of the parent's own transition.
2138 1767 int Playlist::PlayImpl(const std::string& filename, const int position, const int repeat, const int scheduleEntry, const int endPosition) {
2139
1/2
✓ Branch 0 taken 1767 times.
✗ Branch 1 not taken.
1767 LogDebug(VB_PLAYLIST, "Playlist::Play('%s', %d, %d, %d, %d)\n",
2140 filename.c_str(), position, repeat, scheduleEntry, endPosition);
2141
2142 // From before the stop below until the new playlist has started, and no
2143 // longer: the deferred presets and plugin callbacks run after this.
2144
1/2
✓ Branch 0 taken 1767 times.
✗ Branch 1 not taken.
1767 Player::SwitchScope switching;
2145
1/2
✓ Branch 0 taken 1767 times.
✗ Branch 1 not taken.
1767 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2146
2147 // Paused counts as running: it used to fall through to the load below,
2148 // which replaced the paused entry without stopping it -- no stop
2149 // callback, no PLAYLIST_STOPPED preset, and an inserted level loaded in
2150 // place instead of handing back to its parent.
2151
2/2
✓ Branch 0 taken 1579 times.
✓ Branch 1 taken 2 times.
3348 if ((m_status == FPP_STATUS_PLAYLIST_PLAYING) ||
2152
1/2
✓ Branch 0 taken 1579 times.
✗ Branch 1 not taken.
3160 (m_status == FPP_STATUS_PLAYLIST_PAUSED) ||
2153
5/6
✓ Branch 0 taken 1581 times.
✓ Branch 1 taken 186 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1579 times.
✓ Branch 4 taken 188 times.
✓ Branch 5 taken 1579 times.
4927 (m_status == FPP_STATUS_STOPPING_GRACEFULLY) ||
2154 1579 (m_status == FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP)) {
2155
5/10
✓ Branch 0 taken 188 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 188 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 188 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 188 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 188 times.
✗ Branch 9 not taken.
188 std::string fullfilename = FPP_DIR_PLAYLIST("/" + filename + ".json");
2156
2157 // Only a fully loaded copy can be jumped within: SetPosition() indexes
2158 // the loaded entries, and a copy Load() trimmed (given a start and
2159 // end item) no longer lines up with the playlist's positions.
2160
4/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 187 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 188 times.
✗ Branch 5 not taken.
188 bool loadedWhole = ((m_loadStartPos == -1) || (m_loadStartPos == 0)) && (m_loadEndPos < 0);
2161
6/8
✓ Branch 0 taken 124 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 124 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 45 times.
✓ Branch 5 taken 79 times.
✓ Branch 6 taken 45 times.
✗ Branch 7 not taken.
316 if ((m_filename == fullfilename) && (repeat == m_repeat) && m_currentSection && position >= 0 &&
2162
5/6
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 45 times.
✓ Branch 5 taken 143 times.
316 loadedWhole && (endPosition < 0)) {
2163 // the requested playlist is already running and loaded, we can jump right to the index
2164
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 PlaylistEntryBase* entry = CurrentEntry();
2165
6/8
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 39 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 39 times.
✓ Branch 7 taken 6 times.
45 if (entry && entry->IsPlaying()) {
2166
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 entry->Stop();
2167 }
2168
2169 45 m_sectionPosition = 0;
2170 // position is a row of the playlist file; the loaded copy counts
2171 // a sub-playlist row's entries separately.
2172
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 int p = FileRowToPosition(position);
2173 45 int mainPos = p - (int)m_leadIn.size();
2174
6/8
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 40 times.
45 if ((m_random > 0) && (mainPos >= 0) && (mainPos < (int)m_mainPlaylist.size())) {
2175 // The row maps to where the entry was loaded, and the main
2176 // playlist is shuffled by now: as when starting part-way in
2177 // below, play that entry next and the rest in a fresh shuffle.
2178
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 p = ShuffleMainPlaylistFrom(p);
2179 }
2180
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 SetPosition(p);
2181 // Deliberate here, unlike the call at the end of this function:
2182 // this branch is a jump inside a playlist that is already running
2183 // (possibly stopping gracefully), so normalising to PLAYING is what
2184 // makes Start() report "playing" rather than "start".
2185 45 m_status = FPP_STATUS_PLAYLIST_PLAYING;
2186
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 Start();
2187 45 return 1;
2188
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 } else if (m_currentSection) {
2189 // Re-entrant callers (command/script entries, plugin callbacks)
2190 // never reach here — Play() deferred them — so it is safe to
2191 // stop the current playlist and load the new one directly.
2192
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 StopNowImpl(1);
2193
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 std::this_thread::sleep_for(std::chrono::milliseconds(100));
2194
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 std::shared_ptr<Playlist> cur = Player::INSTANCE.PlaylistSnapshot();
2195
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 110 times.
143 if (cur.get() != this) {
2196 // This was an inserted (nested) playlist: StopNow's SetIdle
2197 // unwound ONE level — the current playlist is now our parent
2198 // (resumed), and `this` is already queued on PL_CLEANUPS.
2199 // Loading the new playlist into `this` would start it on a
2200 // condemned object the player never processes and the next
2201 // Process() tick releases (the historical "started a playlist
2202 // from inside nested playlists and it blipped and vanished
2203 // while the outer playlist resumed" bug). Delegate to the
2204 // now-active level instead; this repeats per level until the
2205 // stack is flat and the resident object performs the load.
2206
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 return cur->PlayImpl(filename, position, repeat, scheduleEntry, endPosition);
2207 }
2208
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 33 times.
143 }
2209
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 78 times.
188 }
2210 1689 m_scheduleEntry = scheduleEntry;
2211 1689 m_forceStop = 0;
2212
2213 1689 int tmpStartPos = position;
2214 1689 int tmpEndPos = endPosition;
2215
2216
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1681 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
1689 if ((tmpEndPos >= 0) && (tmpEndPos < tmpStartPos)) {
2217 ✗ LogWarn(VB_PLAYLIST, "Playlist::Play() called with end position less than start position: Play('%s', %d, %d, %d, %d)\n",
2218 filename.c_str(), position, repeat, scheduleEntry, endPosition);
2219 ✗ tmpEndPos = -1;
2220 }
2221
2222 // Only a range (a start AND an end, as Insert Playlist sends to play one
2223 // item) trims the loaded copy. A start alone -- Start Playlist At Item, or
2224 // the resume after an fppd restart -- loads every entry and starts part-way
2225 // in. Trimming those left the status page, and every later reload of the
2226 // file, showing only the entries from the start point on: a show resumed
2227 // after a restart came back with part of its playlist missing.
2228 1689 int startRow = -1;
2229
4/4
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1670 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 2 times.
1689 if ((tmpStartPos > 0) && (tmpEndPos < 0)) {
2230 17 startRow = tmpStartPos;
2231 17 tmpStartPos = -1;
2232 }
2233
2234 1689 m_loadStartPos = tmpStartPos;
2235 1689 m_loadEndPos = tmpEndPos;
2236
2237
1/2
✓ Branch 0 taken 1689 times.
✗ Branch 1 not taken.
1689 Load(filename);
2238
2239
2/2
✓ Branch 0 taken 146 times.
✓ Branch 1 taken 1543 times.
1689 if (tmpStartPos >= 0) {
2240 // Load() would have trimmed our internal copy of the playlist, so adjust our values
2241
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 144 times.
146 if (tmpEndPos >= 0)
2242 2 tmpEndPos -= tmpStartPos;
2243
2244 146 tmpStartPos = 0;
2245 }
2246
2247 1689 int p = tmpStartPos;
2248
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1672 times.
1689 if (startRow > 0) {
2249 // A row past the end maps to the end, which Start() wraps to the start.
2250
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 p = FileRowToPosition(startRow);
2251 }
2252
2253
6/6
✓ Branch 0 taken 1543 times.
✓ Branch 1 taken 146 times.
✓ Branch 2 taken 1537 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 15 times.
✓ Branch 5 taken 1668 times.
1689 if ((tmpStartPos == 0 || tmpStartPos == -1) && (m_random > 0)) {
2254 15 int mainPos = p - (int)m_leadIn.size();
2255
6/8
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 8 times.
15 if ((startRow > 0) && (mainPos >= 0) && (mainPos < (int)m_mainPlaylist.size())) {
2256
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 p = ShuffleMainPlaylistFrom(p);
2257 } else {
2258
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 RandomizeMainPlaylist();
2259 }
2260 }
2261
2262
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1683 times.
1689 if (p == -2) {
2263 // random
2264 6 int l = m_mainPlaylist.size();
2265
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (l > 1) {
2266 ✗ p = FPPrand() % l;
2267 ✗ p = p + m_leadIn.size();
2268 }
2269 }
2270
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 1526 times.
1689 if (p >= 0)
2271
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 SetPosition(p);
2272
2273
2/2
✓ Branch 0 taken 1631 times.
✓ Branch 1 taken 58 times.
1689 if (repeat >= 0)
2274
1/2
✓ Branch 0 taken 1631 times.
✗ Branch 1 not taken.
1631 SetRepeat(repeat);
2275
2276 1689 m_stopAtPos = tmpEndPos;
2277
2278 // Deliberately not setting m_status here. Start() tells a fresh start from
2279 // a restart by reading m_status before it flips to PLAYING, and it sets
2280 // PLAYING itself a few lines in. Setting it first made that read always say
2281 // PLAYING, so the playlist callback reported "playing" even when the player
2282 // had been idle - which made "start" unreachable in player mode, the only
2283 // other site that sends it being the remote-mode fake in mediaoutput.cpp.
2284 // Plugins that key on the documented "start" action therefore never ran.
2285 // This call is made holding m_playlistMutex, so no other thread can observe
2286 // the window before Start() sets the status.
2287
1/2
✓ Branch 0 taken 1689 times.
✗ Branch 1 not taken.
1689 int result = Start();
2288
2289
1/2
✓ Branch 0 taken 1689 times.
✗ Branch 1 not taken.
1689 if (result == 1) {
2290 1689 std::map<std::string, std::string> keywords;
2291
3/6
✓ Branch 0 taken 1689 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1689 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1689 times.
✗ Branch 5 not taken.
5067 keywords["PLAYLIST_NAME"] = m_name;
2292
4/6
✓ Branch 0 taken 1689 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1689 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 1678 times.
3378 if (CommandManager::INSTANCE.HasPreset("PLAYLIST_STARTED")) {
2293 // Deferred: preset commands run arbitrary code inline (including
2294 // "Start Playlist", which used to recurse into Play() under the
2295 // lock). Fires after this transition completes and the mutex is
2296 // released — see PlaylistTransitionGuard.
2297
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
11 QueuePlaylistNotification([keywords]() mutable {
2298
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
20 CommandManager::INSTANCE.TriggerPreset("PLAYLIST_STARTED", keywords);
2299 10 });
2300 }
2301 1689 }
2302
2303 1689 return result;
2304 1767 }
2305
2306 /*
2307 *
2308 */
2309 248 void Playlist::SetPosition(int position) {
2310 // The MQTT position topics call this from the MQTT thread, while the
2311 // playlist thread reads m_startPosition in Start() under the lock.
2312
1/2
✓ Branch 0 taken 248 times.
✗ Branch 1 not taken.
248 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2313 248 m_startPosition = position;
2314
2315
2/4
✓ Branch 0 taken 248 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
248 Events::Publish("playlist/position/status", position);
2316 248 }
2317
2318 /*
2319 *
2320 */
2321 3525 void Playlist::SetRepeat(int repeat) {
2322 3525 m_repeat = repeat;
2323
2324
2/4
✓ Branch 0 taken 3525 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3525 times.
✗ Branch 3 not taken.
3525 Events::Publish("playlist/repeat/status", repeat);
2325 3525 }
2326
2327 22 void Playlist::RandomizeMainPlaylist() {
2328
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (m_random == 0) {
2329 ✗ return;
2330 }
2331
2332
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (m_mainPlaylist.empty()) {
2333 ✗ return;
2334 }
2335
2336
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 std::vector<PlaylistEntryBase*> tmpPlaylist = m_mainPlaylist;
2337 22 m_mainPlaylist.clear();
2338 22 int origSize = tmpPlaylist.size();
2339
2340
2/2
✓ Branch 0 taken 135 times.
✓ Branch 1 taken 22 times.
157 while (tmpPlaylist.size()) {
2341 135 int l = tmpPlaylist.size();
2342
2/2
✓ Branch 0 taken 113 times.
✓ Branch 1 taken 22 times.
135 if (l > 1) {
2343
1/2
✓ Branch 0 taken 113 times.
✗ Branch 1 not taken.
113 int p = FPPrand() % l;
2344
2345 // If this is the first item found and it is the last
2346 // item in the previous list then try again unless our playlist is only 2 items long
2347
7/8
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 88 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 110 times.
113 if ((!m_mainPlaylist.size()) && (p == (origSize - 1)) && origSize > 2)
2348 3 continue;
2349
2350
1/2
✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
110 m_mainPlaylist.push_back(tmpPlaylist[p]);
2351
1/2
✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
110 tmpPlaylist.erase(tmpPlaylist.begin() + p);
2352 } else {
2353
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 m_mainPlaylist.push_back(tmpPlaylist.back());
2354 22 tmpPlaylist.pop_back();
2355 }
2356 }
2357 22 }
2358
2359 // Starting part-way into a shuffled main playlist: the entry loaded at
2360 // position (counted across the whole playlist, as loaded) plays first, then
2361 // the rest in a fresh shuffle -- its row says nothing about which of the
2362 // others have played. The entry is found by its load position, not by
2363 // index, since the main playlist may already be shuffled. Returns the
2364 // position it now starts at.
2365 12 int Playlist::ShuffleMainPlaylistFrom(int position) {
2366 12 PlaylistEntryBase* first = nullptr;
2367
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 for (auto* e : m_mainPlaylist) {
2368
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 37 times.
49 if (e->GetPositionInPlaylist() == position) {
2369 12 first = e;
2370 12 break;
2371 }
2372 }
2373
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 RandomizeMainPlaylist();
2374
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!first) {
2375 ✗ return position;
2376 }
2377
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 m_mainPlaylist.erase(std::find(m_mainPlaylist.begin(), m_mainPlaylist.end(), first));
2378
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 m_mainPlaylist.insert(m_mainPlaylist.begin(), first);
2379 12 return m_leadIn.size();
2380 }
2381
2382 /*
2383 *
2384 */
2385 2 void Playlist::Dump(void) {
2386 2 LogDebug(VB_PLAYLIST, "Playlist: %s\n", m_name.c_str());
2387
2388 2 LogDebug(VB_PLAYLIST, " Description : %s\n", m_desc.c_str());
2389 4 LogDebug(VB_PLAYLIST, " Repeat : %d\n", m_repeat.load());
2390 2 LogDebug(VB_PLAYLIST, " Loop Count : %d\n", m_loopCount);
2391 2 LogDebug(VB_PLAYLIST, " Current Section : %s\n", m_currentSectionStr.c_str());
2392 2 LogDebug(VB_PLAYLIST, " Section Position : %d\n", m_sectionPosition);
2393
2394
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (m_leadIn.size()) {
2395 1 LogDebug(VB_PLAYLIST, " Lead In:\n");
2396
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int c = 0; c < m_leadIn.size(); c++)
2397 1 m_leadIn[c]->Dump();
2398 } else {
2399 1 LogDebug(VB_PLAYLIST, " Lead In : (No Lead In)\n");
2400 }
2401
2402
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (m_mainPlaylist.size()) {
2403 2 LogDebug(VB_PLAYLIST, " Main Playlist:\n");
2404
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 for (int c = 0; c < m_mainPlaylist.size(); c++)
2405 3 m_mainPlaylist[c]->Dump();
2406 } else {
2407 ✗ LogDebug(VB_PLAYLIST, " Main Playlist : (No Main Playlist)\n");
2408 }
2409
2410
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (m_leadOut.size()) {
2411 ✗ LogDebug(VB_PLAYLIST, " Lead Out:\n");
2412 ✗ for (int c = 0; c < m_leadOut.size(); c++)
2413 ✗ m_leadOut[c]->Dump();
2414 } else {
2415 2 LogDebug(VB_PLAYLIST, " Lead Out : (No Lead Out)\n");
2416 }
2417 2 }
2418
2419 459004 std::string Playlist::GetPlaylistName(void) {
2420
1/2
✓ Branch 0 taken 459004 times.
✗ Branch 1 not taken.
459004 std::lock_guard<std::mutex> nl(m_nameLock);
2421
1/2
✓ Branch 0 taken 459004 times.
✗ Branch 1 not taken.
918008 return m_name;
2422 459004 }
2423
2424 19 const std::vector<int>& Playlist::CurrentSectionRows() const {
2425
3/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
19 static const std::vector<int> none;
2426
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (m_currentSection == &m_leadIn)
2427 ✗ return m_leadInRows;
2428
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if (m_currentSection == &m_mainPlaylist)
2429 19 return m_mainRows;
2430 ✗ if (m_currentSection == &m_leadOut)
2431 ✗ return m_leadOutRows;
2432 ✗ return none;
2433 }
2434
2435 // File row (0-based, within a section) to a position in the loaded section.
2436 // A row that wasn't loaded (before a start position) maps to the section's
2437 // start; one past the last row, to its end. With no row table (nothing
2438 // loaded from a file) rows and positions are the same.
2439 75 int Playlist::RowToSectionPosition(const std::vector<int>& rows, int row, int sectionSize) {
2440
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (row < 0)
2441 ✗ return 0;
2442
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (rows.empty())
2443 ✗ return std::min(row, sectionSize);
2444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (row >= (int)rows.size())
2445 ✗ return sectionSize;
2446 75 return std::max(rows[row], 0);
2447 }
2448
2449 // The file row a loaded position came from.
2450 8 int Playlist::SectionPositionToRow(const std::vector<int>& rows, int pos) {
2451
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (rows.empty())
2452 ✗ return pos;
2453 8 int row = 0;
2454
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 8 times.
45 for (int r = 0; r < (int)rows.size(); r++) {
2455
5/6
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 32 times.
✓ Branch 5 taken 5 times.
37 if ((rows[r] >= 0) && (rows[r] <= pos))
2456 32 row = r;
2457 }
2458 8 return row;
2459 }
2460
2461 // File row counted across the whole playlist (lead-in, then main, then
2462 // lead-out) to an overall loaded position.
2463 62 int Playlist::FileRowToPosition(int row) {
2464
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 60 times.
62 int li = m_leadInRows.empty() ? (int)m_leadIn.size() : (int)m_leadInRows.size();
2465
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 int mi = m_mainRows.empty() ? (int)m_mainPlaylist.size() : (int)m_mainRows.size();
2466
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 50 times.
62 if (row < li)
2467 12 return RowToSectionPosition(m_leadInRows, row, m_leadIn.size());
2468
1/2
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
50 if (row < li + mi)
2469 50 return m_leadIn.size() + RowToSectionPosition(m_mainRows, row - li, m_mainPlaylist.size());
2470 ✗ return m_leadIn.size() + m_mainPlaylist.size() + RowToSectionPosition(m_leadOutRows, row - li - mi, m_leadOut.size());
2471 }
2472
2473 30793 std::shared_ptr<Playlist> Playlist::CurrentIfNotThis(void) {
2474
1/2
✓ Branch 0 taken 30793 times.
✗ Branch 1 not taken.
30793 std::shared_ptr<Playlist> cur = Player::INSTANCE.PlaylistSnapshot();
2475
5/6
✓ Branch 0 taken 30793 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 30790 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 30790 times.
30793 if (cur && cur.get() != this)
2476 3 return cur;
2477 30790 return nullptr;
2478 30793 }
2479
2480 // Re-entrant Restart/Next/Prev Item: a command entry for one of them runs
2481 // inside its own StartPlaying() frame (Start()/Process() are on the stack).
2482 // Inline, RestartItem() restarted that same command entry, which ran
2483 // RestartItem() again, until the main thread's stack overflowed; Next and Prev
2484 // did the same whenever they landed back on the entry itself (the only entry
2485 // of a repeating playlist, Prev on the first). Defer like StopNow(): the
2486 // outermost guard replays it against whatever playlist is current then, so an
2487 // entry that restarts itself is a drain cascade, which
2488 // MAX_NOTIFICATION_CASCADE bounds, rather than recursion.
2489 27372 static bool DeferIfReentrant(const char* what, void (Playlist::*fn)(void)) {
2490
2/2
✓ Branch 0 taken 27229 times.
✓ Branch 1 taken 143 times.
27372 if (tl_transitionDepth <= 1) {
2491 27229 return false;
2492 }
2493 143 LogDebug(VB_PLAYLIST, "Playlist::%s() re-entrant (depth %d) — deferring until current transition completes\n",
2494 what, tl_transitionDepth);
2495
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 QueuePlaylistNotification([fn]() {
2496
1/2
✓ Branch 0 taken 130 times.
✗ Branch 1 not taken.
130 std::shared_ptr<Playlist> cur = Player::INSTANCE.PlaylistSnapshot();
2497
1/2
✓ Branch 0 taken 130 times.
✗ Branch 1 not taken.
130 if (cur) {
2498
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 130 times.
✗ Branch 3 not taken.
130 ((*cur).*fn)();
2499 }
2500 130 });
2501 143 return true;
2502 }
2503
2504 9100 void Playlist::RestartItem(void) {
2505
2/4
✓ Branch 0 taken 9100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9100 times.
✗ Branch 3 not taken.
9100 LogDebug(VB_PLAYLIST, "RestartItem called for '%s'\n", GetPlaylistName().c_str());
2506 9100 PlaylistTransitionGuard guard;
2507
3/4
✓ Branch 0 taken 9100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
✓ Branch 3 taken 9045 times.
9100 if (DeferIfReentrant("RestartItem", &Playlist::RestartItem)) {
2508 55 return;
2509 }
2510
1/2
✓ Branch 0 taken 9045 times.
✗ Branch 1 not taken.
9045 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2511
2/4
✓ Branch 0 taken 9045 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9045 times.
9045 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
2512 ✗ lck.unlock();
2513 ✗ return cur->RestartItem();
2514
1/2
✓ Branch 0 taken 9045 times.
✗ Branch 1 not taken.
9045 }
2515
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9044 times.
9045 if (m_status == FPP_STATUS_IDLE) {
2516 1 return;
2517 }
2518
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9044 times.
9044 if (m_status != FPP_STATUS_PLAYLIST_PLAYING) {
2519 ✗ return;
2520 }
2521
2522 // Clear global pause state when manually navigating
2523 9044 m_shouldStartGlobalPause = false;
2524 9044 m_isInGlobalPause = false;
2525 9044 m_loopHoldUntilMS = 0;
2526
2527
1/2
✓ Branch 0 taken 9044 times.
✗ Branch 1 not taken.
9044 int pos = GetPosition() - 1;
2528
1/2
✓ Branch 0 taken 9044 times.
✗ Branch 1 not taken.
9044 PlaylistEntryBase* entry = CurrentEntry();
2529
6/8
✓ Branch 0 taken 9043 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9043 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9043 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9043 times.
✓ Branch 7 taken 1 times.
9044 if (entry && entry->IsPlaying())
2530
1/2
✓ Branch 0 taken 9043 times.
✗ Branch 1 not taken.
9043 entry->Stop();
2531
2532 9044 m_sectionPosition = 0;
2533 9044 m_startPosition = pos;
2534
1/2
✓ Branch 0 taken 9044 times.
✗ Branch 1 not taken.
9044 Start();
2535
4/4
✓ Branch 0 taken 9044 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9044 times.
✓ Branch 3 taken 56 times.
9101 }
2536 9152 void Playlist::NextItem(void) {
2537
2/4
✓ Branch 0 taken 9152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9152 times.
✗ Branch 3 not taken.
9152 LogDebug(VB_PLAYLIST, "NextItem called for '%s'\n", GetPlaylistName().c_str());
2538 9152 PlaylistTransitionGuard guard;
2539
3/4
✓ Branch 0 taken 9152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 9108 times.
9152 if (DeferIfReentrant("NextItem", &Playlist::NextItem)) {
2540 44 return;
2541 }
2542
1/2
✓ Branch 0 taken 9108 times.
✗ Branch 1 not taken.
9108 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2543
2/4
✓ Branch 0 taken 9108 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9108 times.
9108 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
2544 ✗ lck.unlock();
2545 ✗ return cur->NextItem();
2546
1/2
✓ Branch 0 taken 9108 times.
✗ Branch 1 not taken.
9108 }
2547
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9107 times.
9108 if (m_status == FPP_STATUS_IDLE) {
2548 1 return;
2549 }
2550
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9107 times.
9107 if (m_status != FPP_STATUS_PLAYLIST_PLAYING) {
2551 ✗ return;
2552 }
2553
2554 // Clear global pause state when manually navigating
2555 9107 m_shouldStartGlobalPause = false;
2556 9107 m_isInGlobalPause = false;
2557 9107 m_loopHoldUntilMS = 0;
2558 9107 bool somewhereToGo = true;
2559
1/2
✓ Branch 0 taken 9107 times.
✗ Branch 1 not taken.
9107 int pos = GetPosition();
2560
3/4
✓ Branch 0 taken 9107 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 9106 times.
9107 if (m_currentSectionStr == "LeadIn") {
2561
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
2 if ((pos < m_leadIn.size()) ||
2562
2/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
2 (m_mainPlaylist.size()) ||
2563 ✗ (m_leadOut.size())) {
2564 // as long as we have somewhere to go then go to next
2565 1 pos++;
2566 } else {
2567 // nowhere to go, stop playlist
2568 ✗ somewhereToGo = false;
2569 }
2570
2/4
✓ Branch 0 taken 9106 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9106 times.
✗ Branch 3 not taken.
9106 } else if (m_currentSectionStr == "MainPlaylist") {
2571
2/2
✓ Branch 0 taken 6073 times.
✓ Branch 1 taken 3033 times.
9106 if (pos < (m_leadIn.size() + m_mainPlaylist.size())) {
2572 // if not at end of main go to next item
2573 6073 pos++;
2574
1/2
✓ Branch 0 taken 3033 times.
✗ Branch 1 not taken.
3033 } else if (m_repeat) {
2575 // if at end of main and repeating, go to first item in main
2576 3033 pos = m_leadIn.size() + 1;
2577 ✗ } else if (m_leadOut.size()) {
2578 // if at end of main and non-repeating, go to first in LeadOut
2579 ✗ pos++;
2580 } else {
2581 // nowhere to go, stop playlist
2582 ✗ somewhereToGo = false;
2583 }
2584 ✗ } else if (m_currentSectionStr == "LeadOut") {
2585 ✗ if (pos < (m_leadIn.size() + m_mainPlaylist.size() + m_leadOut.size())) {
2586 // more in leadOut so go to next
2587 ✗ pos++;
2588 } else {
2589 // nowhere to go, stop playlist
2590 ✗ somewhereToGo = false;
2591 }
2592 } else {
2593 ✗ return;
2594 }
2595
2596
1/2
✓ Branch 0 taken 9107 times.
✗ Branch 1 not taken.
9107 PlaylistEntryBase* entry = CurrentEntry();
2597
6/8
✓ Branch 0 taken 9107 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9107 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9104 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 9104 times.
✓ Branch 7 taken 3 times.
9107 if (entry && entry->IsPlaying())
2598
1/2
✓ Branch 0 taken 9104 times.
✗ Branch 1 not taken.
9104 entry->Stop();
2599
2600
1/2
✓ Branch 0 taken 9107 times.
✗ Branch 1 not taken.
9107 if (somewhereToGo) {
2601 9107 pos--;
2602 9107 m_sectionPosition = 0;
2603 9107 m_startPosition = pos;
2604
1/2
✓ Branch 0 taken 9107 times.
✗ Branch 1 not taken.
9107 Start();
2605 }
2606
4/4
✓ Branch 0 taken 9107 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9107 times.
✓ Branch 3 taken 45 times.
9153 }
2607
2608 /*
2609 *
2610 */
2611 9120 void Playlist::PrevItem(void) {
2612
2/4
✓ Branch 0 taken 9120 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9120 times.
✗ Branch 3 not taken.
9120 LogDebug(VB_PLAYLIST, "PrevItem called for '%s'\n", GetPlaylistName().c_str());
2613 9120 PlaylistTransitionGuard guard;
2614
3/4
✓ Branch 0 taken 9120 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 9076 times.
9120 if (DeferIfReentrant("PrevItem", &Playlist::PrevItem)) {
2615 44 return;
2616 }
2617
1/2
✓ Branch 0 taken 9076 times.
✗ Branch 1 not taken.
9076 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2618
2/4
✓ Branch 0 taken 9076 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9076 times.
9076 if (std::shared_ptr<Playlist> cur = CurrentIfNotThis()) {
2619 ✗ lck.unlock();
2620 ✗ return cur->PrevItem();
2621
1/2
✓ Branch 0 taken 9076 times.
✗ Branch 1 not taken.
9076 }
2622
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9075 times.
9076 if (m_status == FPP_STATUS_IDLE) {
2623 1 return;
2624 }
2625
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9075 times.
9075 if (m_status != FPP_STATUS_PLAYLIST_PLAYING) {
2626 ✗ return;
2627 }
2628
2629 // Clear global pause state when manually navigating
2630 9075 m_shouldStartGlobalPause = false;
2631 9075 m_isInGlobalPause = false;
2632 9075 m_loopHoldUntilMS = 0;
2633
2634
1/2
✓ Branch 0 taken 9075 times.
✗ Branch 1 not taken.
9075 int pos = GetPosition();
2635
3/6
✓ Branch 0 taken 9075 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9075 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 9075 times.
18150 if ((m_currentSectionStr == "LeadIn") ||
2636
2/4
✓ Branch 0 taken 9075 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9075 times.
9075 (m_currentSectionStr == "LeadOut")) {
2637 // No repeat on LeadIn/Out so just go to previous item
2638 ✗ if (pos > 1)
2639 ✗ pos--;
2640
3/4
✓ Branch 0 taken 9075 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9074 times.
✓ Branch 3 taken 1 times.
9075 } else if (m_currentSectionStr == "MainPlaylist") {
2641
2/2
✓ Branch 0 taken 6049 times.
✓ Branch 1 taken 3025 times.
9074 if (pos > (m_leadIn.size() + 1)) {
2642 // If not first item in Main, go to prev item
2643 6049 pos--;
2644
1/2
✓ Branch 0 taken 3025 times.
✗ Branch 1 not taken.
3025 } else if (m_repeat) {
2645 // if first item in main and repeating, go to end of main
2646 3025 pos = m_leadIn.size() + m_mainPlaylist.size();
2647 ✗ } else if (m_leadIn.size()) {
2648 // if first item in main and non-repeating go to end of LeadIn
2649 ✗ pos = m_leadIn.size();
2650 }
2651 }
2652
2653
1/2
✓ Branch 0 taken 9075 times.
✗ Branch 1 not taken.
9075 PlaylistEntryBase* entry = CurrentEntry();
2654
6/8
✓ Branch 0 taken 9074 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9074 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9074 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9074 times.
✓ Branch 7 taken 1 times.
9075 if (entry && entry->IsPlaying())
2655
1/2
✓ Branch 0 taken 9074 times.
✗ Branch 1 not taken.
9074 entry->Stop();
2656
2657 9075 m_sectionPosition = 0;
2658 9075 m_startPosition = pos - 1;
2659
1/2
✓ Branch 0 taken 9075 times.
✗ Branch 1 not taken.
9075 Start();
2660
4/4
✓ Branch 0 taken 9075 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9075 times.
✓ Branch 3 taken 45 times.
9121 }
2661
2662 312804 PlaylistEntryBase* Playlist::CurrentEntry(void) {
2663
5/6
✓ Branch 0 taken 312802 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 312802 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 312802 times.
312804 if (m_currentSection == nullptr || m_sectionPosition >= (int)m_currentSection->size())
2664 2 return nullptr;
2665
2666 312802 return m_currentSection->at(m_sectionPosition);
2667 }
2668
2669 /*
2670 *
2671 */
2672 538780 int Playlist::GetPosition(void) {
2673 // Read from status, restart and sync paths on other threads; the sections
2674 // and m_currentSectionStr change under this lock.
2675
1/2
✓ Branch 0 taken 538780 times.
✗ Branch 1 not taken.
538780 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2676 538780 int result = 0;
2677
2678
2/2
✓ Branch 0 taken 8161 times.
✓ Branch 1 taken 530619 times.
538780 if (m_status == FPP_STATUS_IDLE)
2679 8161 return result;
2680
2681
3/4
✓ Branch 0 taken 530619 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3527 times.
✓ Branch 3 taken 527092 times.
530619 if (m_currentSectionStr == "LeadIn")
2682 3527 return m_sectionPosition + 1;
2683
2684
3/4
✓ Branch 0 taken 527092 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 526910 times.
✓ Branch 3 taken 182 times.
527092 if (m_currentSectionStr == "MainPlaylist")
2685 526910 return m_leadIn.size() + m_sectionPosition + 1;
2686
2687
3/4
✓ Branch 0 taken 182 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 180 times.
✓ Branch 3 taken 2 times.
182 if (m_currentSectionStr == "LeadOut")
2688 180 return m_leadIn.size() + m_mainPlaylist.size() + m_sectionPosition + 1;
2689
2690 2 return result;
2691 538780 }
2692
2693 /*
2694 * 0-based position a restart should resume this playlist at: the current
2695 * entry if it still has work left, the one after it if it does not.
2696 *
2697 * Resuming AT an entry that has already done its work replays it. For a
2698 * command entry that is the command that asked for the restart ("Restart
2699 * FPPD", "Switch To Player Mode"), so the new fppd runs it again and restarts
2700 * in a loop for the rest of the schedule window. That entry is not yet marked
2701 * finished -- Process() only does that on the next main-loop pass, and the
2702 * restart ends the loop first -- so a completed command counts as finished
2703 * here too.
2704 *
2705 * The position is counted in the playlist FILE, which is what the restarted
2706 * fppd loads. A playlist that was itself given a start and end item (an
2707 * Insert Playlist range) was loaded with every entry before the start trimmed
2708 * off (see Load()), so GetPosition() counts from the trimmed start; add the
2709 * trimmed entries back or a second restart resumes too early.
2710 *
2711 * A position past the end is wrapped back to the start by Start().
2712 */
2713 6 int Playlist::GetResumePosition(void) {
2714
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2715
2716 6 bool done = false;
2717
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 PlaylistEntryBase* entry = CurrentEntry();
2718
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (entry) {
2719
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 PlaylistEntryCommand* cmdEntry = dynamic_cast<PlaylistEntryCommand*>(entry);
2720
4/12
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 6 times.
6 if (entry->IsFinished() || (cmdEntry && cmdEntry->CommandDone())) {
2721 ✗ done = true;
2722 }
2723 }
2724
2725
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 const std::vector<int>& rows = CurrentSectionRows();
2726
4/8
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
6 if (rows.empty() || !m_currentSection || (m_status == FPP_STATUS_IDLE)) {
2727 // No row table (not loaded from a playlist file): positions are rows.
2728 ✗ int pos = GetPosition() - 1;
2729 ✗ if (m_loadStartPos > 0) {
2730 ✗ pos += m_loadStartPos;
2731 }
2732 ✗ return done ? pos + 1 : pos;
2733 }
2734
2735 // The row of the file this entry came from, counted across the whole
2736 // playlist. It used to be the loaded position plus the trimmed rows,
2737 // which mixed the two once a sub-playlist row had expanded into several
2738 // entries. Resuming inside a sub-playlist restarts that sub-playlist: a
2739 // row is as fine as a position in the file can be.
2740
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 int pos = m_sectionPosition + (done ? 1 : 0);
2741 6 int base = 0;
2742 6 int loadedBase = 0;
2743
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (m_currentSection == &m_mainPlaylist) {
2744 6 base = m_leadInRows.size();
2745 6 loadedBase = m_leadIn.size();
2746 ✗ } else if (m_currentSection == &m_leadOut) {
2747 ✗ base = m_leadInRows.size() + m_mainRows.size();
2748 ✗ loadedBase = m_leadIn.size() + m_mainPlaylist.size();
2749 }
2750 6 int row = (int)rows.size();
2751
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (pos < (int)m_currentSection->size()) {
2752 // The row table is in load order, and a random playlist's main
2753 // section is shuffled: go by where the entry was loaded, not by its
2754 // index, or the resume lands on whichever entry was loaded at that
2755 // index -- one that may already have played.
2756 6 int loadedPos = (*m_currentSection)[pos]->GetPositionInPlaylist() - loadedBase;
2757
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 row = SectionPositionToRow(rows, loadedPos);
2758 }
2759 6 return base + row;
2760 6 }
2761
2762 /*
2763 *
2764 */
2765 281875 int Playlist::GetSize(void) {
2766
1/2
✓ Branch 0 taken 281875 times.
✗ Branch 1 not taken.
281875 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2767
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 281875 times.
281875 if (m_status == FPP_STATUS_IDLE)
2768 ✗ return 0;
2769
2770 281875 return m_leadIn.size() + m_mainPlaylist.size() + m_leadOut.size();
2771 281875 }
2772
2773 /*
2774 *
2775 */
2776 105491 Json::Value Playlist::GetCurrentEntry(void) {
2777 105491 Json::Value result;
2778
2779
2/2
✓ Branch 0 taken 19572 times.
✓ Branch 1 taken 85919 times.
105491 if (m_status == FPP_STATUS_IDLE)
2780 19572 return result;
2781
2782
1/2
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
85919 PlaylistEntryBase* entry = CurrentEntry();
2783
1/2
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
85919 if (entry)
2784
1/2
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
85919 result = entry->GetConfig();
2785
2786 85919 return result;
2787 ✗ }
2788 2 static void GetFilenames(PlaylistEntryBase* entry, std::string& seq, std::string& med) {
2789
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 PlaylistEntrySequence* se = dynamic_cast<PlaylistEntrySequence*>(entry);
2790
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 PlaylistEntryMedia* me = dynamic_cast<PlaylistEntryMedia*>(entry);
2791
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 PlaylistEntryBoth* be = dynamic_cast<PlaylistEntryBoth*>(entry);
2792
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (se) {
2793 ✗ seq = se->GetSequenceName();
2794 }
2795
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (me) {
2796 ✗ med = me->GetMediaName();
2797 }
2798
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (be) {
2799 ✗ seq = be->GetSequenceName();
2800 ✗ med = be->GetMediaName();
2801 }
2802 2 }
2803
2804 2 void Playlist::GetFilenamesForPos(int pos, std::string& seq, std::string& med) {
2805
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2806
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 for (auto& a : m_leadIn) {
2807
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (a->GetPositionInPlaylist() == pos) {
2808
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 GetFilenames(a, seq, med);
2809 2 return;
2810 }
2811 }
2812 ✗ for (auto& a : m_mainPlaylist) {
2813 ✗ if (a->GetPositionInPlaylist() == pos) {
2814 ✗ GetFilenames(a, seq, med);
2815 ✗ return;
2816 }
2817 }
2818 ✗ for (auto& a : m_leadOut) {
2819 ✗ if (a->GetPositionInPlaylist() == pos) {
2820 ✗ GetFilenames(a, seq, med);
2821 ✗ return;
2822 }
2823 }
2824
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 }
2825
2826 ✗ int Playlist::FindPosForMS(uint64_t& t, bool itemDefinedOnly) {
2827 ✗ std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2828 ✗ if (itemDefinedOnly) {
2829 ✗ PlaylistEntryBase* bestOption = nullptr;
2830 ✗ uint64_t diff = 0xFFFFFFFFFFL;
2831 ✗ for (auto& a : m_leadIn) {
2832 ✗ if (a->GetTimeCode() < t) {
2833 ✗ uint64_t d2 = t - a->GetTimeCode();
2834 ✗ if (d2 < diff) {
2835 ✗ diff = d2;
2836 ✗ bestOption = a;
2837 }
2838 }
2839 }
2840 ✗ for (auto& a : m_mainPlaylist) {
2841 ✗ if (a->GetTimeCode() < t) {
2842 ✗ uint64_t d2 = t - a->GetTimeCode();
2843 ✗ if (d2 < diff) {
2844 ✗ diff = d2;
2845 ✗ bestOption = a;
2846 }
2847 }
2848 }
2849 ✗ for (auto& a : m_leadOut) {
2850 ✗ if (a->GetTimeCode() < t) {
2851 ✗ uint64_t d2 = t - a->GetTimeCode();
2852 ✗ if (d2 < diff) {
2853 ✗ diff = d2;
2854 ✗ bestOption = a;
2855 }
2856 }
2857 }
2858 ✗ if (bestOption) {
2859 ✗ t -= bestOption->GetTimeCode();
2860 ✗ return bestOption->GetPositionInPlaylist();
2861 }
2862 } else {
2863 ✗ for (auto& a : m_leadIn) {
2864 ✗ uint64_t i = a->GetLengthInMS();
2865 ✗ if (t < i) {
2866 ✗ return a->GetPositionInPlaylist();
2867 }
2868 ✗ t -= i;
2869 }
2870 ✗ for (auto& a : m_mainPlaylist) {
2871 ✗ uint64_t i = a->GetLengthInMS();
2872 ✗ if (t < i) {
2873 ✗ return a->GetPositionInPlaylist();
2874 }
2875 ✗ t -= i;
2876 }
2877 ✗ for (auto& a : m_leadOut) {
2878 ✗ uint64_t i = a->GetLengthInMS();
2879 ✗ if (t < i) {
2880 ✗ return a->GetPositionInPlaylist();
2881 }
2882 ✗ t -= i;
2883 }
2884 }
2885 ✗ t = 0;
2886 ✗ return -1;
2887 ✗ }
2888
2889 ✗ uint64_t Playlist::GetCurrentPosInMS() {
2890 ✗ int pos = 0;
2891 uint64_t posms;
2892 ✗ return GetCurrentPosInMS(pos, posms, false);
2893 }
2894 ✗ uint64_t Playlist::GetCurrentPosInMS(int& position, uint64_t& posms, bool itemDefinedOnly) {
2895 ✗ std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2896
2897 ✗ position = -1;
2898 ✗ posms = 0;
2899 ✗ if (m_status == FPP_STATUS_IDLE) {
2900 ✗ return 0;
2901 }
2902 ✗ PlaylistEntryBase* entry = CurrentEntry();
2903 ✗ if (entry == nullptr) {
2904 ✗ return 0;
2905 }
2906 ✗ position = entry->GetPositionInPlaylist();
2907 ✗ posms = entry->GetElapsedMS();
2908 ✗ if (itemDefinedOnly) {
2909 ✗ int pos = entry->GetTimeCode();
2910 ✗ if (pos >= 0) {
2911 ✗ return pos + posms;
2912 } else {
2913 ✗ posms = 0;
2914 ✗ return 0;
2915 }
2916 }
2917
2918 ✗ uint64_t pos = 0;
2919 ✗ for (int x = 0; x < m_sectionPosition; x++) {
2920 ✗ pos += m_currentSection->at(x)->GetLengthInMS();
2921 }
2922 ✗ pos += posms;
2923 // if we aren't in the LeadIn, add the time of the LeadIn
2924 ✗ if (m_currentSectionStr != "LeadIn") {
2925 ✗ for (auto& a : m_leadIn) {
2926 ✗ pos += a->GetLengthInMS();
2927 }
2928 } else {
2929 ✗ return pos;
2930 }
2931 ✗ if (m_currentSectionStr != "MainPlaylist") {
2932 // must be in the leadOut, add the main list length
2933 ✗ for (auto& a : m_mainPlaylist) {
2934 ✗ pos += a->GetLengthInMS();
2935 }
2936 }
2937 ✗ return pos;
2938 ✗ }
2939 ✗ uint64_t Playlist::GetPosStartInMS(int pos) {
2940 ✗ std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2941 ✗ uint64_t ms = 0;
2942
2943 ✗ for (auto& a : m_leadIn) {
2944 ✗ if (a->GetPositionInPlaylist() == pos) {
2945 ✗ return ms;
2946 }
2947 ✗ ms += a->GetLengthInMS();
2948 }
2949 ✗ for (auto& a : m_mainPlaylist) {
2950 ✗ if (a->GetPositionInPlaylist() == pos) {
2951 ✗ return ms;
2952 }
2953 ✗ ms += a->GetLengthInMS();
2954 }
2955 ✗ for (auto& a : m_leadOut) {
2956 ✗ if (a->GetPositionInPlaylist() == pos) {
2957 ✗ return ms;
2958 }
2959 ✗ ms += a->GetLengthInMS();
2960 }
2961 ✗ return ms;
2962 ✗ }
2963
2964 4 Json::Value Playlist::GetMqttStatusJSON(void) {
2965 // this is called on background thread, need to lock
2966
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2967
2968
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 Json::Value result;
2969
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 result["status"] = PlaylistStatusToString(m_status); // Works because single playlist
2970
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 Json::Value playlistArray = Json::Value(Json::arrayValue);
2971
2972
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
4 PlaylistEntryBase* entry = (m_status != FPP_STATUS_IDLE) ? CurrentEntry() : nullptr;
2973
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 if (entry) {
2974
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 Json::Value entryArray = Json::Value(Json::arrayValue);
2975
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 Json::Value playlist;
2976 // Only one entry right now.
2977
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 Json::Value playlistEntry = entry->GetMqttStatus();
2978
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 entryArray.append(playlistEntry);
2979
2980
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 playlist["name"] = m_name;
2981
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 playlist["repeat"] = m_repeat.load();
2982
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 playlist["description"] = m_desc;
2983
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 playlist["currentItems"] = entryArray;
2984
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 playlistArray.append(playlist);
2985 3 }
2986
2987
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 result["activePlaylists"] = playlistArray;
2988 4 return result;
2989 4 }
2990
2991 238337 void Playlist::GetCurrentStatus(Json::Value& result) {
2992
1/2
✓ Branch 0 taken 238337 times.
✗ Branch 1 not taken.
238337 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
2993
5/6
✓ Branch 0 taken 195956 times.
✓ Branch 1 taken 42381 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 195956 times.
✓ Branch 4 taken 42381 times.
✓ Branch 5 taken 195956 times.
238337 if (m_status == FPP_STATUS_IDLE || m_currentSection == nullptr) {
2994
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["repeat_mode"] = "0";
2995
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["current_playlist"]["description"] = "";
2996
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["current_playlist"]["playlist"] = "";
2997
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["current_playlist"]["count"] = "0";
2998
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["current_playlist"]["index"] = "0";
2999
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["current_playlist"]["type"] = "";
3000
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["current_playlist"]["generated"] = false;
3001
3002
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["current_sequence"] = "";
3003
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["current_song"] = "";
3004
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["seconds_played"] = "0";
3005
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["seconds_remaining"] = "0";
3006
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["time_elapsed"] = "00:00";
3007
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["time_remaining"] = "00:00";
3008
3009 // Still provide global pause and random info even when idle
3010
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["global_pause"]["configured"] = m_globalPauseBetweenSequencesMS > 0;
3011
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["global_pause"]["duration_ms"] = m_globalPauseBetweenSequencesMS;
3012
3/6
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42381 times.
✗ Branch 5 not taken.
42381 result["global_pause"]["active"] = false; // Always false when idle
3013
2/4
✓ Branch 0 taken 42381 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42381 times.
✗ Branch 3 not taken.
42381 result["random"] = m_random;
3014 42381 return;
3015 }
3016
3017
1/2
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
195956 std::string plname = m_name;
3018
2/4
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
391912 result["repeat_mode"] = m_repeat.load();
3019
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
195956 result["current_playlist"]["description"] = m_desc;
3020
4/8
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 195956 times.
✗ Branch 7 not taken.
195956 result["current_playlist"]["count"] = std::to_string(GetSize());
3021
4/8
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 195956 times.
✗ Branch 7 not taken.
195956 result["current_playlist"]["index"] = std::to_string(GetPosition());
3022
3023
2/4
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
195956 result["random"] = m_random;
3024
3025 // Global pause between sequences status
3026
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
195956 result["global_pause"]["configured"] = m_globalPauseBetweenSequencesMS > 0;
3027
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
195956 result["global_pause"]["duration_ms"] = m_globalPauseBetweenSequencesMS;
3028
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
195956 result["global_pause"]["active"] = m_isInGlobalPause;
3029
3/4
✓ Branch 0 taken 3197 times.
✓ Branch 1 taken 192759 times.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
195956 if (m_isInGlobalPause && m_globalPauseBetweenSequencesMS > 0) {
3030
1/2
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
3197 long long elapsed = GetMonotonicTimeMS() - m_globalPauseStartTime;
3031 3197 long long remaining = m_globalPauseBetweenSequencesMS - elapsed;
3032
2/2
✓ Branch 0 taken 599 times.
✓ Branch 1 taken 2598 times.
3197 if (remaining < 0) remaining = 0;
3033
3/6
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3197 times.
✗ Branch 5 not taken.
3197 result["global_pause"]["elapsed_ms"] = static_cast<Json::Int64>(elapsed);
3034
3/6
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3197 times.
✗ Branch 5 not taken.
3197 result["global_pause"]["remaining_ms"] = static_cast<Json::Int64>(remaining);
3035
3/6
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3197 times.
✗ Branch 5 not taken.
3197 result["global_pause"]["remaining_seconds"] = static_cast<Json::Int64>((remaining + 999) / 1000); // Round up
3036 }
3037
3038 // CurrentEntry() returns null while the playlist is between items --
3039 // Process() parks m_sectionPosition at exactly the section size when
3040 // advancing past the end of a section, and checks for that state rather
3041 // than treating it as invalid. Indexing unguarded here would throw
3042 // std::out_of_range out of the /api/fppd/status handler on a drogon I/O
3043 // thread, which aborts fppd; treat "no current entry" as the null case
3044 // the code below already handles.
3045
1/2
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
195956 PlaylistEntryBase* ple = CurrentEntry();
3046 195956 std::string type;
3047
1/2
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
195956 if (ple) {
3048
1/2
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
195956 type = ple->GetType();
3049 }
3050
3051
3/4
✓ Branch 0 taken 196334 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 378 times.
✓ Branch 3 taken 195956 times.
196334 while (type == "dynamic") {
3052
1/2
✓ Branch 0 taken 378 times.
✗ Branch 1 not taken.
378 PlaylistEntryDynamic* dyn = dynamic_cast<PlaylistEntryDynamic*>(ple);
3053
1/2
✓ Branch 0 taken 378 times.
✗ Branch 1 not taken.
378 ple = dyn->GetCurrentEntry();
3054
2/2
✓ Branch 0 taken 337 times.
✓ Branch 1 taken 41 times.
378 if (ple) {
3055
1/2
✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
337 type = ple->GetType();
3056 } else {
3057
1/2
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
41 type = "unknown";
3058 }
3059 }
3060
3061
1/2
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
195956 plname = plname.substr(plname.find_last_of("\\/") + 1);
3062
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 195956 times.
391912 if (endsWith(plname, ".json")) {
3063 ✗ plname = plname.substr(0, plname.find_last_of("."));
3064 }
3065
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
195956 result["current_playlist"]["playlist"] = plname;
3066
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
195956 result["current_playlist"]["type"] = type;
3067
3/6
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195956 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195956 times.
✗ Branch 5 not taken.
195956 result["current_playlist"]["generated"] = m_generatedOnTheFly;
3068
3069
2/2
✓ Branch 0 taken 195915 times.
✓ Branch 1 taken 41 times.
195956 if (ple) {
3070 // Check if we're currently in a global pause - if so, show pause progress instead of item progress
3071
3/4
✓ Branch 0 taken 3197 times.
✓ Branch 1 taken 192718 times.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
195915 if (m_isInGlobalPause && m_globalPauseBetweenSequencesMS > 0) {
3072 // Show global pause progress
3073
1/2
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
3197 long long elapsed = GetMonotonicTimeMS() - m_globalPauseStartTime;
3074 3197 long long remaining = m_globalPauseBetweenSequencesMS - elapsed;
3075
2/2
✓ Branch 0 taken 599 times.
✓ Branch 1 taken 2598 times.
3197 if (remaining < 0) remaining = 0;
3076
3077 3197 int secsElapsed = (int)(elapsed / 1000);
3078 3197 int secsRemaining = (int)(remaining / 1000);
3079
3080
2/4
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
3197 result["current_sequence"] = "Global Pause";
3081
2/4
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
3197 result["current_song"] = "";
3082
2/4
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
3197 result["seconds_played"] = std::to_string(secsElapsed);
3083
2/4
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
3197 result["seconds_elapsed"] = std::to_string(secsElapsed);
3084
2/4
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
3197 result["milliseconds_elapsed"] = static_cast<Json::Int64>(elapsed);
3085
2/4
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
3197 result["seconds_remaining"] = std::to_string(secsRemaining);
3086
3/6
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3197 times.
✗ Branch 5 not taken.
3197 result["time_elapsed"] = secondsToTime(secsElapsed);
3087
3/6
✓ Branch 0 taken 3197 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3197 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3197 times.
✗ Branch 5 not taken.
3197 result["time_remaining"] = secondsToTime(secsRemaining);
3088 3197 } else {
3089 // Normal item progress
3090
1/2
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
192718 int msecs = ple->GetElapsedMS();
3091 192718 int secsElapsed = (int)(msecs / 1000);
3092
2/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
192718 int secsRemaining = (int)((ple->GetLengthInMS() - ple->GetElapsedMS()) / 1000);
3093
3094 192718 std::string currentSeq;
3095 192718 std::string currentSong;
3096
3/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26119 times.
✓ Branch 3 taken 166599 times.
192718 if (type == "media") {
3097
1/2
✓ Branch 0 taken 26119 times.
✗ Branch 1 not taken.
26119 PlaylistEntryMedia* med = dynamic_cast<PlaylistEntryMedia*>(ple);
3098
1/2
✓ Branch 0 taken 26119 times.
✗ Branch 1 not taken.
26119 currentSong = med->GetMediaName();
3099
3/4
✓ Branch 0 taken 166599 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1388 times.
✓ Branch 3 taken 165211 times.
166599 } else if (type == "both") {
3100
1/2
✓ Branch 0 taken 1388 times.
✗ Branch 1 not taken.
1388 PlaylistEntryBoth* both = dynamic_cast<PlaylistEntryBoth*>(ple);
3101
1/2
✓ Branch 0 taken 1388 times.
✗ Branch 1 not taken.
1388 currentSeq = both->GetSequenceName();
3102
1/2
✓ Branch 0 taken 1388 times.
✗ Branch 1 not taken.
1388 currentSong = both->GetMediaName();
3103
3/4
✓ Branch 0 taken 165211 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11500 times.
✓ Branch 3 taken 153711 times.
165211 } else if (type == "sequence") {
3104
1/2
✓ Branch 0 taken 11500 times.
✗ Branch 1 not taken.
11500 PlaylistEntrySequence* seq = dynamic_cast<PlaylistEntrySequence*>(ple);
3105
1/2
✓ Branch 0 taken 11500 times.
✗ Branch 1 not taken.
11500 currentSeq = seq->GetSequenceName();
3106 11500 secsElapsed = sequence->m_seqMSElapsed / 1000;
3107 11500 secsRemaining = sequence->m_seqMSRemaining / 1000;
3108
3/4
✓ Branch 0 taken 153711 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 153709 times.
153711 } else if (type == "script") {
3109
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 PlaylistEntryScript* scr = dynamic_cast<PlaylistEntryScript*>(ple);
3110
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 currentSeq = scr->GetScriptName();
3111 }
3112
2/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
192718 result["current_sequence"] = currentSeq;
3113
2/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
192718 result["current_song"] = currentSong;
3114
2/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
192718 result["seconds_played"] = std::to_string(secsElapsed);
3115
2/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
192718 result["seconds_elapsed"] = std::to_string(secsElapsed);
3116
2/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
192718 result["milliseconds_elapsed"] = msecs;
3117
2/4
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
192718 result["seconds_remaining"] = std::to_string(secsRemaining);
3118
3/6
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 192718 times.
✗ Branch 5 not taken.
192718 result["time_elapsed"] = secondsToTime(secsElapsed);
3119
3/6
✓ Branch 0 taken 192718 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192718 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 192718 times.
✗ Branch 5 not taken.
192718 result["time_remaining"] = secondsToTime(secsRemaining);
3120 192718 }
3121 } else {
3122 // No current entry (between items, or a dynamic entry with nothing
3123 // resolved). Emit the same placeholders the idle branch above uses so
3124 // the status JSON keeps a stable shape for API consumers.
3125
2/4
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 result["current_sequence"] = "";
3126
2/4
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 result["current_song"] = "";
3127
2/4
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 result["seconds_played"] = "0";
3128
2/4
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 result["seconds_remaining"] = "0";
3129
2/4
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 result["time_elapsed"] = "00:00";
3130
2/4
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 result["time_remaining"] = "00:00";
3131 }
3132
3133 195956 std::list<std::string> parents;
3134
1/2
✓ Branch 0 taken 195956 times.
✗ Branch 1 not taken.
195956 GetParentPlaylistNames(parents);
3135
2/2
✓ Branch 0 taken 9442 times.
✓ Branch 1 taken 186514 times.
195956 if (!parents.empty()) {
3136
2/2
✓ Branch 0 taken 9675 times.
✓ Branch 1 taken 9442 times.
19117 for (auto& n : parents) {
3137
3/6
✓ Branch 0 taken 9675 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9675 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9675 times.
✗ Branch 5 not taken.
9675 result["breadcrumbs"].append(n);
3138 }
3139 }
3140
2/2
✓ Branch 0 taken 195956 times.
✓ Branch 1 taken 42381 times.
238337 }
3141 7 int Playlist::GetLevelCount() {
3142 7 int levels = 1;
3143
2/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
7 for (std::shared_ptr<Playlist> par = m_parent.load(); par; par = par->m_parent.load()) {
3144 ✗ levels++;
3145 7 }
3146 7 return levels;
3147 }
3148 205631 void Playlist::GetParentPlaylistNames(std::list<std::string>& names) {
3149
3/4
✓ Branch 0 taken 205631 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9675 times.
✓ Branch 3 taken 195956 times.
205631 if (std::shared_ptr<Playlist> par = m_parent.load()) {
3150
1/2
✓ Branch 0 taken 9675 times.
✗ Branch 1 not taken.
9675 par->GetParentPlaylistNames(names);
3151
2/4
✓ Branch 0 taken 9675 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9675 times.
✗ Branch 3 not taken.
9675 names.push_back(par->GetPlaylistName());
3152 205631 }
3153 205631 }
3154
3155 /*
3156 *
3157 */
3158 80127 Json::Value Playlist::GetInfo(void) {
3159 80127 Json::Value result;
3160
1/2
✓ Branch 0 taken 80127 times.
✗ Branch 1 not taken.
80127 GetInfo(result);
3161 80127 return result;
3162 ✗ }
3163
3164 105491 void Playlist::GetInfo(Json::Value& result) {
3165
1/2
✓ Branch 0 taken 105491 times.
✗ Branch 1 not taken.
105491 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
3166
3/6
✓ Branch 0 taken 105491 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 105491 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 105491 times.
✗ Branch 5 not taken.
105491 result["currentState"] = PlaylistStatusToString(m_status);
3167
2/2
✓ Branch 0 taken 19572 times.
✓ Branch 1 taken 85919 times.
105491 if (m_status == FPP_STATUS_IDLE) {
3168
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["name"] = "";
3169
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["desc"] = "";
3170
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["repeat"] = 0;
3171
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["loop"] = 0;
3172
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["loopCount"] = 0;
3173
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["random"] = 0;
3174
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["blankAtEnd"] = 0;
3175
2/4
✓ Branch 0 taken 19572 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19572 times.
✗ Branch 3 not taken.
19572 result["size"] = 0;
3176 } else {
3177
2/4
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
85919 result["name"] = m_name;
3178
2/4
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
85919 result["desc"] = m_desc;
3179
2/4
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
171838 result["repeat"] = m_repeat.load();
3180
2/4
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
171838 result["loop"] = m_loop.load();
3181
2/4
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
85919 result["loopCount"] = m_loopCount;
3182
2/4
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
85919 result["random"] = m_random;
3183
2/4
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
85919 result["blankAtEnd"] = m_blankAtEnd;
3184
3/6
✓ Branch 0 taken 85919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85919 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 85919 times.
✗ Branch 5 not taken.
85919 result["size"] = GetSize();
3185 }
3186
2/4
✓ Branch 0 taken 105491 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 105491 times.
✗ Branch 3 not taken.
105491 result["currentEntry"] = GetCurrentEntry();
3187 105491 }
3188
3189 /*
3190 *
3191 */
3192 ✗ std::string Playlist::GetConfigStr(void) {
3193 ✗ return SaveJsonToString(GetConfig());
3194 }
3195
3196 /*
3197 *
3198 */
3199 25364 Json::Value Playlist::GetConfig(void) {
3200
1/2
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
25364 Json::Value result;
3201
1/2
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
25364 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
3202
1/2
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
25364 GetInfo(result);
3203
3204
2/2
✓ Branch 0 taken 9252 times.
✓ Branch 1 taken 16112 times.
25364 if (m_leadIn.size()) {
3205
2/4
✓ Branch 0 taken 9252 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9252 times.
✗ Branch 3 not taken.
9252 result["leadIn"] = Json::Value(Json::arrayValue);
3206
1/2
✓ Branch 0 taken 9252 times.
✗ Branch 1 not taken.
9252 Json::Value& jsonArray = result["leadIn"];
3207
2/2
✓ Branch 0 taken 9252 times.
✓ Branch 1 taken 9252 times.
18504 for (int c = 0; c < m_leadIn.size(); c++) {
3208
2/4
✓ Branch 0 taken 9252 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9252 times.
✗ Branch 3 not taken.
9252 jsonArray.append(m_leadIn[c]->GetConfig());
3209 }
3210 }
3211
3212
2/2
✓ Branch 0 taken 17205 times.
✓ Branch 1 taken 8159 times.
25364 if (m_mainPlaylist.size()) {
3213
2/4
✓ Branch 0 taken 17205 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17205 times.
✗ Branch 3 not taken.
17205 result["mainPlaylist"] = Json::Value(Json::arrayValue);
3214
1/2
✓ Branch 0 taken 17205 times.
✗ Branch 1 not taken.
17205 Json::Value& jsonArray = result["mainPlaylist"];
3215
2/2
✓ Branch 0 taken 26458 times.
✓ Branch 1 taken 17205 times.
43663 for (int c = 0; c < m_mainPlaylist.size(); c++) {
3216
2/4
✓ Branch 0 taken 26458 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26458 times.
✗ Branch 3 not taken.
26458 jsonArray.append(m_mainPlaylist[c]->GetConfig());
3217 }
3218 }
3219
3220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25364 times.
25364 if (m_leadOut.size()) {
3221 ✗ result["leadOut"] = Json::Value(Json::arrayValue);
3222 ✗ Json::Value& jsonArray = result["leadOut"];
3223 ✗ for (int c = 0; c < m_leadOut.size(); c++) {
3224 ✗ jsonArray.append(m_leadOut[c]->GetConfig());
3225 }
3226 }
3227
3228 25364 m_configTime = time(NULL);
3229
2/4
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25364 times.
✗ Branch 3 not taken.
25364 result["configTime"] = (Json::UInt64)m_configTime;
3230
3231
2/4
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25364 times.
✗ Branch 3 not taken.
25364 result["random"] = m_random;
3232
2/4
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25364 times.
✗ Branch 3 not taken.
25364 result["globalPauseBetweenSequencesMS"] = m_globalPauseBetweenSequencesMS;
3233
3234
2/4
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25364 times.
✗ Branch 3 not taken.
25364 result["playlistInfo"] = m_playlistInfo;
3235
1/2
✓ Branch 0 taken 25364 times.
✗ Branch 1 not taken.
25364 m_config = result;
3236
3237 25364 return result;
3238 25364 }
3239
3240 /*
3241 *
3242 */
3243 142 int Playlist::MQTTHandler(std::string topic, std::string msg) {
3244
2/4
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 142 times.
✗ Branch 3 not taken.
142 LogDebug(VB_PLAYLIST, "Playlist::MQTTHandler('%s', '%s') while playing '%s'\n",
3245 topic.c_str(), msg.c_str(), GetPlaylistName().c_str());
3246
3247 // note the leading /set/playlist will be removed from topic by now
3248
3249 142 int pos = topic.find("/");
3250
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 142 times.
142 if (pos == std::string::npos) {
3251 ✗ LogWarn(VB_PLAYLIST, "Ignoring Invalid playlist topic: playlist/%s\n",
3252 topic.c_str());
3253 ✗ return 0;
3254 }
3255
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 std::string newPlaylistName = topic.substr(0, pos);
3256
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 std::string topicEnd = topic.substr(pos);
3257
3258 /*
3259 * NOTE: This because multiple playlist are not supported, the newPlaylistname value
3260 * is only considered when starting a playlist. All other actions will
3261 * apply to the current running playlist even if the names don't match
3262 */
3263
3264 // Stops go through the Player, as the API's and the commands' do, so the
3265 // force-stop is recorded and the scheduler does not start the playlist
3266 // straight back up.
3267 // ALLPLAYLIST should be checked first to avoid name colision.
3268
3/4
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
✓ Branch 3 taken 93 times.
142 if (topic == "ALLPLAYLISTS/stop/now") {
3269
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 Player::INSTANCE.StopNow(1);
3270
3271
2/4
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 93 times.
93 } else if (topic == "ALLPLAYLISTS/stop/graceful") {
3272 ✗ Player::INSTANCE.StopGracefully(1);
3273
3274
2/4
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 93 times.
93 } else if (topic == "ALLPLAYLISTS/stop/afterloop") {
3275 ✗ Player::INSTANCE.StopGracefully(1, 1);
3276
3277
2/4
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 93 times.
93 } else if (topicEnd == "/next") {
3278 ✗ NextItem();
3279
3280
2/4
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 93 times.
93 } else if (topicEnd == "/prev") {
3281 ✗ PrevItem();
3282
3283
2/4
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 93 times.
93 } else if (topicEnd == "/repeat") {
3284 ✗ SetRepeat(atoi(msg.c_str()));
3285
3286
2/4
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 93 times.
93 } else if (topicEnd == "/startPosition") {
3287 ✗ SetPosition(atoi(msg.c_str()));
3288
3289
3/4
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 85 times.
93 } else if (topicEnd == "/stop/now") {
3290
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
8 Player::INSTANCE.StopNow(1);
3291
3292
3/4
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 84 times.
85 } else if (topicEnd == "/stop/graceful") {
3293
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 Player::INSTANCE.StopGracefully(1);
3294
3295
2/4
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 84 times.
84 } else if (topicEnd == "/stop/afterloop") {
3296 ✗ Player::INSTANCE.StopGracefully(1, 1);
3297
3298 // These three are depgrecated and should be removed
3299
3/4
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 44 times.
84 } else if (topic == "name/set") {
3300
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 LogInfo(VB_CONTROL, "playlist/%s is deprecated and will be removed in a future release\n",
3301 topic.c_str());
3302 // On the MQTT thread: the playlist thread moves m_sectionPosition
3303 // under the lock as it advances.
3304 int position;
3305 {
3306
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 std::unique_lock<std::recursive_mutex> lck(m_playlistMutex);
3307 40 position = m_sectionPosition;
3308 40 }
3309
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 Play(msg, position, m_repeat);
3310
3311
3/4
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 40 times.
44 } else if (topic == "repeat/set") {
3312
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 LogInfo(VB_PLAYLIST, "playlist/%s is deprecated and will be removed in a future release\n",
3313 topic.c_str());
3314
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 SetRepeat(atoi(msg.c_str()));
3315
3316
2/4
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
40 } else if (topic == "sectionPosition/set") {
3317
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 LogInfo(VB_PLAYLIST, "playlist/%s is deprecated and will be removed in a future release\n",
3318 topic.c_str());
3319
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 SetPosition(atoi(msg.c_str()));
3320
3321 } else {
3322 ✗ LogWarn(VB_PLAYLIST, "Ignoring Invalid playlist topic: playlist/%s\n", topic.c_str());
3323 ✗ return 0;
3324 }
3325
3326 140 return 1;
3327 140 }
3328
3329 /*
3330 *
3331 */
3332 ✗ std::string Playlist::ReplaceMatches(std::string in) {
3333 ✗ std::string out = in;
3334
3335 ✗ LogDebug(VB_PLAYLIST, "In: '%s'\n", in.c_str());
3336
3337 // FIXME, Playlist
3338
3339 ✗ LogDebug(VB_PLAYLIST, "Out: '%s'\n", out.c_str());
3340
3341 ✗ return out;
3342 ✗ }
3343
3344 /*
3345 * Helper method to start playing an entry, handling global pause between sequences
3346 */
3347 3603 void Playlist::StartPlayingWithGlobalPause(PlaylistEntryBase* entry) {
3348
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3597 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
3603 if (m_shouldStartGlobalPause && m_globalPauseBetweenSequencesMS > 0) {
3349 6 LogDebug(VB_PLAYLIST, "Starting global pause between sequences for %d ms before next item\n", m_globalPauseBetweenSequencesMS);
3350 6 m_shouldStartGlobalPause = false;
3351 6 m_isInGlobalPause = true;
3352 6 m_globalPauseStartTime = GetMonotonicTimeMS();
3353 // Don't start the entry yet, it will be started when the pause completes
3354 } else {
3355 // No global pause needed, start immediately
3356 3597 entry->StartPlayingOrFinish();
3357 }
3358 3603 }
3359