mirror of
https://github.com/mikedilger/gossip.git
synced 2024-10-01 09:21:53 +00:00
Get ffmpeg based video player working (needs more work)
This commit is contained in:
parent
ae94252ba9
commit
42b65ea853
210
Cargo.lock
generated
210
Cargo.lock
generated
@ -198,6 +198,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.2.0"
|
||||
@ -421,6 +427,25 @@ version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.59.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
@ -586,6 +611,15 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -644,8 +678,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@ -660,6 +697,17 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "4.5.0"
|
||||
@ -671,6 +719,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
@ -1122,6 +1179,23 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui-video"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/n00kii/egui-video#50b4efd6d8250b555a85f966deca737c161ee6df"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"egui",
|
||||
"ffmpeg-next",
|
||||
"itertools",
|
||||
"parking_lot",
|
||||
"ringbuf",
|
||||
"sdl2",
|
||||
"tempfile",
|
||||
"timer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.21.1"
|
||||
@ -1354,6 +1428,30 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-next"
|
||||
version = "5.1.1"
|
||||
source = "git+https://github.com/n00kii/rust-ffmpeg.git#36cf42c36635e566972ecf26996eb0b28012ef86"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "5.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d780b36e092254367e2f1f21191992735c8e23f31a5a5a8678db3a79f775021f"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
@ -1547,7 +1645,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -1572,6 +1670,12 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.12.1"
|
||||
@ -1658,6 +1762,7 @@ dependencies = [
|
||||
"dashmap",
|
||||
"dirs",
|
||||
"eframe",
|
||||
"egui-video",
|
||||
"egui-winit",
|
||||
"egui_extras",
|
||||
"encoding_rs",
|
||||
@ -1680,10 +1785,11 @@ dependencies = [
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rusqlite",
|
||||
"sdl2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tracing",
|
||||
@ -2003,6 +2109,15 @@ version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
@ -2093,6 +2208,12 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.2"
|
||||
@ -2302,7 +2423,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
@ -2737,6 +2858,12 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@ -3099,6 +3226,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ringbuf"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.8.0"
|
||||
@ -3145,6 +3281,12 @@ dependencies = [
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
@ -3289,6 +3431,30 @@ dependencies = [
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cmake",
|
||||
"libc",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.1"
|
||||
@ -3417,6 +3583,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
@ -3666,6 +3838,17 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.20"
|
||||
@ -3693,6 +3876,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "timer"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.8.3"
|
||||
@ -4057,6 +4249,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfd18cb0279ed472822644fed8a86507fa8578241d4e047eddb3129591350cc0"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
@ -4089,6 +4287,12 @@ dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -16,6 +16,10 @@ lang-cjk = []
|
||||
native-tls = [ "reqwest/native-tls", "tungstenite/native-tls", "tokio-tungstenite/native-tls"]
|
||||
rustls-tls = [ "reqwest/rustls-tls", "tungstenite/rustls-tls-webpki-roots", "tokio-tungstenite/rustls-tls-webpki-roots"]
|
||||
|
||||
[patch.crates-io]
|
||||
# override egui crate for egui_video dependency
|
||||
egui = { git = "https://github.com/mikedilger/egui", branch="gossip" }
|
||||
|
||||
[dependencies]
|
||||
async-recursion = "1.0"
|
||||
async-trait = "0.1"
|
||||
@ -25,6 +29,7 @@ dirs = "4.0"
|
||||
eframe = { git = "https://github.com/mikedilger/egui", branch="gossip", features = [ "dark-light", "persistence" ] }
|
||||
egui-winit = { git = "https://github.com/mikedilger/egui", branch="gossip", features = [ "default" ] }
|
||||
egui_extras = { git = "https://github.com/mikedilger/egui", branch="gossip", features = [ "image", "svg", "tracing" ] }
|
||||
egui-video = { git = "https://github.com/n00kii/egui-video", features = [ "from_bytes" ] }
|
||||
encoding_rs = "0.8"
|
||||
fallible-iterator = "0.2"
|
||||
futures = "0.3"
|
||||
@ -45,6 +50,7 @@ rand = "0.8"
|
||||
regex = "1.7"
|
||||
reqwest = { version = "0.11", default-features=false, features = ["brotli", "deflate", "gzip", "json"] }
|
||||
rusqlite = { version = "0.28", features = ["bundled", "chrono", "serde_json"] }
|
||||
sdl2 = { version = "0.35.2", features = ["bundled"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sha2 = "0.10"
|
||||
|
11
README.md
11
README.md
@ -116,6 +116,17 @@ Most dependencies are probably already installed in your base operating system.
|
||||
- pkg-config (debian: "pkg-config")
|
||||
- openssl (debian: "libssl-dev")
|
||||
- fontconfig (debian: "libfontconfig1-dev")
|
||||
- rust-sdl2 (follow the instructions in the [readme](https://github.com/Rust-SDL2/rust-sdl2/))
|
||||
|
||||
#### macOS
|
||||
|
||||
a. Install rust with rust-up: https://rustup.rs/
|
||||
b. Install homebrew if you don't have it yet https://brew.sh/
|
||||
c. Install these dependencies:
|
||||
|
||||
```
|
||||
brew install cmake sdl2 pkg-config ffmpeg@5
|
||||
```
|
||||
|
||||
### Step 3 - Clone this Repository
|
||||
|
||||
|
64
src/media.rs
64
src/media.rs
@ -12,7 +12,8 @@ pub struct Media {
|
||||
// until the UI next asks for them, at which point we remove them
|
||||
// and hand them over. This way we can do the work that takes
|
||||
// longer and the UI can do as little work as possible.
|
||||
media_temp: DashMap<Url, ColorImage>,
|
||||
image_temp: DashMap<Url, ColorImage>,
|
||||
data_temp: DashMap<Url, Vec<u8>>,
|
||||
media_pending_processing: DashSet<Url>,
|
||||
failed_media: RwLock<HashSet<UncheckedUrl>>,
|
||||
}
|
||||
@ -20,7 +21,8 @@ pub struct Media {
|
||||
impl Media {
|
||||
pub fn new() -> Media {
|
||||
Media {
|
||||
media_temp: DashMap::new(),
|
||||
image_temp: DashMap::new(),
|
||||
data_temp: DashMap::new(),
|
||||
media_pending_processing: DashSet::new(),
|
||||
failed_media: RwLock::new(HashSet::new()),
|
||||
}
|
||||
@ -33,7 +35,6 @@ impl Media {
|
||||
Err(_) => {
|
||||
// this cannot recover without new metadata
|
||||
self.failed_media.blocking_write().insert(unchecked_url);
|
||||
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@ -48,18 +49,9 @@ impl Media {
|
||||
self.failed_media.blocking_write().remove(unchecked_url);
|
||||
}
|
||||
|
||||
pub fn get_media(&self, url: &Url) -> Option<ColorImage> {
|
||||
// If it failed before, error out now
|
||||
if self
|
||||
.failed_media
|
||||
.blocking_read()
|
||||
.contains(&url.to_unchecked_url())
|
||||
{
|
||||
return None; // cannot recover.
|
||||
}
|
||||
|
||||
pub fn get_image(&self, url: &Url) -> Option<ColorImage> {
|
||||
// If we have it, hand it over (we won't need a copy anymore)
|
||||
if let Some(th) = self.media_temp.remove(url) {
|
||||
if let Some(th) = self.image_temp.remove(url) {
|
||||
return Some(th.1);
|
||||
}
|
||||
|
||||
@ -68,14 +60,8 @@ impl Media {
|
||||
return None; // will recover after processing completes
|
||||
}
|
||||
|
||||
// Do not fetch if disabled
|
||||
if !GLOBALS.settings.read().load_media {
|
||||
return None; // can recover if the setting is switched
|
||||
}
|
||||
|
||||
match GLOBALS.fetcher.try_get(url.clone()) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(bytes)) => {
|
||||
match self.get_data(url) {
|
||||
Some(bytes) => {
|
||||
// Finish this later (spawn)
|
||||
let aurl = url.to_owned();
|
||||
tokio::spawn(async move {
|
||||
@ -85,12 +71,12 @@ impl Media {
|
||||
.load(Ordering::Relaxed)
|
||||
/ 100;
|
||||
if let Ok(color_image) = egui_extras::image::load_image_bytes(&bytes) {
|
||||
GLOBALS.media.media_temp.insert(aurl, color_image);
|
||||
GLOBALS.media.image_temp.insert(aurl, color_image);
|
||||
} else if let Ok(color_image) = egui_extras::image::load_svg_bytes_with_size(
|
||||
&bytes,
|
||||
FitTo::Size(size, size),
|
||||
) {
|
||||
GLOBALS.media.media_temp.insert(aurl, color_image);
|
||||
GLOBALS.media.image_temp.insert(aurl, color_image);
|
||||
} else {
|
||||
// this cannot recover without new metadata
|
||||
GLOBALS
|
||||
@ -103,6 +89,36 @@ impl Media {
|
||||
});
|
||||
self.media_pending_processing.insert(url.clone());
|
||||
None
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_data(&self, url: &Url) -> Option<Vec<u8>> {
|
||||
// If it failed before, error out now
|
||||
if self
|
||||
.failed_media
|
||||
.blocking_read()
|
||||
.contains(&url.to_unchecked_url())
|
||||
{
|
||||
return None; // cannot recover.
|
||||
}
|
||||
|
||||
// If we have it, hand it over (we won't need a copy anymore)
|
||||
if let Some(th) = self.data_temp.remove(url) {
|
||||
return Some(th.1);
|
||||
}
|
||||
|
||||
// Do not fetch if disabled
|
||||
if !GLOBALS.settings.read().load_media {
|
||||
return None; // can recover if the setting is switched
|
||||
}
|
||||
|
||||
match GLOBALS.fetcher.try_get(url.clone()) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(bytes)) => {
|
||||
self.data_temp.insert(url.clone(), bytes);
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("{}", e);
|
||||
|
@ -117,9 +117,8 @@ pub(super) fn render_hyperlink(
|
||||
let link = note.shattered_content.slice(linkspan).unwrap();
|
||||
if let Some(image_url) = as_image_url(app, link) {
|
||||
show_image_toggle(app, ui, image_url);
|
||||
//} else if is_video_url(&lowercase) {
|
||||
// TODO
|
||||
// crate::ui::widgets::break_anywhere_hyperlink_to(ui, link, link);
|
||||
} else if let Some(video_url) = as_video_url(app, link) {
|
||||
show_video_toggle(app, ui, video_url);
|
||||
} else {
|
||||
crate::ui::widgets::break_anywhere_hyperlink_to(ui, link, link);
|
||||
}
|
||||
@ -189,7 +188,6 @@ fn as_image_url(app: &mut GossipUi, url: &str) -> Option<Url> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn is_video_url(url: &str) -> bool {
|
||||
let lower = url.to_lowercase();
|
||||
lower.ends_with(".mov")
|
||||
@ -197,7 +195,14 @@ fn is_video_url(url: &str) -> bool {
|
||||
|| lower.ends_with(".mkv")
|
||||
|| lower.ends_with(".webm")
|
||||
}
|
||||
*/
|
||||
|
||||
fn as_video_url(app: &mut GossipUi, url: &str) -> Option<Url> {
|
||||
if is_video_url(url) {
|
||||
app.try_check_url(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
let row_height = ui.cursor().height();
|
||||
@ -209,7 +214,7 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
|| (!app.settings.show_media && app.media_show_list.contains(&url));
|
||||
|
||||
if show_image {
|
||||
if let Some(response) = try_render_media(app, ui, url.clone()) {
|
||||
if let Some(response) = try_render_image(app, ui, url.clone()) {
|
||||
show_link = false;
|
||||
|
||||
// full-width toggle
|
||||
@ -268,54 +273,16 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
|
||||
/// Try to fetch and render a piece of media
|
||||
/// - return: true if successfully rendered, false otherwise
|
||||
fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
||||
fn try_render_image(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
||||
let mut response_return = None;
|
||||
if let Some(media) = app.try_get_media(ui.ctx(), url.clone()) {
|
||||
let ui_max = if app.media_full_width_list.contains(&url) {
|
||||
Vec2::new(
|
||||
ui.available_width() * 0.9,
|
||||
ui.ctx().screen_rect().height() * 0.9,
|
||||
)
|
||||
} else {
|
||||
Vec2::new(
|
||||
ui.available_width() / 2.0,
|
||||
ui.ctx().screen_rect().height() / 3.0,
|
||||
)
|
||||
};
|
||||
let msize = media.size_vec2();
|
||||
let aspect = media.aspect_ratio();
|
||||
let size = media_scale(app.media_full_width_list.contains(&url), ui, media.size_vec2());
|
||||
|
||||
// insert a newline if the current line has text
|
||||
if ui.cursor().min.x > ui.max_rect().min.x {
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
// determine maximum x and y sizes
|
||||
let max_x = if ui_max.x > msize.x {
|
||||
msize.x
|
||||
} else {
|
||||
ui_max.x
|
||||
};
|
||||
let max_y = if ui_max.y > msize.y {
|
||||
msize.y
|
||||
} else {
|
||||
ui_max.y
|
||||
};
|
||||
|
||||
// now determine if we are constrained by x or by y and
|
||||
// calculate the resulting size
|
||||
let mut size = Vec2::new(0.0, 0.0);
|
||||
size.x = if max_x > max_y * aspect {
|
||||
max_y * aspect
|
||||
} else {
|
||||
max_x
|
||||
};
|
||||
size.y = if max_y > max_x / aspect {
|
||||
max_x / aspect
|
||||
} else {
|
||||
max_y
|
||||
};
|
||||
|
||||
// render the image with a nice frame around it
|
||||
egui::Frame::none()
|
||||
.inner_margin(egui::Margin::same(0.0))
|
||||
@ -396,3 +363,130 @@ fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Respons
|
||||
};
|
||||
response_return
|
||||
}
|
||||
|
||||
fn show_video_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
let row_height = ui.cursor().height();
|
||||
let url_string = url.to_string();
|
||||
let mut show_link = true;
|
||||
|
||||
// FIXME show/hide lists should persist app restarts
|
||||
let show_video = (app.settings.show_media && !app.media_hide_list.contains(&url))
|
||||
|| (!app.settings.show_media && app.media_show_list.contains(&url));
|
||||
|
||||
if show_video {
|
||||
if let Some(response) = try_render_video(app, ui, url.clone()) {
|
||||
show_link = false;
|
||||
|
||||
// full-width toggle
|
||||
if response.clicked() {
|
||||
if app.media_full_width_list.contains(&url) {
|
||||
app.media_full_width_list.remove(&url);
|
||||
} else {
|
||||
app.media_full_width_list.insert(url.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if show_link {
|
||||
let response = ui.link("[ Video ]");
|
||||
// show url on hover
|
||||
response.clone().on_hover_text(url_string.clone());
|
||||
// show media toggle
|
||||
if response.clicked() {
|
||||
if app.settings.show_media {
|
||||
app.media_hide_list.remove(&url);
|
||||
} else {
|
||||
app.media_show_list.insert(url.clone());
|
||||
}
|
||||
if !app.settings.load_media {
|
||||
*GLOBALS.status_message.blocking_write() = "Fetch Media setting is disabled. Right-click link to open in browser or copy URL".to_owned();
|
||||
}
|
||||
}
|
||||
// context menu
|
||||
response.context_menu(|ui| {
|
||||
if ui.button("Open in browser").clicked() {
|
||||
let modifiers = ui.ctx().input(|i| i.modifiers);
|
||||
ui.ctx().output_mut(|o| {
|
||||
o.open_url = Some(egui::output::OpenUrl {
|
||||
url: url_string.clone(),
|
||||
new_tab: modifiers.any(),
|
||||
});
|
||||
});
|
||||
}
|
||||
if ui.button("Copy URL").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = url_string.clone());
|
||||
}
|
||||
if app.has_media_loading_failed(url_string.as_str())
|
||||
&& ui.button("Retry loading ...").clicked()
|
||||
{
|
||||
app.retry_media(&url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ui.end_row();
|
||||
|
||||
// workaround for egui bug where image enlarges the cursor height
|
||||
ui.set_row_height(row_height);
|
||||
}
|
||||
|
||||
fn try_render_video(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
||||
let mut response_return = None;
|
||||
let show_full_width = app.media_full_width_list.contains(&url);
|
||||
if let Some(player_ref) = app.try_get_player(ui.ctx(), url.clone()) {
|
||||
if let Ok(mut player) = player_ref.try_borrow_mut() {
|
||||
let size = media_scale(show_full_width, ui, Vec2{ x: player.width as f32, y: player.height as f32 });
|
||||
|
||||
// show the player
|
||||
let response = player.ui( ui, [ size.x, size.y ] );
|
||||
|
||||
// TODO fix click action
|
||||
let new_rect = response.rect.shrink(size.x / 2.0);
|
||||
response_return = Some(response.with_new_rect(new_rect))
|
||||
}
|
||||
}
|
||||
response_return
|
||||
}
|
||||
|
||||
fn media_scale(show_full_width: bool, ui: &Ui, media_size: Vec2) -> Vec2 {
|
||||
let aspect = media_size.x / media_size.y;
|
||||
let ui_max = if show_full_width {
|
||||
Vec2::new(
|
||||
ui.available_width() * 0.9,
|
||||
ui.ctx().screen_rect().height() * 0.9,
|
||||
)
|
||||
} else {
|
||||
Vec2::new(
|
||||
ui.available_width() / 2.0,
|
||||
ui.ctx().screen_rect().height() / 3.0,
|
||||
)
|
||||
};
|
||||
|
||||
// determine maximum x and y sizes
|
||||
let max_x = if ui_max.x > media_size.x {
|
||||
media_size.x
|
||||
} else {
|
||||
ui_max.x
|
||||
};
|
||||
let max_y = if ui_max.y > media_size.y {
|
||||
media_size.y
|
||||
} else {
|
||||
ui_max.y
|
||||
};
|
||||
|
||||
// now determine if we are constrained by x or by y and
|
||||
// calculate the resulting size
|
||||
let mut size = Vec2::new(0.0, 0.0);
|
||||
size.x = if max_x > max_y * aspect {
|
||||
max_y * aspect
|
||||
} else {
|
||||
max_x
|
||||
};
|
||||
size.y = if max_y > max_x / aspect {
|
||||
max_x / aspect
|
||||
} else {
|
||||
max_y
|
||||
};
|
||||
size
|
||||
}
|
||||
|
@ -37,8 +37,11 @@ use egui::{
|
||||
Color32, ColorImage, Context, Image, ImageData, Label, RichText, SelectableLabel, Sense,
|
||||
TextStyle, TextureHandle, TextureOptions, Ui, Vec2,
|
||||
};
|
||||
use egui_video::{AudioDevice, Player};
|
||||
use nostr_types::{Id, IdHex, Metadata, PublicKey, PublicKeyHex, RelayUrl, UncheckedUrl, Url};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::{Duration, Instant};
|
||||
use zeroize::Zeroize;
|
||||
@ -104,6 +107,9 @@ pub enum HighlightType {
|
||||
}
|
||||
|
||||
struct GossipUi {
|
||||
// ffmpeg player
|
||||
audio_device: Option<AudioDevice>,
|
||||
|
||||
// Rendering
|
||||
next_frame: Instant,
|
||||
override_dpi: bool,
|
||||
@ -140,7 +146,8 @@ struct GossipUi {
|
||||
placeholder_avatar: TextureHandle,
|
||||
settings: Settings,
|
||||
avatars: HashMap<PublicKeyHex, TextureHandle>,
|
||||
media: HashMap<Url, TextureHandle>,
|
||||
images: HashMap<Url, TextureHandle>,
|
||||
video_players: HashMap<Url, Rc<RefCell<egui_video::Player>>>,
|
||||
/// used when settings.show_media=false to explicitly show
|
||||
media_show_list: HashSet<Url>,
|
||||
/// used when settings.show_media=false to explicitly hide
|
||||
@ -257,6 +264,18 @@ impl GossipUi {
|
||||
)
|
||||
};
|
||||
|
||||
let audio_device = {
|
||||
let mut device = None;
|
||||
if let Ok(init) = sdl2::init() {
|
||||
if let Ok(audio) = init.audio() {
|
||||
if let Ok(dev) = egui_video::init_audio_device(&audio) {
|
||||
device = Some(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
device
|
||||
};
|
||||
|
||||
// how to load an svg
|
||||
// let expand_right_symbol = {
|
||||
// let bytes = include_bytes!("../../assets/expand-image.svg");
|
||||
@ -289,6 +308,7 @@ impl GossipUi {
|
||||
theme::apply_theme(settings.theme, &cctx.egui_ctx);
|
||||
|
||||
GossipUi {
|
||||
audio_device,
|
||||
next_frame: Instant::now(),
|
||||
override_dpi,
|
||||
override_dpi_value,
|
||||
@ -311,7 +331,8 @@ impl GossipUi {
|
||||
placeholder_avatar: placeholder_avatar_texture_handle,
|
||||
settings,
|
||||
avatars: HashMap::new(),
|
||||
media: HashMap::new(),
|
||||
images: HashMap::new(),
|
||||
video_players: HashMap::new(),
|
||||
media_show_list: HashSet::new(),
|
||||
media_hide_list: HashSet::new(),
|
||||
media_full_width_list: HashSet::new(),
|
||||
@ -758,20 +779,56 @@ impl GossipUi {
|
||||
}
|
||||
|
||||
// see if we already have a texturehandle for this media
|
||||
if let Some(th) = self.media.get(&url) {
|
||||
if let Some(th) = self.images.get(&url) {
|
||||
return Some(th.to_owned());
|
||||
}
|
||||
|
||||
if let Some(color_image) = GLOBALS.media.get_media(&url) {
|
||||
if let Some(color_image) = GLOBALS.media.get_image(&url) {
|
||||
let texture_handle =
|
||||
ctx.load_texture(url.0.clone(), color_image, TextureOptions::default());
|
||||
self.media.insert(url, texture_handle.clone());
|
||||
self.images.insert(url, texture_handle.clone());
|
||||
Some(texture_handle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_player(&mut self, ctx: &Context, url: Url) -> Option<Rc<RefCell<egui_video::Player>>> {
|
||||
// Do not keep retrying if failed
|
||||
if GLOBALS.media.has_failed(&url.to_unchecked_url()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// see if we already have a player for this video
|
||||
if let Some(player) = self.video_players.get(&url) {
|
||||
return Some(player.to_owned());
|
||||
}
|
||||
|
||||
if let Some(bytes) = GLOBALS.media.get_data(&url) {
|
||||
if let Ok(player) = Player::new_from_bytes(ctx, &bytes) {
|
||||
if let Some(audio) = &mut self.audio_device {
|
||||
if let Ok(player) = player.with_audio(audio) {
|
||||
let player_ref = Rc::new( RefCell::new( player ) );
|
||||
self.video_players.insert(url.clone(), player_ref.clone());
|
||||
Some(player_ref)
|
||||
} else {
|
||||
GLOBALS.media.has_failed(&url.to_unchecked_url());
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let player_ref = Rc::new( RefCell::new( player ) );
|
||||
self.video_players.insert(url.clone(), player_ref.clone());
|
||||
Some(player_ref)
|
||||
}
|
||||
} else {
|
||||
GLOBALS.media.has_failed(&url.to_unchecked_url());
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_qr(&mut self, ui: &mut Ui, ctx: &Context, key: &str, content: &str) {
|
||||
// Remember the UI runs this every frame. We do NOT want to load the texture to the GPU
|
||||
// every frame, so we remember the texture handle in app.qr_codes, and only load to the GPU
|
||||
|
Loading…
Reference in New Issue
Block a user