| 68 | } |
| 69 | |
| 70 | void EntryThumbnailCache::buildThread(Timestamp start_ns, Timestamp end_ns) { |
| 71 | // Own decoder + own extractor (the build runs concurrently with playback). |
| 72 | StreamingVideoDecoder decoder; |
| 73 | decoder.attach(store_, topic_, extractor_); |
| 74 | |
| 75 | auto range = store_->timeRange(topic_); |
| 76 | const Timestamp lo = (end_ns >= start_ns) ? start_ns : range.first; |
| 77 | const Timestamp hi = (end_ns >= start_ns) ? end_ns : range.second; |
| 78 | const int64_t span = std::max<int64_t>(1, hi - lo); |
| 79 | // ~1 thumbnail per second, but never more than max_tiles over the whole span |
| 80 | // (a long clip just gets a coarser interval instead of more tiles). |
| 81 | const int64_t interval = |
| 82 | std::max<int64_t>(kOneSecondNs, span / static_cast<int64_t>(std::max<std::size_t>(1, cfg_.max_tiles))); |
| 83 | |
| 84 | // Single forward decode pass; the decoder surfaces ~1 frame per interval in |
| 85 | // ascending PTS order. We encode each and stop at the tile / byte budget. |
| 86 | bool resolution_checked = false; |
| 87 | decoder.decodeSampled(interval, [&](const DecodedFrame& f) -> bool { |
| 88 | if (!running_.load()) { |
| 89 | return false; |
| 90 | } |
| 91 | if (!resolution_checked) { |
| 92 | resolution_checked = true; |
| 93 | // Resolution gate: if the source is already <= our cap, the downscale is a |
| 94 | // no-op and on-scrub decode is cheap, so a thumbnail track buys nothing. |
| 95 | if (f.width <= cfg_.max_width) { |
| 96 | return false; |
| 97 | } |
| 98 | } |
| 99 | JpegThumbnail thumb = encodeThumbnailJpeg(f, cfg_.max_width, cfg_.quality); |
| 100 | if (thumb.jpeg.empty()) { |
| 101 | return true; |
| 102 | } |
| 103 | std::lock_guard<std::mutex> lock(mutex_); |
| 104 | if (bytes_ + thumb.jpeg.size() > cfg_.max_bytes) { |
| 105 | return false; // hard ceiling |
| 106 | } |
| 107 | bytes_ += thumb.jpeg.size(); |
| 108 | tiles_.push_back({f.pts, std::move(thumb.jpeg), thumb.width, thumb.height}); |
| 109 | return tiles_.size() < cfg_.max_tiles; |
| 110 | }); |
| 111 | |
| 112 | // decodeSampled emits ascending PTS, but sort defensively for lookup()'s |
| 113 | // binary search (drain order is not guaranteed). |
| 114 | { |
| 115 | std::lock_guard<std::mutex> lock(mutex_); |
| 116 | std::sort(tiles_.begin(), tiles_.end(), [](const Tile& a, const Tile& b) { return a.ts < b.ts; }); |
| 117 | } |
| 118 | running_.store(false); |
| 119 | } |
| 120 | |
| 121 | } // namespace PJ |
nothing calls this directly
no test coverage detected