blob: b7218459ebf98ef9d516522a0f9b1bf7253d90f2 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::base::SettingType,
crate::call,
crate::handler::setting_handler::ControllerError,
crate::internal::event::{Event, Publisher},
crate::service_context::ExternalServiceProxy,
crate::switchboard::base::AudioStream,
fidl::{self, endpoints::create_proxy},
fidl_fuchsia_media::{AudioRenderUsage, Usage},
fidl_fuchsia_media_audio::VolumeControlProxy,
fuchsia_async as fasync,
futures::channel::mpsc::UnboundedSender,
futures::stream::StreamExt,
futures::TryStreamExt,
std::sync::Arc,
};
const PUBLISHER_EVENT_NAME: &str = "volume_control_events";
const CONTROLLER_ERROR_DEPENDENCY: &str = "fuchsia.media.audio";
/// Closure definition for an action that can be triggered by ActionFuse.
pub type ExitAction = Arc<dyn Fn() + Send + Sync + 'static>;
// Stores an AudioStream and a VolumeControl proxy bound to the AudioCore
// service for |stored_stream|'s stream type. |proxy| is set to None if it
// fails to bind to the AudioCore service. |early_exit_action| specifies a
// closure to be run if the StreamVolumeControl exits prematurely.
// TODO(fxbug.dev/57705): Replace UnboundedSender with a oneshot channel.
pub struct StreamVolumeControl {
pub stored_stream: AudioStream,
proxy: Option<VolumeControlProxy>,
audio_service: ExternalServiceProxy<fidl_fuchsia_media::AudioCoreProxy>,
publisher: Option<Publisher>,
early_exit_action: Option<ExitAction>,
listen_exit_tx: Option<UnboundedSender<()>>,
}
impl Drop for StreamVolumeControl {
fn drop(&mut self) {
if let Some(exit_tx) = self.listen_exit_tx.take() {
exit_tx.unbounded_send(()).ok();
}
}
}
// TODO(fxbug.dev/37777): Listen for volume changes from Volume Control.
impl StreamVolumeControl {
pub async fn create(
audio_service: &ExternalServiceProxy<fidl_fuchsia_media::AudioCoreProxy>,
stream: AudioStream,
early_exit_action: Option<ExitAction>,
publisher: Option<Publisher>,
) -> Result<Self, ControllerError> {
let mut control = StreamVolumeControl {
stored_stream: stream,
proxy: None,
audio_service: audio_service.clone(),
publisher,
listen_exit_tx: None,
early_exit_action,
};
control.bind_volume_control().await?;
Ok(control)
}
pub async fn set_volume(&mut self, stream: AudioStream) -> Result<(), ControllerError> {
assert_eq!(self.stored_stream.stream_type, stream.stream_type);
// Try to create and bind a new VolumeControl.
if self.proxy.is_none() {
self.bind_volume_control().await?;
}
// Round to 1%.
let mut new_stream_value = stream.clone();
new_stream_value.user_volume_level = (stream.user_volume_level * 100.0).floor() / 100.0;
let proxy = self.proxy.as_ref().unwrap();
if self.stored_stream.user_volume_level != new_stream_value.user_volume_level {
if proxy.set_volume(new_stream_value.user_volume_level).is_err() {
self.stored_stream = new_stream_value;
return Err(ControllerError::ExternalFailure(
SettingType::Audio,
CONTROLLER_ERROR_DEPENDENCY.into(),
"set volume".into(),
));
}
}
if self.stored_stream.user_volume_muted != new_stream_value.user_volume_muted {
if proxy.set_mute(stream.user_volume_muted).is_err() {
self.stored_stream = new_stream_value;
return Err(ControllerError::ExternalFailure(
SettingType::Audio,
CONTROLLER_ERROR_DEPENDENCY.into(),
"set mute".into(),
));
}
}
self.stored_stream = new_stream_value;
Ok(())
}
async fn bind_volume_control(&mut self) -> Result<(), ControllerError> {
if self.proxy.is_some() {
return Ok(());
}
let (vol_control_proxy, server_end) = create_proxy().unwrap();
let stream_type = self.stored_stream.stream_type;
let mut usage = Usage::RenderUsage(AudioRenderUsage::from(stream_type));
if call!(self.audio_service => bind_usage_volume_control(&mut usage, server_end)).is_err() {
return Err(ControllerError::ExternalFailure(
SettingType::Audio,
CONTROLLER_ERROR_DEPENDENCY.into(),
format!("bind_usage_volume_control for audio_core {:?}", usage).into(),
));
}
// Once the volume control is bound, apply the persisted audio settings to it.
if vol_control_proxy.set_volume(self.stored_stream.user_volume_level).is_err() {
return Err(ControllerError::ExternalFailure(
SettingType::Audio,
CONTROLLER_ERROR_DEPENDENCY.into(),
format!("set_volume for vol_control {:?}", stream_type).into(),
));
}
if vol_control_proxy.set_mute(self.stored_stream.user_volume_muted).is_err() {
return Err(ControllerError::ExternalFailure(
SettingType::Audio,
CONTROLLER_ERROR_DEPENDENCY.into(),
"set_mute for vol_control".into(),
));
}
if let Some(exit_tx) = self.listen_exit_tx.take() {
exit_tx.unbounded_send(()).ok();
}
// TODO(fxbug.dev/37777): Update |stored_stream| in StreamVolumeControl and send a notification
// when we receive an update.
let (exit_tx, mut exit_rx) = futures::channel::mpsc::unbounded::<()>();
let publisher_clone = self.publisher.as_ref().map_or(None, |p| Some(p.clone()));
let mut volume_events = vol_control_proxy.take_event_stream();
let early_exit_action = self.early_exit_action.clone();
fasync::Task::spawn(async move {
loop {
futures::select! {
exit = exit_rx.next() => {
if let Some(publisher) = publisher_clone {
publisher.send_event(Event::Closed(PUBLISHER_EVENT_NAME));
}
return;
}
volume_event = volume_events.try_next() => {
if volume_event.is_err() || volume_event.expect("should not be error").is_none(){
if let Some(action) = early_exit_action {
(action)();
}
return;
}
}
}
}
})
.detach();
self.listen_exit_tx = Some(exit_tx);
self.proxy = Some(vol_control_proxy);
Ok(())
}
}