Skip to content

Commit c8524dc

Browse files
committed
feat(player): save video quality in storage
closes #1127
1 parent 4e23e26 commit c8524dc

3 files changed

Lines changed: 71 additions & 12 deletions

File tree

packages/vidstack/src/core/state/media-player-delegate.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { DOMEvent, type InferEventDetail } from 'maverick.js/std';
33

44
import type { MediaContext } from '../api/media-context';
55
import type { MediaEvents } from '../api/media-events';
6+
import type { VideoQuality } from '../quality/video-quality';
67

78
let seenAutoplayWarning = false;
89

@@ -72,7 +73,7 @@ export class MediaPlayerDelegate {
7273
}
7374

7475
let provider = this._media.$provider(),
75-
{ storage } = this._media,
76+
{ storage, qualities } = this._media,
7677
{ muted, volume, clipStartTime, playbackRate } = this._media.$props;
7778

7879
const remotePlaybackTime = remotePlaybackInfo()?.savedState?.currentTime,
@@ -88,6 +89,27 @@ export class MediaPlayerDelegate {
8889
if (startTime > 0) provider.setCurrentTime(startTime);
8990
}
9091

92+
const prefQuality = await storage?.getVideoQuality();
93+
if (prefQuality && qualities.length) {
94+
let currentQuality: VideoQuality | null = null,
95+
currentScore = Infinity;
96+
97+
for (const quality of qualities) {
98+
const score =
99+
Math.abs(prefQuality.width - quality.width) +
100+
Math.abs(prefQuality.height - quality.height) +
101+
(prefQuality.bitrate ? Math.abs(prefQuality.bitrate - quality.bitrate) : 0);
102+
103+
// Lowest score wins (smallest diff between width/height/bitrate).
104+
if (score < currentScore) {
105+
currentQuality = quality;
106+
currentScore = score;
107+
}
108+
}
109+
110+
if (currentQuality) currentQuality.selected = true;
111+
}
112+
91113
if (canPlay() && shouldAutoPlay && !started()) {
92114
await this._attemptAutoplay(trigger);
93115
}

packages/vidstack/src/core/state/media-request-manager.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -430,9 +430,11 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
430430
}
431431

432432
['media-audio-track-change-request'](event: RE.MediaAudioTrackChangeRequestEvent) {
433-
if (this._media.audioTracks.readonly) {
433+
const { logger, audioTracks } = this._media;
434+
435+
if (audioTracks.readonly) {
434436
if (__DEV__) {
435-
this._media.logger
437+
logger
436438
?.warnGroup(`[vidstack] attempted to change audio track but it is currently read-only`)
437439
.labelledLog('Request Event', event)
438440
.dispatch();
@@ -442,16 +444,16 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
442444
}
443445

444446
const index = event.detail,
445-
track = this._media.audioTracks[index];
447+
track = audioTracks[index];
446448

447449
if (track) {
448450
const key = event.type as 'media-audio-track-change-request';
449451
this._request._queue._enqueue(key, event);
450452
track.selected = true;
451453
} else if (__DEV__) {
452-
this._media.logger
454+
logger
453455
?.warnGroup('[vidstack] failed audio track change request (invalid index)')
454-
.labelledLog('Audio Tracks', this._media.audioTracks.toArray())
456+
.labelledLog('Audio Tracks', audioTracks.toArray())
455457
.labelledLog('Index', index)
456458
.labelledLog('Request Event', event)
457459
.dispatch();
@@ -618,9 +620,11 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
618620
}
619621

620622
['media-quality-change-request'](event: RE.MediaQualityChangeRequestEvent) {
621-
if (this._media.qualities.readonly) {
623+
const { qualities, storage, logger } = this._media;
624+
625+
if (qualities.readonly) {
622626
if (__DEV__) {
623-
this._media.logger
627+
logger
624628
?.warnGroup(`[vidstack] attempted to change video quality but it is currently read-only`)
625629
.labelledLog('Request Event', event)
626630
.dispatch();
@@ -632,16 +636,26 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
632636
this._request._queue._enqueue('media-quality-change-request', event);
633637

634638
const index = event.detail;
639+
635640
if (index < 0) {
636-
this._media.qualities.autoSelect(event);
641+
qualities.autoSelect(event);
642+
if (event.isOriginTrusted) storage?.setVideoQuality?.(null);
637643
} else {
638-
const quality = this._media.qualities[index];
644+
const quality = qualities[index];
639645
if (quality) {
640646
quality.selected = true;
647+
if (event.isOriginTrusted) {
648+
storage?.setVideoQuality?.({
649+
id: quality.id,
650+
width: quality.width,
651+
height: quality.height,
652+
bitrate: quality.bitrate,
653+
});
654+
}
641655
} else if (__DEV__) {
642-
this._media.logger
656+
logger
643657
?.warnGroup('[vidstack] failed quality change request (invalid index)')
644-
.labelledLog('Qualities', this._media.qualities.toArray())
658+
.labelledLog('Qualities', qualities.toArray())
645659
.labelledLog('Index', index)
646660
.labelledLog('Request Event', event)
647661
.dispatch();

packages/vidstack/src/core/state/media-storage.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export interface MediaStorage {
2222
getPlaybackRate(): Promise<number | null>;
2323
setPlaybackRate?(rate: number): Promise<void>;
2424

25+
getVideoQuality(): Promise<SerializedVideoQuality | null>;
26+
setVideoQuality?(quality: SerializedVideoQuality | null): Promise<void>;
27+
2528
/**
2629
* Called when the `mediaId` has changed. This method can return a function to be called
2730
* before the next change.
@@ -41,6 +44,13 @@ export interface MediaStorage {
4144
onDestroy?(): void;
4245
}
4346

47+
export interface SerializedVideoQuality {
48+
id: string;
49+
width: number;
50+
height: number;
51+
bitrate?: number;
52+
}
53+
4454
export class LocalMediaStorage implements MediaStorage {
4555
protected playerId = 'vds-player';
4656
protected mediaId: string | null = null;
@@ -52,6 +62,7 @@ export class LocalMediaStorage implements MediaStorage {
5262
lang: null,
5363
captions: null,
5464
rate: null,
65+
quality: null,
5566
};
5667

5768
async getVolume() {
@@ -108,6 +119,15 @@ export class LocalMediaStorage implements MediaStorage {
108119
this.save();
109120
}
110121

122+
async getVideoQuality() {
123+
return this._data.quality;
124+
}
125+
126+
async setVideoQuality(quality: SerializedVideoQuality | null) {
127+
this._data.quality = quality;
128+
this.save();
129+
}
130+
111131
onChange(src: MediaSrc, mediaId: string | null, playerId = 'vds-player') {
112132
const savedData = playerId ? localStorage.getItem(playerId) : null,
113133
savedTime = mediaId ? localStorage.getItem(mediaId) : null;
@@ -120,6 +140,8 @@ export class LocalMediaStorage implements MediaStorage {
120140
muted: null,
121141
lang: null,
122142
captions: null,
143+
rate: null,
144+
quality: null,
123145
...(savedData ? JSON.parse(savedData) : {}),
124146
time: savedTime ? +savedTime : null,
125147
};
@@ -145,4 +167,5 @@ interface SavedMediaData {
145167
lang: string | null;
146168
captions: boolean | null;
147169
rate: number | null;
170+
quality: SerializedVideoQuality | null;
148171
}

0 commit comments

Comments
 (0)