From 8c8ad47581ebee10f6bc6c8e6a1618687451e692 Mon Sep 17 00:00:00 2001
From: Adrian Castro <22133246+castdrian@users.noreply.github.com>
Date: Wed, 27 Mar 2024 12:31:16 +0100
Subject: [PATCH] feat: watch history
---
apps/expo/src/app/(tabs)/index.tsx | 10 ++-
.../src/components/item/ItemListSection.tsx | 35 ---------
.../src/components/player/VideoPlayer.tsx | 37 ++++++++-
apps/expo/src/lib/meta.ts | 23 ++++++
apps/expo/src/stores/settings/index.ts | 75 +++++++++++--------
5 files changed, 107 insertions(+), 73 deletions(-)
diff --git a/apps/expo/src/app/(tabs)/index.tsx b/apps/expo/src/app/(tabs)/index.tsx
index afa509e..7e015db 100644
--- a/apps/expo/src/app/(tabs)/index.tsx
+++ b/apps/expo/src/app/(tabs)/index.tsx
@@ -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 (
-
+ x.item)}
+ />
);
diff --git a/apps/expo/src/components/item/ItemListSection.tsx b/apps/expo/src/components/item/ItemListSection.tsx
index f447913..35f48c7 100644
--- a/apps/expo/src/components/item/ItemListSection.tsx
+++ b/apps/expo/src/components/item/ItemListSection.tsx
@@ -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;
diff --git a/apps/expo/src/components/player/VideoPlayer.tsx b/apps/expo/src/components/player/VideoPlayer.tsx
index 3db8f00..0a093ae 100644
--- a/apps/expo/src/components/player/VideoPlayer.tsx
+++ b/apps/expo/src/components/player/VideoPlayer.tsx
@@ -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 &&
diff --git a/apps/expo/src/lib/meta.ts b/apps/expo/src/lib/meta.ts
index da1939a..7d5b0a9 100644
--- a/apps/expo/src/lib/meta.ts
+++ b/apps/expo/src/lib/meta.ts
@@ -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 => {
diff --git a/apps/expo/src/stores/settings/index.ts b/apps/expo/src/stores/settings/index.ts
index 6ce201d..6056a07 100644
--- a/apps/expo/src/stores/settings/index.ts
+++ b/apps/expo/src/stores/settings/index.ts
@@ -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();
@@ -111,7 +112,7 @@ export const useBookmarkStore = create<
persist(
(set, get) => ({
bookmarks: [],
- setBookmarks: (bookmarks: ItemData[]) => set({ bookmarks }),
+ setBookmarks: (bookmarks: ItemData[]) => set({ bookmarks }),
addBookmark: (item: ItemData) =>
set((state) => ({
bookmarks: [...state.bookmarks, item],
@@ -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;
}
@@ -149,34 +154,38 @@ export const useWatchHistoryStore = create<
WatchHistoryStoreState,
[["zustand/persist", WatchHistoryStoreState]]
>(
- persist(
- (set) => ({
- watchHistory: [],
- setWatchHistory: (watchHistory: WatchHistoryItem[]) =>
- set({ watchHistory }),
- addToWatchHistory: (item: ItemData, media: ScrapeMedia) =>
- set((state) => ({
- watchHistory: [
- ...state.watchHistory.filter(
- (historyItem) => historyItem.item.id !== item.id,
- ),
- {
- item,
- media,
- positionMillis: 0,
- },
- ],
- })),
- removeFromWatchHistory: (item: ItemData) =>
- set((state) => ({
- watchHistory: state.watchHistory.filter(
- (historyItem) => historyItem.item.id !== item.id,
- ),
- }),
- )}),
- {
- name: "watch-history",
- storage: createJSONStorage(() => zustandStorage),
- },
+ persist(
+ (set) => ({
+ watchHistory: [],
+ setWatchHistory: (watchHistory: WatchHistoryItem[]) =>
+ set({ watchHistory }),
+ updateWatchHistory: (
+ item: ItemData,
+ media: ScrapeMedia,
+ positionMillis: number,
+ ) =>
+ set((state) => ({
+ watchHistory: [
+ ...state.watchHistory.filter(
+ (historyItem) => historyItem.item.id !== item.id,
+ ),
+ {
+ item,
+ media,
+ positionMillis,
+ },
+ ],
+ })),
+ removeFromWatchHistory: (item: ItemData) =>
+ set((state) => ({
+ watchHistory: state.watchHistory.filter(
+ (historyItem) => historyItem.item.id !== item.id,
+ ),
+ })),
+ }),
+ {
+ name: "watch-history",
+ storage: createJSONStorage(() => zustandStorage),
+ },
),
-);
\ No newline at end of file
+);