1use std::{fmt, sync::Arc, time::Duration};
2
3use serde::{Deserialize, Serialize};
4use zng_app::{
5 update::UPDATES,
6 view_process::{
7 VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT, ViewAudioOutput,
8 raw_events::{RAW_AUDIO_OUTPUT_OPEN_ERROR_EVENT, RAW_AUDIO_OUTPUT_OPEN_EVENT},
9 },
10};
11use zng_txt::{ToTxt as _, Txt};
12use zng_unit::{Factor, FactorUnits as _};
13use zng_var::{AnyVarHookArgs, Var, impl_from_and_into_var, var};
14use zng_view_api::audio::{AudioMix as ViewAudioMix, AudioMixLayer, AudioOutputConfig, AudioPlayId};
15
16use crate::{AUDIOS, AUDIOS_SV, AudioOutputId, AudioTrack};
17pub use zng_view_api::audio::AudioOutputState;
18
19pub(crate) struct AudioOutputData {
20 id: AudioOutputId,
21 view: Var<Result<ViewAudioOutput, Txt>>,
22
23 volume: Var<Factor>,
24 speed: Var<Factor>,
25 state: Var<AudioOutputState>,
26}
27
28#[derive(Clone)]
34pub struct AudioOutput(pub(crate) Arc<AudioOutputData>);
35impl fmt::Debug for AudioOutput {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("AudioOutput")
38 .field("id", &self.0.id)
39 .field("state", &self.0.state.get())
40 .field("volume", &self.0.volume.get())
41 .field("speed", &self.0.speed.get())
42 .finish_non_exhaustive()
43 }
44}
45impl PartialEq for AudioOutput {
46 fn eq(&self, other: &Self) -> bool {
47 self.0.id == other.0.id
48 }
49}
50impl Eq for AudioOutput {}
51impl AudioOutput {
52 pub(crate) fn open(id: AudioOutputId, opt: AudioOutputOptions) -> Self {
53 let r = Self(Arc::new(AudioOutputData {
54 id,
55 view: var(Err(Txt::from("not connected"))),
56 volume: var(opt.config.volume),
57 speed: var(opt.config.speed),
58 state: var(opt.config.state),
59 }));
60 r.0.state.as_any().hook(r.update_view_handler()).perm();
61 r.0.speed.as_any().hook(r.update_view_handler()).perm();
62 r.0.speed.as_any().hook(r.update_view_handler()).perm();
63
64 let handle = RAW_AUDIO_OUTPUT_OPEN_ERROR_EVENT.hook(move |args| {
65 if args.output_id == id {
66 if let Some(o) = AUDIOS_SV.read().outputs.get(&id)
67 && let Some(o) = o.upgrade()
68 {
69 o.0.view.set(Err(args.error.clone()));
70 }
71 return false;
72 }
73 true
74 });
75 let handle = RAW_AUDIO_OUTPUT_OPEN_EVENT.hook(move |args| {
76 let _hold = &handle;
77 if args.output_id == id {
78 if let Some(vo) = args.output.upgrade()
79 && let Some(o) = AUDIOS_SV.read().outputs.get(&id)
80 && let Some(o) = o.upgrade()
81 {
82 o.0.view.set(Ok(vo));
83 }
84 return false;
85 }
86 true
87 });
88 let handle = VIEW_PROCESS_INITED_EVENT.hook(move |_| {
89 let _hold = &handle;
90
91 if let Some(o) = AUDIOS_SV.read().outputs.get(&id)
92 && let Some(o) = o.upgrade()
93 {
94 let config = AudioOutputConfig::new(o.0.state.get(), o.0.volume.get(), o.0.speed.get());
95 let _ = VIEW_PROCESS.open_audio_output(zng_view_api::audio::AudioOutputRequest::new(
96 zng_view_api::audio::AudioOutputId::from_raw(id.get()),
97 config,
98 ));
99 return true;
100 }
101 false
102 });
103 r.0.view.hold(handle).perm();
104
105 if VIEW_PROCESS.is_connected() {
106 let _ = VIEW_PROCESS.open_audio_output(zng_view_api::audio::AudioOutputRequest::new(
107 zng_view_api::audio::AudioOutputId::from_raw(id.get()),
108 opt.config,
109 ));
110 }
111
112 r
113 }
114 fn update_view_handler(&self) -> impl FnMut(&AnyVarHookArgs) -> bool + Send + 'static {
115 let wk = Arc::downgrade(&self.0);
116 move |_| {
117 if let Some(a) = wk.upgrade() {
118 let r = a.view.with(|v| {
119 if let Ok(v) = v {
120 let cfg = AudioOutputConfig::new(a.state.get(), a.volume.get(), a.speed.get());
121 v.update(cfg)
122 } else {
123 Ok(())
124 }
125 });
126 if let Err(e) = r {
127 a.view.set(Err(e.to_txt()));
129 }
130 true
131 } else {
132 false
133 }
134 }
135 }
136
137 pub fn id(&self) -> AudioOutputId {
139 self.0.id
140 }
141
142 pub fn cue(&self, audio: impl Into<AudioMix>) {
146 self.cue_impl(audio.into());
147 }
148 fn cue_impl(&self, audio: AudioMix) {
149 let s = self.clone();
150 UPDATES.once_update("AudioOutput.cue", move || {
151 let r = s.0.view.with(|v| match v {
152 Ok(v) => v.cue(audio.view),
153 Err(e) => {
154 tracing::error!("failed to cue audio, {e}");
155 Ok(AudioPlayId::INVALID)
156 }
157 });
158 if let Err(e) = r {
159 s.0.view.set(Err(e.to_txt()));
161 }
162 });
163 }
164
165 pub fn volume(&self) -> Var<Factor> {
169 self.0.volume.clone()
170 }
171
172 pub fn speed(&self) -> Var<Factor> {
179 self.0.speed.clone()
180 }
181
182 pub fn state(&self) -> Var<AudioOutputState> {
190 self.0.state.clone()
191 }
192
193 pub fn play(&self) {
200 self.0.state.set(AudioOutputState::Playing);
201 }
202
203 pub fn pause(&self) {
209 self.0.state.set(AudioOutputState::Paused);
210 }
211
212 pub fn stop(&self) {
218 self.0.state.set(AudioOutputState::Stopped);
219 }
220
221 pub fn stop_play(&self) {
226 let s = self.clone();
227 self.0.state.modify(move |a| {
228 a.set(AudioOutputState::Playing);
229 s.0.view.with(|v| {
230 if let Ok(v) = v {
231 let _ = v.update(AudioOutputConfig::new(AudioOutputState::Stopped, s.0.volume.get(), s.0.speed.get()));
232 a.update();
233 }
234 });
235 });
236 }
237
238 pub fn perm(&self) {
240 AUDIOS.perm_output(self);
241 }
242
243 pub fn is_connected(&self) -> Var<bool> {
251 self.0.view.map(|v| v.is_ok())
252 }
253
254 pub fn error(&self) -> Var<Option<Txt>> {
261 self.0.view.map(|v| match v {
262 Ok(_) => None,
263 Err(e) => {
264 if e.is_empty() {
265 None
266 } else {
267 Some(e.clone())
268 }
269 }
270 })
271 }
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278#[non_exhaustive]
279pub struct AudioMix {
280 view: ViewAudioMix,
281}
282impl AudioMix {
283 pub fn new() -> Self {
285 Self { view: ViewAudioMix::new() }
286 }
287
288 pub fn with_delay(mut self, duration: Duration) -> Self {
290 self.view.delay = duration;
291 self
292 }
293
294 pub fn with_total_duration(mut self, duration: Duration) -> Self {
303 self.view.total_duration = Some(duration);
304 self
305 }
306
307 pub fn with_audio(mut self, audio: &AudioTrack) -> Self {
311 if !audio.can_cue() {
312 let e = audio.error();
313 tracing::error!(
314 "cannot cue audio, {}",
315 e.as_deref().unwrap_or("metadata not decoded in view-process")
316 );
317 return self;
318 }
319
320 self.view.layers.push(AudioMixLayer::Audio {
321 audio: audio.view_handle().audio_id(),
322 skip: Duration::ZERO,
323 take: Duration::MAX,
324 });
325 self
326 }
327
328 pub fn with_audio_clip(mut self, audio: &AudioTrack, skip: Duration, take: Duration) -> Self {
334 if !audio.can_cue() {
335 let e = audio.error();
336 tracing::error!(
337 "cannot cue audio, {}",
338 e.as_deref().unwrap_or("metadata not decoded in view-process")
339 );
340 return self;
341 }
342
343 self.view.layers.push(AudioMixLayer::Audio {
344 audio: audio.view_handle().audio_id(),
345 skip,
346 take,
347 });
348 self
349 }
350
351 pub fn with_mix(self, mix: impl Into<AudioMix>) -> Self {
355 self.with_mix_clip(mix.into(), Duration::ZERO, Duration::MAX)
356 }
357 pub fn with_mix_clip(mut self, mix: impl Into<AudioMix>, skip: Duration, take: Duration) -> Self {
363 self.view.layers.push(AudioMixLayer::AudioMix {
364 mix: mix.into().view,
365 skip,
366 take,
367 });
368 self
369 }
370 pub fn with_sine_wave(mut self, frequency: f32, duration: Duration) -> Self {
374 self.view.layers.push(AudioMixLayer::SineWave { frequency, duration });
375 self
376 }
377
378 pub fn with_volume_linear(mut self, start: Duration, duration: Duration, start_volume: Factor, end_volume: Factor) -> Self {
385 self.view.layers.push(AudioMixLayer::VolumeLinear {
386 start,
387 duration,
388 start_volume,
389 end_volume,
390 });
391 self
392 }
393
394 pub fn with_fade_in(self, transition_duration: Duration) -> Self {
398 self.with_volume_linear(Duration::ZERO, transition_duration, 0.fct(), 1.fct())
399 }
400
401 pub fn with_fade_out(self, start: Duration, transition_duration: Duration) -> Self {
410 self.with_volume_linear(start, transition_duration, 1.fct(), 0.fct())
411 .with_volume_linear(start + transition_duration, Duration::MAX, 0.fct(), 0.fct())
412 }
413}
414impl Default for AudioMix {
415 fn default() -> Self {
416 Self::new()
417 }
418}
419impl_from_and_into_var! {
420 fn from(mix: AudioMix) -> ViewAudioMix {
421 mix.view
422 }
423 fn from(audio: AudioTrack) -> AudioMix {
424 AudioMix::new().with_audio(&audio)
425 }
426}
427impl From<&AudioTrack> for AudioMix {
428 fn from(audio: &AudioTrack) -> Self {
429 AudioMix::new().with_audio(audio)
430 }
431}
432
433#[derive(Debug)]
435#[non_exhaustive]
436pub struct AudioOutputOptions {
437 pub config: AudioOutputConfig,
439}
440
441impl Default for AudioOutputOptions {
442 fn default() -> Self {
443 Self {
444 config: AudioOutputConfig::new(AudioOutputState::Playing, 1.fct(), 1.fct()),
445 }
446 }
447}
448
449#[derive(Clone)]
451pub struct WeakAudioOutput(std::sync::Weak<AudioOutputData>);
452impl fmt::Debug for WeakAudioOutput {
453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454 f.debug_tuple("WeakAudioOutput").finish_non_exhaustive()
455 }
456}
457impl PartialEq for WeakAudioOutput {
458 fn eq(&self, other: &Self) -> bool {
459 self.0.ptr_eq(&other.0)
460 }
461}
462impl Eq for WeakAudioOutput {}
463impl WeakAudioOutput {
464 pub const fn new() -> Self {
466 Self(std::sync::Weak::new())
467 }
468
469 pub fn upgrade(&self) -> Option<AudioOutput> {
471 self.0.upgrade().map(AudioOutput)
472 }
473}
474impl Default for WeakAudioOutput {
475 fn default() -> Self {
476 Self::new()
477 }
478}
479
480impl AudioOutput {
481 pub fn downgrade(&self) -> WeakAudioOutput {
483 WeakAudioOutput(std::sync::Arc::downgrade(&self.0))
484 }
485}