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:
Théo Monnom
2023-03-18 03:25:16 +01:00
committed by GitHub
parent 4443eae434
commit cad6d36201
123 changed files with 8539 additions and 4606 deletions
+2 -2
View File
@@ -11,9 +11,9 @@ livekit-webrtc = { path = "../livekit-webrtc", version = "0.1.1" }
livekit-utils = { path = "../livekit-utils", version = "0.1.1" }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
tokio-tungstenite = { version = "0.17.2", features = ["native-tls"] }
tokio-tungstenite = { version = "0.18", features = ["native-tls"] }
tokio = { version = "1", features = ["full"] }
futures = "0.3"
tokio-stream = "0.1"
parking_lot = { version = "0.12.1", features = ["send_guard"] }
url = "2.2.2"
futures-util = "0.3.23"
+2 -5
View File
@@ -1,12 +1,9 @@
extern crate core;
pub mod proto {
include!(concat!(env!("OUT_DIR"), "/livekit.rs"));
}
pub mod proto;
mod room;
mod rtc_engine;
mod signal_client;
mod room;
pub mod webrtc {
pub use livekit_webrtc::*;
+5 -24
View File
@@ -1,31 +1,12 @@
pub use crate::participant::{
LocalParticipant, Participant, ParticipantEvent, ParticipantTrait, RemoteParticipant,
};
pub use crate::participant::{LocalParticipant, Participant, ParticipantEvent, RemoteParticipant};
pub use crate::{ConnectionState, Room, RoomError, RoomEvent, RoomSession};
pub use crate::{ConnectionState, Room, RoomError, RoomEvent, RoomResult, RoomSession};
pub use crate::publication::{
LocalTrackPublication, RemoteTrackPublication, TrackPublication, TrackPublicationTrait,
};
pub use crate::publication::{LocalTrackPublication, RemoteTrackPublication, TrackPublication};
pub use crate::track::{
AudioTrackHandle, LocalAudioTrack, LocalTrackHandle, LocalVideoTrack, RemoteAudioTrack,
RemoteTrackHandle, RemoteVideoTrack, StreamState, TrackEvent, TrackHandle, TrackKind,
TrackSource, TrackTrait, VideoTrackHandle,
AudioTrack, LocalAudioTrack, LocalTrack, LocalVideoTrack, RemoteAudioTrack, RemoteTrack,
RemoteVideoTrack, StreamState, Track, TrackEvent, TrackKind, TrackSource, VideoTrack,
};
pub use crate::id::*;
pub use crate::webrtc::{
data_channel::DataChannel,
media_stream::{
AudioTrack, MediaStream, MediaStreamTrackHandle, MediaStreamTrackTrait,
OnConstraintsChangedHandler, OnDiscardedFrameHandler, OnFrameHandler, VideoTrack,
},
rtp_receiver::RtpReceiver,
rtp_transceiver::RtpTransceiver,
video_frame::{VideoFrame, VideoRotation},
video_frame_buffer::{
VideoFormatType, VideoFrameBuffer, VideoFrameBufferTrait, VideoFrameBufferType,
},
};
+49
View File
@@ -0,0 +1,49 @@
use crate::track;
include!(concat!(env!("OUT_DIR"), "/livekit.rs"));
// Conversions
impl TryFrom<TrackType> for track::TrackKind {
type Error = &'static str;
fn try_from(r#type: TrackType) -> Result<Self, Self::Error> {
match r#type {
TrackType::Audio => Ok(Self::Audio),
TrackType::Video => Ok(Self::Video),
TrackType::Data => Err("data tracks are not implemented yet"),
}
}
}
impl From<track::TrackKind> for TrackType {
fn from(kind: track::TrackKind) -> Self {
match kind {
track::TrackKind::Audio => Self::Audio,
track::TrackKind::Video => Self::Video,
}
}
}
impl From<TrackSource> for track::TrackSource {
fn from(source: TrackSource) -> Self {
match source {
TrackSource::Camera => Self::Camera,
TrackSource::Microphone => Self::Microphone,
TrackSource::ScreenShare => Self::Screenshare,
TrackSource::ScreenShareAudio => Self::ScreenshareAudio,
TrackSource::Unknown => Self::Unknown,
}
}
}
impl From<track::TrackSource> for TrackSource {
fn from(source: track::TrackSource) -> Self {
match source {
track::TrackSource::Camera => Self::Camera,
track::TrackSource::Microphone => Self::Microphone,
track::TrackSource::Screenshare => Self::ScreenShare,
track::TrackSource::ScreenshareAudio => Self::ScreenShareAudio,
track::TrackSource::Unknown => Self::Unknown,
}
}
}
+14 -10
View File
@@ -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,
+1
View File
@@ -0,0 +1 @@
+372
View File
@@ -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,
)
}
}
+173 -24
View File
@@ -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);
+89 -120
View File
@@ -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);
+91
View File
@@ -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);
}
}
+95 -205
View File
@@ -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);
+73
View File
@@ -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);
}
}
+57 -82
View File
@@ -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,
}
}
}
-32
View File
@@ -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]);
+101 -4
View File
@@ -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)
}
}
-32
View File
@@ -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]);
+134 -4
View File
@@ -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
View File
@@ -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;
+86 -12
View File
@@ -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)
}
}
-32
View File
@@ -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]);
+86 -12
View File
@@ -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);
}
}
-32
View File
@@ -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]);
+33 -20
View File
@@ -1,35 +1,48 @@
use livekit_webrtc::peer_connection_factory::PeerConnectionFactory;
use livekit_webrtc::webrtc::RTCRuntime;
use lazy_static::lazy_static;
use livekit_webrtc::prelude::*;
use parking_lot::Mutex;
use std::fmt::{Debug, Formatter};
use std::sync::{Arc, Weak};
use tracing::trace;
/// SAFETY: The order of initialization and deletion is important for LKRuntime.
/// See the C++ constructors & destructors of these fields
pub struct LKRuntime {
pub pc_factory: PeerConnectionFactory,
pub rtc_runtime: RTCRuntime,
lazy_static! {
static ref LK_RUNTIME: Mutex<Weak<LkRuntime>> = Mutex::new(Weak::new());
}
impl Debug for LKRuntime {
pub struct LkRuntime {
pub pc_factory: PeerConnectionFactory,
}
impl Debug for LkRuntime {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "LKRuntime")
f.debug_struct("LkRuntime").finish()
}
}
impl Default for LKRuntime {
fn default() -> Self {
trace!("LKRuntime::default()");
let rtc_runtime = RTCRuntime::new();
Self {
pc_factory: PeerConnectionFactory::new(rtc_runtime.clone()),
rtc_runtime,
impl LkRuntime {
pub fn instance() -> Arc<LkRuntime> {
let mut lk_runtime_ref = LK_RUNTIME.lock();
if let Some(lk_runtime) = lk_runtime_ref.upgrade() {
lk_runtime
} else {
let new_runtime = Arc::new(LkRuntime::default());
*lk_runtime_ref = Arc::downgrade(&new_runtime);
new_runtime
}
}
}
impl Drop for LKRuntime {
fn drop(&mut self) {
trace!("LKRuntime::drop()");
impl Default for LkRuntime {
fn default() -> Self {
trace!("LkRuntime::default()");
Self {
pc_factory: PeerConnectionFactory::default(),
}
}
}
impl Drop for LkRuntime {
fn drop(&mut self) {
trace!("LkRuntime::drop()");
}
}
+102 -62
View File
@@ -1,14 +1,11 @@
use crate::prelude::*;
use crate::options::TrackPublishOptions;
use crate::prelude::LocalTrack;
use crate::proto;
use crate::rtc_engine::lk_runtime::LKRuntime;
use crate::rtc_engine::rtc_session::{RTCSession, SessionEvent, SessionEvents, SessionInfo};
use crate::rtc_engine::lk_runtime::LkRuntime;
use crate::rtc_engine::rtc_session::{RtcSession, SessionEvent, SessionEvents, SessionInfo};
use crate::signal_client::{SignalError, SignalOptions};
use futures::future::BoxFuture;
use futures::FutureExt;
use lazy_static::lazy_static;
use livekit_webrtc::data_channel::DataSendError;
use livekit_webrtc::jsep::SdpParseError;
use livekit_webrtc::prelude::*;
use livekit_webrtc::session_description::SdpParseError;
use parking_lot::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak};
@@ -20,8 +17,8 @@ use tokio::task::JoinHandle;
use tokio::time::{interval, Interval};
use tracing::{error, info, warn};
mod lk_runtime;
mod pc_transport;
pub mod lk_runtime;
mod peer_transport;
mod rtc_events;
mod rtc_session;
@@ -46,13 +43,13 @@ pub enum EngineError {
#[error("signal failure: {0}")]
Signal(#[from] SignalError),
#[error("internal webrtc failure")]
Rtc(#[from] RTCError),
Rtc(#[from] RtcError),
#[error("failed to parse sdp")]
Parse(#[from] SdpParseError),
#[error("serde error")]
Serde(#[from] serde_json::Error),
#[error("failed to send data to the datachannel")]
Data(#[from] DataSendError),
Data(#[from] DataChannelError),
#[error("connection error: {0}")]
Connection(String),
#[error("decode error")]
@@ -67,7 +64,7 @@ pub enum EngineEvent {
updates: Vec<proto::ParticipantInfo>,
},
MediaTrack {
track: MediaStreamTrackHandle,
track: MediaStreamTrack,
stream: MediaStream,
receiver: RtpReceiver,
},
@@ -92,23 +89,19 @@ pub enum EngineEvent {
pub const RECONNECT_ATTEMPTS: u32 = 10;
pub const RECONNECT_INTERVAL: Duration = Duration::from_secs(5);
lazy_static! {
// Share one LKRuntime across all RTCEngine instances
static ref LK_RUNTIME: Mutex<Weak<LKRuntime>> = Mutex::new(Weak::new());
}
///
/// Represents a running RTCSession with the ability to close the session
/// and the engine_task
#[derive(Debug)]
struct EngineHandle {
session: RTCSession,
session: RtcSession,
engine_task: JoinHandle<()>,
close_sender: oneshot::Sender<()>,
}
#[derive(Debug)]
struct EngineInner {
lk_runtime: Arc<LKRuntime>,
lk_runtime: Arc<LkRuntime>,
session_info: Mutex<Option<SessionInfo>>, // Last/Current Sessioninfo
running_handle: AsyncRwLock<Option<EngineHandle>>,
opened: AtomicBool,
@@ -121,26 +114,15 @@ struct EngineInner {
}
#[derive(Debug)]
pub struct RTCEngine {
pub struct RtcEngine {
inner: Arc<EngineInner>,
}
impl RTCEngine {
impl RtcEngine {
pub fn new() -> (Self, EngineEvents) {
let lk_runtime = {
let mut lk_runtime_ref = LK_RUNTIME.lock();
if let Some(lk_runtime) = lk_runtime_ref.upgrade() {
lk_runtime
} else {
let new_runtime = Arc::new(LKRuntime::default());
*lk_runtime_ref = Arc::downgrade(&new_runtime);
new_runtime
}
};
let (engine_emitter, engine_events) = mpsc::channel(8);
let inner = Arc::new(EngineInner {
lk_runtime,
lk_runtime: LkRuntime::instance(),
session_info: Default::default(),
running_handle: Default::default(),
opened: Default::default(),
@@ -153,6 +135,10 @@ impl RTCEngine {
(Self { inner }, engine_events)
}
pub(crate) fn lk_runtime(&self) -> Arc<LkRuntime> {
self.inner.lk_runtime.clone()
}
#[tracing::instrument]
pub async fn connect(
&self,
@@ -200,6 +186,64 @@ impl RTCEngine {
Ok(())
}
pub async fn add_track(&self, req: proto::AddTrackRequest) -> EngineResult<proto::TrackInfo> {
self.inner.wait_reconnection().await?;
self.inner
.running_handle
.read()
.await
.as_ref()
.unwrap()
.session
.add_track(req)
.await
}
pub async fn remove_track(&self, sender: RtpSender) -> EngineResult<()> {
self.inner.wait_reconnection().await?;
self.inner
.running_handle
.read()
.await
.as_ref()
.unwrap()
.session
.remove_track(sender)
.await
}
pub async fn create_sender(
&self,
track: LocalTrack,
options: TrackPublishOptions,
encodings: Vec<RtpEncodingParameters>,
) -> EngineResult<RtpTransceiver> {
self.inner.wait_reconnection().await?;
self.inner
.running_handle
.read()
.await
.as_ref()
.unwrap()
.session
.create_sender(track, options, encodings)
.await
}
pub async fn negotiate_publisher(&self) -> EngineResult<()> {
// TODO(theomonnom): guard for reconnection
self.inner.wait_reconnection().await?;
self.inner
.running_handle
.read()
.await
.as_ref()
.unwrap()
.session
.negotiate_publisher()
.await
}
pub fn join_response(&self) -> Option<proto::JoinResponse> {
if let Some(info) = self.inner.session_info.lock().as_ref() {
Some(info.join_response.clone())
@@ -307,37 +351,33 @@ impl EngineInner {
Ok(())
}
fn connect<'a>(
self: &'a Arc<Self>,
url: &'a str,
token: &'a str,
async fn connect(
self: &Arc<Self>,
url: &str,
token: &str,
options: SignalOptions,
) -> BoxFuture<'a, EngineResult<()>> {
async {
let (session_emitter, session_events) = mpsc::unbounded_channel();
let session = RTCSession::connect(
url,
token,
options,
self.lk_runtime.clone(),
session_emitter,
)
.await?;
) -> EngineResult<()> {
let (session_emitter, session_events) = mpsc::unbounded_channel();
let session = RtcSession::connect(
url,
token,
options,
self.lk_runtime.clone(),
session_emitter,
)
.await?;
let (close_sender, close_receiver) = oneshot::channel();
let engine_task =
tokio::spawn(self.clone().engine_task(session_events, close_receiver));
*self.session_info.lock() = Some(session.info().clone());
*self.running_handle.write().await = Some(EngineHandle {
session,
engine_task,
close_sender,
});
let (close_sender, close_receiver) = oneshot::channel();
let engine_task = tokio::spawn(self.clone().engine_task(session_events, close_receiver));
*self.session_info.lock() = Some(session.info().clone());
*self.running_handle.write().await = Some(EngineHandle {
session,
engine_task,
close_sender,
});
self.opened.store(true, Ordering::SeqCst);
Ok(())
}
.boxed()
self.opened.store(true, Ordering::SeqCst);
Ok(())
}
async fn terminate_session(&self) {
@@ -362,7 +402,7 @@ impl EngineInner {
}
while self.reconnecting.load(Ordering::Acquire) {
tokio::task::yield_now().await;
tokio::task::yield_now().await; // TODO(theomonnom): Remove yield
}
if self.running_handle.read().await.is_none() {
@@ -1,35 +1,31 @@
use crate::proto;
use livekit_webrtc::prelude::*;
use std::fmt::{Debug, Formatter};
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use tracing::{event, Level};
use tracing::{debug, event, Level};
const NEGOTIATION_FREQUENCY: Duration = Duration::from_millis(150);
pub type OnOfferHandler = Box<
dyn (FnMut(SessionDescription) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>)
+ Send
+ Sync,
>;
pub type OnOfferCreated = Box<dyn FnMut(SessionDescription) + Send + Sync>;
pub struct PCTransport {
pub struct PeerTransport {
signal_target: proto::SignalTarget,
peer_connection: PeerConnection,
pending_candidates: Vec<IceCandidate>,
on_offer_handler: Option<OnOfferHandler>,
on_offer_handler: Option<OnOfferCreated>,
renegotiate: bool,
restarting_ice: bool,
}
impl Debug for PCTransport {
impl Debug for PeerTransport {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
f.write_str("PCTransport")
f.debug_struct("PeerTransport")
.field("target", &self.signal_target)
.finish()
}
}
impl PCTransport {
impl PeerTransport {
pub fn new(peer_connection: PeerConnection, signal_target: proto::SignalTarget) -> Self {
Self {
signal_target,
@@ -42,9 +38,10 @@ impl PCTransport {
}
pub fn is_connected(&self) -> bool {
self.peer_connection.ice_connection_state() == IceConnectionState::IceConnectionConnected
|| self.peer_connection.ice_connection_state()
== IceConnectionState::IceConnectionCompleted
matches!(
self.peer_connection.ice_connection_state(),
IceConnectionState::Connected | IceConnectionState::Completed
)
}
pub fn peer_connection(&mut self) -> &mut PeerConnection {
@@ -55,8 +52,8 @@ impl PCTransport {
self.signal_target.clone()
}
pub fn on_offer(&mut self, handler: OnOfferHandler) {
self.on_offer_handler = Some(handler);
pub fn on_offer(&mut self, handler: Option<OnOfferCreated>) {
self.on_offer_handler = handler;
}
pub fn prepare_ice_restart(&mut self) {
@@ -68,8 +65,8 @@ impl PCTransport {
}
#[tracing::instrument(level = Level::DEBUG)]
pub async fn add_ice_candidate(&mut self, ice_candidate: IceCandidate) -> Result<(), RTCError> {
if self.peer_connection.remote_description().is_some() && !self.restarting_ice {
pub async fn add_ice_candidate(&mut self, ice_candidate: IceCandidate) -> Result<(), RtcError> {
if self.peer_connection.current_remote_description().is_some() && !self.restarting_ice {
self.peer_connection
.add_ice_candidate(ice_candidate)
.await?;
@@ -85,7 +82,7 @@ impl PCTransport {
pub async fn set_remote_description(
&mut self,
remote_description: SessionDescription,
) -> Result<(), RTCError> {
) -> Result<(), RtcError> {
self.peer_connection
.set_remote_description(remote_description)
.await?;
@@ -97,31 +94,26 @@ impl PCTransport {
if self.renegotiate {
self.renegotiate = false;
self.create_and_send_offer(RTCOfferAnswerOptions::default())
.await?;
self.create_and_send_offer(OfferOptions::default()).await?;
}
Ok(())
}
#[tracing::instrument(level = Level::DEBUG)]
pub async fn negotiate(&mut self) -> Result<(), RTCError> {
pub async fn negotiate(&mut self) -> Result<(), RtcError> {
// TODO(theomonnom) Debounce here with NEGOTIATION_FREQUENCY
self.create_and_send_offer(RTCOfferAnswerOptions::default())
.await
self.create_and_send_offer(OfferOptions::default()).await
}
#[tracing::instrument(level = Level::DEBUG)]
pub async fn create_anwser(
&mut self,
offer: SessionDescription,
options: RTCOfferAnswerOptions,
) -> Result<SessionDescription, RTCError> {
options: AnswerOptions,
) -> Result<SessionDescription, RtcError> {
self.set_remote_description(offer).await?;
let answer = self
.peer_connection()
.create_answer(RTCOfferAnswerOptions::default())
.await?;
let answer = self.peer_connection().create_answer(options).await?;
self.peer_connection()
.set_local_description(answer.clone())
.await?;
@@ -130,10 +122,7 @@ impl PCTransport {
}
#[tracing::instrument(level = Level::DEBUG)]
pub async fn create_and_send_offer(
&mut self,
options: RTCOfferAnswerOptions,
) -> Result<(), RTCError> {
pub async fn create_and_send_offer(&mut self, options: OfferOptions) -> Result<(), RtcError> {
if self.on_offer_handler.is_none() {
return Ok(());
}
@@ -145,7 +134,8 @@ impl PCTransport {
if self.peer_connection.signaling_state() == SignalingState::HaveLocalOffer {
if options.ice_restart {
if let Some(remote_description) = self.peer_connection.remote_description() {
if let Some(remote_description) = self.peer_connection.current_remote_description()
{
self.peer_connection
.set_remote_description(remote_description)
.await?;
@@ -165,7 +155,7 @@ impl PCTransport {
self.peer_connection
.set_local_description(offer.clone())
.await?;
self.on_offer_handler.as_mut().unwrap()(offer).await;
self.on_offer_handler.as_mut().unwrap()(offer);
Ok(())
}
}
+62 -56
View File
@@ -1,20 +1,15 @@
use super::pc_transport::PCTransport;
use super::peer_transport::PeerTransport;
use crate::proto;
use crate::rtc_engine::pc_transport::OnOfferHandler;
use livekit_webrtc::data_channel::OnMessageHandler;
use livekit_webrtc::peer_connection::{
OnAddTrackHandler, OnConnectionChangeHandler, OnDataChannelHandler, OnIceCandidateErrorHandler,
OnIceCandidateHandler, PeerConnectionState,
};
use livekit_webrtc::prelude::*;
use crate::rtc_engine::peer_transport::OnOfferCreated;
use livekit_webrtc::{self as rtc, prelude::*};
use tokio::sync::mpsc;
use tracing::error;
use tracing::{debug, error};
pub type RTCEmitter = mpsc::UnboundedSender<RTCEvent>;
pub type RTCEvents = mpsc::UnboundedReceiver<RTCEvent>;
pub type RtcEmitter = mpsc::UnboundedSender<RtcEvent>;
pub type RtcEvents = mpsc::UnboundedReceiver<RtcEvent>;
#[derive(Debug)]
pub enum RTCEvent {
pub enum RtcEvent {
IceCandidate {
ice_candidate: IceCandidate,
target: proto::SignalTarget,
@@ -27,14 +22,16 @@ pub enum RTCEvent {
data_channel: DataChannel,
target: proto::SignalTarget,
},
// TODO (theomonnom): Move Offer to PCTransport
// TODO (theomonnom): Move Offer to PeerTransport
Offer {
offer: SessionDescription,
target: proto::SignalTarget,
},
AddTrack {
rtp_receiver: RtpReceiver,
Track {
receiver: RtpReceiver,
streams: Vec<MediaStream>,
track: MediaStreamTrack,
transceiver: RtpTransceiver,
target: proto::SignalTarget,
},
Data {
@@ -46,99 +43,108 @@ pub enum RTCEvent {
/// Handlers used to forward events to a channel
/// Every callback here is called on the signaling thread
fn on_connection_change(
fn on_connection_state_change(
target: proto::SignalTarget,
emitter: RTCEmitter,
) -> OnConnectionChangeHandler {
emitter: RtcEmitter,
) -> rtc::peer_connection::OnConnectionChange {
Box::new(move |state| {
let _ = emitter.send(RTCEvent::ConnectionChange { state, target });
let _ = emitter.send(RtcEvent::ConnectionChange { state, target });
})
}
fn on_ice_candidate(target: proto::SignalTarget, emitter: RTCEmitter) -> OnIceCandidateHandler {
fn on_ice_candidate(
target: proto::SignalTarget,
emitter: RtcEmitter,
) -> rtc::peer_connection::OnIceCandidate {
Box::new(move |ice_candidate| {
let _ = emitter.send(RTCEvent::IceCandidate {
let _ = emitter.send(RtcEvent::IceCandidate {
ice_candidate,
target,
});
})
}
fn on_offer(target: proto::SignalTarget, emitter: RTCEmitter) -> OnOfferHandler {
fn on_offer(target: proto::SignalTarget, emitter: RtcEmitter) -> OnOfferCreated {
Box::new(move |offer| {
let _ = emitter.send(RTCEvent::Offer { offer, target });
Box::pin(async {})
let _ = emitter.send(RtcEvent::Offer { offer, target });
})
}
fn on_data_channel(target: proto::SignalTarget, emitter: RTCEmitter) -> OnDataChannelHandler {
Box::new(move |mut data_channel| {
data_channel.on_message(on_message(emitter.clone()));
fn on_data_channel(
target: proto::SignalTarget,
emitter: RtcEmitter,
) -> rtc::peer_connection::OnDataChannel {
Box::new(move |data_channel| {
data_channel.on_message(Some(on_message(emitter.clone())));
let _ = emitter.send(RTCEvent::DataChannel {
let _ = emitter.send(RtcEvent::DataChannel {
data_channel,
target,
});
})
}
fn on_add_track(target: proto::SignalTarget, emitter: RTCEmitter) -> OnAddTrackHandler {
Box::new(move |rtp_receiver, streams| {
let _ = emitter.send(RTCEvent::AddTrack {
rtp_receiver,
streams,
fn on_track(target: proto::SignalTarget, emitter: RtcEmitter) -> rtc::peer_connection::OnTrack {
Box::new(move |event| {
let _ = emitter.send(RtcEvent::Track {
receiver: event.receiver,
streams: event.streams,
track: event.track,
transceiver: event.transceiver,
target,
});
})
}
fn on_ice_candidate_error(
target: proto::SignalTarget,
_emitter: RTCEmitter,
) -> OnIceCandidateErrorHandler {
Box::new(move |address, port, url, error_code, error_text| {
error!(
"ICE candidate error ({:?}): address: {} - port: {} - url: {} - error_code: {} - error_text: {}",
target, address, port, url, error_code, error_text
);
_target: proto::SignalTarget,
_emitter: RtcEmitter,
) -> rtc::peer_connection::OnIceCandidateError {
Box::new(move |ice_error| {
error!("{:?}", ice_error);
})
}
pub fn forward_pc_events(transport: &mut PCTransport, rtc_emitter: RTCEmitter) {
pub fn forward_pc_events(transport: &mut PeerTransport, rtc_emitter: RtcEmitter) {
let signal_target = transport.signal_target();
transport
.peer_connection()
.on_ice_candidate(on_ice_candidate(signal_target, rtc_emitter.clone()));
.on_ice_candidate(Some(on_ice_candidate(signal_target, rtc_emitter.clone())));
transport
.peer_connection()
.on_data_channel(on_data_channel(signal_target, rtc_emitter.clone()));
.on_data_channel(Some(on_data_channel(signal_target, rtc_emitter.clone())));
transport
.peer_connection()
.on_add_track(on_add_track(signal_target, rtc_emitter.clone()));
.on_track(Some(on_track(signal_target, rtc_emitter.clone())));
transport
.peer_connection()
.on_connection_change(on_connection_change(signal_target, rtc_emitter.clone()));
.on_connection_state_change(Some(on_connection_state_change(
signal_target,
rtc_emitter.clone(),
)));
transport
.peer_connection()
.on_ice_candidate_error(on_ice_candidate_error(signal_target, rtc_emitter.clone()));
.on_ice_candidate_error(Some(on_ice_candidate_error(
signal_target,
rtc_emitter.clone(),
)));
transport.on_offer(on_offer(transport.signal_target(), rtc_emitter.clone()));
transport.on_offer(Some(on_offer(signal_target, rtc_emitter.clone())));
}
fn on_message(emitter: RTCEmitter) -> OnMessageHandler {
Box::new(move |data, binary| {
let _ = emitter.send(RTCEvent::Data {
data: data.to_vec(),
binary,
fn on_message(emitter: RtcEmitter) -> rtc::data_channel::OnMessage {
Box::new(move |buffer| {
let _ = emitter.send(RtcEvent::Data {
data: buffer.data.to_vec(),
binary: buffer.binary,
});
})
}
pub fn forward_dc_events(dc: &mut DataChannel, rtc_emitter: RTCEmitter) {
dc.on_message(on_message(rtc_emitter.clone()));
pub fn forward_dc_events(dc: &mut DataChannel, rtc_emitter: RtcEmitter) {
dc.on_message(Some(on_message(rtc_emitter.clone())));
}
+221 -68
View File
@@ -1,23 +1,28 @@
use super::{rtc_events, EngineError, EngineResult, SimulateScenario};
use crate::rtc_engine::lk_runtime::LKRuntime;
use crate::rtc_engine::pc_transport::PCTransport;
use crate::rtc_engine::rtc_events::{RTCEvent, RTCEvents};
use crate::options::TrackPublishOptions;
use crate::rtc_engine::lk_runtime::LkRuntime;
use crate::rtc_engine::peer_transport::PeerTransport;
use crate::rtc_engine::rtc_events::{RtcEvent, RtcEvents};
use crate::signal_client::{SignalClient, SignalEvent, SignalEvents, SignalOptions};
use crate::track::LocalTrack;
use crate::{proto, signal_client};
use livekit_webrtc::prelude::*;
use parking_lot::Mutex;
use prost::Message;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::convert::TryInto;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{mpsc, watch, Mutex as AsyncMutex};
use tokio::sync::Mutex as AsyncMutex;
use tokio::sync::{mpsc, oneshot, watch};
use tokio::task::JoinHandle;
use tokio::time::sleep;
use tracing::{debug, error, trace, warn};
pub const MAX_ICE_CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
pub const ICE_CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
pub const TRACK_PUBLISH_TIMEOUT: Duration = Duration::from_secs(10);
pub const LOSSY_DC_LABEL: &str = "_lossy";
pub const RELIABLE_DC_LABEL: &str = "_reliable";
@@ -35,7 +40,7 @@ pub enum SessionEvent {
kind: proto::data_packet::Kind,
},
MediaTrack {
track: MediaStreamTrackHandle,
track: MediaStreamTrack,
stream: MediaStream,
receiver: RtpReceiver,
},
@@ -56,8 +61,8 @@ pub enum SessionEvent {
Connected,
}
#[repr(u8)]
pub enum PCState {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeerState {
New,
Connected,
Disconnected,
@@ -65,27 +70,27 @@ pub enum PCState {
Closed,
}
impl TryInto<PCState> for u8 {
impl TryFrom<u8> for PeerState {
type Error = &'static str;
fn try_into(self) -> Result<PCState, Self::Error> {
match self {
0 => Ok(PCState::New),
1 => Ok(PCState::Connected),
2 => Ok(PCState::Disconnected),
3 => Ok(PCState::Reconnecting),
4 => Ok(PCState::Closed),
_ => Err("invalid PCState"),
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
0 => Ok(Self::New),
1 => Ok(Self::Connected),
2 => Ok(Self::Disconnected),
3 => Ok(Self::Reconnecting),
4 => Ok(Self::Closed),
_ => Err("invalid PeerState"),
}
}
}
#[derive(Serialize, Deserialize)]
#[allow(non_snake_case)]
struct IceCandidateJSON {
sdpMid: String,
sdpMLineIndex: i32,
candidate: String,
#[serde(rename_all = "camelCase")]
struct IceCandidateJson {
pub sdp_mid: String,
pub sdp_m_line_index: i32,
pub candidate: String,
}
#[derive(Debug, Clone, Default)]
@@ -104,8 +109,10 @@ struct SessionInner {
pc_state: AtomicU8, // PCState
has_published: AtomicBool,
publisher_pc: AsyncMutex<PCTransport>,
subscriber_pc: AsyncMutex<PCTransport>,
publisher_pc: AsyncMutex<PeerTransport>,
subscriber_pc: AsyncMutex<PeerTransport>,
pending_tracks: Mutex<HashMap<String, oneshot::Sender<proto::TrackInfo>>>,
// Publisher data channels
// used to send data to other participants ( The SFU forwards the messages )
@@ -124,20 +131,21 @@ struct SessionInner {
///
/// RTCSession is also responsable for the signaling and the negotation
#[derive(Debug)]
pub struct RTCSession {
lk_runtime: Arc<LKRuntime>,
pub struct RtcSession {
#[allow(dead_code)]
lk_runtime: Arc<LkRuntime>,
inner: Arc<SessionInner>,
close_tx: watch::Sender<bool>, // false = is_running
signal_task: JoinHandle<()>,
rtc_task: JoinHandle<()>,
}
impl RTCSession {
impl RtcSession {
pub async fn connect(
url: &str,
token: &str,
options: SignalOptions,
lk_runtime: Arc<LKRuntime>,
lk_runtime: Arc<LkRuntime>,
session_emitter: SessionEmitter,
) -> EngineResult<Self> {
// Connect to the SignalClient
@@ -148,16 +156,16 @@ impl RTCSession {
debug!("received JoinResponse: {:?}", join_response);
let (rtc_emitter, rtc_events) = mpsc::unbounded_channel();
let rtc_config = RTCConfiguration::from(join_response.clone());
let rtc_config = RtcConfiguration::from(join_response.clone());
let mut publisher_pc = PCTransport::new(
let mut publisher_pc = PeerTransport::new(
lk_runtime
.pc_factory
.create_peer_connection(rtc_config.clone())?,
proto::SignalTarget::Publisher,
);
let mut subscriber_pc = PCTransport::new(
let mut subscriber_pc = PeerTransport::new(
lk_runtime
.pc_factory
.create_peer_connection(rtc_config.clone())?,
@@ -181,7 +189,7 @@ impl RTCSession {
},
)?;
// Forward events received in the Signaling Thread to our rtc channel
// Forward events received inside the signaling thread to our rtc channel
rtc_events::forward_pc_events(&mut publisher_pc, rtc_emitter.clone());
rtc_events::forward_pc_events(&mut subscriber_pc, rtc_emitter.clone());
rtc_events::forward_dc_events(&mut lossy_dc, rtc_emitter.clone());
@@ -197,11 +205,12 @@ impl RTCSession {
let (close_tx, close_rx) = watch::channel(false);
let inner = Arc::new(SessionInner {
info: session_info,
pc_state: AtomicU8::new(PCState::New as u8),
pc_state: AtomicU8::new(PeerState::New as u8),
has_published: Default::default(),
signal_client,
publisher_pc: AsyncMutex::new(publisher_pc),
subscriber_pc: AsyncMutex::new(subscriber_pc),
pending_tracks: Default::default(),
lossy_dc,
reliable_dc,
subscriber_dc: Default::default(),
@@ -228,6 +237,31 @@ impl RTCSession {
Ok(session)
}
#[inline]
pub async fn add_track(&self, req: proto::AddTrackRequest) -> EngineResult<proto::TrackInfo> {
self.inner.add_track(req).await
}
#[inline]
pub async fn remove_track(&self, sender: RtpSender) -> EngineResult<()> {
self.inner.remove_track(sender).await
}
#[inline]
pub async fn create_sender(
&self,
track: LocalTrack,
options: TrackPublishOptions,
encodings: Vec<RtpEncodingParameters>,
) -> EngineResult<RtpTransceiver> {
self.inner.create_sender(track, options, encodings).await
}
#[inline]
pub async fn negotiate_publisher(&self) -> EngineResult<()> {
self.inner.negotiate_publisher().await
}
/// Close the PeerConnections and the SignalClient
#[tracing::instrument]
pub async fn close(self) {
@@ -238,6 +272,7 @@ impl RTCSession {
let _ = self.signal_task.await;
}
#[inline]
pub async fn publish_data(
&self,
data: &proto::DataPacket,
@@ -246,25 +281,28 @@ impl RTCSession {
self.inner.publish_data(data, kind).await
}
#[inline]
pub async fn restart(&self) -> EngineResult<()> {
self.inner.restart_session().await
}
#[inline]
pub async fn wait_pc_connection(&self) -> EngineResult<()> {
self.inner.wait_pc_connection().await
}
#[inline]
pub async fn simulate_scenario(&self, scenario: SimulateScenario) {
self.inner.simulate_scenario(scenario).await
}
}
impl RTCSession {
#[inline]
pub fn info(&self) -> &SessionInfo {
&self.inner.info
}
pub fn state(&self) -> PCState {
#[inline]
pub fn state(&self) -> PeerState {
self.inner
.pc_state
.load(Ordering::SeqCst)
@@ -272,18 +310,22 @@ impl RTCSession {
.unwrap()
}
pub fn publisher(&self) -> &AsyncMutex<PCTransport> {
#[inline]
pub fn publisher(&self) -> &AsyncMutex<PeerTransport> {
&self.inner.publisher_pc
}
pub fn subscriber(&self) -> &AsyncMutex<PCTransport> {
#[inline]
pub fn subscriber(&self) -> &AsyncMutex<PeerTransport> {
&self.inner.subscriber_pc
}
#[inline]
pub fn signal_client(&self) -> &Arc<SignalClient> {
&self.inner.signal_client
}
#[inline]
pub fn data_channel(&self, kind: proto::data_packet::Kind) -> &DataChannel {
&self.inner.data_channel(kind)
}
@@ -292,7 +334,7 @@ impl RTCSession {
impl SessionInner {
async fn rtc_task(
self: Arc<Self>,
mut rtc_events: RTCEvents,
mut rtc_events: RtcEvents,
mut close_receiver: watch::Receiver<bool>,
) {
loop {
@@ -355,7 +397,8 @@ impl SessionInner {
match event {
proto::signal_response::Message::Answer(answer) => {
trace!("received publisher answer: {:?}", answer);
let answer = SessionDescription::from(answer.r#type.parse().unwrap(), &answer.sdp)?;
let answer =
SessionDescription::parse(&answer.sdp, answer.r#type.parse().unwrap())?;
self.publisher_pc
.lock()
.await
@@ -364,12 +407,12 @@ impl SessionInner {
}
proto::signal_response::Message::Offer(offer) => {
trace!("received subscriber offer: {:?}", offer);
let offer = SessionDescription::from(offer.r#type.parse().unwrap(), &offer.sdp)?;
let offer = SessionDescription::parse(&offer.sdp, offer.r#type.parse().unwrap())?;
let answer = self
.subscriber_pc
.lock()
.await
.create_anwser(offer, RTCOfferAnswerOptions::default())
.create_anwser(offer, AnswerOptions::default())
.await?;
self.signal_client
@@ -384,11 +427,11 @@ impl SessionInner {
proto::signal_response::Message::Trickle(trickle) => {
let target = proto::SignalTarget::from_i32(trickle.target).unwrap();
let ice_candidate = {
let json = serde_json::from_str::<IceCandidateJSON>(&trickle.candidate_init)?;
IceCandidate::from(&json.sdpMid, json.sdpMLineIndex, &json.candidate)?
let json = serde_json::from_str::<IceCandidateJson>(&trickle.candidate_init)?;
IceCandidate::parse(&json.sdp_mid, json.sdp_m_line_index, &json.candidate)?
};
trace!("received ice_candidate {:?} {:?}", target, ice_candidate);
debug!("received ice_candidate {:?} {:?}", target, ice_candidate);
if target == proto::SignalTarget::Publisher {
self.publisher_pc
@@ -428,24 +471,31 @@ impl SessionInner {
updates: quality.updates,
});
}
proto::signal_response::Message::TrackPublished(publish_res) => {
let mut pending_tracks = self.pending_tracks.lock();
if let Some(tx) = pending_tracks.remove(&publish_res.cid) {
let _ = tx.send(publish_res.track.unwrap());
}
}
_ => {}
}
Ok(())
}
async fn on_rtc_event(&self, event: RTCEvent) -> EngineResult<()> {
async fn on_rtc_event(&self, event: RtcEvent) -> EngineResult<()> {
match event {
RTCEvent::IceCandidate {
RtcEvent::IceCandidate {
ice_candidate,
target,
} => {
self.signal_client
.send(proto::signal_request::Message::Trickle(
proto::TrickleRequest {
candidate_init: serde_json::to_string(&IceCandidateJSON {
sdpMid: ice_candidate.sdp_mid(),
sdpMLineIndex: ice_candidate.sdp_mline_index(),
candidate_init: serde_json::to_string(&IceCandidateJson {
sdp_mid: ice_candidate.sdp_mid(),
sdp_m_line_index: ice_candidate.sdp_mline_index(),
candidate: ice_candidate.candidate(),
})?,
target: target as i32,
@@ -453,21 +503,21 @@ impl SessionInner {
))
.await;
}
RTCEvent::ConnectionChange { state, target } => {
trace!("connection change, {:?} {:?}", state, target);
RtcEvent::ConnectionChange { state, target } => {
debug!("connection change, {:?} {:?}", state, target);
let is_primary = self.info.join_response.subscriber_primary
&& target == proto::SignalTarget::Subscriber;
if is_primary && state == PeerConnectionState::Connected {
let old_state = self
.pc_state
.swap(PCState::Connected as u8, Ordering::SeqCst);
if old_state == PCState::New as u8 {
.swap(PeerState::Connected as u8, Ordering::SeqCst);
if old_state == PeerState::New as u8 {
let _ = self.emitter.send(SessionEvent::Connected);
}
} else if state == PeerConnectionState::Failed {
self.pc_state
.store(PCState::Disconnected as u8, Ordering::SeqCst);
.store(PeerState::Disconnected as u8, Ordering::SeqCst);
self.on_session_disconnected(
"pc_state failed",
@@ -478,14 +528,15 @@ impl SessionInner {
);
}
}
RTCEvent::DataChannel {
RtcEvent::DataChannel {
data_channel,
target: _,
} => {
self.subscriber_dc.lock().push(data_channel);
}
RTCEvent::Offer { offer, target: _ } => {
RtcEvent::Offer { offer, target: _ } => {
// Send the publisher offer to the server
debug!("sending publisher offer: {:?}", offer);
self.signal_client
.send(proto::signal_request::Message::Offer(
proto::SessionDescription {
@@ -495,22 +546,24 @@ impl SessionInner {
))
.await;
}
RTCEvent::AddTrack {
rtp_receiver,
RtcEvent::Track {
receiver,
mut streams,
track,
transceiver: _,
target: _,
} => {
if !streams.is_empty() {
let _ = self.emitter.send(SessionEvent::MediaTrack {
track: rtp_receiver.track(),
stream: streams.remove(0),
receiver: rtp_receiver,
track,
receiver,
});
} else {
warn!("AddTrack event with no streams");
warn!("Track event with no streams");
}
}
RTCEvent::Data { data, binary } => {
RtcEvent::Data { data, binary } => {
if !binary {
Err(EngineError::Internal(
"text messages aren't supported".to_string(),
@@ -534,6 +587,106 @@ impl SessionInner {
Ok(())
}
async fn add_track(&self, req: proto::AddTrackRequest) -> EngineResult<proto::TrackInfo> {
let (tx, rx) = oneshot::channel();
let cid = req.cid.clone();
{
let mut pendings_tracks = self.pending_tracks.lock();
if pendings_tracks.contains_key(&req.cid) {
Err(EngineError::Internal("track already published".to_string()))?;
}
pendings_tracks.insert(cid.clone(), tx);
}
self.signal_client
.send(proto::signal_request::Message::AddTrack(req))
.await;
// Wait the result from the server (TrackInfo)
tokio::select! {
Ok(info) = rx => Ok(info),
_ = sleep(TRACK_PUBLISH_TIMEOUT) => {
self.pending_tracks.lock().remove(&cid);
Err(EngineError::Internal("track publication timed out, no response received from the server".to_string()))
},
else => {
Err(EngineError::Internal(
"track publication cancelled".to_string(),
))
}
}
}
async fn remove_track(&self, sender: RtpSender) -> EngineResult<()> {
if let Some(track) = sender.track() {
let mut pending_tracks = self.pending_tracks.lock();
pending_tracks.remove(&track.id());
}
self.publisher_pc
.lock()
.await
.peer_connection()
.remove_track(sender)?;
Ok(())
}
async fn create_sender(
&self,
track: LocalTrack,
options: TrackPublishOptions,
encodings: Vec<RtpEncodingParameters>,
) -> EngineResult<RtpTransceiver> {
let init = RtpTransceiverInit {
direction: RtpTransceiverDirection::SendOnly,
stream_ids: Default::default(),
send_encodings: encodings,
};
let transceiver = self
.publisher_pc
.lock()
.await
.peer_connection()
.add_transceiver(track.rtc_track(), init)?;
let capabilities = LkRuntime::instance()
.pc_factory
.get_rtp_sender_capabilities(track.kind().into());
let mut matched = Vec::new();
let mut partial_matched = Vec::new();
let mut unmatched = Vec::new();
for codec in capabilities.codecs {
let mime_type = codec.mime_type.to_lowercase();
if mime_type == "audio/opus" {
matched.push(codec);
} else if mime_type == format!("video/{}", options.video_codec.as_str()) {
if let Some(sdp_fmtp_line) = codec.sdp_fmtp_line.as_ref() {
// for h264 codecs that have sdpFmtpLine available, use only if the
// profile-level-id is 42e01f for cross-browser compatibility
if sdp_fmtp_line.contains("profile-level-id=42e01f") {
matched.push(codec);
continue;
}
}
partial_matched.push(codec);
} else {
unmatched.push(codec);
}
}
matched.append(&mut partial_matched);
matched.append(&mut unmatched);
transceiver.set_codec_preferences(matched)?;
Ok(transceiver)
}
/// Called when the SignalClient or one of the PeerConnection has lost the connection
/// The RTCEngine may try a reconnect.
fn on_session_disconnected(
@@ -663,7 +816,7 @@ impl SessionInner {
self.publisher_pc
.lock()
.await
.create_and_send_offer(RTCOfferAnswerOptions {
.create_and_send_offer(OfferOptions {
ice_restart: true,
..Default::default()
})
@@ -680,7 +833,7 @@ impl SessionInner {
// Timeout after ['MAX_ICE_CONNECT_TIMEOUT']
async fn wait_pc_connection(&self) -> EngineResult<()> {
let wait_connected = async move {
while self.pc_state.load(Ordering::Acquire) != PCState::Connected as u8 {
while self.pc_state.load(Ordering::Acquire) != PeerState::Connected as u8 {
if self.closed.load(Ordering::Acquire) {
return Err(EngineError::Connection("closed".to_string()));
}
@@ -693,7 +846,7 @@ impl SessionInner {
tokio::select! {
res = wait_connected => res,
_ = sleep(MAX_ICE_CONNECT_TIMEOUT) => {
_ = sleep(ICE_CONNECT_TIMEOUT) => {
let err = EngineError::Connection("wait_pc_connection timed out".to_string());
Err(err)
}
@@ -724,7 +877,7 @@ impl SessionInner {
.await
.peer_connection()
.ice_connection_state()
!= IceConnectionState::IceConnectionChecking
!= IceConnectionState::Checking
{
let _ = self.negotiate_publisher().await;
}
@@ -749,7 +902,7 @@ impl SessionInner {
tokio::select! {
res = wait_connected => res,
_ = sleep(MAX_ICE_CONNECT_TIMEOUT) => {
_ = sleep(ICE_CONNECT_TIMEOUT) => {
let err = EngineError::Connection("could not establish publisher connection: timeout".to_string());
error!(error = ?err);
Err(err)
+2 -2
View File
@@ -114,13 +114,13 @@ impl SignalClient {
}
}
impl From<proto::JoinResponse> for RTCConfiguration {
impl From<proto::JoinResponse> for RtcConfiguration {
fn from(join_response: proto::JoinResponse) -> Self {
Self {
ice_servers: {
let mut servers = vec![];
for ice_server in join_response.ice_servers.clone() {
servers.push(ICEServer {
servers.push(IceServer {
urls: ice_server.urls,
username: ice_server.username,
password: ice_server.credential,
+2 -2
View File
@@ -34,7 +34,7 @@ enum InternalMessage {
///
/// It is replaced by [SignalClient] at each reconnection.
#[derive(Debug)]
pub(crate) struct SignalStream {
pub(super) struct SignalStream {
internal_tx: mpsc::Sender<InternalMessage>,
read_handle: JoinHandle<()>,
write_handle: JoinHandle<()>,
@@ -46,7 +46,7 @@ impl SignalStream {
///
/// SignalStream will never try to reconnect if the connection has been
/// closed.
pub(super) async fn connect(
pub async fn connect(
url: &str,
token: &str,
options: SignalOptions,