1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! [MediaDeviceInfo][0] related representations.
//!
//! [0]: https://w3.org/TR/mediacapture-streams#device-info

use medea_macro::dart_bridge;

use crate::{
    media::MediaDeviceKind,
    platform::dart::utils::{
        dart_string_into_rust, handle::DartHandle, NonNullDartValueArgExt,
    },
};

#[dart_bridge("flutter/lib/src/native/platform/media_device_info.g.dart")]
mod media_device_info {
    use std::{os::raw::c_char, ptr};

    use dart_sys::Dart_Handle;

    use crate::api::DartValueArg;

    extern "C" {
        /// Returns an unique identifier of the provided device.
        pub fn device_id(info: Dart_Handle) -> ptr::NonNull<c_char>;

        /// Returns a kind of the provided device.
        pub fn kind(info: Dart_Handle) -> i64;

        /// Returns a label describing the provided device (for example,
        /// "External USB Webcam").
        ///
        /// If the provided device has no associated label, then returns an
        /// empty string.
        pub fn label(info: Dart_Handle) -> ptr::NonNull<c_char>;

        /// Returns a group identifier of the provided device.
        pub fn group_id(
            info: Dart_Handle,
        ) -> ptr::NonNull<DartValueArg<Option<String>>>;

        /// Indicates whether the last attempt to use the provided device
        /// failed.
        pub fn is_failed(info: Dart_Handle) -> bool;
    }
}

/// Representation of a [MediaDeviceInfo][0] ONLY for input devices.
///
/// [0]: https://w3.org/TR/mediacapture-streams#device-info
#[derive(Clone, Debug)]
pub struct MediaDeviceInfo {
    /// Handle to the Dart side `MediaDeviceInfo`.
    handle: DartHandle,

    /// [`MediaDeviceKind`] of this [`MediaDeviceInfo`].
    kind: MediaDeviceKind,
}

impl MediaDeviceInfo {
    /// Returns a unique identifier of the device represented by this
    /// [`MediaDeviceInfo`].
    #[must_use]
    pub fn device_id(&self) -> String {
        let device_id =
            unsafe { media_device_info::device_id(self.handle.get()) };
        unsafe { dart_string_into_rust(device_id) }
    }

    /// Returns a kind of the device represented by this [`MediaDeviceInfo`].
    #[must_use]
    pub const fn kind(&self) -> MediaDeviceKind {
        self.kind
    }

    /// Returns a label describing the device represented by this
    /// [`MediaDeviceInfo`] (for example, "External USB Webcam").
    ///
    /// If the device has no associated label, then returns an empty string.
    #[must_use]
    pub fn label(&self) -> String {
        let label = unsafe { media_device_info::label(self.handle.get()) };
        unsafe { dart_string_into_rust(label) }
    }

    /// Returns a group identifier of the device represented by this
    /// [`MediaDeviceInfo`]
    ///
    /// Two devices have the same group identifier if they belong to the same
    /// physical device. For example, the audio input and output devices
    /// representing the speaker and microphone of the same headset have the
    /// same [groupId][1].
    ///
    /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadeviceinfo-groupid
    #[allow(clippy::unwrap_in_result)]
    #[must_use]
    pub fn group_id(&self) -> Option<String> {
        let group_id =
            unsafe { media_device_info::group_id(self.handle.get()) };
        Option::try_from(unsafe { group_id.unbox() }).unwrap()
    }

    /// Indicates whether the last attempt to use this device failed.
    #[must_use]
    pub fn is_failed(&self) -> bool {
        unsafe { media_device_info::is_failed(self.handle.get()) }
    }
}

impl TryFrom<DartHandle> for MediaDeviceInfo {
    type Error = NotInput;

    fn try_from(value: DartHandle) -> Result<Self, Self::Error> {
        #[allow(clippy::map_err_ignore)]
        let kind = unsafe { media_device_info::kind(value.get()) }
            .try_into()
            .map_err(|_| NotInput)?;

        Ok(Self {
            handle: value,
            kind,
        })
    }
}

/// Error of a [MediaDeviceInfo][0] representing not an input device.
///
/// [0]: https://w3.org/TR/mediacapture-streams#device-info
#[derive(Clone, Copy, Debug)]
pub struct NotInput;