OpenShot Library | libopenshot 1.1.1
PlayerPrivate.cpp
Go to the documentation of this file.
1
10// Copyright (c) 2008-2019 OpenShot Studios, LLC
11//
12// SPDX-License-Identifier: LGPL-3.0-or-later
13
14#include "PlayerPrivate.h"
15#include "Exceptions.h"
16
17#include <queue>
18#include <thread> // for std::this_thread::sleep_for
19#include <chrono> // for std::chrono microseconds, high_resolution_clock
20
21namespace openshot
22{
24 // Constructor
25 PlayerPrivate::PlayerPrivate(openshot::RendererBase *rb)
26 : Thread("player"), playback_frames(0), video_position(1), audio_position(0),
27 reader(nullptr), speed(1), last_speed(1), renderer(rb),
28 last_video_position(1), max_sleep_ms(125000), is_dirty(true)
29 {
30 videoCache = new openshot::VideoCacheThread();
31 audioPlayback = new openshot::AudioPlaybackThread(videoCache);
32 videoPlayback = new openshot::VideoPlaybackThread(rb);
33 }
34
35 // Destructor
36 PlayerPrivate::~PlayerPrivate()
37 {
38 stopPlayback();
39 delete audioPlayback;
40 delete videoCache;
41 delete videoPlayback;
42 }
43
44 // Start thread
45 void PlayerPrivate::run()
46 {
47 // bail if no reader set
48 if (!reader)
49 return;
50
51 // Start the threads
52 if (reader->info.has_audio)
53 audioPlayback->startThread(Priority::high);
54 if (reader->info.has_video) {
55 videoCache->startThread(Priority::high);
56 videoPlayback->startThread(Priority::high);
57 }
58
59 using std::chrono::duration_cast;
60
61 // Types for storing time durations in whole and fractional microseconds
62 using micro_sec = std::chrono::microseconds;
63 using double_micro_sec = std::chrono::duration<double, micro_sec::period>;
64
65 // Init start_time of playback
66 std::chrono::time_point<std::chrono::system_clock, std::chrono::microseconds> start_time;
67 start_time = std::chrono::time_point_cast<micro_sec>(std::chrono::system_clock::now());
68
69 while (!threadShouldExit()) {
70 // Calculate on-screen time for a single frame
71 int frame_speed = std::max(abs(speed), 1);
72 const auto frame_duration = double_micro_sec(1000000.0 / (reader->info.fps.ToDouble() * frame_speed));
73 const auto max_sleep = frame_duration * 4;
74
75 // Pausing Code (which re-syncs audio/video times)
76 // - If speed is zero or speed changes
77 // - If pre-roll is not ready (This should allow scrubbing of the timeline without waiting on pre-roll)
78 bool wait_paused_hold = (speed == 0 && video_position == last_video_position);
79 bool wait_speed_change = (speed != 0 && last_speed != speed);
80 bool cache_ready = videoCache->isReady();
81 bool wait_preroll = (speed != 0 && !is_dirty && !cache_ready);
82 bool should_wait = (wait_paused_hold || wait_speed_change || wait_preroll);
83
84 if (should_wait)
85 {
86 // Sleep for a fraction of frame duration
87 std::this_thread::sleep_for(frame_duration / 4);
88
89 // Reset current playback start time
90 start_time = std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::system_clock::now());
91 playback_frames = 0;
92 last_speed = speed;
93
94 // Seek audio thread (since audio is also paused)
95 audioPlayback->Seek(video_position);
96 videoCache->AcknowledgeAudioCacheMiss();
97
98 continue;
99 }
100
101 // Get the current video frame
102 const uint64_t frame_seek_generation = seek_generation.load();
103 auto frame_to_render = getFrame();
104 // A seek during decoding invalidates the old frame. Do not hand
105 // that frame to the renderer or overwrite the new playhead.
106 if (seek_generation.load() != frame_seek_generation)
107 continue;
108 const int64_t rendered_position = video_position.load();
109
110 // Set the video frame on the video thread and render frame
111 videoPlayback->SetFrame(std::move(frame_to_render));
112 videoPlayback->rendered.reset();
113 videoPlayback->render.signal();
114 // Keep decode/position advancement aligned with actual preview updates.
115 // This avoids occasional "silent advance then jump" behavior when
116 // preroll transitions and rendering are briefly out-of-sync.
117 const int render_wait_ms = std::max(
118 1,
119 static_cast<int>(frame_duration.count() / 1000.0 * 2.0)
120 );
121 videoPlayback->rendered.wait(render_wait_ms);
122
123 // Keep track of the last displayed frame
124 {
125 std::lock_guard<std::mutex> lock(frame_mutex);
126 if (seek_generation.load() == frame_seek_generation)
127 last_video_position = rendered_position;
128 }
129 last_speed = speed;
130
131 // Calculate the diff between 'now' and the predicted frame end time
132 const auto current_time = std::chrono::system_clock::now();
133 const auto remaining_time = double_micro_sec(start_time +
134 (frame_duration * playback_frames) - current_time);
135
136 // Sleep to display video image on screen
137 if (remaining_time > remaining_time.zero() ) {
138 if (remaining_time < max_sleep) {
139 std::this_thread::sleep_for(remaining_time);
140 } else {
141 // Protect against invalid or too-long sleep times
142 std::this_thread::sleep_for(max_sleep);
143 }
144 } else {
145 // If we're behind schedule (e.g. preroll/render stall), do not
146 // burst through delayed frames. Resync timing baseline so
147 // playback continues smoothly at normal cadence.
148 start_time = std::chrono::time_point_cast<micro_sec>(current_time);
149 playback_frames = 0;
150 }
151 }
152 }
153
154 // Get the next displayed frame (based on speed and direction)
155 std::shared_ptr<openshot::Frame> PlayerPrivate::getFrame()
156 {
157 int64_t position;
158 uint64_t generation;
159 ReaderBase* current_reader;
160 {
161 std::lock_guard<std::mutex> lock(frame_mutex);
162 // Getting new frame, so clear this flag
163 is_dirty = false;
164
165 // Get the next frame (based on speed)
166 const int current_speed = speed.load();
167 const int64_t next_position = video_position.load() + current_speed;
168 if (next_position >= 1 && next_position <= reader->info.video_length) {
169 video_position = next_position;
170 } else if (next_position < 1) {
171 video_position = 1;
172 speed = 0;
173 } else {
174 video_position = reader->info.video_length;
175 speed = 0;
176 }
177
178 position = video_position.load();
179 if (frame && frame->number == position && position == last_video_position)
180 return frame;
181 // Increment playback frames (always in the positive direction)
182 playback_frames += std::abs(speed.load());
183 // Update cache position before releasing the seek lock.
184 videoCache->NotifyPlaybackPosition(position);
185 current_reader = reader;
186 generation = seek_generation.load();
187 }
188
189 // Decoding can take much longer than a frame. Keep Seek responsive while
190 // it runs, then publish only if no newer seek invalidated the result.
191 std::shared_ptr<Frame> decoded;
192 try {
193 decoded = current_reader->GetFrame(position);
194 } catch (const ReaderClosed &) {
195 } catch (const OutOfBoundsFrame &) {
196 }
197 {
198 std::lock_guard<std::mutex> lock(frame_mutex);
199 if (seek_generation.load() != generation)
200 return {};
201 frame = std::move(decoded);
202 return frame;
203 }
204 }
205
206 // Seek to a new position
207 void PlayerPrivate::Seek(int64_t new_position)
208 {
209 std::lock_guard<std::mutex> lock(frame_mutex);
210 ++seek_generation;
211 video_position = new_position;
212 last_video_position = 0;
213 // Drop local frame reference so same-frame refreshes cannot reuse stale
214 // content after timeline/clip property updates.
215 frame.reset();
216 // Always force immediate refresh after seek/update, even while playing.
217 is_dirty = true;
218 }
219
220 // Start video/audio playback
221 bool PlayerPrivate::startPlayback()
222 {
223 if (video_position < 0) return false;
224
225 stopPlayback();
226 startThread(Priority::high);
227 return true;
228 }
229
230 // Stop video/audio playback
231 void PlayerPrivate::stopPlayback()
232 {
233 // Stop the producer first, so it cannot start workers during teardown.
234 // Signal all workers before joining. Never use JUCE's finite-timeout
235 // stopThread, which can forcibly terminate a decoder while holding locks.
236 signalThreadShouldExit();
237 notify();
238 videoCache->Stop();
239 audioPlayback->signalThreadShouldExit();
240 audioPlayback->notify();
241 videoPlayback->signalThreadShouldExit();
242 videoPlayback->render.signal();
243 videoPlayback->notify();
244 waitForThreadToExit(-1);
245 // The producer may have been inside its initial startThread block when
246 // signalled. Repeat worker signals after it has fully exited.
247 audioPlayback->signalThreadShouldExit();
248 audioPlayback->notify();
249 videoPlayback->signalThreadShouldExit();
250 videoPlayback->render.signal();
251 videoPlayback->notify();
252 videoCache->StopThread(-1);
253 audioPlayback->waitForThreadToExit(-1);
254 videoPlayback->waitForThreadToExit(-1);
255 }
256
257}
Header file for all Exception classes.
Source file for PlayerPrivate class.
The audio playback thread.
This is the base class of all Renderers in libopenshot.
Definition: RendererBase.h:32
Handles prefetching and caching of video/audio frames for smooth playback.
The video playback class.
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
int close_to_sync