feat: Added stuff
Some checks failed
build / checks-matrix (push) Has been cancelled
build / checks-build (push) Has been cancelled
build / codecov (push) Has been cancelled
docs / docs (push) Has been cancelled

This commit is contained in:
uttarayan21
2025-11-22 04:39:42 +05:30
parent b1cfc19b96
commit 61a2ea1733
8 changed files with 398 additions and 117 deletions

View File

@@ -6,10 +6,10 @@ use std::sync::Arc;
mod blur_hash;
use blur_hash::BlurHash;
// mod preview;
// use preview::Preview;
mod preview;
use preview::Preview;
use iced::{Alignment, Element, Length, Task, widget::*};
use iced::{Alignment, Element, Length, Shadow, Task, widget::*};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone)]
@@ -38,6 +38,7 @@ impl ItemCache {
self.insert(parent, item);
});
}
pub fn items_of(&self, parent: impl Into<Option<uuid::Uuid>>) -> Vec<&Item> {
let parent = parent.into();
self.tree.get(&None);
@@ -50,6 +51,10 @@ impl ItemCache {
})
.unwrap_or_default()
}
pub fn get(&self, id: &uuid::Uuid) -> Option<&Item> {
self.items.get(id)
}
}
#[derive(Clone, Debug)]
@@ -75,6 +80,7 @@ impl From<api::jellyfin::BaseItemDto> for Item {
.and_then(|hashes| hashes.get(&tag).cloned())
.map(|s| s.clone().into()),
}),
_type: dto._type,
}
}
}
@@ -85,16 +91,16 @@ pub struct Item {
pub parent_id: Option<uuid::Uuid>,
pub name: Option<SharedString>,
pub thumbnail: Option<Thumbnail>,
pub _type: api::jellyfin::BaseItemKind,
}
#[derive(Debug, Clone, Default)]
pub enum Screen {
#[default]
Home,
Item(Option<uuid::Uuid>),
Search(String),
Settings,
User,
Video,
}
#[derive(Debug, Clone)]
struct State {
@@ -146,13 +152,24 @@ pub enum Message {
SetToken(String),
Back,
Home,
OpenVideo(url::Url),
// Login-related messages
UsernameChanged(String),
PasswordChanged(String),
Login,
LoginSuccess(String),
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> {
@@ -227,19 +244,29 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
}
Message::OpenItem(id) => {
let client = state.jellyfin_client.clone();
Task::perform(
async move {
let items: Result<Vec<Item>, api::JellyfinApiError> = client
.items(id)
.await
.map(|items| items.into_iter().map(Item::from).collect());
(id, items)
},
|(msg, items)| match items {
Err(e) => Message::Error(format!("Failed to load item: {}", e)),
Ok(items) => Message::LoadedItem(msg, items),
},
)
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(
async move {
let items: Result<Vec<Item>, api::JellyfinApiError> = client
.items(id)
.await
.map(|items| items.into_iter().map(Item::from).collect());
(id, items)
},
|(msg, items)| match items {
Err(e) => Message::Error(format!("Failed to load item: {}", e)),
Ok(items) => Message::LoadedItem(msg, items),
},
)
}
}
Message::LoadedItem(id, items) => {
state.cache.extend(id, items);
@@ -301,10 +328,57 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
}
})
}
Message::OpenVideo(url) => {
state.video = Video::new(&url).ok().map(Arc::new);
Task::none()
}
Message::Video(msg) => match msg {
VideoMessage::EndOfStream => {
state.video = 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,32 +390,47 @@ fn view(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 player(video: &Video) -> Element<'_, Message> {
container(
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)
.height(Length::Fill)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.into()
}
fn body(state: &State) -> Element<'_, Message> {
if let Some(video) = &state.video {
return container(VideoPlayer::new(video))
.width(Length::Fill)
.height(Length::Fill)
if let Some(ref video) = state.video {
player(video)
} else {
scrollable(
container(
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
.fluid(400)
.spacing(50),
)
.padding(50)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.into();
}
scrollable(
container(
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
.fluid(400)
.spacing(50),
// .align_y(Alignment::Center)
.height(Length::Fill)
.width(Length::Fill),
)
.padding(50)
.align_x(Alignment::Center)
// .align_y(Alignment::Center)
.height(Length::Fill)
.width(Length::Fill),
)
.height(Length::Fill)
.into()
.into()
}
}
fn header(state: &State) -> Element<'_, Message> {
@@ -365,6 +454,9 @@ fn header(state: &State) -> Element<'_, Message> {
row([
button("Refresh").on_press(Message::Refresh).into(),
button("Settings").on_press(Message::OpenSettings).into(),
button("TestVideo")
.on_press(Message::Video(VideoMessage::Test))
.into(),
])
.spacing(10),
)
@@ -539,7 +631,7 @@ fn card(item: &Item) -> Element<'_, Message> {
.as_ref()
.map(|s| s.as_ref())
.unwrap_or("Unnamed Item");
Button::new(
MouseArea::new(
container(
column([
BlurHash::new(
@@ -562,8 +654,6 @@ fn card(item: &Item) -> Element<'_, Message> {
.width(Length::Fill)
.height(Length::Fill),
)
// .width(Length::FillPortion(5))
// .height(Length::FillPortion(3))
.style(container::rounded_box),
)
.on_press(Message::OpenItem(Some(item.id)))