| 473 | } |
| 474 | |
| 475 | void VideoCacheThread::run() |
| 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( |
| 491 | requested_display_frame.load(), resolveTimelineEnd()); |
| 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 |
| 509 | min_frames_ahead.store(settings->VIDEO_CACHE_MIN_PREROLL_FRAMES); |
| 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; |
nothing calls this directly
no test coverage detected