OpenShot Library | libopenshot 0.6.0
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 : renderer(rb), Thread("player"), video_position(1), audio_position(0),
27 speed(1), reader(NULL), last_video_position(1), max_sleep_ms(125000), playback_frames(0), is_dirty(true)
28 {
29 videoCache = new openshot::VideoCacheThread();
30 audioPlayback = new openshot::AudioPlaybackThread(videoCache);
31 videoPlayback = new openshot::VideoPlaybackThread(rb);
32 }
33
34 // Destructor
35 PlayerPrivate::~PlayerPrivate()
36 {
37 stopPlayback();
38 delete audioPlayback;
39 delete videoCache;
40 delete videoPlayback;
41 }
42
43 // Start thread
44 void PlayerPrivate::run()
45 {
46 // bail if no reader set
47 if (!reader)
48 return;
49
50 // Start the threads
51 if (reader->info.has_audio)
52 audioPlayback->startThread(Priority::high);
53 if (reader->info.has_video) {
54 videoCache->startThread(Priority::high);
55 videoPlayback->startThread(Priority::high);
56 }
57
58 using std::chrono::duration_cast;
59
60 // Types for storing time durations in whole and fractional microseconds
61 using micro_sec = std::chrono::microseconds;
62 using double_micro_sec = std::chrono::duration<double, micro_sec::period>;
63
64 // Init start_time of playback
65 std::chrono::time_point<std::chrono::system_clock, std::chrono::microseconds> start_time;
66 start_time = std::chrono::time_point_cast<micro_sec>(std::chrono::system_clock::now());
67
68 while (!threadShouldExit()) {
69 // Calculate on-screen time for a single frame
70 int frame_speed = std::max(abs(speed), 1);
71 const auto frame_duration = double_micro_sec(1000000.0 / (reader->info.fps.ToDouble() * frame_speed));
72 const auto max_sleep = frame_duration * 4;
73
74 // Pausing Code (which re-syncs audio/video times)
75 // - If speed is zero or speed changes
76 // - If pre-roll is not ready (This should allow scrubbing of the timeline without waiting on pre-roll)
77 bool wait_paused_hold = (speed == 0 && video_position == last_video_position);
78 bool wait_speed_change = (speed != 0 && last_speed != speed);
79 bool cache_ready = videoCache->isReady();
80 bool wait_preroll = (speed != 0 && !is_dirty && !cache_ready);
81 bool should_wait = (wait_paused_hold || wait_speed_change || wait_preroll);
82
83 if (should_wait)
84 {
85 // Sleep for a fraction of frame duration
86 std::this_thread::sleep_for(frame_duration / 4);
87
88 // Reset current playback start time
89 start_time = std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::system_clock::now());
90 playback_frames = 0;
91 last_speed = speed;
92
93 // Seek audio thread (since audio is also paused)
94 audioPlayback->Seek(video_position);
95
96 continue;
97 }
98
99 // Get the current video frame
100 frame = getFrame();
101
102 // Set the video frame on the video thread and render frame
103 videoPlayback->frame = frame;
104 videoPlayback->rendered.reset();
105 videoPlayback->render.signal();
106 // Keep decode/position advancement aligned with actual preview updates.
107 // This avoids occasional "silent advance then jump" behavior when
108 // preroll transitions and rendering are briefly out-of-sync.
109 const int render_wait_ms = std::max(
110 1,
111 static_cast<int>(frame_duration.count() / 1000.0 * 2.0)
112 );
113 videoPlayback->rendered.wait(render_wait_ms);
114
115 // Keep track of the last displayed frame
116 last_video_position = video_position;
117 last_speed = speed;
118
119 // Calculate the diff between 'now' and the predicted frame end time
120 const auto current_time = std::chrono::system_clock::now();
121 const auto remaining_time = double_micro_sec(start_time +
122 (frame_duration * playback_frames) - current_time);
123
124 // Sleep to display video image on screen
125 if (remaining_time > remaining_time.zero() ) {
126 if (remaining_time < max_sleep) {
127 std::this_thread::sleep_for(remaining_time);
128 } else {
129 // Protect against invalid or too-long sleep times
130 std::this_thread::sleep_for(max_sleep);
131 }
132 } else {
133 // If we're behind schedule (e.g. preroll/render stall), do not
134 // burst through delayed frames. Resync timing baseline so
135 // playback continues smoothly at normal cadence.
136 start_time = std::chrono::time_point_cast<micro_sec>(current_time);
137 playback_frames = 0;
138 }
139 }
140 }
141
142 // Get the next displayed frame (based on speed and direction)
143 std::shared_ptr<openshot::Frame> PlayerPrivate::getFrame()
144 {
145 try {
146 // Getting new frame, so clear this flag
147 is_dirty = false;
148
149 // Get the next frame (based on speed)
150 if (video_position + speed >= 1 && video_position + speed <= reader->info.video_length) {
151 video_position = video_position + speed;
152
153 } else if (video_position + speed < 1) {
154 // Start of reader (prevent negative frame number and pause playback)
155 video_position = 1;
156 speed = 0;
157 } else if (video_position + speed > reader->info.video_length) {
158 // End of reader (prevent negative frame number and pause playback)
159 video_position = reader->info.video_length;
160 speed = 0;
161 }
162
163 if (frame && frame->number == video_position && video_position == last_video_position) {
164 // return cached frame
165 return frame;
166 }
167 else
168 {
169 // Increment playback frames (always in the positive direction)
170 playback_frames += std::abs(speed);
171
172 // Update playhead hint for cache window tracking without triggering seek behavior.
173 videoCache->NotifyPlaybackPosition(video_position);
174
175 // return frame from reader
176 return reader->GetFrame(video_position);
177 }
178
179 } catch (const ReaderClosed & e) {
180 // ...
181 } catch (const OutOfBoundsFrame & e) {
182 // ...
183 }
184 return std::shared_ptr<openshot::Frame>();
185 }
186
187 // Seek to a new position
188 void PlayerPrivate::Seek(int64_t new_position)
189 {
190 video_position = new_position;
191 last_video_position = 0;
192 // Drop local frame reference so same-frame refreshes cannot reuse stale
193 // content after timeline/clip property updates.
194 frame.reset();
195 // Always force immediate refresh after seek/update, even while playing.
196 is_dirty = true;
197 }
198
199 // Start video/audio playback
200 bool PlayerPrivate::startPlayback()
201 {
202 if (video_position < 0) return false;
203
204 stopPlayback();
205 startThread(Priority::high);
206 return true;
207 }
208
209 // Stop video/audio playback
210 void PlayerPrivate::stopPlayback()
211 {
212 if (videoCache->isThreadRunning() && reader->info.has_video) videoCache->stopThread(max_sleep_ms);
213 if (audioPlayback->isThreadRunning() && reader->info.has_audio) audioPlayback->stopThread(max_sleep_ms);
214 if (videoPlayback->isThreadRunning() && reader->info.has_video) videoPlayback->stopThread(max_sleep_ms);
215 if (isThreadRunning()) stopThread(max_sleep_ms);
216 }
217
218}
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.
Definition Compressor.h:29