mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-06 07:16:12 +00:00
Add audio track support
This commit is contained in:
@@ -1,21 +1,22 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { NitroModules } from 'react-native-nitro-modules';
|
||||
import type { VideoPlayer as VideoPlayerImpl } from '../spec/nitro/VideoPlayer.nitro';
|
||||
import type { VideoPlayerSource } from '../spec/nitro/VideoPlayerSource.nitro';
|
||||
import type { IgnoreSilentSwitchMode } from './types/IgnoreSilentSwitchMode';
|
||||
import type { MixAudioMode } from './types/MixAudioMode';
|
||||
import type { TextTrack } from './types/TextTrack';
|
||||
import type { NoAutocomplete } from './types/Utils';
|
||||
import type { VideoConfig, VideoSource } from './types/VideoConfig';
|
||||
import { Platform } from "react-native";
|
||||
import { NitroModules } from "react-native-nitro-modules";
|
||||
import type { VideoPlayer as VideoPlayerImpl } from "../spec/nitro/VideoPlayer.nitro";
|
||||
import type { VideoPlayerSource } from "../spec/nitro/VideoPlayerSource.nitro";
|
||||
import type { IgnoreSilentSwitchMode } from "./types/IgnoreSilentSwitchMode";
|
||||
import type { MixAudioMode } from "./types/MixAudioMode";
|
||||
import type { TextTrack } from "./types/TextTrack";
|
||||
import type { NoAutocomplete } from "./types/Utils";
|
||||
import type { VideoConfig, VideoSource } from "./types/VideoConfig";
|
||||
import {
|
||||
tryParseNativeVideoError,
|
||||
VideoRuntimeError,
|
||||
} from './types/VideoError';
|
||||
import type { VideoPlayerBase } from './types/VideoPlayerBase';
|
||||
import type { VideoPlayerStatus } from './types/VideoPlayerStatus';
|
||||
import { createPlayer } from './utils/playerFactory';
|
||||
import { createSource } from './utils/sourceFactory';
|
||||
import { VideoPlayerEvents } from './VideoPlayerEvents';
|
||||
} from "./types/VideoError";
|
||||
import type { VideoPlayerBase } from "./types/VideoPlayerBase";
|
||||
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
||||
import { createPlayer } from "./utils/playerFactory";
|
||||
import { createSource } from "./utils/sourceFactory";
|
||||
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
||||
import type { AudioTrack } from "./types/AudioTrack";
|
||||
|
||||
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
protected player: VideoPlayerImpl;
|
||||
@@ -57,7 +58,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
|
||||
if (
|
||||
parsedError instanceof VideoRuntimeError &&
|
||||
this.triggerEvent('onError', parsedError)
|
||||
this.triggerEvent("onError", parsedError)
|
||||
) {
|
||||
// We don't throw errors if onError is provided
|
||||
return;
|
||||
@@ -153,9 +154,9 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
}
|
||||
|
||||
set ignoreSilentSwitchMode(value: IgnoreSilentSwitchMode) {
|
||||
if (__DEV__ && !['ios'].includes(Platform.OS)) {
|
||||
if (__DEV__ && !["ios"].includes(Platform.OS)) {
|
||||
console.warn(
|
||||
'ignoreSilentSwitchMode is not supported on this platform, it wont have any effect'
|
||||
"ignoreSilentSwitchMode is not supported on this platform, it wont have any effect",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,12 +249,16 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
}
|
||||
|
||||
async replaceSourceAsync(
|
||||
source: VideoSource | VideoConfig | NoAutocomplete<VideoPlayerSource> | null
|
||||
source:
|
||||
| VideoSource
|
||||
| VideoConfig
|
||||
| NoAutocomplete<VideoPlayerSource>
|
||||
| null,
|
||||
): Promise<void> {
|
||||
await this.wrapPromise(
|
||||
this.player.replaceSourceAsync(
|
||||
source === null ? null : createSource(source)
|
||||
)
|
||||
source === null ? null : createSource(source),
|
||||
),
|
||||
);
|
||||
|
||||
NitroModules.updateMemorySize(this.player);
|
||||
@@ -281,6 +286,17 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
get selectedTrack(): TextTrack | undefined {
|
||||
return this.player.selectedTrack;
|
||||
}
|
||||
|
||||
// TODO: implement this
|
||||
getAvailableAudioTracks(): AudioTrack[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
selectAudioTrack(_: AudioTrack | null): void {}
|
||||
|
||||
get selectedAudioTrack(): AudioTrack | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export { VideoPlayer };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import videojs from "video.js";
|
||||
import type { VideoPlayerSource } from "../spec/nitro/VideoPlayerSource.nitro";
|
||||
import type { AudioTrack } from "./types/AudioTrack";
|
||||
import type { IgnoreSilentSwitchMode } from "./types/IgnoreSilentSwitchMode";
|
||||
import type { MixAudioMode } from "./types/MixAudioMode";
|
||||
import type { TextTrack } from "./types/TextTrack";
|
||||
@@ -10,16 +11,16 @@ import type {
|
||||
VideoSource,
|
||||
} from "./types/VideoConfig";
|
||||
import type { VideoPlayerBase } from "./types/VideoPlayerBase";
|
||||
import type { VideoPlayerSourceBase } from "./types/VideoPlayerSourceBase";
|
||||
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
||||
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
||||
import { MediaSessionHandler } from "./web/MediaSession";
|
||||
import { WebEventEmiter } from "./web/WebEventEmiter";
|
||||
import type { VideoPlayerSourceBase } from "./types/VideoPlayerSourceBase";
|
||||
|
||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||
|
||||
// declared https://github.com/videojs/video.js/blob/main/src/js/tracks/track-list.js#L58
|
||||
type VideoJsTextTracks = {
|
||||
export type VideoJsTextTracks = {
|
||||
length: number;
|
||||
[i: number]: {
|
||||
// declared: https://github.com/videojs/video.js/blob/main/src/js/tracks/track.js
|
||||
@@ -32,6 +33,16 @@ type VideoJsTextTracks = {
|
||||
};
|
||||
};
|
||||
|
||||
export type VideoJsAudioTracks = {
|
||||
length: number;
|
||||
[i: number]: {
|
||||
id: string;
|
||||
label: string;
|
||||
language: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
protected video: HTMLVideoElement;
|
||||
public player: VideoJsPlayer;
|
||||
@@ -289,6 +300,31 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
get selectedTrack(): TextTrack | undefined {
|
||||
return this.getAvailableTextTracks().find((x) => x.selected);
|
||||
}
|
||||
|
||||
getAvailableAudioTracks(): AudioTrack[] {
|
||||
// @ts-expect-error they define length & index properties via prototype
|
||||
const tracks: VideoJsAudioTracks = this.player.audioTracks();
|
||||
|
||||
return [...Array(tracks.length)].map((_, i) => ({
|
||||
id: tracks[i]!.id,
|
||||
label: tracks[i]!.label,
|
||||
language: tracks[i]!.language,
|
||||
selected: tracks[i]!.enabled,
|
||||
}));
|
||||
}
|
||||
|
||||
selectAudioTrack(track: AudioTrack | null): void {
|
||||
// @ts-expect-error they define length & index properties via prototype
|
||||
const tracks: VideoJsAudioTracks = this.player.audioTracks();
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
tracks[i]!.enabled = tracks[i]!.id === track?.id;
|
||||
}
|
||||
}
|
||||
|
||||
get selectedAudioTrack(): AudioTrack | undefined {
|
||||
return this.getAvailableAudioTracks().find((x) => x.selected);
|
||||
}
|
||||
}
|
||||
|
||||
export { VideoPlayer };
|
||||
|
||||
22
packages/react-native-video/src/core/types/AudioTrack.ts
Normal file
22
packages/react-native-video/src/core/types/AudioTrack.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export interface AudioTrack {
|
||||
/**
|
||||
* Unique identifier for the audio track
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Display label for the audio 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;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AudioTrack } from './AudioTrack';
|
||||
import type { TextTrack } from './TextTrack';
|
||||
import type { VideoRuntimeError } from './VideoError';
|
||||
import type { VideoOrientation } from './VideoOrientation';
|
||||
@@ -16,6 +17,11 @@ export interface VideoPlayerEvents {
|
||||
* @platform Android
|
||||
*/
|
||||
onAudioFocusChange: (hasAudioFocus: boolean) => void;
|
||||
/**
|
||||
* Called when the audio track changes
|
||||
* @param track The new audio track
|
||||
*/
|
||||
onAudioTrackChange: (track: AudioTrack | null) => void;
|
||||
/**
|
||||
* Called when the bandwidth of the video changes.
|
||||
*/
|
||||
@@ -249,6 +255,7 @@ export const ALL_PLAYER_EVENTS: (keyof AllPlayerEvents)[] =
|
||||
allKeysOf<AllPlayerEvents>()(
|
||||
'onAudioBecomingNoisy',
|
||||
'onAudioFocusChange',
|
||||
'onAudioTrackChange',
|
||||
'onBandwidthUpdate',
|
||||
'onBuffer',
|
||||
'onControlsVisibleChange',
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
type VideoRuntimeError,
|
||||
} from "../types/VideoError";
|
||||
import type { VideoPlayerStatus } from "../types/VideoPlayerStatus";
|
||||
import type { AudioTrack } from "../types/AudioTrack";
|
||||
import type { VideoJsAudioTracks } from "../VideoPlayer.web";
|
||||
|
||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||
|
||||
@@ -75,6 +77,9 @@ export class WebEventEmiter implements PlayerEvents {
|
||||
// on status change
|
||||
this._onError = this._onError.bind(this);
|
||||
this.player.on("error", this._onError);
|
||||
|
||||
this._onAudioTrackChange = this._onAudioTrackChange.bind(this);
|
||||
this.player.audioTracks().on("change", this._onAudioTrackChange);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -99,6 +104,8 @@ export class WebEventEmiter implements PlayerEvents {
|
||||
this.player.off("volumechange", this._onVolumeChange);
|
||||
|
||||
this.player.off("error", this._onError);
|
||||
|
||||
this.player.audioTracks().off("change", this._onAudioTrackChange);
|
||||
}
|
||||
|
||||
_onTimeUpdate() {
|
||||
@@ -216,11 +223,27 @@ export class WebEventEmiter implements PlayerEvents {
|
||||
this.onError(new VideoError(codeMap[err.code]!, err.message));
|
||||
}
|
||||
|
||||
_onAudioTrackChange() {
|
||||
// @ts-expect-error they define length & index properties via prototype
|
||||
const tracks: VideoJsAudioTracks = this.player.audioTracks();
|
||||
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.onAudioTrackChange(selected ?? null);
|
||||
}
|
||||
|
||||
NOOP = () => {};
|
||||
|
||||
onError: (error: VideoRuntimeError) => void = this.NOOP;
|
||||
onAudioBecomingNoisy: () => void = this.NOOP;
|
||||
onAudioFocusChange: (hasAudioFocus: boolean) => void = this.NOOP;
|
||||
onAudioTrackChange: (track: AudioTrack | null) => void = this.NOOP;
|
||||
onBandwidthUpdate: (data: BandwidthData) => void = this.NOOP;
|
||||
onBuffer: (buffering: boolean) => void = this.NOOP;
|
||||
onControlsVisibleChange: (visible: boolean) => void = this.NOOP;
|
||||
|
||||
Reference in New Issue
Block a user