avplayer 0.2.1

Safe Rust bindings for Apple's AVPlayer + AVAssetReader — playback and frame-by-frame asset reading on macOS
Documentation
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

use core::ffi::c_char;
use core::ptr;
use std::ffi::CString;

use serde::Deserialize;

use crate::asset::Size;
use crate::error::{from_swift, AVPlayerError};
use crate::ffi;
use crate::player::PlayerItem;
use crate::time::TimeRange;
use crate::util::parse_json_and_free;

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ExtendedPlayerItemInfoPayload {
    automatically_loaded_asset_keys: Vec<String>,
    seekable_time_ranges: Vec<TimeRange>,
    loaded_time_ranges: Vec<TimeRange>,
    can_use_network_resources_for_live_streaming_while_paused: bool,
    preferred_forward_buffer_duration: f64,
    preferred_peak_bit_rate: f64,
    preferred_peak_bit_rate_for_expensive_networks: f64,
    preferred_maximum_resolution: Size,
    preferred_maximum_resolution_for_expensive_networks: Size,
    audio_time_pitch_algorithm: String,
    output_count: usize,
    track_count: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AudioTimePitchAlgorithm {
    Spectral,
    TimeDomain,
    Varispeed,
    LowQualityZeroLatency,
    Unknown(String),
}

impl AudioTimePitchAlgorithm {
    #[must_use]
    pub fn from_raw(raw: &str) -> Self {
        match raw {
            "spectral" => Self::Spectral,
            "time_domain" => Self::TimeDomain,
            "varispeed" => Self::Varispeed,
            "low_quality_zero_latency" => Self::LowQualityZeroLatency,
            other => Self::Unknown(other.to_owned()),
        }
    }

    #[must_use]
    pub fn as_raw(&self) -> &str {
        match self {
            Self::Spectral => "spectral",
            Self::TimeDomain => "time_domain",
            Self::Varispeed => "varispeed",
            Self::LowQualityZeroLatency => "low_quality_zero_latency",
            Self::Unknown(raw) => raw,
        }
    }
}

impl PlayerItem {
    fn extended_info(&self) -> Result<ExtendedPlayerItemInfoPayload, AVPlayerError> {
        let mut err: *mut c_char = ptr::null_mut();
        let json_ptr = unsafe { ffi::av_player_item_info_json(self.ptr, &mut err) };
        if json_ptr.is_null() {
            return Err(unsafe { from_swift(ffi::status::OPERATION_FAILED, err) });
        }
        parse_json_and_free(json_ptr)
    }

    pub fn automatically_loaded_asset_keys(&self) -> Result<Vec<String>, AVPlayerError> {
        Ok(self.extended_info()?.automatically_loaded_asset_keys)
    }

    pub fn seekable_time_ranges(&self) -> Result<Vec<TimeRange>, AVPlayerError> {
        Ok(self.extended_info()?.seekable_time_ranges)
    }

    pub fn loaded_time_ranges(&self) -> Result<Vec<TimeRange>, AVPlayerError> {
        Ok(self.extended_info()?.loaded_time_ranges)
    }

    pub fn can_use_network_resources_for_live_streaming_while_paused(
        &self,
    ) -> Result<bool, AVPlayerError> {
        Ok(self
            .extended_info()?
            .can_use_network_resources_for_live_streaming_while_paused)
    }

    pub fn set_can_use_network_resources_for_live_streaming_while_paused(&self, enabled: bool) {
        unsafe {
            ffi::av_player_item_set_can_use_network_resources_for_live_streaming_while_paused(
                self.ptr, enabled,
            );
        }
    }

    pub fn preferred_forward_buffer_duration(&self) -> Result<f64, AVPlayerError> {
        Ok(self.extended_info()?.preferred_forward_buffer_duration)
    }

    pub fn set_preferred_forward_buffer_duration(&self, duration: f64) {
        unsafe { ffi::av_player_item_set_preferred_forward_buffer_duration(self.ptr, duration) };
    }

    pub fn preferred_peak_bit_rate(&self) -> Result<f64, AVPlayerError> {
        Ok(self.extended_info()?.preferred_peak_bit_rate)
    }

    pub fn set_preferred_peak_bit_rate(&self, value: f64) {
        unsafe { ffi::av_player_item_set_preferred_peak_bit_rate(self.ptr, value) };
    }

    pub fn preferred_peak_bit_rate_for_expensive_networks(&self) -> Result<f64, AVPlayerError> {
        Ok(self
            .extended_info()?
            .preferred_peak_bit_rate_for_expensive_networks)
    }

    pub fn set_preferred_peak_bit_rate_for_expensive_networks(&self, value: f64) {
        unsafe {
            ffi::av_player_item_set_preferred_peak_bit_rate_for_expensive_networks(self.ptr, value);
        }
    }

    pub fn preferred_maximum_resolution(&self) -> Result<Size, AVPlayerError> {
        Ok(self.extended_info()?.preferred_maximum_resolution)
    }

    pub fn set_preferred_maximum_resolution(&self, value: Size) {
        unsafe {
            ffi::av_player_item_set_preferred_maximum_resolution(
                self.ptr,
                value.width,
                value.height,
            );
        }
    }

    pub fn preferred_maximum_resolution_for_expensive_networks(
        &self,
    ) -> Result<Size, AVPlayerError> {
        Ok(self
            .extended_info()?
            .preferred_maximum_resolution_for_expensive_networks)
    }

    pub fn set_preferred_maximum_resolution_for_expensive_networks(&self, value: Size) {
        unsafe {
            ffi::av_player_item_set_preferred_maximum_resolution_for_expensive_networks(
                self.ptr,
                value.width,
                value.height,
            );
        }
    }

    pub fn audio_time_pitch_algorithm(&self) -> Result<AudioTimePitchAlgorithm, AVPlayerError> {
        Ok(AudioTimePitchAlgorithm::from_raw(
            &self.extended_info()?.audio_time_pitch_algorithm,
        ))
    }

    pub fn set_audio_time_pitch_algorithm(
        &self,
        algorithm: &AudioTimePitchAlgorithm,
    ) -> Result<(), AVPlayerError> {
        let algorithm = CString::new(algorithm.as_raw()).map_err(|error| {
            AVPlayerError::InvalidArgument(format!(
                "audio time pitch algorithm contains NUL byte: {error}"
            ))
        })?;
        unsafe { ffi::av_player_item_set_audio_time_pitch_algorithm(self.ptr, algorithm.as_ptr()) };
        Ok(())
    }

    pub fn output_count(&self) -> Result<usize, AVPlayerError> {
        Ok(self.extended_info()?.output_count)
    }

    pub fn track_count(&self) -> Result<usize, AVPlayerError> {
        Ok(self.extended_info()?.track_count)
    }
}