From 76da9621c9db7051b2277007577157bff57730ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Fri, 23 Feb 2024 19:22:20 +0100 Subject: [PATCH] desktop: Add a preference to enable the OpenH264 decoder --- Cargo.lock | 1 + desktop/Cargo.toml | 4 +- .../assets/OpenH264-license.txt | 0 .../assets/texts/en-US/preferences_dialog.ftl | 4 ++ desktop/src/gui/dialogs/preferences_dialog.rs | 54 ++++++++++++++++++- desktop/src/player.rs | 24 +++++++-- desktop/src/preferences.rs | 9 ++++ desktop/src/preferences/read.rs | 44 +++++++++++++++ desktop/src/preferences/write.rs | 21 ++++++++ video/external/src/backend.rs | 8 +-- 10 files changed, 158 insertions(+), 11 deletions(-) rename video/external/src/BINARY_LICENSE.txt => desktop/assets/OpenH264-license.txt (100%) diff --git a/Cargo.lock b/Cargo.lock index 45c0c495e..733050026 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4363,6 +4363,7 @@ dependencies = [ "ruffle_frontend_utils", "ruffle_render", "ruffle_render_wgpu", + "ruffle_video_external", "ruffle_video_software", "sys-locale", "tokio", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 8b3889a12..6fa682059 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -23,6 +23,7 @@ ruffle_core = { path = "../core", features = ["audio", "clap", "mp3", "nellymose ruffle_render = { path = "../render", features = ["clap"] } ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] } ruffle_video_software = { path = "../video/software", optional = true } +ruffle_video_external = { path = "../video/external", optional = true } ruffle_frontend_utils = { path = "../frontend-utils" } tracing = { workspace = true } tracing-subscriber = { workspace = true } @@ -57,13 +58,14 @@ embed-resource = "2" vergen = { version = "8.3.1", features = ["build", "git", "gitcl", "cargo"] } [features] -default = ["software_video"] +default = ["software_video", "external_video"] jpegxr = ["ruffle_core/jpegxr"] # core features avm_debug = ["ruffle_core/avm_debug"] lzma = ["ruffle_core/lzma"] software_video = ["ruffle_video_software"] +external_video = ["ruffle_video_external"] tracy = ["tracing-tracy", "ruffle_render_wgpu/profile-with-tracy"] # wgpu features diff --git a/video/external/src/BINARY_LICENSE.txt b/desktop/assets/OpenH264-license.txt similarity index 100% rename from video/external/src/BINARY_LICENSE.txt rename to desktop/assets/OpenH264-license.txt diff --git a/desktop/assets/texts/en-US/preferences_dialog.ftl b/desktop/assets/texts/en-US/preferences_dialog.ftl index f8e2b1a83..4b0c47c39 100644 --- a/desktop/assets/texts/en-US/preferences_dialog.ftl +++ b/desktop/assets/texts/en-US/preferences_dialog.ftl @@ -14,6 +14,10 @@ language = Language audio-output-device = Audio Output Device audio-output-device-default = System Default +enable-openh264 = Enable OpenH264 +show-license = Show License +openh264-license = OpenH264 License + log-filename-pattern = Log Filename log-filename-pattern-single-file = Single File (ruffle.log) log-filename-pattern-with-timestamp = With Timestamp diff --git a/desktop/src/gui/dialogs/preferences_dialog.rs b/desktop/src/gui/dialogs/preferences_dialog.rs index 06da0e00d..6542faff4 100644 --- a/desktop/src/gui/dialogs/preferences_dialog.rs +++ b/desktop/src/gui/dialogs/preferences_dialog.rs @@ -2,7 +2,7 @@ use crate::gui::{available_languages, optional_text, text}; use crate::log::FilenamePattern; use crate::preferences::{storage::StorageBackend, GlobalPreferences}; use cpal::traits::{DeviceTrait, HostTrait}; -use egui::{Align2, Button, ComboBox, DragValue, Grid, Ui, Widget, Window}; +use egui::{Align2, Button, Checkbox, ComboBox, DragValue, Grid, Ui, Widget, Window}; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use std::borrow::Cow; use unic_langid::LanguageIdentifier; @@ -26,6 +26,10 @@ pub struct PreferencesDialog { available_output_devices: Vec, output_device_changed: bool, + enable_openh264: bool, + enable_openh264_changed: bool, + openh264_license_visible: bool, + recent_limit: usize, recent_limit_changed: bool, @@ -68,6 +72,10 @@ impl PreferencesDialog { available_output_devices, output_device_changed: false, + enable_openh264: preferences.openh264_enabled(), + enable_openh264_changed: false, + openh264_license_visible: false, + recent_limit: preferences.recent_limit(), recent_limit_changed: false, @@ -104,6 +112,8 @@ impl PreferencesDialog { self.show_audio_preferences(locale, ui); + self.show_video_preferences(egui_ctx, locale, ui); + self.show_log_preferences(locale, ui); self.show_storage_preferences(locale, &locked_text, ui); @@ -136,6 +146,7 @@ impl PreferencesDialog { self.graphics_backend != self.preferences.graphics_backends() || self.power_preference != self.preferences.graphics_power_preference() || self.output_device != self.preferences.output_device_name() + || self.enable_openh264 != self.preferences.openh264_enabled() || self.log_filename_pattern != self.preferences.log_filename_pattern() || self.storage_backend != self.preferences.storage_backend() } @@ -261,6 +272,44 @@ impl PreferencesDialog { ui.end_row(); } + fn show_video_preferences( + &mut self, + egui_ctx: &egui::Context, + locale: &LanguageIdentifier, + ui: &mut Ui, + ) { + #[cfg(feature = "external_video")] + { + ui.label(text(locale, "enable-openh264")); + + let previous = self.enable_openh264; + ui.add(Checkbox::without_text(&mut self.enable_openh264)); + ui.end_row(); + + ui.small("OpenH264 Video Codec provided by Cisco Systems, Inc."); + if self.enable_openh264 != previous { + self.enable_openh264_changed = true; + } + if ui.small_button(text(locale, "show-license")).clicked() { + self.openh264_license_visible = true; + }; + let available_size = egui_ctx.available_rect().size(); + egui::Window::new(text(locale, "openh264-license")) + .collapsible(false) + .resizable(false) + .anchor(Align2::CENTER_CENTER, egui::Vec2::ZERO) + .scroll(true) + .open(&mut self.openh264_license_visible) + .min_size(available_size * 0.8) + .max_size(available_size * 0.9) + .show(egui_ctx, |ui| { + // Source: https://www.openh264.org/BINARY_LICENSE.txt + ui.monospace(include_str!("../../../assets/OpenH264-license.txt")); + }); + ui.end_row(); + } + } + fn show_log_preferences(&mut self, locale: &LanguageIdentifier, ui: &mut Ui) { ui.label(text(locale, "log-filename-pattern")); @@ -359,6 +408,9 @@ impl PreferencesDialog { preferences.set_output_device(self.output_device.clone()); // [NA] TODO: Inform the running player that the device changed } + if self.enable_openh264_changed { + preferences.set_enable_openh264(self.enable_openh264); + } if self.log_filename_pattern_changed { preferences.set_log_filename_pattern(self.log_filename_pattern); } diff --git a/desktop/src/player.rs b/desktop/src/player.rs index a4079ef9e..5519c77fa 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -220,9 +220,27 @@ impl ActivePlayer { RfdNavigatorInterface, ); - if cfg!(feature = "software_video") { - builder = - builder.with_video(ruffle_video_software::backend::SoftwareVideoBackend::new()); + if cfg!(feature = "external_video") && preferences.openh264_enabled() { + #[cfg(feature = "external_video")] + { + use ruffle_video_external::backend::ExternalVideoBackend; + let path = tokio::task::block_in_place(ExternalVideoBackend::get_openh264); + let openh264_path = match path { + Ok(path) => Some(path), + Err(e) => { + tracing::error!("Couldn't get OpenH264: {}", e); + None + } + }; + + builder = builder.with_video(ExternalVideoBackend::new(openh264_path)); + } + } else { + #[cfg(feature = "software_video")] + { + builder = + builder.with_video(ruffle_video_software::backend::SoftwareVideoBackend::new()); + } } let renderer = WgpuRenderBackend::new(descriptors, movie_view) diff --git a/desktop/src/preferences.rs b/desktop/src/preferences.rs index 7ae59be5c..b89d6f927 100644 --- a/desktop/src/preferences.rs +++ b/desktop/src/preferences.rs @@ -145,6 +145,13 @@ impl GlobalPreferences { }) } + pub fn openh264_enabled(&self) -> bool { + self.preferences + .lock() + .expect("Preferences is not reentrant") + .enable_openh264 + } + pub fn log_filename_pattern(&self) -> FilenamePattern { self.preferences .lock() @@ -229,6 +236,7 @@ pub struct SavedGlobalPreferences { pub output_device: Option, pub mute: bool, pub volume: f32, + pub enable_openh264: bool, pub recent_limit: usize, pub log: LogPreferences, pub storage: StoragePreferences, @@ -247,6 +255,7 @@ impl Default for SavedGlobalPreferences { output_device: None, mute: false, volume: 1.0, + enable_openh264: true, recent_limit: 10, log: Default::default(), storage: Default::default(), diff --git a/desktop/src/preferences/read.rs b/desktop/src/preferences/read.rs index 7d355eb5a..ec82f38ff 100644 --- a/desktop/src/preferences/read.rs +++ b/desktop/src/preferences/read.rs @@ -51,6 +51,10 @@ pub fn read_preferences(input: &str) -> ParseDetails { result.mute = value; }; + if let Some(value) = document.get_bool(&mut cx, "enable_openh264") { + result.enable_openh264 = value; + }; + if let Some(value) = document.get_integer(&mut cx, "recent_limit") { result.recent_limit = value as usize; } @@ -327,6 +331,46 @@ mod tests { assert_eq!(Vec::::new(), result.warnings); } + #[test] + fn enable_openh264() { + let result = read_preferences("enable_openh264 = \"true\""); + assert_eq!( + &SavedGlobalPreferences { + enable_openh264: true, + ..Default::default() + }, + result.values() + ); + assert_eq!( + vec![ParseWarning::UnexpectedType { + expected: "boolean", + actual: "string", + path: "enable_openh264".to_string() + }], + result.warnings + ); + + let result = read_preferences("enable_openh264 = false"); + assert_eq!( + &SavedGlobalPreferences { + enable_openh264: false, + ..Default::default() + }, + result.values() + ); + assert_eq!(Vec::::new(), result.warnings); + + let result = read_preferences(""); + assert_eq!( + &SavedGlobalPreferences { + enable_openh264: true, + ..Default::default() + }, + result.values() + ); + assert_eq!(Vec::::new(), result.warnings); + } + #[test] fn log_filename() { let result = read_preferences("log = {filename_pattern = 5}"); diff --git a/desktop/src/preferences/write.rs b/desktop/src/preferences/write.rs index 5cb5ac9f0..855b16ca1 100644 --- a/desktop/src/preferences/write.rs +++ b/desktop/src/preferences/write.rs @@ -59,6 +59,13 @@ impl<'a> PreferencesWriter<'a> { }) } + pub fn set_enable_openh264(&mut self, enable: bool) { + self.0.edit(|values, toml_document| { + toml_document["enable_openh264"] = value(enable); + values.enable_openh264 = enable; + }) + } + pub fn set_log_filename_pattern(&mut self, pattern: FilenamePattern) { self.0.edit(|values, toml_document| { toml_document["log"]["filename_pattern"] = value(pattern.as_str()); @@ -168,6 +175,20 @@ mod tests { ); } + #[test] + fn set_enable_openh264() { + test( + "", + |writer| writer.set_enable_openh264(false), + "enable_openh264 = false\n", + ); + test( + "enable_openh264 = false", + |writer| writer.set_enable_openh264(true), + "enable_openh264 = true\n", + ); + } + #[test] fn set_log_filename_pattern() { test( diff --git a/video/external/src/backend.rs b/video/external/src/backend.rs index a4a9f399d..6e7aef97b 100644 --- a/video/external/src/backend.rs +++ b/video/external/src/backend.rs @@ -10,7 +10,7 @@ use ruffle_video::VideoStreamHandle; use ruffle_video_software::backend::SoftwareVideoBackend; use slotmap::SlotMap; use std::fs::File; -use std::io::{copy, Write}; +use std::io::copy; use std::path::PathBuf; use swf::{VideoCodec, VideoDeblocking}; @@ -38,9 +38,6 @@ impl Default for ExternalVideoBackend { } } -/// Source: https://www.openh264.org/BINARY_LICENSE.txt -const BINARY_LICENSE: &[u8] = include_bytes!("BINARY_LICENSE.txt"); - impl ExternalVideoBackend { fn get_openh264_data() -> Result<(&'static str, &'static str), Box> { // Source: https://github.com/cisco/openh264/releases/tag/v2.4.1 @@ -82,6 +79,7 @@ impl ExternalVideoBackend { } pub fn get_openh264() -> Result> { + // See the license at: https://www.openh264.org/BINARY_LICENSE.txt const URL_BASE: &str = "http://ciscobinary.openh264.org/"; const URL_SUFFIX: &str = ".bz2"; @@ -94,8 +92,6 @@ impl ExternalVideoBackend { // If the binary doesn't exist in the expected location, download it. if !filepath.is_file() { - File::create("OpenH264-license.txt")?.write_all(BINARY_LICENSE)?; - let url = format!("{}{}{}", URL_BASE, filename, URL_SUFFIX); let response = reqwest::blocking::get(url)?; let bytes = response.bytes()?;