1use crate::internal::InternalLower;
2use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
3use fission_ir::{
4 op::{EmbedKind, LayoutOp, Op},
5 WidgetId,
6};
7use serde::{Deserialize, Serialize};
8use std::path::Path;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
28#[non_exhaustive]
29pub struct Video {
30 pub id: Option<WidgetId>,
32 pub source: VideoSource,
34 pub width: Option<f32>,
36 pub height: Option<f32>,
38 pub autoplay: bool,
40 pub loop_playback: bool,
42 #[serde(default)]
49 pub audio: VideoAudioOptions,
50}
51
52impl Default for Video {
53 fn default() -> Self {
54 Self {
55 id: None,
56 source: VideoSource::default(),
57 width: None,
58 height: None,
59 autoplay: false,
60 loop_playback: false,
61 audio: VideoAudioOptions::default(),
62 }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72#[serde(tag = "kind", rename_all = "snake_case")]
73pub enum VideoSource {
74 Asset { path: String },
76 File { path: String },
78 Network { url: String },
80}
81
82impl Default for VideoSource {
83 fn default() -> Self {
84 Self::Asset {
85 path: String::new(),
86 }
87 }
88}
89
90impl VideoSource {
91 pub fn as_str(&self) -> &str {
93 match self {
94 Self::Asset { path } | Self::File { path } => path,
95 Self::Network { url } => url,
96 }
97 }
98
99 pub fn key(&self) -> String {
101 match self {
102 Self::Asset { path } => format!("asset:{path}"),
103 Self::File { path } => format!("file:{path}"),
104 Self::Network { url } => format!("network:{url}"),
105 }
106 }
107}
108
109impl From<&str> for VideoSource {
110 fn from(source: &str) -> Self {
111 infer_video_source(source)
112 }
113}
114
115impl From<String> for VideoSource {
116 fn from(source: String) -> Self {
117 infer_video_source(&source)
118 }
119}
120
121fn infer_video_source(source: &str) -> VideoSource {
122 if source.contains("://") {
123 VideoSource::Network {
124 url: source.to_string(),
125 }
126 } else if Path::new(source).is_absolute() {
127 VideoSource::File {
128 path: source.to_string(),
129 }
130 } else {
131 VideoSource::Asset {
132 path: source.to_string(),
133 }
134 }
135}
136
137impl Video {
138 pub fn asset(path: impl Into<String>) -> Self {
140 Self::from_source(VideoSource::Asset { path: path.into() })
141 }
142
143 pub fn file(path: impl Into<String>) -> Self {
145 Self::from_source(VideoSource::File { path: path.into() })
146 }
147
148 pub fn network(url: impl Into<String>) -> Self {
150 Self::from_source(VideoSource::Network { url: url.into() })
151 }
152
153 pub fn from_source(source: impl Into<VideoSource>) -> Self {
155 Self {
156 source: source.into(),
157 ..Self::default()
158 }
159 }
160
161 pub fn id(mut self, id: WidgetId) -> Self {
163 self.id = Some(id);
164 self
165 }
166
167 pub fn width(mut self, width: f32) -> Self {
169 self.width = Some(width);
170 self
171 }
172
173 pub fn height(mut self, height: f32) -> Self {
175 self.height = Some(height);
176 self
177 }
178
179 pub fn size(mut self, width: f32, height: f32) -> Self {
181 self.width = Some(width);
182 self.height = Some(height);
183 self
184 }
185
186 pub fn autoplay(mut self, autoplay: bool) -> Self {
188 self.autoplay = autoplay;
189 self
190 }
191
192 pub fn loop_playback(mut self, loop_playback: bool) -> Self {
194 self.loop_playback = loop_playback;
195 self
196 }
197
198 pub fn audio(mut self, audio: VideoAudioOptions) -> Self {
200 self.audio = audio;
201 self
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211pub struct VideoAudioOptions {
212 pub policy: VideoAudioPolicy,
214 pub activation: VideoAudioActivation,
216 pub mix_with_others: bool,
218 pub duck_others: bool,
220 #[serde(default)]
222 pub ios: IosVideoAudioOptions,
223}
224
225impl Default for VideoAudioOptions {
226 fn default() -> Self {
227 Self::system_default()
228 }
229}
230
231impl VideoAudioOptions {
232 pub fn system_default() -> Self {
238 Self {
239 policy: VideoAudioPolicy::SystemDefault,
240 activation: VideoAudioActivation::OnDemand,
241 mix_with_others: false,
242 duck_others: false,
243 ios: IosVideoAudioOptions::default(),
244 }
245 }
246
247 pub fn ambient() -> Self {
250 Self {
251 policy: VideoAudioPolicy::Ambient,
252 mix_with_others: true,
253 ..Self::system_default()
254 }
255 }
256
257 pub fn playback() -> Self {
262 Self {
263 policy: VideoAudioPolicy::Playback,
264 ..Self::system_default()
265 }
266 }
267
268 pub fn mix_with_others(mut self, value: bool) -> Self {
270 self.mix_with_others = value;
271 self
272 }
273
274 pub fn duck_others(mut self, value: bool) -> Self {
276 self.duck_others = value;
277 self
278 }
279
280 pub fn activation(mut self, activation: VideoAudioActivation) -> Self {
282 self.activation = activation;
283 self
284 }
285
286 pub fn ios(mut self, ios: IosVideoAudioOptions) -> Self {
288 self.ios = ios;
289 self
290 }
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
295pub enum VideoAudioPolicy {
296 SystemDefault,
298 Ambient,
300 Playback,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
306pub enum VideoAudioActivation {
307 OnDemand,
309 OnPlayerCreate,
311 Manual,
313}
314
315#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
321pub struct IosVideoAudioOptions {
322 pub category: Option<IosAudioSessionCategory>,
324 pub mode: Option<IosAudioSessionMode>,
326 #[serde(default)]
328 pub category_options: Vec<IosAudioSessionCategoryOption>,
329}
330
331impl IosVideoAudioOptions {
332 pub fn category(category: IosAudioSessionCategory) -> Self {
334 Self {
335 category: Some(category),
336 ..Default::default()
337 }
338 }
339
340 pub fn mode(mode: IosAudioSessionMode) -> Self {
342 Self {
343 mode: Some(mode),
344 ..Default::default()
345 }
346 }
347
348 pub fn with_option(mut self, option: IosAudioSessionCategoryOption) -> Self {
350 self.category_options.push(option);
351 self
352 }
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
357pub enum IosAudioSessionCategory {
358 Ambient,
359 SoloAmbient,
360 Playback,
361 Record,
362 PlayAndRecord,
363 MultiRoute,
364 Raw(String),
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
371pub enum IosAudioSessionMode {
372 Default,
373 MoviePlayback,
374 SpokenAudio,
375 VideoRecording,
376 Measurement,
377 VoiceChat,
378 VideoChat,
379 GameChat,
380 Raw(String),
383}
384
385#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
387pub enum IosAudioSessionCategoryOption {
388 MixWithOthers,
389 DuckOthers,
390 AllowBluetoothHfp,
391 DefaultToSpeaker,
392 InterruptSpokenAudioAndMixWithOthers,
393 AllowBluetoothA2dp,
394 AllowAirPlay,
395 OverrideMutedMicrophoneInterruption,
396 Raw(u64),
398}
399
400impl InternalLower for Video {
401 fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
402 let widget_id = self
403 .id
404 .unwrap_or_else(|| WidgetId::explicit(&self.source.key()));
405 let layout_id = cx.widget_node_id(widget_id);
406
407 let embed_id = InternalIrBuilder::new(
408 cx.next_node_id(),
409 Op::Layout(LayoutOp::Embed {
410 kind: EmbedKind::Video,
411 widget_id,
412 width: self.width,
413 height: self.height,
414 }),
415 )
416 .build(cx);
417
418 let mut layout_builder = InternalIrBuilder::new(
419 layout_id,
420 Op::Layout(LayoutOp::Box {
421 width: self.width,
422 height: self.height,
423 min_width: None,
424 max_width: None,
425 min_height: None,
426 max_height: None,
427 padding: [0.0; 4],
428 flex_grow: 0.0,
429 flex_shrink: 0.0,
430 aspect_ratio: None,
431 }),
432 );
433 layout_builder.add_child(embed_id);
434 layout_builder.build(cx)
435 }
436}