GCC Code Coverage Report


Directory: ./
File: playlist/PlaylistEntrySequence.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 102 134 76.1%
Functions: 14 16 87.5%
Branches: 65 168 38.7%

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 "../common.h"
18 #include "../log.h"
19 #include "../settings.h"
20
21 #include "../Sequence.h"
22 #include "../channeloutput/ChannelOutputSetup.h"
23 #include "../channeloutput/channeloutputthread.h"
24 #include "../fseq/FSEQFile.h"
25
26 #include "PlaylistEntrySequence.h"
27
28 #include "Events.h"
29
30 #include <chrono>
31
32 // Monotonic millisecond stopwatch used for sequence frame-timing sync.
33 // Must NOT use wall-clock time (gettimeofday/GetTimeMS): RTC-less boards (e.g. a
34 // Pi with no RTC) start a playlist via FPPD_STARTED with an incorrect clock, and
35 // the NTP correction that lands mid-playback would jump the wall clock and wedge
36 // the channel output thread into a permanent hold/skip (see issue #2678).
37 117531 static inline long long SeqMonotonicMS() {
38
1/2
✓ Branch 0 taken 117531 times.
✗ Branch 1 not taken.
117531 return std::chrono::duration_cast<std::chrono::milliseconds>(
39 235062 std::chrono::steady_clock::now().time_since_epoch())
40 235062 .count();
41 }
42
43 654 PlaylistEntrySequence::PlaylistEntrySequence(Playlist* playlist, PlaylistEntryBase* parent) :
44 PlaylistEntryBase(playlist, parent),
45 654 m_duration(0),
46 654 m_prepared(false),
47 654 m_adjustTiming(true),
48 654 m_sequenceFrameTime(0),
49 654 m_pausedFrame(-1) {
50
1/2
✓ Branch 0 taken 654 times.
✗ Branch 1 not taken.
654 LogDebug(VB_PLAYLIST, "PlaylistEntrySequence::PlaylistEntrySequence()\n");
51
52
1/2
✓ Branch 0 taken 654 times.
✗ Branch 1 not taken.
654 m_type = "sequence";
53 654 }
54
55 1652 PlaylistEntrySequence::~PlaylistEntrySequence() {
56 1652 }
57
58 /*
59 *
60 */
61 654 int PlaylistEntrySequence::Init(Json::Value& config) {
62 654 LogDebug(VB_PLAYLIST, "PlaylistEntrySequence::Init()\n");
63
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 654 times.
654 if (!config.isMember("sequenceName")) {
65 ✗ LogErr(VB_PLAYLIST, "Missing sequenceName entry\n");
66 ✗ return 0;
67 }
68
69
2/4
✓ Branch 0 taken 654 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 654 times.
✗ Branch 3 not taken.
654 m_sequenceName = config["sequenceName"].asString();
70 654 m_pausedFrame = -1;
71 654 return PlaylistEntryBase::Init(config);
72 }
73
74 1836 int PlaylistEntrySequence::PreparePlay(int frame) {
75
2/2
✓ Branch 0 taken 691 times.
✓ Branch 1 taken 1145 times.
1836 if (sequence->OpenSequenceFile(m_sequenceName, frame) <= 0) {
76 // OpenSequenceFile() has logged why (once per name); a playlist that
77 // keeps coming round to a missing sequence must not log it each time.
78 691 LogDebug(VB_PLAYLIST, "Error opening sequence %s\n", m_sequenceName.c_str());
79 691 return 0;
80 }
81 1145 m_prepared = true;
82 1145 m_duration = sequence->m_seqMSDuration;
83 1145 m_sequenceFrameTime = sequence->GetSeqStepTime();
84 1145 return 1;
85 }
86
87 /*
88 *
89 */
90 1831 int PlaylistEntrySequence::StartPlaying(void) {
91 1831 LogDebug(VB_PLAYLIST, "PlaylistEntrySequence::StartPlaying()\n");
92
93
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1831 times.
1831 if (!CanPlay()) {
94 ✗ m_prepared = false;
95 ✗ FinishPlay();
96 ✗ return 0;
97 }
98
99
2/2
✓ Branch 0 taken 1786 times.
✓ Branch 1 taken 45 times.
1831 if (!m_prepared) {
100 1786 PreparePlay();
101 }
102 1831 m_pausedFrame = -1;
103 1831 ResetChannelOutputFrameNumber();
104 1831 sequence->StartSequence();
105 1831 m_startTme = SeqMonotonicMS();
106 1831 LogDebug(VB_PLAYLIST, "Started Sequence, ID: %s\n", m_sequenceName.c_str());
107
108
2/4
✓ Branch 0 taken 1831 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1831 times.
✗ Branch 3 not taken.
3627 Events::Publish("playlist/sequence/status", m_sequenceName);
109
2/2
✓ Branch 0 taken 1140 times.
✓ Branch 1 taken 691 times.
1831 if (m_duration > 0) {
110
2/4
✓ Branch 0 taken 1140 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1140 times.
✗ Branch 3 not taken.
3385 Events::Publish("playlist/sequence/secondsTotal", m_duration / 1000);
111 } else {
112
3/6
✓ Branch 0 taken 691 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 691 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 691 times.
✗ Branch 5 not taken.
2764 Events::Publish("playlist/sequence/secondsTotal", "");
113 }
114
115 1831 return PlaylistEntryBase::StartPlaying();
116 }
117
118 /*
119 *
120 */
121 238058 int PlaylistEntrySequence::Process(void) {
122
2/2
✓ Branch 0 taken 1287 times.
✓ Branch 1 taken 236771 times.
238058 if (!sequence->IsSequenceRunning()) {
123 1287 FinishPlay();
124 1287 m_prepared = false;
125
126
3/6
✓ Branch 0 taken 1287 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1287 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1287 times.
✗ Branch 5 not taken.
5148 Events::Publish("playlist/sequence/status", "");
127
3/6
✓ Branch 0 taken 1287 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1287 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1287 times.
✗ Branch 5 not taken.
5148 Events::Publish("playlist/sequence/secondsTotal", "");
128
2/2
✓ Branch 0 taken 115696 times.
✓ Branch 1 taken 121075 times.
236771 } else if (m_adjustTiming) {
129 115696 long long now = SeqMonotonicMS();
130 115696 int total = (now - m_startTme);
131 115696 int frame = total / sequence->GetSeqStepTime();
132 115696 CalculateNewChannelOutputDelayForFrame(frame);
133 }
134
135 238058 return PlaylistEntryBase::Process();
136 }
137
138 /*
139 *
140 */
141 392 int PlaylistEntrySequence::Stop(void) {
142 392 LogDebug(VB_PLAYLIST, "PlaylistEntrySequence::Stop()\n");
143
144 392 sequence->CloseSequenceFile();
145 392 m_prepared = false;
146
3/6
✓ Branch 0 taken 392 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 392 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 392 times.
✗ Branch 5 not taken.
1568 Events::Publish("playlist/sequence/status", "");
147
3/6
✓ Branch 0 taken 392 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 392 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 392 times.
✗ Branch 5 not taken.
1176 Events::Publish("playlist/sequence/secondsTotal", "");
148
149 392 return PlaylistEntryBase::Stop();
150 }
151
152 12888 uint64_t PlaylistEntrySequence::GetLengthInMS() {
153
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12887 times.
12888 if (m_duration == 0) {
154
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.
2 std::string n = FPP_DIR_SEQUENCE("/" + m_sequenceName);
155
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (FileExists(n)) {
156
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 FSEQFile* fs = FSEQFile::openFSEQFile(n);
157
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (fs) {
158 ✗ m_duration = fs->getTotalTimeMS();
159 ✗ delete fs;
160 }
161 }
162 1 }
163 12888 return m_duration;
164 }
165 23002 uint64_t PlaylistEntrySequence::GetElapsedMS() {
166
2/2
✓ Branch 0 taken 22992 times.
✓ Branch 1 taken 10 times.
23002 if (m_prepared) {
167 22992 return sequence->m_seqMSElapsed;
168 }
169 10 return 0;
170 }
171
172 /*
173 *
174 */
175 ✗ void PlaylistEntrySequence::Dump(void) {
176 ✗ PlaylistEntryBase::Dump();
177
178 ✗ LogDebug(VB_PLAYLIST, "Sequence Filename: %s\n", m_sequenceName.c_str());
179 ✗ }
180
181 /*
182 *
183 */
184 19599 Json::Value PlaylistEntrySequence::GetConfig(void) {
185 19599 Json::Value result = PlaylistEntryBase::GetConfig();
186
187
2/4
✓ Branch 0 taken 19599 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19599 times.
✗ Branch 3 not taken.
19599 result["sequenceName"] = m_sequenceName;
188
3/4
✓ Branch 0 taken 19599 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18314 times.
✓ Branch 3 taken 1285 times.
19599 if (sequence->GetSeqFilenameCopy() == m_sequenceName) {
189
2/4
✓ Branch 0 taken 18314 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18314 times.
18314 if (IsPaused()) {
190 ✗ int pos = m_pausedFrame * m_sequenceFrameTime;
191 ✗ result["millisecondsElapsed"] = pos;
192 ✗ result["secondsElapsed"] = pos / 1000;
193 ✗ result["secondsRemaining"] = (m_duration - pos) / 1000;
194 } else {
195
2/4
✓ Branch 0 taken 18314 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18314 times.
✗ Branch 3 not taken.
36628 result["millisecondsElapsed"] = sequence->m_seqMSElapsed.load();
196
2/4
✓ Branch 0 taken 18314 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18314 times.
✗ Branch 3 not taken.
18314 result["secondsElapsed"] = sequence->m_seqMSElapsed / 1000;
197
2/4
✓ Branch 0 taken 18314 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18314 times.
✗ Branch 3 not taken.
18314 result["secondsRemaining"] = sequence->m_seqMSRemaining / 1000;
198 }
199 }
200 19599 return result;
201 ✗ }
202 ✗ Json::Value PlaylistEntrySequence::GetMqttStatus(void) {
203 ✗ Json::Value result = PlaylistEntryBase::GetMqttStatus();
204 ✗ result["sequenceName"] = m_sequenceName;
205 ✗ if (IsPaused()) {
206 ✗ int pos = m_pausedFrame * m_sequenceFrameTime;
207 ✗ result["millisecondsElapsed"] = pos;
208 ✗ result["secondsElapsed"] = pos / 1000;
209 ✗ result["secondsRemaining"] = (m_duration - pos) / 1000;
210 ✗ result["secondsTotal"] = m_duration / 1000;
211 } else {
212 ✗ result["secondsElapsed"] = sequence->m_seqMSElapsed / 1000;
213 ✗ result["secondsRemaining"] = sequence->m_seqMSRemaining / 1000;
214 ✗ result["secondsTotal"] = sequence->m_seqMSDuration / 1000;
215 ✗ result["millisecondsElapsed"] = sequence->m_seqMSElapsed.load();
216 }
217 ✗ return result;
218 ✗ }
219
220 4 void PlaylistEntrySequence::Pause() {
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (sequence->GetSeqStepTime() == 0) {
222 ✗ m_pausedFrame = 0;
223 } else {
224 4 m_pausedFrame = sequence->m_seqMSElapsed / sequence->GetSeqStepTime();
225 }
226 4 sequence->CloseSequenceFile();
227 4 sequence->SendBlankingData();
228 4 m_prepared = false;
229 4 }
230 252747 bool PlaylistEntrySequence::IsPaused() {
231 252747 return m_pausedFrame != -1;
232 }
233 4 void PlaylistEntrySequence::Resume() {
234
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (m_pausedFrame >= 0) {
235 4 PreparePlay(m_pausedFrame);
236 4 sequence->StartSequence();
237 4 m_startTme = SeqMonotonicMS() - m_pausedFrame * sequence->GetSeqStepTime();
238 4 LogDebug(VB_PLAYLIST, "Started Sequence, ID: %s\n", m_sequenceName.c_str());
239 4 m_pausedFrame = -1;
240
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
10 Events::Publish("playlist/sequence/status", m_sequenceName);
241 }
242 4 }
243