mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-06 07:16:12 +00:00
Add video track handling on web
This commit is contained in:
@@ -17,6 +17,7 @@ import { createPlayer } from "./utils/playerFactory";
|
|||||||
import { createSource } from "./utils/sourceFactory";
|
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";
|
||||||
|
|
||||||
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||||
protected player: VideoPlayerImpl;
|
protected player: VideoPlayerImpl;
|
||||||
@@ -297,6 +298,16 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
|||||||
get selectedAudioTrack(): AudioTrack | undefined {
|
get selectedAudioTrack(): AudioTrack | undefined {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvailableVideoTracks(): VideoTrack[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
selectVideoTrack(_: VideoTrack | null): void {}
|
||||||
|
|
||||||
|
get selectedVideoTrack(): VideoTrack | undefined {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { VideoPlayer };
|
export { VideoPlayer };
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
|||||||
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
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";
|
||||||
|
|
||||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ export type VideoJsTextTracks = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VideoJsAudioTracks = {
|
export type VideoJsTracks = {
|
||||||
length: number;
|
length: number;
|
||||||
[i: number]: {
|
[i: number]: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -301,9 +302,11 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
|||||||
return this.getAvailableTextTracks().find((x) => x.selected);
|
return this.getAvailableTextTracks().find((x) => x.selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// audio tracks
|
||||||
|
|
||||||
getAvailableAudioTracks(): AudioTrack[] {
|
getAvailableAudioTracks(): AudioTrack[] {
|
||||||
// @ts-expect-error they define length & index properties via prototype
|
// @ts-expect-error they define length & index properties via prototype
|
||||||
const tracks: VideoJsAudioTracks = this.player.audioTracks();
|
const tracks: VideoJsTracks = this.player.audioTracks();
|
||||||
|
|
||||||
return [...Array(tracks.length)].map((_, i) => ({
|
return [...Array(tracks.length)].map((_, i) => ({
|
||||||
id: tracks[i]!.id,
|
id: tracks[i]!.id,
|
||||||
@@ -315,7 +318,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
|||||||
|
|
||||||
selectAudioTrack(track: AudioTrack | null): void {
|
selectAudioTrack(track: AudioTrack | null): void {
|
||||||
// @ts-expect-error they define length & index properties via prototype
|
// @ts-expect-error they define length & index properties via prototype
|
||||||
const tracks: VideoJsAudioTracks = this.player.audioTracks();
|
const tracks: VideoJsTracks = this.player.audioTracks();
|
||||||
|
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
tracks[i]!.enabled = tracks[i]!.id === track?.id;
|
tracks[i]!.enabled = tracks[i]!.id === track?.id;
|
||||||
@@ -325,6 +328,33 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
|||||||
get selectedAudioTrack(): AudioTrack | undefined {
|
get selectedAudioTrack(): AudioTrack | undefined {
|
||||||
return this.getAvailableAudioTracks().find((x) => x.selected);
|
return this.getAvailableAudioTracks().find((x) => x.selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// video tracks
|
||||||
|
|
||||||
|
getAvailableVideoTracks(): VideoTrack[] {
|
||||||
|
// @ts-expect-error they define length & index properties via prototype
|
||||||
|
const tracks: VideoJsTracks = this.player.videoTracks();
|
||||||
|
|
||||||
|
return [...Array(tracks.length)].map((_, i) => ({
|
||||||
|
id: tracks[i]!.id,
|
||||||
|
label: tracks[i]!.label,
|
||||||
|
language: tracks[i]!.language,
|
||||||
|
selected: tracks[i]!.enabled,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectVideoTrack(track: VideoTrack | null): void {
|
||||||
|
// @ts-expect-error they define length & index properties via prototype
|
||||||
|
const tracks: VideoJsTracks = this.player.videoTracks();
|
||||||
|
|
||||||
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
|
tracks[i]!.enabled = tracks[i]!.id === track?.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedVideoTrack(): VideoTrack | undefined {
|
||||||
|
return this.getAvailableVideoTracks().find((x) => x.selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { VideoPlayer };
|
export { VideoPlayer };
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { VideoRuntimeError } from './VideoError';
|
|||||||
import type { VideoOrientation } from './VideoOrientation';
|
import type { VideoOrientation } from './VideoOrientation';
|
||||||
import type { VideoPlayerSourceBase } from './VideoPlayerSourceBase';
|
import type { VideoPlayerSourceBase } from './VideoPlayerSourceBase';
|
||||||
import type { VideoPlayerStatus } from './VideoPlayerStatus';
|
import type { VideoPlayerStatus } from './VideoPlayerStatus';
|
||||||
|
import type { VideoTrack } from './VideoTrack';
|
||||||
|
|
||||||
export interface VideoPlayerEvents {
|
export interface VideoPlayerEvents {
|
||||||
/**
|
/**
|
||||||
@@ -101,6 +102,11 @@ export interface VideoPlayerEvents {
|
|||||||
* Called when the player status changes.
|
* Called when the player status changes.
|
||||||
*/
|
*/
|
||||||
onStatusChange: (status: VideoPlayerStatus) => void;
|
onStatusChange: (status: VideoPlayerStatus) => void;
|
||||||
|
/**
|
||||||
|
* Called when the video track changes
|
||||||
|
* @param track The new video track
|
||||||
|
*/
|
||||||
|
onVideoTrackChange: (track: VideoTrack | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AllPlayerEvents extends VideoPlayerEvents {
|
export interface AllPlayerEvents extends VideoPlayerEvents {
|
||||||
@@ -273,5 +279,6 @@ export const ALL_PLAYER_EVENTS: (keyof AllPlayerEvents)[] =
|
|||||||
'onTextTrackDataChanged',
|
'onTextTrackDataChanged',
|
||||||
'onTrackChange',
|
'onTrackChange',
|
||||||
'onVolumeChange',
|
'onVolumeChange',
|
||||||
|
'onVideoTrackChange',
|
||||||
'onStatusChange'
|
'onStatusChange'
|
||||||
);
|
);
|
||||||
|
|||||||
22
packages/react-native-video/src/core/types/VideoTrack.ts
Normal file
22
packages/react-native-video/src/core/types/VideoTrack.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export interface VideoTrack {
|
||||||
|
/**
|
||||||
|
* Unique identifier for the video track
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display label for the video track
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Language code (ISO 639-1 or ISO 639-2)
|
||||||
|
* @example "en", "es", "fr"
|
||||||
|
*/
|
||||||
|
language?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this track is currently selected
|
||||||
|
*/
|
||||||
|
selected: boolean;
|
||||||
|
}
|
||||||
@@ -20,7 +20,8 @@ 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 { VideoJsAudioTracks } from "../VideoPlayer.web";
|
import type { VideoJsTracks } from "../VideoPlayer.web";
|
||||||
|
import type { VideoTrack } from "../types/VideoTrack";
|
||||||
|
|
||||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||||
|
|
||||||
@@ -80,6 +81,9 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
|
|
||||||
this._onAudioTrackChange = this._onAudioTrackChange.bind(this);
|
this._onAudioTrackChange = this._onAudioTrackChange.bind(this);
|
||||||
this.player.audioTracks().on("change", this._onAudioTrackChange);
|
this.player.audioTracks().on("change", this._onAudioTrackChange);
|
||||||
|
|
||||||
|
this._onVideoTrackChange = this._onVideoTrackChange.bind(this);
|
||||||
|
this.player.videoTracks().on("change", this._onVideoTrackChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@@ -106,6 +110,8 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
this.player.off("error", this._onError);
|
this.player.off("error", this._onError);
|
||||||
|
|
||||||
this.player.audioTracks().off("change", this._onAudioTrackChange);
|
this.player.audioTracks().off("change", this._onAudioTrackChange);
|
||||||
|
|
||||||
|
this.player.videoTracks().off("change", this._onVideoTrackChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTimeUpdate() {
|
_onTimeUpdate() {
|
||||||
@@ -225,7 +231,7 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
|
|
||||||
_onAudioTrackChange() {
|
_onAudioTrackChange() {
|
||||||
// @ts-expect-error they define length & index properties via prototype
|
// @ts-expect-error they define length & index properties via prototype
|
||||||
const tracks: VideoJsAudioTracks = this.player.audioTracks();
|
const tracks: VideoJsTracks = this.player.audioTracks();
|
||||||
const selected = [...Array(tracks.length)]
|
const selected = [...Array(tracks.length)]
|
||||||
.map((_, i) => ({
|
.map((_, i) => ({
|
||||||
id: tracks[i]!.id,
|
id: tracks[i]!.id,
|
||||||
@@ -238,6 +244,21 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
this.onAudioTrackChange(selected ?? null);
|
this.onAudioTrackChange(selected ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onVideoTrackChange() {
|
||||||
|
// @ts-expect-error they define length & index properties via prototype
|
||||||
|
const tracks: VideoJsTracks = this.player.videoTracks();
|
||||||
|
const selected = [...Array(tracks.length)]
|
||||||
|
.map((_, i) => ({
|
||||||
|
id: tracks[i]!.id,
|
||||||
|
label: tracks[i]!.label,
|
||||||
|
language: tracks[i]!.language,
|
||||||
|
selected: tracks[i]!.enabled,
|
||||||
|
}))
|
||||||
|
.find((x) => x.selected);
|
||||||
|
|
||||||
|
this.onVideoTrackChange(selected ?? null);
|
||||||
|
}
|
||||||
|
|
||||||
NOOP = () => {};
|
NOOP = () => {};
|
||||||
|
|
||||||
onError: (error: VideoRuntimeError) => void = this.NOOP;
|
onError: (error: VideoRuntimeError) => void = this.NOOP;
|
||||||
@@ -262,4 +283,5 @@ export class WebEventEmiter implements PlayerEvents {
|
|||||||
onTrackChange: (track: TextTrack | null) => void = this.NOOP;
|
onTrackChange: (track: TextTrack | null) => void = this.NOOP;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user