| 113 | } |
| 114 | |
| 115 | void StreamingVideoSource::decodeRequest(const AsyncFrameWorker::Request& request, AsyncFrameWorker& worker) { |
| 116 | const int64_t ts = request.target_ns; |
| 117 | |
| 118 | // Instant scrub preview: on a backward seek or a large forward jump, publish |
| 119 | // the nearest thumbnail right away so the drag has feedback while the (slow, |
| 120 | // 4K) GOP decode runs. The full-res frame replaces it below when ready. |
| 121 | if (thumbnail_cache_ && isScrub(last_decoded_ts_, ts)) { |
| 122 | if (auto preview = thumbnail_cache_->lookup(ts); preview.has_value()) { |
| 123 | worker.deposit(std::move(*preview)); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | auto result = decoder_->decodeAt(ts, request.cancel); |
| 128 | if (result.has_value() && !result->isNull()) { |
| 129 | last_decoded_ts_ = ts; |
| 130 | last_decode_error_.clear(); // recovered — let the next genuine failure log again |
| 131 | worker.deposit(std::move(*result)); |
| 132 | } else if (!result.has_value() && result.error() != "cancelled") { |
| 133 | // Surface a genuine decode failure (unsupported codec, evicted keyframe, |
| 134 | // parser/extractor error) instead of leaving a frozen frame with no clue. |
| 135 | // "cancelled" is the benign latest-wins preemption and is skipped. |
| 136 | if (result.error() != last_decode_error_) { |
| 137 | last_decode_error_ = result.error(); |
| 138 | fprintf( |
| 139 | stderr, "[StreamingVideoSource] decode failed @ts=%lld: %s\n", static_cast<long long>(ts), |
| 140 | result.error().c_str()); |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | } // namespace PJ |