quality preference saving

This commit is contained in:
mrjvs 2023-10-18 20:31:03 +02:00
parent bc27a7ffa7
commit 3da2d616a2
4 changed files with 104 additions and 12 deletions

View File

@ -1,5 +1,6 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { Context } from "@/components/player/internals/ContextUtils"; import { Context } from "@/components/player/internals/ContextUtils";
import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayRouter } from "@/hooks/useOverlayRouter";
@ -9,6 +10,7 @@ import {
allQualities, allQualities,
qualityToString, qualityToString,
} from "@/stores/player/utils/qualities"; } from "@/stores/player/utils/qualities";
import { useQualityStore } from "@/stores/quality";
export function QualityOption(props: { export function QualityOption(props: {
children: React.ReactNode; children: React.ReactNode;
@ -41,13 +43,18 @@ export function QualityView({ id }: { id: string }) {
const availableQualities = usePlayerStore((s) => s.qualities); const availableQualities = usePlayerStore((s) => s.qualities);
const currentQuality = usePlayerStore((s) => s.currentQuality); const currentQuality = usePlayerStore((s) => s.currentQuality);
const switchQuality = usePlayerStore((s) => s.switchQuality); const switchQuality = usePlayerStore((s) => s.switchQuality);
const setAutomaticQuality = useQualityStore((s) => s.setAutomaticQuality);
const setLastChosenQuality = useQualityStore((s) => s.setLastChosenQuality);
const autoQuality = useQualityStore((s) => s.quality.automaticQuality);
const change = useCallback( const change = useCallback(
(q: SourceQuality) => { (q: SourceQuality) => {
switchQuality(q); switchQuality(q);
setLastChosenQuality(q);
setAutomaticQuality(false);
router.close(); router.close();
}, },
[router, switchQuality] [router, switchQuality, setLastChosenQuality, setAutomaticQuality]
); );
const allVisibleQualities = allQualities.filter((t) => t !== "unknown"); const allVisibleQualities = allQualities.filter((t) => t !== "unknown");
@ -73,7 +80,10 @@ export function QualityView({ id }: { id: string }) {
<Context.Divider /> <Context.Divider />
<Context.Link> <Context.Link>
<Context.LinkTitle>Automatic quality</Context.LinkTitle> <Context.LinkTitle>Automatic quality</Context.LinkTitle>
<span>Toggle</span> <Toggle
onClick={() => setAutomaticQuality(!autoQuality)}
enabled={autoQuality}
/>
</Context.Link> </Context.Link>
<Context.SmallText> <Context.SmallText>
You can try{" "} You can try{" "}

View File

@ -6,6 +6,7 @@ import {
SourceSliceSource, SourceSliceSource,
selectQuality, selectQuality,
} from "@/stores/player/utils/qualities"; } from "@/stores/player/utils/qualities";
import { useQualityStore } from "@/stores/quality";
import { ValuesOf } from "@/utils/typeguard"; import { ValuesOf } from "@/utils/typeguard";
export const playerStatus = { export const playerStatus = {
@ -118,7 +119,8 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
let qualities: string[] = []; let qualities: string[] = [];
if (stream.type === "file") qualities = Object.keys(stream.qualities); if (stream.type === "file") qualities = Object.keys(stream.qualities);
const store = get(); const store = get();
const loadableStream = selectQuality(stream); const qualityPreferences = useQualityStore.getState();
const loadableStream = selectQuality(stream, qualityPreferences.quality);
set((s) => { set((s) => {
s.source = stream; s.source = stream;

View File

@ -1,3 +1,5 @@
import { QualityStore } from "@/stores/quality";
export type SourceQuality = "unknown" | "360" | "480" | "720" | "1080"; export type SourceQuality = "unknown" | "360" | "480" | "720" | "1080";
export type StreamType = "hls" | "mp4"; export type StreamType = "hls" | "mp4";
@ -23,17 +25,56 @@ export type SourceSliceSource =
}; };
const qualitySorting: Record<SourceQuality, number> = { const qualitySorting: Record<SourceQuality, number> = {
"1080": 40, unknown: 0,
"360": 10, "360": 10,
"480": 20, "480": 20,
"720": 30, "720": 30,
unknown: 0, "1080": 40,
}; };
const sortedQualities: SourceQuality[] = Object.entries(qualitySorting) const sortedQualities: SourceQuality[] = Object.entries(qualitySorting)
.sort((a, b) => b[1] - a[1]) .sort((a, b) => b[1] - a[1])
.map<SourceQuality>((v) => v[0] as SourceQuality); .map<SourceQuality>((v) => v[0] as SourceQuality);
export function selectQuality(source: SourceSliceSource): { function getPreferredQuality(
availableQualites: SourceQuality[],
qualityPreferences: QualityStore["quality"]
) {
if (
qualityPreferences.automaticQuality ||
qualityPreferences.lastChosenQuality === null ||
qualityPreferences.lastChosenQuality === "unknown"
)
return sortedQualities.find((v) => availableQualites.includes(v));
// get preferred quality - not automatic or unknown
const chosenQualityIndex = sortedQualities.indexOf(
qualityPreferences.lastChosenQuality
);
let nearestChoseQuality: undefined | SourceQuality;
// check chosen quality or lower
for (let i = chosenQualityIndex; i < sortedQualities.length; i += 1) {
if (availableQualites.includes(sortedQualities[i])) {
nearestChoseQuality = sortedQualities[i];
break;
}
}
if (nearestChoseQuality) return nearestChoseQuality;
// chosen quality or lower doesn't exist, try higher
for (let i = chosenQualityIndex; i >= 0; i -= 1) {
if (availableQualites.includes(sortedQualities[i])) {
nearestChoseQuality = sortedQualities[i];
break;
}
}
return nearestChoseQuality;
}
export function selectQuality(
source: SourceSliceSource,
qualityPreferences: QualityStore["quality"]
): {
stream: LoadableSource; stream: LoadableSource;
quality: null | SourceQuality; quality: null | SourceQuality;
} { } {
@ -43,13 +84,14 @@ export function selectQuality(source: SourceSliceSource): {
quality: null, quality: null,
}; };
if (source.type === "file") { if (source.type === "file") {
const bestQuality = sortedQualities.find( const availableQualities = Object.entries(source.qualities)
(v) => source.qualities[v] && (source.qualities[v]?.url.length ?? 0) > 0 .filter((entry) => (entry[1].url.length ?? 0) > 0)
); .map((entry) => entry[0]) as SourceQuality[];
if (bestQuality) { const quality = getPreferredQuality(availableQualities, qualityPreferences);
const stream = source.qualities[bestQuality]; if (quality) {
const stream = source.qualities[quality];
if (stream) { if (stream) {
return { stream, quality: bestQuality }; return { stream, quality };
} }
} }
} }

View File

@ -0,0 +1,38 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
import { SourceQuality } from "@/stores/player/utils/qualities";
export interface QualityStore {
quality: {
lastChosenQuality: SourceQuality | null;
automaticQuality: boolean;
};
setLastChosenQuality(v: SourceQuality | null): void;
setAutomaticQuality(v: boolean): void;
}
export const useQualityStore = create(
persist(
immer<QualityStore>((set) => ({
quality: {
automaticQuality: true,
lastChosenQuality: null,
},
setLastChosenQuality(v) {
set((s) => {
s.quality.lastChosenQuality = v;
});
},
setAutomaticQuality(v) {
set((s) => {
s.quality.automaticQuality = v;
});
},
})),
{
name: "__MW::quality",
}
)
);