feat: video publishing (#42)
- Prepare webrtc abstraction ( for future wasm support ) - Added track publish support for videos - Added LogoTrack example to simple_room demo - Lot of cleanup - There are compiler warnings I'll solve on our v1 release
This commit is contained in:
+14
-10
@@ -1,3 +1,4 @@
|
||||
use self::track::RemoteTrack;
|
||||
use crate::participant::ConnectionQuality;
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
@@ -10,6 +11,7 @@ use tokio::sync::mpsc;
|
||||
pub use crate::rtc_engine::SimulateScenario;
|
||||
|
||||
pub mod id;
|
||||
pub mod options;
|
||||
pub mod participant;
|
||||
pub mod publication;
|
||||
pub mod room_session;
|
||||
@@ -25,34 +27,36 @@ pub enum RoomError {
|
||||
Engine(#[from] EngineError),
|
||||
#[error("room failure: {0}")]
|
||||
Internal(String),
|
||||
#[error("this track or a track of the same source is already published")]
|
||||
TrackAlreadyPublished,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RoomEvent {
|
||||
ParticipantConnected(Arc<RemoteParticipant>),
|
||||
ParticipantDisconnected(Arc<RemoteParticipant>),
|
||||
ParticipantConnected(RemoteParticipant),
|
||||
ParticipantDisconnected(RemoteParticipant),
|
||||
TrackSubscribed {
|
||||
track: RemoteTrackHandle,
|
||||
track: RemoteTrack,
|
||||
publication: RemoteTrackPublication,
|
||||
participant: Arc<RemoteParticipant>,
|
||||
participant: RemoteParticipant,
|
||||
},
|
||||
TrackPublished {
|
||||
publication: RemoteTrackPublication,
|
||||
participant: Arc<RemoteParticipant>,
|
||||
participant: RemoteParticipant,
|
||||
},
|
||||
TrackUnpublished {
|
||||
publication: RemoteTrackPublication,
|
||||
participant: Arc<RemoteParticipant>,
|
||||
participant: RemoteParticipant,
|
||||
},
|
||||
TrackUnsubscribed {
|
||||
track: RemoteTrackHandle,
|
||||
track: RemoteTrack,
|
||||
publication: RemoteTrackPublication,
|
||||
participant: Arc<RemoteParticipant>,
|
||||
participant: RemoteParticipant,
|
||||
},
|
||||
TrackSubscriptionFailed {
|
||||
error: track::TrackError,
|
||||
sid: TrackSid,
|
||||
participant: Arc<RemoteParticipant>,
|
||||
participant: RemoteParticipant,
|
||||
},
|
||||
TrackMuted {
|
||||
participant: Participant,
|
||||
@@ -72,7 +76,7 @@ pub enum RoomEvent {
|
||||
DataReceived {
|
||||
payload: Arc<Vec<u8>>,
|
||||
kind: proto::data_packet::Kind,
|
||||
participant: Arc<RemoteParticipant>,
|
||||
participant: RemoteParticipant,
|
||||
},
|
||||
ConnectionStateChanged(ConnectionState),
|
||||
Connected,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use livekit_webrtc::prelude::*;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum VideoCodec {
|
||||
VP8,
|
||||
H264,
|
||||
AV1,
|
||||
}
|
||||
|
||||
impl VideoCodec {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
VideoCodec::VP8 => "vp8",
|
||||
VideoCodec::H264 => "h264",
|
||||
VideoCodec::AV1 => "av1",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VideoResolution {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub frame_rate: f64,
|
||||
pub aspect_ratio: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VideoEncoding {
|
||||
pub max_bitrate: u64,
|
||||
pub max_framerate: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VideoPreset {
|
||||
pub encoding: VideoEncoding,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AudioPreset {
|
||||
pub max_bitrate: u32,
|
||||
}
|
||||
|
||||
impl AudioPreset {
|
||||
pub const fn new(max_bitrate: u32) -> Self {
|
||||
Self { max_bitrate }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VideoCaptureOptions {
|
||||
pub preset: VideoPreset,
|
||||
}
|
||||
|
||||
impl Default for VideoCaptureOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
preset: video::H720,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrackPublishOptions {
|
||||
pub dynacast: bool,
|
||||
pub video_codec: VideoCodec,
|
||||
pub dtx: bool,
|
||||
pub red: bool,
|
||||
pub simulcast: bool,
|
||||
pub screenshare: bool,
|
||||
pub name: String,
|
||||
pub source: TrackSource,
|
||||
}
|
||||
|
||||
impl Default for TrackPublishOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dynacast: false,
|
||||
video_codec: VideoCodec::VP8,
|
||||
dtx: true,
|
||||
red: true,
|
||||
simulcast: true,
|
||||
screenshare: false,
|
||||
name: "unnamed track".to_owned(),
|
||||
source: TrackSource::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoPreset {
|
||||
pub const fn new(width: u32, height: u32, max_bitrate: u64, max_framerate: f64) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
encoding: VideoEncoding {
|
||||
max_bitrate,
|
||||
max_framerate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolution(&self) -> VideoResolution {
|
||||
VideoResolution {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
frame_rate: self.encoding.max_framerate,
|
||||
aspect_ratio: self.width as f32 / self.height as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute appropriate RtpEncodingParameters from the video resolution.
|
||||
/// TrackPublishOptions helps to find the most appropriate encodings
|
||||
pub fn compute_video_encodings(
|
||||
width: u32,
|
||||
height: u32,
|
||||
options: &TrackPublishOptions,
|
||||
) -> Vec<RtpEncodingParameters> {
|
||||
let encoding = compute_appropriate_encoding(options.screenshare, width, height);
|
||||
|
||||
let initial_preset = VideoPreset {
|
||||
width,
|
||||
height,
|
||||
encoding: VideoEncoding {
|
||||
max_bitrate: encoding.max_bitrate,
|
||||
max_framerate: encoding.max_framerate,
|
||||
},
|
||||
};
|
||||
|
||||
if !options.simulcast {
|
||||
return into_rtp_encodings(width, height, &[initial_preset]);
|
||||
}
|
||||
|
||||
let mut simulcast_presets =
|
||||
compute_default_simulcast_presets(options.screenshare, &initial_preset);
|
||||
|
||||
let mid_preset = simulcast_presets.pop();
|
||||
let low_preset = simulcast_presets.pop();
|
||||
|
||||
let size = u32::max(width, height);
|
||||
if size >= 960 && low_preset.is_some() {
|
||||
return into_rtp_encodings(
|
||||
width,
|
||||
height,
|
||||
&[low_preset.unwrap(), mid_preset.unwrap(), initial_preset],
|
||||
);
|
||||
} else if size >= 480 {
|
||||
return into_rtp_encodings(width, height, &[mid_preset.unwrap(), initial_preset]);
|
||||
}
|
||||
|
||||
// Other layers not needed
|
||||
into_rtp_encodings(width, height, &[initial_preset])
|
||||
}
|
||||
|
||||
/// Return an appropriate VideoEncdoding for the specified resolution based on our presets
|
||||
pub fn compute_appropriate_encoding(
|
||||
is_screenshare: bool,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> VideoEncoding {
|
||||
let presets = compute_presets_for_resolution(is_screenshare, width, height);
|
||||
let size = u32::max(width, height);
|
||||
|
||||
for preset in presets {
|
||||
if preset.width >= size {
|
||||
return preset.encoding.clone();
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn compute_presets_for_resolution(
|
||||
is_screenshare: bool,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> &'static [VideoPreset] {
|
||||
if is_screenshare {
|
||||
return screenshare::PRESETS;
|
||||
}
|
||||
|
||||
// Check how close width & height are from 16/9 or 4/3
|
||||
let ar = landscape_aspect_ratio(width, height);
|
||||
if f32::abs(ar - 16.0 / 9.0) < f32::abs(ar - 4.0 / 3.0) {
|
||||
return video::PRESETS;
|
||||
}
|
||||
|
||||
video43::PRESETS
|
||||
}
|
||||
|
||||
/// Returns our most appropriate default presets
|
||||
pub fn compute_default_simulcast_presets(
|
||||
is_screenshare: bool,
|
||||
initial: &VideoPreset,
|
||||
) -> Vec<VideoPreset> {
|
||||
if is_screenshare {
|
||||
return vec![screenshare::compute_default_simulcast_preset(initial)];
|
||||
}
|
||||
|
||||
let ar = landscape_aspect_ratio(initial.width, initial.height);
|
||||
if f32::abs(ar - 16.0 / 9.0) < f32::abs(ar - 4.0 / 3.0) {
|
||||
return video::DEFAULT_SIMULCAST_PRESETS.to_owned();
|
||||
}
|
||||
|
||||
video43::DEFAULT_SIMULCAST_PRESETS.to_owned()
|
||||
}
|
||||
|
||||
pub fn landscape_aspect_ratio(width: u32, height: u32) -> f32 {
|
||||
if width > height {
|
||||
width as f32 / height as f32
|
||||
} else {
|
||||
height as f32 / width as f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Presets must be ordered
|
||||
pub fn into_rtp_encodings(
|
||||
initial_width: u32,
|
||||
initial_height: u32,
|
||||
presets: &[VideoPreset],
|
||||
) -> Vec<RtpEncodingParameters> {
|
||||
let mut encodings = Vec::with_capacity(presets.len());
|
||||
let size = u32::min(initial_width, initial_height);
|
||||
for (i, preset) in presets.iter().enumerate() {
|
||||
encodings.push(RtpEncodingParameters {
|
||||
rid: VIDEO_RIDS[i].to_string(),
|
||||
scale_resolution_down_by: Some(f64::max(
|
||||
1.0,
|
||||
size as f64 / u32::min(preset.width, preset.height) as f64,
|
||||
)),
|
||||
max_bitrate: Some(preset.encoding.max_bitrate),
|
||||
max_framerate: Some(preset.encoding.max_framerate),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
encodings
|
||||
}
|
||||
|
||||
pub fn video_quality_for_rid(rid: &str) -> Option<proto::VideoQuality> {
|
||||
match rid {
|
||||
"f" => Some(proto::VideoQuality::High),
|
||||
"h" => Some(proto::VideoQuality::Medium),
|
||||
"q" => Some(proto::VideoQuality::Low),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_layers_from_encodings(
|
||||
width: u32,
|
||||
height: u32,
|
||||
encodings: &[RtpEncodingParameters],
|
||||
) -> Vec<proto::VideoLayer> {
|
||||
if encodings.is_empty() {
|
||||
return vec![proto::VideoLayer {
|
||||
quality: proto::VideoQuality::High as i32,
|
||||
width,
|
||||
height,
|
||||
bitrate: 0,
|
||||
ssrc: 0,
|
||||
}];
|
||||
}
|
||||
|
||||
let mut layers = Vec::with_capacity(encodings.len());
|
||||
for encoding in encodings {
|
||||
let scale = encoding.scale_resolution_down_by.unwrap_or(1.0);
|
||||
let quality = video_quality_for_rid(&encoding.rid).unwrap_or(proto::VideoQuality::High);
|
||||
|
||||
layers.push(proto::VideoLayer {
|
||||
quality: quality as i32,
|
||||
width: (width as f64 / scale) as u32,
|
||||
height: (height as f64 / scale) as u32,
|
||||
bitrate: encoding.max_bitrate.unwrap_or(0) as u32,
|
||||
ssrc: 0,
|
||||
});
|
||||
}
|
||||
|
||||
layers
|
||||
}
|
||||
|
||||
const VIDEO_RIDS: &[char] = &['q', 'h', 'f'];
|
||||
|
||||
pub mod audio {
|
||||
use super::AudioPreset;
|
||||
|
||||
pub const TELEPHONE: AudioPreset = AudioPreset::new(12_000);
|
||||
pub const SPEECH: AudioPreset = AudioPreset::new(20_000);
|
||||
pub const MUSIC: AudioPreset = AudioPreset::new(32_000);
|
||||
pub const MUSIC_STEREO: AudioPreset = AudioPreset::new(48_000);
|
||||
pub const MUSIC_HIGH_QUALITY: AudioPreset = AudioPreset::new(64_000);
|
||||
pub const MUSIC_HIGH_QUALITY_STEREO: AudioPreset = AudioPreset::new(96_000);
|
||||
|
||||
pub const PRESETS: &[AudioPreset] = &[
|
||||
TELEPHONE,
|
||||
SPEECH,
|
||||
MUSIC,
|
||||
MUSIC_STEREO,
|
||||
MUSIC_HIGH_QUALITY,
|
||||
MUSIC_HIGH_QUALITY_STEREO,
|
||||
];
|
||||
}
|
||||
|
||||
pub mod video {
|
||||
use super::VideoPreset;
|
||||
|
||||
pub const H90: VideoPreset = VideoPreset::new(160, 90, 60_000, 15.0);
|
||||
pub const H180: VideoPreset = VideoPreset::new(320, 180, 120_000, 15.0);
|
||||
pub const H216: VideoPreset = VideoPreset::new(384, 216, 180_000, 15.0);
|
||||
pub const H360: VideoPreset = VideoPreset::new(640, 360, 300_000, 20.0);
|
||||
pub const H540: VideoPreset = VideoPreset::new(960, 540, 600_000, 25.0);
|
||||
pub const H720: VideoPreset = VideoPreset::new(1280, 720, 1_700_000, 30.0);
|
||||
pub const H1080: VideoPreset = VideoPreset::new(1920, 1080, 3_000_000, 30.0);
|
||||
pub const H1440: VideoPreset = VideoPreset::new(2560, 1440, 5_000_000, 30.0);
|
||||
pub const H2160: VideoPreset = VideoPreset::new(3840, 2160, 8_000_000, 30.0);
|
||||
|
||||
pub const PRESETS: &[VideoPreset] = &[H90, H180, H216, H360, H540, H720, H1080, H1440, H2160];
|
||||
pub const DEFAULT_SIMULCAST_PRESETS: &[VideoPreset] = &[H180, H360];
|
||||
}
|
||||
|
||||
pub mod video43 {
|
||||
use super::VideoPreset;
|
||||
|
||||
pub const H120: VideoPreset = VideoPreset::new(160, 120, 80_000, 15.0);
|
||||
pub const H180: VideoPreset = VideoPreset::new(240, 180, 100_000, 15.0);
|
||||
pub const H240: VideoPreset = VideoPreset::new(320, 240, 150_000, 15.0);
|
||||
pub const H360: VideoPreset = VideoPreset::new(480, 360, 225_000, 20.0);
|
||||
pub const H480: VideoPreset = VideoPreset::new(640, 480, 300_000, 20.0);
|
||||
pub const H540: VideoPreset = VideoPreset::new(720, 540, 450_000, 25.0);
|
||||
pub const H720: VideoPreset = VideoPreset::new(960, 720, 1_500_000, 30.0);
|
||||
pub const H1080: VideoPreset = VideoPreset::new(1440, 1080, 2_500_000, 30.0);
|
||||
pub const H1440: VideoPreset = VideoPreset::new(1920, 1440, 3_500_000, 30.0);
|
||||
|
||||
pub const PRESETS: &[VideoPreset] = &[H120, H180, H240, H360, H480, H540, H720, H1080, H1440];
|
||||
pub const DEFAULT_SIMULCAST_PRESETS: &[VideoPreset] = &[H180, H360];
|
||||
}
|
||||
|
||||
pub mod screenshare {
|
||||
/// The screenshare presets are optimized for quality.
|
||||
/// When simulcasting, we prefer to reduce the FPS.
|
||||
use super::VideoPreset;
|
||||
|
||||
pub const H360_FPS3: VideoPreset = VideoPreset::new(640, 360, 200_000, 3.0);
|
||||
pub const H720_FPS5: VideoPreset = VideoPreset::new(1280, 720, 400_000, 5.0);
|
||||
pub const H720_FPS15: VideoPreset = VideoPreset::new(1280, 720, 1_000_000, 15.0);
|
||||
pub const H1080_FPS15: VideoPreset = VideoPreset::new(1920, 1080, 1_500_000, 15.0);
|
||||
pub const H1080_FPS30: VideoPreset = VideoPreset::new(1920, 1080, 3_000_000, 30.0);
|
||||
|
||||
pub const PRESETS: &[VideoPreset] =
|
||||
&[H360_FPS3, H720_FPS5, H720_FPS15, H1080_FPS15, H1080_FPS30];
|
||||
|
||||
/// Only one additional layer for screenshares. (Prioritize quality)
|
||||
pub fn compute_default_simulcast_preset(initial: &VideoPreset) -> VideoPreset {
|
||||
const SCALE_DOWN_FACTOR: u32 = 2;
|
||||
const FPS: f64 = 3.0;
|
||||
|
||||
VideoPreset::new(
|
||||
initial.width / SCALE_DOWN_FACTOR,
|
||||
initial.height / SCALE_DOWN_FACTOR,
|
||||
u64::max(
|
||||
150_000,
|
||||
initial.encoding.max_bitrate as u64
|
||||
/ (SCALE_DOWN_FACTOR.pow(2) as u64
|
||||
* (initial.encoding.max_framerate / FPS) as u64),
|
||||
),
|
||||
FPS,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,130 @@
|
||||
use super::{
|
||||
impl_participant_trait, ConnectionQuality, ParticipantInternalTrait, ParticipantShared,
|
||||
};
|
||||
use super::{ConnectionQuality, ParticipantInner};
|
||||
use crate::options::compute_video_encodings;
|
||||
use crate::options::video_layers_from_encodings;
|
||||
use crate::options::TrackPublishOptions;
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use crate::publication::TrackPublication;
|
||||
use crate::rtc_engine::RTCEngine;
|
||||
use crate::rtc_engine::RtcEngine;
|
||||
use parking_lot::RwLockReadGuard;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalParticipant {
|
||||
shared: ParticipantShared,
|
||||
rtc_engine: Arc<RTCEngine>,
|
||||
inner: Arc<ParticipantInner>,
|
||||
rtc_engine: Arc<RtcEngine>,
|
||||
}
|
||||
|
||||
impl LocalParticipant {
|
||||
pub(crate) fn new(
|
||||
rtc_engine: Arc<RTCEngine>,
|
||||
rtc_engine: Arc<RtcEngine>,
|
||||
sid: ParticipantSid,
|
||||
identity: ParticipantIdentity,
|
||||
name: String,
|
||||
metadata: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared: ParticipantShared::new(sid, identity, name, metadata),
|
||||
inner: Arc::new(ParticipantInner::new(sid, identity, name, metadata)),
|
||||
rtc_engine,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn publish_track(&self) {}
|
||||
pub async fn publish_track(
|
||||
&self,
|
||||
track: LocalTrack,
|
||||
options: TrackPublishOptions,
|
||||
) -> RoomResult<LocalTrackPublication> {
|
||||
let mut req = proto::AddTrackRequest {
|
||||
cid: track.rtc_track().id(),
|
||||
name: options.name.clone(),
|
||||
r#type: proto::TrackType::from(track.kind()) as i32,
|
||||
muted: track.muted(),
|
||||
source: proto::TrackSource::from(options.source) as i32,
|
||||
disable_dtx: !options.dtx,
|
||||
disable_red: !options.red,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut encodings = Vec::default();
|
||||
match &track {
|
||||
LocalTrack::Video(video_track) => {
|
||||
// Get the video dimension
|
||||
// TODO(theomonnom): Use MediaStreamTrack::getSettings() on web
|
||||
let capture_options = video_track.capture_options();
|
||||
req.width = capture_options.preset.width;
|
||||
req.height = capture_options.preset.height;
|
||||
|
||||
encodings = compute_video_encodings(req.width, req.height, &options);
|
||||
req.layers = video_layers_from_encodings(req.width, req.height, &encodings);
|
||||
}
|
||||
LocalTrack::Audio(_audio_track) => {}
|
||||
}
|
||||
|
||||
let track_info = self.rtc_engine.add_track(req).await?;
|
||||
let publication =
|
||||
LocalTrackPublication::new(track_info.clone(), track.clone(), options.clone());
|
||||
track.update_info(track_info); // Update SID + Source
|
||||
debug!("publishing track with cid {:?}", track.rtc_track().id());
|
||||
let transceiver = self
|
||||
.rtc_engine
|
||||
.create_sender(track.clone(), options, encodings)
|
||||
.await?;
|
||||
|
||||
track.update_transceiver(Some(transceiver));
|
||||
track.start();
|
||||
|
||||
tokio::spawn({
|
||||
let rtc_engine = self.rtc_engine.clone();
|
||||
async move {
|
||||
let _ = rtc_engine.negotiate_publisher().await;
|
||||
}
|
||||
});
|
||||
|
||||
self.inner
|
||||
.add_track_publication(TrackPublication::Local(publication.clone()));
|
||||
|
||||
self.inner
|
||||
.dispatcher
|
||||
.dispatch(&ParticipantEvent::LocalTrackPublished {
|
||||
publication: publication.clone(),
|
||||
});
|
||||
|
||||
Ok(publication)
|
||||
}
|
||||
|
||||
pub async fn unpublish_track(
|
||||
&self,
|
||||
track: TrackSid,
|
||||
stop_on_unpublish: bool,
|
||||
) -> RoomResult<LocalTrackPublication> {
|
||||
let mut tracks = self.inner.tracks.write();
|
||||
if let Some(TrackPublication::Local(publication)) = tracks.remove(&track) {
|
||||
let track = publication.track().unwrap();
|
||||
let sender = track.transceiver().unwrap().sender();
|
||||
self.rtc_engine.remove_track(sender).await?;
|
||||
track.update_transceiver(None);
|
||||
|
||||
self.inner
|
||||
.dispatcher
|
||||
.dispatch(&ParticipantEvent::LocalTrackUnpublished {
|
||||
publication: publication.clone(),
|
||||
});
|
||||
publication.update_track(None);
|
||||
|
||||
tokio::spawn({
|
||||
let rtc_engine = self.rtc_engine.clone();
|
||||
async move {
|
||||
let _ = rtc_engine.negotiate_publisher().await;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(publication)
|
||||
} else {
|
||||
Err(RoomError::Internal("track not found".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn publish_data(
|
||||
&self,
|
||||
@@ -52,24 +145,80 @@ impl LocalParticipant {
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParticipantInternalTrait for LocalParticipant {
|
||||
fn update_info(self: &Arc<Self>, info: proto::ParticipantInfo, _emit_events: bool) {
|
||||
self.shared.update_info(info);
|
||||
#[inline]
|
||||
pub fn get_track_publication(&self, sid: &TrackSid) -> Option<LocalTrackPublication> {
|
||||
self.inner.tracks.read().get(sid).map(|track| {
|
||||
if let TrackPublication::Local(local) = track {
|
||||
return local.clone();
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
})
|
||||
}
|
||||
|
||||
fn set_speaking(&self, speaking: bool) {
|
||||
self.shared.set_speaking(speaking);
|
||||
#[inline]
|
||||
pub fn sid(&self) -> ParticipantSid {
|
||||
self.inner.sid()
|
||||
}
|
||||
|
||||
fn set_audio_level(&self, level: f32) {
|
||||
self.shared.set_audio_level(level);
|
||||
#[inline]
|
||||
pub fn identity(&self) -> ParticipantIdentity {
|
||||
self.inner.identity()
|
||||
}
|
||||
|
||||
fn set_connection_quality(&self, quality: ConnectionQuality) {
|
||||
self.shared.set_connection_quality(quality);
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn metadata(&self) -> String {
|
||||
self.inner.metadata()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_speaking(&self) -> bool {
|
||||
self.inner.is_speaking()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tracks(&self) -> RwLockReadGuard<HashMap<TrackSid, TrackPublication>> {
|
||||
self.inner.tracks()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn audio_level(&self) -> f32 {
|
||||
self.inner.audio_level()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn connection_quality(&self) -> ConnectionQuality {
|
||||
self.inner.connection_quality()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<ParticipantEvent> {
|
||||
self.inner.register_observer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_info(self: &Self, info: proto::ParticipantInfo) {
|
||||
self.inner.update_info(info);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_speaking(&self, speaking: bool) {
|
||||
self.inner.set_speaking(speaking);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_audio_level(&self, level: f32) {
|
||||
self.inner.set_audio_level(level);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_connection_quality(&self, quality: ConnectionQuality) {
|
||||
self.inner.set_connection_quality(quality);
|
||||
}
|
||||
}
|
||||
|
||||
impl_participant_trait!(LocalParticipant);
|
||||
|
||||
@@ -24,11 +24,11 @@ pub enum ParticipantEvent {
|
||||
publication: RemoteTrackPublication,
|
||||
},
|
||||
TrackSubscribed {
|
||||
track: RemoteTrackHandle,
|
||||
track: RemoteTrack,
|
||||
publication: RemoteTrackPublication,
|
||||
},
|
||||
TrackUnsubscribed {
|
||||
track: RemoteTrackHandle,
|
||||
track: RemoteTrack,
|
||||
publication: RemoteTrackPublication,
|
||||
},
|
||||
TrackSubscriptionFailed {
|
||||
@@ -51,6 +51,12 @@ pub enum ParticipantEvent {
|
||||
ConnectionQualityChanged {
|
||||
quality: ConnectionQuality,
|
||||
},
|
||||
LocalTrackPublished {
|
||||
publication: LocalTrackPublication,
|
||||
},
|
||||
LocalTrackUnpublished {
|
||||
publication: LocalTrackPublication,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
@@ -83,21 +89,48 @@ impl From<proto::ConnectionQuality> for ConnectionQuality {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ParticipantShared {
|
||||
pub(super) sid: Mutex<ParticipantSid>,
|
||||
pub(super) identity: Mutex<ParticipantIdentity>,
|
||||
pub(super) name: Mutex<String>,
|
||||
pub(super) metadata: Mutex<String>,
|
||||
pub(super) tracks: RwLock<HashMap<TrackSid, TrackPublication>>,
|
||||
pub(super) speaking: AtomicBool,
|
||||
pub(super) audio_level: AtomicU32,
|
||||
pub(super) connection_quality: AtomicU8,
|
||||
pub(super) dispatcher: Mutex<Dispatcher<ParticipantEvent>>,
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Participant {
|
||||
Local(LocalParticipant),
|
||||
Remote(RemoteParticipant),
|
||||
}
|
||||
|
||||
impl ParticipantShared {
|
||||
pub(super) fn new(
|
||||
impl Participant {
|
||||
enum_dispatch!(
|
||||
[Local, Remote];
|
||||
pub fn sid(self: &Self) -> ParticipantSid;
|
||||
pub fn identity(self: &Self) -> ParticipantIdentity;
|
||||
pub fn name(self: &Self) -> String;
|
||||
pub fn metadata(self: &Self) -> String;
|
||||
pub fn is_speaking(self: &Self) -> bool;
|
||||
pub fn audio_level(self: &Self) -> f32;
|
||||
pub fn connection_quality(self: &Self) -> ConnectionQuality;
|
||||
pub fn tracks(self: &Self) -> RwLockReadGuard<HashMap<TrackSid, TrackPublication>>;
|
||||
pub fn register_observer(self: &Self) -> mpsc::UnboundedReceiver<ParticipantEvent>;
|
||||
|
||||
// Internal functions
|
||||
pub(crate) fn set_speaking(self: &Self, speaking: bool) -> ();
|
||||
pub(crate) fn set_audio_level(self: &Self, level: f32) -> ();
|
||||
pub(crate) fn set_connection_quality(self: &Self, quality: ConnectionQuality) -> ();
|
||||
pub(crate) fn update_info(self: &Self, info: proto::ParticipantInfo) -> ();
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParticipantInner {
|
||||
pub sid: Mutex<ParticipantSid>,
|
||||
pub identity: Mutex<ParticipantIdentity>,
|
||||
pub name: Mutex<String>,
|
||||
pub metadata: Mutex<String>,
|
||||
pub speaking: AtomicBool,
|
||||
pub tracks: RwLock<HashMap<TrackSid, TrackPublication>>,
|
||||
pub audio_level: AtomicU32,
|
||||
pub connection_quality: AtomicU8,
|
||||
pub dispatcher: Dispatcher<ParticipantEvent>,
|
||||
}
|
||||
|
||||
impl ParticipantInner {
|
||||
pub fn new(
|
||||
sid: ParticipantSid,
|
||||
identity: ParticipantIdentity,
|
||||
name: String,
|
||||
@@ -116,128 +149,64 @@ impl ParticipantShared {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_info(&self, info: proto::ParticipantInfo) {
|
||||
pub fn sid(&self) -> ParticipantSid {
|
||||
self.sid.lock().clone()
|
||||
}
|
||||
|
||||
pub fn identity(&self) -> ParticipantIdentity {
|
||||
self.identity.lock().clone()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.name.lock().clone()
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> String {
|
||||
self.metadata.lock().clone()
|
||||
}
|
||||
|
||||
pub fn is_speaking(&self) -> bool {
|
||||
self.speaking.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn tracks(&self) -> RwLockReadGuard<HashMap<TrackSid, TrackPublication>> {
|
||||
self.tracks.read()
|
||||
}
|
||||
|
||||
pub fn audio_level(&self) -> f32 {
|
||||
f32::from_bits(self.audio_level.load(Ordering::SeqCst))
|
||||
}
|
||||
|
||||
pub fn connection_quality(&self) -> ConnectionQuality {
|
||||
self.connection_quality.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<ParticipantEvent> {
|
||||
self.dispatcher.register()
|
||||
}
|
||||
|
||||
pub fn update_info(&self, info: proto::ParticipantInfo) {
|
||||
*self.sid.lock() = info.sid.into();
|
||||
*self.identity.lock() = info.identity.into();
|
||||
*self.name.lock() = info.name;
|
||||
*self.metadata.lock() = info.metadata; // TODO(theomonnom): callback MetadataChanged
|
||||
}
|
||||
|
||||
pub(crate) fn set_speaking(&self, speaking: bool) {
|
||||
pub fn set_speaking(&self, speaking: bool) {
|
||||
self.speaking.store(speaking, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub(crate) fn set_audio_level(&self, audio_level: f32) {
|
||||
pub fn set_audio_level(&self, audio_level: f32) {
|
||||
self.audio_level
|
||||
.store(audio_level.to_bits(), Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub(crate) fn register_observer(&self) -> mpsc::UnboundedReceiver<ParticipantEvent> {
|
||||
self.dispatcher.lock().register()
|
||||
}
|
||||
|
||||
pub(crate) fn set_connection_quality(&self, quality: ConnectionQuality) {
|
||||
pub fn set_connection_quality(&self, quality: ConnectionQuality) {
|
||||
self.connection_quality
|
||||
.store(quality as u8, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub(crate) fn add_track_publication(&self, publication: TrackPublication) {
|
||||
pub fn add_track_publication(&self, publication: TrackPublication) {
|
||||
self.tracks.write().insert(publication.sid(), publication);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ParticipantInternalTrait {
|
||||
fn set_speaking(&self, speaking: bool);
|
||||
fn set_audio_level(&self, level: f32);
|
||||
fn set_connection_quality(&self, quality: ConnectionQuality);
|
||||
fn update_info(self: &Arc<Self>, info: proto::ParticipantInfo, emit_events: bool);
|
||||
}
|
||||
|
||||
pub trait ParticipantTrait {
|
||||
fn sid(&self) -> ParticipantSid;
|
||||
fn identity(&self) -> ParticipantIdentity;
|
||||
fn name(&self) -> String;
|
||||
fn metadata(&self) -> String;
|
||||
fn is_speaking(&self) -> bool;
|
||||
fn audio_level(&self) -> f32;
|
||||
fn connection_quality(&self) -> ConnectionQuality;
|
||||
fn tracks(&self) -> RwLockReadGuard<HashMap<TrackSid, TrackPublication>>;
|
||||
fn register_observer(&self) -> mpsc::UnboundedReceiver<ParticipantEvent>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Participant {
|
||||
Local(Arc<LocalParticipant>),
|
||||
Remote(Arc<RemoteParticipant>),
|
||||
}
|
||||
|
||||
// TODO(theomonnom): Should I provide a WeakParticipant here ?
|
||||
|
||||
impl Participant {
|
||||
enum_dispatch!(
|
||||
[Local, Remote]
|
||||
fnc!(pub(crate), update_info, &Self, [info: proto::ParticipantInfo, emit_events: bool], ());
|
||||
fnc!(pub(crate), set_speaking, &Self, [speaking: bool], ());
|
||||
fnc!(pub(crate), set_audio_level, &Self, [audio_level: f32], ());
|
||||
fnc!(pub(crate), set_connection_quality, &Self, [quality: ConnectionQuality], ());
|
||||
);
|
||||
}
|
||||
|
||||
impl ParticipantTrait for Participant {
|
||||
enum_dispatch!(
|
||||
[Local, Remote]
|
||||
fnc!(sid, &Self, [], ParticipantSid);
|
||||
fnc!(identity, &Self, [], ParticipantIdentity);
|
||||
fnc!(name, &Self, [], String);
|
||||
fnc!(metadata, &Self, [], String);
|
||||
fnc!(is_speaking, &Self, [], bool);
|
||||
fnc!(audio_level, &Self, [], f32);
|
||||
fnc!(connection_quality, &Self, [], ConnectionQuality);
|
||||
fnc!(tracks, &Self, [], RwLockReadGuard<HashMap<TrackSid, TrackPublication>>);
|
||||
fnc!(register_observer, &Self, [], mpsc::UnboundedReceiver<ParticipantEvent>);
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! impl_participant_trait {
|
||||
($x:ty) => {
|
||||
impl crate::room::participant::ParticipantTrait for $x {
|
||||
fn sid(&self) -> ParticipantSid {
|
||||
self.shared.sid.lock().clone()
|
||||
}
|
||||
|
||||
fn identity(&self) -> ParticipantIdentity {
|
||||
self.shared.identity.lock().clone()
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
self.shared.name.lock().clone()
|
||||
}
|
||||
|
||||
fn metadata(&self) -> String {
|
||||
self.shared.metadata.lock().clone()
|
||||
}
|
||||
|
||||
fn is_speaking(&self) -> bool {
|
||||
self.shared.speaking.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn audio_level(&self) -> f32 {
|
||||
f32::from_bits(self.shared.audio_level.load(Ordering::SeqCst))
|
||||
}
|
||||
|
||||
fn connection_quality(&self) -> ConnectionQuality {
|
||||
self.shared.connection_quality.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
fn tracks(&self) -> RwLockReadGuard<HashMap<TrackSid, TrackPublication>> {
|
||||
self.shared.tracks.read()
|
||||
}
|
||||
|
||||
fn register_observer(&self) -> mpsc::UnboundedReceiver<ParticipantEvent> {
|
||||
self.shared.register_observer()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) use impl_participant_trait;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
use super::{
|
||||
impl_participant_trait, ConnectionQuality, ParticipantInternalTrait, ParticipantShared,
|
||||
};
|
||||
use super::{ConnectionQuality, ParticipantInner};
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use crate::publication::TrackPublicationInternalTrait;
|
||||
use crate::track::TrackError;
|
||||
use livekit_webrtc::prelude::*;
|
||||
use livekit_webrtc as rtc;
|
||||
use parking_lot::RwLockReadGuard;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::atomic::Ordering;
|
||||
use rtc::prelude::MediaStreamTrack;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -18,9 +14,9 @@ use tracing::{debug, error, instrument, Level};
|
||||
|
||||
const ADD_TRACK_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteParticipant {
|
||||
shared: ParticipantShared,
|
||||
inner: Arc<ParticipantInner>,
|
||||
}
|
||||
|
||||
impl RemoteParticipant {
|
||||
@@ -31,26 +27,25 @@ impl RemoteParticipant {
|
||||
metadata: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared: ParticipantShared::new(sid, identity, name, metadata),
|
||||
inner: Arc::new(ParticipantInner::new(sid, identity, name, metadata)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_track_publication(&self, sid: &TrackSid) -> Option<RemoteTrackPublication> {
|
||||
self.shared.tracks.read().get(sid).map(|track| {
|
||||
#[inline]
|
||||
pub fn get_track_publication(&self, sid: &TrackSid) -> Option<RemoteTrackPublication> {
|
||||
self.inner.tracks.read().get(sid).map(|track| {
|
||||
if let TrackPublication::Remote(remote) = track {
|
||||
remote.clone()
|
||||
} else {
|
||||
unreachable!()
|
||||
return remote.clone();
|
||||
}
|
||||
unreachable!()
|
||||
})
|
||||
}
|
||||
|
||||
/// Called by the RoomSession when receiving data by the RTCSession
|
||||
/// Called by the RoomSession when receiving data from the RrcSession
|
||||
/// It is just used to emit the Data event on the participant dispatcher.
|
||||
pub(crate) fn on_data_received(&self, data: Arc<Vec<u8>>, kind: proto::data_packet::Kind) {
|
||||
self.shared
|
||||
self.inner
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&ParticipantEvent::DataReceived {
|
||||
payload: data,
|
||||
kind,
|
||||
@@ -59,9 +54,9 @@ impl RemoteParticipant {
|
||||
|
||||
#[instrument(level = Level::DEBUG)]
|
||||
pub(crate) async fn add_subscribed_media_track(
|
||||
self: Arc<Self>,
|
||||
&self,
|
||||
sid: TrackSid,
|
||||
media_track: MediaStreamTrackHandle,
|
||||
media_track: rtc::media_stream::MediaStreamTrack,
|
||||
) {
|
||||
let wait_publication = {
|
||||
let participant = self.clone();
|
||||
@@ -73,7 +68,7 @@ impl RemoteParticipant {
|
||||
return publication;
|
||||
}
|
||||
|
||||
tokio::task::yield_now().await;
|
||||
tokio::task::yield_now().await; // Remove yield
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -81,25 +76,25 @@ impl RemoteParticipant {
|
||||
if let Ok(remote_publication) = timeout(ADD_TRACK_TIMEOUT, wait_publication).await {
|
||||
let track = match remote_publication.kind() {
|
||||
TrackKind::Audio => {
|
||||
if let MediaStreamTrackHandle::Audio(rtc_track) = media_track {
|
||||
if let MediaStreamTrack::Audio(rtc_track) = media_track {
|
||||
let audio_track = RemoteAudioTrack::new(
|
||||
remote_publication.sid().into(),
|
||||
remote_publication.name(),
|
||||
rtc_track,
|
||||
);
|
||||
RemoteTrackHandle::Audio(Arc::new(audio_track))
|
||||
RemoteTrack::Audio(audio_track)
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
TrackKind::Video => {
|
||||
if let MediaStreamTrackHandle::Video(rtc_track) = media_track {
|
||||
if let MediaStreamTrack::Video(rtc_track) = media_track {
|
||||
let video_track = RemoteVideoTrack::new(
|
||||
remote_publication.sid().into(),
|
||||
remote_publication.name(),
|
||||
rtc_track,
|
||||
);
|
||||
RemoteTrackHandle::Video(Arc::new(video_track))
|
||||
RemoteTrack::Video(video_track)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
@@ -110,23 +105,30 @@ impl RemoteParticipant {
|
||||
debug!("starting track: {:?}", sid);
|
||||
|
||||
remote_publication.update_track(Some(track.clone().into()));
|
||||
self.shared
|
||||
track.set_muted(remote_publication.muted());
|
||||
track.update_info(proto::TrackInfo {
|
||||
sid: remote_publication.sid().to_string(),
|
||||
name: remote_publication.name().to_string(),
|
||||
r#type: proto::TrackType::from(remote_publication.kind()) as i32,
|
||||
source: proto::TrackSource::from(remote_publication.source()) as i32,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
self.inner
|
||||
.add_track_publication(TrackPublication::Remote(remote_publication.clone()));
|
||||
track.start();
|
||||
|
||||
self.shared
|
||||
self.inner
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&ParticipantEvent::TrackSubscribed {
|
||||
track: track,
|
||||
track,
|
||||
publication: remote_publication,
|
||||
});
|
||||
} else {
|
||||
error!("could not find published track with sid: {:?}", sid);
|
||||
|
||||
self.shared
|
||||
self.inner
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&ParticipantEvent::TrackSubscriptionFailed {
|
||||
sid: sid.clone(),
|
||||
error: TrackError::TrackNotFound(sid.clone().to_string()),
|
||||
@@ -134,81 +136,118 @@ impl RemoteParticipant {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unpublish_track(self: &Arc<Self>, sid: &TrackSid, emit_events: bool) {
|
||||
pub(crate) fn unpublish_track(&self, sid: &TrackSid) {
|
||||
if let Some(publication) = self.get_track_publication(sid) {
|
||||
// Unsubscribe to the track if needed
|
||||
if let Some(track) = publication.track() {
|
||||
track.stop();
|
||||
|
||||
self.shared
|
||||
self.inner
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&ParticipantEvent::TrackUnsubscribed {
|
||||
track: track.clone(),
|
||||
publication: publication.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if emit_events {
|
||||
self.shared
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&ParticipantEvent::TrackUnpublished {
|
||||
publication: publication.clone(),
|
||||
});
|
||||
}
|
||||
self.inner
|
||||
.dispatcher
|
||||
.dispatch(&ParticipantEvent::TrackUnpublished {
|
||||
publication: publication.clone(),
|
||||
});
|
||||
|
||||
publication.update_track(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParticipantInternalTrait for RemoteParticipant {
|
||||
fn update_info(self: &Arc<Self>, info: proto::ParticipantInfo, emit_events: bool) {
|
||||
self.shared.update_info(info.clone());
|
||||
pub(crate) fn update_info(&self, info: proto::ParticipantInfo) {
|
||||
self.inner.update_info(info.clone());
|
||||
|
||||
let mut valid_tracks = HashSet::<TrackSid>::new();
|
||||
for track in info.tracks {
|
||||
if let Some(publication) = self.get_track_publication(&track.sid.clone().into()) {
|
||||
publication.update_info(track.clone());
|
||||
} else {
|
||||
let publication = RemoteTrackPublication::new(track.clone(), self.sid(), None);
|
||||
self.shared
|
||||
let publication = RemoteTrackPublication::new(track.clone(), None);
|
||||
self.inner
|
||||
.add_track_publication(TrackPublication::Remote(publication.clone()));
|
||||
|
||||
// This is a new track, dispatch publish event
|
||||
if emit_events {
|
||||
self.shared
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&ParticipantEvent::TrackPublished { publication });
|
||||
}
|
||||
self.inner
|
||||
.dispatcher
|
||||
.dispatch(&ParticipantEvent::TrackPublished { publication });
|
||||
}
|
||||
|
||||
valid_tracks.insert(track.sid.into());
|
||||
}
|
||||
|
||||
// remove tracks that are no longer valid
|
||||
for (sid, _) in self.shared.tracks.read().iter() {
|
||||
for (sid, _) in self.inner.tracks.read().iter() {
|
||||
if valid_tracks.contains(sid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.unpublish_track(sid, emit_events);
|
||||
self.unpublish_track(sid);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_speaking(&self, speaking: bool) {
|
||||
self.shared.set_speaking(speaking);
|
||||
#[inline]
|
||||
pub fn sid(&self) -> ParticipantSid {
|
||||
self.inner.sid()
|
||||
}
|
||||
|
||||
fn set_audio_level(&self, level: f32) {
|
||||
self.shared.set_audio_level(level);
|
||||
#[inline]
|
||||
pub fn identity(&self) -> ParticipantIdentity {
|
||||
self.inner.identity()
|
||||
}
|
||||
|
||||
fn set_connection_quality(&self, quality: ConnectionQuality) {
|
||||
self.shared.set_connection_quality(quality);
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn metadata(&self) -> String {
|
||||
self.inner.metadata()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_speaking(&self) -> bool {
|
||||
self.inner.is_speaking()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tracks(&self) -> RwLockReadGuard<HashMap<TrackSid, TrackPublication>> {
|
||||
self.inner.tracks()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn audio_level(&self) -> f32 {
|
||||
self.inner.audio_level()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn connection_quality(&self) -> ConnectionQuality {
|
||||
self.inner.connection_quality()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<ParticipantEvent> {
|
||||
self.inner.register_observer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_speaking(&self, speaking: bool) {
|
||||
self.inner.set_speaking(speaking);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_audio_level(&self, level: f32) {
|
||||
self.inner.set_audio_level(level);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_connection_quality(&self, quality: ConnectionQuality) {
|
||||
self.inner.set_connection_quality(quality);
|
||||
}
|
||||
}
|
||||
|
||||
impl_participant_trait!(RemoteParticipant);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
use super::TrackPublicationInner;
|
||||
use crate::id::TrackSid;
|
||||
use crate::options::TrackPublishOptions;
|
||||
use crate::proto;
|
||||
use crate::track::{LocalTrack, Track, TrackDimension, TrackKind, TrackSource};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LocalTrackPublicationInner {
|
||||
publication_inner: TrackPublicationInner,
|
||||
options: Mutex<TrackPublishOptions>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LocalTrackPublication {
|
||||
inner: Arc<LocalTrackPublicationInner>,
|
||||
}
|
||||
|
||||
impl LocalTrackPublication {
|
||||
pub(crate) fn new(
|
||||
info: proto::TrackInfo,
|
||||
track: LocalTrack,
|
||||
options: TrackPublishOptions,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(LocalTrackPublicationInner {
|
||||
publication_inner: TrackPublicationInner::new(info, Some(track.into())),
|
||||
options: Mutex::new(options),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.inner.publication_inner.sid()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.publication_inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.inner.publication_inner.kind()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.inner.publication_inner.source()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn simulcasted(&self) -> bool {
|
||||
self.inner.publication_inner.simulcasted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dimension(&self) -> TrackDimension {
|
||||
self.inner.publication_inner.dimension()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track(&self) -> Option<LocalTrack> {
|
||||
self.inner
|
||||
.publication_inner
|
||||
.track()
|
||||
.map(|track| track.try_into().unwrap())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mime_type(&self) -> String {
|
||||
self.inner.publication_inner.mime_type()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn muted(&self) -> bool {
|
||||
self.inner.publication_inner.muted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_track(&self, track: Option<Track>) {
|
||||
self.inner.publication_inner.update_track(track);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.inner.publication_inner.update_info(info);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,46 @@
|
||||
use super::track::{TrackDimension, TrackEvent};
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use crate::track::Track;
|
||||
use futures_util::stream::StreamExt;
|
||||
use livekit_utils::enum_dispatch;
|
||||
use livekit_utils::observer::Dispatcher;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::sync::Notify;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
use super::track::{TrackDimension, TrackEvent};
|
||||
mod local;
|
||||
pub use local::*;
|
||||
|
||||
pub(crate) trait TrackPublicationInternalTrait {
|
||||
fn update_track(&self, track: Option<TrackHandle>);
|
||||
fn update_info(&self, info: proto::TrackInfo);
|
||||
}
|
||||
|
||||
pub trait TrackPublicationTrait {
|
||||
fn name(&self) -> String;
|
||||
fn sid(&self) -> TrackSid;
|
||||
fn kind(&self) -> TrackKind;
|
||||
fn source(&self) -> TrackSource;
|
||||
fn simulcasted(&self) -> bool;
|
||||
fn dimension(&self) -> TrackDimension;
|
||||
fn mime_type(&self) -> String;
|
||||
fn muted(&self) -> bool;
|
||||
}
|
||||
mod remote;
|
||||
pub use remote::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct TrackPublicationShared {
|
||||
pub(super) track: Mutex<Option<TrackHandle>>,
|
||||
pub(super) name: Mutex<String>,
|
||||
pub(super) sid: Mutex<TrackSid>,
|
||||
pub(super) kind: AtomicU8, // Casted to TrackKind
|
||||
pub(super) source: AtomicU8, // Casted to TrackSource
|
||||
pub(super) simulcasted: AtomicBool,
|
||||
pub(super) dimension: Mutex<TrackDimension>,
|
||||
pub(super) mime_type: Mutex<String>,
|
||||
pub(super) muted: AtomicBool,
|
||||
pub(super) participant: ParticipantSid,
|
||||
pub(super) dispatcher: Mutex<Dispatcher<TrackEvent>>,
|
||||
pub(super) close_sender: Mutex<Option<oneshot::Sender<()>>>,
|
||||
pub(crate) struct TrackPublicationInner {
|
||||
track: Mutex<Option<Track>>,
|
||||
name: Mutex<String>,
|
||||
sid: Mutex<TrackSid>,
|
||||
kind: AtomicU8, // Casted to TrackKind
|
||||
source: AtomicU8, // Casted to TrackSource
|
||||
simulcasted: AtomicBool,
|
||||
dimension: Mutex<TrackDimension>,
|
||||
mime_type: Mutex<String>,
|
||||
muted: AtomicBool,
|
||||
dispatcher: Dispatcher<TrackEvent>,
|
||||
close_notifier: Arc<Notify>,
|
||||
}
|
||||
|
||||
impl TrackPublicationShared {
|
||||
pub fn new(
|
||||
info: proto::TrackInfo,
|
||||
participant: ParticipantSid,
|
||||
track: Option<TrackHandle>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
impl TrackPublicationInner {
|
||||
pub fn new(info: proto::TrackInfo, track: Option<Track>) -> Self {
|
||||
Self {
|
||||
track: Mutex::new(track),
|
||||
name: Mutex::new(info.name),
|
||||
sid: Mutex::new(info.sid.into()),
|
||||
kind: AtomicU8::new(
|
||||
TrackKind::from(proto::TrackType::from_i32(info.r#type).unwrap()) as u8,
|
||||
TrackKind::try_from(proto::TrackType::from_i32(info.r#type).unwrap()).unwrap()
|
||||
as u8,
|
||||
),
|
||||
source: AtomicU8::new(TrackSource::from(
|
||||
proto::TrackSource::from_i32(info.source).unwrap(),
|
||||
@@ -62,46 +50,32 @@ impl TrackPublicationShared {
|
||||
mime_type: Mutex::new(info.mime_type),
|
||||
muted: AtomicBool::new(info.muted),
|
||||
dispatcher: Default::default(),
|
||||
close_sender: Default::default(),
|
||||
participant,
|
||||
})
|
||||
close_notifier: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_track(self: &Arc<Self>, track: Option<TrackHandle>) {
|
||||
pub fn update_track(&self, track: Option<Track>) {
|
||||
let mut old_track = self.track.lock();
|
||||
|
||||
if let Some(close_sender) = self.close_sender.lock().take() {
|
||||
let _ = close_sender.send(());
|
||||
}
|
||||
|
||||
*old_track = track.clone();
|
||||
if let Some(track) = track {
|
||||
let (close_sender, close_receiver) = oneshot::channel();
|
||||
self.close_sender.lock().replace(close_sender);
|
||||
|
||||
let track_receiver = track.register_observer();
|
||||
tokio::spawn(
|
||||
self.clone()
|
||||
.publication_task(close_receiver, track_receiver),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.close_notifier.notify_waiters();
|
||||
|
||||
/// Task used to forward TrackHandle's events to the TrackPublications's dispatcher
|
||||
async fn publication_task(
|
||||
self: Arc<Self>,
|
||||
mut close_receiver: oneshot::Receiver<()>,
|
||||
mut track_receiver: mpsc::UnboundedReceiver<TrackEvent>,
|
||||
) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = track_receiver.recv() => {
|
||||
self.dispatcher.lock().dispatch(&event);
|
||||
if let Some(track) = track.as_ref() {
|
||||
let track_stream = UnboundedReceiverStream::new(track.register_observer());
|
||||
tokio::spawn({
|
||||
let dispatcher = self.dispatcher.clone();
|
||||
let notifier = self.close_notifier.clone();
|
||||
|
||||
async move {
|
||||
let notified = notifier.notified();
|
||||
futures_util::pin_mut!(notified);
|
||||
futures_util::future::select(
|
||||
track_stream.map(Ok).forward(dispatcher),
|
||||
notified,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
_ = &mut close_receiver => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +85,7 @@ impl TrackPublicationShared {
|
||||
*self.dimension.lock() = TrackDimension(info.width, info.height);
|
||||
*self.mime_type.lock() = info.mime_type;
|
||||
self.kind.store(
|
||||
TrackKind::from(proto::TrackType::from_i32(info.r#type).unwrap()) as u8,
|
||||
TrackKind::try_from(proto::TrackType::from_i32(info.r#type).unwrap()).unwrap() as u8,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
self.source.store(
|
||||
@@ -125,13 +99,41 @@ impl TrackPublicationShared {
|
||||
track.set_muted(info.muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TrackPublicationShared {
|
||||
fn drop(&mut self) {
|
||||
if let Some(close_sender) = self.close_sender.lock().take() {
|
||||
let _ = close_sender.send(());
|
||||
}
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.sid.lock().clone()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.name.lock().clone()
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.kind.load(Ordering::SeqCst).try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.source.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
pub fn simulcasted(&self) -> bool {
|
||||
self.simulcasted.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn dimension(&self) -> TrackDimension {
|
||||
self.dimension.lock().clone()
|
||||
}
|
||||
|
||||
pub fn mime_type(&self) -> String {
|
||||
self.mime_type.lock().clone()
|
||||
}
|
||||
|
||||
pub fn track(&self) -> Option<Track> {
|
||||
self.track.lock().clone()
|
||||
}
|
||||
|
||||
pub fn muted(&self) -> bool {
|
||||
self.muted.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,134 +144,22 @@ pub enum TrackPublication {
|
||||
}
|
||||
|
||||
impl TrackPublication {
|
||||
pub fn track(&self) -> Option<TrackHandle> {
|
||||
// Not calling Local/Remote function here, we don't need "cast"
|
||||
enum_dispatch!(
|
||||
[Local, Remote];
|
||||
pub fn sid(self: &Self) -> TrackSid;
|
||||
pub fn name(self: &Self) -> String;
|
||||
pub fn kind(self: &Self) -> TrackKind;
|
||||
pub fn source(self: &Self) -> TrackSource;
|
||||
pub fn simulcasted(self: &Self) -> bool;
|
||||
pub fn dimension(self: &Self) -> TrackDimension;
|
||||
pub fn mime_type(self: &Self) -> String;
|
||||
pub fn muted(self: &Self) -> bool;
|
||||
);
|
||||
|
||||
pub fn track(&self) -> Option<Track> {
|
||||
match self {
|
||||
TrackPublication::Local(p) => p.shared.track.lock().clone(),
|
||||
TrackPublication::Remote(p) => p.shared.track.lock().clone(),
|
||||
TrackPublication::Local(p) => p.track().map(Into::into),
|
||||
TrackPublication::Remote(p) => p.track().map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackPublicationInternalTrait for TrackPublication {
|
||||
enum_dispatch!(
|
||||
[Local, Remote]
|
||||
fnc!(update_track, &Self, [track: Option<TrackHandle>], ());
|
||||
fnc!(update_info, &Self, [info: proto::TrackInfo], ());
|
||||
);
|
||||
}
|
||||
|
||||
impl TrackPublicationTrait for TrackPublication {
|
||||
enum_dispatch!(
|
||||
[Local, Remote]
|
||||
fnc!(sid, &Self, [], TrackSid);
|
||||
fnc!(name, &Self, [], String);
|
||||
fnc!(kind, &Self, [], TrackKind);
|
||||
fnc!(source, &Self, [], TrackSource);
|
||||
fnc!(simulcasted, &Self, [], bool);
|
||||
fnc!(dimension, &Self, [], TrackDimension);
|
||||
fnc!(mime_type, &Self, [], String);
|
||||
fnc!(muted, &Self, [], bool);
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! impl_publication_trait {
|
||||
($x:ident) => {
|
||||
impl TrackPublicationTrait for $x {
|
||||
fn name(&self) -> String {
|
||||
self.shared.name.lock().clone()
|
||||
}
|
||||
|
||||
fn sid(&self) -> TrackSid {
|
||||
self.shared.sid.lock().clone()
|
||||
}
|
||||
|
||||
fn kind(&self) -> TrackKind {
|
||||
self.shared.kind.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
fn source(&self) -> TrackSource {
|
||||
self.shared.source.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
fn simulcasted(&self) -> bool {
|
||||
self.shared.simulcasted.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn dimension(&self) -> TrackDimension {
|
||||
self.shared.dimension.lock().clone()
|
||||
}
|
||||
|
||||
fn mime_type(&self) -> String {
|
||||
self.shared.mime_type.lock().clone()
|
||||
}
|
||||
|
||||
fn muted(&self) -> bool {
|
||||
self.shared.muted.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LocalTrackPublication {
|
||||
shared: Arc<TrackPublicationShared>,
|
||||
}
|
||||
|
||||
impl LocalTrackPublication {
|
||||
pub fn track(&self) -> Option<LocalTrackHandle> {
|
||||
self.shared
|
||||
.track
|
||||
.lock()
|
||||
.clone()
|
||||
.map(|local_track| local_track.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackPublicationInternalTrait for LocalTrackPublication {
|
||||
fn update_track(&self, track: Option<TrackHandle>) {
|
||||
self.shared.update_track(track);
|
||||
}
|
||||
|
||||
fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.shared.update_info(info);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteTrackPublication {
|
||||
shared: Arc<TrackPublicationShared>,
|
||||
}
|
||||
|
||||
impl RemoteTrackPublication {
|
||||
pub fn new(
|
||||
info: proto::TrackInfo,
|
||||
participant: ParticipantSid,
|
||||
track: Option<TrackHandle>,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared: TrackPublicationShared::new(info, participant, track),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track(&self) -> Option<RemoteTrackHandle> {
|
||||
self.shared
|
||||
.track
|
||||
.lock()
|
||||
.clone()
|
||||
.map(|track| track.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackPublicationInternalTrait for RemoteTrackPublication {
|
||||
fn update_track(&self, track: Option<TrackHandle>) {
|
||||
self.shared.update_track(track);
|
||||
}
|
||||
|
||||
fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.shared.update_info(info);
|
||||
}
|
||||
}
|
||||
|
||||
impl_publication_trait!(LocalTrackPublication);
|
||||
impl_publication_trait!(RemoteTrackPublication);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use super::TrackPublicationInner;
|
||||
use crate::id::TrackSid;
|
||||
use crate::proto;
|
||||
use crate::track::{RemoteTrack, Track, TrackDimension, TrackKind, TrackSource};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteTrackPublication {
|
||||
inner: Arc<TrackPublicationInner>,
|
||||
}
|
||||
|
||||
impl RemoteTrackPublication {
|
||||
pub(crate) fn new(info: proto::TrackInfo, track: Option<RemoteTrack>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(TrackPublicationInner::new(info, track.map(Into::into))),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.inner.sid()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.inner.kind()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.inner.source()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn simulcasted(&self) -> bool {
|
||||
self.inner.simulcasted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dimension(&self) -> TrackDimension {
|
||||
self.inner.dimension()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track(&self) -> Option<RemoteTrack> {
|
||||
self.inner.track().map(|track| track.try_into().unwrap())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mime_type(&self) -> String {
|
||||
self.inner.mime_type()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn muted(&self) -> bool {
|
||||
self.inner.muted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_track(&self, track: Option<Track>) {
|
||||
self.inner.update_track(track);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.inner.update_info(info);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::participant::{ConnectionQuality, ParticipantInternalTrait};
|
||||
use crate::participant::ConnectionQuality;
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use crate::rtc_engine::{EngineEvent, EngineEvents, EngineResult, RTCEngine};
|
||||
use crate::rtc_engine::{EngineEvent, EngineEvents, EngineResult, RtcEngine};
|
||||
use crate::signal_client::SignalOptions;
|
||||
use crate::{RoomError, RoomEvent, RoomResult, SimulateScenario};
|
||||
use livekit_utils::observer::Dispatcher;
|
||||
@@ -9,8 +9,7 @@ use parking_lot::{Mutex, RwLock, RwLockReadGuard};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{error, info, instrument, Level};
|
||||
|
||||
@@ -22,17 +21,6 @@ pub enum ConnectionState {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<u8> for ConnectionState {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
0 => ConnectionState::Disconnected,
|
||||
1 => ConnectionState::Connected,
|
||||
2 => ConnectionState::Reconnecting,
|
||||
_ => ConnectionState::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal representation of a RoomSession
|
||||
#[derive(Debug)]
|
||||
struct SessionInner {
|
||||
@@ -40,12 +28,12 @@ struct SessionInner {
|
||||
sid: Mutex<RoomSid>,
|
||||
name: Mutex<String>,
|
||||
metadata: Mutex<String>,
|
||||
participants: RwLock<HashMap<ParticipantSid, Arc<RemoteParticipant>>>,
|
||||
participants: RwLock<HashMap<ParticipantSid, RemoteParticipant>>,
|
||||
participants_tasks: RwLock<HashMap<ParticipantSid, (JoinHandle<()>, oneshot::Sender<()>)>>,
|
||||
active_speakers: RwLock<Vec<Participant>>,
|
||||
rtc_engine: Arc<RTCEngine>,
|
||||
local_participant: Arc<LocalParticipant>,
|
||||
dispatcher: Mutex<Dispatcher<RoomEvent>>,
|
||||
rtc_engine: Arc<RtcEngine>,
|
||||
local_participant: LocalParticipant,
|
||||
dispatcher: Dispatcher<RoomEvent>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -64,7 +52,7 @@ pub struct RoomSession {
|
||||
|
||||
impl SessionHandle {
|
||||
pub async fn connect(url: &str, token: &str) -> RoomResult<Self> {
|
||||
let (rtc_engine, engine_events) = RTCEngine::new();
|
||||
let (rtc_engine, engine_events) = RtcEngine::new();
|
||||
let rtc_engine = Arc::new(rtc_engine);
|
||||
rtc_engine
|
||||
.connect(url, token, SignalOptions::default())
|
||||
@@ -72,13 +60,13 @@ impl SessionHandle {
|
||||
|
||||
let join_response = rtc_engine.join_response().unwrap();
|
||||
let pi = join_response.participant.unwrap().clone();
|
||||
let local_participant = Arc::new(LocalParticipant::new(
|
||||
let local_participant = LocalParticipant::new(
|
||||
rtc_engine.clone(),
|
||||
pi.sid.into(),
|
||||
pi.identity.into(),
|
||||
pi.name,
|
||||
pi.metadata,
|
||||
));
|
||||
);
|
||||
|
||||
let room_info = join_response.room.unwrap();
|
||||
let inner = Arc::new(SessionInner {
|
||||
@@ -99,7 +87,7 @@ impl SessionHandle {
|
||||
let pi = pi.clone();
|
||||
inner.create_participant(pi.sid.into(), pi.identity.into(), pi.name, pi.metadata)
|
||||
};
|
||||
participant.update_info(pi.clone(), false);
|
||||
participant.update_info(pi.clone());
|
||||
}
|
||||
|
||||
let (close_emitter, close_receiver) = oneshot::channel();
|
||||
@@ -108,7 +96,7 @@ impl SessionHandle {
|
||||
inner.update_connection_state(ConnectionState::Connected);
|
||||
|
||||
let session = Self {
|
||||
session: RoomSession::from(inner),
|
||||
session: RoomSession { inner },
|
||||
session_task,
|
||||
close_emitter,
|
||||
};
|
||||
@@ -122,7 +110,7 @@ impl SessionHandle {
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> mpsc::UnboundedReceiver<RoomEvent> {
|
||||
self.session.inner.dispatcher.lock().register()
|
||||
self.session.inner.dispatcher.register()
|
||||
}
|
||||
|
||||
pub fn session(&self) -> RoomSession {
|
||||
@@ -131,10 +119,6 @@ impl SessionHandle {
|
||||
}
|
||||
|
||||
impl RoomSession {
|
||||
fn from(inner: Arc<SessionInner>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn sid(&self) -> RoomSid {
|
||||
self.inner.sid.lock().clone()
|
||||
}
|
||||
@@ -147,7 +131,7 @@ impl RoomSession {
|
||||
self.inner.metadata.lock().clone()
|
||||
}
|
||||
|
||||
pub fn local_participant(&self) -> Arc<LocalParticipant> {
|
||||
pub fn local_participant(&self) -> LocalParticipant {
|
||||
self.inner.local_participant.clone()
|
||||
}
|
||||
|
||||
@@ -155,7 +139,7 @@ impl RoomSession {
|
||||
self.inner.state.load(Ordering::Acquire).try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn participants(&self) -> RwLockReadGuard<HashMap<ParticipantSid, Arc<RemoteParticipant>>> {
|
||||
pub fn participants(&self) -> RwLockReadGuard<HashMap<ParticipantSid, RemoteParticipant>> {
|
||||
self.inner.participants.read()
|
||||
}
|
||||
|
||||
@@ -226,36 +210,30 @@ impl SessionInner {
|
||||
if let Participant::Remote(remote_participant) = participant {
|
||||
match event {
|
||||
ParticipantEvent::TrackPublished { publication } => {
|
||||
self.dispatcher.lock().dispatch(&RoomEvent::TrackPublished {
|
||||
self.dispatcher.dispatch(&RoomEvent::TrackPublished {
|
||||
participant: remote_participant.clone(),
|
||||
publication,
|
||||
});
|
||||
}
|
||||
ParticipantEvent::TrackUnpublished { publication } => {
|
||||
self.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::TrackUnpublished {
|
||||
participant: remote_participant.clone(),
|
||||
publication,
|
||||
});
|
||||
self.dispatcher.dispatch(&RoomEvent::TrackUnpublished {
|
||||
participant: remote_participant.clone(),
|
||||
publication,
|
||||
});
|
||||
}
|
||||
ParticipantEvent::TrackSubscribed { track, publication } => {
|
||||
self.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::TrackSubscribed {
|
||||
participant: remote_participant.clone(),
|
||||
track,
|
||||
publication,
|
||||
});
|
||||
self.dispatcher.dispatch(&RoomEvent::TrackSubscribed {
|
||||
participant: remote_participant.clone(),
|
||||
track,
|
||||
publication,
|
||||
});
|
||||
}
|
||||
ParticipantEvent::TrackUnsubscribed { track, publication } => {
|
||||
self.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::TrackUnsubscribed {
|
||||
participant: remote_participant.clone(),
|
||||
track,
|
||||
publication,
|
||||
});
|
||||
self.dispatcher.dispatch(&RoomEvent::TrackUnsubscribed {
|
||||
participant: remote_participant.clone(),
|
||||
track,
|
||||
publication,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -303,12 +281,12 @@ impl SessionInner {
|
||||
}
|
||||
EngineEvent::Resuming => {
|
||||
if self.update_connection_state(ConnectionState::Reconnecting) {
|
||||
self.dispatcher.lock().dispatch(&RoomEvent::Reconnecting);
|
||||
self.dispatcher.dispatch(&RoomEvent::Reconnecting);
|
||||
}
|
||||
}
|
||||
EngineEvent::Resumed => {
|
||||
self.update_connection_state(ConnectionState::Connected);
|
||||
self.dispatcher.lock().dispatch(&RoomEvent::Reconnected);
|
||||
self.dispatcher.dispatch(&RoomEvent::Reconnected);
|
||||
|
||||
// TODO(theomonnom): Update subscriptions settings
|
||||
// TODO(theomonnom): Send sync state
|
||||
@@ -323,7 +301,7 @@ impl SessionInner {
|
||||
} => {
|
||||
let payload = Arc::new(payload);
|
||||
if let Some(participant) = self.get_participant(&participant_sid.into()) {
|
||||
self.dispatcher.lock().dispatch(&RoomEvent::DataReceived {
|
||||
self.dispatcher.dispatch(&RoomEvent::DataReceived {
|
||||
payload: payload.clone(),
|
||||
kind,
|
||||
participant: participant.clone(),
|
||||
@@ -357,7 +335,6 @@ impl SessionInner {
|
||||
|
||||
self.state.store(state as u8, Ordering::Release);
|
||||
self.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::ConnectionStateChanged(state));
|
||||
return true;
|
||||
}
|
||||
@@ -368,11 +345,10 @@ impl SessionInner {
|
||||
#[instrument(level = Level::DEBUG)]
|
||||
fn handle_participant_update(self: &Arc<Self>, updates: Vec<proto::ParticipantInfo>) {
|
||||
for pi in updates {
|
||||
info!("test");
|
||||
if pi.sid == self.local_participant.sid()
|
||||
|| pi.identity == self.local_participant.identity()
|
||||
{
|
||||
self.local_participant.clone().update_info(pi, true);
|
||||
self.local_participant.clone().update_info(pi);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -386,7 +362,7 @@ impl SessionInner {
|
||||
.handle_participant_disconnect(remote_participant)
|
||||
} else {
|
||||
// Participant is already connected, update the it
|
||||
remote_participant.update_info(pi.clone(), true);
|
||||
remote_participant.update_info(pi.clone());
|
||||
}
|
||||
} else {
|
||||
// Create a new participant
|
||||
@@ -398,10 +374,9 @@ impl SessionInner {
|
||||
|
||||
let _ = self
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::ParticipantConnected(remote_participant.clone()));
|
||||
|
||||
remote_participant.update_info(pi.clone(), true);
|
||||
remote_participant.update_info(pi.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,7 +413,6 @@ impl SessionInner {
|
||||
|
||||
let _ = self
|
||||
.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::ActiveSpeakersChanged { speakers });
|
||||
}
|
||||
|
||||
@@ -466,7 +440,6 @@ impl SessionInner {
|
||||
|
||||
participant.set_connection_quality(quality);
|
||||
self.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::ConnectionQualityChanged {
|
||||
participant,
|
||||
quality,
|
||||
@@ -483,7 +456,7 @@ impl SessionInner {
|
||||
}
|
||||
|
||||
if self.update_connection_state(ConnectionState::Reconnecting) {
|
||||
self.dispatcher.lock().dispatch(&RoomEvent::Reconnecting);
|
||||
self.dispatcher.dispatch(&RoomEvent::Reconnecting);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,10 +466,10 @@ impl SessionInner {
|
||||
let join_response = self.rtc_engine.join_response().unwrap();
|
||||
|
||||
self.update_connection_state(ConnectionState::Connected);
|
||||
self.dispatcher.lock().dispatch(&RoomEvent::Reconnected);
|
||||
self.dispatcher.dispatch(&RoomEvent::Reconnected);
|
||||
|
||||
if let Some(pi) = join_response.participant {
|
||||
self.local_participant.update_info(pi, true); // The sid may have changed
|
||||
self.local_participant.update_info(pi); // The sid may have changed
|
||||
}
|
||||
|
||||
self.handle_participant_update(join_response.other_participants);
|
||||
@@ -511,7 +484,7 @@ impl SessionInner {
|
||||
}
|
||||
|
||||
self.update_connection_state(ConnectionState::Disconnected);
|
||||
self.dispatcher.lock().dispatch(&RoomEvent::Disconnected);
|
||||
self.dispatcher.dispatch(&RoomEvent::Disconnected);
|
||||
}
|
||||
|
||||
/// Create a new participant
|
||||
@@ -523,13 +496,8 @@ impl SessionInner {
|
||||
identity: ParticipantIdentity,
|
||||
name: String,
|
||||
metadata: String,
|
||||
) -> Arc<RemoteParticipant> {
|
||||
let participant = Arc::new(RemoteParticipant::new(
|
||||
sid.clone(),
|
||||
identity,
|
||||
name,
|
||||
metadata,
|
||||
));
|
||||
) -> RemoteParticipant {
|
||||
let participant = RemoteParticipant::new(sid.clone(), identity, name, metadata);
|
||||
|
||||
// Create the participant task
|
||||
let (close_tx, close_rx) = oneshot::channel();
|
||||
@@ -549,10 +517,10 @@ impl SessionInner {
|
||||
/// A participant has disconnected
|
||||
/// Cleanup the participant and emit an event
|
||||
#[instrument(level = Level::DEBUG)]
|
||||
fn handle_participant_disconnect(self: Arc<Self>, remote_participant: Arc<RemoteParticipant>) {
|
||||
fn handle_participant_disconnect(self: Arc<Self>, remote_participant: RemoteParticipant) {
|
||||
tokio::spawn(async move {
|
||||
for (sid, _) in &*remote_participant.tracks() {
|
||||
remote_participant.unpublish_track(&sid, true);
|
||||
remote_participant.unpublish_track(&sid);
|
||||
}
|
||||
|
||||
// Close the participant task
|
||||
@@ -566,16 +534,12 @@ impl SessionInner {
|
||||
}
|
||||
|
||||
self.participants.write().remove(&remote_participant.sid());
|
||||
|
||||
self.dispatcher
|
||||
.lock()
|
||||
.dispatch(&RoomEvent::ParticipantDisconnected(
|
||||
remote_participant.clone(),
|
||||
));
|
||||
.dispatch(&RoomEvent::ParticipantDisconnected(remote_participant));
|
||||
});
|
||||
}
|
||||
|
||||
fn get_participant(&self, sid: &ParticipantSid) -> Option<Arc<RemoteParticipant>> {
|
||||
fn get_participant(&self, sid: &ParticipantSid) -> Option<RemoteParticipant> {
|
||||
self.participants.read().get(sid).cloned()
|
||||
}
|
||||
}
|
||||
@@ -590,3 +554,14 @@ fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for ConnectionState {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
0 => ConnectionState::Disconnected,
|
||||
1 => ConnectionState::Connected,
|
||||
2 => ConnectionState::Reconnecting,
|
||||
_ => ConnectionState::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
use super::impl_track_trait;
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum AudioTrackHandle {
|
||||
Local(Arc<LocalAudioTrack>),
|
||||
Remote(Arc<RemoteAudioTrack>),
|
||||
}
|
||||
|
||||
impl From<AudioTrackHandle> for TrackHandle {
|
||||
fn from(audio_track: AudioTrackHandle) -> Self {
|
||||
match audio_track {
|
||||
AudioTrackHandle::Local(local_audio) => Self::LocalAudio(local_audio),
|
||||
AudioTrackHandle::Remote(remote_audio) => Self::RemoteAudio(remote_audio),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TrackHandle> for AudioTrackHandle {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: TrackHandle) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
TrackHandle::LocalAudio(local_audio) => Ok(Self::Local(local_audio)),
|
||||
TrackHandle::RemoteAudio(remote_audio) => Ok(Self::Remote(remote_audio)),
|
||||
_ => Err("not a audio track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_track_trait!(AudioTrackHandle, enum_dispatch, [Local, Remote]);
|
||||
@@ -1,8 +1,105 @@
|
||||
use super::{impl_track_trait, TrackShared};
|
||||
use super::TrackInner;
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use livekit_webrtc as rtc;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LocalAudioTrack {
|
||||
shared: TrackShared,
|
||||
pub(crate) inner: Arc<TrackInner>,
|
||||
}
|
||||
|
||||
impl_track_trait!(LocalAudioTrack);
|
||||
impl LocalAudioTrack {
|
||||
pub(crate) fn new(
|
||||
sid: TrackSid,
|
||||
name: String,
|
||||
rtc_track: rtc::media_stream::RtcAudioTrack,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(TrackInner::new(
|
||||
sid,
|
||||
name,
|
||||
TrackKind::Audio,
|
||||
rtc::media_stream::MediaStreamTrack::Audio(rtc_track),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.inner.sid()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.inner.kind()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.inner.source()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stream_state(&self) -> StreamState {
|
||||
self.inner.stream_state()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn start(&self) {
|
||||
self.inner.start()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stop(&self) {
|
||||
self.inner.stop()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn muted(&self) -> bool {
|
||||
self.inner.muted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_muted(&self, muted: bool) {
|
||||
self.inner.set_muted(muted)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::RtcAudioTrack {
|
||||
if let rtc::media_stream::MediaStreamTrack::Audio(audio) = self.inner.rtc_track() {
|
||||
audio
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent> {
|
||||
self.inner.register_observer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn transceiver(&self) -> Option<rtc::rtp_transceiver::RtpTransceiver> {
|
||||
self.inner.transceiver()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_transceiver(
|
||||
&self,
|
||||
transceiver: Option<rtc::rtp_transceiver::RtpTransceiver>,
|
||||
) {
|
||||
self.inner.update_transceiver(transceiver)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.inner.update_info(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
use super::impl_track_trait;
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LocalTrackHandle {
|
||||
Audio(Arc<LocalAudioTrack>),
|
||||
Video(Arc<LocalVideoTrack>),
|
||||
}
|
||||
|
||||
impl From<LocalTrackHandle> for TrackHandle {
|
||||
fn from(local_track: LocalTrackHandle) -> Self {
|
||||
match local_track {
|
||||
LocalTrackHandle::Audio(local_audio) => Self::LocalAudio(local_audio),
|
||||
LocalTrackHandle::Video(local_video) => Self::LocalVideo(local_video),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TrackHandle> for LocalTrackHandle {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: TrackHandle) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
TrackHandle::LocalAudio(local_audio) => Ok(Self::Audio(local_audio)),
|
||||
TrackHandle::LocalVideo(local_video) => Ok(Self::Video(local_video)),
|
||||
_ => Err("not a local track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_track_trait!(LocalTrackHandle, enum_dispatch, [Audio, Video]);
|
||||
@@ -1,8 +1,138 @@
|
||||
use super::{impl_track_trait, TrackShared};
|
||||
use super::TrackInner;
|
||||
use crate::proto;
|
||||
use crate::rtc_engine::lk_runtime::LkRuntime;
|
||||
use crate::{options::VideoCaptureOptions, prelude::*};
|
||||
use livekit_webrtc as rtc;
|
||||
use livekit_webrtc::peer_connection_factory::native::PeerConnectionFactoryExt;
|
||||
use parking_lot::Mutex;
|
||||
use rtc::video_source::native::NativeVideoSource;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalVideoTrack {
|
||||
shared: TrackShared,
|
||||
struct LocalVideoTrackInner {
|
||||
track_inner: TrackInner,
|
||||
capture_options: Mutex<VideoCaptureOptions>,
|
||||
}
|
||||
|
||||
impl_track_trait!(LocalVideoTrack);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LocalVideoTrack {
|
||||
inner: Arc<LocalVideoTrackInner>,
|
||||
}
|
||||
|
||||
impl LocalVideoTrack {
|
||||
pub fn new(
|
||||
name: String,
|
||||
rtc_track: rtc::media_stream::RtcVideoTrack,
|
||||
capture_options: VideoCaptureOptions,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(LocalVideoTrackInner {
|
||||
track_inner: TrackInner::new(
|
||||
"unknown".to_string().into(), // sid
|
||||
name,
|
||||
TrackKind::Video,
|
||||
rtc::media_stream::MediaStreamTrack::Video(rtc_track),
|
||||
),
|
||||
capture_options: Mutex::new(capture_options),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capture_options(&self) -> VideoCaptureOptions {
|
||||
self.inner.capture_options.lock().clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.inner.track_inner.sid()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.track_inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.inner.track_inner.kind()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.inner.track_inner.source()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stream_state(&self) -> StreamState {
|
||||
self.inner.track_inner.stream_state()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn start(&self) {
|
||||
self.inner.track_inner.start()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stop(&self) {
|
||||
self.inner.track_inner.stop()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn muted(&self) -> bool {
|
||||
self.inner.track_inner.muted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_muted(&self, muted: bool) {
|
||||
self.inner.track_inner.set_muted(muted)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::RtcVideoTrack {
|
||||
if let rtc::media_stream::MediaStreamTrack::Video(video) =
|
||||
self.inner.track_inner.rtc_track()
|
||||
{
|
||||
video
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent> {
|
||||
self.inner.track_inner.register_observer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transceiver(&self) -> Option<rtc::rtp_transceiver::RtpTransceiver> {
|
||||
self.inner.track_inner.transceiver()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_transceiver(
|
||||
&self,
|
||||
transceiver: Option<rtc::rtp_transceiver::RtpTransceiver>,
|
||||
) {
|
||||
self.inner.track_inner.update_transceiver(transceiver)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.inner.track_inner.update_info(info)
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalVideoTrack {
|
||||
pub fn create_video_track(
|
||||
name: &str,
|
||||
options: VideoCaptureOptions,
|
||||
source: NativeVideoSource,
|
||||
) -> LocalVideoTrack {
|
||||
let rtc_track = LkRuntime::instance()
|
||||
.pc_factory
|
||||
.create_video_track(&rtc::native::create_random_uuid(), source);
|
||||
|
||||
Self::new(name.to_string(), rtc_track, options)
|
||||
}
|
||||
}
|
||||
|
||||
+367
-237
@@ -2,29 +2,22 @@ use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use livekit_utils::enum_dispatch;
|
||||
use livekit_utils::observer::Dispatcher;
|
||||
use livekit_webrtc as rtc;
|
||||
use parking_lot::Mutex;
|
||||
use rtc::MediaType;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub mod audio_track;
|
||||
pub mod local_audio_track;
|
||||
pub mod local_track;
|
||||
pub mod local_video_track;
|
||||
pub mod remote_audio_track;
|
||||
pub mod remote_track;
|
||||
pub mod remote_video_track;
|
||||
pub mod video_track;
|
||||
|
||||
pub use audio_track::*;
|
||||
pub use local_audio_track::*;
|
||||
pub use local_track::*;
|
||||
pub use local_video_track::*;
|
||||
pub use remote_audio_track::*;
|
||||
pub use remote_track::*;
|
||||
pub use remote_video_track::*;
|
||||
pub use video_track::*;
|
||||
|
||||
#[derive(Error, Debug, Clone)]
|
||||
pub enum TrackError {
|
||||
@@ -32,51 +25,19 @@ pub enum TrackError {
|
||||
TrackNotFound(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrackKind {
|
||||
Unknown,
|
||||
Audio,
|
||||
Video,
|
||||
}
|
||||
|
||||
impl From<u8> for TrackKind {
|
||||
fn from(val: u8) -> Self {
|
||||
match val {
|
||||
1 => Self::Audio,
|
||||
2 => Self::Video,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<proto::TrackType> for TrackKind {
|
||||
fn from(r#type: proto::TrackType) -> Self {
|
||||
match r#type {
|
||||
proto::TrackType::Audio => Self::Audio,
|
||||
proto::TrackType::Video => Self::Video,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StreamState {
|
||||
Unknown,
|
||||
Active,
|
||||
Paused,
|
||||
}
|
||||
|
||||
impl From<u8> for StreamState {
|
||||
fn from(val: u8) -> Self {
|
||||
match val {
|
||||
1 => Self::Active,
|
||||
2 => Self::Paused,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrackSource {
|
||||
Unknown,
|
||||
Camera,
|
||||
@@ -85,9 +46,364 @@ pub enum TrackSource {
|
||||
ScreenshareAudio,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrackEvent {
|
||||
Mute,
|
||||
Unmute,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TrackDimension(pub u32, pub u32);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Track {
|
||||
LocalAudio(LocalAudioTrack),
|
||||
LocalVideo(LocalVideoTrack),
|
||||
RemoteAudio(RemoteAudioTrack),
|
||||
RemoteVideo(RemoteVideoTrack),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum LocalTrack {
|
||||
Audio(LocalAudioTrack),
|
||||
Video(LocalVideoTrack),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RemoteTrack {
|
||||
Audio(RemoteAudioTrack),
|
||||
Video(RemoteVideoTrack),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VideoTrack {
|
||||
Local(LocalVideoTrack),
|
||||
Remote(RemoteVideoTrack),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AudioTrack {
|
||||
Local(LocalAudioTrack),
|
||||
Remote(RemoteAudioTrack),
|
||||
}
|
||||
|
||||
macro_rules! track_dispatch {
|
||||
([$($variant:ident),+]) => {
|
||||
enum_dispatch!(
|
||||
[$($variant),+];
|
||||
pub fn sid(self: &Self) -> TrackSid;
|
||||
pub fn name(self: &Self) -> String;
|
||||
pub fn kind(self: &Self) -> TrackKind;
|
||||
pub fn source(self: &Self) -> TrackSource;
|
||||
pub fn stream_state(self: &Self) -> StreamState;
|
||||
pub fn start(self: &Self) -> ();
|
||||
pub fn stop(self: &Self) -> ();
|
||||
pub fn muted(self: &Self) -> bool;
|
||||
pub fn set_muted(self: &Self, muted: bool) -> ();
|
||||
pub fn register_observer(self: &Self) -> mpsc::UnboundedReceiver<TrackEvent>;
|
||||
|
||||
pub(crate) fn transceiver(self: &Self) -> Option<rtc::rtp_transceiver::RtpTransceiver>;
|
||||
pub(crate) fn update_transceiver(self: &Self, transceiver: Option<rtc::rtp_transceiver::RtpTransceiver>) -> ();
|
||||
pub(crate) fn update_info(self: &Self, info: proto::TrackInfo) -> ();
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
impl Track {
|
||||
track_dispatch!([LocalAudio, LocalVideo, RemoteAudio, RemoteVideo]);
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::MediaStreamTrack {
|
||||
match self {
|
||||
Self::LocalAudio(track) => track.rtc_track().into(),
|
||||
Self::LocalVideo(track) => track.rtc_track().into(),
|
||||
Self::RemoteAudio(track) => track.rtc_track().into(),
|
||||
Self::RemoteVideo(track) => track.rtc_track().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalTrack {
|
||||
track_dispatch!([Audio, Video]);
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::MediaStreamTrack {
|
||||
match self {
|
||||
Self::Audio(track) => track.rtc_track().into(),
|
||||
Self::Video(track) => track.rtc_track().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrack {
|
||||
track_dispatch!([Audio, Video]);
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::MediaStreamTrack {
|
||||
match self {
|
||||
Self::Audio(track) => track.rtc_track().into(),
|
||||
Self::Video(track) => track.rtc_track().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoTrack {
|
||||
track_dispatch!([Local, Remote]);
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::RtcVideoTrack {
|
||||
match self {
|
||||
Self::Local(track) => track.rtc_track(),
|
||||
Self::Remote(track) => track.rtc_track(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTrack {
|
||||
track_dispatch!([Local, Remote]);
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::RtcAudioTrack {
|
||||
match self {
|
||||
Self::Local(track) => track.rtc_track().into(),
|
||||
Self::Remote(track) => track.rtc_track().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TrackInner {
|
||||
pub sid: Mutex<TrackSid>,
|
||||
pub name: Mutex<String>,
|
||||
pub kind: AtomicU8, // TrackKind
|
||||
pub source: AtomicU8, // TrackSource
|
||||
pub stream_state: AtomicU8, // StreamState
|
||||
pub muted: AtomicBool,
|
||||
pub rtc_track: rtc::media_stream::MediaStreamTrack,
|
||||
pub transceiver: Mutex<Option<rtc::rtp_transceiver::RtpTransceiver>>,
|
||||
pub dispatcher: Dispatcher<TrackEvent>,
|
||||
}
|
||||
|
||||
impl TrackInner {
|
||||
pub fn new(
|
||||
sid: TrackSid,
|
||||
name: String,
|
||||
kind: TrackKind,
|
||||
rtc_track: rtc::media_stream::MediaStreamTrack,
|
||||
) -> Self {
|
||||
Self {
|
||||
sid: Mutex::new(sid),
|
||||
name: Mutex::new(name),
|
||||
kind: AtomicU8::new(kind as u8),
|
||||
source: AtomicU8::new(TrackSource::Unknown as u8),
|
||||
stream_state: AtomicU8::new(StreamState::Active as u8),
|
||||
muted: AtomicBool::new(false),
|
||||
rtc_track,
|
||||
transceiver: Default::default(),
|
||||
dispatcher: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.sid.lock().clone()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.name.lock().clone()
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.kind.load(Ordering::SeqCst).try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.source.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
pub fn stream_state(&self) -> StreamState {
|
||||
self.stream_state.load(Ordering::SeqCst).try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn muted(&self) -> bool {
|
||||
self.muted.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
self.rtc_track.set_enabled(true);
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
self.rtc_track.set_enabled(false);
|
||||
}
|
||||
|
||||
pub fn set_muted(&self, muted: bool) {
|
||||
if self
|
||||
.muted
|
||||
.compare_exchange(!muted, muted, Ordering::SeqCst, Ordering::SeqCst)
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !muted {
|
||||
self.start();
|
||||
} else {
|
||||
self.stop();
|
||||
}
|
||||
|
||||
let event = if muted {
|
||||
TrackEvent::Mute
|
||||
} else {
|
||||
TrackEvent::Unmute
|
||||
};
|
||||
|
||||
self.dispatcher.dispatch(&event);
|
||||
}
|
||||
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::MediaStreamTrack {
|
||||
self.rtc_track.clone()
|
||||
}
|
||||
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent> {
|
||||
self.dispatcher.register()
|
||||
}
|
||||
|
||||
pub fn transceiver(&self) -> Option<rtc::rtp_transceiver::RtpTransceiver> {
|
||||
self.transceiver.lock().clone()
|
||||
}
|
||||
|
||||
pub fn update_transceiver(&self, transceiver: Option<rtc::rtp_transceiver::RtpTransceiver>) {
|
||||
*self.transceiver.lock() = transceiver;
|
||||
}
|
||||
|
||||
pub fn update_info(&self, info: proto::TrackInfo) {
|
||||
*self.name.lock() = info.name;
|
||||
*self.sid.lock() = info.sid.into();
|
||||
self.kind.store(
|
||||
TrackKind::try_from(proto::TrackType::from_i32(info.r#type).unwrap()).unwrap() as u8,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
self.source.store(
|
||||
TrackSource::from(proto::TrackSource::from_i32(info.source).unwrap()) as u8,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
// Muted and StreamState are not handled separately (events)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RemoteTrack> for Track {
|
||||
fn from(track: RemoteTrack) -> Self {
|
||||
match track {
|
||||
RemoteTrack::Audio(track) => Self::RemoteAudio(track),
|
||||
RemoteTrack::Video(track) => Self::RemoteVideo(track),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalTrack> for Track {
|
||||
fn from(track: LocalTrack) -> Self {
|
||||
match track {
|
||||
LocalTrack::Audio(track) => Self::LocalAudio(track),
|
||||
LocalTrack::Video(track) => Self::LocalVideo(track),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VideoTrack> for Track {
|
||||
fn from(track: VideoTrack) -> Self {
|
||||
match track {
|
||||
VideoTrack::Local(track) => Self::LocalVideo(track),
|
||||
VideoTrack::Remote(track) => Self::RemoteVideo(track),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AudioTrack> for Track {
|
||||
fn from(track: AudioTrack) -> Self {
|
||||
match track {
|
||||
AudioTrack::Local(track) => Self::LocalAudio(track),
|
||||
AudioTrack::Remote(track) => Self::RemoteAudio(track),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Track> for RemoteTrack {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: Track) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
Track::RemoteAudio(track) => Ok(Self::Audio(track)),
|
||||
Track::RemoteVideo(track) => Ok(Self::Video(track)),
|
||||
_ => Err("not a remote track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Track> for LocalTrack {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: Track) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
Track::LocalAudio(track) => Ok(Self::Audio(track)),
|
||||
Track::LocalVideo(track) => Ok(Self::Video(track)),
|
||||
_ => Err("not a local track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Track> for VideoTrack {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: Track) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
Track::LocalVideo(track) => Ok(Self::Local(track)),
|
||||
Track::RemoteVideo(track) => Ok(Self::Remote(track)),
|
||||
_ => Err("not a video track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Track> for AudioTrack {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: Track) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
Track::LocalAudio(track) => Ok(Self::Local(track)),
|
||||
Track::RemoteAudio(track) => Ok(Self::Remote(track)),
|
||||
_ => Err("not an audio track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conversions from integers (Useful since we're using atomic values to represent our enums)
|
||||
|
||||
impl TryFrom<u8> for TrackKind {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(kind: u8) -> Result<Self, Self::Error> {
|
||||
match kind {
|
||||
0 => Ok(Self::Audio),
|
||||
1 => Ok(Self::Video),
|
||||
_ => Err("invalid track kind"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for StreamState {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(state: u8) -> Result<Self, Self::Error> {
|
||||
match state {
|
||||
0 => Ok(Self::Active),
|
||||
1 => Ok(Self::Paused),
|
||||
_ => Err("invalid stream state"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for TrackSource {
|
||||
fn from(val: u8) -> Self {
|
||||
match val {
|
||||
fn from(source: u8) -> Self {
|
||||
match source {
|
||||
1 => Self::Camera,
|
||||
2 => Self::Microphone,
|
||||
3 => Self::Screenshare,
|
||||
@@ -97,197 +413,11 @@ impl From<u8> for TrackSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<proto::TrackSource> for TrackSource {
|
||||
fn from(source: proto::TrackSource) -> Self {
|
||||
match source {
|
||||
proto::TrackSource::Camera => Self::Camera,
|
||||
proto::TrackSource::Microphone => Self::Microphone,
|
||||
proto::TrackSource::ScreenShare => Self::Screenshare,
|
||||
proto::TrackSource::ScreenShareAudio => Self::ScreenshareAudio,
|
||||
proto::TrackSource::Unknown => Self::Unknown,
|
||||
impl From<TrackKind> for MediaType {
|
||||
fn from(kind: TrackKind) -> Self {
|
||||
match kind {
|
||||
TrackKind::Audio => Self::Audio,
|
||||
TrackKind::Video => Self::Video,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TrackDimension(pub u32, pub u32);
|
||||
|
||||
pub trait TrackTrait {
|
||||
fn sid(&self) -> TrackSid;
|
||||
fn name(&self) -> String;
|
||||
fn kind(&self) -> TrackKind;
|
||||
fn stream_state(&self) -> StreamState;
|
||||
fn muted(&self) -> bool;
|
||||
fn start(&self);
|
||||
fn stop(&self);
|
||||
fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent>;
|
||||
fn set_muted(&self, muted: bool);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TrackEvent {
|
||||
Mute,
|
||||
Unmute,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct TrackShared {
|
||||
pub(super) sid: Mutex<TrackSid>,
|
||||
pub(super) name: Mutex<String>,
|
||||
pub(super) kind: AtomicU8, // TrackKind
|
||||
pub(super) stream_state: AtomicU8, // StreamState
|
||||
pub(super) muted: AtomicBool,
|
||||
pub(super) rtc_track: MediaStreamTrackHandle,
|
||||
pub(super) dispatcher: Mutex<Dispatcher<TrackEvent>>,
|
||||
}
|
||||
|
||||
impl TrackShared {
|
||||
pub(crate) fn new(
|
||||
sid: TrackSid,
|
||||
name: String,
|
||||
kind: TrackKind,
|
||||
rtc_track: MediaStreamTrackHandle,
|
||||
) -> Self {
|
||||
Self {
|
||||
sid: Mutex::new(sid),
|
||||
name: Mutex::new(name),
|
||||
kind: AtomicU8::new(kind as u8),
|
||||
stream_state: AtomicU8::new(StreamState::Active as u8),
|
||||
muted: AtomicBool::new(false),
|
||||
rtc_track,
|
||||
dispatcher: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(&self) {
|
||||
self.rtc_track.set_enabled(true);
|
||||
}
|
||||
|
||||
pub(crate) fn stop(&self) {
|
||||
self.rtc_track.set_enabled(false);
|
||||
}
|
||||
|
||||
pub(crate) fn set_muted(&self, muted: bool) {
|
||||
if self.muted.load(Ordering::SeqCst) == muted {
|
||||
return;
|
||||
}
|
||||
|
||||
self.muted.store(muted, Ordering::SeqCst);
|
||||
self.rtc_track.set_enabled(!muted);
|
||||
|
||||
self.dispatcher.lock().dispatch(if muted {
|
||||
&TrackEvent::Mute
|
||||
} else {
|
||||
&TrackEvent::Unmute
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent> {
|
||||
self.dispatcher.lock().register()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TrackHandle {
|
||||
LocalVideo(Arc<LocalVideoTrack>),
|
||||
LocalAudio(Arc<LocalAudioTrack>),
|
||||
RemoteVideo(Arc<RemoteVideoTrack>),
|
||||
RemoteAudio(Arc<RemoteAudioTrack>),
|
||||
}
|
||||
|
||||
impl TrackTrait for TrackHandle {
|
||||
enum_dispatch!(
|
||||
[LocalVideo, LocalAudio, RemoteVideo, RemoteAudio]
|
||||
fnc!(sid, &Self, [], TrackSid);
|
||||
fnc!(name, &Self, [], String);
|
||||
fnc!(kind, &Self, [], TrackKind);
|
||||
fnc!(stream_state, &Self, [], StreamState);
|
||||
fnc!(muted, &Self, [], bool);
|
||||
fnc!(start, &Self, [], ());
|
||||
fnc!(stop, &Self, [], ());
|
||||
fnc!(register_observer, &Self, [], mpsc::UnboundedReceiver<TrackEvent>);
|
||||
fnc!(set_muted, &Self, [muted: bool], ());
|
||||
);
|
||||
}
|
||||
|
||||
impl TrackHandle {
|
||||
pub fn rtc_track(&self) -> MediaStreamTrackHandle {
|
||||
match self {
|
||||
Self::RemoteVideo(remote_video) => {
|
||||
MediaStreamTrackHandle::Video(remote_video.rtc_track())
|
||||
}
|
||||
Self::RemoteAudio(remote_audio) => {
|
||||
MediaStreamTrackHandle::Audio(remote_audio.rtc_track())
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_track_trait {
|
||||
($x:ident) => {
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::sync::mpsc;
|
||||
use $crate::room::id::TrackSid;
|
||||
use $crate::room::track::{StreamState, TrackEvent, TrackKind, TrackTrait};
|
||||
|
||||
impl TrackTrait for $x {
|
||||
fn sid(&self) -> TrackSid {
|
||||
self.shared.sid.lock().clone()
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
self.shared.name.lock().clone()
|
||||
}
|
||||
|
||||
fn kind(&self) -> TrackKind {
|
||||
self.shared.kind.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
fn stream_state(&self) -> StreamState {
|
||||
self.shared.stream_state.load(Ordering::SeqCst).into()
|
||||
}
|
||||
|
||||
fn muted(&self) -> bool {
|
||||
self.shared.muted.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
self.shared.start();
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
self.shared.stop();
|
||||
}
|
||||
|
||||
fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent> {
|
||||
self.shared.register_observer()
|
||||
}
|
||||
|
||||
fn set_muted(&self, muted: bool) {
|
||||
self.shared.set_muted(muted);
|
||||
}
|
||||
}
|
||||
};
|
||||
($x:ident, enum_dispatch, [$($variant:ident),+]) => {
|
||||
use livekit_utils::enum_dispatch;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
impl TrackTrait for $x {
|
||||
enum_dispatch!(
|
||||
[$($variant),+]
|
||||
fnc!(sid, &Self, [], TrackSid);
|
||||
fnc!(name, &Self, [], String);
|
||||
fnc!(kind, &Self, [], TrackKind);
|
||||
fnc!(stream_state, &Self, [], StreamState);
|
||||
fnc!(muted, &Self, [], bool);
|
||||
fnc!(start, &Self, [], ());
|
||||
fnc!(stop, &Self, [], ());
|
||||
fnc!(register_observer, &Self, [], mpsc::UnboundedReceiver<TrackEvent>);
|
||||
fnc!(set_muted, &Self, [muted: bool], ());
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) use impl_track_trait;
|
||||
|
||||
@@ -1,31 +1,105 @@
|
||||
use super::{impl_track_trait, TrackShared};
|
||||
use super::TrackInner;
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use livekit_webrtc as rtc;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteAudioTrack {
|
||||
shared: TrackShared,
|
||||
pub(crate) inner: Arc<TrackInner>,
|
||||
}
|
||||
|
||||
impl RemoteAudioTrack {
|
||||
pub(crate) fn new(sid: TrackSid, name: String, track: Arc<AudioTrack>) -> Self {
|
||||
pub(crate) fn new(
|
||||
sid: TrackSid,
|
||||
name: String,
|
||||
rtc_track: rtc::media_stream::RtcAudioTrack,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared: TrackShared::new(
|
||||
inner: Arc::new(TrackInner::new(
|
||||
sid,
|
||||
name,
|
||||
TrackKind::Audio,
|
||||
MediaStreamTrackHandle::Audio(track),
|
||||
),
|
||||
rtc::media_stream::MediaStreamTrack::Audio(rtc_track),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rtc_track(&self) -> Arc<AudioTrack> {
|
||||
if let MediaStreamTrackHandle::Audio(audio) = &self.shared.rtc_track {
|
||||
audio.clone()
|
||||
#[inline]
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.inner.sid()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.inner.kind()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.inner.source()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stream_state(&self) -> StreamState {
|
||||
self.inner.stream_state()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn start(&self) {
|
||||
self.inner.start()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stop(&self) {
|
||||
self.inner.stop()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn muted(&self) -> bool {
|
||||
self.inner.muted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_muted(&self, muted: bool) {
|
||||
self.inner.set_muted(muted)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::RtcAudioTrack {
|
||||
if let rtc::media_stream::MediaStreamTrack::Audio(audio) = self.inner.rtc_track() {
|
||||
audio
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_track_trait!(RemoteAudioTrack);
|
||||
#[inline]
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent> {
|
||||
self.inner.register_observer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn transceiver(&self) -> Option<rtc::rtp_transceiver::RtpTransceiver> {
|
||||
self.inner.transceiver()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_transceiver(
|
||||
&self,
|
||||
transceiver: Option<rtc::rtp_transceiver::RtpTransceiver>,
|
||||
) {
|
||||
self.inner.update_transceiver(transceiver)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.inner.update_info(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
use super::impl_track_trait;
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RemoteTrackHandle {
|
||||
Audio(Arc<RemoteAudioTrack>),
|
||||
Video(Arc<RemoteVideoTrack>),
|
||||
}
|
||||
|
||||
impl From<RemoteTrackHandle> for TrackHandle {
|
||||
fn from(remote_track: RemoteTrackHandle) -> Self {
|
||||
match remote_track {
|
||||
RemoteTrackHandle::Audio(remote_audio) => Self::RemoteAudio(remote_audio),
|
||||
RemoteTrackHandle::Video(remote_video) => Self::RemoteVideo(remote_video),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TrackHandle> for RemoteTrackHandle {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: TrackHandle) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
TrackHandle::RemoteAudio(remote_audio) => Ok(Self::Audio(remote_audio)),
|
||||
TrackHandle::RemoteVideo(remote_video) => Ok(Self::Video(remote_video)),
|
||||
_ => Err("not a remote track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_track_trait!(RemoteTrackHandle, enum_dispatch, [Audio, Video]);
|
||||
@@ -1,31 +1,105 @@
|
||||
use super::{impl_track_trait, TrackShared};
|
||||
use super::TrackInner;
|
||||
use crate::prelude::*;
|
||||
use crate::proto;
|
||||
use livekit_webrtc as rtc;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteVideoTrack {
|
||||
shared: TrackShared,
|
||||
pub(crate) inner: Arc<TrackInner>,
|
||||
}
|
||||
|
||||
impl RemoteVideoTrack {
|
||||
pub(crate) fn new(sid: TrackSid, name: String, track: Arc<VideoTrack>) -> Self {
|
||||
pub(crate) fn new(
|
||||
sid: TrackSid,
|
||||
name: String,
|
||||
rtc_track: rtc::media_stream::RtcVideoTrack,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared: TrackShared::new(
|
||||
inner: Arc::new(TrackInner::new(
|
||||
sid,
|
||||
name,
|
||||
TrackKind::Video,
|
||||
MediaStreamTrackHandle::Video(track),
|
||||
),
|
||||
rtc::media_stream::MediaStreamTrack::Video(rtc_track),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rtc_track(&self) -> Arc<VideoTrack> {
|
||||
if let MediaStreamTrackHandle::Video(video) = &self.shared.rtc_track {
|
||||
video.clone()
|
||||
#[inline]
|
||||
pub fn sid(&self) -> TrackSid {
|
||||
self.inner.sid()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn kind(&self) -> TrackKind {
|
||||
self.inner.kind()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> TrackSource {
|
||||
self.inner.source()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stream_state(&self) -> StreamState {
|
||||
self.inner.stream_state()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn start(&self) {
|
||||
self.inner.start()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stop(&self) {
|
||||
self.inner.stop()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn muted(&self) -> bool {
|
||||
self.inner.muted()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_muted(&self, muted: bool) {
|
||||
self.inner.set_muted(muted)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rtc_track(&self) -> rtc::media_stream::RtcVideoTrack {
|
||||
if let rtc::media_stream::MediaStreamTrack::Video(video) = self.inner.rtc_track() {
|
||||
video
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_track_trait!(RemoteVideoTrack);
|
||||
#[inline]
|
||||
pub fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent> {
|
||||
self.inner.register_observer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn transceiver(&self) -> Option<rtc::rtp_transceiver::RtpTransceiver> {
|
||||
self.inner.transceiver()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_transceiver(
|
||||
&self,
|
||||
transceiver: Option<rtc::rtp_transceiver::RtpTransceiver>,
|
||||
) {
|
||||
self.inner.update_transceiver(transceiver)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn update_info(&self, info: proto::TrackInfo) {
|
||||
self.inner.update_info(info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
use super::impl_track_trait;
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum VideoTrackHandle {
|
||||
Local(Arc<LocalVideoTrack>),
|
||||
Remote(Arc<RemoteVideoTrack>),
|
||||
}
|
||||
|
||||
impl From<VideoTrackHandle> for TrackHandle {
|
||||
fn from(video_track: VideoTrackHandle) -> Self {
|
||||
match video_track {
|
||||
VideoTrackHandle::Local(local_video) => Self::LocalVideo(local_video),
|
||||
VideoTrackHandle::Remote(remote_video) => Self::RemoteVideo(remote_video),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TrackHandle> for VideoTrackHandle {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(track: TrackHandle) -> Result<Self, Self::Error> {
|
||||
match track {
|
||||
TrackHandle::LocalVideo(local_video) => Ok(Self::Local(local_video)),
|
||||
TrackHandle::RemoteVideo(remote_video) => Ok(Self::Remote(remote_video)),
|
||||
_ => Err("not a video track"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_track_trait!(VideoTrackHandle, enum_dispatch, [Local, Remote]);
|
||||
Reference in New Issue
Block a user