Skip to content
Open
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
34 changes: 34 additions & 0 deletions src/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct SettingsData {
force_transcoding: bool,
window_decorations: Option<WindowDecorations>,
hide_scrollbar: bool,
custom_headers: Vec<(String, String)>,
}

impl Default for SettingsData {
Expand All @@ -77,6 +78,7 @@ impl Default for SettingsData {
force_transcoding: false,
window_decorations: None,
hide_scrollbar: true,
custom_headers: Vec::new(),
}
}
}
Expand Down Expand Up @@ -154,6 +156,17 @@ impl SettingsData {
if let Some(b) = v.get("hideScrollbar").and_then(Value::as_bool) {
self.hide_scrollbar = b;
}
if let Some(arr) = v.get("customHeaders").and_then(Value::as_array) {
self.custom_headers = arr
.iter()
.filter_map(|entry| {
let obj = entry.as_object()?;
let key = obj.get("key")?.as_str()?.to_string();
let value = obj.get("value")?.as_str()?.to_string();
Some((key, value))
})
.collect();
}
}

fn to_json(&self) -> Value {
Expand Down Expand Up @@ -223,6 +236,19 @@ impl SettingsData {
if !self.device_name.is_empty() {
o.insert("deviceName".into(), Value::String(self.device_name.clone()));
}
if !self.custom_headers.is_empty() {
let arr: Vec<Value> = self
.custom_headers
.iter()
.map(|(k, v)| {
let mut entry = Map::new();
entry.insert("key".into(), Value::String(k.clone()));
entry.insert("value".into(), Value::String(v.clone()));
Value::Object(entry)
})
.collect();
o.insert("customHeaders".into(), Value::Array(arr));
}
Value::Object(o)
}

Expand Down Expand Up @@ -548,6 +574,14 @@ pub fn titlebar_theme_color() -> bool {
}
bool_accessors!(hide_scrollbar, set_hide_scrollbar, hide_scrollbar);

pub fn custom_headers() -> Vec<(String, String)> {
state().lock().data.custom_headers.clone()
}

pub fn set_custom_headers(headers: Vec<(String, String)>) {
state().lock().data.custom_headers = headers;
}

pub fn window_geometry() -> JfnWindowGeometry {
state().lock().data.window
}
Expand Down
43 changes: 43 additions & 0 deletions src/jfn_cef/src/business_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,40 @@ fn handle_message(message: BrowserMessage) -> bool {
}
true
}
"getCustomHeaders" => {
let Some(frame) = message.main_frame() else {
return true;
};
let headers = jfn_config::custom_headers();
send_to_renderer(&frame, "customHeaders", |args| {
// Send as JSON string — simple and avoids complex IPC list encoding
let json = serde_json::json!(
headers
.iter()
.map(|(k, v)| { serde_json::json!({"key": k, "value": v}) })
.collect::<Vec<_>>()
);
args.set_string(0, Some(&CefString::from(json.to_string().as_str())));
});
true
}
"setCustomHeaders" => {
let Some(args) = args else { return true };
let json_str = list_string(args, 0);
if let Ok(arr) = serde_json::from_str::<Vec<serde_json::Value>>(&json_str) {
let headers: Vec<(String, String)> = arr
.iter()
.filter_map(|v| {
let key = v.get("key")?.as_str()?.to_string();
let value = v.get("value")?.as_str()?.to_string();
Some((key, value))
})
.collect();
jfn_config::set_custom_headers(headers);
jfn_config::settings_save_async();
}
true
}
_ => false,
}
}
Expand Down Expand Up @@ -318,6 +352,15 @@ fn make_request(method: &str, url: &str, client: UrlrequestClient) -> Option<Url
let req: Request = request_create()?;
req.set_url(Some(&CefString::from(url)));
req.set_method(Some(&CefString::from(method)));
for (key, value) in &jfn_config::custom_headers() {
if !key.is_empty() {
req.set_header_by_name(
Some(&CefString::from(key.as_str())),
Some(&CefString::from(value.as_str())),
1,
);
}
}
let mut req_arg = req;
let mut client_arg = client;
urlrequest_create(Some(&mut req_arg), Some(&mut client_arg), None)
Expand Down
16 changes: 16 additions & 0 deletions src/jfn_cef/src/business_web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ fn handle_player_load(args: &ListValue) {
let Some(ext_sub_c) = js_cstr_or_warn("playerLoad ext sub", &external_sub_url) else {
return;
};
let headers_str = {
let hdrs = jfn_config::custom_headers();
if hdrs.is_empty() {
None
} else {
let joined: Vec<String> = hdrs.iter().map(|(k, v)| format!("{k}: {v}")).collect();
Some(joined.join("\n"))
}
};
let headers_c = headers_str
.as_deref()
.and_then(|s| std::ffi::CString::new(s).ok());
let opts = JfnMpvLoadOptions {
start_secs: start_ms as f64 / 1000.0,
video_track: video_idx,
Expand All @@ -260,6 +272,10 @@ fn handle_player_load(args: &ListValue) {
external_audio_url: ext_audio_c.as_ptr(),
external_sub_url: ext_sub_c.as_ptr(),
is_infinite_stream,
http_headers: headers_c
.as_ref()
.map(|c| c.as_ptr())
.unwrap_or(std::ptr::null()),
};
unsafe { jfn_mpv_load_file(url_c.as_ptr(), &opts) };
}
Expand Down
6 changes: 6 additions & 0 deletions src/jfn_cef/src/client_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ mod load;
mod os_ffi;
mod process_message;
mod render;
mod request;
mod resource_request;
use context_menu::JfnContextMenuHandlerBuilder;
use display::JfnDisplayHandlerBuilder;
use keyboard::JfnKeyboardHandlerBuilder;
use lifespan::JfnLifeSpanHandlerBuilder;
use load::JfnLoadHandlerBuilder;
use render::JfnRenderHandlerBuilder;
use request::JfnRequestHandlerBuilder;

pub fn make_client(inner: Arc<Inner>) -> Client {
JfnClientBuilder::new(inner)
Expand Down Expand Up @@ -47,6 +50,9 @@ wrap_client! {
fn keyboard_handler(&self) -> Option<KeyboardHandler> {
Some(JfnKeyboardHandlerBuilder::new(self.inner.clone()))
}
fn request_handler(&self) -> Option<RequestHandler> {
Some(JfnRequestHandlerBuilder::new(self.inner.clone()))
}
fn on_process_message_received(
&self,
browser: Option<&mut Browser>,
Expand Down
27 changes: 27 additions & 0 deletions src/jfn_cef/src/client_impl/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use cef::*;
use std::sync::Arc;

use crate::client::Inner;

use super::resource_request::JfnResourceRequestHandlerBuilder;

wrap_request_handler! {
pub struct JfnRequestHandlerBuilder {
inner: Arc<Inner>,
}

impl RequestHandler {
fn resource_request_handler(
&self,
_browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
_request: Option<&mut Request>,
_is_navigation: ::std::os::raw::c_int,
_is_download: ::std::os::raw::c_int,
_request_initiator: Option<&CefString>,
_disable_default_handling: Option<&mut ::std::os::raw::c_int>,
) -> Option<ResourceRequestHandler> {
Some(JfnResourceRequestHandlerBuilder::new(self.inner.clone()))
}
}
}
45 changes: 45 additions & 0 deletions src/jfn_cef/src/client_impl/resource_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use cef::*;
use std::sync::Arc;

use crate::app::userfree_to_string;
use crate::client::Inner;

wrap_resource_request_handler! {
pub struct JfnResourceRequestHandlerBuilder {
inner: Arc<Inner>,
}

impl ResourceRequestHandler {
fn on_before_resource_load(
&self,
_browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
request: Option<&mut Request>,
_callback: Option<&mut Callback>,
) -> ReturnValue {
let Some(req) = request else { return ReturnValue::CONTINUE };

let server_url = jfn_config::server_url();
if server_url.is_empty() {
return ReturnValue::CONTINUE;
}

let url = userfree_to_string(&req.url());
if !url.starts_with(&server_url) {
return ReturnValue::CONTINUE;
}

for (key, value) in &jfn_config::custom_headers() {
if !key.is_empty() {
req.set_header_by_name(
Some(&CefString::from(key.as_str())),
Some(&CefString::from(value.as_str())),
1,
);
}
}

ReturnValue::CONTINUE
}
}
}
8 changes: 8 additions & 0 deletions src/jfn_cef/src/injection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ pub(crate) enum NativeFunction {
CsdReady,
MenuItemSelected,
MenuDismissed,
GetCustomHeaders,
SetCustomHeaders,
}

impl NativeFunction {
Expand Down Expand Up @@ -114,6 +116,8 @@ impl NativeFunction {
"csdReady" => Self::CsdReady,
"menuItemSelected" => Self::MenuItemSelected,
"menuDismissed" => Self::MenuDismissed,
"getCustomHeaders" => Self::GetCustomHeaders,
"setCustomHeaders" => Self::SetCustomHeaders,
_ => return None,
})
}
Expand Down Expand Up @@ -165,6 +169,8 @@ impl NativeFunction {
Self::CsdReady => "csdReady",
Self::MenuItemSelected => "menuItemSelected",
Self::MenuDismissed => "menuDismissed",
Self::GetCustomHeaders => "getCustomHeaders",
Self::SetCustomHeaders => "setCustomHeaders",
}
}
}
Expand Down Expand Up @@ -273,6 +279,8 @@ const OVERLAY_FUNCTIONS: &[NativeFunction] = &[
NativeFunction::DismissOverlay,
NativeFunction::CheckServerConnectivity,
NativeFunction::CancelServerConnectivity,
NativeFunction::GetCustomHeaders,
NativeFunction::SetCustomHeaders,
];

const ABOUT_FUNCTIONS: &[NativeFunction] =
Expand Down
30 changes: 30 additions & 0 deletions src/mpv/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ pub unsafe fn jfn_mpv_set_property_string_async(name: *const c_char, value: *con
}
}

