mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-06-02 21:39:23 +00:00
webrtcsrc: add support for navigation events
This provides support GstNavigation events handling in webrtcsrc so that a GStreamer client can be used to control remotely a GStreamer server, similar to how the web client is capable of controlling a wpesrc. This is part of a larger set of patches that require more work on the sinks and sources. server: d3d11screencapturesrc ! webrtcsink enable-data-channel-navigation=true client: webrtcsrc enable-data-channel-navigation=true ! d3d11videosink Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1281>
This commit is contained in:
parent
dd005a1e4c
commit
3000b08ec7
|
@ -6223,6 +6223,18 @@
|
||||||
"type": "GstValueArray",
|
"type": "GstValueArray",
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
|
"enable-data-channel-navigation": {
|
||||||
|
"blurb": "Enable navigation events through a dedicated WebRTCDataChannel",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "false",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "gboolean",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"blurb": "Free form metadata about the consumer",
|
"blurb": "Free form metadata about the consumer",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
|
|
|
@ -894,3 +894,10 @@ pub fn cleanup_codec_caps(mut caps: gst::Caps) -> gst::Caps {
|
||||||
|
|
||||||
caps
|
caps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct NavigationEvent {
|
||||||
|
pub mid: Option<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub event: gst_video::NavigationEvent,
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::utils::{cleanup_codec_caps, is_raw_caps, make_element, Codec, Codecs};
|
use crate::utils::{cleanup_codec_caps, is_raw_caps, make_element, Codec, Codecs, NavigationEvent};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
@ -216,13 +216,6 @@ enum SignallerState {
|
||||||
Stopped,
|
Stopped,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
|
||||||
struct NavigationEvent {
|
|
||||||
mid: Option<String>,
|
|
||||||
#[serde(flatten)]
|
|
||||||
event: gst_video::NavigationEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to ensure signal are disconnected when a new signaller is is
|
// Used to ensure signal are disconnected when a new signaller is is
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct SignallerSignals {
|
struct SignallerSignals {
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
||||||
use crate::signaller::{prelude::*, Signallable, Signaller};
|
use crate::signaller::{prelude::*, Signallable, Signaller};
|
||||||
use crate::utils::{Codec, Codecs, AUDIO_CAPS, RTP_CAPS, VIDEO_CAPS};
|
use crate::utils::{Codec, Codecs, NavigationEvent, AUDIO_CAPS, RTP_CAPS, VIDEO_CAPS};
|
||||||
use crate::webrtcsrc::WebRTCSrcPad;
|
use crate::webrtcsrc::WebRTCSrcPad;
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use gst::glib::once_cell::sync::Lazy;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
use gst_webrtc::WebRTCDataChannel;
|
||||||
|
use std::borrow::BorrowMut;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::AtomicU16;
|
use std::sync::atomic::AtomicU16;
|
||||||
|
@ -17,6 +19,7 @@ use std::sync::Mutex;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const DEFAULT_STUN_SERVER: Option<&str> = Some("stun://stun.l.google.com:19302");
|
const DEFAULT_STUN_SERVER: Option<&str> = Some("stun://stun.l.google.com:19302");
|
||||||
|
const DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION: bool = false;
|
||||||
|
|
||||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
gst::DebugCategory::new(
|
gst::DebugCategory::new(
|
||||||
|
@ -35,6 +38,7 @@ struct Settings {
|
||||||
meta: Option<gst::Structure>,
|
meta: Option<gst::Structure>,
|
||||||
video_codecs: Vec<Codec>,
|
video_codecs: Vec<Codec>,
|
||||||
audio_codecs: Vec<Codec>,
|
audio_codecs: Vec<Codec>,
|
||||||
|
enable_data_channel_navigation: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -83,6 +87,12 @@ impl ObjectImpl for WebRTCSrc {
|
||||||
))
|
))
|
||||||
.element_spec(&glib::ParamSpecString::builder("audio-codec-name").build())
|
.element_spec(&glib::ParamSpecString::builder("audio-codec-name").build())
|
||||||
.build(),
|
.build(),
|
||||||
|
glib::ParamSpecBoolean::builder("enable-data-channel-navigation")
|
||||||
|
.nick("Enable data channel navigation")
|
||||||
|
.blurb("Enable navigation events through a dedicated WebRTCDataChannel")
|
||||||
|
.default_value(DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION)
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -127,6 +137,10 @@ impl ObjectImpl for WebRTCSrc {
|
||||||
.get::<Option<gst::Structure>>()
|
.get::<Option<gst::Structure>>()
|
||||||
.expect("type checked upstream")
|
.expect("type checked upstream")
|
||||||
}
|
}
|
||||||
|
"enable-data-channel-navigation" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
settings.enable_data_channel_navigation = value.get::<bool>().unwrap();
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,6 +168,10 @@ impl ObjectImpl for WebRTCSrc {
|
||||||
.to_value(),
|
.to_value(),
|
||||||
"stun-server" => self.settings.lock().unwrap().stun_server.to_value(),
|
"stun-server" => self.settings.lock().unwrap().stun_server.to_value(),
|
||||||
"meta" => self.settings.lock().unwrap().meta.to_value(),
|
"meta" => self.settings.lock().unwrap().meta.to_value(),
|
||||||
|
"enable-data-channel-navigation" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.enable_data_channel_navigation.to_value()
|
||||||
|
}
|
||||||
name => panic!("{} getter not implemented", name),
|
name => panic!("{} getter not implemented", name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,6 +237,7 @@ impl Default for Settings {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|codec| codec.has_decoder())
|
.filter(|codec| codec.has_decoder())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
enable_data_channel_navigation: DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,6 +288,24 @@ impl WebRTCSrc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_navigation_event(&self, evt: gst_video::NavigationEvent) {
|
||||||
|
if let Some(data_channel) = &self.state.lock().unwrap().data_channel.borrow_mut() {
|
||||||
|
let nav_event = NavigationEvent {
|
||||||
|
mid: None,
|
||||||
|
event: evt,
|
||||||
|
};
|
||||||
|
match serde_json::to_string(&nav_event).ok() {
|
||||||
|
Some(str) => {
|
||||||
|
gst::trace!(CAT, imp: self, "Sending navigation event to peer");
|
||||||
|
data_channel.send_string(Some(str.as_str()));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
gst::error!(CAT, imp: self, "Could not serialize navigation event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_webrtc_src_pad(&self, bin: &gst::Bin, pad: &gst::Pad) {
|
fn handle_webrtc_src_pad(&self, bin: &gst::Bin, pad: &gst::Pad) {
|
||||||
let srcpad = self.get_src_pad_from_webrtcbin_pad(pad);
|
let srcpad = self.get_src_pad_from_webrtcbin_pad(pad);
|
||||||
if let Some(ref srcpad) = srcpad {
|
if let Some(ref srcpad) = srcpad {
|
||||||
|
@ -314,6 +351,19 @@ impl WebRTCSrc {
|
||||||
}))
|
}))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
if self.settings.lock().unwrap().enable_data_channel_navigation {
|
||||||
|
pad.add_probe(gst::PadProbeType::EVENT_UPSTREAM,
|
||||||
|
glib::clone!(@weak self as this => @default-panic, move |_pad, info| {
|
||||||
|
if let Some(gst::PadProbeData::Event(ref ev)) = info.data {
|
||||||
|
if let gst::EventView::Navigation(ev) = ev.view() {
|
||||||
|
this.send_navigation_event (gst_video::NavigationEvent::parse(ev).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gst::PadProbeReturn::Ok
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bin.add_pad(&ghostpad)
|
bin.add_pad(&ghostpad)
|
||||||
.expect("Adding ghostpad to the bin should always work");
|
.expect("Adding ghostpad to the bin should always work");
|
||||||
|
|
||||||
|
@ -446,6 +496,16 @@ impl WebRTCSrc {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
webrtcbin.connect_closure(
|
||||||
|
"on-data-channel",
|
||||||
|
false,
|
||||||
|
glib::closure!(@weak-allow-none self as this => move |
|
||||||
|
_webrtcbin: gst::Bin,
|
||||||
|
data_channel: glib::Object| {
|
||||||
|
this.unwrap().on_data_channel(data_channel);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
bin.add(&webrtcbin).unwrap();
|
bin.add(&webrtcbin).unwrap();
|
||||||
self.obj().add(&bin).context("Could not add `webrtcbin`")?;
|
self.obj().add(&bin).context("Could not add `webrtcbin`")?;
|
||||||
|
|
||||||
|
@ -654,7 +714,6 @@ impl WebRTCSrc {
|
||||||
.map(|codec| codec.name.clone())
|
.map(|codec| codec.name.clone())
|
||||||
.collect::<HashSet<String>>()
|
.collect::<HashSet<String>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
let caps = media
|
let caps = media
|
||||||
.formats()
|
.formats()
|
||||||
.filter_map(|format| {
|
.filter_map(|format| {
|
||||||
|
@ -817,6 +876,12 @@ impl WebRTCSrc {
|
||||||
signaller.send_sdp(&session_id, &answer);
|
signaller.send_sdp(&session_id, &answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_data_channel(&self, data_channel: glib::Object) {
|
||||||
|
gst::info!(CAT, imp: self, "Received data channel {data_channel:?}");
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.data_channel = data_channel.dynamic_cast::<WebRTCDataChannel>().ok();
|
||||||
|
}
|
||||||
|
|
||||||
fn on_ice_candidate(&self, sdp_m_line_index: u32, candidate: String) {
|
fn on_ice_candidate(&self, sdp_m_line_index: u32, candidate: String) {
|
||||||
let signaller = self.signaller();
|
let signaller = self.signaller();
|
||||||
let session_id = match self.state.lock().unwrap().session_id.as_ref() {
|
let session_id = match self.state.lock().unwrap().session_id.as_ref() {
|
||||||
|
@ -978,6 +1043,16 @@ impl ElementImpl for WebRTCSrc {
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_event(&self, event: gst::Event) -> bool {
|
||||||
|
match event.view() {
|
||||||
|
gst::EventView::Navigation(ev) => {
|
||||||
|
self.send_navigation_event(gst_video::NavigationEvent::parse(ev).unwrap());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GstObjectImpl for WebRTCSrc {}
|
impl GstObjectImpl for WebRTCSrc {}
|
||||||
|
@ -1056,6 +1131,7 @@ struct State {
|
||||||
webrtcbin: Option<gst::Element>,
|
webrtcbin: Option<gst::Element>,
|
||||||
flow_combiner: gst_base::UniqueFlowCombiner,
|
flow_combiner: gst_base::UniqueFlowCombiner,
|
||||||
signaller_signals: Option<SignallerSignals>,
|
signaller_signals: Option<SignallerSignals>,
|
||||||
|
data_channel: Option<WebRTCDataChannel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
|
@ -1066,6 +1142,7 @@ impl Default for State {
|
||||||
webrtcbin: None,
|
webrtcbin: None,
|
||||||
flow_combiner: Default::default(),
|
flow_combiner: Default::default(),
|
||||||
signaller_signals: Default::default(),
|
signaller_signals: Default::default(),
|
||||||
|
data_channel: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue