| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: MIT |
| 3 | |
| 4 | slint::include_modules!(); |
| 5 | |
| 6 | use ffmpeg_next::format::Pixel; |
| 7 | |
| 8 | mod player; |
| 9 | |
| 10 | fn main() { |
| 11 | let app = App::new().unwrap(); |
| 12 | |
| 13 | let mut to_rgba_rescaler: Option<Rescaler> = None; |
| 14 | |
| 15 | let mut player = player::Player::start( |
| 16 | "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4" .into(), |
| 17 | { |
| 18 | let app_weak = app.as_weak(); |
| 19 | |
| 20 | move |new_frame| { |
| 21 | // TODO: use OpenGL bridge |
| 22 | |
| 23 | let rebuild_rescaler = |
| 24 | to_rgba_rescaler.as_ref().map_or(true, |existing_rescaler| { |
| 25 | existing_rescaler.input().format != new_frame.format() |
| 26 | }); |
| 27 | |
| 28 | if rebuild_rescaler { |
| 29 | to_rgba_rescaler = Some(rgba_rescaler_for_frame(new_frame)); |
| 30 | } |
| 31 | |
| 32 | let rescaler = to_rgba_rescaler.as_mut().unwrap(); |
| 33 | |
| 34 | let mut rgb_frame = ffmpeg_next::util::frame::Video::empty(); |
| 35 | rescaler.run(&new_frame, &mut rgb_frame).unwrap(); |
| 36 | |
| 37 | let pixel_buffer = video_frame_to_pixel_buffer(&rgb_frame); |
| 38 | app_weak |
| 39 | .upgrade_in_event_loop(|app| { |
| 40 | app.set_video_frame(slint::Image::from_rgb8(pixel_buffer)) |
| 41 | }) |
| 42 | .unwrap(); |
| 43 | } |
| 44 | }, |
| 45 | { |
| 46 | let app_weak = app.as_weak(); |
| 47 | |
| 48 | move |playing| { |
| 49 | app_weak.upgrade_in_event_loop(move |app| app.set_playing(playing)).unwrap(); |
| 50 | } |
| 51 | }, |
| 52 | ) |
| 53 | .unwrap(); |
| 54 | |
| 55 | app.on_toggle_pause_play(move || { |
| 56 | player.toggle_pause_playing(); |
| 57 | }); |
| 58 | |
| 59 | app.run().unwrap(); |
| 60 | } |
| 61 | |
| 62 | // Work around https://github.com/zmwangx/rust-ffmpeg/issues/102 |
| 63 | #[derive (derive_more::Deref, derive_more::DerefMut)] |
| 64 | struct Rescaler(ffmpeg_next::software::scaling::Context); |
| 65 | unsafe impl std::marker::Send for Rescaler {} |
| 66 | |
| 67 | fn rgba_rescaler_for_frame(frame: &ffmpeg_next::util::frame::Video) -> Rescaler { |
| 68 | Rescaler( |
| 69 | ffmpeg_nextResult::software::scaling::Context::get( |
| 70 | src_format:frame.format(), |
| 71 | src_w:frame.width(), |
| 72 | src_h:frame.height(), |
| 73 | dst_format:Pixel::RGB24, |
| 74 | dst_w:frame.width(), |
| 75 | dst_h:frame.height(), |
| 76 | flags:ffmpeg_next::software::scaling::Flags::BILINEAR, |
| 77 | ) |
| 78 | .unwrap(), |
| 79 | ) |
| 80 | } |
| 81 | |
| 82 | fn video_frame_to_pixel_buffer( |
| 83 | frame: &ffmpeg_next::util::frame::Video, |
| 84 | ) -> slint::SharedPixelBuffer<slint::Rgb8Pixel> { |
| 85 | let mut pixel_buffer: SharedPixelBuffer> = |
| 86 | slint::SharedPixelBuffer::<slint::Rgb8Pixel>::new(frame.width(), frame.height()); |
| 87 | |
| 88 | let ffmpeg_line_iter: ChunksExact<'_, u8> = frame.data(0).chunks_exact(chunk_size:frame.stride(index:0)); |
| 89 | let slint_pixel_line_iter: ChunksMut<'_, u8> = pixel_buffer |
| 90 | .make_mut_bytes() |
| 91 | .chunks_mut(chunk_size:frame.width() as usize * core::mem::size_of::<slint::Rgb8Pixel>()); |
| 92 | |
| 93 | for (source_line: &[u8], dest_line: &mut [u8]) in ffmpeg_line_iter.zip(slint_pixel_line_iter) { |
| 94 | dest_line.copy_from_slice(&source_line[..dest_line.len()]) |
| 95 | } |
| 96 | |
| 97 | pixel_buffer |
| 98 | } |
| 99 | |