feat: single task image loader
This commit is contained in:
parent
93e412a07c
commit
56b73b879e
4
TODO.md
Normal file
4
TODO.md
Normal file
@ -0,0 +1,4 @@
|
||||
- [Player] HLS demuxer
|
||||
- [Login] Proper key storage
|
||||
- [NDB] Handle PRE's
|
||||
- [UX] Render non-ascii chars with better font
|
@ -1,5 +1,4 @@
|
||||
use crate::app::{NativeLayerOps, ZapStreamApp};
|
||||
use crate::av_log_redirect;
|
||||
use eframe::Renderer;
|
||||
use egui::{Margin, ViewportBuilder};
|
||||
use serde::de::DeserializeOwned;
|
||||
@ -13,9 +12,6 @@ pub fn start_android(app: AndroidApp) {
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default().with_max_level(log::LevelFilter::Info),
|
||||
);
|
||||
unsafe {
|
||||
egui_video::ffmpeg_sys_the_third::av_log_set_callback(Some(av_log_redirect));
|
||||
}
|
||||
|
||||
let mut options = eframe::NativeOptions::default();
|
||||
options.renderer = Renderer::Glow;
|
||||
|
@ -8,15 +8,11 @@ use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use zap_stream_app::app::{NativeLayerOps, ZapStreamApp};
|
||||
use zap_stream_app::av_log_redirect;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
|
||||
unsafe {
|
||||
egui_video::ffmpeg_sys_the_third::av_log_set_callback(Some(av_log_redirect));
|
||||
}
|
||||
let mut options = eframe::NativeOptions::default();
|
||||
options.viewport = ViewportBuilder::default().with_inner_size(Vec2::new(1300., 900.));
|
||||
|
||||
|
41
src/lib.rs
41
src/lib.rs
@ -13,47 +13,6 @@ mod widgets;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use android_activity::AndroidApp;
|
||||
use log::log;
|
||||
use std::ffi::CStr;
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
type VaList = egui_video::ffmpeg_sys_the_third::va_list;
|
||||
#[cfg(target_os = "linux")]
|
||||
type VaList = *mut egui_video::ffmpeg_sys_the_third::__va_list_tag;
|
||||
#[cfg(target_os = "android")]
|
||||
type VaList = [u64; 4];
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn av_log_redirect(
|
||||
av_class: *mut libc::c_void,
|
||||
level: libc::c_int,
|
||||
fmt: *const libc::c_char,
|
||||
args: VaList,
|
||||
) {
|
||||
use egui_video::ffmpeg_sys_the_third::*;
|
||||
let log_level = match level {
|
||||
AV_LOG_DEBUG => log::Level::Debug,
|
||||
AV_LOG_WARNING => log::Level::Debug, // downgrade to debug (spammy)
|
||||
AV_LOG_INFO => log::Level::Info,
|
||||
AV_LOG_ERROR => log::Level::Error,
|
||||
AV_LOG_PANIC => log::Level::Error,
|
||||
AV_LOG_FATAL => log::Level::Error,
|
||||
_ => log::Level::Trace,
|
||||
};
|
||||
let mut buf: [u8; 1024] = [0; 1024];
|
||||
let mut prefix: libc::c_int = 1;
|
||||
av_log_format_line(
|
||||
av_class,
|
||||
level,
|
||||
fmt,
|
||||
args,
|
||||
buf.as_mut_ptr() as *mut libc::c_char,
|
||||
1024,
|
||||
ptr::addr_of_mut!(prefix),
|
||||
);
|
||||
log!(target: "ffmpeg", log_level, "{}", CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_str().unwrap().trim());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[no_mangle]
|
||||
|
@ -1,14 +1,14 @@
|
||||
use crate::services::ffmpeg_loader::FfmpegLoader;
|
||||
use crate::theme::NEUTRAL_800;
|
||||
use anyhow::Error;
|
||||
use anyhow::{Error, Result};
|
||||
use egui::{ColorImage, Context, Image, ImageData, TextureHandle, TextureOptions};
|
||||
use itertools::Itertools;
|
||||
use log::{error, info};
|
||||
use log::{info, warn};
|
||||
use lru::LruCache;
|
||||
use nostr_sdk::util::hex;
|
||||
use resvg::usvg::Transform;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::PathBuf;
|
||||
@ -16,12 +16,15 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
type ImageCacheStore = Arc<Mutex<LruCache<String, TextureHandle>>>;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
struct LoadRequest(String);
|
||||
|
||||
pub struct ImageCache {
|
||||
ctx: Context,
|
||||
dir: PathBuf,
|
||||
placeholder: TextureHandle,
|
||||
cache: ImageCacheStore,
|
||||
fetch_cache: Arc<Mutex<HashSet<String>>>,
|
||||
fetch_queue: Arc<Mutex<VecDeque<LoadRequest>>>,
|
||||
}
|
||||
|
||||
impl ImageCache {
|
||||
@ -34,24 +37,60 @@ impl ImageCache {
|
||||
ImageData::from(ColorImage::new([1, 1], NEUTRAL_800)),
|
||||
TextureOptions::default(),
|
||||
);
|
||||
let cache = Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(1_000).unwrap())));
|
||||
let fetch_queue = Arc::new(Mutex::new(VecDeque::<LoadRequest>::new()));
|
||||
let cc = cache.clone();
|
||||
let fq = fetch_queue.clone();
|
||||
let out_dir = out.clone();
|
||||
let ctx_clone = ctx.clone();
|
||||
let placeholder_clone = placeholder.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let next = fq.lock().unwrap().pop_front();
|
||||
if let Some(next) = next {
|
||||
let path = Self::find(&out_dir, &next.0);
|
||||
if path.exists() {
|
||||
let th = Self::load_image_texture(&ctx_clone, path, &next.0)
|
||||
.unwrap_or(placeholder_clone.clone());
|
||||
cc.lock().unwrap().put(next.0, th);
|
||||
ctx_clone.request_repaint();
|
||||
} else {
|
||||
match Self::download_image_to_disk(&path, &next.0).await {
|
||||
Ok(()) => {
|
||||
let th = Self::load_image_texture(&ctx_clone, path, &next.0)
|
||||
.unwrap_or(placeholder_clone.clone());
|
||||
cc.lock().unwrap().put(next.0, th);
|
||||
ctx_clone.request_repaint();
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to download image {}: {}", next.0, e);
|
||||
cc.lock().unwrap().put(next.0, placeholder_clone.clone());
|
||||
ctx_clone.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(30)).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
Self {
|
||||
ctx,
|
||||
dir: out,
|
||||
placeholder,
|
||||
cache: Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(1000).unwrap()))),
|
||||
fetch_cache: Arc::new(Mutex::new(HashSet::new())),
|
||||
cache,
|
||||
fetch_queue,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find<U>(&self, url: U) -> PathBuf
|
||||
pub fn find<U>(dir: &PathBuf, url: U) -> PathBuf
|
||||
where
|
||||
U: Into<String>,
|
||||
{
|
||||
let mut sha = Sha256::new();
|
||||
sha2::digest::Update::update(&mut sha, url.into().as_bytes());
|
||||
let hash = hex::encode(sha.finalize());
|
||||
self.dir
|
||||
.join(PathBuf::from(hash[0..2].to_string()))
|
||||
dir.join(PathBuf::from(hash[0..2].to_string()))
|
||||
.join(PathBuf::from(hash))
|
||||
}
|
||||
|
||||
@ -92,49 +131,28 @@ impl ImageCache {
|
||||
return Image::from_texture(i);
|
||||
}
|
||||
}
|
||||
let path = self.find(&u);
|
||||
if !path.exists() && !u.is_empty() {
|
||||
let path = path.clone();
|
||||
let cache = self.cache.clone();
|
||||
let ctx = self.ctx.clone();
|
||||
let fetch_cache = self.fetch_cache.clone();
|
||||
let placeholder = self.placeholder.clone();
|
||||
tokio::spawn(async move {
|
||||
if fetch_cache.lock().unwrap().insert(u.clone()) {
|
||||
info!("Fetching image: {}", &u);
|
||||
if let Ok(data) = reqwest::get(&u).await {
|
||||
tokio::fs::create_dir_all(path.parent().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let img_data = data.bytes().await.unwrap();
|
||||
if let Err(e) = tokio::fs::write(path.clone(), img_data).await {
|
||||
error!("Failed to write file: {}", e);
|
||||
}
|
||||
let t = Self::load_image(&ctx, path, &u)
|
||||
.await
|
||||
.unwrap_or(placeholder);
|
||||
cache.lock().unwrap().put(u.clone(), t);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if path.exists() {
|
||||
let path = path.clone();
|
||||
let ctx = self.ctx.clone();
|
||||
let cache = self.cache.clone();
|
||||
let placeholder = self.placeholder.clone();
|
||||
tokio::spawn(async move {
|
||||
let t = Self::load_image(&ctx, path, &u)
|
||||
.await
|
||||
.unwrap_or(placeholder);
|
||||
cache.lock().unwrap().put(u.clone(), t);
|
||||
ctx.request_repaint();
|
||||
});
|
||||
if let Ok(mut ql) = self.fetch_queue.lock() {
|
||||
let lr = LoadRequest(u.clone());
|
||||
if !ql.contains(&lr) {
|
||||
ql.push_back(lr);
|
||||
}
|
||||
}
|
||||
Image::from_texture(&self.placeholder)
|
||||
}
|
||||
|
||||
async fn load_image(ctx: &Context, path: PathBuf, key: &str) -> Option<TextureHandle> {
|
||||
/// Download an image to disk
|
||||
async fn download_image_to_disk(dst: &PathBuf, u: &str) -> Result<()> {
|
||||
info!("Fetching image: {}", &u);
|
||||
tokio::fs::create_dir_all(dst.parent().unwrap()).await?;
|
||||
|
||||
let data = reqwest::get(u).await?;
|
||||
let img_data = data.bytes().await?;
|
||||
tokio::fs::write(dst, img_data).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an image from disk into an egui texture handle
|
||||
fn load_image_texture(ctx: &Context, path: PathBuf, key: &str) -> Option<TextureHandle> {
|
||||
let loader = FfmpegLoader::new();
|
||||
match loader.load_image(path) {
|
||||
Ok(i) => Some(ctx.load_texture(key, ImageData::from(i), TextureOptions::default())),
|
||||
|
Loading…
x
Reference in New Issue
Block a user