OpenShot Library | libopenshot  0.5.0
VideoCacheThread.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2025 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "VideoCacheThread.h"
14 #include "CacheBase.h"
15 #include "Exceptions.h"
16 #include "Frame.h"
17 #include "Settings.h"
18 #include "Timeline.h"
19 #include <thread>
20 #include <chrono>
21 #include <algorithm>
22 
23 namespace openshot
24 {
25  // Constructor
27  : Thread("video-cache")
28  , speed(0)
29  , last_speed(1)
30  , last_dir(1) // assume forward (+1) on first launch
31  , userSeeked(false)
32  , preroll_on_next_fill(false)
33  , clear_cache_on_next_fill(false)
34  , scrub_active(false)
35  , requested_display_frame(1)
36  , current_display_frame(1)
37  , cached_frame_count(0)
38  , min_frames_ahead(4)
39  , timeline_max_frame(0)
40  , reader(nullptr)
41  , force_directional_cache(false)
42  , last_cached_index(0)
43  , seen_timeline_cache_epoch(0)
44  , timeline_cache_epoch_initialized(false)
45  {
46  }
47 
48  // Destructor
50  {
51  }
52 
53  // Is cache ready for playback (pre-roll)
55  {
56  if (!reader) {
57  return false;
58  }
59 
60  const int64_t ready_min = min_frames_ahead.load();
61  if (ready_min < 0) {
62  return true;
63  }
64 
65  const int64_t cached_index = last_cached_index.load();
66  int64_t playhead = requested_display_frame.load();
67  int dir = computeDirection();
68 
69  // Near timeline boundaries, don't require more pre-roll than can exist.
70  int64_t max_frame = reader->info.video_length;
71  if (auto* timeline = dynamic_cast<Timeline*>(reader)) {
72  const int64_t timeline_max = timeline->GetMaxFrame();
73  if (timeline_max > 0) {
74  max_frame = timeline_max;
75  }
76  }
77  if (max_frame < 1) {
78  return false;
79  }
80  playhead = clampToTimelineRange(playhead, max_frame);
81 
82  int64_t required_ahead = ready_min;
83  int64_t available_ahead = (dir > 0)
84  ? std::max<int64_t>(0, max_frame - playhead)
85  : std::max<int64_t>(0, playhead - 1);
86  required_ahead = std::min(required_ahead, available_ahead);
87 
88  if (dir > 0) {
89  return (cached_index >= playhead + required_ahead);
90  }
91  return (cached_index <= playhead - required_ahead);
92  }
93 
94  void VideoCacheThread::setSpeed(int new_speed)
95  {
96  // Only update last_speed and last_dir when new_speed != 0
97  if (new_speed != 0) {
98  last_speed.store(new_speed);
99  last_dir.store(new_speed > 0 ? 1 : -1);
100  // Leaving paused/scrub context: resume normal cache behavior.
101  scrub_active.store(false);
102  }
103  speed.store(new_speed);
104  }
105 
106  // Get the size in bytes of a frame (rough estimate)
107  int64_t VideoCacheThread::getBytes(int width,
108  int height,
109  int sample_rate,
110  int channels,
111  float fps)
112  {
113  // RGBA video frame
114  int64_t bytes = static_cast<int64_t>(width) * height * sizeof(char) * 4;
115  // Approximate audio: (sample_rate * channels)/fps samples per frame
116  bytes += ((sample_rate * channels) / fps) * sizeof(float);
117  return bytes;
118  }
119 
122  {
123  // JUCE’s startThread() returns void, so we launch it and then check if
124  // the thread actually started:
125  startThread(Priority::high);
126  return isThreadRunning();
127  }
128 
130  bool VideoCacheThread::StopThread(int timeoutMs)
131  {
132  stopThread(timeoutMs);
133  return !isThreadRunning();
134  }
135 
137  {
138  std::lock_guard<std::mutex> guard(seek_state_mutex);
139  reader = new_reader;
142  Play();
143  }
144 
145  void VideoCacheThread::Seek(int64_t new_position, bool start_preroll)
146  {
147  const int64_t timeline_end = resolveTimelineEnd();
148  const int64_t clamped_new_position = clampToTimelineRange(new_position, timeline_end);
149  const int64_t current_requested = requested_display_frame.load();
150 
151  bool should_mark_seek = false;
152  bool should_preroll = false;
153  int64_t new_cached_count = cached_frame_count.load();
154  bool entering_scrub = false;
155  bool leaving_scrub = false;
156  bool cache_contains = false;
157  bool should_clear_cache = false;
158  CacheBase* cache = reader ? reader->GetCache() : nullptr;
159  const bool same_frame_refresh = (new_position == current_requested);
160  if (cache) {
161  cache_contains = cache->Contains(clamped_new_position);
162  }
163 
164  if (start_preroll) {
165  if (same_frame_refresh) {
166  const bool is_paused = (speed.load() == 0);
167  if (is_paused) {
168  const bool was_scrubbing = scrub_active.load();
169  if (was_scrubbing && cache && cache_contains) {
170  // Preserve in-range cache for paused scrub preview -> same-frame commit.
171  should_mark_seek = false;
172  should_preroll = false;
173  should_clear_cache = false;
174  new_cached_count = cache->Count();
175  } else {
176  // Paused same-frame edit refresh: force full cache refresh.
177  if (Timeline* timeline = dynamic_cast<Timeline*>(reader)) {
178  timeline->ClearAllCache();
179  }
180  new_cached_count = 0;
181  should_mark_seek = true;
182  should_preroll = true;
183  should_clear_cache = false;
184  }
185  } else {
186  // Same-frame refresh during playback should stay lightweight.
187  should_mark_seek = false;
188  should_preroll = false;
189  should_clear_cache = false;
190  if (cache && cache_contains) {
191  cache->Remove(clamped_new_position);
192  }
193  if (cache) {
194  new_cached_count = cache->Count();
195  }
196  }
197  } else {
198  if (cache && !cache_contains) {
199  should_mark_seek = true;
200  // Uncached commit seek: defer cache clear to cache thread loop.
201  new_cached_count = 0;
202  should_preroll = true;
203  should_clear_cache = true;
204  }
205  else if (cache)
206  {
207  // In-range commit seek preserves cache window/baseline.
208  should_mark_seek = false;
209  should_preroll = false;
210  should_clear_cache = false;
211  new_cached_count = cache->Count();
212  } else {
213  // No cache object to query: use normal seek behavior.
214  should_mark_seek = true;
215  }
216  }
217  leaving_scrub = true;
218  }
219  else {
220  // Non-preroll seeks cover paused scrubbing and live playback refresh.
221  const bool is_paused = (speed.load() == 0);
222  if (is_paused && same_frame_refresh) {
223  // Same-frame paused refresh updates only that frame.
224  should_mark_seek = false;
225  should_preroll = false;
226  should_clear_cache = false;
227  if (cache && cache_contains) {
228  cache->Remove(clamped_new_position);
229  }
230  if (cache) {
231  new_cached_count = cache->Count();
232  }
233  leaving_scrub = true;
234  }
235  else if (is_paused) {
236  if (cache && !cache_contains) {
237  should_mark_seek = true;
238  new_cached_count = 0;
239  should_clear_cache = true;
240  }
241  else if (cache) {
242  // In-range paused seek preserves cache continuity.
243  should_mark_seek = false;
244  new_cached_count = cache->Count();
245  } else {
246  should_mark_seek = true;
247  }
248  entering_scrub = true;
249  } else {
250  // During playback, keep seek/scrub side effects minimal.
251  should_mark_seek = false;
252  should_preroll = false;
253  should_clear_cache = false;
254  if (cache) {
255  new_cached_count = cache->Count();
256  }
257  leaving_scrub = true;
258  }
259  }
260 
261  {
262  std::lock_guard<std::mutex> guard(seek_state_mutex);
263  // Reset readiness baseline only when rebuilding cache.
264  const int dir = computeDirection();
265  if (should_mark_seek || should_preroll || should_clear_cache) {
266  last_cached_index.store(clamped_new_position - dir);
267  }
268  requested_display_frame.store(new_position);
269  cached_frame_count.store(new_cached_count);
270  preroll_on_next_fill.store(should_preroll);
271  // Clear behavior follows the latest seek intent.
272  clear_cache_on_next_fill.store(should_clear_cache);
273  userSeeked.store(should_mark_seek);
274  if (entering_scrub) {
275  scrub_active.store(true);
276  }
277  if (leaving_scrub) {
278  scrub_active.store(false);
279  }
280  }
281  }
282 
283  void VideoCacheThread::Seek(int64_t new_position)
284  {
285  NotifyPlaybackPosition(new_position);
286  }
287 
288  void VideoCacheThread::NotifyPlaybackPosition(int64_t new_position)
289  {
290  if (new_position <= 0) {
291  return;
292  }
293  if (scrub_active.load()) {
294  return;
295  }
296 
297  int64_t new_cached_count = cached_frame_count.load();
298  if (CacheBase* cache = reader ? reader->GetCache() : nullptr) {
299  new_cached_count = cache->Count();
300  }
301  {
302  std::lock_guard<std::mutex> guard(seek_state_mutex);
303  requested_display_frame.store(new_position);
304  cached_frame_count.store(new_cached_count);
305  }
306  }
307 
309  {
310  // If speed ≠ 0, use its sign; if speed==0, keep last_dir
311  const int current_speed = speed.load();
312  if (current_speed != 0) {
313  return (current_speed > 0 ? 1 : -1);
314  }
315  return last_dir.load();
316  }
317 
318  void VideoCacheThread::handleUserSeek(int64_t playhead, int dir)
319  {
320  // Place last_cached_index just “behind” playhead in the given dir
321  last_cached_index.store(playhead - dir);
322  }
323 
325  int dir,
326  int64_t timeline_end,
327  int64_t preroll_frames)
328  {
329  int64_t preroll_start = playhead;
330  if (preroll_frames > 0) {
331  if (dir > 0) {
332  preroll_start = std::max<int64_t>(1, playhead - preroll_frames);
333  }
334  else {
335  preroll_start = std::min<int64_t>(timeline_end, playhead + preroll_frames);
336  }
337  }
338  last_cached_index.store(preroll_start - dir);
339  }
340 
341  int64_t VideoCacheThread::computePrerollFrames(const Settings* settings) const
342  {
343  if (!settings) {
344  return 0;
345  }
346  int64_t min_frames = settings->VIDEO_CACHE_MIN_PREROLL_FRAMES;
347  int64_t max_frames = settings->VIDEO_CACHE_MAX_PREROLL_FRAMES;
348  if (min_frames < 0) {
349  return 0;
350  }
351  if (max_frames > 0 && min_frames > max_frames) {
352  min_frames = max_frames;
353  }
354  return min_frames;
355  }
356 
358  {
359  if (!reader) {
360  return 0;
361  }
362  int64_t timeline_end = reader->info.video_length;
363  if (auto* timeline = dynamic_cast<Timeline*>(reader)) {
364  const int64_t timeline_max = timeline->GetMaxFrame();
365  if (timeline_max > 0) {
366  timeline_end = timeline_max;
367  }
368  }
369  return timeline_end;
370  }
371 
372  int64_t VideoCacheThread::clampToTimelineRange(int64_t frame, int64_t timeline_end) const
373  {
374  if (timeline_end < 1) {
375  return frame;
376  }
377  return std::clamp<int64_t>(frame, 1, timeline_end);
378  }
379 
381  bool paused,
382  CacheBase* cache)
383  {
384  const int64_t timeline_end = resolveTimelineEnd();
385  int64_t cache_playhead = playhead;
386  if (reader) {
387  cache_playhead = clampToTimelineRange(playhead, timeline_end);
388  }
389  if (paused && !cache->Contains(cache_playhead)) {
390  // If paused and playhead not in cache, clear everything
391  if (Timeline* timeline = dynamic_cast<Timeline*>(reader)) {
392  timeline->ClearAllCache();
393  }
394  cached_frame_count.store(0);
395  return true;
396  }
397  return false;
398  }
399 
401  int dir,
402  int64_t ahead_count,
403  int64_t timeline_end,
404  int64_t& window_begin,
405  int64_t& window_end) const
406  {
407  if (dir > 0) {
408  // Forward window: [playhead ... playhead + ahead_count]
409  window_begin = playhead;
410  window_end = playhead + ahead_count;
411  }
412  else {
413  // Backward window: [playhead - ahead_count ... playhead]
414  window_begin = playhead - ahead_count;
415  window_end = playhead;
416  }
417  // Clamp to [1 ... timeline_end]
418  window_begin = std::max<int64_t>(window_begin, 1);
419  window_end = std::min<int64_t>(window_end, timeline_end);
420  }
421 
423  int64_t window_begin,
424  int64_t window_end,
425  int dir,
426  ReaderBase* reader,
427  int64_t max_frames_to_fetch)
428  {
429  bool window_full = true;
430  int64_t next_frame = last_cached_index.load() + dir;
431  int64_t fetched_this_pass = 0;
432 
433  // Advance from last_cached_index toward window boundary
434  while ((dir > 0 && next_frame <= window_end) ||
435  (dir < 0 && next_frame >= window_begin))
436  {
437  if (threadShouldExit()) {
438  break;
439  }
440  // If a Seek was requested mid-caching, bail out immediately
441  if (userSeeked.load()) {
442  break;
443  }
444 
445  if (!cache->Contains(next_frame)) {
446  // Frame missing, fetch and add
447  try {
448  auto framePtr = reader->GetFrame(next_frame);
449  cache->Add(framePtr);
450  cached_frame_count.store(cache->Count());
451  ++fetched_this_pass;
452  }
453  catch (const OutOfBoundsFrame&) {
454  break;
455  }
456  window_full = false;
457  }
458  else {
459  cache->Touch(next_frame);
460  }
461 
462  last_cached_index.store(next_frame);
463  next_frame += dir;
464 
465  // In active playback, avoid long uninterrupted prefetch bursts
466  // that can delay player thread frame retrieval.
467  if (max_frames_to_fetch > 0 && fetched_this_pass >= max_frames_to_fetch) {
468  break;
469  }
470  }
471 
472  return window_full;
473  }
474 
476  {
477  using micro_sec = std::chrono::microseconds;
478  using double_micro_sec = std::chrono::duration<double, micro_sec::period>;
479 
480  while (!threadShouldExit()) {
481  Settings* settings = Settings::Instance();
482  CacheBase* cache = reader ? reader->GetCache() : nullptr;
483  Timeline* timeline = dynamic_cast<Timeline*>(reader);
484 
485  // Process deferred clears even when caching is currently disabled
486  // (e.g. active scrub mode), so stale ranges are removed promptly.
487  bool should_clear_cache = clear_cache_on_next_fill.exchange(false);
488  if (should_clear_cache && timeline) {
489  const int dir_on_clear = computeDirection();
490  const int64_t clear_playhead = clampToTimelineRange(
492  timeline->ClearAllCache();
493  cached_frame_count.store(0);
494  // Reset ready baseline immediately after clear. Otherwise a
495  // stale last_cached_index from the old cache window can make
496  // isReady() report true before new preroll is actually filled.
497  last_cached_index.store(clear_playhead - dir_on_clear);
498  }
499 
500  // If caching disabled or no reader, mark cache as ready and sleep briefly
501  if (!settings->ENABLE_PLAYBACK_CACHING || !cache) {
502  cached_frame_count.store(cache ? cache->Count() : 0);
503  min_frames_ahead.store(-1);
504  std::this_thread::sleep_for(double_micro_sec(50000));
505  continue;
506  }
507 
508  // init local vars
510 
511  if (!timeline) {
512  std::this_thread::sleep_for(double_micro_sec(50000));
513  continue;
514  }
515  int64_t timeline_end = resolveTimelineEnd();
516  int64_t raw_playhead = requested_display_frame.load();
517  int64_t playhead = clampToTimelineRange(raw_playhead, timeline_end);
518  bool paused = (speed.load() == 0);
519  int64_t preroll_frames = computePrerollFrames(settings);
520 
521  cached_frame_count.store(cache->Count());
522 
523  // Compute effective direction (±1)
524  int dir = computeDirection();
525  if (speed.load() != 0) {
526  last_dir.store(dir);
527  }
528 
529  // If timeline-side cache invalidation occurred (e.g. ApplyJsonDiff / SetJson),
530  // restart fill from the active playhead window so invalidated gaps self-heal.
531  if (timeline) {
532  bool epoch_changed = false;
533  {
534  std::lock_guard<std::mutex> guard(seek_state_mutex);
535  const uint64_t timeline_epoch = timeline->CacheEpoch();
537  seen_timeline_cache_epoch = timeline_epoch;
539  }
540  else if (timeline_epoch != seen_timeline_cache_epoch) {
541  seen_timeline_cache_epoch = timeline_epoch;
542  epoch_changed = true;
543  }
544  }
545  if (epoch_changed) {
546  handleUserSeek(playhead, dir);
547  }
548  }
549 
550  // Compute bytes_per_frame, max_bytes, and capacity once
551  int64_t bytes_per_frame = getBytes(
552  (timeline->preview_width ? timeline->preview_width : reader->info.width),
553  (timeline->preview_height ? timeline->preview_height : reader->info.height),
557  );
558  int64_t max_bytes = cache->GetMaxBytes();
559  int64_t capacity = 0;
560  if (max_bytes > 0 && bytes_per_frame > 0) {
561  capacity = max_bytes / bytes_per_frame;
562  if (capacity > settings->VIDEO_CACHE_MAX_FRAMES) {
563  capacity = settings->VIDEO_CACHE_MAX_FRAMES;
564  }
565  }
566 
567  // Handle a user-initiated seek
568  bool did_user_seek = false;
569  bool use_preroll = false;
570  {
571  std::lock_guard<std::mutex> guard(seek_state_mutex);
572  raw_playhead = requested_display_frame.load();
573  playhead = clampToTimelineRange(raw_playhead, timeline_end);
574  did_user_seek = userSeeked.load();
575  use_preroll = preroll_on_next_fill.load();
576  if (did_user_seek) {
577  userSeeked.store(false);
578  preroll_on_next_fill.store(false);
579  }
580  }
581  if (did_user_seek) {
582  // During active playback, prioritize immediate forward readiness
583  // from the playhead. Use directional preroll offset only while
584  // paused/scrubbing contexts.
585  if (use_preroll && paused) {
586  handleUserSeekWithPreroll(playhead, dir, timeline_end, preroll_frames);
587  }
588  else {
589  handleUserSeek(playhead, dir);
590  }
591  }
592  else if (!paused && capacity >= 1) {
593  // In playback mode, check if last_cached_index drifted outside the new window
594  int64_t base_ahead = static_cast<int64_t>(capacity * settings->VIDEO_CACHE_PERCENT_AHEAD);
595 
596  int64_t window_begin, window_end;
598  playhead,
599  dir,
600  base_ahead,
601  timeline_end,
602  window_begin,
603  window_end
604  );
605 
606  bool outside_window =
607  (dir > 0 && last_cached_index.load() > window_end) ||
608  (dir < 0 && last_cached_index.load() < window_begin);
609  if (outside_window) {
610  handleUserSeek(playhead, dir);
611  }
612  }
613 
614  // If a clear was requested by a seek that arrived after the loop
615  // began, apply it now before any additional prefetch work. This
616  // avoids "build then suddenly clear" behavior during playback.
617  bool should_clear_mid_loop = clear_cache_on_next_fill.exchange(false);
618  if (should_clear_mid_loop && timeline) {
619  timeline->ClearAllCache();
620  cached_frame_count.store(0);
621  last_cached_index.store(playhead - dir);
622  }
623 
624  // While user is dragging/scrubbing, skip cache prefetch work.
625  if (scrub_active.load()) {
626  std::this_thread::sleep_for(double_micro_sec(10000));
627  continue;
628  }
629 
630  // If capacity is insufficient, sleep and retry
631  if (capacity < 1) {
632  std::this_thread::sleep_for(double_micro_sec(50000));
633  continue;
634  }
635  int64_t ahead_count = static_cast<int64_t>(capacity *
636  settings->VIDEO_CACHE_PERCENT_AHEAD);
637  int64_t window_size = ahead_count + 1;
638  if (window_size < 1) {
639  window_size = 1;
640  }
641  int64_t ready_target = window_size - 1;
642  if (ready_target < 0) {
643  ready_target = 0;
644  }
645  int64_t configured_min = settings->VIDEO_CACHE_MIN_PREROLL_FRAMES;
646  const int64_t required_ahead = std::min<int64_t>(configured_min, ready_target);
647  min_frames_ahead.store(required_ahead);
648 
649  // If paused and playhead is no longer in cache, clear everything
650  bool did_clear = clearCacheIfPaused(playhead, paused, cache);
651  if (did_clear) {
652  handleUserSeekWithPreroll(playhead, dir, timeline_end, preroll_frames);
653  }
654 
655  // Compute the current caching window
656  int64_t window_begin, window_end;
657  computeWindowBounds(playhead,
658  dir,
659  ahead_count,
660  timeline_end,
661  window_begin,
662  window_end);
663 
664  // Attempt to fill any missing frames in that window
665  int64_t max_frames_to_fetch = -1;
666  if (!paused) {
667  // Keep cache thread responsive during playback seeks so player
668  // can start as soon as pre-roll is met instead of waiting for a
669  // full cache window pass.
670  max_frames_to_fetch = 8;
671  }
672  bool window_full = prefetchWindow(
673  cache,
674  window_begin,
675  window_end,
676  dir,
677  reader,
678  max_frames_to_fetch
679  );
680 
681  // If paused and window was already full, keep playhead fresh
682  if (paused && window_full) {
683  cache->Touch(playhead);
684  }
685 
686  // Sleep a short fraction of a frame interval
687  int64_t sleep_us = static_cast<int64_t>(
688  1000000.0 / reader->info.fps.ToFloat() / 4.0
689  );
690  std::this_thread::sleep_for(double_micro_sec(sleep_us));
691  }
692  }
693 
694 } // namespace openshot
Settings.h
Header file for global Settings class.
openshot::ReaderInfo::sample_rate
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60
openshot::VideoCacheThread::VideoCacheThread
VideoCacheThread()
Constructor: initializes member variables and assumes forward direction on first launch.
Definition: VideoCacheThread.cpp:26
openshot::Fraction::ToFloat
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:35
openshot::Settings::VIDEO_CACHE_PERCENT_AHEAD
float VIDEO_CACHE_PERCENT_AHEAD
Percentage of cache in front of the playhead (0.0 to 1.0)
Definition: Settings.h:89
openshot::VideoCacheThread::seek_state_mutex
std::mutex seek_state_mutex
Protects coherent seek state updates/consumption.
Definition: VideoCacheThread.h:212
openshot::TimelineBase::preview_width
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:45
openshot::VideoCacheThread::StartThread
bool StartThread()
Start the cache thread at high priority. Returns true if it’s actually running.
Definition: VideoCacheThread.cpp:121
openshot::VideoCacheThread::last_speed
std::atomic< int > last_speed
Last non-zero speed (for tracking).
Definition: VideoCacheThread.h:192
openshot::ReaderBase::GetFrame
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
openshot::VideoCacheThread::clampToTimelineRange
int64_t clampToTimelineRange(int64_t frame, int64_t timeline_end) const
Clamp frame index to [1, timeline_end] when timeline_end is valid.
Definition: VideoCacheThread.cpp:372
openshot::VideoCacheThread::preroll_on_next_fill
std::atomic< bool > preroll_on_next_fill
True if next cache rebuild should include preroll offset.
Definition: VideoCacheThread.h:195
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::TimelineBase::preview_height
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:46
openshot::CacheBase::Add
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
openshot::VideoCacheThread::last_cached_index
std::atomic< int64_t > last_cached_index
Index of the most recently cached frame.
Definition: VideoCacheThread.h:211
openshot::VideoCacheThread::computeDirection
int computeDirection() const
Definition: VideoCacheThread.cpp:308
openshot::VideoCacheThread::reader
ReaderBase * reader
The source reader (e.g., Timeline, FFmpegReader).
Definition: VideoCacheThread.h:206
openshot::ReaderBase::info
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:88
openshot::Settings
This class is contains settings used by libopenshot (and can be safely toggled at any point)
Definition: Settings.h:26
Timeline.h
Header file for Timeline class.
openshot::VideoCacheThread::handleUserSeek
void handleUserSeek(int64_t playhead, int dir)
If userSeeked is true, reset last_cached_index just behind the playhead.
Definition: VideoCacheThread.cpp:318
openshot::Timeline::ClearAllCache
void ClearAllCache(bool deep=false)
Definition: Timeline.cpp:1778
openshot::VideoCacheThread::computePrerollFrames
int64_t computePrerollFrames(const Settings *settings) const
Compute preroll frame count from settings.
Definition: VideoCacheThread.cpp:341
openshot::Settings::ENABLE_PLAYBACK_CACHING
bool ENABLE_PLAYBACK_CACHING
Enable/Disable the cache thread to pre-fetch and cache video frames before we need them.
Definition: Settings.h:101
openshot::ReaderInfo::width
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
openshot::CacheBase
All cache managers in libopenshot are based on this CacheBase class.
Definition: CacheBase.h:34
openshot::VideoCacheThread::Play
void Play()
Play method is unimplemented.
Definition: VideoCacheThread.h:48
openshot::Settings::VIDEO_CACHE_MAX_FRAMES
int VIDEO_CACHE_MAX_FRAMES
Max number of frames (when paused) to cache for playback.
Definition: Settings.h:98
CacheBase.h
Header file for CacheBase class.
openshot::VideoCacheThread::cached_frame_count
std::atomic< int64_t > cached_frame_count
Estimated count of frames currently stored in cache.
Definition: VideoCacheThread.h:201
openshot::OutOfBoundsFrame
Exception for frames that are out of bounds.
Definition: Exceptions.h:306
openshot::CacheBase::Remove
virtual void Remove(int64_t frame_number)=0
Remove a specific frame.
openshot::VideoCacheThread::clear_cache_on_next_fill
std::atomic< bool > clear_cache_on_next_fill
True if next cache loop should clear existing cache ranges.
Definition: VideoCacheThread.h:196
openshot::VideoCacheThread::prefetchWindow
bool prefetchWindow(CacheBase *cache, int64_t window_begin, int64_t window_end, int dir, ReaderBase *reader, int64_t max_frames_to_fetch=-1)
Prefetch all missing frames in [window_begin ... window_end] or [window_end ... window_begin].
Definition: VideoCacheThread.cpp:422
openshot::VideoCacheThread::~VideoCacheThread
~VideoCacheThread() override
Definition: VideoCacheThread.cpp:49
openshot::ReaderInfo::video_length
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
openshot::ReaderInfo::height
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
openshot::Settings::VIDEO_CACHE_MAX_PREROLL_FRAMES
int VIDEO_CACHE_MAX_PREROLL_FRAMES
Max number of frames (ahead of playhead) to cache during playback.
Definition: Settings.h:95
openshot::VideoCacheThread::resolveTimelineEnd
int64_t resolveTimelineEnd() const
Resolve timeline end frame from reader/timeline metadata.
Definition: VideoCacheThread.cpp:357
openshot::VideoCacheThread::userSeeked
std::atomic< bool > userSeeked
True if Seek(..., true) was called (forces a cache reset).
Definition: VideoCacheThread.h:194
openshot::Settings::VIDEO_CACHE_MIN_PREROLL_FRAMES
int VIDEO_CACHE_MIN_PREROLL_FRAMES
Minimum number of frames to cache before playback begins.
Definition: Settings.h:92
openshot::Timeline
This class represents a timeline.
Definition: Timeline.h:152
openshot::VideoCacheThread::setSpeed
void setSpeed(int new_speed)
Set playback speed/direction. Positive = forward, negative = rewind, zero = pause.
Definition: VideoCacheThread.cpp:94
openshot::VideoCacheThread::seen_timeline_cache_epoch
uint64_t seen_timeline_cache_epoch
Last observed Timeline cache invalidation epoch.
Definition: VideoCacheThread.h:208
openshot::VideoCacheThread::speed
std::atomic< int > speed
Current playback speed (0=paused, >0 forward, <0 backward).
Definition: VideoCacheThread.h:191
openshot::Timeline::CacheEpoch
uint64_t CacheEpoch() const
Return the current cache invalidation epoch.
Definition: Timeline.h:320
openshot::VideoCacheThread::Reader
void Reader(ReaderBase *new_reader)
Attach a ReaderBase (e.g. Timeline, FFmpegReader) and begin caching.
Definition: VideoCacheThread.cpp:136
openshot::Settings::Instance
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: Settings.cpp:23
openshot::VideoCacheThread::requested_display_frame
std::atomic< int64_t > requested_display_frame
Frame index the user requested.
Definition: VideoCacheThread.h:199
openshot::VideoCacheThread::NotifyPlaybackPosition
void NotifyPlaybackPosition(int64_t new_position)
Update playback position without triggering seek behavior or cache invalidation.
Definition: VideoCacheThread.cpp:288
openshot::CacheBase::Touch
virtual void Touch(int64_t frame_number)=0
Move frame to front of queue (so it lasts longer)
openshot::VideoCacheThread::timeline_cache_epoch_initialized
bool timeline_cache_epoch_initialized
True once an initial epoch snapshot has been taken.
Definition: VideoCacheThread.h:209
Frame.h
Header file for Frame class.
openshot::VideoCacheThread::run
void run() override
Thread entry point: loops until threadShouldExit() is true.
Definition: VideoCacheThread.cpp:475
openshot::CacheBase::Count
virtual int64_t Count()=0
Count the frames in the queue.
VideoCacheThread.h
Header file for VideoCacheThread class.
openshot::VideoCacheThread::getBytes
int64_t getBytes(int width, int height, int sample_rate, int channels, float fps)
Estimate memory usage for a single frame (video + audio).
Definition: VideoCacheThread.cpp:107
openshot::VideoCacheThread::clearCacheIfPaused
bool clearCacheIfPaused(int64_t playhead, bool paused, CacheBase *cache)
When paused and playhead is outside current cache, clear all frames.
Definition: VideoCacheThread.cpp:380
openshot::VideoCacheThread::last_dir
std::atomic< int > last_dir
Last direction sign (+1 forward, –1 backward).
Definition: VideoCacheThread.h:193
openshot::VideoCacheThread::StopThread
bool StopThread(int timeoutMs=0)
Stop the cache thread (wait up to timeoutMs ms). Returns true if it stopped.
Definition: VideoCacheThread.cpp:130
openshot::CacheBase::GetMaxBytes
int64_t GetMaxBytes()
Gets the maximum bytes value.
Definition: CacheBase.h:101
openshot::ReaderInfo::fps
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
openshot::CacheBase::Contains
virtual bool Contains(int64_t frame_number)=0
Check if frame is already contained in cache.
openshot::ReaderBase
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:75
openshot::VideoCacheThread::computeWindowBounds
void computeWindowBounds(int64_t playhead, int dir, int64_t ahead_count, int64_t timeline_end, int64_t &window_begin, int64_t &window_end) const
Compute the “window” of frames to cache around playhead.
Definition: VideoCacheThread.cpp:400
openshot::VideoCacheThread::scrub_active
std::atomic< bool > scrub_active
True while user is dragging/scrubbing the playhead.
Definition: VideoCacheThread.h:197
openshot::VideoCacheThread::min_frames_ahead
std::atomic< int64_t > min_frames_ahead
Minimum number of frames considered “ready” (pre-roll).
Definition: VideoCacheThread.h:203
openshot::VideoCacheThread::Seek
void Seek(int64_t new_position)
Backward-compatible alias for playback position updates (no seek side effects).
Definition: VideoCacheThread.cpp:283
openshot::VideoCacheThread::handleUserSeekWithPreroll
void handleUserSeekWithPreroll(int64_t playhead, int dir, int64_t timeline_end, int64_t preroll_frames)
Reset last_cached_index to start caching with a directional preroll offset.
Definition: VideoCacheThread.cpp:324
openshot::ReaderInfo::channels
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::VideoCacheThread::isReady
bool isReady()
Definition: VideoCacheThread.cpp:54
openshot::ReaderBase::GetCache
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
Exceptions.h
Header file for all Exception classes.