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 std::path::Path;

use serde::Deserialize;

use crate::asset::UrlAsset;
use crate::error::{from_swift, AVPlayerError};
use crate::ffi;
use crate::util::{parse_json_and_free, to_cstring};

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ContentKeySystem {
    FairPlayStreaming,
    ClearKey,
    AuthorizationToken,
    Unknown(String),
}

impl ContentKeySystem {
    fn from_raw(raw: &str) -> Self {
        match raw {
            "fair_play_streaming" => Self::FairPlayStreaming,
            "clear_key" => Self::ClearKey,
            "authorization_token" => Self::AuthorizationToken,
            other => Self::Unknown(other.to_owned()),
        }
    }

    fn as_raw(&self) -> &str {
        match self {
            Self::FairPlayStreaming => "fair_play_streaming",
            Self::ClearKey => "clear_key",
            Self::AuthorizationToken => "authorization_token",
            Self::Unknown(raw) => raw,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ContentKeySessionInfoPayload {
    key_system: String,
    storage_url: Option<String>,
    content_protection_session_identifier_base64: Option<String>,
    recipient_count: usize,
}

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

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

impl UrlAsset {
    pub fn may_require_content_keys_for_media_data_processing(&self) -> bool {
        unsafe {
            ffi::av_url_asset_may_require_content_keys_for_media_data_processing(self.asset.ptr)
        }
    }
}

impl ContentKeySession {
    pub fn new(key_system: &ContentKeySystem) -> Result<Self, AVPlayerError> {
        let key_system = CString::new(key_system.as_raw()).map_err(|error| {
            AVPlayerError::InvalidArgument(format!("key system contains NUL byte: {error}"))
        })?;
        let mut err: *mut c_char = ptr::null_mut();
        let ptr = unsafe { ffi::av_content_key_session_create(key_system.as_ptr(), &mut err) };
        if ptr.is_null() {
            return Err(unsafe { from_swift(ffi::status::OPERATION_FAILED, err) });
        }
        Ok(Self { ptr })
    }

    pub fn with_storage_directory(
        key_system: &ContentKeySystem,
        path: impl AsRef<Path>,
    ) -> Result<Self, AVPlayerError> {
        let key_system = CString::new(key_system.as_raw()).map_err(|error| {
            AVPlayerError::InvalidArgument(format!("key system contains NUL byte: {error}"))
        })?;
        let path = path
            .as_ref()
            .to_str()
            .ok_or_else(|| AVPlayerError::InvalidArgument("path is not valid UTF-8".into()))?;
        let path = to_cstring(path, "content-key storage directory")?;
        let mut err: *mut c_char = ptr::null_mut();
        let ptr = unsafe {
            ffi::av_content_key_session_create_with_storage_directory(
                key_system.as_ptr(),
                path.as_ptr(),
                &mut err,
            )
        };
        if ptr.is_null() {
            return Err(unsafe { from_swift(ffi::status::OPERATION_FAILED, err) });
        }
        Ok(Self { ptr })
    }

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

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

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

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

    pub fn add_content_key_recipient(&self, recipient: &UrlAsset) {
        unsafe {
            ffi::av_content_key_session_add_content_key_recipient(self.ptr, recipient.asset.ptr);
        }
    }

    pub fn remove_content_key_recipient(&self, recipient: &UrlAsset) {
        unsafe {
            ffi::av_content_key_session_remove_content_key_recipient(self.ptr, recipient.asset.ptr);
        }
    }

    pub fn expire(&self) {
        unsafe { ffi::av_content_key_session_expire(self.ptr) };
    }
}