screencapturekit/cm/
time.rs

1//! Core Media time types
2
3use std::ffi::c_void;
4use std::fmt;
5
6/// `CMTime` representation matching Core Media's `CMTime`
7///
8/// Represents a rational time value with a 64-bit numerator and 32-bit denominator.
9///
10/// # Examples
11///
12/// ```
13/// use screencapturekit::cm::CMTime;
14///
15/// // Create a time of 1 second (30/30)
16/// let time = CMTime::new(30, 30);
17/// assert_eq!(time.as_seconds(), Some(1.0));
18///
19/// // Create a time of 2.5 seconds at 1000 Hz timescale
20/// let time = CMTime::new(2500, 1000);
21/// assert_eq!(time.value, 2500);
22/// assert_eq!(time.timescale, 1000);
23/// assert_eq!(time.as_seconds(), Some(2.5));
24/// ```
25#[repr(C)]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct CMTime {
28    pub value: i64,
29    pub timescale: i32,
30    pub flags: u32,
31    pub epoch: i64,
32}
33
34impl std::hash::Hash for CMTime {
35    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
36        self.value.hash(state);
37        self.timescale.hash(state);
38        self.flags.hash(state);
39        self.epoch.hash(state);
40    }
41}
42
43/// Sample timing information
44///
45/// Contains timing data for a media sample (audio or video frame).
46///
47/// # Examples
48///
49/// ```
50/// use screencapturekit::cm::{CMSampleTimingInfo, CMTime};
51///
52/// let timing = CMSampleTimingInfo::new();
53/// assert!(!timing.is_valid());
54///
55/// let duration = CMTime::new(1, 30);
56/// let pts = CMTime::new(100, 30);
57/// let dts = CMTime::new(100, 30);
58/// let timing = CMSampleTimingInfo::with_times(duration, pts, dts);
59/// assert!(timing.is_valid());
60/// ```
61#[repr(C)]
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct CMSampleTimingInfo {
64    pub duration: CMTime,
65    pub presentation_time_stamp: CMTime,
66    pub decode_time_stamp: CMTime,
67}
68
69impl std::hash::Hash for CMSampleTimingInfo {
70    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
71        self.duration.hash(state);
72        self.presentation_time_stamp.hash(state);
73        self.decode_time_stamp.hash(state);
74    }
75}
76
77impl CMSampleTimingInfo {
78    /// Create a new timing info with all times set to invalid
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use screencapturekit::cm::CMSampleTimingInfo;
84    ///
85    /// let timing = CMSampleTimingInfo::new();
86    /// assert!(!timing.is_valid());
87    /// ```
88    pub const fn new() -> Self {
89        Self {
90            duration: CMTime::INVALID,
91            presentation_time_stamp: CMTime::INVALID,
92            decode_time_stamp: CMTime::INVALID,
93        }
94    }
95
96    /// Create timing info with specific values
97    pub const fn with_times(
98        duration: CMTime,
99        presentation_time_stamp: CMTime,
100        decode_time_stamp: CMTime,
101    ) -> Self {
102        Self {
103            duration,
104            presentation_time_stamp,
105            decode_time_stamp,
106        }
107    }
108
109    /// Check if all timing fields are valid
110    pub const fn is_valid(&self) -> bool {
111        self.duration.is_valid()
112            && self.presentation_time_stamp.is_valid()
113            && self.decode_time_stamp.is_valid()
114    }
115
116    /// Check if presentation timestamp is valid
117    pub const fn has_valid_presentation_time(&self) -> bool {
118        self.presentation_time_stamp.is_valid()
119    }
120
121    /// Check if decode timestamp is valid
122    pub const fn has_valid_decode_time(&self) -> bool {
123        self.decode_time_stamp.is_valid()
124    }
125
126    /// Check if duration is valid
127    pub const fn has_valid_duration(&self) -> bool {
128        self.duration.is_valid()
129    }
130
131    /// Get the presentation timestamp in seconds
132    pub fn presentation_seconds(&self) -> Option<f64> {
133        self.presentation_time_stamp.as_seconds()
134    }
135
136    /// Get the decode timestamp in seconds
137    pub fn decode_seconds(&self) -> Option<f64> {
138        self.decode_time_stamp.as_seconds()
139    }
140
141    /// Get the duration in seconds
142    pub fn duration_seconds(&self) -> Option<f64> {
143        self.duration.as_seconds()
144    }
145}
146
147impl Default for CMSampleTimingInfo {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153impl fmt::Display for CMSampleTimingInfo {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        write!(
156            f,
157            "CMSampleTimingInfo(pts: {}, dts: {}, duration: {})",
158            self.presentation_time_stamp, self.decode_time_stamp, self.duration
159        )
160    }
161}
162
163impl CMTime {
164    pub const ZERO: Self = Self {
165        value: 0,
166        timescale: 0,
167        flags: 1,
168        epoch: 0,
169    };
170
171    pub const INVALID: Self = Self {
172        value: 0,
173        timescale: 0,
174        flags: 0,
175        epoch: 0,
176    };
177
178    pub const fn new(value: i64, timescale: i32) -> Self {
179        Self {
180            value,
181            timescale,
182            flags: 1,
183            epoch: 0,
184        }
185    }
186
187    pub const fn is_valid(&self) -> bool {
188        self.flags & 0x1 != 0
189    }
190
191    /// Check if this time represents zero
192    pub const fn is_zero(&self) -> bool {
193        self.value == 0 && self.is_valid()
194    }
195
196    /// Check if this time is indefinite
197    pub const fn is_indefinite(&self) -> bool {
198        self.flags & 0x2 != 0
199    }
200
201    /// Check if this time is positive infinity
202    pub const fn is_positive_infinity(&self) -> bool {
203        self.flags & 0x4 != 0
204    }
205
206    /// Check if this time is negative infinity
207    pub const fn is_negative_infinity(&self) -> bool {
208        self.flags & 0x8 != 0
209    }
210
211    /// Check if this time has been rounded
212    pub const fn has_been_rounded(&self) -> bool {
213        self.flags & 0x10 != 0
214    }
215
216    /// Compare two times for equality (value and timescale)
217    pub const fn equals(&self, other: &Self) -> bool {
218        if !self.is_valid() || !other.is_valid() {
219            return false;
220        }
221        self.value == other.value && self.timescale == other.timescale
222    }
223
224    /// Create a time representing positive infinity
225    pub const fn positive_infinity() -> Self {
226        Self {
227            value: 0,
228            timescale: 0,
229            flags: 0x5, // kCMTimeFlags_Valid | kCMTimeFlags_PositiveInfinity
230            epoch: 0,
231        }
232    }
233
234    /// Create a time representing negative infinity
235    pub const fn negative_infinity() -> Self {
236        Self {
237            value: 0,
238            timescale: 0,
239            flags: 0x9, // kCMTimeFlags_Valid | kCMTimeFlags_NegativeInfinity
240            epoch: 0,
241        }
242    }
243
244    /// Create an indefinite time
245    pub const fn indefinite() -> Self {
246        Self {
247            value: 0,
248            timescale: 0,
249            flags: 0x3, // kCMTimeFlags_Valid | kCMTimeFlags_Indefinite
250            epoch: 0,
251        }
252    }
253
254    pub fn as_seconds(&self) -> Option<f64> {
255        if self.is_valid() && self.timescale != 0 {
256            // Precision loss is acceptable for time conversion to seconds
257            #[allow(clippy::cast_precision_loss)]
258            Some(self.value as f64 / f64::from(self.timescale))
259        } else {
260            None
261        }
262    }
263}
264
265impl Default for CMTime {
266    fn default() -> Self {
267        Self::INVALID
268    }
269}
270
271impl fmt::Display for CMTime {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        if let Some(seconds) = self.as_seconds() {
274            write!(f, "{seconds:.3}s")
275        } else {
276            write!(f, "invalid")
277        }
278    }
279}
280
281/// `CMClock` wrapper for synchronization clock
282///
283/// Represents a Core Media clock used for time synchronization.
284/// Available on macOS 13.0+.
285pub struct CMClock {
286    ptr: *const c_void,
287}
288
289impl PartialEq for CMClock {
290    fn eq(&self, other: &Self) -> bool {
291        self.ptr == other.ptr
292    }
293}
294
295impl Eq for CMClock {}
296
297impl std::hash::Hash for CMClock {
298    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
299        self.ptr.hash(state);
300    }
301}
302
303impl CMClock {
304    /// Create from raw pointer, returning None if null
305    pub fn from_raw(ptr: *const c_void) -> Option<Self> {
306        if ptr.is_null() {
307            None
308        } else {
309            Some(Self { ptr })
310        }
311    }
312
313    /// Create from raw pointer (used internally)
314    ///
315    /// # Safety
316    /// The caller must ensure the pointer is a valid, retained `CMClock` pointer.
317    #[allow(dead_code)]
318    pub(crate) fn from_ptr(ptr: *const c_void) -> Self {
319        Self { ptr }
320    }
321
322    /// Returns the raw pointer to the underlying `CMClock`
323    pub fn as_ptr(&self) -> *const c_void {
324        self.ptr
325    }
326
327    /// Get the current time from this clock
328    ///
329    /// Note: Returns invalid time. Use `as_ptr()` with Core Media APIs directly
330    /// for full clock functionality.
331    pub fn time(&self) -> CMTime {
332        // This would require FFI to CMClockGetTime - for now return invalid
333        // Users can use the pointer directly with Core Media APIs
334        CMTime::INVALID
335    }
336}
337
338impl Drop for CMClock {
339    fn drop(&mut self) {
340        if !self.ptr.is_null() {
341            // CMClock is a CFType, needs CFRelease
342            extern "C" {
343                fn CFRelease(cf: *const c_void);
344            }
345            unsafe {
346                CFRelease(self.ptr);
347            }
348        }
349    }
350}
351
352impl Clone for CMClock {
353    fn clone(&self) -> Self {
354        if self.ptr.is_null() {
355            Self {
356                ptr: std::ptr::null(),
357            }
358        } else {
359            extern "C" {
360                fn CFRetain(cf: *const c_void) -> *const c_void;
361            }
362            unsafe {
363                Self {
364                    ptr: CFRetain(self.ptr),
365                }
366            }
367        }
368    }
369}
370
371impl std::fmt::Debug for CMClock {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        f.debug_struct("CMClock").field("ptr", &self.ptr).finish()
374    }
375}
376
377impl fmt::Display for CMClock {
378    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379        if self.ptr.is_null() {
380            write!(f, "CMClock(null)")
381        } else {
382            write!(f, "CMClock({:p})", self.ptr)
383        }
384    }
385}
386
387// Safety: CMClock is a CoreFoundation type that is thread-safe
388unsafe impl Send for CMClock {}
389unsafe impl Sync for CMClock {}