feat: watch history

This commit is contained in:
Adrian Castro
2024-03-27 12:31:16 +01:00
parent fa2425c183
commit 8c8ad47581
5 changed files with 107 additions and 73 deletions

View File

@@ -1,18 +1,22 @@
import React from "react";
import { View } from "tamagui";
import { ItemListSection, watching } from "~/components/item/ItemListSection";
import { ItemListSection } from "~/components/item/ItemListSection";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { useBookmarkStore } from "~/stores/settings";
import { useBookmarkStore, useWatchHistoryStore } from "~/stores/settings";
export default function HomeScreen() {
const { bookmarks } = useBookmarkStore();
const { watchHistory } = useWatchHistoryStore();
return (
<View style={{ flex: 1 }} flex={1}>
<ScreenLayout>
<ItemListSection title="Bookmarks" items={bookmarks} />
<ItemListSection title="Continue Watching" items={watching} />
<ItemListSection
title="Continue Watching"
items={watchHistory.map((x) => x.item)}
/>
</ScreenLayout>
</View>
);

View File

@@ -5,41 +5,6 @@ import { ScrollView, Text, View } from "tamagui";
import type { ItemData } from "~/components/item/item";
import Item from "~/components/item/item";
export const watching: ItemData[] = [
{
id: "219651",
title: "Welcome to Samdal-ri",
posterUrl:
"https://www.themoviedb.org/t/p/w500/98IvA2i0PsTY8CThoHByCKOEAjz.jpg",
type: "tv",
year: 2023,
},
{
id: "194797",
title: "Doona!",
posterUrl:
"https://www.themoviedb.org/t/p/w500/bQhiOkU3lCu5pwCqPdNVG5GBLlj.jpg",
type: "tv",
year: 2023,
},
{
id: "113268",
title: "The Uncanny Counter",
posterUrl:
"https://www.themoviedb.org/t/p/w500/tKU34QiJUfVipcuhAs5S3TdCpAF.jpg",
type: "tv",
year: 2020,
},
{
id: "203508",
title: "Earth Arcade",
posterUrl:
"https://www.themoviedb.org/t/p/w500/vBJ0uF0WlFcjr9obZZqE6GSsKoL.jpg",
type: "tv",
year: 2022,
},
];
const padding = 20;
const screenWidth = Dimensions.get("window").width;
const itemWidth = screenWidth / 2.3 - padding;

View File

@@ -25,10 +25,17 @@ import { useBrightness } from "~/hooks/player/useBrightness";
import { usePlaybackSpeed } from "~/hooks/player/usePlaybackSpeed";
import { usePlayer } from "~/hooks/player/usePlayer";
import { useVolume } from "~/hooks/player/useVolume";
import { convertMetaToScrapeMedia, getNextEpisode } from "~/lib/meta";
import {
convertMetaToItemData,
convertMetaToScrapeMedia,
getNextEpisode,
} from "~/lib/meta";
import { useAudioTrackStore } from "~/stores/audio";
import { usePlayerStore } from "~/stores/player/store";
import { usePlayerSettingsStore } from "~/stores/settings";
import {
usePlayerSettingsStore,
useWatchHistoryStore,
} from "~/stores/settings";
import { CaptionRenderer } from "./CaptionRenderer";
import { ControlsOverlay } from "./ControlsOverlay";
@@ -68,6 +75,7 @@ export const VideoPlayer = () => {
const setMeta = usePlayerStore((state) => state.setMeta);
const { gestureControls, autoPlay } = usePlayerSettingsStore();
const { updateWatchHistory, removeFromWatchHistory } = useWatchHistoryStore();
const updateResizeMode = (newMode: ResizeMode) => {
setResizeMode(newMode);
@@ -195,6 +203,15 @@ export const VideoPlayer = () => {
}, 60000);
return () => {
if (meta) {
const item = convertMetaToItemData(meta);
const scrapeMedia = convertMetaToScrapeMedia(meta);
updateWatchHistory(
item,
scrapeMedia,
videoRef?.props.positionMillis ?? 0,
);
}
clearTimeout(timeout);
void synchronizePlayback();
};
@@ -202,11 +219,14 @@ export const VideoPlayer = () => {
asset,
dismissFullscreenPlayer,
hasStartedPlaying,
meta,
router,
selectedAudioTrack,
setVideoSrc,
stream,
synchronizePlayback,
updateWatchHistory,
videoRef?.props.positionMillis,
]);
const onVideoLoadStart = () => {
@@ -218,11 +238,24 @@ export const VideoPlayer = () => {
setHasStartedPlaying(true);
if (videoRef) {
void videoRef.setRateAsync(currentSpeed, true);
if (meta) {
const item = convertMetaToItemData(meta);
const scrapeMedia = convertMetaToScrapeMedia(meta);
updateWatchHistory(
item,
scrapeMedia,
videoRef.props.positionMillis ?? 0,
);
}
}
};
const onPlaybackStatusUpdate = async (status: AVPlaybackStatus) => {
setStatus(status);
if (meta && status.isLoaded && status.didJustFinish) {
const item = convertMetaToItemData(meta);
removeFromWatchHistory(item);
}
if (
status.isLoaded &&
status.didJustFinish &&

View File

@@ -1,6 +1,7 @@
import type { ScrapeMedia } from "@movie-web/provider-utils";
import { fetchMediaDetails, fetchSeasonDetails } from "@movie-web/tmdb";
import type { ItemData } from "~/components/item/item";
import type { PlayerMeta } from "~/stores/player/slices/video";
export const convertMetaToScrapeMedia = (meta: PlayerMeta): ScrapeMedia => {
@@ -27,6 +28,28 @@ export const convertMetaToScrapeMedia = (meta: PlayerMeta): ScrapeMedia => {
throw new Error("Invalid meta type");
};
export const convertMetaToItemData = (meta: PlayerMeta): ItemData => {
if (meta.type === "movie") {
return {
id: meta.tmdbId,
title: meta.title,
year: meta.releaseYear,
type: meta.type,
posterUrl: meta.poster ?? "",
};
}
if (meta.type === "show") {
return {
id: meta.tmdbId,
title: meta.title,
year: meta.releaseYear,
type: "tv",
posterUrl: meta.poster ?? "",
};
}
throw new Error("Invalid media type");
};
export const getNextEpisode = async (
meta: PlayerMeta,
): Promise<PlayerMeta | undefined> => {

View File

@@ -4,10 +4,11 @@ import { MMKV } from "react-native-mmkv";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import type { ScrapeMedia } from "@movie-web/provider-utils";
import type { ItemData } from "~/components/item/item";
import type { DownloadItem } from "~/hooks/DownloadManagerContext";
import type { ThemeStoreOption } from "~/stores/theme";
import type { ScrapeMedia } from "@movie-web/provider-utils";
const storage = new MMKV();
@@ -141,7 +142,11 @@ interface WatchHistoryItem {
interface WatchHistoryStoreState {
watchHistory: WatchHistoryItem[];
setWatchHistory: (watchHistory: WatchHistoryItem[]) => void;
addToWatchHistory: (item: ItemData, media: ScrapeMedia) => void;
updateWatchHistory: (
item: ItemData,
media: ScrapeMedia,
positionMillis: number,
) => void;
removeFromWatchHistory: (item: ItemData) => void;
}
@@ -154,7 +159,11 @@ export const useWatchHistoryStore = create<
watchHistory: [],
setWatchHistory: (watchHistory: WatchHistoryItem[]) =>
set({ watchHistory }),
addToWatchHistory: (item: ItemData, media: ScrapeMedia) =>
updateWatchHistory: (
item: ItemData,
media: ScrapeMedia,
positionMillis: number,
) =>
set((state) => ({
watchHistory: [
...state.watchHistory.filter(
@@ -163,7 +172,7 @@ export const useWatchHistoryStore = create<
{
item,
media,
positionMillis: 0,
positionMillis,
},
],
})),
@@ -172,8 +181,8 @@ export const useWatchHistoryStore = create<
watchHistory: state.watchHistory.filter(
(historyItem) => historyItem.item.id !== item.id,
),
})),
}),
)}),
{
name: "watch-history",
storage: createJSONStorage(() => zustandStorage),