#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]
use std::ffi::{c_void, CStr};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use doom_fish_utils::completion::{error_from_cstr, AsyncCompletion, AsyncCompletionFuture};
use serde::Deserialize;
use crate::asset::{Asset, MediaType, Size};
use crate::error::AVPlayerError;
use crate::ffi;
use crate::metadata::MetadataItem;
use crate::player::{Player, PlayerItem};
use crate::time::Time;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct AsyncAssetPropertiesPayload {
duration: Time,
metadata: Vec<MetadataItem>,
is_playable: bool,
is_exportable: bool,
has_protected_content: bool,
preferred_rate: f32,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct TrackInfoPayload {
track_id: i32,
media_type: String,
natural_size: Size,
nominal_frame_rate: String,
estimated_data_rate: String,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct AssetProperties {
pub duration: Time,
pub metadata: Vec<MetadataItem>,
pub is_playable: bool,
pub is_exportable: bool,
pub has_protected_content: bool,
pub preferred_rate: f32,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct TrackProperties {
pub track_id: i32,
pub media_type: MediaType,
pub natural_size: Size,
pub nominal_frame_rate: Option<f32>,
pub estimated_data_rate: Option<f32>,
}
impl TrackProperties {
fn from_payload(p: &TrackInfoPayload) -> Self {
Self {
track_id: p.track_id,
media_type: MediaType::from_raw(&p.media_type),
natural_size: p.natural_size,
nominal_frame_rate: p.nominal_frame_rate.parse().ok(),
estimated_data_rate: p.estimated_data_rate.parse().ok(),
}
}
}
const fn bridge_err(msg: String) -> AVPlayerError {
AVPlayerError::LoadFailed(msg)
}
extern "C" fn asset_properties_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<String>::complete_err(ctx, msg) };
} else if !result.is_null() {
let s = unsafe { CStr::from_ptr(result.cast()).to_string_lossy().into_owned() };
unsafe { ffi::avp_string_free(result as *mut _) };
unsafe { AsyncCompletion::complete_ok(ctx, s) };
} else {
unsafe {
AsyncCompletion::<String>::complete_err(ctx, "bridge returned null result".into());
};
}
}
pub struct AssetPropertiesFuture {
inner: AsyncCompletionFuture<String>,
}
impl std::fmt::Debug for AssetPropertiesFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AssetPropertiesFuture")
.finish_non_exhaustive()
}
}
impl Future for AssetPropertiesFuture {
type Output = Result<AssetProperties, AVPlayerError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
let json = r.map_err(bridge_err)?;
let p = serde_json::from_str::<AsyncAssetPropertiesPayload>(&json).map_err(|e| {
AVPlayerError::OperationFailed(format!("async bridge JSON decode failed: {e}"))
})?;
Ok(AssetProperties {
duration: p.duration,
metadata: p.metadata,
is_playable: p.is_playable,
is_exportable: p.is_exportable,
has_protected_content: p.has_protected_content,
preferred_rate: p.preferred_rate,
})
})
}
}
extern "C" fn asset_tracks_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
asset_properties_cb(result, error, ctx); }
pub struct AssetTracksFuture {
inner: AsyncCompletionFuture<String>,
}
impl std::fmt::Debug for AssetTracksFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AssetTracksFuture").finish_non_exhaustive()
}
}
impl Future for AssetTracksFuture {
type Output = Result<Vec<TrackProperties>, AVPlayerError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
let json = r.map_err(bridge_err)?;
let payloads = serde_json::from_str::<Vec<TrackInfoPayload>>(&json).map_err(|e| {
AVPlayerError::OperationFailed(format!("async bridge JSON decode failed: {e}"))
})?;
Ok(payloads
.into_iter()
.map(|p| TrackProperties::from_payload(&p))
.collect())
})
}
}
extern "C" fn asset_track_by_id_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
asset_properties_cb(result, error, ctx); }
pub struct AssetTrackByIdFuture {
inner: AsyncCompletionFuture<String>,
}
impl std::fmt::Debug for AssetTrackByIdFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AssetTrackByIdFuture")
.finish_non_exhaustive()
}
}
impl Future for AssetTrackByIdFuture {
type Output = Result<Option<TrackProperties>, AVPlayerError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
let json = r.map_err(bridge_err)?;
if json.trim() == "null" {
return Ok(None);
}
let payload = serde_json::from_str::<TrackInfoPayload>(&json).map_err(|e| {
AVPlayerError::OperationFailed(format!("async bridge JSON decode failed: {e}"))
})?;
Ok(Some(TrackProperties::from_payload(&payload)))
})
}
}
extern "C" fn bool_completion_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
if error.is_null() {
let finished = !result.is_null();
unsafe { AsyncCompletion::complete_ok(ctx, finished) };
} else {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<bool>::complete_err(ctx, msg) };
}
}
pub struct PlayerItemSeekFuture {
inner: AsyncCompletionFuture<bool>,
}
impl std::fmt::Debug for PlayerItemSeekFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PlayerItemSeekFuture")
.finish_non_exhaustive()
}
}
impl Future for PlayerItemSeekFuture {
type Output = Result<bool, AVPlayerError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(bridge_err))
}
}
pub struct PlayerSeekFuture {
inner: AsyncCompletionFuture<bool>,
}
impl std::fmt::Debug for PlayerSeekFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PlayerSeekFuture").finish_non_exhaustive()
}
}
impl Future for PlayerSeekFuture {
type Output = Result<bool, AVPlayerError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(bridge_err))
}
}
pub struct PlayerPrerollFuture {
inner: AsyncCompletionFuture<bool>,
}
impl std::fmt::Debug for PlayerPrerollFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PlayerPrerollFuture")
.finish_non_exhaustive()
}
}
impl Future for PlayerPrerollFuture {
type Output = Result<bool, AVPlayerError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(bridge_err))
}
}
pub struct AsyncAsset<'a> {
asset: &'a Asset,
}
impl std::fmt::Debug for AsyncAsset<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncAsset").finish_non_exhaustive()
}
}
impl<'a> AsyncAsset<'a> {
pub const fn new(asset: &'a Asset) -> Self {
Self { asset }
}
pub fn load_properties(&self) -> AssetPropertiesFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe { ffi::avp_asset_load_properties_async(self.asset.ptr, asset_properties_cb, ctx) };
AssetPropertiesFuture { inner: future }
}
pub fn load_tracks(&self) -> AssetTracksFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe { ffi::avp_asset_load_tracks_async(self.asset.ptr, asset_tracks_cb, ctx) };
AssetTracksFuture { inner: future }
}
pub fn load_tracks_with_media_type(&self, media_type: &str) -> AssetTracksFuture {
use std::ffi::CString;
let mt = CString::new(media_type).unwrap_or_default();
let (future, ctx) = AsyncCompletion::create();
unsafe {
ffi::avp_asset_load_tracks_with_media_type_async(
self.asset.ptr,
mt.as_ptr(),
asset_tracks_cb,
ctx,
);
};
AssetTracksFuture { inner: future }
}
pub fn load_track_with_id(&self, track_id: i32) -> AssetTrackByIdFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe {
ffi::avp_asset_load_track_with_id_async(
self.asset.ptr,
track_id,
asset_track_by_id_cb,
ctx,
);
};
AssetTrackByIdFuture { inner: future }
}
}
pub struct AsyncPlayerItem<'a> {
item: &'a PlayerItem,
}
impl std::fmt::Debug for AsyncPlayerItem<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncPlayerItem").finish_non_exhaustive()
}
}
impl<'a> AsyncPlayerItem<'a> {
pub const fn new(item: &'a PlayerItem) -> Self {
Self { item }
}
pub fn seek(&self, time: Time) -> PlayerItemSeekFuture {
let (future, ctx) = AsyncCompletion::create();
let (value, timescale, kind) = time.to_raw();
unsafe {
ffi::avp_player_item_seek_async(
self.item.ptr,
value,
timescale,
kind,
bool_completion_cb,
ctx,
);
};
PlayerItemSeekFuture { inner: future }
}
}
pub struct AsyncPlayer<'a> {
player: &'a Player,
}
impl std::fmt::Debug for AsyncPlayer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncPlayer").finish_non_exhaustive()
}
}
impl<'a> AsyncPlayer<'a> {
pub const fn new(player: &'a Player) -> Self {
Self { player }
}
pub fn seek(&self, time: Time) -> PlayerSeekFuture {
let (future, ctx) = AsyncCompletion::create();
let (value, timescale, kind) = time.to_raw();
unsafe {
ffi::avp_player_seek_async(
self.player.ptr,
value,
timescale,
kind,
bool_completion_cb,
ctx,
);
};
PlayerSeekFuture { inner: future }
}
pub fn preroll(&self, rate: f32) -> PlayerPrerollFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe {
ffi::avp_player_preroll_async(self.player.ptr, rate, bool_completion_cb, ctx);
};
PlayerPrerollFuture { inner: future }
}
}