inset_embedder/media.rs
1//! Players and cameras: the system's own media stack, as a host hands it to the app. Not a
2//! dart:ui file: Flutter reaches media through its `video_player` and `camera` plugins, whose
3//! controllers come from their method channels. These follow the systems' own model instead,
4//! which they share: a player is one object made from a URL, loading by itself, which the app
5//! controls and whose state it observes (`AVPlayer`, Media3's `ExoPlayer`, Windows'
6//! `MediaPlayer`, the web's `<video>`); a camera is a session on one device with outputs, each for
7//! one use (`AVCaptureSession` with its preview layer and its photo, video data and movie file
8//! outputs, CameraX's use cases).
9//!
10//! Every system has two levels: a view that takes a player (`AVPlayerLayer(player:)`, Media3's
11//! `PlayerView.player`), which is the framework's, and a player that puts its pictures where it
12//! is given (Media3's `setVideoSurface`), which is the host's and this module's. A player's
13//! picture goes to one destination at a time, which the app makes and gives it
14//! ([`MediaPlayer::set_video_output`]): a [`CompositionLayer`], whose content becomes a layer the
15//! system draws the picture into, which is the only way protected and HDR video show at all, with
16//! no copy; or a [`VideoFrameReader`], made on its own ([`VideoFrameReaders::make_reader`]), which
17//! hands the frames to the app's GPU, as an Android app makes an `ImageReader` and an Apple app an
18//! `AVPlayerItemVideoOutput` and gives it to its player. A camera, once opened, lists the formats
19//! it runs in, and starts in one of them with its outputs given whole ([`CameraOutputs`]): a layer
20//! for its preview and a reader for its frames, made the same way, beside its photos and
21//! recordings, all of which the system configures together, as Apple configures an
22//! `AVCaptureSession` between `beginConfiguration` and `commitConfiguration` and Android binds
23//! CameraX's use cases at once. A camera's photos are encoded by the system and its recordings
24//! encoded and written to a file by the system: the app encodes nothing.
25//!
26//! Listing the cameras, opening one and starting it answer futures, as systems that answer them
27//! later do: the web's `enumerateDevices` and `getUserMedia` answer promises, and Apple's
28//! documentation of `AVCaptureSession` has a session started on a serial queue of its own, since
29//! `startRunning` blocks until the session runs or fails.
30//!
31//! A player tells its media's tracks, plays one audio track and shows one text track or none, as
32//! every system's player does (Apple's media selection groups, Media3's track overrides); it tells
33//! what the media's tags say ([`MediaMetadata`]); and it hands the app the [`Cue`]s of its text
34//! track to draw, the system drawing none, as most players draw their own (Media3's
35//! `SubtitleView`, Safari over AVFoundation's legible output). A cue carries the media's own
36//! styling, and the user's caption style is a system setting beside it, which the platform's
37//! caption settings answer ([`Platform::caption_settings`](crate::Platform::caption_settings))
38//! and the app merges in as it draws, as Media3's `SubtitleView` and WebKit's cue style sheet do.
39//!
40//! A player's state tells what the system's player does, not what the app asked of it: whether
41//! it is paused, waiting or playing, and why, as Apple's `timeControlStatus` and Windows'
42//! `PlaybackState` tell it, since the system pauses a player itself (a call takes the sound,
43//! headphones are unplugged); a seek under way; what is loaded and where it can seek; the rate,
44//! volume and mute it plays with, which the system can change; and a failure with its kind, at
45//! any time. A player's position is read when the app needs it ([`MediaPlayer::position`]), as
46//! every system lets an app read it (AVPlayer's `currentTime()`, Media3's
47//! `getCurrentPosition()`, Windows' `PlaybackSession.Position`, the web's `currentTime`), and told
48//! only when it jumps (a seek landing, a loop), as the systems tell it (Apple's
49//! `AVPlayerItemTimeJumped`, Media3's `onPositionDiscontinuity`, the web's `seeked`): while it
50//! plays it moves on by itself, and an app that shows it reads it at each frame it draws.
51//!
52//! The platform answers `None` where its system has no players, no cameras or no frames. A player
53//! or a session reports its state through [`MediaPlayer::state`] and [`CaptureSession::state`],
54//! and the cameras their list through [`Cameras::cameras`], each read when the future from its
55//! `changed` resolves: the invariant a host keeps is that such a future resolves at every change
56//! of what it reads, a player's metadata and cues included, at every jump of a player's position,
57//! and when the player or session goes, so the app never waits on one for ever.
58
59use std::any::Any;
60use std::fmt;
61use std::future::Future;
62use std::path::PathBuf;
63use std::pin::Pin;
64use std::sync::Arc;
65use std::time::{Duration, Instant};
66
67use crate::{CompositionLayer, Cue, Size};
68
69/// The system's players, or `None` from [`Platform::players`](crate::Platform::players) where it
70/// has none.
71pub trait Players {
72 /// A player of the media at `url`, which loads by itself, its state saying how far it got: a
73 /// link the system fetches (`https:`, a streaming playlist), or a picked file's
74 /// [`PickedFile::url`](crate::PickedFile::url), as every system's player takes a URL. A URL
75 /// the system cannot play gives a player that failed, with the system's reason. A player of
76 /// bytes the app makes itself is for later.
77 fn open(&self, url: &str) -> Box<dyn MediaPlayer>;
78}
79
80/// One player, controlled on the app's main thread. Dropped, it stops, and its picture goes
81/// nowhere: it leaves the layer it showed on, and its reader reads nothing more.
82pub trait MediaPlayer {
83 /// Plays from where it is, at the rate last set: from the start once it has ended.
84 fn play(&self);
85
86 fn pause(&self);
87
88 /// Moves to `position`, or as near it as `tolerance` lets the system land for speed,
89 /// playing on from there if it was playing. The state tells the seek under way until it
90 /// lands; a seek outside the seekable ranges moves nothing.
91 fn seek(&self, position: Duration, tolerance: SeekTolerance);
92
93 /// How fast it plays, `1.0` the media's own speed; kept while paused, for the next play.
94 fn set_rate(&self, rate: f64);
95
96 /// How loud it plays, from `0.0`, silent, to `1.0`, the media's own volume.
97 fn set_volume(&self, volume: f64);
98
99 /// Whether its sound is off, apart from its volume.
100 fn set_muted(&self, muted: bool);
101
102 /// Whether it starts again from the beginning when it reaches the end, rather than ending.
103 fn set_looping(&self, looping: bool);
104
105 /// The player's state now.
106 fn state(&self) -> PlayerState;
107
108 /// Where it is in the media now, read from the system at each call; it moves on by itself
109 /// while the player plays, and is the target while a seek is under way, as the web's and
110 /// Media3's are.
111 fn position(&self) -> Duration;
112
113 /// Resolves once [`state`](Self::state), [`metadata`](Self::metadata) or
114 /// [`cues`](Self::cues) may answer something else than when this was called, once the
115 /// [`position`](Self::position) jumped, as a seek landing or a loop makes it, or once the
116 /// player is gone; not as the position moves on while the player plays.
117 fn changed(&self) -> MediaChangeFuture;
118
119 /// Sends the picture to `output` from now on, and nowhere it went before; `None` sends it
120 /// nowhere, as Media3's `setVideoSurface` does. The player holds the output while it sends
121 /// the picture there, as a producer holds its layer. A layer no compositor of this host made
122 /// shows nothing, and a reader no [`VideoFrameReaders`] of this host made reads nothing.
123 fn set_video_output(&self, output: Option<VideoOutput>);
124
125 /// Plays the audio track `id`, one of the state's [`tracks`](PlayerState::tracks), in place
126 /// of the one playing.
127 fn select_audio_track(&self, id: &str);
128
129 /// Shows the text track `id`, one of the state's [`tracks`](PlayerState::tracks), or none:
130 /// subtitles off. Its cues are what [`cues`](Self::cues) answers.
131 fn select_text_track(&self, id: Option<&str>);
132
133 /// What the media's tags say about it; empty until the player has read them.
134 fn metadata(&self) -> MediaMetadata;
135
136 /// The cues of the text track shown, to show now, as the media styles them: the user's
137 /// caption style is not merged in. Empty while no text track is shown.
138 fn cues(&self) -> Vec<Cue>;
139}
140
141/// Where a player sends its picture, one destination at a time.
142#[derive(Clone)]
143pub enum VideoOutput {
144 /// The system draws the picture into a layer of its own that is this layer's content.
145 Layer(Arc<dyn CompositionLayer>),
146 /// The app draws the frames this reader hands out.
147 Frames(Arc<dyn VideoFrameReader>),
148}
149
150/// What a player's media is, and what the system's player does with it.
151#[derive(Clone, Debug, PartialEq)]
152pub struct PlayerState {
153 pub status: PlayerStatus,
154 /// Whether it is paused, waiting or playing, as the system reports it.
155 pub activity: PlayerActivity,
156 /// Where a seek is taking it, until it lands; `None` while no seek is under way. A later
157 /// seek replaces the target.
158 pub seeking: Option<Duration>,
159 /// The rate it plays at while it plays, `1.0` the media's own speed, kept while paused for
160 /// the next play; as the system reports it, which can differ from the rate asked for.
161 pub rate: f64,
162 /// How loud it plays, from `0.0`, silent, to `1.0`, the media's own volume.
163 pub volume: f64,
164 /// Whether its sound is off, apart from its volume.
165 pub muted: bool,
166 /// How long the media is; `None` until it is known, and for a live stream.
167 pub duration: Option<Duration>,
168 /// The parts of the media loaded now, in order.
169 pub loaded: Vec<TimeRange>,
170 /// The parts it can seek into, in order: the whole media for a file, a moving window for a
171 /// live stream, none where it cannot seek.
172 pub seekable: Vec<TimeRange>,
173 /// The picture's size, in its own pixels, as it is presented: its aspect corrected; `None`
174 /// until it is known, and for media without a picture.
175 pub size: Option<Size>,
176 /// The media's tracks; empty until it is ready.
177 pub tracks: Vec<MediaTrack>,
178}
179
180impl PlayerState {
181 /// A player that is loading, paused as the app has not played it: nothing known yet.
182 pub fn loading() -> PlayerState {
183 PlayerState {
184 status: PlayerStatus::Loading,
185 activity: PlayerActivity::Paused(Some(PauseReason::Requested)),
186 seeking: None,
187 rate: 1.0,
188 volume: 1.0,
189 muted: false,
190 duration: None,
191 loaded: Vec::new(),
192 seekable: Vec::new(),
193 size: None,
194 tracks: Vec::new(),
195 }
196 }
197
198 /// A player that failed with `error`, and does nothing more.
199 pub fn failed(error: PlayerError) -> PlayerState {
200 PlayerState {
201 status: PlayerStatus::Failed(error),
202 activity: PlayerActivity::Paused(None),
203 ..PlayerState::loading()
204 }
205 }
206}
207
208/// How far a player got with its media.
209#[derive(Clone, Debug, PartialEq, Eq)]
210pub enum PlayerStatus {
211 Loading,
212 /// It can play.
213 Ready,
214 /// It cannot play, from the start or partway through, and does nothing more: Apple's failed
215 /// item and its failure to play to the end, Media3's player error, the web's `error`. It is
216 /// opened again to try again.
217 Failed(PlayerError),
218}
219
220/// What a player is doing: Apple's `timeControlStatus`, Windows' `PlaybackState`.
221#[derive(Clone, Copy, Debug, PartialEq, Eq)]
222pub enum PlayerActivity {
223 /// Not playing, and not trying to; the reason is `None` where the system does not say.
224 Paused(Option<PauseReason>),
225 /// Trying to play, and waiting; the reason is `None` where the system does not say.
226 Waiting(Option<WaitReason>),
227 Playing,
228}
229
230/// Why a player is paused.
231#[derive(Clone, Copy, Debug, PartialEq, Eq)]
232pub enum PauseReason {
233 /// The app paused it, or has not played it yet.
234 Requested,
235 /// It reached the end of media it does not loop: Apple's played-to-end, Media3's end of the
236 /// media item, the web's `ended`.
237 End,
238 /// The system took the sound for something else, a call or another app: Apple's audio
239 /// session interruption, Android's audio focus loss.
240 Interrupted,
241 /// The sound's output went, such as headphones unplugged: Android's audio becoming noisy.
242 OutputRemoved,
243 /// The app went to the background, where the system pauses its video: Apple's app
244 /// backgrounded.
245 Backgrounded,
246}
247
248/// Why a player waits to play.
249#[derive(Clone, Copy, Debug, PartialEq, Eq)]
250pub enum WaitReason {
251 /// For media data: Apple's waiting to minimize stalls, Media3's buffering, the web's
252 /// `waiting`, Windows' buffering.
253 Data,
254 /// The system judges whether data comes fast enough to play on, and Apple advises showing
255 /// no waiting yet: Apple's waiting while evaluating the buffering rate.
256 EvaluatingData,
257 /// The system holds it back for a moment: Android's playback suppressed by a short loss of
258 /// audio focus.
259 HeldBySystem,
260}
261
262/// Why a player failed.
263#[derive(Clone, Debug, PartialEq, Eq)]
264pub struct PlayerError {
265 pub kind: PlayerErrorKind,
266 /// The system's own words, for logs: not for showing as they are.
267 pub message: String,
268}
269
270/// The kind of a player's failure, which an app words for its user and acts on: the web's
271/// `MediaError` and Windows' `MediaPlayerError` share the first three, and Apple's `AVError` and
272/// Media3's error codes fall into them.
273#[derive(Clone, Copy, Debug, PartialEq, Eq)]
274pub enum PlayerErrorKind {
275 /// Reaching the media failed: a connection, a timeout, an HTTP error, a file that is not
276 /// there.
277 Network,
278 /// The media is damaged, or decoding it failed.
279 Decode,
280 /// A format or codec the system cannot play.
281 Unsupported,
282 /// The media is protected, and the system may not play it here.
283 Protected,
284 /// The system or the host does not let the player reach the source: a sandbox's rules, a
285 /// file it may not read, a link it may not fetch.
286 Refused,
287 /// Anything else the system reports.
288 Other,
289}
290
291/// A span of a player's media, from `start` to `end`.
292#[derive(Clone, Copy, Debug, PartialEq, Eq)]
293pub struct TimeRange {
294 pub start: Duration,
295 pub end: Duration,
296}
297
298/// How far from its target a seek may land, before and after it, for the system to land
299/// sooner: Apple's tolerances, Media3's `SeekParameters`. A system that seeks only exactly keeps
300/// any tolerance.
301#[derive(Clone, Copy, Debug, PartialEq, Eq)]
302pub struct SeekTolerance {
303 pub before: Duration,
304 pub after: Duration,
305}
306
307impl SeekTolerance {
308 /// Exactly at the target, which may take longer to decode to.
309 pub const EXACT: SeekTolerance = SeekTolerance {
310 before: Duration::ZERO,
311 after: Duration::ZERO,
312 };
313
314 /// Wherever the system lands soonest, such as the nearest keyframe.
315 pub const ANY: SeekTolerance = SeekTolerance {
316 before: Duration::MAX,
317 after: Duration::MAX,
318 };
319}
320
321/// One of a player's media's tracks.
322#[derive(Clone, Debug, PartialEq, Eq)]
323pub struct MediaTrack {
324 /// Names the track for this player's run.
325 pub id: String,
326 pub kind: MediaTrackKind,
327 /// A BCP 47 language tag; empty where the media gives none.
328 pub language: String,
329 /// Its name as the media gives it; empty where it gives none.
330 pub label: String,
331 /// Whether it plays now: one audio track at a time, and one text track or none.
332 pub selected: bool,
333}
334
335/// What a track carries.
336#[derive(Clone, Copy, Debug, PartialEq, Eq)]
337pub enum MediaTrackKind {
338 Audio,
339 Video,
340 /// Subtitles or captions.
341 Text,
342}
343
344/// What a player's media says about itself, from its tags; a field is `None` where it does not
345/// say.
346#[derive(Clone, Debug, Default, PartialEq, Eq)]
347pub struct MediaMetadata {
348 pub title: Option<String>,
349 pub artist: Option<String>,
350 pub album: Option<String>,
351 /// The cover image, encoded as the media holds it.
352 pub artwork: Option<EncodedImage>,
353}
354
355/// An image as its file format encodes it.
356#[derive(Clone, Debug, PartialEq, Eq)]
357pub struct EncodedImage {
358 /// Its format's MIME type, such as `image/jpeg`.
359 pub mime_type: String,
360 /// Shared, so that a player hands the same image out again without copying it.
361 pub data: Arc<[u8]>,
362}
363
364/// The system's cameras, or `None` from [`Platform::cameras`](crate::Platform::cameras) where it
365/// has none.
366pub trait Cameras {
367 /// The cameras attached now, in the system's order. Listing asks the user nothing.
368 fn cameras(&self) -> CameraListFuture;
369
370 /// Resolves once [`cameras`](Self::cameras) may answer something else than when this was
371 /// called: a camera plugged in or out, as Apple's discovery session, Android's
372 /// `CameraManager.AvailabilityCallback`, the web's `devicechange` event and Windows'
373 /// `DeviceWatcher` tell it.
374 fn changed(&self) -> MediaChangeFuture;
375
376 /// Opens `camera`, asking the user for the cameras first where the system asks and the user
377 /// has not answered, as an Android app opens Camera2's `CameraDevice` once it holds the
378 /// camera permission: [`CameraError::NotAllowed`] where the user or the system's policy does
379 /// not let the app use them, and [`CameraError::Unplugged`] for a camera no longer attached.
380 fn open(&self, camera: &Camera) -> CameraOpenFuture;
381}
382
383/// A camera, as the system lists it.
384#[derive(Clone, Debug, PartialEq, Eq)]
385pub struct Camera {
386 pub id: CameraId,
387 /// What the system calls it, which the user knows it by.
388 pub name: String,
389 pub facing: CameraFacing,
390}
391
392/// The system's name for a camera, kept while it stays attached.
393#[derive(Clone, Debug, PartialEq, Eq, Hash)]
394pub struct CameraId(pub String);
395
396/// Which way a camera looks.
397#[derive(Clone, Copy, Debug, PartialEq, Eq)]
398pub enum CameraFacing {
399 /// At the user: a phone's selfie camera, a laptop's.
400 Front,
401 /// Away from the user: a phone's main camera.
402 Back,
403 /// A camera the system cannot say of: one plugged in, or on another device.
404 External,
405}
406
407/// A camera the app opened, controlled on the app's main thread: Camera2's `CameraDevice`. A
408/// session started on it runs on after it is dropped.
409pub trait CameraDevice {
410 /// The formats the camera runs in, in the system's order, each with all its outputs: Apple's
411 /// `AVCaptureDevice.formats`, Android's stream configuration map.
412 fn formats(&self) -> Vec<CameraFormat>;
413
414 /// Starts the camera in `format`, one of [`formats`](Self::formats), feeding `outputs`, which
415 /// the system configures together, and answers the session once it runs, or is interrupted,
416 /// from which it resumes by itself. Where the camera already runs for another session and the
417 /// system runs a camera in one format at a time, the session joins that format instead, and
418 /// its [`format`](CaptureSession::format) says so. It fails for what can happen at any time:
419 /// the camera in use, unplugged, or not allowed; and for a format the camera does not list.
420 fn start(&self, format: &CameraFormat, outputs: CameraOutputs) -> CameraStartFuture;
421}
422
423/// One way a camera runs, with all its outputs.
424#[derive(Clone, Debug, PartialEq)]
425pub struct CameraFormat {
426 /// The size of its frames and its preview, in the camera's own pixels.
427 pub size: Size,
428 /// The slowest frame rate it keeps at that size, in frames a second.
429 pub min_frame_rate: f64,
430 /// The fastest frame rate it keeps at that size, in frames a second.
431 pub max_frame_rate: f64,
432 /// The sizes it takes photos at while it runs so, in pixels.
433 pub photo_sizes: Vec<Size>,
434}
435
436/// What a camera feeds, given whole to [`CameraDevice::start`]: each output the system's own, for
437/// one use. An output left out is not fed.
438#[derive(Clone, Default)]
439pub struct CameraOutputs {
440 /// The layer the preview shows on, drawn by the system into a layer of its own that is this
441 /// layer's content. The session holds it while it runs. A layer no compositor of this host
442 /// made shows nothing.
443 pub preview: Option<Arc<dyn CompositionLayer>>,
444 /// The reader the frames go to, for the app to draw. A reader no [`VideoFrameReaders`] of this
445 /// host made reads nothing.
446 pub frames: Option<Arc<dyn VideoFrameReader>>,
447 /// The size photos are taken at, one of the format's
448 /// [`photo_sizes`](CameraFormat::photo_sizes), each captured, processed and encoded by the
449 /// system. In a joined format that does not list it, photos come at the system's own size
450 /// for that format.
451 pub photos: Option<Size>,
452 /// Recordings, each encoded and written to a file by the system, with sound where `options`
453 /// ask for it, which asks the user for the microphone as the session starts.
454 pub movies: Option<MovieOptions>,
455}
456
457impl CameraOutputs {
458 /// Outputs that feed nothing.
459 pub fn new() -> CameraOutputs {
460 CameraOutputs::default()
461 }
462
463 pub fn preview(mut self, layer: Arc<dyn CompositionLayer>) -> CameraOutputs {
464 self.preview = Some(layer);
465 self
466 }
467
468 pub fn frames(mut self, reader: Arc<dyn VideoFrameReader>) -> CameraOutputs {
469 self.frames = Some(reader);
470 self
471 }
472
473 pub fn photos(mut self, size: Size) -> CameraOutputs {
474 self.photos = Some(size);
475 self
476 }
477
478 pub fn movies(mut self, options: MovieOptions) -> CameraOutputs {
479 self.movies = Some(options);
480 self
481 }
482}
483
484/// A camera running for the app, controlled on the app's main thread. Dropped, it stops for the
485/// app: its preview leaves the layer it showed on, and its reader reads nothing more.
486pub trait CaptureSession {
487 /// The format it runs in: the one picked, unless it joined the format the camera ran in for
488 /// another session.
489 fn format(&self) -> CameraFormat;
490
491 /// The session's state now.
492 fn state(&self) -> CaptureState;
493
494 /// Resolves once [`state`](Self::state) may answer something else than when this was
495 /// called, or once the session is gone.
496 fn changed(&self) -> MediaChangeFuture;
497
498 /// Takes a photo while the session runs with photos among its outputs, answering it once the
499 /// system has encoded it.
500 fn take_photo(&self) -> PhotoFuture;
501
502 /// Records to a new file at `path`, as a QuickTime movie, while the session runs with movies
503 /// among its outputs, until [`stop_recording`](Self::stop_recording); answers once the file is
504 /// written whole, or why it was not.
505 fn start_recording(&self, path: PathBuf) -> RecordingFuture;
506
507 /// Ends the recording, whose future then answers.
508 fn stop_recording(&self);
509}
510
511/// What a camera session is doing.
512#[derive(Clone, Debug, PartialEq, Eq)]
513pub enum CaptureState {
514 Running,
515 /// No frames for now, for this reason where the system gives one; it resumes by itself.
516 /// macOS gives none: Apple marks the interruption's reason unavailable there.
517 Interrupted(Option<CameraInterruption>),
518 /// For good, for this reason.
519 Ended(CameraError),
520}
521
522/// Why a camera session gives no frames for now.
523#[derive(Clone, Copy, Debug, PartialEq, Eq)]
524pub enum CameraInterruption {
525 /// Another app has the camera, as a phone allows one at a time.
526 InUseByAnotherApp,
527 /// The app is in the background.
528 Background,
529 /// The system is too hot or too busy.
530 SystemPressure,
531}
532
533/// Why a camera did not open, start or run.
534#[derive(Clone, Debug, PartialEq, Eq)]
535pub enum CameraError {
536 /// The user did not let the app use the cameras, or the system's policy does not.
537 NotAllowed,
538 /// Another app has the camera, and the system does not share it.
539 InUse,
540 /// The camera is no longer attached.
541 Unplugged,
542 /// The system failed, for its reason.
543 Failed(String),
544}
545
546impl fmt::Display for CameraError {
547 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548 match self {
549 CameraError::NotAllowed => f.write_str("the app may not use the camera"),
550 CameraError::InUse => f.write_str("another app has the camera"),
551 CameraError::Unplugged => f.write_str("the camera is no longer attached"),
552 CameraError::Failed(reason) => write!(f, "the camera failed: {reason}"),
553 }
554 }
555}
556
557impl std::error::Error for CameraError {}
558
559/// A photo, as the system encoded it.
560#[derive(Clone, Debug, PartialEq, Eq)]
561pub struct Photo {
562 pub bytes: Vec<u8>,
563 /// The encoding's MIME type: `image/jpeg`.
564 pub mime_type: String,
565}
566
567/// What a recording asks for beyond the picture.
568#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
569pub struct MovieOptions {
570 /// Whether it records sound from the system's microphone.
571 pub sound: bool,
572}
573
574/// The system's frame readers, or `None` from
575/// [`Platform::video_frame_readers`](crate::Platform::video_frame_readers) where it hands out no
576/// frames.
577pub trait VideoFrameReaders {
578 /// A reader fed by nothing yet, which reads the frames of the source it is given to: a
579 /// player's [`set_video_output`](MediaPlayer::set_video_output), a camera's outputs
580 /// ([`CameraOutputs::frames`]). The reader goes when its last handle does: the app's, and its
581 /// source's.
582 fn make_reader(&self) -> Arc<dyn VideoFrameReader>;
583}
584
585/// The pictures of the one source a reader is given to, read from any thread as they are drawn.
586/// It takes one source at a time, as Android's `ImageReader` takes one producer: given to a second
587/// while the first still feeds it, it is refused, and the second sends its frames nowhere. A
588/// reader keeps no frame for later: one not read before the next arrives is dropped.
589///
590/// A host gets its own type back from a reader it is given by downcasting through `Any`.
591pub trait VideoFrameReader: Any + Send + Sync {
592 /// The frame to show at `display_time`, the moment the picture being drawn reaches the
593 /// screen: a player's frame for that moment of the video, as a display link asks
594 /// AVFoundation's video output for each refresh's target time, and a camera's newest. `None`
595 /// when it is the frame handed out last, or none came.
596 fn frame_for(&self, display_time: Instant) -> Option<Arc<dyn VideoFrame>>;
597}
598
599/// One picture of a video as the system hands it out, as WebCodecs' `VideoFrame`. It holds its
600/// slot in the decoder's pool until it, and every texture made of it, are dropped.
601///
602/// Each host implements it over the frames its system hands out. Platform code reaches the
603/// system's own frame by downcasting through `Any` to the type of the host that handed it out:
604/// on Apple's systems `inset-embedder-winit`'s `CoreVideoFrame`, whose `pixel_buffer` is the
605/// `CVPixelBuffer` AVFoundation handed out.
606pub trait VideoFrame: Any + Send + Sync {
607 /// The picture's width, in its own pixels.
608 fn width(&self) -> u32;
609
610 /// The picture's height, in its own pixels.
611 fn height(&self) -> u32;
612
613 /// A texture of the frame on `device`, for sampling, sharing its memory where the system can.
614 /// The texture holds the frame. `None` for a device that cannot take the frame's memory: on
615 /// macOS a wgpu device not on Metal, or one Core Video makes no texture of the frame on.
616 fn texture(&self, device: &wgpu::Device) -> Option<wgpu::Texture>;
617}
618
619/// Why a camera session's photo or recording answered nothing.
620#[derive(Clone, Debug, PartialEq, Eq)]
621pub enum MediaError {
622 /// The session is interrupted or ended.
623 NotRunning,
624 /// The system failed, for its reason.
625 Failed(String),
626}
627
628impl fmt::Display for MediaError {
629 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630 match self {
631 MediaError::NotRunning => f.write_str("the camera session does not run"),
632 MediaError::Failed(reason) => write!(f, "the camera failed: {reason}"),
633 }
634 }
635}
636
637impl std::error::Error for MediaError {}
638
639/// What a player's, a session's, the cameras' or the caption settings' `changed` answers.
640pub type MediaChangeFuture = Pin<Box<dyn Future<Output = ()>>>;
641/// What listing the cameras answers.
642pub type CameraListFuture = Pin<Box<dyn Future<Output = Vec<Camera>>>>;
643/// What opening a camera answers.
644pub type CameraOpenFuture =
645 Pin<Box<dyn Future<Output = Result<Box<dyn CameraDevice>, CameraError>>>>;
646/// What starting a camera answers.
647pub type CameraStartFuture =
648 Pin<Box<dyn Future<Output = Result<Box<dyn CaptureSession>, CameraError>>>>;
649/// What taking a photo answers.
650pub type PhotoFuture = Pin<Box<dyn Future<Output = Result<Photo, MediaError>>>>;
651/// What a recording answers once it ends.
652pub type RecordingFuture = Pin<Box<dyn Future<Output = Result<(), MediaError>>>>;