Skip to content

Commit

Permalink
Report video decoding errors to the user (rerun-io#7496)
Browse files Browse the repository at this point in the history
### What
* Closes rerun-io#7488

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7496?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7496?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!
* [x] If have noted any breaking changes to the log API in
`CHANGELOG.md` and the migration guide

- [PR Build Summary](https://build.rerun.io/pr/7496)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
  • Loading branch information
emilk authored Sep 24, 2024
1 parent 7c7c712 commit 5776163
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 12 deletions.
46 changes: 35 additions & 11 deletions crates/viewer/re_renderer/src/video/decoder/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub struct VideoDecoder {
decoder: web_sys::VideoDecoder,

frames: Arc<Mutex<Vec<BufferedFrame>>>,
decode_error: Arc<Mutex<Option<DecodingError>>>,

last_used_frame_timestamp: Time,
current_segment_idx: usize,
current_sample_idx: usize,
Expand Down Expand Up @@ -97,10 +99,13 @@ impl VideoDecoder {
data: Arc<re_video::VideoData>,
) -> Result<Self, DecodingError> {
let frames = Arc::new(Mutex::new(Vec::with_capacity(16)));
let decode_error = Arc::new(Mutex::new(None));

let timescale = data.timescale;

let decoder = init_video_decoder({
let on_frame = {
let frames = frames.clone();
let decode_error = decode_error.clone();
move |frame: web_sys::VideoFrame| {
let composition_timestamp =
Time::from_micros(frame.timestamp().unwrap_or(0.0), timescale);
Expand All @@ -111,8 +116,16 @@ impl VideoDecoder {
duration,
inner: frame,
});
*decode_error.lock() = None; // clear error on success
}
})?;
};
let on_error = {
let decode_error = decode_error.clone();
move |err| {
*decode_error.lock() = Some(DecodingError::Decoding(err));
}
};
let decoder = init_video_decoder(on_frame, on_error)?;

let queue = render_context.queue.clone();

Expand All @@ -132,6 +145,8 @@ impl VideoDecoder {
decoder,

frames,
decode_error,

last_used_frame_timestamp: Time::new(u64::MAX),
current_segment_idx: usize::MAX,
current_sample_idx: usize::MAX,
Expand All @@ -145,6 +160,14 @@ impl VideoDecoder {
render_ctx: &RenderContext,
timestamp_s: f64,
) -> FrameDecodingResult {
if let Some(error) = self.decode_error.lock().clone() {
// TODO(emilk): if there is a decoding error in one segment or sample,
// then we currently never try decoding any more samples because of this early-out here.
// We should fix this, and test it with a video that has some broken segments/samples
// in the middle, but then are fine again.
return FrameDecodingResult::Error(error);
}

let result = self.frame_at_internal(timestamp_s);
match &result {
FrameDecodingResult::Ready(_) => {
Expand Down Expand Up @@ -328,17 +351,16 @@ impl VideoDecoder {
chunk.set_duration(sample.duration.into_micros(self.data.timescale));
let Some(chunk) = EncodedVideoChunk::new(&chunk)
.inspect_err(|err| {
// TODO(#7373): return this error once the decoder tries to return a frame for this sample. how exactly?
re_log::error_once!("failed to create video chunk: {}", js_error_to_string(err));
*self.decode_error.lock() =
Some(DecodingError::CreateChunk(js_error_to_string(err)));
})
.ok()
else {
return;
};

if let Err(err) = self.decoder.decode(&chunk) {
// TODO(#7373): return this error once the decoder tries to return a frame for this sample. how exactly?
re_log::error_once!("Failed to decode video chunk: {}", js_error_to_string(&err));
*self.decode_error.lock() = Some(DecodingError::DecodeChunk(js_error_to_string(&err)));
}
}

Expand Down Expand Up @@ -413,13 +435,15 @@ fn copy_video_frame_to_texture(

fn init_video_decoder(
on_output: impl Fn(web_sys::VideoFrame) + 'static,
on_error: impl Fn(String) + 'static,
) -> Result<web_sys::VideoDecoder, DecodingError> {
let on_output = Closure::wrap(Box::new(on_output) as Box<dyn Fn(web_sys::VideoFrame)>);
let on_error = Closure::wrap(Box::new(|err: js_sys::Error| {
// TODO(#7373): store this error and report during decode
let err = std::string::ToString::to_string(&err.to_string());
re_log::error!("failed to decode video: {err}");
}) as Box<dyn Fn(js_sys::Error)>);

let on_error =
Closure::wrap(
Box::new(move |err: js_sys::Error| on_error(js_error_to_string(&err)))
as Box<dyn Fn(js_sys::Error)>,
);

let Ok(on_output) = on_output.into_js_value().dyn_into::<Function>() else {
unreachable!()
Expand Down
14 changes: 13 additions & 1 deletion crates/viewer/re_renderer/src/video/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{resource_managers::GpuTexture2D, RenderContext};

/// Error that can occur during frame decoding.
// TODO(jan, andreas): These errors are for the most part specific to the web decoder right now.
#[derive(thiserror::Error, Debug)]
#[derive(thiserror::Error, Debug, Clone)]
pub enum DecodingError {
// TODO(#7298): Native support.
#[error("Video playback not yet available in the native viewer. Try the web viewer instead.")]
Expand All @@ -31,6 +31,18 @@ pub enum DecodingError {
#[error("Failed to configure the video decoder: {0}")]
ConfigureFailure(String),

// e.g. unsupported codec
#[error("Failed to create video chunk: {0}")]
CreateChunk(String),

// e.g. unsupported codec
#[error("Failed to decode video chunk: {0}")]
DecodeChunk(String),

// e.g. unsupported codec
#[error("Failed to decode video: {0}")]
Decoding(String),

#[error("The timestamp passed was negative.")]
NegativeTimestamp,
}
Expand Down

0 comments on commit 5776163

Please sign in to comment.