use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlaybackState {
Stopped,
Playing,
Paused,
}
#[derive(Clone, Debug)]
pub enum AudioSource {
File(String),
Bytes(Arc<Vec<u8>>),
Asset(String),
}
impl AudioSource {
pub fn file(path: impl Into<String>) -> Self {
Self::File(path.into())
}
pub fn bytes(data: Vec<u8>) -> Self {
Self::Bytes(Arc::new(data))
}
pub fn asset(path: impl Into<String>) -> Self {
Self::Asset(path.into())
}
}
pub struct AudioPlayer {
inner: Rc<RefCell<AudioPlayerInner>>,
}
struct AudioPlayerInner {
state: PlaybackState,
volume: f32,
#[allow(dead_code)]
looping: bool,
play_start: Option<std::time::Instant>,
position_offset_ms: u64,
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
sink: Option<rodio::Sink>,
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
_stream: Option<rodio::OutputStream>,
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
stream_handle: Option<rodio::OutputStreamHandle>,
}
impl AudioPlayer {
pub fn new() -> Self {
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
{
let (stream, handle) = rodio::OutputStream::try_default()
.map(|(s, h)| (Some(s), Some(h)))
.unwrap_or((None, None));
Self {
inner: Rc::new(RefCell::new(AudioPlayerInner {
state: PlaybackState::Stopped,
volume: 1.0,
looping: false,
play_start: None,
position_offset_ms: 0,
sink: None,
_stream: stream,
stream_handle: handle,
})),
}
}
#[cfg(any(target_os = "android", target_os = "ios", target_arch = "wasm32"))]
{
Self {
inner: Rc::new(RefCell::new(AudioPlayerInner {
state: PlaybackState::Stopped,
volume: 1.0,
looping: false,
play_start: None,
position_offset_ms: 0,
})),
}
}
}
pub fn play(&self, source: AudioSource) {
let mut inner = self.inner.borrow_mut();
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
{
if let Some(ref handle) = inner.stream_handle {
let sink = rodio::Sink::try_new(handle).ok();
if let Some(ref sink) = sink {
match &source {
AudioSource::File(path) => {
if let Ok(file) = std::fs::File::open(path) {
let reader = std::io::BufReader::new(file);
if let Ok(decoder) = rodio::Decoder::new(reader) {
sink.append(decoder);
}
}
}
AudioSource::Bytes(data) => {
let cursor = std::io::Cursor::new(data.as_ref().clone());
if let Ok(decoder) = rodio::Decoder::new(cursor) {
sink.append(decoder);
}
}
AudioSource::Asset(path) => {
if let Ok(data) = blinc_platform::assets::load_asset(path) {
let cursor = std::io::Cursor::new(data);
if let Ok(decoder) = rodio::Decoder::new(cursor) {
sink.append(decoder);
}
}
}
}
sink.set_volume(inner.volume);
}
inner.sink = sink;
}
}
#[cfg(any(target_os = "android", target_os = "ios", target_arch = "wasm32"))]
{
match &source {
AudioSource::File(path) | AudioSource::Asset(path) => {
let _ = blinc_core::native_bridge::native_call::<(), _>(
"audio",
"play",
vec![blinc_core::native_bridge::NativeValue::String(path.clone())],
);
}
AudioSource::Bytes(_) => {
tracing::warn!("Bytes audio source not yet supported on mobile");
}
}
}
inner.state = PlaybackState::Playing;
inner.play_start = Some(std::time::Instant::now());
}
pub fn pause(&self) {
let mut inner = self.inner.borrow_mut();
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
if let Some(ref sink) = inner.sink {
sink.pause();
}
#[cfg(any(target_os = "android", target_os = "ios", target_arch = "wasm32"))]
{
let _ = blinc_core::native_bridge::native_call::<(), _>("audio", "pause", ());
}
if let Some(start) = inner.play_start.take() {
inner.position_offset_ms += start.elapsed().as_millis() as u64;
}
inner.state = PlaybackState::Paused;
}
pub fn resume(&self) {
let mut inner = self.inner.borrow_mut();
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
if let Some(ref sink) = inner.sink {
sink.play();
}
#[cfg(any(target_os = "android", target_os = "ios", target_arch = "wasm32"))]
{
let _ = blinc_core::native_bridge::native_call::<(), _>("audio", "resume", ());
}
inner.state = PlaybackState::Playing;
inner.play_start = Some(std::time::Instant::now());
}
pub fn stop(&self) {
let mut inner = self.inner.borrow_mut();
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
{
inner.sink = None;
}
#[cfg(any(target_os = "android", target_os = "ios", target_arch = "wasm32"))]
{
let _ = blinc_core::native_bridge::native_call::<(), _>("audio", "stop", ());
}
inner.state = PlaybackState::Stopped;
inner.play_start = None;
inner.position_offset_ms = 0;
}
pub fn position_ms(&self) -> u64 {
let inner = self.inner.borrow();
let elapsed = inner
.play_start
.map(|s| s.elapsed().as_millis() as u64)
.unwrap_or(0);
inner.position_offset_ms + elapsed
}
pub fn seek(&self, _position_ms: u64) {
let mut inner = self.inner.borrow_mut();
inner.position_offset_ms = _position_ms;
inner.play_start = if inner.state == PlaybackState::Playing {
Some(std::time::Instant::now())
} else {
None
};
#[cfg(any(target_os = "android", target_os = "ios", target_arch = "wasm32"))]
{
let _ = blinc_core::native_bridge::native_call::<(), _>(
"audio",
"seek",
vec![blinc_core::native_bridge::NativeValue::Int64(
_position_ms as i64,
)],
);
}
}
pub fn set_volume(&self, volume: f32) {
let mut inner = self.inner.borrow_mut();
inner.volume = volume.clamp(0.0, 1.0);
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
if let Some(ref sink) = inner.sink {
sink.set_volume(inner.volume);
}
#[cfg(any(target_os = "android", target_os = "ios", target_arch = "wasm32"))]
{
let _ = blinc_core::native_bridge::native_call::<(), _>(
"audio",
"set_volume",
vec![blinc_core::native_bridge::NativeValue::Float32(
inner.volume,
)],
);
}
}
pub fn state(&self) -> PlaybackState {
let inner = self.inner.borrow_mut();
#[cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))]
{
if let Some(ref sink) = inner.sink {
if sink.empty() {
return PlaybackState::Stopped;
}
if sink.is_paused() {
return PlaybackState::Paused;
}
return PlaybackState::Playing;
}
}
inner.state
}
pub fn volume(&self) -> f32 {
self.inner.borrow_mut().volume
}
pub fn is_playing(&self) -> bool {
self.state() == PlaybackState::Playing
}
}
impl Default for AudioPlayer {
fn default() -> Self {
Self::new()
}
}
impl crate::player::Player for AudioPlayer {
fn play(&self) {
self.resume();
}
fn pause(&self) {
AudioPlayer::pause(self);
}
fn stop(&self) {
AudioPlayer::stop(self);
}
fn seek(&self, position_ms: u64) {
AudioPlayer::seek(self, position_ms);
}
fn position_ms(&self) -> u64 {
AudioPlayer::position_ms(self)
}
fn duration_ms(&self) -> u64 {
0
} fn volume(&self) -> f32 {
AudioPlayer::volume(self)
}
fn set_volume(&self, volume: f32) {
AudioPlayer::set_volume(self, volume);
}
fn is_playing(&self) -> bool {
AudioPlayer::is_playing(self)
}
}