diff --git a/Cargo.lock b/Cargo.lock index cd29cffc..5ff5ef4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 1ee13b7b..fdda546b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 81c87126..0b06ad56 100644 --- a/README.md +++ b/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 diff --git a/src/media.rs b/src/media.rs index ab1292cc..3660e50d 100644 --- a/src/media.rs +++ b/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, + image_temp: DashMap, + data_temp: DashMap>, media_pending_processing: DashSet, failed_media: RwLock>, } @@ -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 { - // 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 { // 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> { + // 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); diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index 10782198..0e9b7289 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -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 { } } -/* 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 { + 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 { +fn try_render_image(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option { 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 Option { + 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 +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 02707322..18e5e4cd 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -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, + // Rendering next_frame: Instant, override_dpi: bool, @@ -140,7 +146,8 @@ struct GossipUi { placeholder_avatar: TextureHandle, settings: Settings, avatars: HashMap, - media: HashMap, + images: HashMap, + video_players: HashMap>>, /// used when settings.show_media=false to explicitly show media_show_list: HashSet, /// 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>> { + // 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