Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ ignore = [
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0436", reason = "`paste` comes from `wgpu` transitive deps; awaiting ecosystem migration" },
{ id = "RUSTSEC-2026-0192", reason = "`ttf-parser` comes from Iced and `ab_glyph`; no safe upgrade is available while the ecosystem migrates to `skrifa`" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
Expand Down
9 changes: 2 additions & 7 deletions subwave_appsink/src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl AppsinkVideo {
.downcast::<gst::Pipeline>()
.map_err(|_| Error::Cast)?;

// Apply http-headers context before any state transitions
// Install HTTP source hooks before any state transitions.
if let Some(h) = headers {
subwave_core::http::set_http_headers_on_pipeline(&pipeline, h);
}
Expand Down Expand Up @@ -452,9 +452,7 @@ impl AppsinkVideo {
self.0.get_mut().expect("lock")
}

/// Set HTTP headers for HTTP-based sources via GStreamer "http-headers" context.
/// Applies the context to the underlying pipeline so that HTTP elements (e.g. souphttpsrc,
/// adaptivedemux segment fetchers) can use them for requests.
/// Set request headers on HTTP sources created by the playback pipeline.
pub fn set_http_headers(&mut self, headers: &[(impl AsRef<str>, impl AsRef<str>)]) {
let pipeline = self.get_mut().source.clone();
subwave_core::http::set_http_headers_on_pipeline(&pipeline, headers);
Expand All @@ -479,9 +477,6 @@ impl Video for AppsinkVideo {
(props.width, props.height)
}

/// Set HTTP headers for HTTP-based sources via GStreamer "http-headers" context.
/// Applies the context to the underlying pipeline so that HTTP elements (e.g. souphttpsrc,
/// adaptivedemux segment fetchers) can use them for requests.
/// Get the framerate of the video as frames per second.
fn framerate(&self) -> f64 {
let inner = self.read();
Expand Down
140 changes: 122 additions & 18 deletions subwave_core/src/http.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,138 @@
use std::sync::Arc;

use gstreamer as gst;
use gstreamer::prelude::*;

/// Build a GStreamer `Context` of type `"http-headers"` from provided headers.
/// Returns `None` if the provided slice is empty.
pub fn build_http_headers_context<T: AsRef<str>, U: AsRef<str>>(
/// Build the structure expected by HTTP sources such as `souphttpsrc`.
pub fn build_extra_headers<T: AsRef<str>, U: AsRef<str>>(
headers: &[(T, U)],
) -> Option<gst::Context> {
) -> Option<gst::Structure> {
if headers.is_empty() {
return None;
}
let mut ctx = gst::Context::new("http-headers", true);
{
let s = ctx.get_mut().unwrap().structure_mut();
for (k, v) in headers.iter() {
s.set(k.as_ref(), v.as_ref());
}

let mut extra_headers = gst::Structure::new_empty("extra-headers");
for (name, value) in headers {
extra_headers.set(name.as_ref(), value.as_ref());
}
Some(extra_headers)
}

fn set_headers_on_source(source: &gst::Element, headers: &[(String, String)]) -> bool {
if !source.has_property("extra-headers") {
return false;
}
Some(ctx)

let Some(extra_headers) = build_extra_headers(headers) else {
return false;
};
source.set_property("extra-headers", extra_headers);
true
}

/// Convenience helper to apply HTTP headers to a pipeline using the `http-headers` context.
/// Returns true if a context was applied.
/// Apply HTTP headers to current and future HTTP sources in a playback pipeline.
///
/// `souphttpsrc` consumes headers through its `extra-headers` property, not a
/// `GstContext`. The `source-setup` hook configures the primary URI source,
/// while `deep-element-added` covers sources created in nested bins.
pub fn set_http_headers_on_pipeline<T: AsRef<str>, U: AsRef<str>>(
pipeline: &gst::Pipeline,
headers: &[(T, U)],
) -> bool {
if let Some(ctx) = build_http_headers_context(headers) {
pipeline.set_context(&ctx);
true
} else {
false
if headers.is_empty() {
return false;
}

let headers = Arc::new(
headers
.iter()
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
.collect::<Vec<_>>(),
);

if gst::glib::subclass::SignalId::lookup("source-setup", pipeline.type_()).is_some() {
let source_headers = Arc::clone(&headers);
pipeline.connect("source-setup", false, move |values| {
if let Some(source) = values
.get(1)
.and_then(|value| value.get::<gst::Element>().ok())
{
set_headers_on_source(&source, source_headers.as_slice());
}
None
});
}

let nested_headers = Arc::clone(&headers);
pipeline.connect_deep_element_added(move |_pipeline, _bin, element| {
set_headers_on_source(element, nested_headers.as_slice());
});

for element in pipeline.iterate_recurse().into_iter().flatten() {
set_headers_on_source(&element, headers.as_slice());
}

true
}

#[cfg(test)]
mod tests {
use super::*;

fn source_headers(source: &gst::Element) -> gst::Structure {
source
.property::<Option<gst::Structure>>("extra-headers")
.expect("HTTP source should have extra headers")
}

#[test]
fn source_setup_applies_headers_to_primary_source() {
gst::init().unwrap();
let pipeline = gst::ElementFactory::make("playbin3")
.build()
.unwrap()
.downcast::<gst::Pipeline>()
.unwrap();
let source = gst::ElementFactory::make("souphttpsrc").build().unwrap();

assert!(set_http_headers_on_pipeline(
&pipeline,
&[("Authorization", "Bearer playback-ticket")],
));
pipeline.emit_by_name::<()>("source-setup", &[&source]);

assert_eq!(
source_headers(&source).get::<String>("Authorization"),
Ok("Bearer playback-ticket".to_string())
);
}

#[test]
fn nested_http_sources_receive_headers() {
gst::init().unwrap();
let pipeline = gst::Pipeline::new();
let bin = gst::Bin::new();
pipeline.add(&bin).unwrap();

assert!(set_http_headers_on_pipeline(
&pipeline,
&[("Authorization", "Bearer playback-ticket")],
));

let source = gst::ElementFactory::make("souphttpsrc").build().unwrap();
bin.add(&source).unwrap();

assert_eq!(
source_headers(&source).get::<String>("Authorization"),
Ok("Bearer playback-ticket".to_string())
);
}

#[test]
fn empty_headers_are_not_installed() {
gst::init().unwrap();
let pipeline = gst::Pipeline::new();

assert!(!set_http_headers_on_pipeline::<&str, &str>(&pipeline, &[],));
}
}
5 changes: 2 additions & 3 deletions subwave_unified/src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,8 @@ impl SubwaveVideo {

/// Provide HTTP headers to be used by HTTP-based sources within the pipeline.
///
/// This sets a GStreamer "http-headers" context on the underlying pipeline when available.
/// For the Wayland backend where the pipeline is lazily created, headers are stored and
/// applied once the pipeline is initialized.
/// Headers are applied to HTTP sources created by the underlying playback pipeline.
/// The Wayland backend retains them until its pipeline is initialized.
pub fn set_http_headers(&mut self, headers: &[(impl AsRef<str>, impl AsRef<str>)]) {
match self {
SubwaveVideo::Appsink { inner, .. } => {
Expand Down
2 changes: 1 addition & 1 deletion subwave_wayland/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ once_cell = "^1.19"
libc = "0.2.175"
tempfile = "3.21.0"
ab_glyph = "0.2"
quick-xml = { version = "0.39.2", default-features = false, features = ["escape-html"] }
quick-xml = { version = "0.41.0", default-features = false, features = ["escape-html"] }

[target.'cfg(target_os = "linux")'.dependencies]
# Iced dependencies
Expand Down
6 changes: 3 additions & 3 deletions subwave_wayland/src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ impl SubsurfaceVideo {
Ok(SubsurfaceVideo(RwLock::new(inner)))
}

/// Set HTTP headers for HTTP-based sources via GStreamer "http-headers" context.
/// If the pipeline is not yet initialized, headers are stored and applied during init.
/// Set request headers on HTTP sources created by the playback pipeline.
/// Headers are retained until the lazy Wayland pipeline is initialized.
pub fn set_http_headers(&mut self, headers: &[(impl AsRef<str>, impl AsRef<str>)]) {
// Stash a copy for later application
{
Expand Down Expand Up @@ -411,7 +411,7 @@ impl SubsurfaceVideo {
subtitle_tx,
)?);

// Apply any pending HTTP headers context before starting message processing
// Install pending HTTP source hooks before starting message processing.
if let Some(h) = self.0.read().pending_http_headers.clone() {
subwave_core::http::set_http_headers_on_pipeline(&pipeline.pipeline, h.as_slice());
}
Expand Down
Loading