···3838 2160: "4k",
3939};
40404141+// Define quality thresholds for mapping non-standard resolutions
4242+const qualityThresholds = [
4343+ { minHeight: 1800, quality: "4k" as SourceQuality },
4444+ { minHeight: 800, quality: "1080" as SourceQuality },
4545+ { minHeight: 600, quality: "720" as SourceQuality },
4646+ { minHeight: 420, quality: "480" as SourceQuality },
4747+ { minHeight: 0, quality: "360" as SourceQuality },
4848+];
4949+4150function hlsLevelToQuality(level?: Level): SourceQuality | null {
4242- return levelConversionMap[level?.height ?? 0] ?? null;
4343-}
5151+ if (!level?.height) return null;
44524545-function qualityToHlsLevel(quality: SourceQuality): number | null {
4646- const found = Object.entries(levelConversionMap).find(
4747- (entry) => entry[1] === quality,
4848- );
4949- return found ? +found[0] : null;
5353+ // First check for exact matches
5454+ const exactMatch = levelConversionMap[level.height];
5555+ if (exactMatch) return exactMatch;
5656+5757+ // For non-standard resolutions, map to closest standard quality
5858+ for (const threshold of qualityThresholds) {
5959+ if (level.height >= threshold.minHeight) {
6060+ return threshold.quality;
6161+ }
6262+ }
6363+6464+ return "unknown"; // fallback to unknown quality
5065}
51665267function hlsLevelsToQualities(levels: Level[]): SourceQuality[] {
5368 return levels
5469 .map((v) => hlsLevelToQuality(v))
5570 .filter((v): v is SourceQuality => !!v);
7171+}
7272+7373+// Sort levels by quality (height) to ensure we can select the best one
7474+function sortLevelsByQuality(levels: Level[]): Level[] {
7575+ return [...levels].sort((a, b) => (b.height || 0) - (a.height || 0));
5676}
57775878export function makeVideoElementDisplayInterface(): DisplayInterface {
···115135116136 if (!hls) return;
117137 if (!automaticQuality) {
118118- const qualities = hlsLevelsToQualities(hls.levels);
138138+ const sortedLevels = sortLevelsByQuality(hls.levels);
139139+ const qualities = hlsLevelsToQualities(sortedLevels);
119140 const availableQuality = getPreferredQuality(qualities, {
120141 lastChosenQuality: preferenceQuality,
121142 automaticQuality,
122143 });
123144 if (availableQuality) {
124124- const levelIndex = hls.levels.findIndex(
125125- (v) => v.height === qualityToHlsLevel(availableQuality),
145145+ // Find the best level that matches our preferred quality
146146+ const matchingLevels = hls.levels.filter(
147147+ (level) => hlsLevelToQuality(level) === availableQuality,
126148 );
127127- if (levelIndex !== -1) {
128128- hls.currentLevel = levelIndex;
129129- hls.loadLevel = levelIndex;
149149+ if (matchingLevels.length > 0) {
150150+ // Pick the highest resolution level for this quality
151151+ const bestLevel = sortLevelsByQuality(matchingLevels)[0];
152152+ const levelIndex = hls.levels.indexOf(bestLevel);
153153+ if (levelIndex !== -1) {
154154+ hls.currentLevel = levelIndex;
155155+ hls.loadLevel = levelIndex;
156156+ }
130157 }
131158 }
132159 } else {
+4-1
src/stores/player/utils/qualities.ts
···5252 qualityPreferences.automaticQuality ||
5353 qualityPreferences.lastChosenQuality === null ||
5454 qualityPreferences.lastChosenQuality === "unknown"
5555- )
5555+ ) {
5656+ // For automatic quality, select the best available quality
5757+ // Sort by our quality preference order and pick the first (best) available
5658 return sortedQualities.find((v) => availableQualites.includes(v));
5959+ }
57605861 // get preferred quality - not automatic or unknown
5962 const chosenQualityIndex = sortedQualities.indexOf(