Files
songbird/src/input/adapters/async_adapter.rs
Kyle Simpson ec665a8f87 Fix: Ringbuf patch release requires explicit type param
Ringbuf 0.4.2 onwards adds in a new storage type for `SharedRb`.
Unfortunately, this is handled in such a way that we fail to
compile because an explicit type parameter is required to
disambiguate the method in question.
2024-08-17 23:01:51 +01:00

386 lines
12 KiB
Rust

use crate::input::AudioStreamError;
use async_trait::async_trait;
use flume::{Receiver, RecvError, Sender, TryRecvError};
use futures::{future::Either, stream::FuturesUnordered, FutureExt, StreamExt};
use parking_lot::Mutex;
use ringbuf::{storage::Heap, traits::*, *};
use std::{
io::{
Error as IoError,
ErrorKind as IoErrorKind,
Read,
Result as IoResult,
Seek,
SeekFrom,
Write,
},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use symphonia_core::io::MediaSource;
use tokio::{
io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt},
sync::Notify,
};
struct AsyncAdapterSink {
bytes_in: HeapProd<u8>,
req_rx: Receiver<AdapterRequest>,
resp_tx: Sender<AdapterResponse>,
stream: Box<dyn AsyncMediaSource>,
notify_rx: Arc<Notify>,
}
impl AsyncAdapterSink {
async fn launch(mut self) {
let mut inner_buf = [0u8; 32 * 1024];
let mut read_region = 0..0;
let mut hit_end = false;
let mut blocked = false;
let mut pause_buf_moves = false;
let mut seek_res = None;
let mut seen_bytes = 0;
loop {
// if read_region is empty, refill from src.
// if that read is zero, tell other half.
// if WouldBlock, block on msg acquire,
// else non_block msg acquire.
if !pause_buf_moves {
if !hit_end && read_region.is_empty() {
if let Ok(n) = self.stream.read(&mut inner_buf).await {
read_region = 0..n;
if n == 0 {
drop(self.resp_tx.send_async(AdapterResponse::ReadZero).await);
hit_end = true;
}
seen_bytes += n as u64;
} else {
match self.stream.try_resume(seen_bytes).await {
Ok(s) => {
self.stream = s;
},
Err(_e) => break,
}
}
}
while !read_region.is_empty() && !blocked {
if let Ok(n_moved) = self
.bytes_in
.write(&inner_buf[read_region.start..read_region.end])
{
read_region.start += n_moved;
drop(self.resp_tx.send_async(AdapterResponse::ReadOccurred).await);
} else {
blocked = true;
}
}
}
let msg = if blocked || hit_end {
let mut fs = FuturesUnordered::new();
fs.push(Either::Left(self.req_rx.recv_async()));
fs.push(Either::Right(self.notify_rx.notified().map(|()| {
let o: Result<AdapterRequest, RecvError> = Ok(AdapterRequest::Wake);
o
})));
match fs.next().await {
Some(Ok(a)) => a,
_ => break,
}
} else {
match self.req_rx.try_recv() {
Ok(a) => a,
Err(TryRecvError::Empty) => continue,
_ => break,
}
};
match msg {
AdapterRequest::Wake => blocked = false,
AdapterRequest::ByteLen => {
drop(
self.resp_tx
.send_async(AdapterResponse::ByteLen(self.stream.byte_len().await))
.await,
);
},
AdapterRequest::Seek(pos) => {
pause_buf_moves = true;
drop(self.resp_tx.send_async(AdapterResponse::SeekClear).await);
seek_res = Some(self.stream.seek(pos).await);
},
AdapterRequest::SeekCleared => {
if let Some(res) = seek_res.take() {
drop(
self.resp_tx
.send_async(AdapterResponse::SeekResult(res))
.await,
);
}
pause_buf_moves = false;
},
}
}
}
}
/// An adapter for converting an async media source into a synchronous one
/// usable by symphonia.
///
/// This adapter takes a source implementing `AsyncRead`, and allows the receive side to
/// pass along seek requests needed. This allows for passing bytes from exclusively `AsyncRead`
/// streams (e.g., hyper HTTP sessions) to Songbird.
pub struct AsyncAdapterStream {
// Note: this mutex is here to appease symphonia's Send + Sync bound.
// Only one thread should own and pull from this stream, so in practice
// there is no contention.
bytes_out: Mutex<HeapCons<u8>>,
can_seek: bool,
// Note: these are Atomic just to work around the need for
// check_messages to take &self rather than &mut.
finalised: AtomicBool,
bytes_known_present: AtomicBool,
req_tx: Sender<AdapterRequest>,
resp_rx: Receiver<AdapterResponse>,
notify_tx: Arc<Notify>,
}
impl AsyncAdapterStream {
/// Wrap and pull from an async file stream, with an intermediate ring-buffer of size `buf_len`
/// between the async and sync halves.
#[must_use]
pub fn new(stream: Box<dyn AsyncMediaSource>, buf_len: usize) -> AsyncAdapterStream {
let (bytes_in, bytes_out) = SharedRb::<Heap<_>>::new(buf_len).split();
let bytes_out = bytes_out.into();
let (resp_tx, resp_rx) = flume::unbounded();
let (req_tx, req_rx) = flume::unbounded();
let can_seek = stream.is_seekable();
let notify_rx = Arc::new(Notify::new());
let notify_tx = notify_rx.clone();
let sink = AsyncAdapterSink {
bytes_in,
req_rx,
resp_tx,
stream,
notify_rx,
};
let stream = AsyncAdapterStream {
bytes_out,
can_seek,
finalised: false.into(),
bytes_known_present: false.into(),
req_tx,
resp_rx,
notify_tx,
};
tokio::spawn(async move {
Box::pin(sink.launch()).await;
});
stream
}
fn handle_messages(&self, op: Operation) -> Option<AdapterResponse> {
loop {
let msg = if op.will_block() {
self.resp_rx.recv().ok()
} else {
self.resp_rx.try_recv().ok()
};
let Some(msg) = msg else { break None };
// state changes
match &msg {
AdapterResponse::ReadZero => {
self.finalised.store(true, Ordering::Relaxed);
},
AdapterResponse::ReadOccurred => {
self.bytes_known_present.store(true, Ordering::Relaxed);
},
_ => {},
}
if op.expected_msg(&msg) {
break Some(msg);
}
}
}
fn is_dropped_and_clear(&self) -> bool {
self.resp_rx.is_empty() && self.resp_rx.is_disconnected()
}
fn check_dropped(&self) -> IoResult<()> {
if self.is_dropped_and_clear() {
Err(IoError::new(
IoErrorKind::UnexpectedEof,
"Async half was dropped.",
))
} else {
Ok(())
}
}
}
impl Read for AsyncAdapterStream {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
loop {
let block = !(self.bytes_known_present.load(Ordering::Relaxed)
|| self.finalised.load(Ordering::Relaxed));
drop(self.handle_messages(Operation::Read { block }));
let mut rb = self.bytes_out.lock();
match rb.read(buf) {
Ok(n) => {
self.notify_tx.notify_one();
return Ok(n);
},
Err(e) if e.kind() == IoErrorKind::WouldBlock => {
// receive side must ABSOLUTELY be unblocked here.
self.notify_tx.notify_one();
if self.finalised.load(Ordering::Relaxed) {
return Ok(0);
}
self.bytes_known_present.store(false, Ordering::Relaxed);
self.check_dropped()?;
},
a => {
println!("Misc err {a:?}");
return a;
},
}
}
}
}
impl Seek for AsyncAdapterStream {
fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
if !self.can_seek {
return Err(IoError::new(
IoErrorKind::Unsupported,
"Async half does not support seek operations.",
));
}
self.check_dropped()?;
_ = self.req_tx.send(AdapterRequest::Seek(pos));
// wait for async to tell us that it has stopped writing,
// then clear buf and allow async to write again.
self.finalised.store(false, Ordering::Relaxed);
match self.handle_messages(Operation::Seek) {
Some(AdapterResponse::SeekClear) => {},
None => self.check_dropped().map(|()| unreachable!())?,
_ => unreachable!(),
}
let mut rb = self.bytes_out.lock();
let cap = rb.capacity();
rb.skip(cap.into());
_ = self.req_tx.send(AdapterRequest::SeekCleared);
match self.handle_messages(Operation::Seek) {
Some(AdapterResponse::SeekResult(a)) => a,
None => self.check_dropped().map(|()| unreachable!()),
_ => unreachable!(),
}
}
}
impl MediaSource for AsyncAdapterStream {
fn is_seekable(&self) -> bool {
self.can_seek
}
fn byte_len(&self) -> Option<u64> {
self.check_dropped().ok()?;
_ = self.req_tx.send(AdapterRequest::ByteLen);
match self.handle_messages(Operation::Len) {
Some(AdapterResponse::ByteLen(a)) => a,
None => self.check_dropped().ok().map(|()| unreachable!()),
_ => unreachable!(),
}
}
}
enum AdapterRequest {
Wake,
Seek(SeekFrom),
SeekCleared,
ByteLen,
}
enum AdapterResponse {
SeekResult(IoResult<u64>),
SeekClear,
ByteLen(Option<u64>),
ReadZero,
ReadOccurred,
}
#[derive(Copy, Clone)]
enum Operation {
Read { block: bool },
Seek,
Len,
}
impl Operation {
fn will_block(self) -> bool {
match self {
Self::Read { block } => block,
_ => true,
}
}
fn expected_msg(self, msg: &AdapterResponse) -> bool {
match self {
Self::Read { .. } => matches!(
msg,
AdapterResponse::ReadOccurred | AdapterResponse::ReadZero
),
Self::Seek => matches!(
msg,
AdapterResponse::SeekResult(_) | AdapterResponse::SeekClear
),
Self::Len => matches!(msg, AdapterResponse::ByteLen(_)),
}
}
}
/// An async port of symphonia's [`MediaSource`].
///
/// Streams which are not seekable should implement `AsyncSeek` such that all operations
/// fail with `Unsupported`, and implement `fn is_seekable(&self) -> { false }`.
///
/// [`MediaSource`]: MediaSource
#[async_trait]
pub trait AsyncMediaSource: AsyncRead + AsyncSeek + Send + Sync + Unpin {
/// Returns if the source is seekable. This may be an expensive operation.
fn is_seekable(&self) -> bool;
/// Returns the length in bytes, if available. This may be an expensive operation.
async fn byte_len(&self) -> Option<u64>;
/// Tries to recreate this stream in event of an error, resuming from the given offset.
async fn try_resume(
&mut self,
_offset: u64,
) -> Result<Box<dyn AsyncMediaSource>, AudioStreamError> {
Err(AudioStreamError::Unsupported)
}
}