1use alloc::vec::Vec;
13
14use azul_core::callbacks::Update;
15use azul_core::dom::{ComponentEventFilter, DatasetMergeCallbackType, Dom, EventFilter};
16use azul_core::refany::{OptionRefAny, RefAny};
17use azul_core::resources::{ImageRef, RawImageFormat};
18use azul_core::task::{ThreadId, ThreadReceiver};
19use azul_core::video::{VideoConfig, VideoFrame};
20
21use super::capture_common::{
22 invoke_on_frame, present_frame, OnVideoFrame, OnVideoFrameCallback, OptionOnVideoFrame,
23};
24use crate::callbacks::{Callback, CallbackInfo, CallbackType};
25use crate::thread::{
26 Thread, ThreadCallback, ThreadReceiveMsg, ThreadSender, ThreadWriteBackMsg, WriteBackCallback,
27};
28
29const DEFAULT_W: u32 = 1280;
32const DEFAULT_H: u32 = 720;
33
34pub struct VideoWidgetState {
37 pub config: VideoConfig,
39 pub started: bool,
41 pub gl_texture_id: Option<u32>,
43 pub on_frame: OptionOnVideoFrame,
46}
47
48#[repr(C)]
51pub struct VideoWidget {
52 pub config: VideoConfig,
54 pub on_frame: OptionOnVideoFrame,
56}
57
58impl VideoWidget {
59 pub fn create(config: VideoConfig) -> Self {
61 Self {
62 config,
63 on_frame: OptionOnVideoFrame::None,
64 }
65 }
66
67 pub fn set_on_frame<C: Into<OnVideoFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
71 self.on_frame = Some(OnVideoFrame {
72 refany: data,
73 callback: on_frame.into(),
74 })
75 .into();
76 }
77
78 pub fn with_on_frame<C: Into<OnVideoFrameCallback>>(
80 mut self,
81 data: RefAny,
82 on_frame: C,
83 ) -> Self {
84 self.set_on_frame(data, on_frame);
85 self
86 }
87
88 pub fn dom(self) -> Dom {
91 let state = VideoWidgetState {
92 config: self.config,
93 started: false,
94 gl_texture_id: None,
95 on_frame: self.on_frame,
96 };
97 let dataset = RefAny::new(state);
98
99 let placeholder = ImageRef::null_image(
100 DEFAULT_W as usize,
101 DEFAULT_H as usize,
102 RawImageFormat::BGRA8,
103 b"azul-video-placeholder".to_vec(),
104 );
105
106 Dom::create_image(placeholder)
107 .with_dataset(OptionRefAny::Some(dataset.clone()))
108 .with_merge_callback(merge_video_state as DatasetMergeCallbackType)
109 .with_callback(
110 EventFilter::Component(ComponentEventFilter::AfterMount),
111 dataset,
112 Callback::from(video_on_after_mount as CallbackType),
113 )
114 }
115}
116
117extern "C" fn video_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
119 {
120 let mut s = match data.downcast_mut::<VideoWidgetState>() {
121 Some(s) => s,
122 None => return Update::DoNothing,
123 };
124 if s.started {
125 return Update::DoNothing;
126 }
127 s.started = true;
128 }
129 info.add_thread(
130 ThreadId::unique(),
131 Thread::create(
132 RefAny::new(()),
133 data.clone(),
134 ThreadCallback::new(video_test_worker),
135 ),
136 );
137 Update::DoNothing
138}
139
140extern "C" fn video_test_worker(_init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
143 const BARS: [[u8; 3]; 7] = [
144 [235, 235, 235],
145 [235, 235, 16],
146 [16, 235, 235],
147 [16, 235, 16],
148 [235, 16, 235],
149 [235, 16, 16],
150 [16, 16, 235],
151 ];
152 let (w, h) = (DEFAULT_W as usize, DEFAULT_H as usize);
153 let mut tick: u32 = 0;
154 loop {
155 let shift = (tick as usize / 4) % 7;
156 let mut bytes = Vec::with_capacity(w * h * 4);
157 for _y in 0..h {
158 for x in 0..w {
159 let c = BARS[((x * 7 / w) + shift) % 7];
160 bytes.extend_from_slice(&[c[0], c[1], c[2], 255]);
161 }
162 }
163 let frame = VideoFrame {
164 width: w as u32,
165 height: h as u32,
166 bytes: bytes.into(),
167 };
168 let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
169 WriteBackCallback::new(video_writeback),
170 RefAny::new(frame),
171 )));
172 if !sent {
173 break;
174 }
175 std::thread::sleep(std::time::Duration::from_millis(33));
176 tick = tick.wrapping_add(2);
177 }
178}
179
180extern "C" fn video_writeback(
183 mut writeback_data: RefAny,
184 mut frame_data: RefAny,
185 mut info: CallbackInfo,
186) -> Update {
187 let (current, hook) = match writeback_data.downcast_ref::<VideoWidgetState>() {
188 Some(s) => (s.gl_texture_id, s.on_frame.clone()),
189 None => (None, OptionOnVideoFrame::None),
190 };
191 let mut user_update = Update::DoNothing;
192 let new_id = match frame_data.downcast_ref::<VideoFrame>() {
193 Some(frame) => {
194 let id = present_frame(&mut info, writeback_data.clone(), current, &frame);
195 user_update = invoke_on_frame(&hook, &mut info, &frame);
196 id
197 }
198 None => return Update::DoNothing,
199 };
200 if let Some(mut s) = writeback_data.downcast_mut::<VideoWidgetState>() {
201 s.gl_texture_id = new_id;
202 }
203 user_update
204}
205
206extern "C" fn merge_video_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
208 {
209 let new_guard = new_data.downcast_mut::<VideoWidgetState>();
210 let old_guard = old_data.downcast_ref::<VideoWidgetState>();
211 if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
212 new_g.started = old_g.started;
213 new_g.gl_texture_id = old_g.gl_texture_id;
214 }
215 }
216 new_data
217}