Merge branch 'feat-providers-video' of https://github.com/castdrian/mw-native into pr/9

This commit is contained in:
Jorrin
2024-03-24 20:41:11 +01:00
7 changed files with 128 additions and 6 deletions

View File

@@ -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-

View File

@@ -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-

View File

@@ -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-

View File

@@ -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 = () => {
<SourceSelector />
<AudioTrackSelector />
<PlaybackSpeedSelector />
<QualitySelector />
<DownloadButton />
</View>
</View>

View File

@@ -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 (
<>
<Controls>
<MWButton
type="secondary"
icon={
<MaterialIcons
name="hd"
size={24}
color={theme.buttonSecondaryText.val}
/>
}
onPress={() => setOpen(true)}
>
Quality
</MWButton>
</Controls>
<Settings.Sheet
forceRemoveScrollEnabled={open}
open={open}
onOpenChange={setOpen}
>
<Settings.SheetOverlay />
<Settings.SheetHandle />
<Settings.SheetFrame>
<Settings.Header
icon={
<MaterialCommunityIcons
name="close"
size={24}
color={theme.playerSettingsUnactiveText.val}
onPress={() => setOpen(false)}
/>
}
title="Quality settings"
/>
<Settings.Content>
{qualityMap?.map((quality) => (
<Settings.Item
key={quality.quality}
title={quality.quality}
iconRight={
quality.quality === currentQuality && (
<MaterialCommunityIcons
name="check-circle"
size={24}
color={theme.sheetItemSelected.val}
/>
)
}
onPress={() => {
void videoRef.unloadAsync();
void videoRef.loadAsync(
{ uri: quality.url, headers: stream.headers },
{ shouldPlay: true },
);
}}
/>
))}
</Settings.Content>
</Settings.SheetFrame>
</Settings.Sheet>
</>
);
};

View File

@@ -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<AVPlaybackSource>();
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,
]);

View File

@@ -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<VideoSlice> = (set) => ({
videoRef: null,
videoSrc: null,
status: null,
meta: null,
asset: null,
@@ -74,6 +77,11 @@ export const createVideoSlice: MakeSlice<VideoSlice> = (set) => ({
setVideoRef: (ref) => {
set({ videoRef: ref });
},
setVideoSrc: (src) => {
set((s) => {
s.videoSrc = src;
});
},
setStatus: (status) => {
set((s) => {
s.status = status;