From c5679549726bf84cbf8a692b7d9afcf08a510c82 Mon Sep 17 00:00:00 2001
From: Adrian Castro <22133246+castdrian@users.noreply.github.com>
Date: Sun, 24 Mar 2024 13:15:44 +0100
Subject: [PATCH 1/2] chore: cache entire dir
---
.github/workflows/build-mobile-comment.yml | 2 +-
.github/workflows/build-mobile.yml | 2 +-
.github/workflows/release-mobile.yml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build-mobile-comment.yml b/.github/workflows/build-mobile-comment.yml
index 9a1757c..dbf2c23 100644
--- a/.github/workflows/build-mobile-comment.yml
+++ b/.github/workflows/build-mobile-comment.yml
@@ -104,7 +104,7 @@ jobs:
- name: Cache Pods
uses: actions/cache@v4
with:
- path: apps/expo/ios/Pods
+ path: apps/expo/ios
key: ${{ runner.os }}-pods-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pods-
diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml
index b73f1ee..7888beb 100644
--- a/.github/workflows/build-mobile.yml
+++ b/.github/workflows/build-mobile.yml
@@ -95,7 +95,7 @@ jobs:
- name: Cache Pods
uses: actions/cache@v4
with:
- path: apps/expo/ios/Pods
+ path: apps/expo/ios
key: ${{ runner.os }}-pods-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pods-
diff --git a/.github/workflows/release-mobile.yml b/.github/workflows/release-mobile.yml
index e245e04..29d6903 100644
--- a/.github/workflows/release-mobile.yml
+++ b/.github/workflows/release-mobile.yml
@@ -123,7 +123,7 @@ jobs:
- name: Cache Pods
uses: actions/cache@v4
with:
- path: apps/expo/ios/Pods
+ path: apps/expo/ios
key: ${{ runner.os }}-pods-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pods-
From ea435d91defd6b07fc04126ebe4e6e5470aa11f5 Mon Sep 17 00:00:00 2001
From: Adrian Castro <22133246+castdrian@users.noreply.github.com>
Date: Sun, 24 Mar 2024 17:36:14 +0100
Subject: [PATCH 2/2] feat: quality selector
---
.../src/components/player/BottomControls.tsx | 2 +
.../src/components/player/QualitySelector.tsx | 111 ++++++++++++++++++
.../src/components/player/VideoPlayer.tsx | 5 +-
apps/expo/src/stores/player/slices/video.ts | 10 +-
4 files changed, 125 insertions(+), 3 deletions(-)
create mode 100644 apps/expo/src/components/player/QualitySelector.tsx
diff --git a/apps/expo/src/components/player/BottomControls.tsx b/apps/expo/src/components/player/BottomControls.tsx
index 846f10c..539fe26 100644
--- a/apps/expo/src/components/player/BottomControls.tsx
+++ b/apps/expo/src/components/player/BottomControls.tsx
@@ -9,6 +9,7 @@ import { Controls } from "./Controls";
import { DownloadButton } from "./DownloadButton";
import { PlaybackSpeedSelector } from "./PlaybackSpeedSelector";
import { ProgressBar } from "./ProgressBar";
+import { QualitySelector } from "./QualitySelector";
import { SeasonSelector } from "./SeasonEpisodeSelector";
import { SourceSelector } from "./SourceSelector";
import { mapMillisecondsToTime } from "./utils";
@@ -80,6 +81,7 @@ export const BottomControls = () => {
+
diff --git a/apps/expo/src/components/player/QualitySelector.tsx b/apps/expo/src/components/player/QualitySelector.tsx
new file mode 100644
index 0000000..3f05c31
--- /dev/null
+++ b/apps/expo/src/components/player/QualitySelector.tsx
@@ -0,0 +1,111 @@
+import { useState } from "react";
+import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
+import { useTheme } from "tamagui";
+
+import { constructFullUrl } from "~/lib/url";
+import { usePlayerStore } from "~/stores/player/store";
+import { MWButton } from "../ui/Button";
+import { Controls } from "./Controls";
+import { Settings } from "./settings/Sheet";
+
+export const QualitySelector = () => {
+ const theme = useTheme();
+ const [open, setOpen] = useState(false);
+ const videoRef = usePlayerStore((state) => state.videoRef);
+ const videoSrc = usePlayerStore((state) => state.videoSrc);
+ const stream = usePlayerStore((state) => state.interface.currentStream);
+ const hlsTracks = usePlayerStore((state) => state.interface.hlsTracks);
+
+ if (!videoRef || !videoSrc || !stream) return null;
+ let qualityMap: { quality: string; url: string }[];
+ let currentQuality: string | undefined;
+
+ if (stream.type === "file") {
+ const { qualities } = stream;
+
+ currentQuality = Object.keys(qualities).find(
+ (key) => qualities[key as keyof typeof qualities]!.url === videoSrc.uri,
+ );
+
+ qualityMap = Object.keys(qualities).map((key: string) => ({
+ quality: key,
+ url: qualities[key as keyof typeof qualities]!.url,
+ }));
+ } else if (stream.type === "hls") {
+ if (!hlsTracks?.video) return null;
+
+ qualityMap = hlsTracks.video.map((video) => ({
+ quality:
+ (video.properties[0]?.attributes.resolution as string) ?? "unknown",
+ url: constructFullUrl(stream.playlist, video.uri),
+ }));
+ } else {
+ return null;
+ }
+
+ return (
+ <>
+
+
+ }
+ onPress={() => setOpen(true)}
+ >
+ Quality
+
+
+
+
+
+
+
+ setOpen(false)}
+ />
+ }
+ title="Quality settings"
+ />
+
+ {qualityMap?.map((quality) => (
+
+ )
+ }
+ onPress={() => {
+ void videoRef.unloadAsync();
+ void videoRef.loadAsync(
+ { uri: quality.url, headers: stream.headers },
+ { shouldPlay: true },
+ );
+ }}
+ />
+ ))}
+
+
+
+ >
+ );
+};
diff --git a/apps/expo/src/components/player/VideoPlayer.tsx b/apps/expo/src/components/player/VideoPlayer.tsx
index 181e028..d927336 100644
--- a/apps/expo/src/components/player/VideoPlayer.tsx
+++ b/apps/expo/src/components/player/VideoPlayer.tsx
@@ -1,4 +1,3 @@
-import type { AVPlaybackSource } from "expo-av";
import type { SharedValue } from "react-native-reanimated";
import { useEffect, useState } from "react";
import { Dimensions, Platform } from "react-native";
@@ -42,7 +41,6 @@ export const VideoPlayer = () => {
const { currentSpeed } = usePlaybackSpeed();
const { synchronizePlayback } = useAudioTrack();
const { dismissFullscreenPlayer } = usePlayer();
- const [videoSrc, setVideoSrc] = useState();
const [isLoading, setIsLoading] = useState(true);
const [resizeMode, setResizeMode] = useState(ResizeMode.CONTAIN);
const [hasStartedPlaying, setHasStartedPlaying] = useState(false);
@@ -57,6 +55,8 @@ export const VideoPlayer = () => {
const videoRef = usePlayerStore((state) => state.videoRef);
const asset = usePlayerStore((state) => state.asset);
const setVideoRef = usePlayerStore((state) => state.setVideoRef);
+ const videoSrc = usePlayerStore((state) => state.videoSrc) ?? undefined;
+ const setVideoSrc = usePlayerStore((state) => state.setVideoSrc);
const setStatus = usePlayerStore((state) => state.setStatus);
const setIsIdle = usePlayerStore((state) => state.setIsIdle);
const toggleAudio = usePlayerStore((state) => state.toggleAudio);
@@ -201,6 +201,7 @@ export const VideoPlayer = () => {
hasStartedPlaying,
router,
selectedAudioTrack,
+ setVideoSrc,
stream,
synchronizePlayback,
]);
diff --git a/apps/expo/src/stores/player/slices/video.ts b/apps/expo/src/stores/player/slices/video.ts
index 26a9516..4e57f9b 100644
--- a/apps/expo/src/stores/player/slices/video.ts
+++ b/apps/expo/src/stores/player/slices/video.ts
@@ -1,4 +1,4 @@
-import type { AVPlaybackStatus, Video } from "expo-av";
+import type { AVPlaybackSourceObject, AVPlaybackStatus, Video } from "expo-av";
import type { Asset } from "expo-media-library";
import type { ScrapeMedia } from "@movie-web/provider-utils";
@@ -30,11 +30,13 @@ export interface PlayerMeta {
export interface VideoSlice {
videoRef: Video | null;
+ videoSrc: AVPlaybackSourceObject | null;
status: AVPlaybackStatus | null;
meta: PlayerMeta | null;
asset: Asset | null;
setVideoRef(ref: Video | null): void;
+ setVideoSrc(src: AVPlaybackSourceObject | null): void;
setStatus(status: AVPlaybackStatus | null): void;
setMeta(meta: PlayerMeta | null): void;
setAsset(asset: Asset | null): void;
@@ -67,6 +69,7 @@ export const convertMetaToScrapeMedia = (meta: PlayerMeta): ScrapeMedia => {
export const createVideoSlice: MakeSlice = (set) => ({
videoRef: null,
+ videoSrc: null,
status: null,
meta: null,
asset: null,
@@ -74,6 +77,11 @@ export const createVideoSlice: MakeSlice = (set) => ({
setVideoRef: (ref) => {
set({ videoRef: ref });
},
+ setVideoSrc: (src) => {
+ set((s) => {
+ s.videoSrc = src;
+ });
+ },
setStatus: (status) => {
set((s) => {
s.status = status;