mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
quality preference saving
This commit is contained in:
parent
bc27a7ffa7
commit
3da2d616a2
4 changed files with 104 additions and 12 deletions
|
@ -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{" "}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
src/stores/quality/index.ts
Normal file
38
src/stores/quality/index.ts
Normal 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",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
Loading…
Reference in a new issue