Skip to content

Commit

Permalink
feat(player): save video quality in storage
Browse files Browse the repository at this point in the history
closes #1127
  • Loading branch information
mihar-22 committed Feb 20, 2024
1 parent 4e23e26 commit c8524dc
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 12 deletions.
24 changes: 23 additions & 1 deletion packages/vidstack/src/core/state/media-player-delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DOMEvent, type InferEventDetail } from 'maverick.js/std';

import type { MediaContext } from '../api/media-context';
import type { MediaEvents } from '../api/media-events';
import type { VideoQuality } from '../quality/video-quality';

let seenAutoplayWarning = false;

Expand Down Expand Up @@ -72,7 +73,7 @@ export class MediaPlayerDelegate {
}

let provider = this._media.$provider(),
{ storage } = this._media,
{ storage, qualities } = this._media,
{ muted, volume, clipStartTime, playbackRate } = this._media.$props;

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

const prefQuality = await storage?.getVideoQuality();
if (prefQuality && qualities.length) {
let currentQuality: VideoQuality | null = null,
currentScore = Infinity;

for (const quality of qualities) {
const score =
Math.abs(prefQuality.width - quality.width) +
Math.abs(prefQuality.height - quality.height) +
(prefQuality.bitrate ? Math.abs(prefQuality.bitrate - quality.bitrate) : 0);

// Lowest score wins (smallest diff between width/height/bitrate).
if (score < currentScore) {
currentQuality = quality;
currentScore = score;
}
}

if (currentQuality) currentQuality.selected = true;
}

if (canPlay() && shouldAutoPlay && !started()) {
await this._attemptAutoplay(trigger);
}
Expand Down
36 changes: 25 additions & 11 deletions packages/vidstack/src/core/state/media-request-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,11 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
}

['media-audio-track-change-request'](event: RE.MediaAudioTrackChangeRequestEvent) {
if (this._media.audioTracks.readonly) {
const { logger, audioTracks } = this._media;

if (audioTracks.readonly) {
if (__DEV__) {
this._media.logger
logger
?.warnGroup(`[vidstack] attempted to change audio track but it is currently read-only`)
.labelledLog('Request Event', event)
.dispatch();
Expand All @@ -442,16 +444,16 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
}

const index = event.detail,
track = this._media.audioTracks[index];
track = audioTracks[index];

if (track) {
const key = event.type as 'media-audio-track-change-request';
this._request._queue._enqueue(key, event);
track.selected = true;
} else if (__DEV__) {
this._media.logger
logger
?.warnGroup('[vidstack] failed audio track change request (invalid index)')
.labelledLog('Audio Tracks', this._media.audioTracks.toArray())
.labelledLog('Audio Tracks', audioTracks.toArray())
.labelledLog('Index', index)
.labelledLog('Request Event', event)
.dispatch();
Expand Down Expand Up @@ -618,9 +620,11 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
}

['media-quality-change-request'](event: RE.MediaQualityChangeRequestEvent) {
if (this._media.qualities.readonly) {
const { qualities, storage, logger } = this._media;

if (qualities.readonly) {
if (__DEV__) {
this._media.logger
logger
?.warnGroup(`[vidstack] attempted to change video quality but it is currently read-only`)
.labelledLog('Request Event', event)
.dispatch();
Expand All @@ -632,16 +636,26 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR
this._request._queue._enqueue('media-quality-change-request', event);

const index = event.detail;

if (index < 0) {
this._media.qualities.autoSelect(event);
qualities.autoSelect(event);
if (event.isOriginTrusted) storage?.setVideoQuality?.(null);
} else {
const quality = this._media.qualities[index];
const quality = qualities[index];
if (quality) {
quality.selected = true;
if (event.isOriginTrusted) {
storage?.setVideoQuality?.({
id: quality.id,
width: quality.width,
height: quality.height,
bitrate: quality.bitrate,
});
}
} else if (__DEV__) {
this._media.logger
logger
?.warnGroup('[vidstack] failed quality change request (invalid index)')
.labelledLog('Qualities', this._media.qualities.toArray())
.labelledLog('Qualities', qualities.toArray())
.labelledLog('Index', index)
.labelledLog('Request Event', event)
.dispatch();
Expand Down
23 changes: 23 additions & 0 deletions packages/vidstack/src/core/state/media-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export interface MediaStorage {
getPlaybackRate(): Promise<number | null>;
setPlaybackRate?(rate: number): Promise<void>;

getVideoQuality(): Promise<SerializedVideoQuality | null>;
setVideoQuality?(quality: SerializedVideoQuality | null): Promise<void>;

/**
* Called when the `mediaId` has changed. This method can return a function to be called
* before the next change.
Expand All @@ -41,6 +44,13 @@ export interface MediaStorage {
onDestroy?(): void;
}

export interface SerializedVideoQuality {
id: string;
width: number;
height: number;
bitrate?: number;
}

export class LocalMediaStorage implements MediaStorage {
protected playerId = 'vds-player';
protected mediaId: string | null = null;
Expand All @@ -52,6 +62,7 @@ export class LocalMediaStorage implements MediaStorage {
lang: null,
captions: null,
rate: null,
quality: null,
};

async getVolume() {
Expand Down Expand Up @@ -108,6 +119,15 @@ export class LocalMediaStorage implements MediaStorage {
this.save();
}

async getVideoQuality() {
return this._data.quality;
}

async setVideoQuality(quality: SerializedVideoQuality | null) {
this._data.quality = quality;
this.save();
}

onChange(src: MediaSrc, mediaId: string | null, playerId = 'vds-player') {
const savedData = playerId ? localStorage.getItem(playerId) : null,
savedTime = mediaId ? localStorage.getItem(mediaId) : null;
Expand All @@ -120,6 +140,8 @@ export class LocalMediaStorage implements MediaStorage {
muted: null,
lang: null,
captions: null,
rate: null,
quality: null,
...(savedData ? JSON.parse(savedData) : {}),
time: savedTime ? +savedTime : null,
};
Expand All @@ -145,4 +167,5 @@ interface SavedMediaData {
lang: string | null;
captions: boolean | null;
rate: number | null;
quality: SerializedVideoQuality | null;
}

0 comments on commit c8524dc

Please sign in to comment.