use crate::internal::InternalLower;
use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
use fission_ir::{
op::{EmbedKind, LayoutOp, Op},
WidgetId,
};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Video {
pub id: Option<WidgetId>,
pub source: VideoSource,
pub width: Option<f32>,
pub height: Option<f32>,
pub autoplay: bool,
pub loop_playback: bool,
#[serde(default)]
pub audio: VideoAudioOptions,
}
impl Default for Video {
fn default() -> Self {
Self {
id: None,
source: VideoSource::default(),
width: None,
height: None,
autoplay: false,
loop_playback: false,
audio: VideoAudioOptions::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum VideoSource {
Asset { path: String },
File { path: String },
Network { url: String },
}
impl Default for VideoSource {
fn default() -> Self {
Self::Asset {
path: String::new(),
}
}
}
impl VideoSource {
pub fn as_str(&self) -> &str {
match self {
Self::Asset { path } | Self::File { path } => path,
Self::Network { url } => url,
}
}
pub fn key(&self) -> String {
match self {
Self::Asset { path } => format!("asset:{path}"),
Self::File { path } => format!("file:{path}"),
Self::Network { url } => format!("network:{url}"),
}
}
}
impl From<&str> for VideoSource {
fn from(source: &str) -> Self {
infer_video_source(source)
}
}
impl From<String> for VideoSource {
fn from(source: String) -> Self {
infer_video_source(&source)
}
}
fn infer_video_source(source: &str) -> VideoSource {
if source.contains("://") {
VideoSource::Network {
url: source.to_string(),
}
} else if Path::new(source).is_absolute() {
VideoSource::File {
path: source.to_string(),
}
} else {
VideoSource::Asset {
path: source.to_string(),
}
}
}
impl Video {
pub fn asset(path: impl Into<String>) -> Self {
Self::from_source(VideoSource::Asset { path: path.into() })
}
pub fn file(path: impl Into<String>) -> Self {
Self::from_source(VideoSource::File { path: path.into() })
}
pub fn network(url: impl Into<String>) -> Self {
Self::from_source(VideoSource::Network { url: url.into() })
}
pub fn from_source(source: impl Into<VideoSource>) -> Self {
Self {
source: source.into(),
..Self::default()
}
}
pub fn id(mut self, id: WidgetId) -> Self {
self.id = Some(id);
self
}
pub fn width(mut self, width: f32) -> Self {
self.width = Some(width);
self
}
pub fn height(mut self, height: f32) -> Self {
self.height = Some(height);
self
}
pub fn size(mut self, width: f32, height: f32) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
pub fn autoplay(mut self, autoplay: bool) -> Self {
self.autoplay = autoplay;
self
}
pub fn loop_playback(mut self, loop_playback: bool) -> Self {
self.loop_playback = loop_playback;
self
}
pub fn audio(mut self, audio: VideoAudioOptions) -> Self {
self.audio = audio;
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VideoAudioOptions {
pub policy: VideoAudioPolicy,
pub activation: VideoAudioActivation,
pub mix_with_others: bool,
pub duck_others: bool,
#[serde(default)]
pub ios: IosVideoAudioOptions,
}
impl Default for VideoAudioOptions {
fn default() -> Self {
Self::system_default()
}
}
impl VideoAudioOptions {
pub fn system_default() -> Self {
Self {
policy: VideoAudioPolicy::SystemDefault,
activation: VideoAudioActivation::OnDemand,
mix_with_others: false,
duck_others: false,
ios: IosVideoAudioOptions::default(),
}
}
pub fn ambient() -> Self {
Self {
policy: VideoAudioPolicy::Ambient,
mix_with_others: true,
..Self::system_default()
}
}
pub fn playback() -> Self {
Self {
policy: VideoAudioPolicy::Playback,
..Self::system_default()
}
}
pub fn mix_with_others(mut self, value: bool) -> Self {
self.mix_with_others = value;
self
}
pub fn duck_others(mut self, value: bool) -> Self {
self.duck_others = value;
self
}
pub fn activation(mut self, activation: VideoAudioActivation) -> Self {
self.activation = activation;
self
}
pub fn ios(mut self, ios: IosVideoAudioOptions) -> Self {
self.ios = ios;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VideoAudioPolicy {
SystemDefault,
Ambient,
Playback,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VideoAudioActivation {
OnDemand,
OnPlayerCreate,
Manual,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IosVideoAudioOptions {
pub category: Option<IosAudioSessionCategory>,
pub mode: Option<IosAudioSessionMode>,
#[serde(default)]
pub category_options: Vec<IosAudioSessionCategoryOption>,
}
impl IosVideoAudioOptions {
pub fn category(category: IosAudioSessionCategory) -> Self {
Self {
category: Some(category),
..Default::default()
}
}
pub fn mode(mode: IosAudioSessionMode) -> Self {
Self {
mode: Some(mode),
..Default::default()
}
}
pub fn with_option(mut self, option: IosAudioSessionCategoryOption) -> Self {
self.category_options.push(option);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum IosAudioSessionCategory {
Ambient,
SoloAmbient,
Playback,
Record,
PlayAndRecord,
MultiRoute,
Raw(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum IosAudioSessionMode {
Default,
MoviePlayback,
SpokenAudio,
VideoRecording,
Measurement,
VoiceChat,
VideoChat,
GameChat,
Raw(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum IosAudioSessionCategoryOption {
MixWithOthers,
DuckOthers,
AllowBluetoothHfp,
DefaultToSpeaker,
InterruptSpokenAudioAndMixWithOthers,
AllowBluetoothA2dp,
AllowAirPlay,
OverrideMutedMicrophoneInterruption,
Raw(u64),
}
impl InternalLower for Video {
fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
let widget_id = self
.id
.unwrap_or_else(|| WidgetId::explicit(&self.source.key()));
let layout_id = cx.widget_node_id(widget_id);
let embed_id = InternalIrBuilder::new(
cx.next_node_id(),
Op::Layout(LayoutOp::Embed {
kind: EmbedKind::Video,
widget_id,
width: self.width,
height: self.height,
}),
)
.build(cx);
let mut layout_builder = InternalIrBuilder::new(
layout_id,
Op::Layout(LayoutOp::Box {
width: self.width,
height: self.height,
min_width: None,
max_width: None,
min_height: None,
max_height: None,
padding: [0.0; 4],
flex_grow: 0.0,
flex_shrink: 0.0,
aspect_ratio: None,
}),
);
layout_builder.add_child(embed_id);
layout_builder.build(cx)
}
}