diff --git a/docs/design/playing_media.md b/docs/design/playing_media.md
new file mode 100644
index 0000000..8c09652
--- /dev/null
+++ b/docs/design/playing_media.md
@@ -0,0 +1,85 @@
+# Playing media
+
+This document will describe the application UX flow.
+
+## Types of media & Expected behavior
+
+- movie
+ - actual movie -> show the video player and start/continue playing the movie
+ - collection -> Show a picker for which movie to play (also allow play-all)
+- audio/music
+ - artist -> Show top songs, albums, and a play-all button
+ - album -> Show songs in album and a play-all button
+ - song -> Show audio player and start playing the song
+- show/tv
+ - series -> Show a picker for the seasons and include a play button that will start/continue the first non-completed episode
+ - season -> Show a picker for the episodes and include a play button that will start/continue the first non-completed episode
+ - episode -> Show the video player and start/continue the episode (include the skip to previous&next episode buttons)
+- playlist -> play the selected entry according to the above listed definitions
+
+## UX flow
+
+```txt
+WHEN type of media IS {
+ audio -> {
+ WHEN entry does not exist in lidarr {
+ add entry to lidarr
+ }
+
+ WHEN queue record status IS {
+ paused -> ???
+
+ downloading -> {
+ display estimated time remaining
+ wait for download to complete
+ }
+
+ _ -> ???
+ }
+
+ play audio
+ }
+
+ show/tv -> {
+ WHEN entry is not an episode {
+ redirect to earliest non-completed episode
+ }
+
+ WHEN entry does not exist in sonarr {
+ add entry to sonarr
+ }
+
+ WHEN queue record status IS {
+ paused -> ???
+
+ downloading -> {
+ display estimated time remaining
+ wait for download to complete
+ }
+
+ _ -> ???
+ }
+
+ play episode
+ }
+
+ movie -> {
+ WHEN entry does not exist in radarr {
+ add entry to radarr
+ }
+
+ WHEN queue record status IS {
+ paused -> ???
+
+ downloading -> {
+ display estimated time remaining
+ wait for download to complete
+ }
+
+ _ -> ???
+ }
+
+ play movie
+ }
+}
+```
diff --git a/src/components/hero/hero.tsx b/src/components/hero/hero.tsx
index ec6a51b..f68307d 100644
--- a/src/components/hero/hero.tsx
+++ b/src/components/hero/hero.tsx
@@ -25,7 +25,7 @@ const Page: Component<{ entry: Entry }> = (props) => {
>
{props.entry.title}
-
+
Continue
diff --git a/src/features/content/apis/jellyfin.ts b/src/features/content/apis/jellyfin.ts
index b20a469..74a031f 100644
--- a/src/features/content/apis/jellyfin.ts
+++ b/src/features/content/apis/jellyfin.ts
@@ -213,15 +213,13 @@ export const getItemStream = query(
const userData = await getItemUserData(userId, itemId);
- console.log(userData);
-
const { response } = await getClient().GET("/Videos/{itemId}/stream", {
params: {
path: {
itemId,
},
query: {
- // startTimeTicks: userData?.playbackPositionTicks,
+ startTimeTicks: userData?.playbackPositionTicks,
},
},
parseAs: 'stream',
diff --git a/src/features/content/apis/radarr.ts b/src/features/content/apis/radarr.ts
index aebcca4..dddb2da 100644
--- a/src/features/content/apis/radarr.ts
+++ b/src/features/content/apis/radarr.ts
@@ -26,10 +26,52 @@ export const getClient = () => {
});
};
-export const get = query(async () => {
+export const TEST = query(async (id: number) => {
"use server";
- const { data, error } = await getClient().GET('/api/v3/movie');
+ const { data, error } = await getClient().GET('/api/v3/queue/details', {
+ params: {
+ query: {
+ movieId: id,
+ // includeMovie: true,
+ },
+ },
+ });
+
+ const item = data?.[0];
+
+ if (!item?.status) {
+ return;
+ }
+
+ switch(item.status) {
+ case 'paused': {
+ console.log('not sure what to do now. there\'s a reason it is paused after all. just report it to the client perhaps?');
+ return;
+ }
+
+ case 'downloading': {
+ console.log(`download is esitmated to complete at ${item.estimatedCompletionTime}`);
+
+ return;
+ }
+
+ default: {
+ return;
+ }
+ }
+}, 'radarr.TEST');
+
+export const get = query(async (id: number) => {
+ "use server";
+
+ const { data, error } = await getClient().GET('/api/v3/movie/{id}', {
+ params: {
+ path: {
+ id,
+ },
+ },
+ });
return data;
}, 'radarr.get');
diff --git a/src/features/content/service.ts b/src/features/content/service.ts
index 97b059b..bd8e929 100644
--- a/src/features/content/service.ts
+++ b/src/features/content/service.ts
@@ -10,7 +10,7 @@ import {
searchMulti,
} from "./apis/tmdb";
import { listIds as listSerieIds, addSeries } from "./apis/sonarr";
-import { listIds as listMovieIds, addMovie } from "./apis/radarr";
+import { listIds as listMovieIds, addMovie, TEST } from "./apis/radarr";
import { merge } from "~/utilities";
const jellyfinUserId = "a9c51af84bf54578a99ab4dd0ebf0763";
@@ -34,8 +34,14 @@ export const getStream = query(async (id: string, range: string) => {
return getItemStream(jellyfinUserId, ids.jellyfin, range);
}
- if (ids?.[manager]) {
- console.log('id is known, but jellyfin does not (yet) have the file');
+ if (ids?.radarr) {
+ console.log(`radarr has the entry '${ids.radarr}', but jellyfin does not (yet) have the file`);
+ console.log(await TEST(ids.radarr))
+ return;
+ }
+
+ if (ids?.sonarr) {
+ console.log('sonarr has the entry, but jellyfin does not (yet) have the file');
return;
}
diff --git a/src/features/overview/list-item.tsx b/src/features/overview/list-item.tsx
index e48a7ed..d0beff8 100644
--- a/src/features/overview/list-item.tsx
+++ b/src/features/overview/list-item.tsx
@@ -8,13 +8,12 @@ export const ListItem: Component<{ entry: Entry }> = (props) => {
return (
-
-
+
{props.entry.title}
- Watch now
+ Watch now
);
diff --git a/src/features/player/context.tsx b/src/features/player/context.tsx
index d5e0258..deaee6e 100644
--- a/src/features/player/context.tsx
+++ b/src/features/player/context.tsx
@@ -3,7 +3,7 @@ import {
ContextProviderProps,
createContextProvider,
} from "@solid-primitives/context";
-import { Accessor, createEffect, onMount, Setter } from "solid-js";
+import { Accessor, createEffect, on, onMount, Setter } from "solid-js";
import { createStore } from "solid-js/store";
import { createEventListenerMap } from "@solid-primitives/event-listener";
import { createFullscreen } from "@solid-primitives/fullscreen";
@@ -124,13 +124,20 @@ export const [VideoProvider, useVideo] = createContextProvider<
},
};
+ console.log(props.root, props.video);
+
if (isServer || video === undefined) {
return api;
}
- createEffect(() => {
- video[store.state === "playing" ? "play" : "pause"]();
- });
+ createEffect(
+ on(
+ () => store.state,
+ (state) => {
+ video[state === "playing" ? "play" : "pause"]();
+ }
+ )
+ );
createEffect(() => {
video.muted = store.volume.muted;
@@ -174,9 +181,11 @@ export const [VideoProvider, useVideo] = createContextProvider<
);
},
canplay() {
+ console.log("can play!");
// setStore("loading", false);
},
canplaythrough() {
+ console.log("can play through!");
setStore("loading", false);
},
waiting() {
diff --git a/src/features/player/player.tsx b/src/features/player/player.tsx
index 1a44289..aab9adf 100644
--- a/src/features/player/player.tsx
+++ b/src/features/player/player.tsx
@@ -33,15 +33,15 @@ const metadata = query(async (id: string) => {
// 3. create sprite from images
// 4. remove thumbs
- const path = `${import.meta.dirname}/SampleVideo_1280x720_10mb`;
+ // const path = `${import.meta.dirname}/SampleVideo_1280x720_10mb`;
- return json({
- captions: await Bun.file(`${path}.captions.vtt`).bytes(),
- thumbnails: {
- track: await Bun.file(`${path}.thumbnails.vtt`).text(),
- image: await Bun.file(`${import.meta.dirname}/overview.jpg`).bytes(),
- },
- });
+ // return json({
+ // captions: await Bun.file(`${path}.captions.vtt`).bytes(),
+ // thumbnails: {
+ // track: await Bun.file(`${path}.thumbnails.vtt`).text(),
+ // image: await Bun.file(`${import.meta.dirname}/overview.jpg`).bytes(),
+ // },
+ // });
}, "player.metadata");
interface PlayerProps {
@@ -56,32 +56,30 @@ export const Player: Component = (props) => {
undefined as unknown as HTMLVideoElement
);
- const data = createAsync(() => metadata(props.entry.id), {
- deferStream: true,
- initialValue: {} as any,
- });
- const captionUrl = createMemo(() => {
- const { captions } = data();
+ // const data = createAsync(() => metadata(props.entry.id), {
+ // deferStream: true,
+ // initialValue: {} as any,
+ // });
+ // const captionUrl = createMemo(() => {
+ // const { captions } = data();
- return captions !== undefined
- ? URL.createObjectURL(new Blob([captions], { type: "text/vtt" }))
- : "";
- });
- const thumbnails = createMemo(() => {
- const { thumbnails } = data();
+ // return captions !== undefined
+ // ? URL.createObjectURL(new Blob([captions], { type: "text/vtt" }))
+ // : "";
+ // });
+ // const thumbnails = createMemo(() => {
+ // const { thumbnails } = data();
- return thumbnails !== undefined
- ? URL.createObjectURL(new Blob([thumbnails.track], { type: "text/vtt" }))
- : "";
- });
+ // return thumbnails !== undefined
+ // ? URL.createObjectURL(new Blob([thumbnails.track], { type: "text/vtt" }))
+ // : "";
+ // });
- createEffect(on(thumbnails, (thumbnails) => {}));
+ // createEffect(on(thumbnails, (thumbnails) => {}));
return (
<>
- {/* {props.entry.title} */}
-
= (props) => {
lang="en"
autoplay
>
- = (props) => {
src={captionUrl()}
/>
- {/*
-
-
-
- */}
+
+
+
+
+ */}
diff --git a/src/routes/(shell)/watch/[slug].tsx b/src/routes/(shell)/play/[slug].tsx
similarity index 77%
rename from src/routes/(shell)/watch/[slug].tsx
rename to src/routes/(shell)/play/[slug].tsx
index a0de601..1900a29 100644
--- a/src/routes/(shell)/watch/[slug].tsx
+++ b/src/routes/(shell)/play/[slug].tsx
@@ -7,7 +7,7 @@ import {
RouteDefinition,
useParams,
} from "@solidjs/router";
-import { createEffect, createMemo, Show } from "solid-js";
+import { Show } from "solid-js";
import { createSlug, getEntryFromSlug } from "~/features/content";
import { Player } from "~/features/player";
import { Title } from "@solidjs/meta";
@@ -27,8 +27,8 @@ const healUrl = query(async (slug: string) => {
}
// Not entirely sure a permanent redirect is what we want in this case
- throw redirect(`/watch/${actualSlug}`, { status: 308 });
-}, "watch.heal");
+ throw redirect(`/play/${actualSlug}`, { status: 308 });
+}, "play.heal");
interface ItemParams extends Params {
slug: string;
@@ -51,19 +51,16 @@ export const route = {
export default function Item() {
const { slug } = useParams();
const entry = createAsync(() => getEntryFromSlug(slug));
- const title = createMemo(() => entry()?.title);
-
- console.log(entry());
-
- createEffect(() => {
- console.log(entry());
- });
return (
-
{title()}
+
{entry()?.title}
- {(entry) => }
+ {(entry) => (
+ <>
+
+ >
+ )}
);
diff --git a/src/routes/(shell)/watch/slug.module.css b/src/routes/(shell)/play/slug.module.css
similarity index 100%
rename from src/routes/(shell)/watch/slug.module.css
rename to src/routes/(shell)/play/slug.module.css