avplayer 0.5.0

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, c_void};
use core::ptr;
use std::ffi::CString;

use apple_cf::cm::CMSampleBuffer;
use serde::Deserialize;

use crate::error::{from_swift, AVPlayerError};
use crate::ffi;
use crate::player_layer::VideoGravity;
use crate::util::parse_json_and_free;

#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SampleBufferDisplayLayerPayload {
    status: i32,
    error_message: Option<String>,
    video_gravity: String,
    ready_for_display: bool,
    ready_for_more_media_data: bool,
    has_sufficient_media_data_for_reliable_playback_start: bool,
    requires_flush_to_resume_decoding: bool,
    prevents_capture: bool,
    prevents_display_sleep_during_video_playback: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum QueuedSampleBufferRenderingStatus {
    Unknown,
    Rendering,
    Failed,
}

impl QueuedSampleBufferRenderingStatus {
    #[must_use]
    pub const fn from_raw(raw: i32) -> Self {
        match raw {
            1 => Self::Rendering,
            2 => Self::Failed,
            _ => Self::Unknown,
        }
    }
}

#[derive(Debug)]
pub struct SampleBufferDisplayLayer {
    ptr: *mut c_void,
}

impl Drop for SampleBufferDisplayLayer {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe { ffi::av_sample_buffer_display_layer_release(self.ptr) };
            self.ptr = ptr::null_mut();
        }
    }
}

unsafe impl Send for SampleBufferDisplayLayer {}

impl SampleBufferDisplayLayer {
    pub fn new() -> Result<Self, AVPlayerError> {
        let ptr = unsafe { ffi::av_sample_buffer_display_layer_create() };
        if ptr.is_null() {
            return Err(AVPlayerError::OperationFailed(
                "bridge returned null AVSampleBufferDisplayLayer".into(),
            ));
        }
        Ok(Self { ptr })
    }

    fn info(&self) -> Result<SampleBufferDisplayLayerPayload, AVPlayerError> {
        let mut err: *mut c_char = ptr::null_mut();
        let json_ptr = unsafe { ffi::av_sample_buffer_display_layer_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 status(&self) -> Result<QueuedSampleBufferRenderingStatus, AVPlayerError> {
        Ok(QueuedSampleBufferRenderingStatus::from_raw(
            self.info()?.status,
        ))
    }

    pub fn error(&self) -> Result<Option<String>, AVPlayerError> {
        Ok(self.info()?.error_message)
    }

    pub fn video_gravity(&self) -> Result<VideoGravity, AVPlayerError> {
        Ok(VideoGravity::from_raw(&self.info()?.video_gravity))
    }

    pub fn set_video_gravity(&self, video_gravity: VideoGravity) -> Result<(), AVPlayerError> {
        let video_gravity = CString::new(video_gravity.as_raw()).map_err(|error| {
            AVPlayerError::InvalidArgument(format!("video gravity contains NUL byte: {error}"))
        })?;
        unsafe {
            ffi::av_sample_buffer_display_layer_set_video_gravity(self.ptr, video_gravity.as_ptr());
        };
        Ok(())
    }

    pub fn is_ready_for_display(&self) -> Result<bool, AVPlayerError> {
        Ok(self.info()?.ready_for_display)
    }

    pub fn is_ready_for_more_media_data(&self) -> Result<bool, AVPlayerError> {
        Ok(self.info()?.ready_for_more_media_data)
    }

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

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

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

    pub fn set_prevents_capture(&self, prevents_capture: bool) {
        unsafe {
            ffi::av_sample_buffer_display_layer_set_prevents_capture(self.ptr, prevents_capture);
        };
    }

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

    pub fn set_prevents_display_sleep_during_video_playback(&self, prevents_display_sleep: bool) {
        unsafe {
            ffi::av_sample_buffer_display_layer_set_prevents_display_sleep(
                self.ptr,
                prevents_display_sleep,
            );
        };
    }

    pub fn enqueue_sample_buffer(&self, sample_buffer: &CMSampleBuffer) {
        unsafe {
            ffi::av_sample_buffer_display_layer_enqueue_sample_buffer(
                self.ptr,
                sample_buffer.as_ptr(),
            );
        };
    }

    pub fn flush(&self) {
        unsafe { ffi::av_sample_buffer_display_layer_flush(self.ptr) };
    }

    pub fn flush_and_remove_image(&self) {
        unsafe { ffi::av_sample_buffer_display_layer_flush_and_remove_image(self.ptr) };
    }

    pub fn stop_requesting_media_data(&self) {
        unsafe { ffi::av_sample_buffer_display_layer_stop_requesting_media_data(self.ptr) };
    }
}