servo/components/script/dom/webrtc/rtcpeerconnection.rs
Narfinger 177f6d6502
Replace Hash Algorithm in HashMap/Set with FxHashMap/Set for simple types (#39166)
FxHash is faster than FnvHash and SipHash for simple types up to at
least 64 bytes. The cryptographic guarantees are not needed for any
types changed here because they are simple ids.
This changes the types in script and net crates.
In a future PR we will change the remaining Fnv to be also Fx unless
there is a reason to keep them as Fnv.

Testing: Should not change functionality but unit test and wpt will find
it.

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
2025-09-09 08:33:46 +00:00

858 lines
30 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::rc::Rc;
use dom_struct::dom_struct;
use js::rust::HandleObject;
use rustc_hash::FxHashMap;
use servo_media::ServoMedia;
use servo_media::streams::MediaStreamType;
use servo_media::streams::registry::MediaStreamId;
use servo_media::webrtc::{
BundlePolicy, DataChannelEvent, DataChannelId, DataChannelState, GatheringState, IceCandidate,
IceConnectionState, SdpType, SessionDescription, SignalingState, WebRtcController,
WebRtcSignaller,
};
use crate::conversions::Convert;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::RTCDataChannelInit;
use crate::dom::bindings::codegen::Bindings::RTCIceCandidateBinding::RTCIceCandidateInit;
use crate::dom::bindings::codegen::Bindings::RTCPeerConnectionBinding::{
RTCAnswerOptions, RTCBundlePolicy, RTCConfiguration, RTCIceConnectionState,
RTCIceGatheringState, RTCOfferOptions, RTCPeerConnectionMethods, RTCRtpTransceiverInit,
RTCSignalingState,
};
use crate::dom::bindings::codegen::Bindings::RTCSessionDescriptionBinding::{
RTCSdpType, RTCSessionDescriptionInit, RTCSessionDescriptionMethods,
};
use crate::dom::bindings::codegen::UnionTypes::{MediaStreamTrackOrString, StringOrStringSequence};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::USVString;
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::mediastream::MediaStream;
use crate::dom::mediastreamtrack::MediaStreamTrack;
use crate::dom::promise::Promise;
use crate::dom::rtcdatachannel::RTCDataChannel;
use crate::dom::rtcdatachannelevent::RTCDataChannelEvent;
use crate::dom::rtcicecandidate::RTCIceCandidate;
use crate::dom::rtcpeerconnectioniceevent::RTCPeerConnectionIceEvent;
use crate::dom::rtcrtptransceiver::RTCRtpTransceiver;
use crate::dom::rtcsessiondescription::RTCSessionDescription;
use crate::dom::rtctrackevent::RTCTrackEvent;
use crate::dom::window::Window;
use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::CanGc;
use crate::task_source::SendableTaskSource;
#[dom_struct]
pub(crate) struct RTCPeerConnection {
eventtarget: EventTarget,
#[ignore_malloc_size_of = "defined in servo-media"]
#[no_trace]
controller: DomRefCell<Option<WebRtcController>>,
closed: Cell<bool>,
// Helps track state changes between the time createOffer/createAnswer
// is called and resolved
offer_answer_generation: Cell<u32>,
#[ignore_malloc_size_of = "promises are hard"]
offer_promises: DomRefCell<Vec<Rc<Promise>>>,
#[ignore_malloc_size_of = "promises are hard"]
answer_promises: DomRefCell<Vec<Rc<Promise>>>,
local_description: MutNullableDom<RTCSessionDescription>,
remote_description: MutNullableDom<RTCSessionDescription>,
gathering_state: Cell<RTCIceGatheringState>,
ice_connection_state: Cell<RTCIceConnectionState>,
signaling_state: Cell<RTCSignalingState>,
#[ignore_malloc_size_of = "defined in servo-media"]
data_channels: DomRefCell<FxHashMap<DataChannelId, Dom<RTCDataChannel>>>,
}
struct RTCSignaller {
trusted: Trusted<RTCPeerConnection>,
task_source: SendableTaskSource,
}
impl WebRtcSignaller for RTCSignaller {
fn on_ice_candidate(&self, _: &WebRtcController, candidate: IceCandidate) {
let this = self.trusted.clone();
self.task_source.queue(task!(on_ice_candidate: move || {
let this = this.root();
this.on_ice_candidate(candidate, CanGc::note());
}));
}
fn on_negotiation_needed(&self, _: &WebRtcController) {
let this = self.trusted.clone();
self.task_source
.queue(task!(on_negotiation_needed: move || {
let this = this.root();
this.on_negotiation_needed(CanGc::note());
}));
}
fn update_gathering_state(&self, state: GatheringState) {
let this = self.trusted.clone();
self.task_source
.queue(task!(update_gathering_state: move || {
let this = this.root();
this.update_gathering_state(state, CanGc::note());
}));
}
fn update_ice_connection_state(&self, state: IceConnectionState) {
let this = self.trusted.clone();
self.task_source
.queue(task!(update_ice_connection_state: move || {
let this = this.root();
this.update_ice_connection_state(state, CanGc::note());
}));
}
fn update_signaling_state(&self, state: SignalingState) {
let this = self.trusted.clone();
self.task_source
.queue(task!(update_signaling_state: move || {
let this = this.root();
this.update_signaling_state(state, CanGc::note());
}));
}
fn on_add_stream(&self, id: &MediaStreamId, ty: MediaStreamType) {
let this = self.trusted.clone();
let id = *id;
self.task_source.queue(task!(on_add_stream: move || {
let this = this.root();
this.on_add_stream(id, ty, CanGc::note());
}));
}
fn on_data_channel_event(
&self,
channel: DataChannelId,
event: DataChannelEvent,
_: &WebRtcController,
) {
// XXX(ferjm) get label and options from channel properties.
let this = self.trusted.clone();
self.task_source
.queue(task!(on_data_channel_event: move || {
let this = this.root();
let global = this.global();
let _ac = enter_realm(&*global);
this.on_data_channel_event(channel, event, CanGc::note());
}));
}
fn close(&self) {
// do nothing
}
}
impl RTCPeerConnection {
pub(crate) fn new_inherited() -> RTCPeerConnection {
RTCPeerConnection {
eventtarget: EventTarget::new_inherited(),
controller: DomRefCell::new(None),
closed: Cell::new(false),
offer_answer_generation: Cell::new(0),
offer_promises: DomRefCell::new(vec![]),
answer_promises: DomRefCell::new(vec![]),
local_description: Default::default(),
remote_description: Default::default(),
gathering_state: Cell::new(RTCIceGatheringState::New),
ice_connection_state: Cell::new(RTCIceConnectionState::New),
signaling_state: Cell::new(RTCSignalingState::Stable),
data_channels: DomRefCell::new(FxHashMap::default()),
}
}
fn new(
window: &Window,
proto: Option<HandleObject>,
config: &RTCConfiguration,
can_gc: CanGc,
) -> DomRoot<RTCPeerConnection> {
let this = reflect_dom_object_with_proto(
Box::new(RTCPeerConnection::new_inherited()),
window,
proto,
can_gc,
);
let signaller = this.make_signaller();
*this.controller.borrow_mut() = Some(ServoMedia::get().create_webrtc(signaller));
if let Some(ref servers) = config.iceServers {
if let Some(server) = servers.first() {
let server = match server.urls {
StringOrStringSequence::String(ref s) => Some(s.clone()),
StringOrStringSequence::StringSequence(ref s) => s.first().cloned(),
};
if let Some(server) = server {
let policy = match config.bundlePolicy {
RTCBundlePolicy::Balanced => BundlePolicy::Balanced,
RTCBundlePolicy::Max_compat => BundlePolicy::MaxCompat,
RTCBundlePolicy::Max_bundle => BundlePolicy::MaxBundle,
};
this.controller
.borrow()
.as_ref()
.unwrap()
.configure(server.to_string(), policy);
}
}
}
this
}
pub(crate) fn get_webrtc_controller(&self) -> &DomRefCell<Option<WebRtcController>> {
&self.controller
}
fn make_signaller(&self) -> Box<dyn WebRtcSignaller> {
let trusted = Trusted::new(self);
Box::new(RTCSignaller {
trusted,
task_source: self.global().task_manager().networking_task_source().into(),
})
}
fn on_ice_candidate(&self, candidate: IceCandidate, can_gc: CanGc) {
if self.closed.get() {
return;
}
let candidate = RTCIceCandidate::new(
self.global().as_window(),
candidate.candidate.into(),
None,
Some(candidate.sdp_mline_index as u16),
None,
can_gc,
);
let event = RTCPeerConnectionIceEvent::new(
self.global().as_window(),
atom!("icecandidate"),
Some(&candidate),
None,
true,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn on_negotiation_needed(&self, can_gc: CanGc) {
if self.closed.get() {
return;
}
let event = Event::new(
&self.global(),
atom!("negotiationneeded"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn on_add_stream(&self, id: MediaStreamId, ty: MediaStreamType, can_gc: CanGc) {
if self.closed.get() {
return;
}
let track = MediaStreamTrack::new(&self.global(), id, ty, can_gc);
let event = RTCTrackEvent::new(
self.global().as_window(),
atom!("track"),
false,
false,
&track,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn on_data_channel_event(
&self,
channel_id: DataChannelId,
event: DataChannelEvent,
can_gc: CanGc,
) {
if self.closed.get() {
return;
}
match event {
DataChannelEvent::NewChannel => {
let channel = RTCDataChannel::new(
&self.global(),
self,
USVString::from("".to_owned()),
&RTCDataChannelInit::empty(),
Some(channel_id),
can_gc,
);
let event = RTCDataChannelEvent::new(
self.global().as_window(),
atom!("datachannel"),
false,
false,
&channel,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
},
_ => {
let channel: DomRoot<RTCDataChannel> =
if let Some(channel) = self.data_channels.borrow().get(&channel_id) {
DomRoot::from_ref(&**channel)
} else {
warn!(
"Got an event for an unregistered data channel {:?}",
channel_id
);
return;
};
match event {
DataChannelEvent::Open => channel.on_open(can_gc),
DataChannelEvent::Close => channel.on_close(can_gc),
DataChannelEvent::Error(error) => channel.on_error(error, can_gc),
DataChannelEvent::OnMessage(message) => channel.on_message(message, can_gc),
DataChannelEvent::StateChange(state) => channel.on_state_change(state, can_gc),
DataChannelEvent::NewChannel => unreachable!(),
}
},
};
}
pub(crate) fn register_data_channel(&self, id: DataChannelId, channel: &RTCDataChannel) {
if self
.data_channels
.borrow_mut()
.insert(id, Dom::from_ref(channel))
.is_some()
{
warn!("Data channel already registered {:?}", id);
}
}
pub(crate) fn unregister_data_channel(&self, id: &DataChannelId) {
self.data_channels.borrow_mut().remove(id);
}
/// <https://www.w3.org/TR/webrtc/#update-ice-gathering-state>
fn update_gathering_state(&self, state: GatheringState, can_gc: CanGc) {
// step 1
if self.closed.get() {
return;
}
// step 2 (state derivation already done by gstreamer)
let state: RTCIceGatheringState = state.convert();
// step 3
if state == self.gathering_state.get() {
return;
}
// step 4
self.gathering_state.set(state);
// step 5
let event = Event::new(
&self.global(),
atom!("icegatheringstatechange"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
// step 6
if state == RTCIceGatheringState::Complete {
let event = RTCPeerConnectionIceEvent::new(
self.global().as_window(),
atom!("icecandidate"),
None,
None,
true,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
}
/// <https://www.w3.org/TR/webrtc/#update-ice-connection-state>
fn update_ice_connection_state(&self, state: IceConnectionState, can_gc: CanGc) {
// step 1
if self.closed.get() {
return;
}
// step 2 (state derivation already done by gstreamer)
let state: RTCIceConnectionState = state.convert();
// step 3
if state == self.ice_connection_state.get() {
return;
}
// step 4
self.ice_connection_state.set(state);
// step 5
let event = Event::new(
&self.global(),
atom!("iceconnectionstatechange"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn update_signaling_state(&self, state: SignalingState, can_gc: CanGc) {
if self.closed.get() {
return;
}
let state: RTCSignalingState = state.convert();
if state == self.signaling_state.get() {
return;
}
self.signaling_state.set(state);
let event = Event::new(
&self.global(),
atom!("signalingstatechange"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn create_offer(&self) {
let generation = self.offer_answer_generation.get();
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
let this = Trusted::new(self);
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.create_offer(Box::new(move |desc: SessionDescription| {
task_source.queue(task!(offer_created: move || {
let this = this.root();
if this.offer_answer_generation.get() != generation {
// the state has changed since we last created the offer,
// create a fresh one
this.create_offer();
} else {
let init: RTCSessionDescriptionInit = desc.convert();
for promise in this.offer_promises.borrow_mut().drain(..) {
promise.resolve_native(&init, CanGc::note());
}
}
}));
}));
}
fn create_answer(&self) {
let generation = self.offer_answer_generation.get();
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
let this = Trusted::new(self);
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.create_answer(Box::new(move |desc: SessionDescription| {
task_source.queue(task!(answer_created: move || {
let this = this.root();
if this.offer_answer_generation.get() != generation {
// the state has changed since we last created the offer,
// create a fresh one
this.create_answer();
} else {
let init: RTCSessionDescriptionInit = desc.convert();
for promise in this.answer_promises.borrow_mut().drain(..) {
promise.resolve_native(&init, CanGc::note());
}
}
}));
}));
}
}
impl RTCPeerConnectionMethods<crate::DomTypeHolder> for RTCPeerConnection {
// https://w3c.github.io/webrtc-pc/#dom-peerconnection
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
config: &RTCConfiguration,
) -> Fallible<DomRoot<RTCPeerConnection>> {
Ok(RTCPeerConnection::new(window, proto, config, can_gc))
}
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-icecandidate
event_handler!(icecandidate, GetOnicecandidate, SetOnicecandidate);
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-ontrack
event_handler!(track, GetOntrack, SetOntrack);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-iceconnectionstatechange
event_handler!(
iceconnectionstatechange,
GetOniceconnectionstatechange,
SetOniceconnectionstatechange
);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-icegatheringstatechange
event_handler!(
icegatheringstatechange,
GetOnicegatheringstatechange,
SetOnicegatheringstatechange
);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-onnegotiationneeded
event_handler!(
negotiationneeded,
GetOnnegotiationneeded,
SetOnnegotiationneeded
);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-signalingstatechange
event_handler!(
signalingstatechange,
GetOnsignalingstatechange,
SetOnsignalingstatechange
);
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-ondatachannel
event_handler!(datachannel, GetOndatachannel, SetOndatachannel);
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addicecandidate>
fn AddIceCandidate(
&self,
candidate: &RTCIceCandidateInit,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
if candidate.sdpMid.is_none() && candidate.sdpMLineIndex.is_none() {
p.reject_error(
Error::Type("one of sdpMid and sdpMLineIndex must be set".to_string()),
can_gc,
);
return p;
}
// XXXManishearth add support for sdpMid
if candidate.sdpMLineIndex.is_none() {
p.reject_error(
Error::Type("servo only supports sdpMLineIndex right now".to_string()),
can_gc,
);
return p;
}
// XXXManishearth this should be enqueued
// https://w3c.github.io/webrtc-pc/#enqueue-an-operation
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.add_ice_candidate(IceCandidate {
sdp_mline_index: candidate.sdpMLineIndex.unwrap() as u32,
candidate: candidate.candidate.to_string(),
});
// XXXManishearth add_ice_candidate should have a callback
p.resolve_native(&(), can_gc);
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer>
fn CreateOffer(&self, _options: &RTCOfferOptions, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
if self.closed.get() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
self.offer_promises.borrow_mut().push(p.clone());
self.create_offer();
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer>
fn CreateAnswer(
&self,
_options: &RTCAnswerOptions,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
if self.closed.get() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
self.answer_promises.borrow_mut().push(p.clone());
self.create_answer();
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-localdescription>
fn GetLocalDescription(&self) -> Option<DomRoot<RTCSessionDescription>> {
self.local_description.get()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-remotedescription>
fn GetRemoteDescription(&self) -> Option<DomRoot<RTCSessionDescription>> {
self.remote_description.get()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-setlocaldescription>
fn SetLocalDescription(
&self,
desc: &RTCSessionDescriptionInit,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
// XXXManishearth validate the current state
let p = Promise::new_in_current_realm(comp, can_gc);
let this = Trusted::new(self);
let desc: SessionDescription = desc.convert();
let trusted_promise = TrustedPromise::new(p.clone());
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.set_local_description(
desc.clone(),
Box::new(move || {
task_source.queue(task!(local_description_set: move || {
// XXXManishearth spec actually asks for an intricate
// dance between pending/current local/remote descriptions
let this = this.root();
let desc = desc.convert();
let desc = RTCSessionDescription::Constructor(
this.global().as_window(),
None,
CanGc::note(),
&desc,
).unwrap();
this.local_description.set(Some(&desc));
trusted_promise.root().resolve_native(&(), CanGc::note())
}));
}),
);
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-setremotedescription>
fn SetRemoteDescription(
&self,
desc: &RTCSessionDescriptionInit,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
// XXXManishearth validate the current state
let p = Promise::new_in_current_realm(comp, can_gc);
let this = Trusted::new(self);
let desc: SessionDescription = desc.convert();
let trusted_promise = TrustedPromise::new(p.clone());
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.set_remote_description(
desc.clone(),
Box::new(move || {
task_source.queue(task!(remote_description_set: move || {
// XXXManishearth spec actually asks for an intricate
// dance between pending/current local/remote descriptions
let this = this.root();
let desc = desc.convert();
let desc = RTCSessionDescription::Constructor(
this.global().as_window(),
None,
CanGc::note(),
&desc,
).unwrap();
this.remote_description.set(Some(&desc));
trusted_promise.root().resolve_native(&(), CanGc::note())
}));
}),
);
p
}
// https://w3c.github.io/webrtc-pc/#legacy-interface-extensions
fn AddStream(&self, stream: &MediaStream) {
for track in &*stream.get_tracks() {
self.controller
.borrow()
.as_ref()
.unwrap()
.add_stream(&track.id());
}
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-icegatheringstate>
fn IceGatheringState(&self) -> RTCIceGatheringState {
self.gathering_state.get()
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-iceconnectionstate>
fn IceConnectionState(&self) -> RTCIceConnectionState {
self.ice_connection_state.get()
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-signalingstate>
fn SignalingState(&self) -> RTCSignalingState {
self.signaling_state.get()
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close>
fn Close(&self, can_gc: CanGc) {
// Step 1
if self.closed.get() {
return;
}
// Step 2
self.closed.set(true);
// Step 4
self.signaling_state.set(RTCSignalingState::Closed);
// Step 5 handled by backend
self.controller.borrow_mut().as_ref().unwrap().quit();
// Step 6
for (_, val) in self.data_channels.borrow().iter() {
val.on_state_change(DataChannelState::Closed, can_gc);
}
// Step 7-10
// (no current support for transports, etc)
// Step 11
self.ice_connection_state.set(RTCIceConnectionState::Closed);
// Step 11
// (no current support for connection state)
}
/// <https://www.w3.org/TR/webrtc/#dom-peerconnection-createdatachannel>
fn CreateDataChannel(
&self,
label: USVString,
init: &RTCDataChannelInit,
) -> DomRoot<RTCDataChannel> {
RTCDataChannel::new(&self.global(), self, label, init, None, CanGc::note())
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver>
fn AddTransceiver(
&self,
_track_or_kind: MediaStreamTrackOrString,
init: &RTCRtpTransceiverInit,
) -> DomRoot<RTCRtpTransceiver> {
RTCRtpTransceiver::new(&self.global(), init.direction, CanGc::note())
}
}
impl Convert<RTCSessionDescriptionInit> for SessionDescription {
fn convert(self) -> RTCSessionDescriptionInit {
let type_ = match self.type_ {
SdpType::Answer => RTCSdpType::Answer,
SdpType::Offer => RTCSdpType::Offer,
SdpType::Pranswer => RTCSdpType::Pranswer,
SdpType::Rollback => RTCSdpType::Rollback,
};
RTCSessionDescriptionInit {
type_,
sdp: self.sdp.into(),
}
}
}
impl Convert<SessionDescription> for &RTCSessionDescriptionInit {
fn convert(self) -> SessionDescription {
let type_ = match self.type_ {
RTCSdpType::Answer => SdpType::Answer,
RTCSdpType::Offer => SdpType::Offer,
RTCSdpType::Pranswer => SdpType::Pranswer,
RTCSdpType::Rollback => SdpType::Rollback,
};
SessionDescription {
type_,
sdp: self.sdp.to_string(),
}
}
}
impl Convert<RTCIceGatheringState> for GatheringState {
fn convert(self) -> RTCIceGatheringState {
match self {
GatheringState::New => RTCIceGatheringState::New,
GatheringState::Gathering => RTCIceGatheringState::Gathering,
GatheringState::Complete => RTCIceGatheringState::Complete,
}
}
}
impl Convert<RTCIceConnectionState> for IceConnectionState {
fn convert(self) -> RTCIceConnectionState {
match self {
IceConnectionState::New => RTCIceConnectionState::New,
IceConnectionState::Checking => RTCIceConnectionState::Checking,
IceConnectionState::Connected => RTCIceConnectionState::Connected,
IceConnectionState::Completed => RTCIceConnectionState::Completed,
IceConnectionState::Disconnected => RTCIceConnectionState::Disconnected,
IceConnectionState::Failed => RTCIceConnectionState::Failed,
IceConnectionState::Closed => RTCIceConnectionState::Closed,
}
}
}
impl Convert<RTCSignalingState> for SignalingState {
fn convert(self) -> RTCSignalingState {
match self {
SignalingState::Stable => RTCSignalingState::Stable,
SignalingState::HaveLocalOffer => RTCSignalingState::Have_local_offer,
SignalingState::HaveRemoteOffer => RTCSignalingState::Have_remote_offer,
SignalingState::HaveLocalPranswer => RTCSignalingState::Have_local_pranswer,
SignalingState::HaveRemotePranswer => RTCSignalingState::Have_remote_pranswer,
SignalingState::Closed => RTCSignalingState::Closed,
}
}
}