local: integrate yoda libcosmic and fix thumbnail hang
Some checks are pending
Validate .desktop files / validate (push) Waiting to run
Some checks are pending
Validate .desktop files / validate (push) Waiting to run
This commit is contained in:
parent
d1f63c570c
commit
22e5890a7c
16 changed files with 2388 additions and 310 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -5,4 +5,8 @@
|
||||||
/debian/files
|
/debian/files
|
||||||
/target/
|
/target/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
!/vendor/
|
||||||
|
/vendor/*
|
||||||
|
!/vendor/iced_video_player/
|
||||||
|
!/vendor/iced_video_player/**
|
||||||
/vendor.tar
|
/vendor.tar
|
||||||
|
|
|
||||||
401
Cargo.lock
generated
401
Cargo.lock
generated
|
|
@ -153,12 +153,6 @@ dependencies = [
|
||||||
"equator",
|
"equator",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "allocator-api2"
|
|
||||||
version = "0.2.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "almost"
|
name = "almost"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -308,12 +302,6 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "as-raw-xcb-connection"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "as-slice"
|
name = "as-slice"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -884,7 +872,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard_macos"
|
name = "clipboard_macos"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
|
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
"objc-foundation",
|
"objc-foundation",
|
||||||
|
|
@ -894,22 +882,13 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard_wayland"
|
name = "clipboard_wayland"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
|
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dnd",
|
"dnd",
|
||||||
"mime",
|
"mime",
|
||||||
"smithay-clipboard",
|
"smithay-clipboard",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clipboard_x11"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
"x11rb",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa"
|
name = "cocoa"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
|
|
@ -1074,7 +1053,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-config"
|
name = "cosmic-config"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomicwrites",
|
"atomicwrites",
|
||||||
"cosmic-config-derive",
|
"cosmic-config-derive",
|
||||||
|
|
@ -1095,7 +1073,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-config-derive"
|
name = "cosmic-config-derive"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
|
|
@ -1130,7 +1107,7 @@ dependencies = [
|
||||||
"icu_collator",
|
"icu_collator",
|
||||||
"icu_locale",
|
"icu_locale",
|
||||||
"image",
|
"image",
|
||||||
"libcosmic",
|
"libcosmic-yoda",
|
||||||
"log",
|
"log",
|
||||||
"mpris-server",
|
"mpris-server",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
|
|
@ -1167,8 +1144,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-text"
|
name = "cosmic-text"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/pop-os/cosmic-text.git#c24886c2471e5606587c46090cd25dbbf209186b"
|
||||||
checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
|
|
@ -1191,7 +1167,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-theme"
|
name = "cosmic-theme"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"almost",
|
"almost",
|
||||||
"configparser",
|
"configparser",
|
||||||
|
|
@ -1257,7 +1232,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryoglyph"
|
name = "cryoglyph"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/iced-rs/cryoglyph.git?rev=e429a025df36ab8145708acb309080ae3deec17a#e429a025df36ab8145708acb309080ae3deec17a"
|
source = "git+https://github.com/pop-os/glyphon.git?tag=cosmic-0.14#c49de15bce4d8254ac136d1be9911960cc85ce12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
"etagere",
|
"etagere",
|
||||||
|
|
@ -1294,15 +1269,6 @@ dependencies = [
|
||||||
"uncased",
|
"uncased",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ctor"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf"
|
|
||||||
dependencies = [
|
|
||||||
"dtor",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cursor-icon"
|
name = "cursor-icon"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
@ -1448,7 +1414,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dnd"
|
name = "dnd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
|
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"mime",
|
"mime",
|
||||||
|
|
@ -1477,51 +1443,6 @@ name = "dpi"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366"
|
source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "drm"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.1",
|
|
||||||
"bytemuck",
|
|
||||||
"drm-ffi",
|
|
||||||
"drm-fourcc",
|
|
||||||
"rustix 0.38.44",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "drm-ffi"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6"
|
|
||||||
dependencies = [
|
|
||||||
"drm-sys",
|
|
||||||
"rustix 0.38.44",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "drm-fourcc"
|
|
||||||
version = "2.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "drm-sys"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"linux-raw-sys 0.6.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dtor"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
@ -2018,16 +1939,6 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gethostname"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
|
||||||
dependencies = [
|
|
||||||
"rustix 1.1.4",
|
|
||||||
"windows-link 0.2.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
|
@ -2190,17 +2101,34 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpu-allocator"
|
name = "gpu-alloc"
|
||||||
version = "0.28.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795"
|
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"gpu-alloc-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-alloc-types"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-allocator"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ash",
|
|
||||||
"hashbrown 0.16.1",
|
|
||||||
"log",
|
"log",
|
||||||
"presser",
|
"presser",
|
||||||
"thiserror 2.0.18",
|
"thiserror 1.0.69",
|
||||||
"windows 0.62.2",
|
"windows 0.58.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2486,8 +2414,6 @@ version = "0.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2",
|
|
||||||
"equivalent",
|
|
||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2600,7 +2526,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced"
|
name = "iced"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dnd",
|
"dnd",
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
|
|
@ -2621,7 +2546,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_accessibility"
|
name = "iced_accessibility"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_winit",
|
"accesskit_winit",
|
||||||
|
|
@ -2630,7 +2554,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -2654,7 +2577,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_debug"
|
name = "iced_debug"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
|
|
@ -2664,7 +2586,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_futures"
|
name = "iced_futures"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
|
@ -2678,7 +2599,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_graphics"
|
name = "iced_graphics"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
@ -2699,7 +2619,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_program"
|
name = "iced_program"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_graphics",
|
"iced_graphics",
|
||||||
"iced_runtime",
|
"iced_runtime",
|
||||||
|
|
@ -2708,7 +2627,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_renderer"
|
name = "iced_renderer"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_graphics",
|
"iced_graphics",
|
||||||
"iced_tiny_skia",
|
"iced_tiny_skia",
|
||||||
|
|
@ -2720,7 +2638,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_runtime"
|
name = "iced_runtime"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"cosmic-client-toolkit",
|
"cosmic-client-toolkit",
|
||||||
|
|
@ -2735,7 +2652,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_tiny_skia"
|
name = "iced_tiny_skia"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
|
|
@ -2752,7 +2668,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_video_player"
|
name = "iced_video_player"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/wash2/iced_video_player.git?branch=iced-rebase#cb3635192f3dec37619855c904a168ca37a3dac3"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glib",
|
"glib",
|
||||||
"gstreamer",
|
"gstreamer",
|
||||||
|
|
@ -2761,7 +2676,7 @@ dependencies = [
|
||||||
"gstreamer-pbutils",
|
"gstreamer-pbutils",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
"iced_wgpu",
|
"iced_wgpu",
|
||||||
"libcosmic",
|
"libcosmic-yoda",
|
||||||
"log",
|
"log",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"url",
|
"url",
|
||||||
|
|
@ -2770,9 +2685,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_wgpu"
|
name = "iced_wgpu"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as-raw-xcb-connection",
|
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cosmic-client-toolkit",
|
"cosmic-client-toolkit",
|
||||||
|
|
@ -2789,19 +2702,16 @@ dependencies = [
|
||||||
"rustc-hash 2.1.2",
|
"rustc-hash 2.1.2",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tiny-xlib",
|
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
"wayland-sys",
|
"wayland-sys",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"x11rb",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_widget"
|
name = "iced_widget"
|
||||||
version = "0.14.2"
|
version = "0.14.2"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cosmic-client-toolkit",
|
"cosmic-client-toolkit",
|
||||||
"dnd",
|
"dnd",
|
||||||
|
|
@ -2819,7 +2729,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_winit"
|
name = "iced_winit"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cosmic-client-toolkit",
|
"cosmic-client-toolkit",
|
||||||
"cursor-icon",
|
"cursor-icon",
|
||||||
|
|
@ -3379,9 +3288,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libcosmic"
|
name = "libcosmic-yoda"
|
||||||
version = "1.0.0"
|
version = "0.1.0-yoda.2"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"apply",
|
"apply",
|
||||||
"ashpd 0.12.3",
|
"ashpd 0.12.3",
|
||||||
|
|
@ -3483,12 +3391,6 @@ version = "0.4.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.6.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -3643,9 +3545,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "metal"
|
name = "metal"
|
||||||
version = "0.33.0"
|
version = "0.32.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"
|
checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"block",
|
"block",
|
||||||
|
|
@ -3659,7 +3561,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
|
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"smithay-clipboard",
|
"smithay-clipboard",
|
||||||
]
|
]
|
||||||
|
|
@ -3723,9 +3625,9 @@ checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "28.0.0"
|
version = "27.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135"
|
checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
|
|
@ -5334,12 +5236,10 @@ name = "softbuffer"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#c2b2c19ddb38ff17495643699f97cb1f2064a1be"
|
source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#c2b2c19ddb38ff17495643699f97cb1f2064a1be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as-raw-xcb-connection",
|
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"drm",
|
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|
@ -5349,14 +5249,12 @@ dependencies = [
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall 0.5.18",
|
"redox_syscall 0.5.18",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"tiny-xlib",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-sys",
|
"wayland-sys",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
"x11rb",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -5619,19 +5517,6 @@ dependencies = [
|
||||||
"strict-num",
|
"strict-num",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tiny-xlib"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4"
|
|
||||||
dependencies = [
|
|
||||||
"as-raw-xcb-connection",
|
|
||||||
"ctor",
|
|
||||||
"libloading",
|
|
||||||
"pkg-config",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -6366,13 +6251,12 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu"
|
name = "wgpu"
|
||||||
version = "28.0.0"
|
version = "27.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f"
|
checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"bytemuck",
|
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"document-features",
|
"document-features",
|
||||||
|
|
@ -6396,9 +6280,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core"
|
name = "wgpu-core"
|
||||||
version = "28.0.1"
|
version = "27.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d23f4642f53f666adcfd2d3218ab174d1e6681101aef18696b90cbe64d1c10f9"
|
checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
|
|
@ -6428,36 +6312,36 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-apple"
|
name = "wgpu-core-deps-apple"
|
||||||
version = "28.0.0"
|
version = "27.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da"
|
checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-emscripten"
|
name = "wgpu-core-deps-emscripten"
|
||||||
version = "28.0.0"
|
version = "27.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79"
|
checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-windows-linux-android"
|
name = "wgpu-core-deps-windows-linux-android"
|
||||||
version = "28.0.0"
|
version = "27.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae"
|
checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-hal"
|
name = "wgpu-hal"
|
||||||
version = "28.0.1"
|
version = "27.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44d6cb474beb218824dcc9e1ce679d973f719262789bfb27407da560cac20eeb"
|
checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
|
|
@ -6471,6 +6355,7 @@ dependencies = [
|
||||||
"core-graphics-types 0.2.0",
|
"core-graphics-types 0.2.0",
|
||||||
"glow",
|
"glow",
|
||||||
"glutin_wgl_sys",
|
"glutin_wgl_sys",
|
||||||
|
"gpu-alloc",
|
||||||
"gpu-allocator",
|
"gpu-allocator",
|
||||||
"gpu-descriptor",
|
"gpu-descriptor",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
|
@ -6497,20 +6382,21 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
"windows 0.62.2",
|
"windows 0.58.0",
|
||||||
"windows-core 0.62.2",
|
"windows-core 0.58.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-types"
|
name = "wgpu-types"
|
||||||
version = "28.0.0"
|
version = "27.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c"
|
checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
|
"thiserror 2.0.18",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -6548,12 +6434,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "window_clipboard"
|
name = "window_clipboard"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
|
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clipboard-win",
|
"clipboard-win",
|
||||||
"clipboard_macos",
|
"clipboard_macos",
|
||||||
"clipboard_wayland",
|
"clipboard_wayland",
|
||||||
"clipboard_x11",
|
|
||||||
"dnd",
|
"dnd",
|
||||||
"mime",
|
"mime",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
|
|
@ -6562,27 +6447,25 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.61.3"
|
version = "0.58.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-collections 0.2.0",
|
"windows-core 0.58.0",
|
||||||
"windows-core 0.61.2",
|
"windows-targets 0.52.6",
|
||||||
"windows-future 0.2.1",
|
|
||||||
"windows-link 0.1.3",
|
|
||||||
"windows-numerics 0.2.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.62.2"
|
version = "0.61.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-collections 0.3.2",
|
"windows-collections",
|
||||||
"windows-core 0.62.2",
|
"windows-core 0.61.2",
|
||||||
"windows-future 0.3.2",
|
"windows-future",
|
||||||
"windows-numerics 0.3.1",
|
"windows-link 0.1.3",
|
||||||
|
"windows-numerics",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6595,12 +6478,16 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-collections"
|
name = "windows-core"
|
||||||
version = "0.3.2"
|
version = "0.58.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.62.2",
|
"windows-implement 0.58.0",
|
||||||
|
"windows-interface 0.58.0",
|
||||||
|
"windows-result 0.2.0",
|
||||||
|
"windows-strings 0.1.0",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6609,26 +6496,13 @@ version = "0.61.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement 0.60.2",
|
||||||
"windows-interface",
|
"windows-interface 0.59.3",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
"windows-strings 0.4.2",
|
"windows-strings 0.4.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.62.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-implement",
|
|
||||||
"windows-interface",
|
|
||||||
"windows-link 0.2.1",
|
|
||||||
"windows-result 0.4.1",
|
|
||||||
"windows-strings 0.5.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-future"
|
name = "windows-future"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -6637,18 +6511,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-threading 0.1.0",
|
"windows-threading",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-future"
|
name = "windows-implement"
|
||||||
version = "0.3.2"
|
version = "0.58.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.62.2",
|
"proc-macro2",
|
||||||
"windows-link 0.2.1",
|
"quote",
|
||||||
"windows-threading 0.2.1",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6662,6 +6536,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.59.3"
|
version = "0.59.3"
|
||||||
|
|
@ -6696,13 +6581,12 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-numerics"
|
name = "windows-result"
|
||||||
version = "0.3.1"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.62.2",
|
"windows-targets 0.52.6",
|
||||||
"windows-link 0.2.1",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6715,12 +6599,13 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-strings"
|
||||||
version = "0.4.1"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-result 0.2.0",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6732,15 +6617,6 @@ dependencies = [
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-strings"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.2.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
|
@ -6843,15 +6719,6 @@ dependencies = [
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-threading"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.2.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
|
|
@ -7013,7 +6880,6 @@ dependencies = [
|
||||||
"winit-wayland",
|
"winit-wayland",
|
||||||
"winit-web",
|
"winit-web",
|
||||||
"winit-win32",
|
"winit-win32",
|
||||||
"winit-x11",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -7064,7 +6930,6 @@ dependencies = [
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"tracing",
|
"tracing",
|
||||||
"winit-core",
|
"winit-core",
|
||||||
"x11-dl",
|
|
||||||
"xkbcommon-dl",
|
"xkbcommon-dl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -7182,29 +7047,6 @@ dependencies = [
|
||||||
"winit-core",
|
"winit-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winit-x11"
|
|
||||||
version = "0.31.0-beta.2"
|
|
||||||
source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.1",
|
|
||||||
"bytemuck",
|
|
||||||
"calloop",
|
|
||||||
"cursor-icon",
|
|
||||||
"dpi",
|
|
||||||
"libc",
|
|
||||||
"percent-encoding",
|
|
||||||
"raw-window-handle",
|
|
||||||
"rustix 1.1.4",
|
|
||||||
"smol_str",
|
|
||||||
"tracing",
|
|
||||||
"winit-common",
|
|
||||||
"winit-core",
|
|
||||||
"x11-dl",
|
|
||||||
"x11rb",
|
|
||||||
"xkbcommon-dl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -7320,39 +7162,6 @@ version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x11-dl"
|
|
||||||
version = "2.21.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"pkg-config",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x11rb"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
|
||||||
dependencies = [
|
|
||||||
"as-raw-xcb-connection",
|
|
||||||
"gethostname",
|
|
||||||
"libc",
|
|
||||||
"libloading",
|
|
||||||
"once_cell",
|
|
||||||
"rustix 1.1.4",
|
|
||||||
"x11rb-protocol",
|
|
||||||
"xcursor",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x11rb-protocol"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xcursor"
|
name = "xcursor"
|
||||||
version = "0.3.10"
|
version = "0.3.10"
|
||||||
|
|
|
||||||
18
Cargo.toml
18
Cargo.toml
|
|
@ -32,12 +32,12 @@ env_logger = "0.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
[dependencies.iced_video_player]
|
[dependencies.iced_video_player]
|
||||||
git = "https://github.com/wash2/iced_video_player.git"
|
path = "vendor/iced_video_player"
|
||||||
branch = "iced-rebase"
|
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.cosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic.git"
|
package = "libcosmic-yoda"
|
||||||
|
path = "../libcosmic"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["advanced-shaping", "tokio", "winit", "multi-window"]
|
features = ["advanced-shaping", "tokio", "winit", "multi-window"]
|
||||||
|
|
||||||
|
|
@ -51,9 +51,9 @@ fork = "0.7"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mpris-server", "xdg-portal", "wgpu", "wayland"]
|
default = ["mpris-server", "xdg-portal", "wgpu", "wayland"]
|
||||||
xdg-portal = ["ashpd", "libcosmic/xdg-portal"]
|
xdg-portal = ["ashpd", "cosmic/xdg-portal"]
|
||||||
wgpu = ["iced_video_player/wgpu", "libcosmic/wgpu"]
|
wgpu = ["iced_video_player/wgpu", "cosmic/wgpu"]
|
||||||
wayland = ["libcosmic/wayland"]
|
wayland = ["cosmic/wayland"]
|
||||||
|
|
||||||
[profile.release-with-debug]
|
[profile.release-with-debug]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|
@ -62,10 +62,6 @@ debug = true
|
||||||
# [patch.'https://github.com/wash2/iced_video_player']
|
# [patch.'https://github.com/wash2/iced_video_player']
|
||||||
# iced_video_player = { path = "../iced_video_player" }
|
# iced_video_player = { path = "../iced_video_player" }
|
||||||
|
|
||||||
# [patch.'https://github.com/pop-os/libcosmic']
|
|
||||||
# libcosmic = { path = "../libcosmic" }
|
|
||||||
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
|
||||||
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
|
||||||
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
|
|
|
||||||
32
README.md
32
README.md
|
|
@ -1,2 +1,34 @@
|
||||||
# cosmic-player
|
# cosmic-player
|
||||||
WIP COSMIC media player
|
WIP COSMIC media player
|
||||||
|
|
||||||
|
## Yoda local fork notes
|
||||||
|
|
||||||
|
This fork is used for the local COSMIC desktop build.
|
||||||
|
|
||||||
|
Local integration changes:
|
||||||
|
|
||||||
|
- `cosmic-player` depends on the local `libcosmic-yoda` crate from `../libcosmic`
|
||||||
|
instead of upstream `pop-os/libcosmic`.
|
||||||
|
- `iced_video_player` is vendored in `vendor/iced_video_player` so it uses the
|
||||||
|
same local `libcosmic-yoda` and `iced_wgpu` stack. This avoids mixing upstream
|
||||||
|
and local Iced/libcosmic types.
|
||||||
|
- The app now runs `App::close()` before application exit and before the Quit
|
||||||
|
menu action exits the runtime. This lets the GStreamer pipeline be dropped
|
||||||
|
normally instead of short-circuiting through `process::exit(0)`.
|
||||||
|
- Thumbnail generation has a bounded wait for video frames. Some files/codecs can
|
||||||
|
fail to produce a frame; the thumbnailer now returns an error instead of
|
||||||
|
spinning forever as a background `cosmic-player --thumbnail ...` process.
|
||||||
|
|
||||||
|
Validation commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo +1.93.0 check
|
||||||
|
cargo +1.93.0 tree -i libcosmic-yoda --depth 2
|
||||||
|
cargo +1.93.0 tree -i libcosmic --depth 2 || true
|
||||||
|
```
|
||||||
|
|
||||||
|
Local redeploy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
COSMIC_PLAYER_REPO=/home/lionel/Projets/COSMIC/cosmic-player ./redeploy.sh
|
||||||
|
```
|
||||||
|
|
|
||||||
12
src/main.rs
12
src/main.rs
|
|
@ -7,10 +7,10 @@ use cosmic::cosmic_config::{self, CosmicConfigEntry};
|
||||||
use cosmic::iced::event::{self, Event};
|
use cosmic::iced::event::{self, Event};
|
||||||
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
|
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
|
||||||
use cosmic::iced::mouse::{Event as MouseEvent, ScrollDelta};
|
use cosmic::iced::mouse::{Event as MouseEvent, ScrollDelta};
|
||||||
use cosmic::iced::window::{self, set_mode};
|
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
Alignment, Background, Border, Color, ContentFit, Length, Limits, Subscription,
|
Alignment, Background, Border, Color, ContentFit, Length, Limits, Subscription, exit, window,
|
||||||
};
|
};
|
||||||
|
use cosmic::iced::window::set_mode;
|
||||||
use cosmic::widget::menu::action::MenuAction;
|
use cosmic::widget::menu::action::MenuAction;
|
||||||
use cosmic::widget::{self, Slider, nav_bar, segmented_button};
|
use cosmic::widget::{self, Slider, nav_bar, segmented_button};
|
||||||
use cosmic::{Application, ApplicationExt, Element, action, cosmic_theme, executor, font, theme};
|
use cosmic::{Application, ApplicationExt, Element, action, cosmic_theme, executor, font, theme};
|
||||||
|
|
@ -951,6 +951,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_app_exit(&mut self) -> Option<Self::Message> {
|
||||||
|
self.close();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Message> {
|
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Message> {
|
||||||
// Toggle open state and get clone of node data
|
// Toggle open state and get clone of node data
|
||||||
let node_opt = match self.nav_model.data_mut::<ProjectNode>(id) {
|
let node_opt = match self.nav_model.data_mut::<ProjectNode>(id) {
|
||||||
|
|
@ -1606,7 +1611,8 @@ impl Application for App {
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
}
|
}
|
||||||
Message::WindowClose => {
|
Message::WindowClose => {
|
||||||
process::exit(0);
|
self.close();
|
||||||
|
return exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
|
||||||
2
vendor/iced_video_player/.gitignore
vendored
Normal file
2
vendor/iced_video_player/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
.direnv
|
||||||
88
vendor/iced_video_player/Cargo.toml
vendored
Normal file
88
vendor/iced_video_player/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
[package]
|
||||||
|
name = "iced_video_player"
|
||||||
|
description = "A convenient video player widget for Iced"
|
||||||
|
homepage = "https://github.com/jazzfool/iced_video_player"
|
||||||
|
repository = "https://github.com/jazzfool/iced_video_player"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["gui", "iced", "video"]
|
||||||
|
categories = ["gui", "multimedia"]
|
||||||
|
version = "0.6.0"
|
||||||
|
authors = ["jazzfool"]
|
||||||
|
edition = "2021"
|
||||||
|
resolver = "2"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
exclude = [".media/test.mp4"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gstreamer = "0.25"
|
||||||
|
gstreamer-app = "0.25" # appsink
|
||||||
|
gstreamer-base = "0.25" # basesrc
|
||||||
|
gstreamer-pbutils = "0.25"
|
||||||
|
glib = "0.22" # gobject traits and error type
|
||||||
|
log = "0.4"
|
||||||
|
thiserror = "2"
|
||||||
|
url = "2" # media uri
|
||||||
|
html-escape = "0.2.13"
|
||||||
|
iced_wgpu = { path = "../../../libcosmic/iced/wgpu", default-features = false, optional = true }
|
||||||
|
|
||||||
|
[dependencies.cosmic]
|
||||||
|
package = "libcosmic-yoda"
|
||||||
|
path = "../../../libcosmic"
|
||||||
|
default-features = false
|
||||||
|
features = ["tokio", "winit"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.11"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["wgpu"]
|
||||||
|
wgpu = ["cosmic/wgpu", "iced_wgpu"]
|
||||||
|
|
||||||
|
[package.metadata.nix]
|
||||||
|
systems = ["x86_64-linux"]
|
||||||
|
app = true
|
||||||
|
build = true
|
||||||
|
runtimeLibs = [
|
||||||
|
"vulkan-loader",
|
||||||
|
"wayland",
|
||||||
|
"wayland-protocols",
|
||||||
|
"libxkbcommon",
|
||||||
|
"xorg.libX11",
|
||||||
|
"xorg.libXrandr",
|
||||||
|
"xorg.libXi",
|
||||||
|
"gst_all_1.gstreamer",
|
||||||
|
"gst_all_1.gstreamermm",
|
||||||
|
"gst_all_1.gst-plugins-bad",
|
||||||
|
"gst_all_1.gst-plugins-ugly",
|
||||||
|
"gst_all_1.gst-plugins-good",
|
||||||
|
"gst_all_1.gst-plugins-base",
|
||||||
|
]
|
||||||
|
buildInputs = [
|
||||||
|
"libxkbcommon",
|
||||||
|
"gst_all_1.gstreamer",
|
||||||
|
"gst_all_1.gstreamermm",
|
||||||
|
"gst_all_1.gst-plugins-bad",
|
||||||
|
"gst_all_1.gst-plugins-ugly",
|
||||||
|
"gst_all_1.gst-plugins-good",
|
||||||
|
"gst_all_1.gst-plugins-base",
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
rustc-args = ["--cfg", "docsrs"]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
targets = ["wasm32-unknown-unknown"]
|
||||||
|
|
||||||
|
|
||||||
|
# [patch.'https://github.com/pop-os/libcosmic']
|
||||||
|
# libcosmic = { path = "../libcosmic" }
|
||||||
|
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||||
|
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||||
|
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
|
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
|
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
|
|
||||||
|
[dev_dependencies]
|
||||||
|
libcosmic = { git = "https://github.com/pop-os/libcosmic", features = [
|
||||||
|
"wgpu",
|
||||||
|
"wayland",
|
||||||
|
] }
|
||||||
176
vendor/iced_video_player/LICENSE-APACHE
vendored
Normal file
176
vendor/iced_video_player/LICENSE-APACHE
vendored
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
23
vendor/iced_video_player/LICENSE-MIT
vendored
Normal file
23
vendor/iced_video_player/LICENSE-MIT
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
64
vendor/iced_video_player/README.md
vendored
Normal file
64
vendor/iced_video_player/README.md
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Iced Video Player Widget
|
||||||
|
|
||||||
|
Composable component to play videos in any Iced application built on the excellent GStreamer library.
|
||||||
|
|
||||||
|
<img src=".media/screenshot.png" width="50%" />
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
In general, this supports anything that [`gstreamer/playbin`](https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c) supports.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Load video files from any file path **or URL** (support for streaming over network).
|
||||||
|
- Video buffering when streaming on a network.
|
||||||
|
- Audio support.
|
||||||
|
- Programmatic control.
|
||||||
|
- Can capture thumbnails from a set of timestamps.
|
||||||
|
- Good performance (i.e., comparable to other video players). GStreamer (with the right plugins) will perform hardware-accelerated decoding, and the color space (YUV to RGB) is converted on the GPU whilst rendering the frame.
|
||||||
|
|
||||||
|
Limitations (hopefully to be fixed):
|
||||||
|
- GStreamer is a bit annoying to set up on Windows.
|
||||||
|
|
||||||
|
The player **does not** come with any surrounding GUI controls, but they should be quite easy to implement should you need them.
|
||||||
|
See the "minimal" example for a demonstration on how you could implement pausing, looping, and seeking.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use iced_video_player::{Video, VideoPlayer};
|
||||||
|
|
||||||
|
fn main() -> iced::Result {
|
||||||
|
iced::run("Video Player", (), App::view)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
video: Video,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
App {
|
||||||
|
video: Video::new(&url::Url::parse("file:///C:/my_video.mp4").unwrap()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn view(&self) -> iced::Element<()> {
|
||||||
|
VideoPlayer::new(&self.video).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Follow the [GStreamer build instructions](https://github.com/sdroege/gstreamer-rs#installation). This should be able to compile on MSVC, MinGW, Linux, and MacOS.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under either
|
||||||
|
|
||||||
|
- [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
- [MIT](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
154
vendor/iced_video_player/examples/minimal.rs
vendored
Normal file
154
vendor/iced_video_player/examples/minimal.rs
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use cosmic::iced::{self, Program, Task, Theme};
|
||||||
|
|
||||||
|
use iced::{
|
||||||
|
application::Application,
|
||||||
|
widget::{Button, Column, Container, Row, Slider, Text},
|
||||||
|
};
|
||||||
|
use iced_video_player::{Video, VideoPlayer};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
application().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn application() -> Application<impl Program<Message = Message, Theme = Theme>> {
|
||||||
|
iced::application(App::default, App::update, App::view).window_size((500.0, 800.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum Message {
|
||||||
|
TogglePause,
|
||||||
|
ToggleLoop,
|
||||||
|
Seek(f64),
|
||||||
|
SeekRelease,
|
||||||
|
EndOfStream,
|
||||||
|
NewFrame,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
video: Video,
|
||||||
|
position: f64,
|
||||||
|
dragging: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
App {
|
||||||
|
video: Video::new(
|
||||||
|
&url::Url::from_file_path(
|
||||||
|
std::path::PathBuf::from(file!())
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("../.media/test.mp4")
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
position: 0.0,
|
||||||
|
dragging: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
|
match message {
|
||||||
|
Message::TogglePause => {
|
||||||
|
self.video.set_paused(!self.video.paused());
|
||||||
|
}
|
||||||
|
Message::ToggleLoop => {
|
||||||
|
self.video.set_looping(!self.video.looping());
|
||||||
|
}
|
||||||
|
Message::Seek(secs) => {
|
||||||
|
self.dragging = true;
|
||||||
|
self.video.set_paused(true);
|
||||||
|
self.position = secs;
|
||||||
|
}
|
||||||
|
Message::SeekRelease => {
|
||||||
|
self.dragging = false;
|
||||||
|
self.video
|
||||||
|
.seek(Duration::from_secs_f64(self.position), false)
|
||||||
|
.expect("seek");
|
||||||
|
self.video.set_paused(false);
|
||||||
|
}
|
||||||
|
Message::EndOfStream => {
|
||||||
|
println!("end of stream");
|
||||||
|
}
|
||||||
|
Message::NewFrame => {
|
||||||
|
if !self.dragging {
|
||||||
|
self.position = self.video.position().as_secs_f64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> iced::Element<'_, Message> {
|
||||||
|
Column::new()
|
||||||
|
.push(
|
||||||
|
Container::new(
|
||||||
|
VideoPlayer::new(&self.video)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.content_fit(iced::ContentFit::Contain)
|
||||||
|
.on_end_of_stream(Message::EndOfStream)
|
||||||
|
.on_new_frame(Message::NewFrame),
|
||||||
|
)
|
||||||
|
.align_x(iced::Alignment::Center)
|
||||||
|
.align_y(iced::Alignment::Center)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.height(iced::Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Container::new(
|
||||||
|
Slider::new(
|
||||||
|
0.0..=self.video.duration().as_secs_f64(),
|
||||||
|
self.position,
|
||||||
|
Message::Seek,
|
||||||
|
)
|
||||||
|
.step(0.1)
|
||||||
|
.on_release(Message::SeekRelease),
|
||||||
|
)
|
||||||
|
.padding(iced::Padding::new(5.0).left(10.0).right(10.0)),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Row::new()
|
||||||
|
.spacing(5)
|
||||||
|
.align_y(iced::alignment::Vertical::Center)
|
||||||
|
.padding(iced::Padding::new(10.0).top(0.0))
|
||||||
|
.push(
|
||||||
|
Button::new(Text::new(if self.video.paused() {
|
||||||
|
"Play"
|
||||||
|
} else {
|
||||||
|
"Pause"
|
||||||
|
}))
|
||||||
|
.width(80.0)
|
||||||
|
.on_press(Message::TogglePause),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Button::new(Text::new(if self.video.looping() {
|
||||||
|
"Disable Loop"
|
||||||
|
} else {
|
||||||
|
"Enable Loop"
|
||||||
|
}))
|
||||||
|
.width(120.0)
|
||||||
|
.on_press(Message::ToggleLoop),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Text::new(format!(
|
||||||
|
"{}:{:02}s / {}:{:02}s",
|
||||||
|
self.position as u64 / 60,
|
||||||
|
self.position as u64 % 60,
|
||||||
|
self.video.duration().as_secs() / 60,
|
||||||
|
self.video.duration().as_secs() % 60,
|
||||||
|
))
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_x(iced::alignment::Horizontal::Right),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
79
vendor/iced_video_player/src/lib.rs
vendored
Normal file
79
vendor/iced_video_player/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
//! # Iced Video Player
|
||||||
|
//!
|
||||||
|
//! A convenient video player widget for Iced.
|
||||||
|
//!
|
||||||
|
//! To get started, load a video from a URI (e.g., a file path prefixed with `file:///`) using [`Video::new`](crate::Video::new),
|
||||||
|
//! then use it like any other Iced widget in your `view` function by creating a [`VideoPlayer`].
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```rust
|
||||||
|
//! use iced_video_player::{Video, VideoPlayer};
|
||||||
|
//!
|
||||||
|
//! fn main() -> iced::Result {
|
||||||
|
//! iced::run("Video Player", (), App::view)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! struct App {
|
||||||
|
//! video: Video,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl Default for App {
|
||||||
|
//! fn default() -> Self {
|
||||||
|
//! App {
|
||||||
|
//! video: Video::new(&url::Url::parse("file:///C:/my_video.mp4").unwrap()).unwrap(),
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl App {
|
||||||
|
//! fn view(&self) -> iced::Element<()> {
|
||||||
|
//! VideoPlayer::new(&self.video).into()
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! You can programmatically control the video (e.g., seek, pause, loop, grab thumbnails) by accessing various methods on [`Video`].
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
mod pipeline;
|
||||||
|
mod video;
|
||||||
|
mod video_player;
|
||||||
|
|
||||||
|
pub use gstreamer as gst;
|
||||||
|
pub use gstreamer_app as gst_app;
|
||||||
|
pub use gstreamer_pbutils as gst_pbutils;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub use video::Position;
|
||||||
|
pub use video::Video;
|
||||||
|
pub use video_player::VideoPlayer;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("{0}")]
|
||||||
|
Glib(#[from] glib::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
Bool(#[from] glib::BoolError),
|
||||||
|
#[error("failed to get the gstreamer bus")]
|
||||||
|
Bus,
|
||||||
|
#[error("failed to get AppSink element with name='{0}' from gstreamer pipeline")]
|
||||||
|
AppSink(String),
|
||||||
|
#[error("{0}")]
|
||||||
|
StateChange(#[from] gst::StateChangeError),
|
||||||
|
#[error("failed to cast gstreamer element")]
|
||||||
|
Cast,
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("invalid URI")]
|
||||||
|
Uri,
|
||||||
|
#[error("failed to get media capabilities")]
|
||||||
|
Caps,
|
||||||
|
#[error("failed to query media duration or position")]
|
||||||
|
Duration,
|
||||||
|
#[error("failed to sync with playback")]
|
||||||
|
Sync,
|
||||||
|
#[error("failed to lock internal sync primitive")]
|
||||||
|
Lock,
|
||||||
|
#[error("invalid framerate: {0}")]
|
||||||
|
Framerate(f64),
|
||||||
|
}
|
||||||
447
vendor/iced_video_player/src/pipeline.rs
vendored
Normal file
447
vendor/iced_video_player/src/pipeline.rs
vendored
Normal file
|
|
@ -0,0 +1,447 @@
|
||||||
|
use cosmic::iced::wgpu;
|
||||||
|
use cosmic::iced::widget::shader::{Pipeline, Primitive, Viewport};
|
||||||
|
use cosmic::iced::{self, Rectangle};
|
||||||
|
use std::{
|
||||||
|
collections::{btree_map::Entry, BTreeMap},
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc, Mutex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::video::Frame;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Uniforms {
|
||||||
|
rect: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VideoEntry {
|
||||||
|
texture_y: wgpu::Texture,
|
||||||
|
texture_uv: wgpu::Texture,
|
||||||
|
uniforms: wgpu::Buffer,
|
||||||
|
bg0: wgpu::BindGroup,
|
||||||
|
alive: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VideoPipeline {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bg0_layout: wgpu::BindGroupLayout,
|
||||||
|
sampler: wgpu::Sampler,
|
||||||
|
videos: BTreeMap<u64, VideoEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoPipeline {
|
||||||
|
fn new(device: &wgpu::Device, format: wgpu::TextureFormat, queue: &wgpu::Queue) -> Self {
|
||||||
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("iced_video_player shader"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bg0_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("iced_video_player bind group 0 layout"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("iced_video_player pipeline layout"),
|
||||||
|
bind_group_layouts: &[&bg0_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("iced_video_player pipeline"),
|
||||||
|
layout: Some(&layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format,
|
||||||
|
blend: None,
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: Some("iced_video_player sampler"),
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
lod_min_clamp: 0.0,
|
||||||
|
lod_max_clamp: 1.0,
|
||||||
|
compare: None,
|
||||||
|
anisotropy_clamp: 1,
|
||||||
|
border_color: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
VideoPipeline {
|
||||||
|
pipeline,
|
||||||
|
bg0_layout,
|
||||||
|
sampler,
|
||||||
|
videos: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
video_id: u64,
|
||||||
|
alive: &Arc<AtomicBool>,
|
||||||
|
(width, height): (u32, u32),
|
||||||
|
frame: &[u8],
|
||||||
|
) {
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Entry::Vacant(entry) = self.videos.entry(video_id) {
|
||||||
|
let texture_y = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("iced_video_player texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::R8Unorm,
|
||||||
|
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let texture_uv = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("iced_video_player texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: width / 2,
|
||||||
|
height: height / 2,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rg8Unorm,
|
||||||
|
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let view_y = texture_y.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
label: Some("iced_video_player texture view"),
|
||||||
|
format: None,
|
||||||
|
dimension: None,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
base_mip_level: 0,
|
||||||
|
mip_level_count: None,
|
||||||
|
base_array_layer: 0,
|
||||||
|
array_layer_count: None,
|
||||||
|
usage: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let view_uv = texture_uv.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
label: Some("iced_video_player texture view"),
|
||||||
|
format: None,
|
||||||
|
dimension: None,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
base_mip_level: 0,
|
||||||
|
mip_level_count: None,
|
||||||
|
base_array_layer: 0,
|
||||||
|
array_layer_count: None,
|
||||||
|
usage: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("iced_video_player uniform buffer"),
|
||||||
|
size: std::mem::size_of::<Uniforms>() as _,
|
||||||
|
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("iced_video_player bind group"),
|
||||||
|
layout: &self.bg0_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&view_y),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&view_uv),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: &buffer,
|
||||||
|
offset: 0,
|
||||||
|
size: None,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
entry.insert(VideoEntry {
|
||||||
|
texture_y,
|
||||||
|
texture_uv,
|
||||||
|
uniforms: buffer,
|
||||||
|
bg0: bind_group,
|
||||||
|
alive: Arc::clone(alive),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let VideoEntry {
|
||||||
|
texture_y,
|
||||||
|
texture_uv,
|
||||||
|
..
|
||||||
|
} = self.videos.get(&video_id).unwrap();
|
||||||
|
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::TexelCopyTextureInfo {
|
||||||
|
texture: texture_y,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
},
|
||||||
|
&frame[..(width * height) as usize],
|
||||||
|
wgpu::TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(width),
|
||||||
|
rows_per_image: Some(height),
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::TexelCopyTextureInfo {
|
||||||
|
texture: texture_uv,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
},
|
||||||
|
&frame[(width * height) as usize..],
|
||||||
|
wgpu::TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(width),
|
||||||
|
rows_per_image: Some(height / 2),
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width: width / 2,
|
||||||
|
height: height / 2,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self) {
|
||||||
|
let ids: Vec<_> = self
|
||||||
|
.videos
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(id, entry)| (!entry.alive.load(Ordering::SeqCst)).then_some(*id))
|
||||||
|
.collect();
|
||||||
|
for id in ids {
|
||||||
|
if let Some(video) = self.videos.remove(&id) {
|
||||||
|
video.texture_y.destroy();
|
||||||
|
video.texture_uv.destroy();
|
||||||
|
video.uniforms.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(&mut self, queue: &wgpu::Queue, video_id: u64, bounds: &iced::Rectangle) {
|
||||||
|
if let Some(video) = self.videos.get(&video_id) {
|
||||||
|
let uniforms = Uniforms {
|
||||||
|
rect: [
|
||||||
|
bounds.x,
|
||||||
|
bounds.y,
|
||||||
|
bounds.x + bounds.width,
|
||||||
|
bounds.y + bounds.height,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
queue.write_buffer(&video.uniforms, 0, unsafe {
|
||||||
|
std::slice::from_raw_parts(
|
||||||
|
&uniforms as *const _ as *const u8,
|
||||||
|
std::mem::size_of::<Uniforms>(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
viewport: &iced::Rectangle<u32>,
|
||||||
|
video_id: u64,
|
||||||
|
) {
|
||||||
|
if let Some(video) = self.videos.get(&video_id) {
|
||||||
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("iced_video_player render pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: target,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
depth_slice: None,
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
pass.set_bind_group(0, &video.bg0, &[]);
|
||||||
|
pass.set_viewport(
|
||||||
|
viewport.x as _,
|
||||||
|
viewport.y as _,
|
||||||
|
viewport.width as _,
|
||||||
|
viewport.height as _,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
pass.draw(0..4, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline for VideoPipeline {
|
||||||
|
fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
VideoPipeline::new(device, format, queue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct VideoPrimitive {
|
||||||
|
video_id: u64,
|
||||||
|
alive: Arc<AtomicBool>,
|
||||||
|
frame: Arc<Mutex<Frame>>,
|
||||||
|
size: (u32, u32),
|
||||||
|
upload_frame: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoPrimitive {
|
||||||
|
pub fn new(
|
||||||
|
video_id: u64,
|
||||||
|
alive: Arc<AtomicBool>,
|
||||||
|
frame: Arc<Mutex<Frame>>,
|
||||||
|
size: (u32, u32),
|
||||||
|
upload_frame: bool,
|
||||||
|
) -> Self {
|
||||||
|
VideoPrimitive {
|
||||||
|
video_id,
|
||||||
|
alive,
|
||||||
|
frame,
|
||||||
|
size,
|
||||||
|
upload_frame,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Primitive for VideoPrimitive {
|
||||||
|
type Pipeline = VideoPipeline;
|
||||||
|
|
||||||
|
fn prepare(
|
||||||
|
&self,
|
||||||
|
pipeline: &mut Self::Pipeline,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
bounds: &Rectangle,
|
||||||
|
viewport: &Viewport,
|
||||||
|
) {
|
||||||
|
if self.upload_frame {
|
||||||
|
if let Some(readable) = self.frame.lock().expect("lock frame mutex").readable() {
|
||||||
|
pipeline.upload(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
self.video_id,
|
||||||
|
&self.alive,
|
||||||
|
self.size,
|
||||||
|
readable.as_slice(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.prepare(queue, self.video_id, &bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&self,
|
||||||
|
pipeline: &Self::Pipeline,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
clip_bounds: &Rectangle<u32>,
|
||||||
|
) {
|
||||||
|
pipeline.draw(target, encoder, &clip_bounds, self.video_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
vendor/iced_video_player/src/shader.wgsl
vendored
Normal file
58
vendor/iced_video_player/src/shader.wgsl
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(0) uv: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Uniforms {
|
||||||
|
rect: vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var tex_y: texture_2d<f32>;
|
||||||
|
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var tex_uv: texture_2d<f32>;
|
||||||
|
|
||||||
|
@group(0) @binding(2)
|
||||||
|
var s: sampler;
|
||||||
|
|
||||||
|
@group(0) @binding(3)
|
||||||
|
var<uniform> uniforms: Uniforms;
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput {
|
||||||
|
let quad = array<vec2<f32>, 6>(
|
||||||
|
uniforms.rect.xy,
|
||||||
|
uniforms.rect.zy,
|
||||||
|
uniforms.rect.xw,
|
||||||
|
uniforms.rect.zy,
|
||||||
|
uniforms.rect.zw,
|
||||||
|
uniforms.rect.xw,
|
||||||
|
);
|
||||||
|
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.uv = vec2<f32>(0.0);
|
||||||
|
out.uv.x = select(0.0, 2.0, in_vertex_index == 1u);
|
||||||
|
out.uv.y = select(0.0, 2.0, in_vertex_index == 2u);
|
||||||
|
out.position = vec4<f32>(out.uv * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), 1.0, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let yuv2r = vec3<f32>(1.164, 0.0, 1.596);
|
||||||
|
let yuv2g = vec3<f32>(1.164, -0.391, -0.813);
|
||||||
|
let yuv2b = vec3<f32>(1.164, 2.018, 0.0);
|
||||||
|
|
||||||
|
var yuv = vec3<f32>(0.0);
|
||||||
|
yuv.x = textureSample(tex_y, s, in.uv).r - 0.0625;
|
||||||
|
yuv.y = textureSample(tex_uv, s, in.uv).r - 0.5;
|
||||||
|
yuv.z = textureSample(tex_uv, s, in.uv).g - 0.5;
|
||||||
|
|
||||||
|
var rgb = vec3<f32>(0.0);
|
||||||
|
rgb.x = dot(yuv, yuv2r);
|
||||||
|
rgb.y = dot(yuv, yuv2g);
|
||||||
|
rgb.z = dot(yuv, yuv2b);
|
||||||
|
|
||||||
|
return vec4<f32>(rgb, 1.0);
|
||||||
|
}
|
||||||
664
vendor/iced_video_player/src/video.rs
vendored
Normal file
664
vendor/iced_video_player/src/video.rs
vendored
Normal file
|
|
@ -0,0 +1,664 @@
|
||||||
|
use crate::Error;
|
||||||
|
use cosmic::iced::widget::image as img;
|
||||||
|
use gstreamer as gst;
|
||||||
|
use gstreamer_app as gst_app;
|
||||||
|
use gstreamer_app::prelude::*;
|
||||||
|
use std::num::NonZeroU8;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
const THUMBNAIL_FRAME_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
/// Position in the media.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum Position {
|
||||||
|
/// Position based on time.
|
||||||
|
///
|
||||||
|
/// Not the most accurate format for videos.
|
||||||
|
Time(Duration),
|
||||||
|
/// Position based on nth frame.
|
||||||
|
Frame(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Position> for gst::GenericFormattedValue {
|
||||||
|
fn from(pos: Position) -> Self {
|
||||||
|
match pos {
|
||||||
|
Position::Time(t) => gst::ClockTime::from_nseconds(t.as_nanos() as _).into(),
|
||||||
|
Position::Frame(f) => gst::format::Default::from_u64(f).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Duration> for Position {
|
||||||
|
fn from(t: Duration) -> Self {
|
||||||
|
Position::Time(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for Position {
|
||||||
|
fn from(f: u64) -> Self {
|
||||||
|
Position::Frame(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Frame(gst::Sample);
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(gst::Sample::builder().build())
|
||||||
|
}
|
||||||
|
pub fn store(&mut self, sample: gst::Sample) -> Option<()> {
|
||||||
|
if sample.buffer().is_some() {
|
||||||
|
self.0 = sample;
|
||||||
|
Some(())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn readable(&self) -> Option<gst::BufferMap<gst::buffer::Readable>> {
|
||||||
|
self.0.buffer().map(|x| x.map_readable().ok()).flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Internal {
|
||||||
|
pub(crate) id: u64,
|
||||||
|
|
||||||
|
pub(crate) bus: gst::Bus,
|
||||||
|
pub(crate) source: gst::Pipeline,
|
||||||
|
pub(crate) alive: Arc<AtomicBool>,
|
||||||
|
pub(crate) worker: Option<std::thread::JoinHandle<()>>,
|
||||||
|
|
||||||
|
pub(crate) has_video: bool,
|
||||||
|
pub(crate) width: i32,
|
||||||
|
pub(crate) height: i32,
|
||||||
|
pub(crate) framerate: f64,
|
||||||
|
pub(crate) duration: Duration,
|
||||||
|
pub(crate) speed: f64,
|
||||||
|
pub(crate) sync_av: bool,
|
||||||
|
|
||||||
|
pub(crate) frame: Arc<Mutex<Frame>>,
|
||||||
|
pub(crate) upload_frame: Arc<AtomicBool>,
|
||||||
|
pub(crate) redrawing: Arc<AtomicBool>,
|
||||||
|
pub(crate) last_frame_time: Arc<Mutex<Instant>>,
|
||||||
|
pub(crate) looping: bool,
|
||||||
|
pub(crate) is_eos: bool,
|
||||||
|
pub(crate) restart_stream: bool,
|
||||||
|
pub(crate) sync_av_avg: u64,
|
||||||
|
pub(crate) sync_av_counter: u64,
|
||||||
|
|
||||||
|
pub(crate) subtitle_text: Arc<Mutex<Option<String>>>,
|
||||||
|
pub(crate) upload_text: Arc<AtomicBool>,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
pub(crate) handle_opt: Option<img::Handle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Internal {
|
||||||
|
pub(crate) fn seek(&self, position: impl Into<Position>, accurate: bool) -> Result<(), Error> {
|
||||||
|
let position = position.into();
|
||||||
|
|
||||||
|
// gstreamer complains if the start & end value types aren't the same
|
||||||
|
match &position {
|
||||||
|
Position::Time(_) => self.source.seek(
|
||||||
|
self.speed,
|
||||||
|
gst::SeekFlags::FLUSH
|
||||||
|
| if accurate {
|
||||||
|
gst::SeekFlags::ACCURATE
|
||||||
|
} else {
|
||||||
|
gst::SeekFlags::empty()
|
||||||
|
},
|
||||||
|
gst::SeekType::Set,
|
||||||
|
gst::GenericFormattedValue::from(position),
|
||||||
|
gst::SeekType::Set,
|
||||||
|
gst::ClockTime::NONE,
|
||||||
|
)?,
|
||||||
|
Position::Frame(_) => self.source.seek(
|
||||||
|
self.speed,
|
||||||
|
gst::SeekFlags::FLUSH
|
||||||
|
| if accurate {
|
||||||
|
gst::SeekFlags::ACCURATE
|
||||||
|
} else {
|
||||||
|
gst::SeekFlags::empty()
|
||||||
|
},
|
||||||
|
gst::SeekType::Set,
|
||||||
|
gst::GenericFormattedValue::from(position),
|
||||||
|
gst::SeekType::Set,
|
||||||
|
gst::format::Default::NONE,
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_speed(&mut self, speed: f64) -> Result<(), Error> {
|
||||||
|
let Some(position) = self.source.query_position::<gst::ClockTime>() else {
|
||||||
|
return Err(Error::Caps);
|
||||||
|
};
|
||||||
|
if speed > 0.0 {
|
||||||
|
self.source.seek(
|
||||||
|
speed,
|
||||||
|
gst::SeekFlags::FLUSH | gst::SeekFlags::ACCURATE,
|
||||||
|
gst::SeekType::Set,
|
||||||
|
position,
|
||||||
|
gst::SeekType::End,
|
||||||
|
gst::ClockTime::from_seconds(0),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
self.source.seek(
|
||||||
|
speed,
|
||||||
|
gst::SeekFlags::FLUSH | gst::SeekFlags::ACCURATE,
|
||||||
|
gst::SeekType::Set,
|
||||||
|
gst::ClockTime::from_seconds(0),
|
||||||
|
gst::SeekType::Set,
|
||||||
|
position,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
self.speed = speed;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn restart_stream(&mut self) -> Result<(), Error> {
|
||||||
|
self.is_eos = false;
|
||||||
|
self.set_paused(false);
|
||||||
|
self.seek(0, false)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_paused(&mut self, paused: bool) {
|
||||||
|
self.source
|
||||||
|
.set_state(if paused {
|
||||||
|
gst::State::Paused
|
||||||
|
} else {
|
||||||
|
gst::State::Playing
|
||||||
|
})
|
||||||
|
.unwrap(/* state was changed in ctor; state errors caught there */);
|
||||||
|
|
||||||
|
// Set restart_stream flag to make the stream restart on the next Message::NextFrame
|
||||||
|
if self.is_eos && !paused {
|
||||||
|
self.restart_stream = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn paused(&self) -> bool {
|
||||||
|
self.source.state(gst::ClockTime::ZERO).1 == gst::State::Paused
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Syncs audio with video when there is (inevitably) latency presenting the frame.
|
||||||
|
pub(crate) fn set_av_offset(&mut self, offset: Duration) {
|
||||||
|
if self.sync_av {
|
||||||
|
self.sync_av_counter += 1;
|
||||||
|
self.sync_av_avg = self.sync_av_avg * (self.sync_av_counter - 1) / self.sync_av_counter
|
||||||
|
+ offset.as_nanos() as u64 / self.sync_av_counter;
|
||||||
|
if self.sync_av_counter % 128 == 0 {
|
||||||
|
self.source
|
||||||
|
.set_property("av-offset", -(self.sync_av_avg as i64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A multimedia video loaded from a URI (e.g., a local file path or HTTP stream).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Video(pub(crate) RwLock<Internal>);
|
||||||
|
|
||||||
|
impl Drop for Video {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let inner = self.0.get_mut().expect("failed to lock");
|
||||||
|
|
||||||
|
inner
|
||||||
|
.source
|
||||||
|
.set_state(gst::State::Null)
|
||||||
|
.expect("failed to set state");
|
||||||
|
|
||||||
|
inner.alive.store(false, Ordering::SeqCst);
|
||||||
|
if let Some(worker) = inner.worker.take() {
|
||||||
|
worker.join().expect("failed to stop video thread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Video {
|
||||||
|
/// Create a new video player from a given video which loads from `uri`.
|
||||||
|
/// Note that live sources will report the duration to be zero.
|
||||||
|
pub fn new(uri: &url::Url) -> Result<Self, Error> {
|
||||||
|
gst::init()?;
|
||||||
|
|
||||||
|
let pipeline = format!(
|
||||||
|
"playbin uri=\"{}\" text-sink=\"appsink name=iced_text sync=true caps=text/x-raw\" video-sink=\"videoscale ! videoconvert ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"",
|
||||||
|
uri.as_str()
|
||||||
|
);
|
||||||
|
let pipeline = gst::parse::launch(pipeline.as_ref())?
|
||||||
|
.downcast::<gst::Pipeline>()
|
||||||
|
.map_err(|_| Error::Cast)?;
|
||||||
|
|
||||||
|
let video_sink: gst::Element = pipeline.property("video-sink");
|
||||||
|
let pad = video_sink.pads().first().cloned().unwrap();
|
||||||
|
let pad = pad.dynamic_cast::<gst::GhostPad>().unwrap();
|
||||||
|
let bin = pad
|
||||||
|
.parent_element()
|
||||||
|
.unwrap()
|
||||||
|
.downcast::<gst::Bin>()
|
||||||
|
.unwrap();
|
||||||
|
let video_sink = bin.by_name("iced_video").unwrap();
|
||||||
|
let video_sink = video_sink.downcast::<gst_app::AppSink>().unwrap();
|
||||||
|
|
||||||
|
let text_sink: gst::Element = pipeline.property("text-sink");
|
||||||
|
let text_sink = text_sink.downcast::<gst_app::AppSink>().unwrap();
|
||||||
|
|
||||||
|
Self::from_gst_pipeline(pipeline, video_sink, Some(text_sink))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new video based on an existing GStreamer pipeline and appsink.
|
||||||
|
/// Expects an `appsink` plugin with `caps=video/x-raw,format=NV12`.
|
||||||
|
///
|
||||||
|
/// An optional `text_sink` can be provided, which enables subtitle messages
|
||||||
|
/// to be emitted.
|
||||||
|
///
|
||||||
|
/// **Note:** Many functions of [`Video`] assume a `playbin` pipeline.
|
||||||
|
/// Non-`playbin` pipelines given here may not have full functionality.
|
||||||
|
pub fn from_gst_pipeline(
|
||||||
|
pipeline: gst::Pipeline,
|
||||||
|
video_sink: gst_app::AppSink,
|
||||||
|
text_sink: Option<gst_app::AppSink>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
gst::init()?;
|
||||||
|
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
let pad = video_sink.pads().first().cloned().unwrap();
|
||||||
|
|
||||||
|
pipeline.set_state(gst::State::Playing)?;
|
||||||
|
|
||||||
|
// wait for up to 5 seconds until the decoder gets the source capabilities
|
||||||
|
pipeline.state(gst::ClockTime::from_seconds(5)).0?;
|
||||||
|
|
||||||
|
// extract resolution and framerate
|
||||||
|
// TODO(jazzfool): maybe we want to extract some other information too?
|
||||||
|
let (width, height, framerate, has_video) = if let Some(caps) = pad.current_caps() {
|
||||||
|
let s = caps.structure(0).ok_or(Error::Caps)?;
|
||||||
|
let width = s.get::<i32>("width").map_err(|_| Error::Caps)?;
|
||||||
|
let height = s.get::<i32>("height").map_err(|_| Error::Caps)?;
|
||||||
|
// resolution should be mod4
|
||||||
|
let width = ((width + 4 - 1) / 4) * 4;
|
||||||
|
let framerate = s
|
||||||
|
.get::<gst::Fraction>("framerate")
|
||||||
|
.map_err(|_| Error::Caps)?;
|
||||||
|
let framerate = framerate.numer() as f64 / framerate.denom() as f64;
|
||||||
|
(width, height, framerate, true)
|
||||||
|
} else {
|
||||||
|
log::warn!("Video caps not found, falling back to audio only");
|
||||||
|
(0, 0, 4.0, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
if framerate.is_nan()
|
||||||
|
|| framerate.is_infinite()
|
||||||
|
|| framerate < 0.0
|
||||||
|
|| framerate.abs() < f64::EPSILON
|
||||||
|
{
|
||||||
|
return Err(Error::Framerate(framerate));
|
||||||
|
}
|
||||||
|
|
||||||
|
let duration = Duration::from_nanos(
|
||||||
|
pipeline
|
||||||
|
.query_duration::<gst::ClockTime>()
|
||||||
|
.map(|duration| duration.nseconds())
|
||||||
|
.unwrap_or(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
let sync_av = pipeline.has_property("av-offset");
|
||||||
|
|
||||||
|
// NV12 = 12bpp
|
||||||
|
let frame = Arc::new(Mutex::new(Frame::new()));
|
||||||
|
let upload_frame = Arc::new(AtomicBool::new(false));
|
||||||
|
let alive = Arc::new(AtomicBool::new(true));
|
||||||
|
let last_frame_time = Arc::new(Mutex::new(Instant::now()));
|
||||||
|
|
||||||
|
let frame_ref = Arc::clone(&frame);
|
||||||
|
let upload_frame_ref = Arc::clone(&upload_frame);
|
||||||
|
let alive_ref = Arc::clone(&alive);
|
||||||
|
let last_frame_time_ref = Arc::clone(&last_frame_time);
|
||||||
|
|
||||||
|
let subtitle_text = Arc::new(Mutex::new(None));
|
||||||
|
let upload_text = Arc::new(AtomicBool::new(false));
|
||||||
|
let subtitle_text_ref = Arc::clone(&subtitle_text);
|
||||||
|
let upload_text_ref = Arc::clone(&upload_text);
|
||||||
|
|
||||||
|
let pipeline_ref = pipeline.clone();
|
||||||
|
|
||||||
|
let worker = std::thread::spawn(move || {
|
||||||
|
let mut clear_subtitles_at = None;
|
||||||
|
|
||||||
|
while alive_ref.load(Ordering::Acquire) {
|
||||||
|
match (|| -> Result<(), gst::FlowError> {
|
||||||
|
let sample =
|
||||||
|
if pipeline_ref.state(gst::ClockTime::ZERO).1 != gst::State::Playing {
|
||||||
|
video_sink
|
||||||
|
.try_pull_preroll(gst::ClockTime::from_mseconds(16))
|
||||||
|
.ok_or(gst::FlowError::Eos)?
|
||||||
|
} else {
|
||||||
|
video_sink
|
||||||
|
.try_pull_sample(gst::ClockTime::from_mseconds(16))
|
||||||
|
.ok_or(gst::FlowError::Eos)?
|
||||||
|
};
|
||||||
|
|
||||||
|
*last_frame_time_ref
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| gst::FlowError::Error)? = Instant::now();
|
||||||
|
|
||||||
|
let buffer = sample.buffer().ok_or(gst::FlowError::Error)?;
|
||||||
|
let pts = buffer.pts().unwrap_or_default();
|
||||||
|
{
|
||||||
|
let mut frame_guard =
|
||||||
|
frame_ref.lock().map_err(|_| gst::FlowError::Error)?;
|
||||||
|
*frame_guard = Frame(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
upload_frame_ref.swap(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
if let Some(at) = clear_subtitles_at {
|
||||||
|
if pts >= at {
|
||||||
|
*subtitle_text_ref
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| gst::FlowError::Error)? = None;
|
||||||
|
upload_text_ref.store(true, Ordering::SeqCst);
|
||||||
|
clear_subtitles_at = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = text_sink
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|sink| sink.try_pull_sample(gst::ClockTime::from_seconds(0)));
|
||||||
|
if let Some(text) = text {
|
||||||
|
let text = text.buffer().ok_or(gst::FlowError::Error)?;
|
||||||
|
let pts = text.pts().unwrap_or_default();
|
||||||
|
let duration = text.duration().unwrap_or(gst::ClockTime::ZERO);
|
||||||
|
let map = text.map_readable().map_err(|_| gst::FlowError::Error)?;
|
||||||
|
|
||||||
|
let text = html_escape::decode_html_entities(
|
||||||
|
std::str::from_utf8(map.as_slice())
|
||||||
|
.map_err(|_| gst::FlowError::Error)?,
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
|
*subtitle_text_ref
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| gst::FlowError::Error)? = Some(text);
|
||||||
|
upload_text_ref.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
clear_subtitles_at = Some(pts + duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})() {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(gst::FlowError::Eos) => {
|
||||||
|
if !has_video {
|
||||||
|
// Simulate frame upload when there is no video stream
|
||||||
|
upload_frame_ref.swap(true, Ordering::SeqCst);
|
||||||
|
std::thread::sleep(Duration::from_secs_f64(1.0 / framerate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("error pulling frame: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Video(RwLock::new(Internal {
|
||||||
|
id,
|
||||||
|
|
||||||
|
bus: pipeline.bus().unwrap(),
|
||||||
|
source: pipeline,
|
||||||
|
alive,
|
||||||
|
worker: Some(worker),
|
||||||
|
|
||||||
|
has_video,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
framerate,
|
||||||
|
duration,
|
||||||
|
speed: 1.0,
|
||||||
|
sync_av,
|
||||||
|
|
||||||
|
frame,
|
||||||
|
upload_frame,
|
||||||
|
redrawing: Arc::new(AtomicBool::new(false)),
|
||||||
|
last_frame_time,
|
||||||
|
looping: false,
|
||||||
|
is_eos: false,
|
||||||
|
restart_stream: false,
|
||||||
|
sync_av_avg: 0,
|
||||||
|
sync_av_counter: 0,
|
||||||
|
|
||||||
|
subtitle_text,
|
||||||
|
upload_text,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
handle_opt: None,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn read(&self) -> impl Deref<Target = Internal> + '_ {
|
||||||
|
self.0.read().expect("lock")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write(&self) -> impl DerefMut<Target = Internal> + '_ {
|
||||||
|
self.0.write().expect("lock")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_mut(&mut self) -> impl DerefMut<Target = Internal> + '_ {
|
||||||
|
self.0.get_mut().expect("lock")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_video(&self) -> bool {
|
||||||
|
self.read().has_video
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size/resolution of the video as `(width, height)`.
|
||||||
|
pub fn size(&self) -> (i32, i32) {
|
||||||
|
(self.read().width, self.read().height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the framerate of the video as frames per second.
|
||||||
|
pub fn framerate(&self) -> f64 {
|
||||||
|
self.read().framerate
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the volume multiplier of the audio.
|
||||||
|
/// `0.0` = 0% volume, `1.0` = 100% volume.
|
||||||
|
///
|
||||||
|
/// This uses a linear scale, for example `0.5` is perceived as half as loud.
|
||||||
|
pub fn set_volume(&mut self, volume: f64) {
|
||||||
|
self.get_mut().source.set_property("volume", volume);
|
||||||
|
self.set_muted(self.muted()); // for some reason gstreamer unmutes when changing volume?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the volume multiplier of the audio.
|
||||||
|
pub fn volume(&self) -> f64 {
|
||||||
|
self.read().source.property("volume")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set if the audio is muted or not, without changing the volume.
|
||||||
|
pub fn set_muted(&mut self, muted: bool) {
|
||||||
|
self.get_mut().source.set_property("mute", muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get if the audio is muted or not.
|
||||||
|
pub fn muted(&self) -> bool {
|
||||||
|
self.read().source.property("mute")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get if the stream ended or not.
|
||||||
|
pub fn eos(&self) -> bool {
|
||||||
|
self.read().is_eos
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get if the media will loop or not.
|
||||||
|
pub fn looping(&self) -> bool {
|
||||||
|
self.read().looping
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set if the media will loop or not.
|
||||||
|
pub fn set_looping(&mut self, looping: bool) {
|
||||||
|
self.get_mut().looping = looping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set if the media is paused or not.
|
||||||
|
pub fn set_paused(&mut self, paused: bool) {
|
||||||
|
self.get_mut().set_paused(paused)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get if the media is paused or not.
|
||||||
|
pub fn paused(&self) -> bool {
|
||||||
|
self.read().paused()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Jumps to a specific position in the media.
|
||||||
|
/// Passing `true` to the `accurate` parameter will result in more accurate seeking,
|
||||||
|
/// however, it is also slower. For most seeks (e.g., scrubbing) this is not needed.
|
||||||
|
pub fn seek(&mut self, position: impl Into<Position>, accurate: bool) -> Result<(), Error> {
|
||||||
|
self.get_mut().seek(position, accurate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the playback speed of the media.
|
||||||
|
/// The default speed is `1.0`.
|
||||||
|
pub fn set_speed(&mut self, speed: f64) -> Result<(), Error> {
|
||||||
|
self.get_mut().set_speed(speed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current playback speed.
|
||||||
|
pub fn speed(&self) -> f64 {
|
||||||
|
self.read().speed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current playback position in time.
|
||||||
|
pub fn position(&self) -> Duration {
|
||||||
|
Duration::from_nanos(
|
||||||
|
self.read()
|
||||||
|
.source
|
||||||
|
.query_position::<gst::ClockTime>()
|
||||||
|
.map_or(0, |pos| pos.nseconds()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the media duration.
|
||||||
|
pub fn duration(&self) -> Duration {
|
||||||
|
self.read().duration
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restarts a stream; seeks to the first frame and unpauses, sets the `eos` flag to false.
|
||||||
|
pub fn restart_stream(&mut self) -> Result<(), Error> {
|
||||||
|
self.get_mut().restart_stream()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the subtitle URL to display.
|
||||||
|
pub fn set_subtitle_url(&mut self, url: &url::Url) -> Result<(), Error> {
|
||||||
|
let paused = self.paused();
|
||||||
|
let mut inner = self.get_mut();
|
||||||
|
inner.source.set_state(gst::State::Ready)?;
|
||||||
|
inner.source.set_property("suburi", url.as_str());
|
||||||
|
inner.set_paused(paused);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current subtitle URL.
|
||||||
|
pub fn subtitle_url(&self) -> Option<url::Url> {
|
||||||
|
url::Url::parse(&self.read().source.property::<String>("suburi")).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the underlying GStreamer pipeline.
|
||||||
|
pub fn pipeline(&self) -> gst::Pipeline {
|
||||||
|
self.read().source.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a list of thumbnails based on a set of positions in the media, downscaled by a given factor.
|
||||||
|
///
|
||||||
|
/// Slow; only needs to be called once for each instance.
|
||||||
|
/// It's best to call this at the very start of playback, otherwise the position may shift.
|
||||||
|
pub fn thumbnails<I>(
|
||||||
|
&mut self,
|
||||||
|
positions: I,
|
||||||
|
downscale: NonZeroU8,
|
||||||
|
) -> Result<Vec<img::Handle>, Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Position>,
|
||||||
|
{
|
||||||
|
let downscale = u8::from(downscale) as u32;
|
||||||
|
|
||||||
|
let paused = self.paused();
|
||||||
|
let muted = self.muted();
|
||||||
|
let pos = self.position();
|
||||||
|
|
||||||
|
self.set_paused(false);
|
||||||
|
self.set_muted(true);
|
||||||
|
|
||||||
|
let out = {
|
||||||
|
let inner = self.read();
|
||||||
|
let width = inner.width;
|
||||||
|
let height = inner.height;
|
||||||
|
positions
|
||||||
|
.into_iter()
|
||||||
|
.map(|pos| {
|
||||||
|
inner.seek(pos, true)?;
|
||||||
|
|
||||||
|
inner.upload_frame.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
let started = Instant::now();
|
||||||
|
while !inner.upload_frame.load(Ordering::SeqCst) {
|
||||||
|
if started.elapsed() >= THUMBNAIL_FRAME_TIMEOUT {
|
||||||
|
return Err(Error::Sync);
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(5));
|
||||||
|
}
|
||||||
|
let frame_guard = inner.frame.lock().map_err(|_| Error::Lock)?;
|
||||||
|
let frame = frame_guard.readable().ok_or(Error::Lock)?;
|
||||||
|
|
||||||
|
Ok(img::Handle::from_rgba(
|
||||||
|
inner.width as u32 / downscale,
|
||||||
|
inner.height as u32 / downscale,
|
||||||
|
yuv_to_rgba(frame.as_slice(), width as _, height as _, downscale),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_paused(paused);
|
||||||
|
self.set_muted(muted);
|
||||||
|
self.seek(pos, true)?;
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn yuv_to_rgba(yuv: &[u8], width: u32, height: u32, downscale: u32) -> Vec<u8> {
|
||||||
|
let uv_start = width * height;
|
||||||
|
let mut rgba = vec![];
|
||||||
|
|
||||||
|
for y in 0..height / downscale {
|
||||||
|
for x in 0..width / downscale {
|
||||||
|
let x_src = x * downscale;
|
||||||
|
let y_src = y * downscale;
|
||||||
|
|
||||||
|
let uv_i = uv_start + width * (y_src / 2) + x_src / 2 * 2;
|
||||||
|
|
||||||
|
let y = yuv[(y_src * width + x_src) as usize] as f32;
|
||||||
|
let u = yuv[uv_i as usize] as f32;
|
||||||
|
let v = yuv[(uv_i + 1) as usize] as f32;
|
||||||
|
|
||||||
|
let r = 1.164 * (y - 16.0) + 1.596 * (v - 128.0);
|
||||||
|
let g = 1.164 * (y - 16.0) - 0.813 * (v - 128.0) - 0.391 * (u - 128.0);
|
||||||
|
let b = 1.164 * (y - 16.0) + 2.018 * (u - 128.0);
|
||||||
|
|
||||||
|
rgba.push(r as u8);
|
||||||
|
rgba.push(g as u8);
|
||||||
|
rgba.push(b as u8);
|
||||||
|
rgba.push(0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba
|
||||||
|
}
|
||||||
476
vendor/iced_video_player/src/video_player.rs
vendored
Normal file
476
vendor/iced_video_player/src/video_player.rs
vendored
Normal file
|
|
@ -0,0 +1,476 @@
|
||||||
|
use crate::{gst, gst_pbutils, video::Video};
|
||||||
|
use cosmic::iced::{
|
||||||
|
self,
|
||||||
|
advanced::{self, layout, widget, Widget},
|
||||||
|
mouse, Element,
|
||||||
|
};
|
||||||
|
use gstreamer_app::prelude::*;
|
||||||
|
use log::error;
|
||||||
|
use std::{marker::PhantomData, sync::atomic::Ordering, time::Duration};
|
||||||
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
use crate::pipeline::VideoPrimitive;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
use iced_wgpu::primitive::Renderer as PrimitiveRenderer;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
use crate::video::yuv_to_rgba;
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
use cosmic::iced::advanced::image::Renderer as ImageRenderer;
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
trait PrimitiveRenderer: ImageRenderer<Handle = advanced::image::Handle> {}
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
impl PrimitiveRenderer for iced::Renderer {}
|
||||||
|
|
||||||
|
/// Video player widget which displays the current frame of a [`Video`](crate::Video).
|
||||||
|
pub struct VideoPlayer<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer>
|
||||||
|
where
|
||||||
|
Renderer: PrimitiveRenderer,
|
||||||
|
{
|
||||||
|
video: &'a Video,
|
||||||
|
content_fit: iced::ContentFit,
|
||||||
|
width: iced::Length,
|
||||||
|
height: iced::Length,
|
||||||
|
mouse_hidden: bool,
|
||||||
|
on_duration_changed: Option<Box<dyn Fn(Duration) -> Message + 'a>>,
|
||||||
|
on_end_of_stream: Option<Message>,
|
||||||
|
on_new_frame: Option<Message>,
|
||||||
|
on_subtitle_text: Option<Box<dyn Fn(Option<String>) -> Message + 'a>>,
|
||||||
|
on_error: Option<Box<dyn Fn(glib::Error) -> Message + 'a>>,
|
||||||
|
on_missing_plugin: Option<Box<dyn Fn(gst::Message) -> Message + 'a>>,
|
||||||
|
on_tags: Option<Box<dyn Fn(gst::TagList) -> Message + 'a>>,
|
||||||
|
on_warning: Option<Box<dyn Fn(glib::Error) -> Message + 'a>>,
|
||||||
|
id: Option<cosmic::widget::Id>,
|
||||||
|
_phantom: PhantomData<(Theme, Renderer)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> VideoPlayer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: PrimitiveRenderer,
|
||||||
|
{
|
||||||
|
/// Creates a new video player widget for a given video.
|
||||||
|
pub fn new(video: &'a Video) -> Self {
|
||||||
|
VideoPlayer {
|
||||||
|
video,
|
||||||
|
content_fit: iced::ContentFit::Contain,
|
||||||
|
width: iced::Length::Shrink,
|
||||||
|
height: iced::Length::Shrink,
|
||||||
|
mouse_hidden: false,
|
||||||
|
on_duration_changed: None,
|
||||||
|
on_end_of_stream: None,
|
||||||
|
on_new_frame: None,
|
||||||
|
on_subtitle_text: None,
|
||||||
|
on_error: None,
|
||||||
|
on_missing_plugin: None,
|
||||||
|
on_tags: None,
|
||||||
|
on_warning: None,
|
||||||
|
_phantom: Default::default(),
|
||||||
|
id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the ID of the `VideoPlayer`.
|
||||||
|
pub fn id(mut self, id: cosmic::widget::Id) -> Self {
|
||||||
|
self.id = Some(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the width of the `VideoPlayer` boundaries.
|
||||||
|
pub fn width(self, width: impl Into<iced::Length>) -> Self {
|
||||||
|
VideoPlayer {
|
||||||
|
width: width.into(),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the height of the `VideoPlayer` boundaries.
|
||||||
|
pub fn height(self, height: impl Into<iced::Length>) -> Self {
|
||||||
|
VideoPlayer {
|
||||||
|
height: height.into(),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `ContentFit` of the `VideoPlayer`.
|
||||||
|
pub fn content_fit(self, content_fit: iced::ContentFit) -> Self {
|
||||||
|
VideoPlayer {
|
||||||
|
content_fit,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_hidden(self, mouse_hidden: bool) -> Self {
|
||||||
|
VideoPlayer {
|
||||||
|
mouse_hidden,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_duration_changed<F>(self, on_duration_changed: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'a + Fn(Duration) -> Message,
|
||||||
|
{
|
||||||
|
VideoPlayer {
|
||||||
|
on_duration_changed: Some(Box::new(on_duration_changed)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message to send when the video reaches the end of stream (i.e., the video ends).
|
||||||
|
pub fn on_end_of_stream(self, on_end_of_stream: Message) -> Self {
|
||||||
|
VideoPlayer {
|
||||||
|
on_end_of_stream: Some(on_end_of_stream),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message to send when the video receives a new frame.
|
||||||
|
pub fn on_new_frame(self, on_new_frame: Message) -> Self {
|
||||||
|
VideoPlayer {
|
||||||
|
on_new_frame: Some(on_new_frame),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message to send when the video receives a new frame.
|
||||||
|
pub fn on_subtitle_text<F>(self, on_subtitle_text: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'a + Fn(Option<String>) -> Message,
|
||||||
|
{
|
||||||
|
VideoPlayer {
|
||||||
|
on_subtitle_text: Some(Box::new(on_subtitle_text)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message to send when the video playback encounters an error.
|
||||||
|
pub fn on_error<F>(self, on_error: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'a + Fn(glib::Error) -> Message,
|
||||||
|
{
|
||||||
|
VideoPlayer {
|
||||||
|
on_error: Some(Box::new(on_error)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_missing_plugin<F>(self, on_missing_plugin: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'a + Fn(gst::Message) -> Message,
|
||||||
|
{
|
||||||
|
VideoPlayer {
|
||||||
|
on_missing_plugin: Some(Box::new(on_missing_plugin)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_tags<F>(self, on_tags: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'a + Fn(gst::TagList) -> Message,
|
||||||
|
{
|
||||||
|
VideoPlayer {
|
||||||
|
on_tags: Some(Box::new(on_tags)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_warning<F>(self, on_warning: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'a + Fn(glib::Error) -> Message,
|
||||||
|
{
|
||||||
|
VideoPlayer {
|
||||||
|
on_warning: Some(Box::new(on_warning)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
for VideoPlayer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Message: Clone,
|
||||||
|
Renderer: PrimitiveRenderer,
|
||||||
|
{
|
||||||
|
fn size(&self) -> iced::Size<iced::Length> {
|
||||||
|
iced::Size {
|
||||||
|
width: iced::Length::Shrink,
|
||||||
|
height: iced::Length::Shrink,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_tree: &mut widget::Tree,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
let (video_width, video_height) = self.video.size();
|
||||||
|
|
||||||
|
// based on `Image::layout`
|
||||||
|
let image_size = iced::Size::new(video_width as f32, video_height as f32);
|
||||||
|
let raw_size = limits.resolve(self.width, self.height, image_size);
|
||||||
|
let full_size = self.content_fit.fit(image_size, raw_size);
|
||||||
|
let final_size = iced::Size {
|
||||||
|
width: match self.width {
|
||||||
|
iced::Length::Shrink => f32::min(raw_size.width, full_size.width),
|
||||||
|
_ => raw_size.width,
|
||||||
|
},
|
||||||
|
height: match self.height {
|
||||||
|
iced::Length::Shrink => f32::min(raw_size.height, full_size.height),
|
||||||
|
_ => raw_size.height,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
layout::Node::new(final_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_tree: &widget::Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
_theme: &Theme,
|
||||||
|
_style: &advanced::renderer::Style,
|
||||||
|
layout: advanced::Layout<'_>,
|
||||||
|
_cursor: advanced::mouse::Cursor,
|
||||||
|
_viewport: &iced::Rectangle,
|
||||||
|
) {
|
||||||
|
let mut inner = self.video.write();
|
||||||
|
|
||||||
|
// bounds based on `Image::draw`
|
||||||
|
let image_size = iced::Size::new(inner.width as f32, inner.height as f32);
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
|
||||||
|
let scale = iced::Vector::new(
|
||||||
|
adjusted_fit.width / image_size.width,
|
||||||
|
adjusted_fit.height / image_size.height,
|
||||||
|
);
|
||||||
|
let final_size = iced::Size::new(image_size.width * scale.x, image_size.height * scale.y);
|
||||||
|
|
||||||
|
let position = match self.content_fit {
|
||||||
|
iced::ContentFit::None => iced::Point::new(
|
||||||
|
bounds.x + (image_size.width - adjusted_fit.width) / 2.0,
|
||||||
|
bounds.y + (image_size.height - adjusted_fit.height) / 2.0,
|
||||||
|
),
|
||||||
|
_ => iced::Point::new(
|
||||||
|
bounds.center_x() - final_size.width / 2.0,
|
||||||
|
bounds.center_y() - final_size.height / 2.0,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let drawing_bounds = iced::Rectangle::new(position, final_size);
|
||||||
|
|
||||||
|
let upload_frame = inner.upload_frame.swap(false, Ordering::SeqCst);
|
||||||
|
inner.redrawing.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
if upload_frame {
|
||||||
|
let last_frame_time = inner
|
||||||
|
.last_frame_time
|
||||||
|
.lock()
|
||||||
|
.map(|time| *time)
|
||||||
|
.unwrap_or_else(|_| Instant::now());
|
||||||
|
inner.set_av_offset(Instant::now() - last_frame_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
renderer.draw_primitive(
|
||||||
|
drawing_bounds,
|
||||||
|
VideoPrimitive::new(
|
||||||
|
inner.id,
|
||||||
|
Arc::clone(&inner.alive),
|
||||||
|
Arc::clone(&inner.frame),
|
||||||
|
(inner.width as _, inner.height as _),
|
||||||
|
upload_frame,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu"))]
|
||||||
|
{
|
||||||
|
if upload_frame {
|
||||||
|
let mut opt = None;
|
||||||
|
{
|
||||||
|
let yuv_data_opt = match inner.frame.lock() {
|
||||||
|
Ok(frame) => Some(frame),
|
||||||
|
Err(_err) => None,
|
||||||
|
};
|
||||||
|
if let Some(yuv_data) = yuv_data_opt.as_ref().and_then(|d| d.readable()) {
|
||||||
|
//TODO: convert on worker thread?
|
||||||
|
let rgba_data =
|
||||||
|
yuv_to_rgba(&yuv_data, inner.width as _, inner.height as _, 1);
|
||||||
|
opt = Some(advanced::image::Handle::from_rgba(
|
||||||
|
inner.width as _,
|
||||||
|
inner.height as _,
|
||||||
|
rgba_data,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
inner.handle_opt = opt;
|
||||||
|
}
|
||||||
|
if let Some(handle) = &inner.handle_opt {
|
||||||
|
use cosmic::iced::Radians;
|
||||||
|
|
||||||
|
renderer.draw_image(
|
||||||
|
handle.clone(),
|
||||||
|
advanced::image::FilterMethod::Nearest,
|
||||||
|
drawing_bounds,
|
||||||
|
Radians(0.),
|
||||||
|
1.0,
|
||||||
|
[0.0; 4],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
_state: &mut widget::Tree,
|
||||||
|
event: &iced::Event,
|
||||||
|
_layout: advanced::Layout<'_>,
|
||||||
|
_cursor: advanced::mouse::Cursor,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
_clipboard: &mut dyn advanced::Clipboard,
|
||||||
|
shell: &mut advanced::Shell<'_, Message>,
|
||||||
|
_viewport: &iced::Rectangle,
|
||||||
|
) {
|
||||||
|
let mut inner = self.video.write();
|
||||||
|
|
||||||
|
if let iced::Event::Window(iced::window::Event::RedrawRequested(_)) = event {
|
||||||
|
if inner.restart_stream || (!inner.is_eos && !inner.paused()) {
|
||||||
|
let mut restart_stream = false;
|
||||||
|
if inner.restart_stream {
|
||||||
|
restart_stream = true;
|
||||||
|
// Set flag to false to avoid potentially multiple seeks
|
||||||
|
inner.restart_stream = false;
|
||||||
|
}
|
||||||
|
let mut eos_pause = false;
|
||||||
|
|
||||||
|
while let Some(msg) = inner.bus.pop_filtered(&[
|
||||||
|
gst::MessageType::DurationChanged,
|
||||||
|
gst::MessageType::Error,
|
||||||
|
gst::MessageType::Element,
|
||||||
|
gst::MessageType::Eos,
|
||||||
|
gst::MessageType::Tag,
|
||||||
|
gst::MessageType::Warning,
|
||||||
|
]) {
|
||||||
|
match msg.view() {
|
||||||
|
gst::MessageView::DurationChanged(_) => {
|
||||||
|
inner.duration = Duration::from_nanos(
|
||||||
|
inner
|
||||||
|
.source
|
||||||
|
.query_duration::<gst::ClockTime>()
|
||||||
|
.map(|duration| duration.nseconds())
|
||||||
|
.unwrap_or(0),
|
||||||
|
);
|
||||||
|
if let Some(ref on_duration_changed) = self.on_duration_changed {
|
||||||
|
shell.publish(on_duration_changed(inner.duration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gst::MessageView::Error(err) => {
|
||||||
|
error!("bus returned an error: {err}");
|
||||||
|
if let Some(ref on_error) = self.on_error {
|
||||||
|
shell.publish(on_error(err.error()))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
gst::MessageView::Element(element) => {
|
||||||
|
if gst_pbutils::MissingPluginMessage::is(&element) {
|
||||||
|
if let Some(ref on_missing_plugin) = self.on_missing_plugin {
|
||||||
|
shell.publish(on_missing_plugin(element.copy()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gst::MessageView::Eos(_eos) => {
|
||||||
|
if let Some(on_end_of_stream) = self.on_end_of_stream.clone() {
|
||||||
|
shell.publish(on_end_of_stream);
|
||||||
|
}
|
||||||
|
if inner.looping {
|
||||||
|
restart_stream = true;
|
||||||
|
} else {
|
||||||
|
eos_pause = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gst::MessageView::Tag(tag_msg) => {
|
||||||
|
if let Some(ref on_tags) = self.on_tags {
|
||||||
|
shell.publish(on_tags(tag_msg.tags()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gst::MessageView::Warning(warn) => {
|
||||||
|
log::warn!("bus returned a warning: {warn}");
|
||||||
|
if let Some(ref on_warning) = self.on_warning {
|
||||||
|
shell.publish(on_warning(warn.error()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't run eos_pause if restart_stream is true; fixes "pausing" after restarting a stream
|
||||||
|
if restart_stream {
|
||||||
|
if let Err(err) = inner.restart_stream() {
|
||||||
|
error!("cannot restart stream (can't seek): {err:#?}");
|
||||||
|
}
|
||||||
|
} else if eos_pause {
|
||||||
|
inner.is_eos = true;
|
||||||
|
inner.set_paused(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inner.redrawing.load(Ordering::SeqCst)
|
||||||
|
&& inner.upload_frame.load(Ordering::SeqCst)
|
||||||
|
{
|
||||||
|
if let Some(on_new_frame) = self.on_new_frame.clone() {
|
||||||
|
inner.redrawing.store(true, Ordering::SeqCst);
|
||||||
|
shell.publish(on_new_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(on_subtitle_text) = &self.on_subtitle_text {
|
||||||
|
if inner.upload_text.swap(false, Ordering::SeqCst) {
|
||||||
|
if let Ok(text) = inner.subtitle_text.try_lock() {
|
||||||
|
shell.publish(on_subtitle_text(text.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
|
} else {
|
||||||
|
shell.request_redraw_at(iced::window::RedrawRequest::At(
|
||||||
|
Instant::now() + Duration::from_millis(32),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
_tree: &widget::Tree,
|
||||||
|
_layout: advanced::Layout<'_>,
|
||||||
|
_cursor_position: mouse::Cursor,
|
||||||
|
_viewport: &iced::Rectangle,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
if self.mouse_hidden {
|
||||||
|
mouse::Interaction::Hidden
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: widget::Id) {
|
||||||
|
self.id = Some(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<widget::Id> {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> From<VideoPlayer<'a, Message, Theme, Renderer>>
|
||||||
|
for Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Message: 'a + Clone,
|
||||||
|
Theme: 'a,
|
||||||
|
Renderer: 'a + PrimitiveRenderer,
|
||||||
|
{
|
||||||
|
fn from(video_player: VideoPlayer<'a, Message, Theme, Renderer>) -> Self {
|
||||||
|
Self::new(video_player)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue