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 | |