From 90c6c2093b7111dad3216616ed6c74cf45797fb7 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 19 Feb 2024 22:12:08 +0100 Subject: [PATCH] improve loading, caption renderer, season/episode selector, source selector --- apps/expo/package.json | 6 +- apps/expo/src/app/(tabs)/search/_layout.tsx | 21 +- apps/expo/src/app/_layout.tsx | 47 +-- apps/expo/src/app/videoPlayer/index.tsx | 310 +--------------- apps/expo/src/app/videoPlayer/loading.tsx | 332 +++++++++--------- apps/expo/src/components/item/item.tsx | 5 +- .../expo/src/components/player/BackButton.tsx | 6 +- .../src/components/player/BottomControls.tsx | 46 ++- .../src/components/player/CaptionRenderer.tsx | 19 +- .../components/player/CaptionsSelector.tsx | 31 +- .../src/components/player/ControlsOverlay.tsx | 11 +- apps/expo/src/components/player/Header.tsx | 28 +- .../src/components/player/ProgressBar.tsx | 2 +- .../src/components/player/ScraperProcess.tsx | 115 ++++++ .../player/SeasonEpisodeSelector.tsx | 205 ++++++++--- .../src/components/player/SourceSelector.tsx | 207 +++++++++-- .../src/components/player/VideoPlayer.tsx | 222 ++++++++++++ apps/expo/src/components/ui/Divider.tsx | 5 + apps/expo/src/hooks/player/usePlayer.ts | 21 ++ apps/expo/src/hooks/player/useSourceScrape.ts | 89 +++++ .../src/stores/player/slices/interface.ts | 71 ++-- apps/expo/src/stores/player/slices/video.ts | 64 ++++ package.json | 6 +- packages/provider-utils/src/index.ts | 6 +- packages/provider-utils/src/util.ts | 8 +- packages/provider-utils/src/video.ts | 250 ++++++++----- packages/tmdb/src/details.ts | 46 ++- pnpm-lock.yaml | 67 ++-- tooling/eslint/react.js | 1 + tooling/tailwind/colors.ts | 23 ++ tooling/tailwind/native.ts | 7 +- 31 files changed, 1453 insertions(+), 824 deletions(-) create mode 100644 apps/expo/src/components/player/ScraperProcess.tsx create mode 100644 apps/expo/src/components/player/VideoPlayer.tsx create mode 100644 apps/expo/src/components/ui/Divider.tsx create mode 100644 apps/expo/src/hooks/player/usePlayer.ts create mode 100644 apps/expo/src/hooks/player/useSourceScrape.ts diff --git a/apps/expo/package.json b/apps/expo/package.json index 53e39ba..75a1f5d 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -23,6 +23,7 @@ "@movie-web/tmdb": "*", "@react-native-anywhere/polyfill-base64": "0.0.1-alpha.0", "@react-navigation/native": "^6.1.9", + "@tanstack/react-query": "^5.22.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo": "~50.0.5", @@ -38,12 +39,12 @@ "expo-status-bar": "~1.11.1", "expo-web-browser": "^12.8.2", "immer": "^10.0.3", - "nativewind": "~4.0.23", + "nativewind": "^4.0.35", "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.73.2", "react-native-context-menu-view": "^1.14.1", - "react-native-css-interop": "~0.0.22", + "react-native-css-interop": "^0.0.35", "react-native-gesture-handler": "~2.14.1", "react-native-modal": "^13.0.1", "react-native-quick-base64": "^2.0.8", @@ -65,6 +66,7 @@ "@movie-web/prettier-config": "workspace:^0.1.0", "@movie-web/tailwind-config": "workspace:^0.1.0", "@movie-web/tsconfig": "workspace:^0.1.0", + "@tanstack/eslint-plugin-query": "^5.20.1", "@types/babel__core": "^7.20.5", "@types/react": "^18.2.48", "babel-plugin-module-resolver": "^5.0.0", diff --git a/apps/expo/src/app/(tabs)/search/_layout.tsx b/apps/expo/src/app/(tabs)/search/_layout.tsx index c716922..a0d07ca 100644 --- a/apps/expo/src/app/(tabs)/search/_layout.tsx +++ b/apps/expo/src/app/(tabs)/search/_layout.tsx @@ -6,6 +6,7 @@ import Animated, { useSharedValue, withTiming, } from "react-native-reanimated"; +import { useQuery } from "@tanstack/react-query"; import { getMediaPoster, searchTitle } from "@movie-web/tmdb"; @@ -16,18 +17,14 @@ import { Text } from "~/components/ui/Text"; import Searchbar from "./Searchbar"; export default function SearchScreen() { - const [searchResults, setSearchResults] = useState([]); + const [query, setQuery] = useState(""); const translateY = useSharedValue(0); const fadeAnim = useSharedValue(1); - const handleSearchChange = async (query: string) => { - if (query.length > 0) { - const results = await fetchSearchResults(query).catch(() => []); - setSearchResults(results); - } else { - setSearchResults([]); - } - }; + const { data } = useQuery({ + queryKey: ["searchResults", query], + queryFn: () => fetchSearchResults(query), + }); useEffect(() => { const keyboardWillShowListener = Keyboard.addListener( @@ -83,7 +80,7 @@ export default function SearchScreen() { 0} + scrollEnabled={data && data.length > 0} keyboardDismissMode="on-drag" keyboardShouldPersistTaps="handled" > @@ -95,7 +92,7 @@ export default function SearchScreen() { } > - {searchResults.map((item, index) => ( + {data?.map((item, index) => ( @@ -109,7 +106,7 @@ export default function SearchScreen() { animatedStyle, ]} > - + ); diff --git a/apps/expo/src/app/_layout.tsx b/apps/expo/src/app/_layout.tsx index aad4fb0..f146f34 100644 --- a/apps/expo/src/app/_layout.tsx +++ b/apps/expo/src/app/_layout.tsx @@ -10,6 +10,7 @@ import { DefaultTheme, ThemeProvider, } from "@react-navigation/native"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import Colors from "@movie-web/tailwind-config/colors"; @@ -30,6 +31,8 @@ SplashScreen.preventAutoHideAsync().catch(() => { /* reloading the app might trigger this, so it's safe to ignore */ }); +const queryClient = new QueryClient(); + export default function RootLayout() { const [loaded, error] = useFonts({ OpenSansRegular: require("../../assets/fonts/OpenSans-Regular.ttf"), @@ -69,32 +72,34 @@ function RootLayoutNav() { const colorScheme = useColorScheme(); return ( - - - + + - - + > + + + + ); } diff --git a/apps/expo/src/app/videoPlayer/index.tsx b/apps/expo/src/app/videoPlayer/index.tsx index c2e5dad..8489a9e 100644 --- a/apps/expo/src/app/videoPlayer/index.tsx +++ b/apps/expo/src/app/videoPlayer/index.tsx @@ -1,304 +1,26 @@ -import type { AVPlaybackSource } from "expo-av"; -import React, { useEffect, useState } from "react"; -import { - ActivityIndicator, - Dimensions, - Platform, - StyleSheet, - View, -} from "react-native"; -import { Gesture, GestureDetector } from "react-native-gesture-handler"; -import { runOnJS, useSharedValue } from "react-native-reanimated"; -import { ResizeMode, Video } from "expo-av"; -import * as NavigationBar from "expo-navigation-bar"; import { useLocalSearchParams, useRouter } from "expo-router"; -import * as StatusBar from "expo-status-bar"; - -import type { HLSTracks, ScrapeMedia, Stream } from "@movie-web/provider-utils"; -import { - extractTracksFromHLS, - findHighestQuality, -} from "@movie-web/provider-utils"; -import { fetchSeasonDetails } from "@movie-web/tmdb"; import type { ItemData } from "~/components/item/item"; -import type { HeaderData } from "~/components/player/Header"; -import { CaptionRenderer } from "~/components/player/CaptionRenderer"; -import { ControlsOverlay } from "~/components/player/ControlsOverlay"; -import { Text } from "~/components/ui/Text"; -import { useBrightness } from "~/hooks/player/useBrightness"; -import { useVolume } from "~/hooks/player/useVolume"; +import { ScraperProcess } from "~/components/player/ScraperProcess"; +import { VideoPlayer } from "~/components/player/VideoPlayer"; +import { usePlayer } from "~/hooks/player/usePlayer"; import { usePlayerStore } from "~/stores/player/store"; export default function VideoPlayerWrapper() { + const playerStatus = usePlayerStore((state) => state.interface.playerStatus); + const { presentFullscreenPlayer } = usePlayer(); + + const router = useRouter(); const params = useLocalSearchParams(); const data = params.data - ? (JSON.parse(params.data as string) as VideoPlayerData) + ? (JSON.parse(params.data as string) as ItemData) : null; - return ; + + if (!data) return router.back(); + + void presentFullscreenPlayer(); + + if (playerStatus === "scraping") return ; + + if (playerStatus === "ready") return ; } - -export interface VideoPlayerData { - sourceId?: string; - item: ItemData; - stream: Stream; - media: ScrapeMedia; -} - -interface VideoPlayerProps { - data: VideoPlayerData | null; -} - -const VideoPlayer: React.FC = ({ data }) => { - const { - brightness, - debouncedBrightness, - showBrightnessOverlay, - setShowBrightnessOverlay, - handleBrightnessChange, - } = useBrightness(); - const { - currentVolume, - debouncedVolume, - showVolumeOverlay, - setShowVolumeOverlay, - handleVolumeChange, - } = useVolume(); - const [videoSrc, setVideoSrc] = useState(); - const [isLoading, setIsLoading] = useState(true); - const [headerData, setHeaderData] = useState(); - const [resizeMode, setResizeMode] = useState(ResizeMode.CONTAIN); - const [shouldPlay, setShouldPlay] = useState(true); - const router = useRouter(); - const scale = useSharedValue(1); - - const isIdle = usePlayerStore((state) => state.interface.isIdle); - const setStream = usePlayerStore((state) => state.setStream); - const setVideoRef = usePlayerStore((state) => state.setVideoRef); - const setStatus = usePlayerStore((state) => state.setStatus); - const setIsIdle = usePlayerStore((state) => state.setIsIdle); - const _setSourceId = usePlayerStore((state) => state.setSourceId); - const setData = usePlayerStore((state) => state.setData); - const setSeasonData = usePlayerStore((state) => state.setSeasonData); - const presentFullscreenPlayer = usePlayerStore( - (state) => state.presentFullscreenPlayer, - ); - const dismissFullscreenPlayer = usePlayerStore( - (state) => state.dismissFullscreenPlayer, - ); - - const updateResizeMode = (newMode: ResizeMode) => { - setResizeMode(newMode); - }; - - const pinchGesture = Gesture.Pinch().onUpdate((e) => { - scale.value = e.scale; - if (scale.value > 1 && resizeMode !== ResizeMode.COVER) { - runOnJS(updateResizeMode)(ResizeMode.COVER); - } else if (scale.value <= 1 && resizeMode !== ResizeMode.CONTAIN) { - runOnJS(updateResizeMode)(ResizeMode.CONTAIN); - } - }); - - const togglePlayback = () => { - setShouldPlay(!shouldPlay); - }; - - const doubleTapGesture = Gesture.Tap() - .numberOfTaps(2) - .onEnd(() => { - runOnJS(togglePlayback)(); - }); - - const screenHalfWidth = Dimensions.get("window").width / 2; - - const panGesture = Gesture.Pan() - .onUpdate((event) => { - const divisor = 5000; - const panIsInHeaderOrFooter = event.y < 100 || event.y > 400; - if (panIsInHeaderOrFooter) return; - - const directionMultiplier = event.velocityY < 0 ? 1 : -1; - - if (event.x > screenHalfWidth) { - const change = - directionMultiplier * Math.abs(event.velocityY / divisor); - const newVolume = Math.max( - 0, - Math.min(1, currentVolume.value + change), - ); - runOnJS(handleVolumeChange)(newVolume); - } else { - const change = - directionMultiplier * Math.abs(event.velocityY / divisor); - const newBrightness = Math.max( - 0, - Math.min(1, brightness.value + change), - ); - brightness.value = newBrightness; - runOnJS(handleBrightnessChange)(newBrightness); - } - }) - .onEnd(() => { - runOnJS(setShowVolumeOverlay)(false); - runOnJS(setShowBrightnessOverlay)(false); - }); - - const composedGesture = Gesture.Race( - panGesture, - pinchGesture, - doubleTapGesture, - ); - - useEffect(() => { - const initializePlayer = async () => { - if (!data) { - await dismissFullscreenPlayer(); - return router.push("/(tabs)"); - } - - StatusBar.setStatusBarHidden(true); - - if (Platform.OS === "android") { - await NavigationBar.setVisibilityAsync("hidden"); - } - setData(data.item); - setIsLoading(true); - - const { item, stream, media } = data; - - if (media.type === "show") { - const seasonData = await fetchSeasonDetails( - media.tmdbId, - media.season.number, - ); - if (seasonData) { - setSeasonData(seasonData); - } - } - - setStream(stream); - - setHeaderData({ - title: item.title, - year: item.year, - season: media.type === "show" ? media.season.number : undefined, - episode: media.type === "show" ? media.episode.number : undefined, - }); - - let highestQuality; - let url; - let _tracks: HLSTracks | null; - - switch (stream.type) { - case "file": - highestQuality = findHighestQuality(stream); - url = highestQuality ? stream.qualities[highestQuality]?.url : null; - return url ?? null; - case "hls": - url = stream.playlist; - _tracks = await extractTracksFromHLS(url, { - ...stream.preferredHeaders, - ...stream.headers, - }); - } - - setVideoSrc({ - uri: url, - headers: { - ...stream.preferredHeaders, - ...stream.headers, - }, - }); - - setIsLoading(false); - }; - - setIsLoading(true); - void presentFullscreenPlayer(); - void initializePlayer(); - - return () => { - void dismissFullscreenPlayer(); - StatusBar.setStatusBarHidden(false); - if (Platform.OS === "android") { - void NavigationBar.setVisibilityAsync("visible"); - } - }; - }, [ - data, - dismissFullscreenPlayer, - presentFullscreenPlayer, - router, - setData, - setSeasonData, - setStream, - ]); - - const onVideoLoadStart = () => { - setIsLoading(true); - }; - - const onReadyForDisplay = () => { - setIsLoading(false); - }; - - return ( - - - - - ); -}; - -// interface Caption { -// type: "srt" | "vtt"; -// id: string; -// url: string; -// hasCorsRestrictions: boolean; -// language: string; -// } - -const styles = StyleSheet.create({ - video: { - position: "absolute", - top: 0, - bottom: 0, - left: 0, - right: 0, - }, -}); diff --git a/apps/expo/src/app/videoPlayer/loading.tsx b/apps/expo/src/app/videoPlayer/loading.tsx index 734ac5f..ced7ce1 100644 --- a/apps/expo/src/app/videoPlayer/loading.tsx +++ b/apps/expo/src/app/videoPlayer/loading.tsx @@ -1,189 +1,185 @@ -import { useEffect, useState } from "react"; -import { Text } from "react-native"; -import { useLocalSearchParams, useRouter } from "expo-router"; +// import { useEffect, useState } from "react"; +// import { Text } from "react-native"; +// import { useLocalSearchParams, useRouter } from "expo-router"; -import type { RunnerEvent } from "@movie-web/provider-utils"; -import { - getVideoStream, - transformSearchResultToScrapeMedia, -} from "@movie-web/provider-utils"; -import { fetchMediaDetails } from "@movie-web/tmdb"; +// import type { RunnerEvent } from "@movie-web/provider-utils"; +// import { +// getVideoStream, +// transformSearchResultToScrapeMedia, +// } from "@movie-web/provider-utils"; +// import { fetchMediaDetails } from "@movie-web/tmdb"; -import type { VideoPlayerData } from "."; -import type { ItemData } from "~/components/item/item"; -import ScreenLayout from "~/components/layout/ScreenLayout"; +// import type { ItemData } from "~/components/item/item"; +// import ScreenLayout from "~/components/layout/ScreenLayout"; -interface Event { - originalEvent: RunnerEvent; - formattedMessage: string; - style: object; -} +// interface Event { +// originalEvent: RunnerEvent; +// formattedMessage: string; +// style: object; +// } -export default function LoadingScreenWrapper() { - const params = useLocalSearchParams(); - const sourceId = params.sourceID as string | undefined; - const data = params.data - ? (JSON.parse(params.data as string) as ItemData) - : null; - const seasonData = params.seasonData - ? (JSON.parse(params.seasonData as string) as { - season: number; - episode: number; - }) - : null; - return ( - - ); -} +// export default function LoadingScreenWrapper() { +// const params = useLocalSearchParams(); +// const sourceId = params.sourceID as string | undefined; +// const data = params.data +// ? (JSON.parse(params.data as string) as ItemData) +// : null; +// const seasonData = params.seasonData +// ? (JSON.parse(params.seasonData as string) as { +// season: number; +// episode: number; +// }) +// : null; +// return ( +// +// ); +// } -function LoadingScreen({ - sourceId, - data, - seasonData, -}: { - sourceId: string | undefined; - data: ItemData | null; - seasonData: { season: number; episode: number } | null; -}) { - const router = useRouter(); - const [eventLog, setEventLog] = useState([]); +// function LoadingScreen({ +// sourceId, +// data, +// seasonData, +// }: { +// sourceId: string | undefined; +// data: ItemData | null; +// seasonData: { season: number; episode: number } | null; +// }) { +// const router = useRouter(); +// const [eventLog, setEventLog] = useState([]); - const handleEvent = (event: RunnerEvent) => { - const { message, style } = formatEvent(event); - const formattedEvent: Event = { - originalEvent: event, - formattedMessage: message, - style: style, - }; - setEventLog((prevLog) => [...prevLog, formattedEvent]); - }; +// const handleEvent = (event: RunnerEvent) => { +// const { message, style } = formatEvent(event); +// const formattedEvent: Event = { +// originalEvent: event, +// formattedMessage: message, +// style: style, +// }; +// setEventLog((prevLog) => [...prevLog, formattedEvent]); +// }; - useEffect(() => { - const fetchVideo = async () => { - if (!data) return null; - const { id, type } = data; - const media = await fetchMediaDetails(id, type).catch(() => null); - if (!media) return null; +// useEffect(() => { +// const fetchVideo = async () => { +// if (!data) return null; +// const { id, type } = data; +// const media = await fetchMediaDetails(id, type).catch(() => null); +// if (!media) return null; - const { result } = media; - let season: number | undefined; // defaults to 1 when undefined - let episode: number | undefined; +// const { result } = media; +// let season: number | undefined; // defaults to 1 when undefined +// let episode: number | undefined; - if (type === "tv") { - season = seasonData?.season ?? undefined; - episode = seasonData?.episode ?? undefined; - } +// if (type === "tv") { +// season = seasonData?.season ?? undefined; +// episode = seasonData?.episode ?? undefined; +// } - const scrapeMedia = transformSearchResultToScrapeMedia( - type, - result, - season, - episode, - ); +// const scrapeMedia = transformSearchResultToScrapeMedia( +// type, +// result, +// season, +// episode, +// ); - const stream = await getVideoStream({ - sourceId, - media: scrapeMedia, - onEvent: handleEvent, - }).catch(() => null); - if (!stream) return null; +// const stream = await getVideoStream({ +// media: scrapeMedia, +// onEvent: handleEvent, +// }).catch(() => null); +// if (!stream) return null; - return { stream, scrapeMedia }; - }; +// return { stream, scrapeMedia }; +// }; - const initialize = async () => { - const video = await fetchVideo(); - if (!video || !data) { - return router.back(); - } +// const initialize = async () => { +// const video = await fetchVideo(); +// if (!video || !data) { +// return router.back(); +// } - const videoPlayerData: VideoPlayerData = { - item: data, - stream: video.stream, - media: video.scrapeMedia, - }; +// router.replace({ +// pathname: "/videoPlayer", +// params: { +// data: JSON.stringify({ +// item: data, +// }), +// }, +// }); +// }; - router.replace({ - pathname: "/videoPlayer", - params: { data: JSON.stringify(videoPlayerData) }, - }); - }; +// void initialize(); +// }, [data, router, seasonData?.episode, seasonData?.season, sourceId]); - void initialize(); - }, [data, router, seasonData?.episode, seasonData?.season, sourceId]); +// return ( +// +// {eventLog.map((event, index) => ( +// +// {event.formattedMessage} +// +// ))} +// +// ); +// } - return ( - - {eventLog.map((event, index) => ( - - {event.formattedMessage} - - ))} - - ); -} +// function formatEvent(event: RunnerEvent): { message: string; style: object } { +// let message = ""; +// let style = {}; -function formatEvent(event: RunnerEvent): { message: string; style: object } { - let message = ""; - let style = {}; +// if (typeof event === "string") { +// message = `🚀 Start: ID - ${event}`; +// style = { color: "lime" }; +// } else if (typeof event === "object" && event !== null) { +// if ("percentage" in event) { +// const evt = event; +// const statusMessage = +// evt.status === "success" +// ? "✅ Completed" +// : evt.status === "failure" +// ? "❌ Failed - " + (evt.reason ?? "Unknown Error") +// : evt.status === "notfound" +// ? "🔍 Not Found" +// : evt.status === "pending" +// ? "⏳ In Progress" +// : "❓ Unknown Status"; - if (typeof event === "string") { - message = `🚀 Start: ID - ${event}`; - style = { color: "lime" }; - } else if (typeof event === "object" && event !== null) { - if ("percentage" in event) { - const evt = event; - const statusMessage = - evt.status === "success" - ? "✅ Completed" - : evt.status === "failure" - ? "❌ Failed - " + (evt.reason ?? "Unknown Error") - : evt.status === "notfound" - ? "🔍 Not Found" - : evt.status === "pending" - ? "⏳ In Progress" - : "❓ Unknown Status"; +// message = `Update: ${evt.percentage}% - Status: ${statusMessage}`; +// let color = ""; +// switch (evt.status) { +// case "success": +// color = "green"; +// break; +// case "failure": +// color = "red"; +// break; +// case "notfound": +// color = "blue"; +// break; +// case "pending": +// color = "yellow"; +// break; +// default: +// color = "grey"; +// break; +// } +// style = { color }; +// } else if ("sourceIds" in event) { +// const evt = event; +// message = `🔍 Initialization: Source IDs - ${evt.sourceIds.join(" ")}`; +// style = { color: "skyblue" }; +// } else if ("sourceId" in event) { +// const evt = event; +// const embedsInfo = evt.embeds +// .map((embed) => `ID: ${embed.id}, Scraper: ${embed.embedScraperId}`) +// .join("; "); - message = `Update: ${evt.percentage}% - Status: ${statusMessage}`; - let color = ""; - switch (evt.status) { - case "success": - color = "green"; - break; - case "failure": - color = "red"; - break; - case "notfound": - color = "blue"; - break; - case "pending": - color = "yellow"; - break; - default: - color = "grey"; - break; - } - style = { color }; - } else if ("sourceIds" in event) { - const evt = event; - message = `🔍 Initialization: Source IDs - ${evt.sourceIds.join(" ")}`; - style = { color: "skyblue" }; - } else if ("sourceId" in event) { - const evt = event; - const embedsInfo = evt.embeds - .map((embed) => `ID: ${embed.id}, Scraper: ${embed.embedScraperId}`) - .join("; "); +// message = `🔗 Discovered Embeds: Source ID - ${evt.sourceId} [${embedsInfo}]`; +// style = { color: "orange" }; +// } +// } else { +// message = JSON.stringify(event); +// style = { color: "grey" }; +// } - message = `🔗 Discovered Embeds: Source ID - ${evt.sourceId} [${embedsInfo}]`; - style = { color: "orange" }; - } - } else { - message = JSON.stringify(event); - style = { color: "grey" }; - } - - return { message, style }; -} +// return { message, style }; +// } diff --git a/apps/expo/src/components/item/item.tsx b/apps/expo/src/components/item/item.tsx index 6fbb25e..dc38906 100644 --- a/apps/expo/src/components/item/item.tsx +++ b/apps/expo/src/components/item/item.tsx @@ -5,6 +5,7 @@ import ContextMenu from "react-native-context-menu-view"; import { useRouter } from "expo-router"; import { Text } from "~/components/ui/Text"; +import { usePlayerStore } from "~/stores/player/store"; export interface ItemData { id: string; @@ -15,13 +16,15 @@ export interface ItemData { } export default function Item({ data }: { data: ItemData }) { + const resetVideo = usePlayerStore((state) => state.resetVideo); const router = useRouter(); const { title, type, year, posterUrl } = data; const handlePress = () => { + resetVideo(); Keyboard.dismiss(); router.push({ - pathname: "/videoPlayer/loading", + pathname: "/videoPlayer", params: { data: JSON.stringify(data) }, }); }; diff --git a/apps/expo/src/components/player/BackButton.tsx b/apps/expo/src/components/player/BackButton.tsx index d987571..51e5e39 100644 --- a/apps/expo/src/components/player/BackButton.tsx +++ b/apps/expo/src/components/player/BackButton.tsx @@ -2,19 +2,19 @@ import { Keyboard } from "react-native"; import { useRouter } from "expo-router"; import { Ionicons } from "@expo/vector-icons"; -import { usePlayerStore } from "~/stores/player/store"; +import { usePlayer } from "~/hooks/player/usePlayer"; export const BackButton = ({ className, }: Partial>) => { - const unlockOrientation = usePlayerStore((state) => state.unlockOrientation); + const { dismissFullscreenPlayer } = usePlayer(); const router = useRouter(); return ( { - unlockOrientation() + dismissFullscreenPlayer() .then(() => { router.back(); return setTimeout(() => { diff --git a/apps/expo/src/components/player/BottomControls.tsx b/apps/expo/src/components/player/BottomControls.tsx index 7d6b4e2..2b9ef8b 100644 --- a/apps/expo/src/components/player/BottomControls.tsx +++ b/apps/expo/src/components/player/BottomControls.tsx @@ -6,7 +6,7 @@ import { Text } from "../ui/Text"; import { CaptionsSelector } from "./CaptionsSelector"; import { Controls } from "./Controls"; import { ProgressBar } from "./ProgressBar"; -import { SeasonEpisodeSelector } from "./SeasonEpisodeSelector"; +import { SeasonSelector } from "./SeasonEpisodeSelector"; import { SourceSelector } from "./SourceSelector"; import { mapMillisecondsToTime } from "./utils"; @@ -36,32 +36,28 @@ export const BottomControls = () => { if (status?.isLoaded) { return ( - - - - - {getCurrentTime()} - / - - - {showRemaining - ? getRemainingTime() - : mapMillisecondsToTime(status.durationMillis ?? 0)} - - - - - - - - - - - - + + + + {getCurrentTime()} + / + + + {showRemaining + ? getRemainingTime() + : mapMillisecondsToTime(status.durationMillis ?? 0)} + + + + + + + + + - + ); } }; diff --git a/apps/expo/src/components/player/CaptionRenderer.tsx b/apps/expo/src/components/player/CaptionRenderer.tsx index 9520ee5..db927bc 100644 --- a/apps/expo/src/components/player/CaptionRenderer.tsx +++ b/apps/expo/src/components/player/CaptionRenderer.tsx @@ -69,28 +69,13 @@ export const CaptionRenderer = () => { [selectedCaption, delay, status], ); - console.log(visibleCaptions); - if (!status?.isLoaded || !selectedCaption || !visibleCaptions?.length) return null; return ( - // https://github.com/marklawlor/nativewind/issues/790 {visibleCaptions?.map((caption) => ( diff --git a/apps/expo/src/components/player/CaptionsSelector.tsx b/apps/expo/src/components/player/CaptionsSelector.tsx index 957da04..23e20c0 100644 --- a/apps/expo/src/components/player/CaptionsSelector.tsx +++ b/apps/expo/src/components/player/CaptionsSelector.tsx @@ -13,6 +13,7 @@ import { useCaptionsStore } from "~/stores/captions"; import { usePlayerStore } from "~/stores/player/store"; import { Button } from "../ui/Button"; import { Text } from "../ui/Text"; +import { Controls } from "./Controls"; const parseCaption = async ( caption: Stream["captions"][0], @@ -25,7 +26,9 @@ const parseCaption = async ( }; export const CaptionsSelector = () => { - const captions = usePlayerStore((state) => state.interface.stream?.captions); + const captions = usePlayerStore( + (state) => state.interface.currentStream?.captions, + ); const setSelectedCaption = useCaptionsStore( (state) => state.setSelectedCaption, ); @@ -46,18 +49,20 @@ export const CaptionsSelector = () => { return ( -