OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
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.
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.