diff --git a/Cargo.lock b/Cargo.lock index ada165f..3ada0f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,28 +148,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3aa2999eb46af81abb65c2d30d446778d7e613b60bbf4e174a027e80f90a3c14" -[[package]] -name = "alsa" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" -dependencies = [ - "alsa-sys", - "bitflags 2.6.0", - "cfg-if", - "libc", -] - -[[package]] -name = "alsa-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "android-activity" version = "0.5.2" @@ -281,9 +259,9 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a" +checksum = "4d43c03d9e36dd40cab48435be0b09646da362c278223ca535493877b2c1dee9" dependencies = [ "enumflags2", "futures-channel", @@ -624,44 +602,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 1.0.109", -] - -[[package]] -name = "bindgen" -version = "0.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" -dependencies = [ - "bitflags 2.6.0", - "cexpr", - "clang-sys", - "itertools", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.79", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -868,15 +808,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-expr" version = "0.17.0" @@ -919,17 +850,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading 0.8.5", -] - [[package]] name = "clipboard-win" version = "5.4.0" @@ -1124,30 +1044,10 @@ dependencies = [ "libc", ] -[[package]] -name = "coreaudio-rs" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" -dependencies = [ - "bitflags 1.3.2", - "core-foundation-sys", - "coreaudio-sys", -] - -[[package]] -name = "coreaudio-sys" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" -dependencies = [ - "bindgen 0.70.1", -] - [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1166,7 +1066,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "quote", "syn 1.0.109", @@ -1176,17 +1076,13 @@ dependencies = [ name = "cosmic-player" version = "0.1.0" dependencies = [ - "cpal", "env_logger", - "ffmpeg-next", "i18n-embed", "i18n-embed-fl", "iced_video_player", "lazy_static", - "lexopt", "libcosmic", "log", - "paste", "rust-embed", "serde", "smol_str", @@ -1220,7 +1116,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "almost", "cosmic-config", @@ -1234,29 +1130,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cpal" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" -dependencies = [ - "alsa", - "core-foundation-sys", - "coreaudio-rs", - "dasp_sample", - "jni", - "js-sys", - "libc", - "mach2", - "ndk", - "ndk-context", - "oboe", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows 0.54.0", -] - [[package]] name = "cpufeatures" version = "0.2.14" @@ -1411,12 +1284,6 @@ dependencies = [ "parking_lot_core 0.9.10", ] -[[package]] -name = "dasp_sample" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" - [[package]] name = "data-url" version = "0.3.1" @@ -1740,31 +1607,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "ffmpeg-next" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e72c72e8dcf638fb0fb03f033a954691662b5dabeaa3f85a6607d101569fccd" -dependencies = [ - "bitflags 1.3.2", - "ffmpeg-sys-next", - "libc", -] - -[[package]] -name = "ffmpeg-sys-next" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2529ad916d08c3562c754c21bc9b17a26c7882c0f5706cc2cd69472175f1620" -dependencies = [ - "bindgen 0.64.0", - "cc", - "libc", - "num_cpus", - "pkg-config", - "vcpkg", -] - [[package]] name = "filetime" version = "0.2.25" @@ -2222,12 +2064,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "glow" version = "0.13.1" @@ -2691,7 +2527,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core", ] [[package]] @@ -2706,7 +2542,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "dnd", "iced_accessibility", @@ -2724,7 +2560,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "accesskit", "accesskit_winit", @@ -2733,7 +2569,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "bitflags 2.6.0", "dnd", @@ -2753,7 +2589,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "futures", "iced_core", @@ -2766,7 +2602,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -2790,7 +2626,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2802,7 +2638,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "dnd", "iced_core", @@ -2814,7 +2650,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "iced_core", "once_cell", @@ -2824,7 +2660,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "bytemuck", "cosmic-text", @@ -2841,7 +2677,7 @@ dependencies = [ [[package]] name = "iced_video_player" version = "0.6.0" -source = "git+https://github.com/jackpot51/iced_video_player.git?branch=cosmic#edf6354fdd3d365df9177cd90de4a12024096dff" +source = "git+https://github.com/jackpot51/iced_video_player.git?branch=cosmic#8086eb49d569f5174ae9750f45e7b2e566352d9d" dependencies = [ "glib", "gstreamer", @@ -2857,7 +2693,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "as-raw-xcb-connection", "bitflags 2.6.0", @@ -2886,7 +2722,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "dnd", "iced_renderer", @@ -2902,7 +2738,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "dnd", "iced_graphics", @@ -3192,24 +3028,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" -[[package]] -name = "lexopt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" - [[package]] name = "libc" version = "0.2.159" @@ -3219,10 +3043,10 @@ checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic.git#8b37f658e2d0885b6972952cbf0e606d188ba2b7" dependencies = [ "apply", - "ashpd 0.9.1", + "ashpd 0.9.2", "chrono", "cosmic-config", "cosmic-theme", @@ -3411,15 +3235,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -3494,12 +3309,6 @@ dependencies = [ "smithay-clipboard", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.4" @@ -3630,16 +3439,6 @@ dependencies = [ "memoffset 0.9.1", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "notify" version = "6.1.1" @@ -3692,17 +3491,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.79", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -3861,29 +3649,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "oboe" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" -dependencies = [ - "jni", - "ndk", - "ndk-context", - "num-derive", - "num-traits", - "oboe-sys", -] - -[[package]] -name = "oboe-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" -dependencies = [ - "cc", -] - [[package]] name = "once_cell" version = "1.20.2" @@ -4052,12 +3817,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -5571,12 +5330,6 @@ dependencies = [ "tiny-skia-path", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version-compare" version = "0.2.0" @@ -6024,17 +5777,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" -dependencies = [ - "windows-core 0.54.0", + "windows-core", "windows-targets 0.52.6", ] @@ -6047,16 +5790,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" -dependencies = [ - "windows-result", - "windows-targets 0.52.6", -] - [[package]] name = "windows-implement" version = "0.48.0" @@ -6079,15 +5812,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index f44fa0d..93663db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,13 +5,9 @@ edition = "2021" [dependencies] lazy_static = "1" -paste = "1" serde = { version = "1", features = ["serde_derive"] } tokio = "1" url = "2" -# ffmpeg -cpal = { version = "0.15", optional = true } -ffmpeg-next = { version = "6", optional = true } # Internationalization i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] } i18n-embed-fl = "0.6" @@ -19,12 +15,11 @@ rust-embed = "6" # Logging env_logger = "0.10" log = "0.4" -lexopt = "0.3" [dependencies.iced_video_player] git = "https://github.com/jackpot51/iced_video_player.git" branch = "cosmic" -optional = true +default-features = false [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic.git" @@ -36,10 +31,8 @@ version = "0.2.1" features = ["serde"] [features] -default = ["gstreamer", "wgpu"] -ffmpeg = ["dep:cpal", "dep:ffmpeg-next"] -gstreamer = ["dep:iced_video_player", "wgpu"] -wgpu = ["libcosmic/wgpu"] +default = ["wgpu"] +wgpu = ["iced_video_player/wgpu", "libcosmic/wgpu"] [profile.release-with-debug] inherits = "release" diff --git a/src/config.rs b/src/config.rs index cc50b00..f6b11b1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,12 +4,7 @@ use cosmic::{ cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}, theme, }; -use lexopt::prelude::*; use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, process}; - -#[cfg(feature = "ffmpeg")] -use crate::app::hardware::DeviceType; pub const CONFIG_VERSION: u64 = 1; @@ -34,68 +29,12 @@ impl AppTheme { #[serde(default)] pub struct Config { pub app_theme: AppTheme, - #[cfg(feature = "ffmpeg")] - pub hw_decoder: DeviceType, } impl Default for Config { fn default() -> Self { Self { app_theme: AppTheme::System, - #[cfg(feature = "ffmpeg")] - hw_decoder: DeviceType::default(), } } -} - -#[cfg(feature = "ffmpeg")] -impl Config { - pub fn with_args(&mut self, args: &mut Args) { - if let Some(decoder) = args.decoder { - self.hw_decoder = decoder; - } - } -} - -#[cfg(feature = "ffmpeg")] -pub struct Args { - pub paths: Vec, - pub decoder: Option, -} - -#[cfg(feature = "ffmpeg")] -impl Args { - pub fn parse_args() -> Result { - let mut paths = Vec::new(); - let mut decoder = None; - - let mut parser = lexopt::Parser::from_env(); - while let Some(arg) = parser.next()? { - match arg { - Long("list-hwdec") => { - println!("Supported hardware decoders:"); - for hwdec in DeviceType::supported_devices() { - println!("\t* [{}] {hwdec}", hwdec.short_name()); - } - process::exit(0); - } - Long("hwdec") => { - decoder = Some(parser.value()?.parse()?); - } - Value(path) => { - let path = path.parse()?; - paths.push(path); - } - _ => return Err(arg.unexpected()), - } - } - - if paths.is_empty() { - return Err(lexopt::Error::MissingValue { - option: Some("missing video path".into()), - }); - } - - Ok(Self { paths, decoder }) - } -} +} \ No newline at end of file diff --git a/src/ffmpeg/hardware/device_type.rs b/src/ffmpeg/hardware/device_type.rs deleted file mode 100644 index 8e82c68..0000000 --- a/src/ffmpeg/hardware/device_type.rs +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use std::{fmt, str::FromStr}; - -use ffmpeg_next::ffi::AVHWDeviceType; -use serde::{ - de::{value::Error as DeError, Error as DeErrorTrait, Unexpected}, - Deserialize, Serialize, -}; - -use super::iter::SupportedDeviceIter; - -/// Delegate type for [`ffmpeg_next::ffi::AVHWDeviceType`] for configs. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum DeviceType { - None, - /// Compute Unified Device Architecture - /// Nvidia only. - /// https://developer.nvidia.com/video-codec-sdk - Cuda, - /// Direct3D 11 Video API - /// https://learn.microsoft.com/en-us/windows/win32/medfound/direct3d-11-video-apis - D3d11va, - /// Direct3D 12 Video API - /// https://learn.microsoft.com/en-us/windows/win32/medfound/direct3d-12-video-overview - D3d12va, - /// DirectX Video Acceleration 2.0 - /// https://learn.microsoft.com/en-us/windows/win32/medfound/about-dxva-2-0 - Dxva2, - /// Direct Rendering Manager - /// https://dri.freedesktop.org/wiki/DRM/ - Drm, - /// MediaCodec - /// Android only - /// https://developer.android.com/reference/android/media/MediaCodec - MediaCodec, - /// OpenCL - /// Only used in filters - /// https://www.khronos.org/opencl/ - OpenCl, - /// Intel Quick Sync Video - /// https://www.intel.com/content/www/us/en/developer/tools/vpl/overview.html - Qsv, - /// Video Acceleration API - /// https://www.intel.com/content/www/us/en/developer/articles/technical/linuxmedia-vaapi.html - Vaapi, - /// Video Decode and Presentation API for Unix - /// https://www.freedesktop.org/wiki/Software/VDPAU/ - Vdpau, - /// Video Toolbox - /// https://developer.apple.com/documentation/videotoolbox - VideoToolbox, - /// Vulkan - Vulkan, -} - -impl DeviceType { - /// Hardware device names for user facing interfaces (logging, configs). - pub const fn name(self) -> &'static str { - match self { - Self::None => "None", - Self::Cuda => "CUDA", - Self::Dxva2 => "DirectX Video Acceleration 2.0", - Self::D3d11va => "DirectX 11 Video Acceleration", - Self::D3d12va => "DirectX 12 Video Acceleration", - Self::Drm => "Direct Rendering Manager (DRM)", - Self::MediaCodec => "MediaCodec", - Self::OpenCl => "OpenCL", - Self::Qsv => "Intel Quick Video Sync", - Self::Vaapi => "VA-API", - Self::Vdpau => "VDPAU", - Self::VideoToolbox => "VideoToolbox", - Self::Vulkan => "Vulkan", - } - } - - /// Short name for CLI arguments - pub const fn short_name(self) -> &'static str { - match self { - Self::None => "none", - Self::Cuda => "cuda", - Self::Dxva2 => "dxva2", - Self::D3d11va => "d3d11va", - Self::D3d12va => "d3d12va", - Self::Drm => "drm", - Self::MediaCodec => "mediacodec", - Self::OpenCl => "opencl", - Self::Qsv => "qsv", - Self::Vaapi => "vaapi", - Self::Vdpau => "vdpau", - Self::VideoToolbox => "videotoolbox", - Self::Vulkan => "vulkan", - } - } - - /// System's supported hardware decoders - pub fn supported_devices() -> SupportedDeviceIter { - SupportedDeviceIter::default() - } -} - -impl FromStr for DeviceType { - type Err = DeError; - - // av_hwdevice_find_type_by_name returns None for invalid device type names, but this type - // is used for deserializing configs (etc.) so the error is preserved. - fn from_str(s: &str) -> Result { - match s { - "none" => Ok(Self::None), - "cuda" => Ok(Self::Cuda), - "dxva2" => Ok(Self::Dxva2), - "d3d11va" => Ok(Self::D3d11va), - "d3d12va" => Ok(Self::D3d12va), - "drm" => Ok(Self::Drm), - "mediacodec" => Ok(Self::MediaCodec), - "opencl" => Ok(Self::OpenCl), - "qsv" => Ok(Self::Qsv), - "vaapi" => Ok(Self::Vaapi), - "vdpau" => Ok(Self::Vdpau), - "videotoolbox" => Ok(Self::VideoToolbox), - "vulkan" => Ok(Self::Vulkan), - _ => Err(DeError::invalid_value( - Unexpected::Str(s), - &"valid hardware decoder", - )), - } - } -} - -impl fmt::Display for DeviceType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.name()) - } -} - -impl From for DeviceType { - fn from(value: AVHWDeviceType) -> Self { - match value { - AVHWDeviceType::AV_HWDEVICE_TYPE_NONE => Self::None, - AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA => Self::Cuda, - AVHWDeviceType::AV_HWDEVICE_TYPE_DXVA2 => Self::Dxva2, - AVHWDeviceType::AV_HWDEVICE_TYPE_D3D11VA => Self::D3d11va, - // This variant exists in ffmpeg's C lib but not in Rust's crate yet. - // AVHWDeviceType::AV_HWDEVICE_TYPE_D3D12VA => Self::D3d12va - AVHWDeviceType::AV_HWDEVICE_TYPE_DRM => Self::Drm, - AVHWDeviceType::AV_HWDEVICE_TYPE_MEDIACODEC => Self::MediaCodec, - AVHWDeviceType::AV_HWDEVICE_TYPE_OPENCL => Self::OpenCl, - AVHWDeviceType::AV_HWDEVICE_TYPE_QSV => Self::Qsv, - AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI => Self::Vaapi, - AVHWDeviceType::AV_HWDEVICE_TYPE_VDPAU => Self::Vdpau, - AVHWDeviceType::AV_HWDEVICE_TYPE_VIDEOTOOLBOX => Self::VideoToolbox, - AVHWDeviceType::AV_HWDEVICE_TYPE_VULKAN => Self::Vulkan, - } - } -} - -impl From for AVHWDeviceType { - fn from(value: DeviceType) -> Self { - match value { - DeviceType::None => Self::AV_HWDEVICE_TYPE_NONE, - DeviceType::Cuda => Self::AV_HWDEVICE_TYPE_CUDA, - DeviceType::D3d11va => Self::AV_HWDEVICE_TYPE_D3D11VA, - // NOTE: Next FFmpeg release - DeviceType::D3d12va => Self::AV_HWDEVICE_TYPE_NONE, - DeviceType::Dxva2 => Self::AV_HWDEVICE_TYPE_DXVA2, - DeviceType::Drm => Self::AV_HWDEVICE_TYPE_DRM, - DeviceType::MediaCodec => Self::AV_HWDEVICE_TYPE_MEDIACODEC, - DeviceType::OpenCl => Self::AV_HWDEVICE_TYPE_OPENCL, - DeviceType::Qsv => Self::AV_HWDEVICE_TYPE_QSV, - DeviceType::Vaapi => Self::AV_HWDEVICE_TYPE_VAAPI, - DeviceType::Vdpau => Self::AV_HWDEVICE_TYPE_VDPAU, - DeviceType::VideoToolbox => Self::AV_HWDEVICE_TYPE_VIDEOTOOLBOX, - DeviceType::Vulkan => Self::AV_HWDEVICE_TYPE_VULKAN, - } - } -} - -impl Default for DeviceType { - fn default() -> Self { - Self::Vaapi - } -} diff --git a/src/ffmpeg/hardware/iter.rs b/src/ffmpeg/hardware/iter.rs deleted file mode 100644 index 0a52f7e..0000000 --- a/src/ffmpeg/hardware/iter.rs +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use std::iter::FusedIterator; - -use ffmpeg_next::ffi::{av_hwdevice_iterate_types, AVHWDeviceType}; - -use super::device_type::DeviceType; - -/// Iterator over system's supported hardware decoders. -pub struct SupportedDeviceIter { - current: AVHWDeviceType, -} - -impl Default for SupportedDeviceIter { - fn default() -> Self { - // SAFETY: FFmpeg's documentation states that the iterator is delimited by AV_HWDEVICE_TYPE_NONE. - let current = unsafe { av_hwdevice_iterate_types(AVHWDeviceType::AV_HWDEVICE_TYPE_NONE) }; - Self { current } - } -} - -impl Iterator for SupportedDeviceIter { - type Item = DeviceType; - - fn next(&mut self) -> Option { - // None is a sentinel value that indicates the iterator is exhausted - if self.current == AVHWDeviceType::AV_HWDEVICE_TYPE_NONE { - None - } else { - let prev = self.current; - // SAFETY: The docs and examples state that the iterator yields the next value - // when the previous is passed in. - self.current = unsafe { av_hwdevice_iterate_types(prev) }; - - Some(prev.into()) - } - } -} - -impl FusedIterator for SupportedDeviceIter {} - -#[cfg(test)] -mod tests { - use std::hint::black_box; - - use super::*; - - // The iterator's yielded values aren't important since hardware decoders vary by system - // This is just a sanity check to ensure the iterator works - #[test] - fn supported_device_iter_doesnt_seg_fault() { - for decoder in DeviceType::supported_devices() { - black_box(decoder); - } - - let _decoders: Vec<_> = black_box(DeviceType::supported_devices().collect()); - } -} diff --git a/src/ffmpeg/hardware/mod.rs b/src/ffmpeg/hardware/mod.rs deleted file mode 100644 index 143259d..0000000 --- a/src/ffmpeg/hardware/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pub mod device_type; -pub mod iter; - -pub use device_type::DeviceType; -pub use iter::SupportedDeviceIter; diff --git a/src/ffmpeg/mod.rs b/src/ffmpeg/mod.rs deleted file mode 100644 index 9a2314f..0000000 --- a/src/ffmpeg/mod.rs +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use cosmic::{ - app::{message, Command, Core, Settings}, - cosmic_config::{self, CosmicConfigEntry}, - cosmic_theme, executor, - iced::{ - event::{self, Event}, - keyboard::{Event as KeyEvent, Key, Modifiers}, - subscription::{self, Subscription}, - window, Alignment, Length, Limits, - }, - widget, Application, ApplicationExt, Element, -}; -use std::{ - any::TypeId, - collections::HashMap, - env, process, - sync::{mpsc, Arc, Mutex}, - time::{Duration, Instant}, -}; - -use crate::{ - config::{AppTheme, Args, Config, CONFIG_VERSION}, - fl, - key_bind::{key_binds, KeyBind}, - localize, -}; - -pub mod hardware; - -mod player; -use player::{PlayerMessage, VideoFrame, VideoQueue}; - -/// Runs application with these settings -#[rustfmt::skip] -pub fn main() -> Result<(), Box> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); - - localize::localize(); - - let mut args = match Args::parse_args() { - Ok(args) => args, - Err(e) => { - log::error!("{e}"); - process::exit(1); - } - }; - - let (config_handler, config) = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) { - Ok(config_handler) => { - let mut config = match Config::get_entry(&config_handler) { - Ok(ok) => ok, - Err((errs, config)) => { - log::info!("errors loading config: {:?}", errs); - config - } - }; - // Update config with command line args - config.with_args(&mut args); - (Some(config_handler), config) - } - Err(err) => { - log::error!("failed to create config handler: {}", err); - (None, Config::default()) - } - }; - - //TODO: support using multiple paths - let Args { mut paths, .. } = args; - let path = paths.pop().unwrap(); - - // TODO: Update video player config when it's updated via the app - let (player_tx, video_queue_lock) = player::run(path, config.clone()); - - let mut settings = Settings::default(); - settings = settings.theme(config.app_theme.theme()); - settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0)); - - let flags = Flags { - config_handler, - config, - player_tx, - video_queue_lock, - }; - cosmic::app::run::(settings, flags)?; - - Ok(()) -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Action { - Todo, - SeekBackward, - SeekForward, -} - -impl Action { - pub fn message(&self) -> Message { - match self { - Self::Todo => Message::Todo, - Self::SeekBackward => Message::Player(PlayerMessage::SeekRelative(-10.0)), - Self::SeekForward => Message::Player(PlayerMessage::SeekRelative(10.0)), - } - } -} - -#[derive(Clone)] -pub struct Flags { - config_handler: Option, - config: Config, - player_tx: mpsc::Sender, - video_queue_lock: Arc>, -} - -/// Messages that are used specifically by our [`App`]. -#[derive(Clone, Debug)] -pub enum Message { - Todo, - AppTheme(AppTheme), - Config(Config), - Key(Modifiers, Key), - Player(PlayerMessage), - SystemThemeModeChange(cosmic_theme::ThemeMode), - Tick(Instant), - ToggleContextPage(ContextPage), - WindowClose, - WindowNew, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ContextPage { - Settings, -} - -impl ContextPage { - fn title(&self) -> String { - match self { - Self::Settings => fl!("settings"), - } - } -} - -/// The [`App`] stores application-specific state. -pub struct App { - core: Core, - flags: Flags, - app_themes: Vec, - context_page: ContextPage, - key_binds: HashMap, - handle_opt: Option, -} - -impl App { - fn update_config(&mut self) -> Command { - cosmic::app::command::set_theme(self.flags.config.app_theme.theme()) - } - - fn update_title(&mut self) -> Command { - let title = "COSMIC Media Player"; - self.set_header_title(title.to_string()); - self.set_window_title(title.to_string()) - } - - fn settings(&self) -> Element { - let app_theme_selected = match self.flags.config.app_theme { - AppTheme::Dark => 1, - AppTheme::Light => 2, - AppTheme::System => 0, - }; - widget::settings::view_column(vec![widget::settings::view_section(fl!("appearance")) - .add( - widget::settings::item::builder(fl!("theme")).control(widget::dropdown( - &self.app_themes, - Some(app_theme_selected), - move |index| { - Message::AppTheme(match index { - 1 => AppTheme::Dark, - 2 => AppTheme::Light, - _ => AppTheme::System, - }) - }, - )), - ) - .into()]) - .into() - } -} - -/// Implement [`Application`] to integrate with COSMIC. -impl Application for App { - /// Default async executor to use with the app. - type Executor = executor::Default; - - /// Argument received - type Flags = Flags; - - /// Message type specific to our [`App`]. - type Message = Message; - - /// The unique application ID to supply to the window manager. - const APP_ID: &'static str = "com.system76.CosmicPlayer"; - - fn core(&self) -> &Core { - &self.core - } - - fn core_mut(&mut self) -> &mut Core { - &mut self.core - } - - /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, flags: Self::Flags) -> (Self, Command) { - let app_themes = vec![fl!("match-desktop"), fl!("dark"), fl!("light")]; - let mut app = App { - core, - flags, - app_themes, - context_page: ContextPage::Settings, - key_binds: key_binds(), - handle_opt: None, - }; - - let command = app.update_title(); - (app, command) - } - - fn on_escape(&mut self) -> Command { - if self.core.window.show_context { - // Close context drawer if open - self.core.window.show_context = false; - } - Command::none() - } - - /// Handle application events here. - fn update(&mut self, message: Self::Message) -> Command { - // Helper for updating config values efficiently - macro_rules! config_set { - ($name: ident, $value: expr) => { - match &self.flags.config_handler { - Some(config_handler) => { - match paste::paste! { self.flags.config.[](config_handler, $value) } { - Ok(_) => {} - Err(err) => { - log::warn!( - "failed to save config {:?}: {}", - stringify!($name), - err - ); - } - } - } - None => { - self.flags.config.$name = $value; - log::warn!( - "failed to save config {:?}: no config handler", - stringify!($name) - ); - } - } - }; - } - - match message { - Message::Todo => { - log::warn!("TODO"); - } - Message::AppTheme(app_theme) => { - config_set!(app_theme, app_theme); - return self.update_config(); - } - Message::Config(config) => { - if config != self.flags.config { - log::info!("update config"); - //TODO: update syntax theme by clearing tabs, only if needed - self.flags.config = config; - return self.update_config(); - } - } - Message::Key(modifiers, key) => { - for (key_bind, action) in self.key_binds.iter() { - if key_bind.matches(modifiers, &key) { - return self.update(action.message()); - } - } - } - Message::Player(player_message) => { - self.flags.player_tx.send(player_message).unwrap(); - } - Message::SystemThemeModeChange(_theme_mode) => { - return self.update_config(); - } - Message::Tick(frame_time) => { - let start = Instant::now(); - - let mut video_frame_opt: Option = None; - let delayed_time = { - let mut video_queue = self.flags.video_queue_lock.lock().unwrap(); - let delayed_time = frame_time - video_queue.delay; - while let Some(video_frame) = video_queue.data.pop_front() { - if video_frame.1.unwrap_or(delayed_time) <= delayed_time { - if let Some(old_frame) = video_frame_opt { - //TODO: log this outside of locking video_queue_lock? - log::warn!("skipping video frame {:?}", old_frame.0.pts()); - } - // Frame is ready to be shown - video_frame_opt = Some(video_frame); - } else { - // Put frame back and exit loop - video_queue.data.push_front(video_frame); - break; - } - } - delayed_time - }; - - match video_frame_opt { - Some(video_frame) => { - let pts = video_frame.0.pts(); - let present_time_opt = video_frame.1; - self.handle_opt = Some(video_frame.into_handle()); - - let duration = start.elapsed(); - log::debug!( - "converted video frame at {:?} to handle in {:?}", - pts, - duration - ); - - if let Some(present_time) = present_time_opt { - if present_time > delayed_time { - let ahead = present_time - delayed_time; - if ahead > Duration::from_millis(1) { - log::debug!("video ahead {:?}", ahead); - } - } else { - let behind = delayed_time - present_time; - if behind > Duration::from_millis(1) { - log::debug!("video behind {:?}", behind); - } - } - } - } - None => {} - } - } - Message::ToggleContextPage(context_page) => { - //TODO: ensure context menus are closed - if self.context_page == context_page { - self.core.window.show_context = !self.core.window.show_context; - } else { - self.context_page = context_page; - self.core.window.show_context = true; - } - self.set_context_title(context_page.title()); - } - Message::WindowClose => { - return window::close(window::Id::MAIN); - } - Message::WindowNew => match env::current_exe() { - Ok(exe) => match process::Command::new(&exe).spawn() { - Ok(_child) => {} - Err(err) => { - log::error!("failed to execute {:?}: {}", exe, err); - } - }, - Err(err) => { - log::error!("failed to get current executable path: {}", err); - } - }, - } - - Command::none() - } - - fn context_drawer(&self) -> Option> { - if !self.core.window.show_context { - return None; - } - - Some(match self.context_page { - ContextPage::Settings => self.settings(), - }) - } - - /// Creates a view after each update. - fn view(&self) -> Element { - let content: Element<_> = match &self.handle_opt { - Some(handle) => widget::image(handle.clone()) - .width(Length::Fill) - .height(Length::Fill) - .into(), - None => widget::text("Loading").into(), - }; - - // Uncomment to debug layout: - //content.explain(cosmic::iced::Color::WHITE) - content - } - - fn subscription(&self) -> Subscription { - struct ConfigSubscription; - struct ThemeSubscription; - - Subscription::batch([ - window::frames().map(|(_window_id, instant)| Message::Tick(instant)), - event::listen_with(|event, _status| match event { - Event::Keyboard(KeyEvent::KeyPressed { key, modifiers, .. }) => { - Some(Message::Key(modifiers, key)) - } - _ => None, - }), - cosmic_config::config_subscription( - TypeId::of::(), - Self::APP_ID.into(), - CONFIG_VERSION, - ) - .map(|update| { - if !update.errors.is_empty() { - log::debug!("errors loading config: {:?}", update.errors); - } - Message::SystemThemeModeChange(update.config) - }), - cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>( - TypeId::of::(), - cosmic_theme::THEME_MODE_ID.into(), - cosmic_theme::ThemeMode::version(), - ) - .map(|update| { - if !update.errors.is_empty() { - log::debug!("errors loading theme mode: {:?}", update.errors); - } - Message::SystemThemeModeChange(update.config) - }), - ]) - } -} diff --git a/src/ffmpeg/player.rs b/src/ffmpeg/player.rs deleted file mode 100644 index 2bf1473..0000000 --- a/src/ffmpeg/player.rs +++ /dev/null @@ -1,654 +0,0 @@ -extern crate ffmpeg_next as ffmpeg; - -use cosmic::widget; -use cpal::{ - traits::{DeviceTrait, HostTrait, StreamTrait}, - FromSample, SizedSample, -}; -use ffmpeg::{ - codec, ffi, - format::{input, Pixel}, - media::Type, - software::{resampling, scaling}, - util::{ - channel_layout, error, - format::sample, - frame::{audio::Audio, video::Video}, - }, - Packet, -}; -use std::{ - cmp, - collections::VecDeque, - error::Error, - path::{Path, PathBuf}, - ptr, slice, - sync::{mpsc, Arc, Mutex}, - thread, - time::{Duration, Instant}, -}; - -use crate::config::Config; - -//TODO: calculate presentation time of end of queue -pub struct AudioQueue { - pub channels: usize, - pub rate: f64, - pub data: VecDeque, - // Delay for data to hit speakers, used to sync with video - pub delay: Duration, -} - -impl AudioQueue { - pub fn new(channels: cpal::ChannelCount, rate: cpal::SampleRate) -> Self { - Self { - channels: channels as usize, - rate: rate.0 as f64, - data: VecDeque::new(), - delay: Duration::default(), - } - } - - pub fn duration(&self) -> Duration { - self.duration_for_samples(self.data.len()) - } - - pub fn duration_for_samples(&self, samples: usize) -> Duration { - let frames = samples / self.channels; - let seconds = (frames as f64) / self.rate; - Duration::from_secs_f64(seconds) - } -} - -#[derive(Clone, Debug)] -pub enum PlayerMessage { - SeekRelative(f64), -} - -pub struct VideoFrame(pub Video, pub Option); - -impl VideoFrame { - pub fn into_handle(self) -> widget::image::Handle { - let width = self.0.width(); - let height = self.0.height(); - widget::image::Handle::from_pixels(width, height, self) - } -} - -impl AsRef<[u8]> for VideoFrame { - fn as_ref(&self) -> &[u8] { - self.0.data(0) - } -} - -pub struct VideoQueue { - pub data: VecDeque, - // Delay to add to each frame to sync with audio - pub delay: Duration, -} - -impl VideoQueue { - pub fn new() -> Self { - Self { - data: VecDeque::new(), - delay: Duration::default(), - } - } - - pub fn push(&mut self, frame: VideoFrame) { - // Discard all frames that are newer than frame to fix seeking and duration calculation - self.data - .retain(|other| other.1.map_or(true, |x| x <= frame.1.unwrap_or(x))); - self.data.push_back(frame); - } - - pub fn duration(&self) -> Duration { - //TODO: can accurate duration actually be calculated since one frame would count as zero? - let mut start_end_opt = None; - for frame in self.data.iter() { - if let Some(frame_time) = frame.1 { - start_end_opt = Some(match start_end_opt { - Some((start, end)) => (cmp::min(start, frame_time), cmp::max(end, frame_time)), - None => (frame_time, frame_time), - }); - } - } - if let Some((start, end)) = start_end_opt { - end.duration_since(start) - } else { - Duration::default() - } - } -} - -fn cpal() -> ( - cpal::SupportedStreamConfig, - Box, - Arc>, -) { - let host = cpal::default_host(); - let device = host - .default_output_device() - .expect("failed to get default audio output device"); - let config = device - .default_output_config() - .expect("failed to get default audio output config"); - println!("{:?}: {:?}", device.name(), config); - - let audio_queue_lock = Arc::new(Mutex::new(AudioQueue::new( - config.channels(), - config.sample_rate(), - ))); - let stream = { - let config = config.clone(); - let audio_queue_lock = audio_queue_lock.clone(); - match config.sample_format() { - cpal::SampleFormat::I8 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::I16 => cpal_stream::(device, config.into(), audio_queue_lock), - // cpal::SampleFormat::I24 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::I32 => cpal_stream::(device, config.into(), audio_queue_lock), - // cpal::SampleFormat::I48 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::I64 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::U8 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::U16 => cpal_stream::(device, config.into(), audio_queue_lock), - // cpal::SampleFormat::U24 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::U32 => cpal_stream::(device, config.into(), audio_queue_lock), - // cpal::SampleFormat::U48 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::U64 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::F32 => cpal_stream::(device, config.into(), audio_queue_lock), - cpal::SampleFormat::F64 => cpal_stream::(device, config.into(), audio_queue_lock), - sample_format => panic!("unsupported sample format '{sample_format}'"), - } - .unwrap() - }; - - (config, stream, audio_queue_lock) -} - -fn cpal_stream( - device: cpal::Device, - config: cpal::StreamConfig, - audio_queue_lock: Arc>, -) -> Result, Box> -where - T: SizedSample + FromSample, -{ - let data_fn = { - move |samples: &mut [T], info: &cpal::OutputCallbackInfo| { - let timestamp = info.timestamp(); - let delay = timestamp.playback.duration_since(×tamp.callback); - - let mut underrun = 0; - { - //TODO: buffer audio - let mut audio_queue = audio_queue_lock.lock().unwrap(); - //TODO: also add samples time? - audio_queue.delay = delay.unwrap_or_default(); - for sample in samples { - let float = match audio_queue.data.pop_front() { - Some(some) => some, - None => { - underrun += 1; - 0.0 - } - }; - *sample = T::from_sample(float); - } - }; - if underrun > 0 { - log::error!("audio underrun {}", underrun); - } - } - }; - let err_fn = |err| eprintln!("an error occurred on stream: {}", err); - let stream = device.build_output_stream(&config, data_fn, err_fn, None)?; - Ok(Box::new(stream)) -} - -fn ffmpeg_thread>( - path: P, - player_rx: mpsc::Receiver, - video_queue_lock: Arc>, - config: Config, -) -> Result<(), Box> { - let (audio_config, cpal_stream, audio_queue_lock) = cpal(); - - let mut ictx = input(&path)?; - - let video_stream = ictx - .streams() - .best(Type::Video) - .ok_or(ffmpeg::Error::StreamNotFound)?; - let video_stream_index = video_stream.index(); - let video_time_base = f64::from(video_stream.time_base()); - - let mut video_decoder = { - let mut video_decoder_context = - codec::context::Context::from_parameters(video_stream.parameters())?; - - //TODO: safe wrappers - let mut hw_device_ctx = ptr::null_mut(); - unsafe { - //TODO: support other types - let hw_device_kind = config.hw_decoder; - if ffi::av_hwdevice_ctx_create( - &mut hw_device_ctx, - hw_device_kind.into(), - ptr::null(), - ptr::null_mut(), - 0, - ) == 0 - { - log::info!("using {hw_device_kind} decoding"); - (&mut *video_decoder_context.as_mut_ptr()).hw_device_ctx = - ffi::av_buffer_ref(hw_device_ctx); - } else { - //TODO: support other hardware devices - log::warn!( - "failed to use {hw_device_kind} decoding, falling back to software decoding" - ); - } - } - - video_decoder_context.decoder().video()? - }; - - let (cpu_frame_tx, cpu_frame_rx) = mpsc::channel::<(Video, Option)>(); - { - let video_format = video_decoder.format(); - let video_width = video_decoder.width(); - let video_height = video_decoder.height(); - let video_queue_lock = video_queue_lock.clone(); - thread::Builder::new() - .name("video_scale".to_string()) - .spawn(move || { - let mut video_scaler = scaling::context::Context::get( - video_format, - video_width, - video_height, - Pixel::RGBA, - video_width, - video_height, - scaling::Flags::FAST_BILINEAR, - ) - .unwrap(); - - loop { - let mut recv_opt: Option<(Video, Option)> = None; - /*TODO: SKIP - while let Ok(recv) = cpu_frame_rx.try_recv() { - if let Some((old_frame, _)) = recv_opt { - //TODO: only skip if behind (frames come in weird timing from codecs) - log::warn!("skipping cpu video frame at {:?}", old_frame.pts()); - } - recv_opt = Some(recv); - } - */ - let (cpu_frame, sync_time_opt) = match recv_opt { - Some(some) => some, - None => cpu_frame_rx.recv().unwrap(), - }; - let pts_opt = cpu_frame.pts(); - - // Start count after blocking recv - let start = Instant::now(); - - video_scaler.cached( - cpu_frame.format(), - cpu_frame.width(), - cpu_frame.height(), - Pixel::RGBA, - cpu_frame.width(), - cpu_frame.height(), - scaling::Flags::FAST_BILINEAR, - ); - - let mut scaled_frame = Video::empty(); - video_scaler.run(&cpu_frame, &mut scaled_frame).unwrap(); - scaled_frame.set_pts(pts_opt); - - let present_time_opt = if let Some(pts) = pts_opt { - let expected_float = pts as f64 * video_time_base; - let expected = Duration::from_secs_f64(expected_float); - if let Some(sync_time) = sync_time_opt { - Some(sync_time + expected) - } else { - None - } - } else { - None - }; - - let video_frame = VideoFrame(scaled_frame, present_time_opt); - { - let mut video_queue = video_queue_lock.lock().unwrap(); - video_queue.push(video_frame); - } - - let duration = start.elapsed(); - log::debug!("scaled video frame at {:?} in {:?}", pts_opt, duration,); - } - })? - }; - - // Sync channel to prevent allocation issues and falling behind - let (gpu_frame_tx, gpu_frame_rx) = mpsc::sync_channel::<(Video, Option)>(2); - thread::Builder::new() - .name("video_map_gpu_cpu".to_string()) - .spawn(move || { - loop { - let mut recv_opt: Option<(Video, Option)> = None; - /*TODO: SKIP - while let Ok(recv) = gpu_frame_rx.try_recv() { - if let Some((old_frame, _)) = recv_opt { - //TODO: only skip if behind (frames come in weird timing from codecs) - log::warn!("skipping gpu video frame at {:?}", old_frame.pts()); - } - recv_opt = Some(recv); - } - */ - let (gpu_frame, sync_time_opt) = match recv_opt { - Some(some) => some, - None => gpu_frame_rx.recv().unwrap(), - }; - let pts = gpu_frame.pts(); - - // Start timer after blocking recv - let start = Instant::now(); - - let mut cpu_frame = Video::empty(); - unsafe { - if (&*gpu_frame.as_ptr()).hw_frames_ctx.is_null() { - cpu_frame = gpu_frame; - } else { - if ffi::av_hwframe_transfer_data( - cpu_frame.as_mut_ptr(), - gpu_frame.as_ptr(), - 0, - ) < 0 - { - panic!("av_hwframe_transfer_data failed"); - } - /*TODO: MAP OR TRANSFER? - if ffi::av_hwframe_map( - cpu_frame.as_mut_ptr(), - gpu_frame.as_ptr(), - ffi::AV_HWFRAME_MAP_READ as i32, - ) < 0 - { - panic!("av_hwframe_map failed"); - } - */ - } - } - cpu_frame.set_pts(pts); - cpu_frame_tx.send((cpu_frame, sync_time_opt)).unwrap(); - - let duration = start.elapsed(); - log::debug!("map gpu video frame to cpu at {:?} in {:?}", pts, duration); - } - })?; - - // Sync channel to prevent getting too far behind - let (video_packet_tx, video_packet_rx) = mpsc::sync_channel::<(Packet, Option)>(2); - thread::Builder::new() - .name("video_decode".to_string()) - .spawn(move || { - let mut eof = false; - while !eof { - let mut sync_time_opt = None; - - { - let packet_res = video_packet_rx.recv(); - - // Start timer after blocking recv - let start = Instant::now(); - - let mut packet_pts = None; - match packet_res { - Ok((packet, time_opt)) => { - packet_pts = packet.pts(); - sync_time_opt = time_opt; - video_decoder.send_packet(&packet).unwrap(); - } - Err(_err) => { - video_decoder.send_eof().unwrap(); - eof = true; - } - } - - let duration = start.elapsed(); - log::debug!("sent packet at {:?} in {:?}", packet_pts, duration); - } - - let start = Instant::now(); - - let mut pts = None; - let mut video_frames = 0; - loop { - let mut gpu_frame = Video::empty(); - if video_decoder.receive_frame(&mut gpu_frame).is_ok() { - pts = gpu_frame.pts(); - gpu_frame_tx.send((gpu_frame, sync_time_opt)).unwrap(); - video_frames += 1; - } else { - break; - } - } - - if video_frames > 0 { - let duration = start.elapsed(); - log::debug!( - "received {} video frames at {:?} in {:?}", - video_frames, - pts, - duration - ); - } - } - })?; - - let audio_stream = ictx - .streams() - .best(Type::Audio) - .ok_or(ffmpeg::Error::StreamNotFound)?; - let audio_stream_index = audio_stream.index(); - let audio_time_base = f64::from(audio_stream.time_base()); - - let audio_context_decoder = - codec::context::Context::from_parameters(audio_stream.parameters())?; - let mut audio_decoder = audio_context_decoder.decoder().audio()?; - - let mut audio_resampler = resampling::Context::get( - audio_decoder.format(), - audio_decoder.channel_layout(), - audio_decoder.rate(), - //TODO: support other formats? - sample::Sample::F32(sample::Type::Packed), - match audio_config.channels() { - 1 => channel_layout::ChannelLayout::MONO, - 2 => channel_layout::ChannelLayout::STEREO, - //TODO: more channel configs - unsupported => { - panic!("unsupported audio channels {:?}", unsupported); - } - }, - audio_config.sample_rate().0, - )?; - - let min_sleep = Duration::from_millis(1); - let min_skip = Duration::from_millis(1); - let mut receive_and_process_decoded_audio_frames = |decoder: &mut ffmpeg::decoder::Audio, - sync_time_opt: &mut Option| - -> Result<(), ffmpeg::Error> { - let mut decoded = Audio::empty(); - let mut resampled = Audio::empty(); - let mut pts_opt = None; - while decoder.receive_frame(&mut decoded).is_ok() { - pts_opt = decoded.pts(); - - audio_resampler.run(&decoded, &mut resampled)?; - { - // plane method doesn't work with packed samples, so do it manually - let plane = unsafe { - slice::from_raw_parts( - (*resampled.as_ptr()).data[0] as *const f32, - resampled.samples() * resampled.channels() as usize, - ) - }; - { - let mut audio_queue = audio_queue_lock.lock().unwrap(); - audio_queue.data.extend(plane); - } - } - } - - if let Some(pts) = pts_opt { - let expected_float = pts as f64 * audio_time_base; - let expected = Duration::from_secs_f64(expected_float); - if let Some(sync_time) = &sync_time_opt { - // Sync with audio - let actual = sync_time.elapsed(); - if expected > actual { - let sleep = expected - actual; - if sleep > min_sleep { - // We leave min_sleep of buffer room - log::debug!("audio ahead {:?}", sleep); - } - } else { - let skip = actual - expected; - if skip > min_skip { - //TODO: handle frame skipping - log::debug!("audio behind {:?}", skip); - } - } - } else { - // Set up sync - *sync_time_opt = Some(Instant::now() - expected); - } - } - Ok(()) - }; - - //TODO: dynamically choose this - let buffer_duration = Duration::from_millis(250); - - // Start CPAL stream - cpal_stream.play()?; - - let mut sync_time_opt = None; - let mut seconds_opt = None; - loop { - let mut packet = Packet::empty(); - match packet.read(&mut ictx) { - Ok(()) => { - if packet.stream() == video_stream_index { - video_packet_tx.send((packet, sync_time_opt)).unwrap(); - } else if packet.stream() == audio_stream_index { - audio_decoder.send_packet(&packet)?; - receive_and_process_decoded_audio_frames( - &mut audio_decoder, - &mut sync_time_opt, - )?; - if let Some(pts) = packet.pts() { - seconds_opt = Some(pts as f64 * audio_time_base); - } - } - } - Err(error::Error::Eof) => break, - Err(_err) => {} - } - - let (audio_queue_duration, audio_queue_delay) = { - let audio_queue = audio_queue_lock.lock().unwrap(); - (audio_queue.duration(), audio_queue.delay) - }; - - let (video_queue_duration, video_queue_delay) = { - let mut video_queue = video_queue_lock.lock().unwrap(); - let video_queue_duration = video_queue.duration(); - if video_queue_duration < buffer_duration { - // If we do not have enough video queued, delay the video output - video_queue.delay = buffer_duration - video_queue_duration; - } else { - video_queue.delay = Duration::default(); - } - // Add audio queue delay to sync with audio - video_queue.delay += audio_queue_delay; - (video_queue_duration, video_queue.delay) - }; - - log::debug!( - "video: {:?}, {:?} audio: {:?}, {:?}", - video_queue_duration, - video_queue_delay, - audio_queue_duration, - audio_queue_delay - ); - - let min_queue_duration = cmp::min(video_queue_duration, audio_queue_duration); - if min_queue_duration > buffer_duration { - // If we have enough queued, we can sleep - let sleep = min_queue_duration - buffer_duration; - log::debug!("sleep {:?}", sleep); - thread::sleep(sleep); - } - - while let Ok(message) = player_rx.try_recv() { - match message { - PlayerMessage::SeekRelative(seek_seconds) => { - if let Some(seconds) = seconds_opt { - //TODO: use time base instead of hardcoded values - let timestamp = ((seconds + seek_seconds) * 1000000.0) as i64; - if seek_seconds.is_sign_negative() { - println!( - "backwards {} from {} = {}", - seek_seconds, seconds, timestamp - ); - ictx.seek(timestamp, ..timestamp)?; - } else { - println!("forwards {} from {} = {}", seek_seconds, seconds, timestamp); - ictx.seek(timestamp, timestamp..)?; - } - - // Clear audio sync time - sync_time_opt = None; - // Clear audio and video queues - { - let mut audio_queue = audio_queue_lock.lock().unwrap(); - audio_queue.data.clear(); - } - { - //TODO: clear pending data stuck in channels - let mut video_queue = video_queue_lock.lock().unwrap(); - video_queue.data.clear(); - } - } - } - } - } - } - - audio_decoder.send_eof()?; - receive_and_process_decoded_audio_frames(&mut audio_decoder, &mut sync_time_opt)?; - - Ok(()) -} - -pub fn run(path: PathBuf, config: Config) -> (mpsc::Sender, Arc>) { - ffmpeg::init().unwrap(); - - let (player_tx, player_rx) = mpsc::channel(); - let video_queue_lock = Arc::new(Mutex::new(VideoQueue::new())); - { - let video_queue_lock = video_queue_lock.clone(); - thread::Builder::new() - .name("ffmpeg".to_string()) - .spawn(move || { - ffmpeg_thread(path, player_rx, video_queue_lock, config).unwrap(); - }) - .unwrap(); - } - - (player_tx, video_queue_lock) -} diff --git a/src/gstreamer/mod.rs b/src/gstreamer/mod.rs deleted file mode 100644 index 0c9030f..0000000 --- a/src/gstreamer/mod.rs +++ /dev/null @@ -1,585 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use cosmic::{ - app::{message, Command, Core, Settings}, - cosmic_config::{self, CosmicConfigEntry}, - cosmic_theme, executor, font, - iced::{ - event::{self, Event}, - keyboard::{Event as KeyEvent, Key, Modifiers}, - mouse::Event as MouseEvent, - subscription::Subscription, - window, Alignment, Color, Length, Limits, - }, - theme, - widget::{self, Slider}, - Application, ApplicationExt, Element, -}; -use iced_video_player::{ - gst::{self, prelude::*}, - gst_pbutils, Video, VideoPlayer, -}; -use std::{ - any::TypeId, - collections::HashMap, - time::{Duration, Instant}, -}; - -use crate::{ - config::{Config, CONFIG_VERSION}, - key_bind::{key_binds, KeyBind}, - localize, -}; - -static CONTROLS_TIMEOUT: Duration = Duration::new(2, 0); - -const GST_PLAY_FLAG_VIDEO: i32 = 1 << 0; -const GST_PLAY_FLAG_AUDIO: i32 = 1 << 1; -const GST_PLAY_FLAG_TEXT: i32 = 1 << 2; - -/// Runs application with these settings -#[rustfmt::skip] -pub fn main() -> Result<(), Box> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); - - localize::localize(); - - let (config_handler, config) = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) { - Ok(config_handler) => { - let config = match Config::get_entry(&config_handler) { - Ok(ok) => ok, - Err((errs, config)) => { - log::info!("errors loading config: {:?}", errs); - config - } - }; - (Some(config_handler), config) - } - Err(err) => { - log::error!("failed to create config handler: {}", err); - (None, Config::default()) - } - }; - - let mut settings = Settings::default(); - settings = settings.theme(config.app_theme.theme()); - settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0)); - - let url = url::Url::from_file_path( - std::env::args().nth(1).unwrap() - ) - .unwrap(); - let flags = Flags { - config_handler, - config, - url, - }; - cosmic::app::run::(settings, flags)?; - - Ok(()) -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Action { - Fullscreen, - PlayPause, - SeekBackward, - SeekForward, -} - -impl Action { - pub fn message(&self) -> Message { - match self { - Self::Fullscreen => Message::Fullscreen, - Self::PlayPause => Message::PlayPause, - Self::SeekBackward => Message::SeekRelative(-10.0), - Self::SeekForward => Message::SeekRelative(10.0), - } - } -} - -#[derive(Clone)] -pub struct Flags { - config_handler: Option, - config: Config, - url: url::Url, -} - -/// Messages that are used specifically by our [`App`]. -#[derive(Clone, Debug)] -pub enum Message { - Config(Config), - Fullscreen, - Key(Modifiers, Key), - AudioCode(usize), - TextCode(usize), - PlayPause, - Seek(f64), - SeekRelative(f64), - SeekRelease, - EndOfStream, - MissingPlugin(gst::Message), - NewFrame, - Reload, - ShowControls, - SystemThemeModeChange(cosmic_theme::ThemeMode), -} - -/// The [`App`] stores application-specific state. -pub struct App { - core: Core, - flags: Flags, - controls: bool, - controls_time: Instant, - fullscreen: bool, - key_binds: HashMap, - video_opt: Option