use std::collections::HashMap;
use std::ffi::c_void;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use crate::cm::CMTime;
static RECORDING_DELEGATE_REGISTRY: Mutex<Option<HashMap<usize, RecordingDelegateEntry>>> =
Mutex::new(None);
static NEXT_DELEGATE_ID: AtomicUsize = AtomicUsize::new(1);
struct RecordingDelegateEntry {
delegate: Box<dyn SCRecordingOutputDelegate>,
ref_count: usize,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SCRecordingOutputCodec {
#[default]
H264 = 0,
HEVC = 1,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SCRecordingOutputFileType {
#[default]
MP4 = 0,
MOV = 1,
}
pub struct SCRecordingOutputConfiguration {
ptr: *const c_void,
}
impl SCRecordingOutputConfiguration {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { crate::ffi::sc_recording_output_configuration_create() };
Self { ptr }
}
#[must_use]
pub fn with_output_url(self, path: &Path) -> Self {
if let Some(path_str) = path.to_str() {
if let Ok(c_path) = std::ffi::CString::new(path_str) {
unsafe {
crate::ffi::sc_recording_output_configuration_set_output_url(
self.ptr,
c_path.as_ptr(),
);
}
}
}
self
}
#[must_use]
pub fn with_video_codec(self, codec: SCRecordingOutputCodec) -> Self {
unsafe {
crate::ffi::sc_recording_output_configuration_set_video_codec(self.ptr, codec as i32);
}
self
}
pub fn video_codec(&self) -> SCRecordingOutputCodec {
let value =
unsafe { crate::ffi::sc_recording_output_configuration_get_video_codec(self.ptr) };
match value {
1 => SCRecordingOutputCodec::HEVC,
_ => SCRecordingOutputCodec::H264,
}
}
#[must_use]
pub fn with_output_file_type(self, file_type: SCRecordingOutputFileType) -> Self {
unsafe {
crate::ffi::sc_recording_output_configuration_set_output_file_type(
self.ptr,
file_type as i32,
);
}
self
}
pub fn output_file_type(&self) -> SCRecordingOutputFileType {
let value =
unsafe { crate::ffi::sc_recording_output_configuration_get_output_file_type(self.ptr) };
match value {
1 => SCRecordingOutputFileType::MOV,
_ => SCRecordingOutputFileType::MP4,
}
}
pub fn available_video_codecs_count(&self) -> usize {
let count = unsafe {
crate::ffi::sc_recording_output_configuration_get_available_video_codecs_count(self.ptr)
};
#[allow(clippy::cast_sign_loss)]
if count > 0 {
count as usize
} else {
0
}
}
pub fn available_video_codecs(&self) -> Vec<SCRecordingOutputCodec> {
let count = self.available_video_codecs_count();
let mut codecs = Vec::with_capacity(count);
for i in 0..count {
#[allow(clippy::cast_possible_wrap)]
let codec_value = unsafe {
crate::ffi::sc_recording_output_configuration_get_available_video_codec_at(
self.ptr, i as isize,
)
};
match codec_value {
0 => codecs.push(SCRecordingOutputCodec::H264),
1 => codecs.push(SCRecordingOutputCodec::HEVC),
_ => {}
}
}
codecs
}
pub fn available_output_file_types_count(&self) -> usize {
let count = unsafe {
crate::ffi::sc_recording_output_configuration_get_available_output_file_types_count(
self.ptr,
)
};
#[allow(clippy::cast_sign_loss)]
if count > 0 {
count as usize
} else {
0
}
}
pub fn available_output_file_types(&self) -> Vec<SCRecordingOutputFileType> {
let count = self.available_output_file_types_count();
let mut file_types = Vec::with_capacity(count);
for i in 0..count {
#[allow(clippy::cast_possible_wrap)]
let file_type_value = unsafe {
crate::ffi::sc_recording_output_configuration_get_available_output_file_type_at(
self.ptr, i as isize,
)
};
match file_type_value {
0 => file_types.push(SCRecordingOutputFileType::MP4),
1 => file_types.push(SCRecordingOutputFileType::MOV),
_ => {}
}
}
file_types
}
#[must_use]
pub fn as_ptr(&self) -> *const c_void {
self.ptr
}
}
impl Default for SCRecordingOutputConfiguration {
fn default() -> Self {
Self::new()
}
}
impl Clone for SCRecordingOutputConfiguration {
fn clone(&self) -> Self {
unsafe {
Self {
ptr: crate::ffi::sc_recording_output_configuration_retain(self.ptr),
}
}
}
}
impl Drop for SCRecordingOutputConfiguration {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
crate::ffi::sc_recording_output_configuration_release(self.ptr);
}
}
}
}
impl std::fmt::Debug for SCRecordingOutputConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SCRecordingOutputConfiguration")
.field("video_codec", &self.video_codec())
.field("file_type", &self.output_file_type())
.finish()
}
}
pub trait SCRecordingOutputDelegate: Send + 'static {
fn recording_did_start(&self) {}
fn recording_did_fail(&self, _error: String) {}
fn recording_did_finish(&self) {}
}
#[allow(clippy::struct_field_names)]
pub struct RecordingCallbacks {
on_start: Option<Box<dyn Fn() + Send + 'static>>,
on_fail: Option<Box<dyn Fn(String) + Send + 'static>>,
on_finish: Option<Box<dyn Fn() + Send + 'static>>,
}
impl RecordingCallbacks {
#[must_use]
pub fn new() -> Self {
Self {
on_start: None,
on_fail: None,
on_finish: None,
}
}
#[must_use]
pub fn on_start<F>(mut self, f: F) -> Self
where
F: Fn() + Send + 'static,
{
self.on_start = Some(Box::new(f));
self
}
#[must_use]
pub fn on_fail<F>(mut self, f: F) -> Self
where
F: Fn(String) + Send + 'static,
{
self.on_fail = Some(Box::new(f));
self
}
#[must_use]
pub fn on_finish<F>(mut self, f: F) -> Self
where
F: Fn() + Send + 'static,
{
self.on_finish = Some(Box::new(f));
self
}
}
impl Default for RecordingCallbacks {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for RecordingCallbacks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RecordingCallbacks")
.field("on_start", &self.on_start.is_some())
.field("on_fail", &self.on_fail.is_some())
.field("on_finish", &self.on_finish.is_some())
.finish()
}
}
impl SCRecordingOutputDelegate for RecordingCallbacks {
fn recording_did_start(&self) {
if let Some(ref f) = self.on_start {
f();
}
}
fn recording_did_fail(&self, error: String) {
if let Some(ref f) = self.on_fail {
f(error);
}
}
fn recording_did_finish(&self) {
if let Some(ref f) = self.on_finish {
f();
}
}
}
pub struct SCRecordingOutput {
ptr: *const c_void,
delegate_id: Option<usize>,
}
extern "C" fn recording_started_callback(ctx: *mut c_void) {
let key = ctx as usize;
if let Ok(registry) = RECORDING_DELEGATE_REGISTRY.lock() {
if let Some(ref delegates) = *registry {
if let Some(entry) = delegates.get(&key) {
entry.delegate.recording_did_start();
}
}
}
}
extern "C" fn recording_failed_callback(ctx: *mut c_void, error_code: i32, error: *const i8) {
let key = ctx as usize;
let error_str = if error.is_null() {
String::from("Unknown error")
} else {
unsafe { std::ffi::CStr::from_ptr(error) }
.to_string_lossy()
.into_owned()
};
let full_error = if error_code != 0 {
crate::error::SCStreamErrorCode::from_raw(error_code).map_or_else(
|| format!("{error_str} (code: {error_code})"),
|code| format!("{error_str} ({code})"),
)
} else {
error_str
};
if let Ok(registry) = RECORDING_DELEGATE_REGISTRY.lock() {
if let Some(ref delegates) = *registry {
if let Some(entry) = delegates.get(&key) {
entry.delegate.recording_did_fail(full_error);
}
}
}
}
extern "C" fn recording_finished_callback(ctx: *mut c_void) {
let key = ctx as usize;
if let Ok(registry) = RECORDING_DELEGATE_REGISTRY.lock() {
if let Some(ref delegates) = *registry {
if let Some(entry) = delegates.get(&key) {
entry.delegate.recording_did_finish();
}
}
}
}
impl SCRecordingOutput {
pub fn new(config: &SCRecordingOutputConfiguration) -> Option<Self> {
let ptr = unsafe { crate::ffi::sc_recording_output_create(config.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(Self {
ptr,
delegate_id: None,
})
}
}
pub fn new_with_delegate<D: SCRecordingOutputDelegate>(
config: &SCRecordingOutputConfiguration,
delegate: D,
) -> Option<Self> {
let delegate_id = NEXT_DELEGATE_ID.fetch_add(1, Ordering::Relaxed);
{
let mut registry = RECORDING_DELEGATE_REGISTRY.lock().unwrap();
if registry.is_none() {
*registry = Some(HashMap::new());
}
if let Some(ref mut delegates) = *registry {
delegates.insert(
delegate_id,
RecordingDelegateEntry {
delegate: Box::new(delegate),
ref_count: 1,
},
);
}
}
let ctx = delegate_id as *mut c_void;
let ptr = unsafe {
crate::ffi::sc_recording_output_create_with_delegate(
config.as_ptr(),
Some(recording_started_callback),
Some(recording_failed_callback),
Some(recording_finished_callback),
ctx,
)
};
if ptr.is_null() {
if let Ok(mut registry) = RECORDING_DELEGATE_REGISTRY.lock() {
if let Some(ref mut delegates) = *registry {
delegates.remove(&delegate_id);
}
}
None
} else {
Some(Self {
ptr,
delegate_id: Some(delegate_id),
})
}
}
pub fn recorded_duration(&self) -> CMTime {
let mut value: i64 = 0;
let mut timescale: i32 = 0;
unsafe {
crate::ffi::sc_recording_output_get_recorded_duration(
self.ptr,
&mut value,
&mut timescale,
);
}
CMTime {
value,
timescale,
flags: 0,
epoch: 0,
}
}
pub fn recorded_file_size(&self) -> i64 {
unsafe { crate::ffi::sc_recording_output_get_recorded_file_size(self.ptr) }
}
#[must_use]
pub fn as_ptr(&self) -> *const c_void {
self.ptr
}
}
impl Clone for SCRecordingOutput {
fn clone(&self) -> Self {
if let Some(delegate_id) = self.delegate_id {
if let Ok(mut registry) = RECORDING_DELEGATE_REGISTRY.lock() {
if let Some(ref mut delegates) = *registry {
if let Some(entry) = delegates.get_mut(&delegate_id) {
entry.ref_count += 1;
}
}
}
}
unsafe {
Self {
ptr: crate::ffi::sc_recording_output_retain(self.ptr),
delegate_id: self.delegate_id,
}
}
}
}
impl std::fmt::Debug for SCRecordingOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SCRecordingOutput")
.field("recorded_duration", &self.recorded_duration())
.field("recorded_file_size", &self.recorded_file_size())
.field("has_delegate", &self.delegate_id.is_some())
.finish_non_exhaustive()
}
}
impl Drop for SCRecordingOutput {
fn drop(&mut self) {
if let Some(delegate_id) = self.delegate_id {
let mut should_remove = false;
if let Ok(mut registry) = RECORDING_DELEGATE_REGISTRY.lock() {
if let Some(ref mut delegates) = *registry {
if let Some(entry) = delegates.get_mut(&delegate_id) {
entry.ref_count -= 1;
if entry.ref_count == 0 {
should_remove = true;
}
}
if should_remove {
delegates.remove(&delegate_id);
}
}
}
}
if !self.ptr.is_null() {
unsafe {
crate::ffi::sc_recording_output_release(self.ptr);
}
}
}
}
unsafe impl Send for SCRecordingOutput {}
unsafe impl Sync for SCRecordingOutput {}
unsafe impl Send for SCRecordingOutputConfiguration {}
unsafe impl Sync for SCRecordingOutputConfiguration {}