fix(theme): unlock unmuted media autoplay without user interaction on macos
This commit is contained in:
1 parent
ce572de0d2
commit
a025f9b285
5 files changed
+89
-1
No files matched your search
@@ -3055,6 +3055,7 @@ pub async fn reveal_window(window: tauri::WebviewWindow) -> Result<(), String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let res = crate::macos::activate_and_focus_window(&win);
|
||||
let _ = crate::macos::unlock_media_autoplay(&win);
|
||||
let _ = tx.send(res);
|
||||
}
|
||||
|
||||
@@ -3066,3 +3067,16 @@ pub async fn reveal_window(window: tauri::WebviewWindow) -> Result<(), String> {
|
||||
rx.await
|
||||
.map_err(|_| "Main thread channel closed".to_string())?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unlock_media_autoplay(window: tauri::WebviewWindow) -> Result<(), String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
crate::macos::unlock_media_autoplay(&window)?;
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
let _ = window;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -33,7 +33,7 @@ use crate::{
|
||||
resolve_osu_folder, restore_theme_backup, reveal_window, run_open_tablet_driver, run_osu, run_osu_updater,
|
||||
save_theme_project, set_logging_enabled, set_osu_config_values, set_osu_user_config_values,
|
||||
stop_open_tablet_driver, theme_asset_base, theme_editor_context, theme_preview_url,
|
||||
valid_osu_folder,
|
||||
unlock_media_autoplay, valid_osu_folder,
|
||||
},
|
||||
state::AppState,
|
||||
};
|
||||
@@ -136,6 +136,9 @@ pub fn run() {
|
||||
if let Err(error) = macos::allow_media_autoplay(&window) {
|
||||
log::warn!(target: "launcher", "{error}");
|
||||
}
|
||||
if let Err(error) = macos::unlock_media_autoplay(&window) {
|
||||
log::warn!(target: "launcher", "{error}");
|
||||
}
|
||||
if let Err(error) = macos::activate_and_focus_window(&window) {
|
||||
log::warn!(target: "launcher", "Could not focus launcher window on startup: {error}");
|
||||
}
|
||||
@@ -216,6 +219,7 @@ pub fn run() {
|
||||
set_logging_enabled,
|
||||
open_logs_folder,
|
||||
reveal_window,
|
||||
unlock_media_autoplay,
|
||||
logging::list_log_bundles,
|
||||
logging::read_log_bundle
|
||||
])
|
||||
@@ -238,6 +242,9 @@ pub fn run() {
|
||||
if let Err(error) = macos::allow_media_autoplay(&window) {
|
||||
log::warn!(target: "launcher", "{error}");
|
||||
}
|
||||
if let Err(error) = macos::unlock_media_autoplay(&window) {
|
||||
log::warn!(target: "launcher", "{error}");
|
||||
}
|
||||
if let Err(error) = macos::activate_and_focus_window(&window) {
|
||||
log::warn!(target: "launcher", "Could not focus launcher window on ready: {error}");
|
||||
}
|
||||
|
||||
@@ -197,6 +197,70 @@ pub fn allow_media_autoplay(window: &tauri::WebviewWindow) -> Result<(), String>
|
||||
.map_err(|error| format!("Could not enable theme media autoplay: {error}"))
|
||||
}
|
||||
|
||||
/// Programmatically unlock media autoplay with unmuted sound using WebKit's
|
||||
/// user gesture SPI and media resumption APIs.
|
||||
pub fn unlock_media_autoplay(window: &tauri::WebviewWindow) -> Result<(), String> {
|
||||
use objc2::msg_send;
|
||||
use objc2::runtime::AnyObject;
|
||||
use std::ffi::CString;
|
||||
|
||||
window
|
||||
.with_webview(|webview| unsafe {
|
||||
let webview = webview.inner() as *mut AnyObject;
|
||||
if webview.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Resume all media playback
|
||||
let sel_resume = objc2::sel!(_resumeAllMediaPlayback);
|
||||
if msg_send![webview, respondsToSelector: sel_resume] {
|
||||
let _: () = msg_send![webview, _resumeAllMediaPlayback];
|
||||
} else {
|
||||
let sel_resume_pub = objc2::sel!(resumeAllMediaPlayback:);
|
||||
if msg_send![webview, respondsToSelector: sel_resume_pub] {
|
||||
let _: () = msg_send![
|
||||
webview,
|
||||
resumeAllMediaPlayback: std::ptr::null_mut::<AnyObject>()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Evaluate unmuted media play script with explicit user gesture SPI
|
||||
let sel_eval = objc2::sel!(
|
||||
_evaluateJavaScript:withSourceURL:inFrame:inContentWorld:withUserGesture:completionHandler:
|
||||
);
|
||||
if msg_send![webview, respondsToSelector: sel_eval] {
|
||||
let script_str = match CString::new(
|
||||
"document.querySelectorAll('video, audio').forEach(function(el) { \
|
||||
el.muted = false; \
|
||||
var p = el.play(); \
|
||||
if (p !== undefined) p.catch(function() {}); \
|
||||
});",
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let ns_script: *mut AnyObject =
|
||||
msg_send![objc2::class!(NSString), stringWithUTF8String: script_str.as_ptr()];
|
||||
let page_world: *mut AnyObject =
|
||||
msg_send![objc2::class!(WKContentWorld), pageWorld];
|
||||
|
||||
let _: () = msg_send![
|
||||
webview,
|
||||
_evaluateJavaScript: ns_script,
|
||||
withSourceURL: std::ptr::null_mut::<AnyObject>(),
|
||||
inFrame: std::ptr::null_mut::<AnyObject>(),
|
||||
inContentWorld: page_world,
|
||||
withUserGesture: true,
|
||||
completionHandler: std::ptr::null_mut::<AnyObject>()
|
||||
];
|
||||
log::debug!(target: "launcher", "Unlocked theme media autoplay with user gesture SPI");
|
||||
}
|
||||
})
|
||||
.map_err(|error| format!("Could not unlock theme media autoplay: {error}"))
|
||||
}
|
||||
|
||||
/// Show a native open panel that accepts `.app` bundles as well as folders.
|
||||
///
|
||||
/// AppKit treats application bundles as packages (directories) by default, so a
|
||||
|
||||
Reference in new issue
Block a user