Skip to content

Commit 2336ba9

Browse files
turtletongueevdokimovstyranron
authored
Replace AudioTrack and VideoTrack with generic Track in medea-flutter-webrtc-native crate (#209)
Co-authored-by: evdokimovs <[email protected]> Co-authored-by: Kai Ren <[email protected]>
1 parent 7a566c9 commit 2336ba9

File tree

10 files changed

+1922
-1780
lines changed

10 files changed

+1922
-1780
lines changed

crates/native/src/api/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ use crate::{
3535
frb_generated::{
3636
FLUTTER_RUST_BRIDGE_CODEGEN_VERSION, RustOpaque, StreamSink,
3737
},
38+
media::TrackOrigin,
3839
pc::PeerConnectionId,
3940
renderer::FrameHandler,
40-
user_media::TrackOrigin,
4141
};
4242

4343
/// Custom [`Handler`] for executing Rust code called from Dart.

crates/native/src/devices.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
Webrtc, api,
1515
api::WEBRTC,
1616
frb_generated::StreamSink,
17-
user_media::{AudioDeviceId, MediaTrackSource, TrackOrigin, VideoDeviceId},
17+
media::{AudioDeviceId, MediaTrackSource, TrackOrigin, VideoDeviceId},
1818
};
1919

2020
/// Returns a list of all available displays that can be used for screen
@@ -328,7 +328,7 @@ impl Webrtc {
328328
for (_, delete_ai) in old_audio_ins {
329329
for track in self.audio_tracks.iter() {
330330
if let MediaTrackSource::Local(s) = track.source() {
331-
if s.device_id == delete_ai {
331+
if s.device_id() == &delete_ai {
332332
tracks_to_remove.push((
333333
track.id().into(),
334334
api::MediaType::Audio,
@@ -344,7 +344,7 @@ impl Webrtc {
344344
for (_, delete_vi) in old_video_ins {
345345
for track in self.video_tracks.iter() {
346346
if let MediaTrackSource::Local(s) = track.source() {
347-
if s.device_id == delete_vi {
347+
if s.device_id() == &delete_vi {
348348
tracks_to_remove.push((
349349
track.id().into(),
350350
api::MediaType::Video,

crates/native/src/lib.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@
150150
clippy::missing_docs_in_private_items,
151151
clippy::missing_panics_doc,
152152
clippy::multiple_inherent_impl,
153-
clippy::partial_pub_fields,
154153
clippy::undocumented_unsafe_blocks,
155154
clippy::unwrap_in_result,
156155
clippy::unwrap_used,
@@ -188,9 +187,9 @@ pub mod api;
188187
mod frb_generated;
189188
mod devices;
190189
pub mod frb;
190+
mod media;
191191
mod pc;
192192
mod renderer;
193-
mod user_media;
194193
pub mod video_sink;
195194

196195
use std::{
@@ -208,17 +207,17 @@ use threadpool::ThreadPool;
208207
#[doc(inline)]
209208
pub use crate::{
210209
devices::DevicesState,
211-
pc::{
212-
PeerConnection, RtpEncodingParameters, RtpParameters, RtpTransceiver,
213-
},
214-
user_media::{
210+
media::{
215211
AudioDeviceId, AudioDeviceModule, AudioSource, AudioTrack,
216-
AudioTrackId, MediaStreamId, VideoDeviceId, VideoDeviceInfo,
212+
AudioTrackId, MediaStreamId, Track, VideoDeviceId, VideoDeviceInfo,
217213
VideoSource, VideoTrack, VideoTrackId,
218214
},
215+
pc::{
216+
PeerConnection, RtpEncodingParameters, RtpParameters, RtpTransceiver,
217+
},
219218
video_sink::VideoSink,
220219
};
221-
use crate::{user_media::TrackOrigin, video_sink::Id as VideoSinkId};
220+
use crate::{media::TrackOrigin, video_sink::Id as VideoSinkId};
222221

223222
/// Main [`ThreadPool`] used by [`flutter_rust_bridge`] when calling
224223
/// synchronous Rust code to avoid locking [`libwebrtc`] threads.

crates/native/src/media/device.rs

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
use std::hash::Hash;
2+
3+
use anyhow::bail;
4+
use derive_more::with_trait::{AsRef, Display, From, Into};
5+
use libwebrtc_sys as sys;
6+
use sys::AudioProcessing;
7+
use xxhash::xxh3::xxh3_64;
8+
9+
use crate::api;
10+
11+
/// ID of a video input device that provides data to some [`VideoSource`].
12+
///
13+
/// [`VideoSource`]: crate::media::VideoSource
14+
#[derive(AsRef, Clone, Debug, Display, Eq, From, Hash, Into, PartialEq)]
15+
#[as_ref(forward)]
16+
pub struct VideoDeviceId(String);
17+
18+
/// ID of an `AudioDevice`.
19+
#[derive(
20+
AsRef, Clone, Debug, Default, Display, Eq, From, Hash, Into, PartialEq,
21+
)]
22+
#[as_ref(forward)]
23+
pub struct AudioDeviceId(String);
24+
25+
/// [`sys::VideoDeviceInfo`] wrapper.
26+
pub struct VideoDeviceInfo(sys::VideoDeviceInfo);
27+
28+
impl VideoDeviceInfo {
29+
/// Creates a new [`VideoDeviceInfo`].
30+
///
31+
/// # Errors
32+
///
33+
/// If [`sys::VideoDeviceInfo::create()`] returns error.
34+
pub fn new() -> anyhow::Result<Self> {
35+
Ok(Self(sys::VideoDeviceInfo::create()?))
36+
}
37+
38+
/// Returns count of a video recording devices.
39+
pub fn number_of_devices(&mut self) -> u32 {
40+
if api::is_fake_media() { 1 } else { self.0.number_of_devices() }
41+
}
42+
43+
/// Returns the `(label, id)` tuple for the given video device `index`.
44+
///
45+
/// # Errors
46+
///
47+
/// If [`sys::VideoDeviceInfo::device_name()`] call returns error.
48+
pub fn device_name(
49+
&mut self,
50+
index: u32,
51+
) -> anyhow::Result<(String, String)> {
52+
if api::is_fake_media() {
53+
Ok((String::from("fake camera"), String::from("fake camera id")))
54+
} else {
55+
self.0.device_name(index)
56+
}
57+
}
58+
}
59+
60+
/// [`sys::AudioDeviceModule`] wrapper tracking the currently used audio input
61+
/// device.
62+
#[derive(AsRef)]
63+
pub struct AudioDeviceModule {
64+
/// [`sys::AudioDeviceModule`] backing this [`AudioDeviceModule`].
65+
#[as_ref]
66+
inner: sys::AudioDeviceModule,
67+
}
68+
69+
impl AudioDeviceModule {
70+
/// Creates a new [`AudioDeviceModule`] according to the passed
71+
/// [`sys::AudioLayer`].
72+
///
73+
/// # Errors
74+
///
75+
/// If could not find any available recording device.
76+
pub fn new(
77+
worker_thread: &mut sys::Thread,
78+
audio_layer: sys::AudioLayer,
79+
task_queue_factory: &mut sys::TaskQueueFactory,
80+
) -> anyhow::Result<Self> {
81+
let inner = sys::AudioDeviceModule::create_proxy(
82+
worker_thread,
83+
audio_layer,
84+
task_queue_factory,
85+
)?;
86+
inner.init()?;
87+
88+
Ok(Self { inner })
89+
}
90+
91+
/// Returns the `(label, id)` tuple for the given audio playout device
92+
/// `index`.
93+
///
94+
/// # Errors
95+
///
96+
/// If [`sys::AudioDeviceModule::playout_device_name()`] call fails.
97+
pub fn playout_device_name(
98+
&self,
99+
index: i16,
100+
) -> anyhow::Result<(String, String)> {
101+
if api::is_fake_media() {
102+
return Ok((
103+
String::from("fake headset"),
104+
String::from("fake headset id"),
105+
));
106+
}
107+
108+
let (label, mut device_id) = self.inner.playout_device_name(index)?;
109+
110+
if device_id.is_empty() {
111+
let hash = xxh3_64(
112+
[label.as_bytes(), &[api::MediaDeviceKind::AudioOutput as u8]]
113+
.concat()
114+
.as_slice(),
115+
);
116+
device_id = hash.to_string();
117+
}
118+
119+
Ok((label, device_id))
120+
}
121+
122+
/// Returns the `(label, id)` tuple for the given audio recording device
123+
/// `index`.
124+
///
125+
/// # Errors
126+
///
127+
/// If [`sys::AudioDeviceModule::recording_device_name()`] call fails.
128+
pub fn recording_device_name(
129+
&self,
130+
index: i16,
131+
) -> anyhow::Result<(String, String)> {
132+
if api::is_fake_media() {
133+
return Ok((String::from("fake mic"), String::from("fake mic id")));
134+
}
135+
136+
let (label, mut device_id) = self.inner.recording_device_name(index)?;
137+
138+
if device_id.is_empty() {
139+
let hash = xxh3_64(
140+
[label.as_bytes(), &[api::MediaDeviceKind::AudioOutput as u8]]
141+
.concat()
142+
.as_slice(),
143+
);
144+
device_id = hash.to_string();
145+
}
146+
147+
Ok((label, device_id))
148+
}
149+
150+
/// Returns count of available audio playout devices.
151+
///
152+
/// # Errors
153+
///
154+
/// If [`sys::AudioDeviceModule::playout_devices()`] call fails.
155+
#[must_use]
156+
pub fn playout_devices(&self) -> u32 {
157+
self.inner.playout_devices()
158+
}
159+
160+
/// Returns count of available audio recording devices.
161+
///
162+
/// # Errors
163+
///
164+
/// If [`sys::AudioDeviceModule::recording_devices()`] call fails.
165+
#[must_use]
166+
pub fn recording_devices(&self) -> u32 {
167+
if api::is_fake_media() { 1 } else { self.inner.recording_devices() }
168+
}
169+
170+
/// Creates a new [`sys::AudioSourceInterface`] based on the provided
171+
/// `device_index`.
172+
///
173+
/// # Errors
174+
///
175+
/// If [`sys::AudioDeviceModule::recording_devices()`] call fails.
176+
pub fn create_audio_source(
177+
&mut self,
178+
device_index: u16,
179+
ap: &AudioProcessing,
180+
) -> anyhow::Result<sys::AudioSourceInterface> {
181+
if api::is_fake_media() {
182+
self.inner.create_fake_audio_source()
183+
} else {
184+
self.inner.create_audio_source(device_index, ap)
185+
}
186+
}
187+
188+
/// Disposes a [`sys::AudioSourceInterface`] by the provided
189+
/// [`AudioDeviceId`].
190+
pub fn dispose_audio_source(&mut self, device_id: &AudioDeviceId) {
191+
self.inner.dispose_audio_source(device_id.to_string());
192+
}
193+
194+
/// Sets the microphone system volume according to the given level in
195+
/// percents.
196+
///
197+
/// # Errors
198+
///
199+
/// Errors if any of the following calls fail:
200+
/// - [`sys::AudioDeviceModule::microphone_volume_is_available()`];
201+
/// - [`sys::AudioDeviceModule::min_microphone_volume()`];
202+
/// - [`sys::AudioDeviceModule::max_microphone_volume()`];
203+
/// - [`sys::AudioDeviceModule::set_microphone_volume()`].
204+
pub fn set_microphone_volume(&self, mut level: u8) -> anyhow::Result<()> {
205+
if !self.microphone_volume_is_available()? {
206+
bail!("The microphone volume is unavailable.")
207+
}
208+
209+
if level > 100 {
210+
level = 100;
211+
}
212+
213+
let min_volume = self.inner.min_microphone_volume()?;
214+
let max_volume = self.inner.max_microphone_volume()?;
215+
216+
let volume = f64::from(max_volume - min_volume)
217+
.mul_add(f64::from(level) / 100.0, f64::from(min_volume));
218+
219+
#[expect( // intentional
220+
clippy::cast_possible_truncation,
221+
clippy::cast_sign_loss,
222+
reason = "size fits and non-negative"
223+
)]
224+
self.inner.set_microphone_volume(volume as u32)
225+
}
226+
227+
/// Indicates if the microphone is available to set volume.
228+
///
229+
/// # Errors
230+
///
231+
/// If [`sys::AudioDeviceModule::microphone_volume_is_available()`] call
232+
/// fails.
233+
pub fn microphone_volume_is_available(&self) -> anyhow::Result<bool> {
234+
Ok(self.inner.microphone_volume_is_available().unwrap_or(false))
235+
}
236+
237+
/// Returns the current level of the microphone volume in percents.
238+
///
239+
/// # Errors
240+
///
241+
/// If fails on:
242+
/// - [`sys::AudioDeviceModule::microphone_volume()`] call
243+
/// - [`sys::AudioDeviceModule::min_microphone_volume()`] call
244+
/// - [`sys::AudioDeviceModule::max_microphone_volume()`] call
245+
pub fn microphone_volume(&self) -> anyhow::Result<u32> {
246+
let volume = self.inner.microphone_volume()?;
247+
let min_volume = self.inner.min_microphone_volume()?;
248+
let max_volume = self.inner.max_microphone_volume()?;
249+
250+
#[expect( // intentional
251+
clippy::cast_possible_truncation,
252+
clippy::cast_sign_loss,
253+
reason = "size fits and non-negative"
254+
)]
255+
let level = (f64::from(volume - min_volume)
256+
/ f64::from(max_volume - min_volume)
257+
* 100.0) as u32;
258+
259+
Ok(level)
260+
}
261+
262+
/// Changes the playout device for this [`AudioDeviceModule`].
263+
///
264+
/// # Errors
265+
///
266+
/// If [`sys::AudioDeviceModule::set_playout_device()`] call fails.
267+
pub fn set_playout_device(&self, index: u16) -> anyhow::Result<()> {
268+
self.inner.set_playout_device(index)?;
269+
270+
Ok(())
271+
}
272+
273+
/// Stops playout of audio on this [`AudioDeviceModule`].
274+
///
275+
/// # Errors
276+
///
277+
/// If [`sys::AudioDeviceModule::stop_playout()`] call fails.
278+
pub fn stop_playout(&self) -> anyhow::Result<()> {
279+
self.inner.stop_playout()
280+
}
281+
282+
/// Indicates whether stereo is available in this playout
283+
/// [`AudioDeviceModule`].
284+
///
285+
/// # Errors
286+
///
287+
/// If [`sys::AudioDeviceModule::stereo_playout_is_available()`] call fails.
288+
pub fn stereo_playout_is_available(&self) -> anyhow::Result<bool> {
289+
self.inner.stereo_playout_is_available()
290+
}
291+
292+
/// Initializes this playout [`AudioDeviceModule`].
293+
///
294+
/// # Errors
295+
///
296+
/// If [`sys::AudioDeviceModule::init_playout()`] call fails.
297+
pub fn init_playout(&self) -> anyhow::Result<()> {
298+
self.inner.init_playout()
299+
}
300+
301+
/// Starts playout of audio on this [`AudioDeviceModule`].
302+
///
303+
/// # Errors
304+
///
305+
/// If [`sys::AudioDeviceModule::start_playout()`] call fails.
306+
pub fn start_playout(&self) -> anyhow::Result<()> {
307+
self.inner.start_playout()
308+
}
309+
}

0 commit comments

Comments
 (0)