feat: Added stuff
This commit is contained in:
42
Cargo.lock
generated
42
Cargo.lock
generated
@@ -655,6 +655,15 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.71.1"
|
version = "0.71.1"
|
||||||
@@ -3271,6 +3280,7 @@ source = "git+https://github.com/iced-rs/iced#645643bfd63ed4c01aa281f97992e3c276
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"iced_debug",
|
"iced_debug",
|
||||||
|
"iced_devtools",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
"iced_renderer",
|
"iced_renderer",
|
||||||
"iced_runtime",
|
"iced_runtime",
|
||||||
@@ -3280,6 +3290,21 @@ dependencies = [
|
|||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iced_beacon"
|
||||||
|
version = "0.14.0-dev"
|
||||||
|
source = "git+https://github.com/iced-rs/iced#645643bfd63ed4c01aa281f97992e3c276e71498"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"futures",
|
||||||
|
"iced_core",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
@@ -3292,6 +3317,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
|
"serde",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -3302,11 +3328,23 @@ name = "iced_debug"
|
|||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
source = "git+https://github.com/iced-rs/iced#645643bfd63ed4c01aa281f97992e3c276e71498"
|
source = "git+https://github.com/iced-rs/iced#645643bfd63ed4c01aa281f97992e3c276e71498"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"iced_beacon",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iced_devtools"
|
||||||
|
version = "0.14.0-dev"
|
||||||
|
source = "git+https://github.com/iced-rs/iced#645643bfd63ed4c01aa281f97992e3c276e71498"
|
||||||
|
dependencies = [
|
||||||
|
"iced_debug",
|
||||||
|
"iced_program",
|
||||||
|
"iced_widget",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_futures"
|
name = "iced_futures"
|
||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
@@ -6294,6 +6332,10 @@ name = "semver"
|
|||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ iced = { git = "https://github.com/iced-rs/iced", features = [
|
|||||||
"image",
|
"image",
|
||||||
"sipper",
|
"sipper",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"debug",
|
||||||
] }
|
] }
|
||||||
iced_wgpu = { git = "https://github.com/iced-rs/iced" }
|
iced_wgpu = { git = "https://github.com/iced-rs/iced" }
|
||||||
iced_video_player = { path = "crates/iced_video_player" }
|
iced_video_player = { path = "crates/iced_video_player" }
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ authors = ["jazzfool"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
exclude = [
|
exclude = [".media/test.mp4"]
|
||||||
".media/test.mp4"
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { git = "https://github.com/iced-rs/iced", features = ["image", "advanced", "wgpu"] }
|
iced = { git = "https://github.com/iced-rs/iced", features = [
|
||||||
|
"image",
|
||||||
|
"advanced",
|
||||||
|
"wgpu",
|
||||||
|
] }
|
||||||
iced_wgpu = { git = "https://github.com/iced-rs/iced" }
|
iced_wgpu = { git = "https://github.com/iced-rs/iced" }
|
||||||
gstreamer = "0.23"
|
gstreamer = "0.23"
|
||||||
gstreamer-app = "0.23" # appsink
|
gstreamer-app = "0.23" # appsink
|
||||||
@@ -37,9 +39,23 @@ runtimeLibs = [
|
|||||||
"libxkbcommon",
|
"libxkbcommon",
|
||||||
"xorg.libX11",
|
"xorg.libX11",
|
||||||
"xorg.libXrandr",
|
"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",
|
"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",
|
||||||
]
|
]
|
||||||
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]
|
[package.metadata.docs.rs]
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
rustc-args = ["--cfg", "docsrs"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use iced_video_player::{Video, VideoPlayer};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
iced::run("Iced Video Player", App::update, App::view)
|
iced::run(App::update, App::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -29,17 +29,10 @@ impl Default for App {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
App {
|
App {
|
||||||
video: Video::new(
|
video: Video::new(
|
||||||
&url::Url::from_file_path(
|
&url::Url::parse("https://jellyfin.tsuba.darksailor.dev/Videos/1d7e2012-e17d-edbb-25c3-2dbcc803d6b6/stream?static=true")
|
||||||
std::path::PathBuf::from(file!())
|
.expect("Failed to parse URL"),
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.join("../.media/test.mp4")
|
|
||||||
.canonicalize()
|
|
||||||
.unwrap(),
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.expect("Failed to create video"),
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
position: 0.0,
|
position: 0.0,
|
||||||
dragging: false,
|
dragging: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,6 +273,8 @@ impl Video {
|
|||||||
|
|
||||||
let pad = video_sink.pads().first().cloned().unwrap();
|
let pad = video_sink.pads().first().cloned().unwrap();
|
||||||
|
|
||||||
|
dbg!(&pad);
|
||||||
|
dbg!(&pipeline);
|
||||||
cleanup!(pipeline.set_state(gst::State::Playing))?;
|
cleanup!(pipeline.set_state(gst::State::Playing))?;
|
||||||
|
|
||||||
// wait for up to 5 seconds until the decoder gets the source capabilities
|
// wait for up to 5 seconds until the decoder gets the source capabilities
|
||||||
|
|||||||
34
flake.nix
34
flake.nix
@@ -74,10 +74,28 @@
|
|||||||
|
|
||||||
buildInputs = with pkgs;
|
buildInputs = with pkgs;
|
||||||
[
|
[
|
||||||
vulkan-loader
|
gst_all_1.gst-libav
|
||||||
|
gst_all_1.gst-plugins-bad
|
||||||
|
gst_all_1.gst-editing-services
|
||||||
|
gst_all_1.gst-plugins-rs
|
||||||
|
gst_all_1.gst-plugins-base
|
||||||
|
gst_all_1.gst-plugins-good
|
||||||
|
gst_all_1.gst-plugins-ugly
|
||||||
|
gst_all_1.gstreamer
|
||||||
|
gst_all_1.gstreamermm
|
||||||
|
gst_all_1.gst-rtsp-server
|
||||||
|
gst_all_1.gst-vaapi
|
||||||
|
|
||||||
|
xorg.libX11
|
||||||
|
xorg.libXrandr
|
||||||
|
xorg.libXi
|
||||||
|
|
||||||
|
libxkbcommon
|
||||||
openssl
|
openssl
|
||||||
gst_all_1.gstreamer.dev
|
vulkan-loader
|
||||||
gst_all_1.gst-plugins-base.dev
|
wayland
|
||||||
|
wayland-protocols
|
||||||
|
glib
|
||||||
]
|
]
|
||||||
++ (lib.optionals pkgs.stdenv.isLinux [
|
++ (lib.optionals pkgs.stdenv.isLinux [
|
||||||
alsa-lib-with-plugins
|
alsa-lib-with-plugins
|
||||||
@@ -146,11 +164,10 @@
|
|||||||
devShells = {
|
devShells = {
|
||||||
default =
|
default =
|
||||||
pkgs.mkShell.override {
|
pkgs.mkShell.override {
|
||||||
stdenv = pkgs.clangStdenv;
|
stdenv =
|
||||||
# stdenv =
|
if pkgs.stdenv.isLinux
|
||||||
# if pkgs.stdenv.isLinux
|
then (pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv)
|
||||||
# then (pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv)
|
else pkgs.clangStdenv;
|
||||||
# else pkgs.clangStdenv;
|
|
||||||
} (commonArgs
|
} (commonArgs
|
||||||
// {
|
// {
|
||||||
packages = with pkgs;
|
packages = with pkgs;
|
||||||
@@ -164,6 +181,7 @@
|
|||||||
cargo-hack
|
cargo-hack
|
||||||
cargo-outdated
|
cargo-outdated
|
||||||
lld
|
lld
|
||||||
|
lldb
|
||||||
]
|
]
|
||||||
++ (lib.optionals pkgs.stdenv.isDarwin [
|
++ (lib.optionals pkgs.stdenv.isDarwin [
|
||||||
apple-sdk_13
|
apple-sdk_13
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use std::sync::Arc;
|
|||||||
mod blur_hash;
|
mod blur_hash;
|
||||||
use blur_hash::BlurHash;
|
use blur_hash::BlurHash;
|
||||||
|
|
||||||
// mod preview;
|
mod preview;
|
||||||
// use preview::Preview;
|
use preview::Preview;
|
||||||
|
|
||||||
use iced::{Alignment, Element, Length, Task, widget::*};
|
use iced::{Alignment, Element, Length, Shadow, Task, widget::*};
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -38,6 +38,7 @@ impl ItemCache {
|
|||||||
self.insert(parent, item);
|
self.insert(parent, item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn items_of(&self, parent: impl Into<Option<uuid::Uuid>>) -> Vec<&Item> {
|
pub fn items_of(&self, parent: impl Into<Option<uuid::Uuid>>) -> Vec<&Item> {
|
||||||
let parent = parent.into();
|
let parent = parent.into();
|
||||||
self.tree.get(&None);
|
self.tree.get(&None);
|
||||||
@@ -50,6 +51,10 @@ impl ItemCache {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: &uuid::Uuid) -> Option<&Item> {
|
||||||
|
self.items.get(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -75,6 +80,7 @@ impl From<api::jellyfin::BaseItemDto> for Item {
|
|||||||
.and_then(|hashes| hashes.get(&tag).cloned())
|
.and_then(|hashes| hashes.get(&tag).cloned())
|
||||||
.map(|s| s.clone().into()),
|
.map(|s| s.clone().into()),
|
||||||
}),
|
}),
|
||||||
|
_type: dto._type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,16 +91,16 @@ pub struct Item {
|
|||||||
pub parent_id: Option<uuid::Uuid>,
|
pub parent_id: Option<uuid::Uuid>,
|
||||||
pub name: Option<SharedString>,
|
pub name: Option<SharedString>,
|
||||||
pub thumbnail: Option<Thumbnail>,
|
pub thumbnail: Option<Thumbnail>,
|
||||||
|
pub _type: api::jellyfin::BaseItemKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub enum Screen {
|
pub enum Screen {
|
||||||
#[default]
|
#[default]
|
||||||
Home,
|
Home,
|
||||||
Item(Option<uuid::Uuid>),
|
|
||||||
Search(String),
|
|
||||||
Settings,
|
Settings,
|
||||||
User,
|
User,
|
||||||
|
Video,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct State {
|
struct State {
|
||||||
@@ -146,13 +152,24 @@ pub enum Message {
|
|||||||
SetToken(String),
|
SetToken(String),
|
||||||
Back,
|
Back,
|
||||||
Home,
|
Home,
|
||||||
OpenVideo(url::Url),
|
|
||||||
// Login-related messages
|
// Login-related messages
|
||||||
UsernameChanged(String),
|
UsernameChanged(String),
|
||||||
PasswordChanged(String),
|
PasswordChanged(String),
|
||||||
Login,
|
Login,
|
||||||
LoginSuccess(String),
|
LoginSuccess(String),
|
||||||
Logout,
|
Logout,
|
||||||
|
Video(VideoMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum VideoMessage {
|
||||||
|
EndOfStream,
|
||||||
|
Open(url::Url),
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
|
Seek(f64),
|
||||||
|
Stop,
|
||||||
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(state: &mut State, message: Message) -> Task<Message> {
|
fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||||
@@ -227,6 +244,15 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
|||||||
}
|
}
|
||||||
Message::OpenItem(id) => {
|
Message::OpenItem(id) => {
|
||||||
let client = state.jellyfin_client.clone();
|
let client = state.jellyfin_client.clone();
|
||||||
|
use api::jellyfin::BaseItemKind::*;
|
||||||
|
if let Some(cached) = id.as_ref().and_then(|id| state.cache.get(id))
|
||||||
|
&& matches!(cached._type, Video | Movie | Episode)
|
||||||
|
{
|
||||||
|
let url = client
|
||||||
|
.stream_url(id.expect("ID exists"))
|
||||||
|
.expect("Failed to get stream URL");
|
||||||
|
Task::done(Message::Video(VideoMessage::Open(url)))
|
||||||
|
} else {
|
||||||
Task::perform(
|
Task::perform(
|
||||||
async move {
|
async move {
|
||||||
let items: Result<Vec<Item>, api::JellyfinApiError> = client
|
let items: Result<Vec<Item>, api::JellyfinApiError> = client
|
||||||
@@ -241,6 +267,7 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Message::LoadedItem(id, items) => {
|
Message::LoadedItem(id, items) => {
|
||||||
state.cache.extend(id, items);
|
state.cache.extend(id, items);
|
||||||
state.history.push(state.current);
|
state.history.push(state.current);
|
||||||
@@ -301,10 +328,57 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Message::OpenVideo(url) => {
|
Message::Video(msg) => match msg {
|
||||||
state.video = Video::new(&url).ok().map(Arc::new);
|
VideoMessage::EndOfStream => {
|
||||||
|
state.video = None;
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
VideoMessage::Open(url) => {
|
||||||
|
state.video = Video::new(&url)
|
||||||
|
.inspect_err(|err| {
|
||||||
|
tracing::error!("Failed to play video at {}: {:?}", url, err);
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.map(Arc::new);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
VideoMessage::Pause => {
|
||||||
|
if let Some(video) = state.video.as_mut().and_then(Arc::get_mut) {
|
||||||
|
video.set_paused(true);
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
VideoMessage::Play => {
|
||||||
|
if let Some(video) = state.video.as_mut().and_then(Arc::get_mut) {
|
||||||
|
video.set_paused(false);
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
VideoMessage::Seek(position) => {
|
||||||
|
// if let Some(ref video) = state.video {
|
||||||
|
// // video.seek(position, true);
|
||||||
|
// }
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
VideoMessage::Stop => {
|
||||||
|
state.video = None;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
VideoMessage::Test => {
|
||||||
|
let url = url::Url::parse(
|
||||||
|
// "file:///home/servius/Projects/jello/crates/iced_video_player/.media/test.mp4",
|
||||||
|
"https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
state.video = Video::new(&url)
|
||||||
|
.inspect_err(|err| {
|
||||||
|
dbg!(err);
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.map(Arc::new);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,18 +390,32 @@ fn view(state: &State) -> Element<'_, Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn home(state: &State) -> Element<'_, Message> {
|
fn home(state: &State) -> Element<'_, Message> {
|
||||||
column([header(state), body(state), footer(state)]).into()
|
column([header(state), body(state), footer(state)])
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn body(state: &State) -> Element<'_, Message> {
|
fn player(video: &Video) -> Element<'_, Message> {
|
||||||
if let Some(video) = &state.video {
|
container(
|
||||||
return container(VideoPlayer::new(video))
|
VideoPlayer::new(video)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.content_fit(iced::ContentFit::Contain)
|
||||||
|
.on_end_of_stream(Message::Video(VideoMessage::EndOfStream)),
|
||||||
|
)
|
||||||
|
.style(|_| container::background(iced::Color::BLACK))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.align_x(Alignment::Center)
|
.align_x(Alignment::Center)
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
.into();
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn body(state: &State) -> Element<'_, Message> {
|
||||||
|
if let Some(ref video) = state.video {
|
||||||
|
player(video)
|
||||||
|
} else {
|
||||||
scrollable(
|
scrollable(
|
||||||
container(
|
container(
|
||||||
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
|
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
|
||||||
@@ -342,6 +430,7 @@ fn body(state: &State) -> Element<'_, Message> {
|
|||||||
)
|
)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header(state: &State) -> Element<'_, Message> {
|
fn header(state: &State) -> Element<'_, Message> {
|
||||||
@@ -365,6 +454,9 @@ fn header(state: &State) -> Element<'_, Message> {
|
|||||||
row([
|
row([
|
||||||
button("Refresh").on_press(Message::Refresh).into(),
|
button("Refresh").on_press(Message::Refresh).into(),
|
||||||
button("Settings").on_press(Message::OpenSettings).into(),
|
button("Settings").on_press(Message::OpenSettings).into(),
|
||||||
|
button("TestVideo")
|
||||||
|
.on_press(Message::Video(VideoMessage::Test))
|
||||||
|
.into(),
|
||||||
])
|
])
|
||||||
.spacing(10),
|
.spacing(10),
|
||||||
)
|
)
|
||||||
@@ -539,7 +631,7 @@ fn card(item: &Item) -> Element<'_, Message> {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.as_ref())
|
.map(|s| s.as_ref())
|
||||||
.unwrap_or("Unnamed Item");
|
.unwrap_or("Unnamed Item");
|
||||||
Button::new(
|
MouseArea::new(
|
||||||
container(
|
container(
|
||||||
column([
|
column([
|
||||||
BlurHash::new(
|
BlurHash::new(
|
||||||
@@ -562,8 +654,6 @@ fn card(item: &Item) -> Element<'_, Message> {
|
|||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill),
|
.height(Length::Fill),
|
||||||
)
|
)
|
||||||
// .width(Length::FillPortion(5))
|
|
||||||
// .height(Length::FillPortion(3))
|
|
||||||
.style(container::rounded_box),
|
.style(container::rounded_box),
|
||||||
)
|
)
|
||||||
.on_press(Message::OpenItem(Some(item.id)))
|
.on_press(Message::OpenItem(Some(item.id)))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use iced::{Animation, advanced::image::Handle, widget::image};
|
use iced::{Animation, Function, advanced::image::Handle, widget::image};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -11,6 +11,15 @@ pub struct ImageDownloader {
|
|||||||
Option<Arc<dyn Fn(reqwest::RequestBuilder) -> reqwest::RequestBuilder + Send + Sync>>,
|
Option<Arc<dyn Fn(reqwest::RequestBuilder) -> reqwest::RequestBuilder + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for ImageDownloader {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.debug_struct("ImageDownloader")
|
||||||
|
.field("client", &self.client)
|
||||||
|
.field("request_modifier", &self.request_modifier.is_some())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ImageDownloader {
|
impl ImageDownloader {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -57,45 +66,58 @@ pub enum Preview {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Preview {
|
#[derive(Debug, Clone)]
|
||||||
// pub fn thumbnail(image: Image, blur_hash: BlurHash) -> Self {
|
pub struct ImageCache {
|
||||||
// Preview::Thumbnail {
|
cache: std::collections::HashMap<uuid::Uuid, Preview>,
|
||||||
// thumbnail: image,
|
downloader: ImageDownloader,
|
||||||
// blur_hash,
|
}
|
||||||
// }
|
|
||||||
// }
|
impl Preview {
|
||||||
//
|
pub fn thumbnail(image: Image, blur_hash: BlurHash) -> Self {
|
||||||
// pub fn blur_hash(blur_hash: BlurHash) -> Self {
|
Preview::Thumbnail {
|
||||||
// Preview::BlurHash { blur_hash }
|
thumbnail: image,
|
||||||
// }
|
blur_hash,
|
||||||
//
|
}
|
||||||
// pub fn upgrade(
|
}
|
||||||
// self,
|
|
||||||
// fut: impl core::future::Future<Output = bytes::Bytes> + 'static + Send,
|
pub fn blur_hash(blur_hash: BlurHash) -> Self {
|
||||||
// ) -> iced::Task<PreviewMessage> {
|
Preview::BlurHash { blur_hash }
|
||||||
// // let sip = iced::task::sipper(async move |mut sender| {
|
}
|
||||||
// // let bytes = fut.await;
|
|
||||||
// // let handle = Handle::from_bytes(bytes.clone());
|
// pub fn upgrade(
|
||||||
// // let allocation = image::allocate(handle);
|
// self,
|
||||||
// // let image = Image {
|
// fut: impl core::future::Future<Output = bytes::Bytes> + 'static + Send,
|
||||||
// // bytes,
|
// ) -> iced::Task<PreviewMessage> {
|
||||||
// // handle,
|
// // let sip = iced::task::sipper(async move |mut sender| {
|
||||||
// // allocation,
|
// // let bytes = fut.await;
|
||||||
// // fade_in: Animation::new(false),
|
// // let handle = Handle::from_bytes(bytes.clone());
|
||||||
// // };
|
// // let allocation = image::allocate(handle);
|
||||||
// // let _ = sender.send(image).await;
|
// // let image = Image {
|
||||||
// // });
|
// // bytes,
|
||||||
// // iced::Task::sip(sip, ||)
|
// // handle,
|
||||||
// Task::
|
// // allocation,
|
||||||
// }
|
// // fade_in: Animation::new(false),
|
||||||
// }
|
// // };
|
||||||
//
|
// // let _ = sender.send(image).await;
|
||||||
// enum PreviewMessage {
|
// // });
|
||||||
// BlurHashLoaded(BlurHash),
|
// // iced::Task::sip(sip, ||)
|
||||||
// ThumbnailLoaded(Image),
|
// Task::
|
||||||
// ThumbnailAllocated(image::Allocation),
|
// }
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
|
enum PreviewMessage {
|
||||||
|
BlurHashLoaded(uuid::Uuid, BlurHash),
|
||||||
|
|
||||||
|
ThumbnailDownloaded(uuid::Uuid, bytes::Bytes),
|
||||||
|
// ThumbnailAllocated(
|
||||||
|
// uuid::Uuid,
|
||||||
|
// Result<image::Allocation, iced::advanced::image::Error>,
|
||||||
|
// ),
|
||||||
|
ThumbnailLoaded(uuid::Uuid, Image),
|
||||||
|
|
||||||
|
Failed(uuid::Uuid, String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
bytes: bytes::Bytes,
|
bytes: bytes::Bytes,
|
||||||
@@ -103,3 +125,100 @@ pub struct Image {
|
|||||||
allocation: image::Allocation,
|
allocation: image::Allocation,
|
||||||
fade_in: Animation<bool>,
|
fade_in: Animation<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PreviewSource {
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
pub url: String,
|
||||||
|
pub width: iced::Length,
|
||||||
|
pub height: iced::Length,
|
||||||
|
pub blur_hash: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreviewSource {
|
||||||
|
pub fn new(id: uuid::Uuid, url: String) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
width: iced::Length::Fill,
|
||||||
|
height: iced::Length::Fill,
|
||||||
|
blur_hash: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blur_hash(mut self, blur_hash: String) -> Self {
|
||||||
|
self.blur_hash = Some(blur_hash);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn url(mut self, url: impl Into<String>) -> Self {
|
||||||
|
self.url = url.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn width(mut self, width: iced::Length) -> Self {
|
||||||
|
self.width = width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn height(mut self, height: iced::Length) -> Self {
|
||||||
|
self.height = height;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn upgrade(self) -> Task<PreviewMessage> {
|
||||||
|
// let sip = iced::task::sipper(async move |mut sender| {
|
||||||
|
// let bytes = fut.await;
|
||||||
|
// let handle = Handle::from_bytes(bytes.clone());
|
||||||
|
// let allocation = image::allocate(handle);
|
||||||
|
// let image = Image {
|
||||||
|
// bytes,
|
||||||
|
// handle,
|
||||||
|
// allocation,
|
||||||
|
// fade_in: Animation::new(false),
|
||||||
|
// };
|
||||||
|
// let _ = sender.send(image).await;
|
||||||
|
// });
|
||||||
|
// // iced::Task::sip(sip, ||)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageCache {
|
||||||
|
pub fn upgrade(&self, source: PreviewSource) -> iced::Task<PreviewMessage> {
|
||||||
|
let downloader = self.downloader.clone();
|
||||||
|
let sipper = iced::task::sipper(async move |mut sender| {
|
||||||
|
if let Some(blur_hash_str) = source.blur_hash {
|
||||||
|
let blur_hash = BlurHash::new(&blur_hash_str);
|
||||||
|
let _ = sender.send(blur_hash).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = downloader.download(&source.url).await?;
|
||||||
|
reqwest::Result::<bytes::Bytes>::Ok(bytes)
|
||||||
|
});
|
||||||
|
iced::Task::sip(
|
||||||
|
sipper,
|
||||||
|
move |progress| PreviewMessage::BlurHashLoaded(source.id, progress),
|
||||||
|
move |output: reqwest::Result<bytes::Bytes>| match output {
|
||||||
|
Ok(bytes) => PreviewMessage::ThumbnailDownloaded(source.id, bytes),
|
||||||
|
Err(e) => PreviewMessage::Failed(source.id, e.to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(|message| match message {
|
||||||
|
PreviewMessage::ThumbnailDownloaded(id, bytes) => {
|
||||||
|
let handle = Handle::from_bytes(bytes.clone());
|
||||||
|
let allocation = image::allocate(&handle);
|
||||||
|
allocation.map(move |output| match output {
|
||||||
|
Ok(allocation) => {
|
||||||
|
let image = Image {
|
||||||
|
bytes: bytes.clone(),
|
||||||
|
handle: handle.clone(),
|
||||||
|
allocation,
|
||||||
|
fade_in: Animation::new(false),
|
||||||
|
};
|
||||||
|
PreviewMessage::ThumbnailLoaded(id, image)
|
||||||
|
}
|
||||||
|
Err(e) => PreviewMessage::Failed(id, e.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
other => iced::Task::done(other),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user