screencapturekit/cm/
time.rs

1//! Core Media time types
2
3use std::fmt;
4
5/// `CMTime` representation matching Core Media's `CMTime`
6///
7/// Represents a rational time value with a 64-bit numerator and 32-bit denominator.
8///
9/// # Examples
10///
11/// ```
12/// use screencapturekit::cm::CMTime;
13///
14/// // Create a time of 1 second (30/30)
15/// let time = CMTime::new(30, 30);
16/// assert_eq!(time.as_seconds(), Some(1.0));
17///
18/// // Create a time of 2.5 seconds at 1000 Hz timescale
19/// let time = CMTime::new(2500, 1000);
20/// assert_eq!(time.value, 2500);
21/// assert_eq!(time.timescale, 1000);
22/// assert_eq!(time.as_seconds(), Some(2.5));
23/// ```
24#[repr(C)]
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct CMTime {
27    pub value: i64,
28    pub timescale: i32,
29    pub flags: u32,
30    pub epoch: i64,
31}
32
33impl std::hash::Hash for CMTime {
34    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
35        self.value.hash(state);
36        self.timescale.hash(state);
37        self.flags.hash(state);
38        self.epoch.hash(state);
39    }
40}
41
42/// Sample timing information
43///
44/// Contains timing data for a media sample (audio or video frame).
45///
46/// # Examples
47///
48/// ```
49/// use screencapturekit::cm::{CMSampleTimingInfo, CMTime};
50///
51/// let timing = CMSampleTimingInfo::new();
52/// assert!(!timing.is_valid());
53///
54/// let duration = CMTime::new(1, 30);
55/// let pts = CMTime::new(100, 30);
56/// let dts = CMTime::new(100, 30);
57/// let timing = CMSampleTimingInfo::with_times(duration, pts, dts);
58/// assert!(timing.is_valid());
59/// ```
60#[repr(C)]
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct CMSampleTimingInfo {
63    pub duration: CMTime,
64    pub presentation_time_stamp: CMTime,
65    pub decode_time_stamp: CMTime,
66}
67
68impl std::hash::Hash for CMSampleTimingInfo {
69    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
70        self.duration.hash(state);
71        self.presentation_time_stamp.hash(state);
72        self.decode_time_stamp.hash(state);
73    }
74}
75
76impl CMSampleTimingInfo {
77    /// Create a new timing info with all times set to invalid
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use screencapturekit::cm::CMSampleTimingInfo;
83    ///
84    /// let timing = CMSampleTimingInfo::new();
85    /// assert!(!timing.is_valid());
86    /// ```
87    pub const fn new() -> Self {
88        Self {
89            duration: CMTime::INVALID,
90            presentation_time_stamp: CMTime::INVALID,
91            decode_time_stamp: CMTime::INVALID,
92        }
93    }
94
95    /// Create timing info with specific values
96    pub const fn with_times(
97        duration: CMTime,
98        presentation_time_stamp: CMTime,
99        decode_time_stamp: CMTime,
100    ) -> Self {
101        Self {
102            duration,
103            presentation_time_stamp,
104            decode_time_stamp,
105        }
106    }
107
108    /// Check if all timing fields are valid
109    pub const fn is_valid(&self) -> bool {
110        self.duration.is_valid()
111            && self.presentation_time_stamp.is_valid()
112            && self.decode_time_stamp.is_valid()
113    }
114
115    /// Check if presentation timestamp is valid
116    pub const fn has_valid_presentation_time(&self) -> bool {
117        self.presentation_time_stamp.is_valid()
118    }
119
120    /// Check if decode timestamp is valid
121    pub const fn has_valid_decode_time(&self) -> bool {
122        self.decode_time_stamp.is_valid()
123    }
124
125    /// Check if duration is valid
126    pub const fn has_valid_duration(&self) -> bool {
127        self.duration.is_valid()
128    }
129
130    /// Get the presentation timestamp in seconds
131    pub fn presentation_seconds(&self) -> Option<f64> {
132        self.presentation_time_stamp.as_seconds()
133    }
134
135    /// Get the decode timestamp in seconds
136    pub fn decode_seconds(&self) -> Option<f64> {
137        self.decode_time_stamp.as_seconds()
138    }
139
140    /// Get the duration in seconds
141    pub fn duration_seconds(&self) -> Option<f64> {
142        self.duration.as_seconds()
143    }
144}
145
146impl Default for CMSampleTimingInfo {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152impl fmt::Display for CMSampleTimingInfo {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        write!(
155            f,
156            "CMSampleTimingInfo(pts: {}, dts: {}, duration: {})",
157            self.presentation_time_stamp, self.decode_time_stamp, self.duration
158        )
159    }
160}
161
162impl CMTime {
163    pub const ZERO: Self = Self {
164        value: 0,
165        timescale: 0,
166        flags: 1,
167        epoch: 0,
168    };
169
170    pub const INVALID: Self = Self {
171        value: 0,
172        timescale: 0,
173        flags: 0,
174        epoch: 0,
175    };
176
177    pub const fn new(value: i64, timescale: i32) -> Self {
178        Self {
179            value,
180            timescale,
181            flags: 1,
182            epoch: 0,
183        }
184    }
185
186    pub const fn is_valid(&self) -> bool {
187        self.flags & 0x1 != 0
188    }
189
190    /// Check if this time represents zero
191    pub const fn is_zero(&self) -> bool {
192        self.value == 0 && self.is_valid()
193    }
194
195    /// Check if this time is indefinite
196    pub const fn is_indefinite(&self) -> bool {
197        self.flags & 0x2 != 0
198    }
199
200    /// Check if this time is positive infinity
201    pub const fn is_positive_infinity(&self) -> bool {
202        self.flags & 0x4 != 0
203    }
204
205    /// Check if this time is negative infinity
206    pub const fn is_negative_infinity(&self) -> bool {
207        self.flags & 0x8 != 0
208    }
209
210    /// Check if this time has been rounded
211    pub const fn has_been_rounded(&self) -> bool {
212        self.flags & 0x10 != 0
213    }
214
215    /// Compare two times for equality (value and timescale)
216    pub const fn equals(&self, other: &Self) -> bool {
217        if !self.is_valid() || !other.is_valid() {
218            return false;
219        }
220        self.value == other.value && self.timescale == other.timescale
221    }
222
223    /// Create a time representing positive infinity
224    pub const fn positive_infinity() -> Self {
225        Self {
226            value: 0,
227            timescale: 0,
228            flags: 0x5, // kCMTimeFlags_Valid | kCMTimeFlags_PositiveInfinity
229            epoch: 0,
230        }
231    }
232
233    /// Create a time representing negative infinity
234    pub const fn negative_infinity() -> Self {
235        Self {
236            value: 0,
237            timescale: 0,
238            flags: 0x9, // kCMTimeFlags_Valid | kCMTimeFlags_NegativeInfinity
239            epoch: 0,
240        }
241    }
242
243    /// Create an indefinite time
244    pub const fn indefinite() -> Self {
245        Self {
246            value: 0,
247            timescale: 0,
248            flags: 0x3, // kCMTimeFlags_Valid | kCMTimeFlags_Indefinite
249            epoch: 0,
250        }
251    }
252
253    pub fn as_seconds(&self) -> Option<f64> {
254        if self.is_valid() && self.timescale != 0 {
255            // Precision loss is acceptable for time conversion to seconds
256            #[allow(clippy::cast_precision_loss)]
257            Some(self.value as f64 / f64::from(self.timescale))
258        } else {
259            None
260        }
261    }
262}
263
264impl Default for CMTime {
265    fn default() -> Self {
266        Self::INVALID
267    }
268}
269
270impl fmt::Display for CMTime {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        if let Some(seconds) = self.as_seconds() {
273            write!(f, "{seconds:.3}s")
274        } else {
275            write!(f, "invalid")
276        }
277    }
278}