blob: 5a0a5d7ac922ee624d27ecdb123f85691c980385 [file] [log] [blame]
// Copyright 2020 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::call_async,
crate::internal::event::Publisher,
crate::service_context::{ExternalServiceProxy, ServiceContextHandle},
anyhow::{format_err, Context as _, Error},
fidl_fuchsia_media::AudioRenderUsage,
fidl_fuchsia_media_sounds::{PlayerMarker, PlayerProxy},
fuchsia_async as fasync,
fuchsia_syslog::{fx_log_debug, fx_log_err},
fuchsia_zircon as zx,
futures::lock::Mutex,
std::collections::HashSet,
std::fs::File,
std::sync::Arc,
};
/// Creates a file-based sound from a resource file.
fn resource_file(
name: &str,
) -> Result<fidl::endpoints::ClientEnd<fidl_fuchsia_io::FileMarker>, Error> {
// We try two paths here, because normal components see their config package data resources in
// /pkg/data and shell tools see them in /pkgfs/packages/config-data/0/meta/data/<pkg>.
Ok(fidl::endpoints::ClientEnd::<fidl_fuchsia_io::FileMarker>::new(zx::Channel::from(
fdio::transfer_fd(
File::open(format!("/config/data/{}", name))
.or_else(|_| {
File::open(format!(
"/pkgfs/packages/config-data/0/meta/data/setui_service/{}",
name
))
})
.context("Opening package data file")?,
)?,
)))
}
/// Establish a connection to the sound player and return the proxy representing the service.
/// Will not do anything if the sound player connection is already established.
pub async fn connect_to_sound_player(
publisher: Publisher,
service_context_handle: ServiceContextHandle,
sound_player_connection: Arc<Mutex<Option<ExternalServiceProxy<PlayerProxy>>>>,
) {
let mut sound_player_connection_lock = sound_player_connection.lock().await;
if sound_player_connection_lock.is_none() {
*sound_player_connection_lock = service_context_handle
.lock()
.await
.connect_with_publisher::<PlayerMarker>(publisher)
.await
.context("Connecting to fuchsia.media.sounds.Player")
.map_err(|e| fx_log_err!("Failed to connect to fuchsia.media.sounds.Player: {}", e))
.ok()
}
}
/// Plays a sound with the given [id] and [file_name] via the [sound_player_proxy].
///
/// The id and file_name are expected to be unique and mapped 1:1 to each other. This allows
/// the sound file to be reused without having to load it again.
pub async fn play_sound<'a>(
sound_player_proxy: &ExternalServiceProxy<PlayerProxy>,
file_name: &'a str,
id: u32,
added_files: Arc<Mutex<HashSet<&'a str>>>,
) -> Result<(), Error> {
// New sound, add it to the sound player set.
if added_files.lock().await.insert(file_name) {
let sound_file_channel = match resource_file(file_name) {
Ok(file) => Some(file),
Err(e) => return Err(format_err!("[earcons] Failed to convert sound file: {}", e)),
};
if let Some(file_channel) = sound_file_channel {
match call_async!(sound_player_proxy => add_sound_from_file(id, file_channel)).await {
Ok(_) => fx_log_debug!("[earcons] Added sound to Player: {}", file_name),
Err(e) => {
return Err(format_err!("[earcons] Unable to add sound to Player: {}", e));
}
};
}
}
let sound_player_proxy = sound_player_proxy.clone();
// This fasync thread is needed so that the earcons sounds can play rapidly and not wait
// for the previous sound to finish to send another request.
fasync::Task::spawn(async move {
match call_async!(sound_player_proxy => play_sound(id, AudioRenderUsage::Background)).await
{
Ok(_) => {
// TODO(fxbug.dev/50246): Add inspect logging.
}
Err(e) => fx_log_err!("[earcons] Unable to Play sound from Player: {}", e),
};
})
.detach();
Ok(())
}