#![allow(
clippy::derive_partial_eq_without_eq,
clippy::missing_errors_doc,
clippy::must_use_candidate
)]
use core::ffi::{c_char, c_void};
use core::ptr;
use serde::Deserialize;
use crate::error::{from_swift, AVPlayerError};
use crate::ffi;
use crate::player::Player;
use crate::reader::VideoOutputSettings;
use crate::time::Time;
use crate::util::{maybe_json_cstring, parse_json_and_free};
pub type PlayerVideoOutputSettings = VideoOutputSettings;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PlayerVideoOutputTagCollectionPreset {
Monoscopic,
Stereoscopic,
}
impl PlayerVideoOutputTagCollectionPreset {
const fn raw(self) -> u32 {
match self {
Self::Monoscopic => 0,
Self::Stereoscopic => 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PlayerVideoOutputTagCollectionPayload {
tags: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AffineTransform {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
pub tx: f64,
pub ty: f64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlayerVideoOutputConfiguration {
pub has_source_player_item: bool,
pub data_channel_descriptions: Vec<Vec<String>>,
pub preferred_transform: Option<AffineTransform>,
pub activation_time: Time,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PlayerVideoTaggedBufferKind {
PixelBuffer,
SampleBuffer,
Unknown(String),
}
impl PlayerVideoTaggedBufferKind {
fn from_raw(raw: &str) -> Self {
match raw {
"pixel_buffer" => Self::PixelBuffer,
"sample_buffer" => Self::SampleBuffer,
other => Self::Unknown(other.to_owned()),
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PlayerVideoTaggedBufferPayload {
tags: Vec<String>,
buffer_kind: String,
pixel_buffer_width: Option<usize>,
pixel_buffer_height: Option<usize>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PlayerVideoTaggedBuffer {
pub tags: Vec<String>,
pub kind: PlayerVideoTaggedBufferKind,
pub pixel_buffer_width: Option<usize>,
pub pixel_buffer_height: Option<usize>,
}
impl From<PlayerVideoTaggedBufferPayload> for PlayerVideoTaggedBuffer {
fn from(payload: PlayerVideoTaggedBufferPayload) -> Self {
Self {
tags: payload.tags,
kind: PlayerVideoTaggedBufferKind::from_raw(&payload.buffer_kind),
pixel_buffer_width: payload.pixel_buffer_width,
pixel_buffer_height: payload.pixel_buffer_height,
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PlayerVideoOutputSamplePayload {
tagged_buffers: Vec<PlayerVideoTaggedBufferPayload>,
presentation_time: Time,
active_configuration: PlayerVideoOutputConfiguration,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PlayerVideoOutputSample {
pub tagged_buffers: Vec<PlayerVideoTaggedBuffer>,
pub presentation_time: Time,
pub active_configuration: PlayerVideoOutputConfiguration,
}
impl From<PlayerVideoOutputSamplePayload> for PlayerVideoOutputSample {
fn from(payload: PlayerVideoOutputSamplePayload) -> Self {
Self {
tagged_buffers: payload
.tagged_buffers
.into_iter()
.map(PlayerVideoTaggedBuffer::from)
.collect(),
presentation_time: payload.presentation_time,
active_configuration: payload.active_configuration,
}
}
}
pub struct PlayerVideoOutputTagCollection {
pub(crate) ptr: *mut c_void,
}
impl Drop for PlayerVideoOutputTagCollection {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { ffi::av_player_video_output_tag_collection_release(self.ptr) };
self.ptr = ptr::null_mut();
}
}
}
impl PlayerVideoOutputTagCollection {
pub fn from_preset(preset: PlayerVideoOutputTagCollectionPreset) -> Result<Self, AVPlayerError> {
let mut err: *mut c_char = ptr::null_mut();
let ptr = unsafe {
ffi::av_player_video_output_tag_collection_create_with_preset(preset.raw(), &mut err)
};
if ptr.is_null() {
return Err(unsafe { from_swift(ffi::status::OPERATION_FAILED, err) });
}
Ok(Self { ptr })
}
fn info(&self) -> Result<PlayerVideoOutputTagCollectionPayload, AVPlayerError> {
let mut err: *mut c_char = ptr::null_mut();
let json_ptr = unsafe { ffi::av_player_video_output_tag_collection_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 tags(&self) -> Result<Vec<String>, AVPlayerError> {
Ok(self.info()?.tags)
}
}
pub struct VideoOutputSpecification {
pub(crate) ptr: *mut c_void,
}
impl Drop for VideoOutputSpecification {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { ffi::av_video_output_specification_release(self.ptr) };
self.ptr = ptr::null_mut();
}
}
}
impl VideoOutputSpecification {
pub fn new(tag_collections: &[&PlayerVideoOutputTagCollection]) -> Result<Self, AVPlayerError> {
if tag_collections.is_empty() {
return Err(AVPlayerError::InvalidArgument(
"video output specifications require at least one tag collection".into(),
));
}
let mut err: *mut c_char = ptr::null_mut();
let ptrs = tag_collections.iter().map(|collection| collection.ptr).collect::<Vec<_>>();
let ptr = unsafe {
ffi::av_video_output_specification_create(ptrs.as_ptr(), ptrs.len(), &mut err)
};
if ptr.is_null() {
return Err(unsafe { from_swift(ffi::status::OPERATION_FAILED, err) });
}
Ok(Self { ptr })
}
fn preferred_tag_collection_payloads(
&self,
) -> Result<Vec<PlayerVideoOutputTagCollectionPayload>, AVPlayerError> {
let mut err: *mut c_char = ptr::null_mut();
let json_ptr = unsafe { ffi::av_video_output_specification_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 preferred_tag_collections(&self) -> Result<Vec<Vec<String>>, AVPlayerError> {
Ok(self
.preferred_tag_collection_payloads()?
.into_iter()
.map(|payload| payload.tags)
.collect())
}
pub fn set_default_output_settings(
&self,
settings: Option<&PlayerVideoOutputSettings>,
) -> Result<(), AVPlayerError> {
let settings = maybe_json_cstring(settings, "default video output settings")?;
let mut err: *mut c_char = ptr::null_mut();
let status = unsafe {
ffi::av_video_output_specification_set_default_output_settings(
self.ptr,
settings.as_ref().map_or(ptr::null(), |settings| settings.as_ptr()),
&mut err,
)
};
if status != ffi::status::OK {
return Err(unsafe { from_swift(status, err) });
}
Ok(())
}
pub fn set_output_settings_for_tag_collection(
&self,
tag_collection: &PlayerVideoOutputTagCollection,
settings: Option<&PlayerVideoOutputSettings>,
) -> Result<(), AVPlayerError> {
let settings = maybe_json_cstring(settings, "video output tag collection settings")?;
let mut err: *mut c_char = ptr::null_mut();
let status = unsafe {
ffi::av_video_output_specification_set_output_settings_for_tag_collection(
self.ptr,
settings.as_ref().map_or(ptr::null(), |settings| settings.as_ptr()),
tag_collection.ptr,
&mut err,
)
};
if status != ffi::status::OK {
return Err(unsafe { from_swift(status, err) });
}
Ok(())
}
}
pub struct PlayerVideoOutput {
pub(crate) ptr: *mut c_void,
}
impl Drop for PlayerVideoOutput {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { ffi::av_player_video_output_release(self.ptr) };
self.ptr = ptr::null_mut();
}
}
}
impl PlayerVideoOutput {
pub fn new(specification: &VideoOutputSpecification) -> Result<Self, AVPlayerError> {
let mut err: *mut c_char = ptr::null_mut();
let ptr = unsafe { ffi::av_player_video_output_create(specification.ptr, &mut err) };
if ptr.is_null() {
return Err(unsafe { from_swift(ffi::status::OPERATION_FAILED, err) });
}
Ok(Self { ptr })
}
pub fn sample_for_host_time(&self, host_time: Time) -> Result<Option<PlayerVideoOutputSample>, AVPlayerError> {
let (value, timescale, kind) = host_time.to_raw();
let mut err: *mut c_char = ptr::null_mut();
let json_ptr = unsafe {
ffi::av_player_video_output_sample_json(self.ptr, value, timescale, kind, &mut err)
};
if json_ptr.is_null() {
return Err(unsafe { from_swift(ffi::status::OPERATION_FAILED, err) });
}
Ok(parse_json_and_free::<Option<PlayerVideoOutputSamplePayload>>(json_ptr)?
.map(PlayerVideoOutputSample::from))
}
}
impl Player {
pub fn set_video_output(&self, output: Option<&PlayerVideoOutput>) {
unsafe { ffi::av_player_set_video_output(self.ptr, output.map_or(ptr::null_mut(), |output| output.ptr)) };
}
pub fn video_output(&self) -> Option<PlayerVideoOutput> {
let ptr = unsafe { ffi::av_player_copy_video_output(self.ptr) };
(!ptr.is_null()).then_some(PlayerVideoOutput { ptr })
}
}