mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-05 23:06:14 +00:00
Add quality selector
This commit is contained in:
@@ -18,6 +18,7 @@ import { createSource } from "./utils/sourceFactory";
|
|||||||
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
||||||
import type { AudioTrack } from "./types/AudioTrack";
|
import type { AudioTrack } from "./types/AudioTrack";
|
||||||
import type { VideoTrack } from "./types/VideoTrack";
|
import type { VideoTrack } from "./types/VideoTrack";
|
||||||
|
import type { QualityLevel } from "./types/QualityLevel";
|
||||||
|
|
||||||
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||||
protected player: VideoPlayerImpl;
|
protected player: VideoPlayerImpl;
|
||||||
@@ -308,6 +309,22 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
|||||||
get selectedVideoTrack(): VideoTrack | undefined {
|
get selectedVideoTrack(): VideoTrack | undefined {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// quality
|
||||||
|
|
||||||
|
getAvailableQualities(): QualityLevel[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
selectQuality(_: QualityLevel | null): void {}
|
||||||
|
|
||||||
|
get currentQuality(): QualityLevel | undefined {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get autoQualityEnabled(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { VideoPlayer };
|
export { VideoPlayer };
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
|||||||
import { MediaSessionHandler } from "./web/MediaSession";
|
import { MediaSessionHandler } from "./web/MediaSession";
|
||||||
import { WebEventEmiter } from "./web/WebEventEmiter";
|
import { WebEventEmiter } from "./web/WebEventEmiter";
|
||||||
import type { VideoTrack } from "./types/VideoTrack";
|
import type { VideoTrack } from "./types/VideoTrack";
|
||||||
|
import type { QualityLevel } from "./types/QualityLevel";
|
||||||
|
|
||||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||||
|
|
||||||
@@ -44,6 +45,21 @@ export type VideoJsTracks = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// declared https://github.com/videojs/videojs-contrib-quality-levels/blob/main/src/quality-level.js#L32
|
||||||
|
export type VideoJsQualityArray = {
|
||||||
|
length: number;
|
||||||
|
selectedIndex: number;
|
||||||
|
[i: number]: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
bitrate: number;
|
||||||
|
frameRate: number;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||||
protected video: HTMLVideoElement;
|
protected video: HTMLVideoElement;
|
||||||
public player: VideoJsPlayer;
|
public player: VideoJsPlayer;
|
||||||
@@ -52,7 +68,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
|||||||
|
|
||||||
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
|
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
|
||||||
const video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
const player = videojs(video);
|
const player = videojs(video, { qualityLevels: true });
|
||||||
|
|
||||||
super(new WebEventEmiter(player));
|
super(new WebEventEmiter(player));
|
||||||
|
|
||||||
@@ -355,6 +371,43 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
|||||||
get selectedVideoTrack(): VideoTrack | undefined {
|
get selectedVideoTrack(): VideoTrack | undefined {
|
||||||
return this.getAvailableVideoTracks().find((x) => x.selected);
|
return this.getAvailableVideoTracks().find((x) => x.selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// quality
|
||||||
|
|
||||||
|
getAvailableQualities(): QualityLevel[] {
|
||||||
|
// @ts-expect-error this isn't typed
|
||||||
|
const levels: VideoJsQualityArray = this.player.qualityLevels();
|
||||||
|
return [...Array(levels.length)].map((_, i) => ({
|
||||||
|
id: levels[i]!.id,
|
||||||
|
width: levels[i]!.width,
|
||||||
|
height: levels[i]!.height,
|
||||||
|
bitrate: levels[i]!.bitrate,
|
||||||
|
selected: levels.selectedIndex === i,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectQuality(quality: QualityLevel | null): void {
|
||||||
|
// @ts-expect-error this isn't typed
|
||||||
|
const levels: VideoJsQualityArray = this.player.qualityLevels();
|
||||||
|
|
||||||
|
for (let i = 0; i < levels.length; i++) {
|
||||||
|
// if quality is null, enable back auto-quality switch (so enable all lvls)
|
||||||
|
levels[i]!.enabled = !quality || levels[i]!.id === quality.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentQuality(): QualityLevel | undefined {
|
||||||
|
return this.getAvailableQualities().find((x) => x.selected);
|
||||||
|
}
|
||||||
|
get autoQualityEnabled(): boolean {
|
||||||
|
// @ts-expect-error this isn't typed
|
||||||
|
const levels: VideoJsQualityArray = this.player.qualityLevels();
|
||||||
|
// if we have a quality disabled that means we manually disabled it & disabled auto quality
|
||||||
|
for (let i = 0; i < levels.length; i++) {
|
||||||
|
if (!levels[i]!.enabled) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { VideoPlayer };
|
export { VideoPlayer };
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { AudioTrack } from './AudioTrack';
|
import type { AudioTrack } from './AudioTrack';
|
||||||
|
import type { QualityLevel } from './QualityLevel';
|
||||||
import type { TextTrack } from './TextTrack';
|
import type { TextTrack } from './TextTrack';
|
||||||
import type { VideoRuntimeError } from './VideoError';
|
import type { VideoRuntimeError } from './VideoError';
|
||||||
import type { VideoOrientation } from './VideoOrientation';
|
import type { VideoOrientation } from './VideoOrientation';
|
||||||
@@ -70,6 +71,10 @@ export interface VideoPlayerEvents {
|
|||||||
* Called when the player progress changes.
|
* Called when the player progress changes.
|
||||||
*/
|
*/
|
||||||
onProgress: (data: onProgressData) => void;
|
onProgress: (data: onProgressData) => void;
|
||||||
|
/**
|
||||||
|
* Called when the player quality changes.
|
||||||
|
*/
|
||||||
|
onQualityChange: (quality: QualityLevel) => void;
|
||||||
/**
|
/**
|
||||||
* Called when the video is ready to display.
|
* Called when the video is ready to display.
|
||||||
*/
|
*/
|
||||||
@@ -273,6 +278,7 @@ export const ALL_PLAYER_EVENTS: (keyof AllPlayerEvents)[] =
|
|||||||
'onPlaybackStateChange',
|
'onPlaybackStateChange',
|
||||||
'onPlaybackRateChange',
|
'onPlaybackRateChange',
|
||||||
'onProgress',
|
'onProgress',
|
||||||
|
'onQualityChange',
|
||||||
'onReadyToDisplay',
|
'onReadyToDisplay',
|
||||||
'onSeek',
|
'onSeek',
|
||||||
'onTimedMetadata',
|
'onTimedMetadata',
|
||||||
|
|||||||
26
packages/react-native-video/src/core/types/QualityLevel.ts
Normal file
26
packages/react-native-video/src/core/types/QualityLevel.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
export interface QualityLevel {
|
||||||
|
/**
|
||||||
|
* Unique identifier for the quality
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Width of the quality
|
||||||
|
*/
|
||||||
|
width: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Height of the quality
|
||||||
|
*/
|
||||||
|
height: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitrate of the quality
|
||||||
|
*/
|
||||||
|
bitrate: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this quality is currently selected
|
||||||
|
*/
|
||||||
|
selected: boolean;
|
||||||
|
}
|
||||||
@@ -20,8 +20,9 @@ import {
|
|||||||
} from "../types/VideoError";
|
} from "../types/VideoError";
|
||||||
import type { VideoPlayerStatus } from "../types/VideoPlayerStatus";
|
import type { VideoPlayerStatus } from "../types/VideoPlayerStatus";
|
||||||
import type { AudioTrack } from "../types/AudioTrack";
|
import type { AudioTrack } from "../types/AudioTrack";
|
||||||
import type { VideoJsTracks } from "../VideoPlayer.web";
|
import type { VideoJsTracks, VideoJsQualityArray } from "../VideoPlayer.web";
|
||||||
import type { VideoTrack } from "../types/VideoTrack";
|
import type { VideoTrack } from "../types/VideoTrack";
|
||||||
|
import type { QualityLevel } from "../types/QualityLevel";
|
||||||
|
|
||||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||||
|
|
||||||
@@ -84,6 +85,10 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
|
|
||||||
this._onVideoTrackChange = this._onVideoTrackChange.bind(this);
|
this._onVideoTrackChange = this._onVideoTrackChange.bind(this);
|
||||||
this.player.videoTracks().on("change", this._onVideoTrackChange);
|
this.player.videoTracks().on("change", this._onVideoTrackChange);
|
||||||
|
|
||||||
|
this._onQualityChange = this._onQualityChange.bind(this);
|
||||||
|
// @ts-expect-error this isn't typed
|
||||||
|
this.player.qualityLevels().on("change", this._onQualityChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@@ -112,6 +117,10 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
this.player.audioTracks().off("change", this._onAudioTrackChange);
|
this.player.audioTracks().off("change", this._onAudioTrackChange);
|
||||||
|
|
||||||
this.player.videoTracks().off("change", this._onVideoTrackChange);
|
this.player.videoTracks().off("change", this._onVideoTrackChange);
|
||||||
|
|
||||||
|
this._onQualityChange = this._onQualityChange.bind(this);
|
||||||
|
// @ts-expect-error this isn't typed
|
||||||
|
this.player.qualityLevels().off("change", this._onQualityChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTimeUpdate() {
|
_onTimeUpdate() {
|
||||||
@@ -259,6 +268,20 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
this.onVideoTrackChange(selected ?? null);
|
this.onVideoTrackChange(selected ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onQualityChange() {
|
||||||
|
// @ts-expect-error this isn't typed
|
||||||
|
const levels: VideoJsQualityArray = this.player.qualityLevels();
|
||||||
|
const quality = levels[levels.selectedIndex]!;
|
||||||
|
|
||||||
|
this.onQualityChange({
|
||||||
|
id: quality.id,
|
||||||
|
width: quality.width,
|
||||||
|
height: quality.height,
|
||||||
|
bitrate: quality.bitrate,
|
||||||
|
selected: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
NOOP = () => {};
|
NOOP = () => {};
|
||||||
|
|
||||||
onError: (error: VideoRuntimeError) => void = this.NOOP;
|
onError: (error: VideoRuntimeError) => void = this.NOOP;
|
||||||
@@ -284,4 +307,5 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
onVolumeChange: (data: onVolumeChangeData) => void = this.NOOP;
|
onVolumeChange: (data: onVolumeChangeData) => void = this.NOOP;
|
||||||
onStatusChange: (status: VideoPlayerStatus) => void = this.NOOP;
|
onStatusChange: (status: VideoPlayerStatus) => void = this.NOOP;
|
||||||
onVideoTrackChange: (track: VideoTrack | null) => void = this.NOOP;
|
onVideoTrackChange: (track: VideoTrack | null) => void = this.NOOP;
|
||||||
|
onQualityChange: (quality: QualityLevel) => void = this.NOOP;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ export type { IgnoreSilentSwitchMode } from "./core/types/IgnoreSilentSwitchMode
|
|||||||
export type { MixAudioMode } from "./core/types/MixAudioMode";
|
export type { MixAudioMode } from "./core/types/MixAudioMode";
|
||||||
export type { ResizeMode } from "./core/types/ResizeMode";
|
export type { ResizeMode } from "./core/types/ResizeMode";
|
||||||
export type { TextTrack } from "./core/types/TextTrack";
|
export type { TextTrack } from "./core/types/TextTrack";
|
||||||
|
export type { AudioTrack } from "./core/types/AudioTrack";
|
||||||
|
export type { VideoTrack } from "./core/types/VideoTrack";
|
||||||
|
export type { QualityLevel } from "./core/types/QualityLevel";
|
||||||
export type { VideoConfig, VideoSource } from "./core/types/VideoConfig";
|
export type { VideoConfig, VideoSource } from "./core/types/VideoConfig";
|
||||||
export type {
|
export type {
|
||||||
LibraryError,
|
LibraryError,
|
||||||
|
|||||||
Reference in New Issue
Block a user