pub unsafe fn jfn_mpv_set_property_string(name: *const c_char, value: *const c_char) {
let h = raw();
if h.is_null() {
return;
}
let Some(n) = (unsafe { cstr(name) }) else {
return;
};
let Some(v) = (unsafe { cstr(value) }) else {
return;
};
unsafe {
sys::mpv_set_property_string(h, n.as_ptr(), v.as_ptr());
}
}

/// Sync int property read. Writes the value into `*out` and returns
/// libmpv's error code (0 on success, negative on failure). NULL `out`
/// or missing handle returns `MPV_ERROR_INVALID_PARAMETER` (-4).
Expand Down Expand Up @@ -372,6 +388,7 @@ pub struct JfnMpvLoadOptions {
pub external_audio_url: *const c_char,
pub external_sub_url: *const c_char,
pub is_infinite_stream: bool,
pub http_headers: *const c_char,
}

struct PendingTrack {
Expand Down Expand Up @@ -437,6 +454,19 @@ pub unsafe fn jfn_mpv_load_file(path: *const c_char, opts: *const JfnMpvLoadOpti
s.valid = true;
}

// Set custom HTTP headers for the stream before loading.
// Set both the global option and file-local to cover all backends.
if !o.http_headers.is_null()
&& let Some(h) = unsafe { cstr(o.http_headers) }
{
let global = CString::new("http-header-fields").unwrap_or_default();
let local = CString::new("file-local-options/http-header-fields").unwrap_or_default();
unsafe {
sys::mpv_set_property_string(raw(), global.as_ptr(), h.as_ptr());
sys::mpv_set_property_string(raw(), local.as_ptr(), h.as_ptr());
}
}

let mut opts_str = format!("start={},pause=yes", o.start_secs);
if defer_audio {
// Per-file enable so mpv's demuxer picks the format-correct
Expand Down
Loading