Get ffmpeg based video player working (needs more work)

This commit is contained in:
Nethanja Focking 2023-04-24 18:16:37 -06:00
parent ae94252ba9
commit 42b65ea853
6 changed files with 466 additions and 78 deletions

210
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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);

View File

@ -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
}

View File

@ -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