diff --git a/src/components/player/atoms/settings/QualityView.tsx b/src/components/player/atoms/settings/QualityView.tsx
index 4808dde2..139eb802 100644
--- a/src/components/player/atoms/settings/QualityView.tsx
+++ b/src/components/player/atoms/settings/QualityView.tsx
@@ -1,5 +1,6 @@
import { useCallback } from "react";
+import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon";
import { Context } from "@/components/player/internals/ContextUtils";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
@@ -9,6 +10,7 @@ import {
allQualities,
qualityToString,
} from "@/stores/player/utils/qualities";
+import { useQualityStore } from "@/stores/quality";
export function QualityOption(props: {
children: React.ReactNode;
@@ -41,13 +43,18 @@ export function QualityView({ id }: { id: string }) {
const availableQualities = usePlayerStore((s) => s.qualities);
const currentQuality = usePlayerStore((s) => s.currentQuality);
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(
(q: SourceQuality) => {
switchQuality(q);
+ setLastChosenQuality(q);
+ setAutomaticQuality(false);
router.close();
},
- [router, switchQuality]
+ [router, switchQuality, setLastChosenQuality, setAutomaticQuality]
);
const allVisibleQualities = allQualities.filter((t) => t !== "unknown");
@@ -73,7 +80,10 @@ export function QualityView({ id }: { id: string }) {
Automatic quality
- Toggle
+ setAutomaticQuality(!autoQuality)}
+ enabled={autoQuality}
+ />
You can try{" "}
diff --git a/src/stores/player/slices/source.ts b/src/stores/player/slices/source.ts
index b221cdb5..ea7b89d9 100644
--- a/src/stores/player/slices/source.ts
+++ b/src/stores/player/slices/source.ts
@@ -6,6 +6,7 @@ import {
SourceSliceSource,
selectQuality,
} from "@/stores/player/utils/qualities";
+import { useQualityStore } from "@/stores/quality";
import { ValuesOf } from "@/utils/typeguard";
export const playerStatus = {
@@ -118,7 +119,8 @@ export const createSourceSlice: MakeSlice = (set, get) => ({
let qualities: string[] = [];
if (stream.type === "file") qualities = Object.keys(stream.qualities);
const store = get();
- const loadableStream = selectQuality(stream);
+ const qualityPreferences = useQualityStore.getState();
+ const loadableStream = selectQuality(stream, qualityPreferences.quality);
set((s) => {
s.source = stream;
diff --git a/src/stores/player/utils/qualities.ts b/src/stores/player/utils/qualities.ts
index b83ef303..4082a9a2 100644
--- a/src/stores/player/utils/qualities.ts
+++ b/src/stores/player/utils/qualities.ts
@@ -1,3 +1,5 @@
+import { QualityStore } from "@/stores/quality";
+
export type SourceQuality = "unknown" | "360" | "480" | "720" | "1080";
export type StreamType = "hls" | "mp4";
@@ -23,17 +25,56 @@ export type SourceSliceSource =
};
const qualitySorting: Record = {
- "1080": 40,
+ unknown: 0,
"360": 10,
"480": 20,
"720": 30,
- unknown: 0,
+ "1080": 40,
};
const sortedQualities: SourceQuality[] = Object.entries(qualitySorting)
.sort((a, b) => b[1] - a[1])
.map((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;
quality: null | SourceQuality;
} {
@@ -43,13 +84,14 @@ export function selectQuality(source: SourceSliceSource): {
quality: null,
};
if (source.type === "file") {
- const bestQuality = sortedQualities.find(
- (v) => source.qualities[v] && (source.qualities[v]?.url.length ?? 0) > 0
- );
- if (bestQuality) {
- const stream = source.qualities[bestQuality];
+ const availableQualities = Object.entries(source.qualities)
+ .filter((entry) => (entry[1].url.length ?? 0) > 0)
+ .map((entry) => entry[0]) as SourceQuality[];
+ const quality = getPreferredQuality(availableQualities, qualityPreferences);
+ if (quality) {
+ const stream = source.qualities[quality];
if (stream) {
- return { stream, quality: bestQuality };
+ return { stream, quality };
}
}
}
diff --git a/src/stores/quality/index.ts b/src/stores/quality/index.ts
new file mode 100644
index 00000000..e5c8e48d
--- /dev/null
+++ b/src/stores/quality/index.ts
@@ -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((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",
+ }
+ )
+);