mirror of
https://github.com/zoriya/react-native-video.git
synced 2026-05-14 03:56:39 +00:00
fix: lint errors, add missing VideoPlayerBase members, fix eslint for web files
This commit is contained in:
@@ -3,6 +3,6 @@ module.exports = {
|
||||
extends: ["../../config/.eslintrc.js"],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: true,
|
||||
project: ['./tsconfig.json', './tsconfig.web.json'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -326,7 +326,6 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
get selectedTrack(): TextTrack | undefined {
|
||||
return this.player.selectedTrack;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { VideoPlayer };
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import type { AudioTrack } from "./types/AudioTrack";
|
||||
import type { IgnoreSilentSwitchMode } from "./types/IgnoreSilentSwitchMode";
|
||||
import type { MixAudioMode } from "./types/MixAudioMode";
|
||||
import type { TextTrack } from "./types/TextTrack";
|
||||
import type { VideoTrack } from "./types/VideoTrack";
|
||||
import type { NoAutocomplete } from "./types/Utils";
|
||||
import type { AudioTrack } from './types/AudioTrack';
|
||||
import type { IgnoreSilentSwitchMode } from './types/IgnoreSilentSwitchMode';
|
||||
import type { MixAudioMode } from './types/MixAudioMode';
|
||||
import type { TextTrack } from './types/TextTrack';
|
||||
import type { VideoTrack } from './types/VideoTrack';
|
||||
import type { NoAutocomplete } from './types/Utils';
|
||||
import type {
|
||||
NativeVideoConfig,
|
||||
VideoConfig,
|
||||
VideoSource,
|
||||
} from "./types/VideoConfig";
|
||||
import type { WebVideoPlayer } from "./types/WebVideoPlayer";
|
||||
import type { VideoPlayerSourceBase } from "./types/VideoPlayerSourceBase";
|
||||
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
||||
import { VideoPlayerEvents } from "./events/VideoPlayerEvents";
|
||||
import { MediaSessionHandler } from "./web/MediaSession";
|
||||
import { WebEventEmitter } from "./web/WebEventEmitter";
|
||||
import type { VideoStore } from "./web/VideoStore";
|
||||
} from './types/VideoConfig';
|
||||
import type { WebVideoPlayer } from './types/WebVideoPlayer';
|
||||
import type { VideoPlayerSourceBase } from './types/VideoPlayerSourceBase';
|
||||
import type { VideoPlayerStatus } from './types/VideoPlayerStatus';
|
||||
import { VideoPlayerEvents } from './events/VideoPlayerEvents';
|
||||
import { MediaSessionHandler } from './web/MediaSession';
|
||||
import { WebEventEmitter } from './web/WebEventEmitter';
|
||||
import type { VideoStore } from './web/VideoStore';
|
||||
|
||||
function setExternalSubtitles(
|
||||
video: HTMLVideoElement,
|
||||
subtitles: NativeVideoConfig["externalSubtitles"],
|
||||
subtitles: NativeVideoConfig['externalSubtitles']
|
||||
) {
|
||||
video.querySelectorAll("track").forEach((t) => t.remove());
|
||||
video.querySelectorAll('track').forEach((t) => t.remove());
|
||||
for (const sub of subtitles ?? []) {
|
||||
const track = document.createElement("track");
|
||||
track.kind = "subtitles";
|
||||
const track = document.createElement('track');
|
||||
track.kind = 'subtitles';
|
||||
track.src = sub.uri;
|
||||
track.srclang = sub.language ?? "und";
|
||||
track.srclang = sub.language ?? 'und';
|
||||
track.label = sub.label;
|
||||
video.appendChild(track);
|
||||
}
|
||||
}
|
||||
|
||||
type TrackType = "textTracks" | "audioTracks" | "videoTracks";
|
||||
type TrackType = 'textTracks' | 'audioTracks' | 'videoTracks';
|
||||
|
||||
/**
|
||||
* Reads tracks from HTMLVideoElement.
|
||||
@@ -41,17 +41,25 @@ type TrackType = "textTracks" | "audioTracks" | "videoTracks";
|
||||
*/
|
||||
function getTracks(
|
||||
video: HTMLVideoElement,
|
||||
prop: TrackType,
|
||||
prop: TrackType
|
||||
): Array<{ id: string; label: string; language?: string; selected: boolean }> {
|
||||
const tracks = (video as any)[prop];
|
||||
if (!tracks) return [];
|
||||
const result = [];
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const t = tracks[i]!;
|
||||
const selected = prop === "textTracks"
|
||||
? t.mode === "showing"
|
||||
: prop === "audioTracks" ? t.enabled : t.selected;
|
||||
result.push({ id: t.id || t.label, label: t.label, language: t.language, selected });
|
||||
const selected =
|
||||
prop === 'textTracks'
|
||||
? t.mode === 'showing'
|
||||
: prop === 'audioTracks'
|
||||
? t.enabled
|
||||
: t.selected;
|
||||
result.push({
|
||||
id: t.id || t.label,
|
||||
label: t.label,
|
||||
language: t.language,
|
||||
selected,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -59,15 +67,15 @@ function getTracks(
|
||||
function selectTrack(
|
||||
video: HTMLVideoElement,
|
||||
prop: TrackType,
|
||||
trackId: string | null,
|
||||
trackId: string | null
|
||||
): void {
|
||||
const tracks = (video as any)[prop];
|
||||
if (!tracks) return;
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const id = tracks[i]!.id || tracks[i]!.label;
|
||||
if (prop === "textTracks") {
|
||||
tracks[i]!.mode = id === trackId ? "showing" : "disabled";
|
||||
} else if (prop === "audioTracks") {
|
||||
if (prop === 'textTracks') {
|
||||
tracks[i]!.mode = id === trackId ? 'showing' : 'disabled';
|
||||
} else if (prop === 'audioTracks') {
|
||||
tracks[i]!.enabled = id === trackId;
|
||||
} else {
|
||||
tracks[i]!.selected = id === trackId;
|
||||
@@ -97,18 +105,20 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
* VideoView later mounts it into the DOM and connects the video.js store.
|
||||
*/
|
||||
constructor(source: VideoSource | VideoConfig | VideoPlayerSourceBase) {
|
||||
if (typeof window === "undefined") {
|
||||
throw new Error("[react-native-video] VideoPlayer cannot be created in SSR environment.");
|
||||
if (typeof window === 'undefined') {
|
||||
throw new Error(
|
||||
'[react-native-video] VideoPlayer cannot be created in SSR environment.'
|
||||
);
|
||||
}
|
||||
|
||||
const video = document.createElement("video");
|
||||
const video = document.createElement('video');
|
||||
video.playsInline = true;
|
||||
|
||||
super(new WebEventEmitter(null, () => video));
|
||||
this.video = video;
|
||||
|
||||
(this.eventEmitter as WebEventEmitter).addOnErrorListener((error) => {
|
||||
this.triggerJSEvent("onError", error);
|
||||
this.triggerJSEvent('onError', error);
|
||||
});
|
||||
|
||||
this.replaceSourceAsync(source);
|
||||
@@ -145,8 +155,8 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
|
||||
get source(): VideoPlayerSourceBase {
|
||||
return {
|
||||
uri: this._source?.uri ?? "",
|
||||
config: this._source ?? { uri: "" },
|
||||
uri: this._source?.uri ?? '',
|
||||
config: this._source ?? { uri: '' },
|
||||
getAssetInformationAsync: async () => ({
|
||||
bitrate: NaN,
|
||||
width: this.video.videoWidth,
|
||||
@@ -155,32 +165,49 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
fileSize: -1n,
|
||||
isHDR: false,
|
||||
isLive: false,
|
||||
orientation: "landscape" as const,
|
||||
orientation: 'landscape' as const,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
get status(): VideoPlayerStatus {
|
||||
if (this.media.error) return "error";
|
||||
if (this.video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) return "readyToPlay";
|
||||
if (this.video.readyState > HTMLMediaElement.HAVE_NOTHING) return "loading";
|
||||
if (this.video.src) return "loading";
|
||||
return "idle";
|
||||
if (this.media.error) return 'error';
|
||||
if (this.video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA)
|
||||
return 'readyToPlay';
|
||||
if (this.video.readyState > HTMLMediaElement.HAVE_NOTHING) return 'loading';
|
||||
if (this.video.src) return 'loading';
|
||||
return 'idle';
|
||||
}
|
||||
|
||||
get duration(): number { return this.media.duration || NaN; }
|
||||
get currentTime(): number { return this.media.currentTime; }
|
||||
get volume(): number { return this.media.volume; }
|
||||
get muted(): boolean { return this.media.muted; }
|
||||
get loop(): boolean { return this.video.loop; }
|
||||
get rate(): number { return this.media.playbackRate; }
|
||||
get isPlaying(): boolean { return !this.media.paused; }
|
||||
get duration(): number {
|
||||
return this.media.duration || NaN;
|
||||
}
|
||||
get currentTime(): number {
|
||||
return this.media.currentTime;
|
||||
}
|
||||
get volume(): number {
|
||||
return this.media.volume;
|
||||
}
|
||||
get muted(): boolean {
|
||||
return this.media.muted;
|
||||
}
|
||||
get loop(): boolean {
|
||||
return this.video.loop;
|
||||
}
|
||||
get rate(): number {
|
||||
return this.media.playbackRate;
|
||||
}
|
||||
get isPlaying(): boolean {
|
||||
return !this.media.paused;
|
||||
}
|
||||
|
||||
// --- Playback state (write through store or video element) ---
|
||||
|
||||
set volume(v: number) {
|
||||
const clamped = Math.max(0, Math.min(1, v));
|
||||
this._store ? this._store.setVolume(clamped) : (this.video.volume = clamped);
|
||||
this._store
|
||||
? this._store.setVolume(clamped)
|
||||
: (this.video.volume = clamped);
|
||||
}
|
||||
|
||||
set currentTime(v: number) {
|
||||
@@ -188,22 +215,36 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
}
|
||||
|
||||
// video.js store has toggleMuted() but no direct setter
|
||||
set muted(v: boolean) { this.video.muted = v; }
|
||||
set loop(v: boolean) { this.video.loop = v; }
|
||||
set muted(v: boolean) {
|
||||
this.video.muted = v;
|
||||
}
|
||||
set loop(v: boolean) {
|
||||
this.video.loop = v;
|
||||
}
|
||||
|
||||
set rate(v: number) {
|
||||
this._store ? this._store.setPlaybackRate(v) : (this.video.playbackRate = v);
|
||||
this._store
|
||||
? this._store.setPlaybackRate(v)
|
||||
: (this.video.playbackRate = v);
|
||||
}
|
||||
|
||||
// --- Unsupported on web (no-op) ---
|
||||
|
||||
get mixAudioMode(): MixAudioMode { return "auto"; }
|
||||
get mixAudioMode(): MixAudioMode {
|
||||
return 'auto';
|
||||
}
|
||||
set mixAudioMode(_: MixAudioMode) {}
|
||||
get ignoreSilentSwitchMode(): IgnoreSilentSwitchMode { return "auto"; }
|
||||
get ignoreSilentSwitchMode(): IgnoreSilentSwitchMode {
|
||||
return 'auto';
|
||||
}
|
||||
set ignoreSilentSwitchMode(_: IgnoreSilentSwitchMode) {}
|
||||
get playInBackground(): boolean { return true; }
|
||||
get playInBackground(): boolean {
|
||||
return true;
|
||||
}
|
||||
set playInBackground(_: boolean) {}
|
||||
get playWhenInactive(): boolean { return true; }
|
||||
get playWhenInactive(): boolean {
|
||||
return true;
|
||||
}
|
||||
set playWhenInactive(_: boolean) {}
|
||||
|
||||
// --- Media Session ---
|
||||
@@ -214,7 +255,10 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
|
||||
set showNotificationControls(value: boolean) {
|
||||
if (!this.mediaSession) return;
|
||||
if (!value) { this.mediaSession.disable(); return; }
|
||||
if (!value) {
|
||||
this.mediaSession.disable();
|
||||
return;
|
||||
}
|
||||
this.mediaSession.enable();
|
||||
this.mediaSession.updateMediaSession(this._source?.metadata);
|
||||
}
|
||||
@@ -224,13 +268,19 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
async initialize(): Promise<void> {}
|
||||
|
||||
async preload(): Promise<void> {
|
||||
this.video.preload = "auto";
|
||||
this.video.preload = 'auto';
|
||||
this.video.load();
|
||||
}
|
||||
|
||||
release(): void { this.__destroy(); }
|
||||
play(): void { this.media.play()?.catch(() => {}); }
|
||||
pause(): void { this.media.pause(); }
|
||||
release(): void {
|
||||
this.__destroy();
|
||||
}
|
||||
play(): void {
|
||||
this.media.play()?.catch(() => {});
|
||||
}
|
||||
pause(): void {
|
||||
this.media.pause();
|
||||
}
|
||||
|
||||
seekTo(time: number): void {
|
||||
this._store ? this._store.seek(time) : (this.video.currentTime = time);
|
||||
@@ -247,26 +297,30 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
| VideoSource
|
||||
| VideoConfig
|
||||
| NoAutocomplete<VideoPlayerSourceBase>
|
||||
| null,
|
||||
| null
|
||||
): Promise<void> {
|
||||
if (!source) {
|
||||
this.video.removeAttribute("src");
|
||||
this.video.removeAttribute('src');
|
||||
this.video.load();
|
||||
this._source = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof source === "string") {
|
||||
if (typeof source === 'string') {
|
||||
source = { uri: source };
|
||||
}
|
||||
|
||||
if (typeof source === "number" || typeof source.uri === "number") {
|
||||
console.error("A source uri must be a string. Numbers are only supported on native.");
|
||||
if (typeof source === 'number' || typeof source.uri === 'number') {
|
||||
console.error(
|
||||
'A source uri must be a string. Numbers are only supported on native.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._source = source as NativeVideoConfig;
|
||||
this._store ? this._store.loadSource(source.uri) : (this.video.src = source.uri);
|
||||
this._store
|
||||
? this._store.loadSource(source.uri)
|
||||
: (this.video.src = source.uri);
|
||||
|
||||
if (this.mediaSession?.enabled) {
|
||||
this.mediaSession.updateMediaSession(source.metadata);
|
||||
@@ -279,18 +333,36 @@ class VideoPlayer extends VideoPlayerEvents implements WebVideoPlayer {
|
||||
|
||||
// --- Tracks ---
|
||||
|
||||
getAvailableTextTracks(): TextTrack[] { return getTracks(this.video, "textTracks"); }
|
||||
selectTextTrack(t: TextTrack | null): void { selectTrack(this.video, "textTracks", t?.id ?? null); }
|
||||
get selectedTrack(): TextTrack | undefined { return this.getAvailableTextTracks().find((x) => x.selected); }
|
||||
getAvailableTextTracks(): TextTrack[] {
|
||||
return getTracks(this.video, 'textTracks');
|
||||
}
|
||||
selectTextTrack(t: TextTrack | null): void {
|
||||
selectTrack(this.video, 'textTracks', t?.id ?? null);
|
||||
}
|
||||
get selectedTrack(): TextTrack | undefined {
|
||||
return this.getAvailableTextTracks().find((x) => x.selected);
|
||||
}
|
||||
|
||||
// Audio/video tracks: web-only, ~16% browser support (Safari only, flags in Chrome/Firefox)
|
||||
getAvailableAudioTracks(): AudioTrack[] { return getTracks(this.video, "audioTracks"); }
|
||||
selectAudioTrack(t: AudioTrack | null): void { selectTrack(this.video, "audioTracks", t?.id ?? null); }
|
||||
get selectedAudioTrack(): AudioTrack | undefined { return this.getAvailableAudioTracks().find((x) => x.selected); }
|
||||
getAvailableAudioTracks(): AudioTrack[] {
|
||||
return getTracks(this.video, 'audioTracks');
|
||||
}
|
||||
selectAudioTrack(t: AudioTrack | null): void {
|
||||
selectTrack(this.video, 'audioTracks', t?.id ?? null);
|
||||
}
|
||||
get selectedAudioTrack(): AudioTrack | undefined {
|
||||
return this.getAvailableAudioTracks().find((x) => x.selected);
|
||||
}
|
||||
|
||||
getAvailableVideoTracks(): VideoTrack[] { return getTracks(this.video, "videoTracks"); }
|
||||
selectVideoTrack(t: VideoTrack | null): void { selectTrack(this.video, "videoTracks", t?.id ?? null); }
|
||||
get selectedVideoTrack(): VideoTrack | undefined { return this.getAvailableVideoTracks().find((x) => x.selected); }
|
||||
getAvailableVideoTracks(): VideoTrack[] {
|
||||
return getTracks(this.video, 'videoTracks');
|
||||
}
|
||||
selectVideoTrack(t: VideoTrack | null): void {
|
||||
selectTrack(this.video, 'videoTracks', t?.id ?? null);
|
||||
}
|
||||
get selectedVideoTrack(): VideoTrack | undefined {
|
||||
return this.getAvailableVideoTracks().find((x) => x.selected);
|
||||
}
|
||||
}
|
||||
|
||||
export { VideoPlayer };
|
||||
|
||||
@@ -5,7 +5,7 @@ import { VideoPlayerEventsBase } from './VideoPlayerEventsBase';
|
||||
export class VideoPlayerEvents extends VideoPlayerEventsBase {
|
||||
addEventListener<Event extends keyof PlayerEvents>(
|
||||
event: Event,
|
||||
callback: PlayerEvents[Event],
|
||||
callback: PlayerEvents[Event]
|
||||
): ListenerSubscription {
|
||||
switch (event) {
|
||||
// ----------------- Native-only Events -----------------
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { AllPlayerEvents as PlayerEvents } from "../types/Events";
|
||||
import type { ListenerSubscription } from "../types/EventEmitter";
|
||||
import { VideoPlayerEventsBase } from "./VideoPlayerEventsBase";
|
||||
import type { AllPlayerEvents as PlayerEvents } from '../types/Events';
|
||||
import type { ListenerSubscription } from '../types/EventEmitter';
|
||||
import { VideoPlayerEventsBase } from './VideoPlayerEventsBase';
|
||||
|
||||
export class VideoPlayerEvents extends VideoPlayerEventsBase {
|
||||
addEventListener<Event extends keyof PlayerEvents>(
|
||||
event: Event,
|
||||
callback: PlayerEvents[Event],
|
||||
callback: PlayerEvents[Event]
|
||||
): ListenerSubscription {
|
||||
switch (event) {
|
||||
// Web-only events will be added here
|
||||
|
||||
@@ -24,50 +24,50 @@ export interface ListenerSubscription {
|
||||
export interface VideoPlayerEventEmitterBase {
|
||||
addOnAudioBecomingNoisyListener(listener: () => void): ListenerSubscription;
|
||||
addOnAudioFocusChangeListener(
|
||||
listener: (hasAudioFocus: boolean) => void,
|
||||
listener: (hasAudioFocus: boolean) => void
|
||||
): ListenerSubscription;
|
||||
addOnBandwidthUpdateListener(
|
||||
listener: (data: BandwidthData) => void,
|
||||
listener: (data: BandwidthData) => void
|
||||
): ListenerSubscription;
|
||||
addOnBufferListener(
|
||||
listener: (buffering: boolean) => void,
|
||||
listener: (buffering: boolean) => void
|
||||
): ListenerSubscription;
|
||||
addOnControlsVisibleChangeListener(
|
||||
listener: (visible: boolean) => void,
|
||||
listener: (visible: boolean) => void
|
||||
): ListenerSubscription;
|
||||
addOnEndListener(listener: () => void): ListenerSubscription;
|
||||
addOnExternalPlaybackChangeListener(
|
||||
listener: (externalPlaybackActive: boolean) => void,
|
||||
listener: (externalPlaybackActive: boolean) => void
|
||||
): ListenerSubscription;
|
||||
addOnLoadListener(listener: (data: onLoadData) => void): ListenerSubscription;
|
||||
addOnLoadStartListener(
|
||||
listener: (data: onLoadStartData) => void,
|
||||
listener: (data: onLoadStartData) => void
|
||||
): ListenerSubscription;
|
||||
addOnPlaybackStateChangeListener(
|
||||
listener: (data: onPlaybackStateChangeData) => void,
|
||||
listener: (data: onPlaybackStateChangeData) => void
|
||||
): ListenerSubscription;
|
||||
addOnPlaybackRateChangeListener(
|
||||
listener: (rate: number) => void,
|
||||
listener: (rate: number) => void
|
||||
): ListenerSubscription;
|
||||
addOnProgressListener(
|
||||
listener: (data: onProgressData) => void,
|
||||
listener: (data: onProgressData) => void
|
||||
): ListenerSubscription;
|
||||
addOnReadyToDisplayListener(listener: () => void): ListenerSubscription;
|
||||
addOnSeekListener(listener: (position: number) => void): ListenerSubscription;
|
||||
addOnStatusChangeListener(
|
||||
listener: (status: VideoPlayerStatus) => void,
|
||||
listener: (status: VideoPlayerStatus) => void
|
||||
): ListenerSubscription;
|
||||
addOnTimedMetadataListener(
|
||||
listener: (data: TimedMetadata) => void,
|
||||
listener: (data: TimedMetadata) => void
|
||||
): ListenerSubscription;
|
||||
addOnTextTrackDataChangedListener(
|
||||
listener: (data: string[]) => void,
|
||||
listener: (data: string[]) => void
|
||||
): ListenerSubscription;
|
||||
addOnTrackChangeListener(
|
||||
listener: (track: TextTrack | null) => void,
|
||||
listener: (track: TextTrack | null) => void
|
||||
): ListenerSubscription;
|
||||
addOnVolumeChangeListener(
|
||||
listener: (data: onVolumeChangeData) => void,
|
||||
listener: (data: onVolumeChangeData) => void
|
||||
): ListenerSubscription;
|
||||
clearAllListeners(): void;
|
||||
}
|
||||
|
||||
@@ -8,4 +8,3 @@ export const isVideoPlayerSource = (obj: any): obj is VideoPlayerSource => {
|
||||
obj.name === 'VideoPlayerSource' // obj.name is 'VideoPlayerSource'
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import type { ViewStyle } from 'react-native';
|
||||
import { NitroModules } from 'react-native-nitro-modules';
|
||||
import type { ListenerSubscription } from '../../spec/nitro/VideoPlayerEventEmitter.nitro';
|
||||
import type {
|
||||
|
||||
@@ -6,16 +6,21 @@ import {
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
type CSSProperties,
|
||||
} from "react";
|
||||
import { View } from "react-native";
|
||||
import type { VideoPlayer } from "../VideoPlayer.web";
|
||||
import type { VideoViewEvents } from "../types/Events";
|
||||
import type { ListenerSubscription } from "../types/EventEmitter";
|
||||
import type { VideoViewProps, VideoViewRef } from "./VideoViewProps";
|
||||
import { createPlayer, videoFeatures, usePlayerContext, useMediaAttach } from "@videojs/react";
|
||||
import { VideoSkin } from "@videojs/react/video";
|
||||
import "@videojs/react/video/skin.css";
|
||||
import type { VideoStore } from "../web/VideoStore";
|
||||
} from 'react';
|
||||
import { View } from 'react-native';
|
||||
import type { VideoPlayer } from '../VideoPlayer.web';
|
||||
import type { VideoViewEvents } from '../types/Events';
|
||||
import type { ListenerSubscription } from '../types/EventEmitter';
|
||||
import type { VideoViewProps, VideoViewRef } from './VideoViewProps';
|
||||
import {
|
||||
createPlayer,
|
||||
videoFeatures,
|
||||
usePlayerContext,
|
||||
useMediaAttach,
|
||||
} from '@videojs/react';
|
||||
import { VideoSkin } from '@videojs/react/video';
|
||||
import '@videojs/react/video/skin.css';
|
||||
import type { VideoStore } from '../web/VideoStore';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
|
||||
@@ -38,7 +43,11 @@ function PlayerBridge({ player }: { player: VideoPlayer }) {
|
||||
|
||||
return () => {
|
||||
player.__setStore(null);
|
||||
try { detach?.(); } catch { /* store may already be destroyed by Provider */ }
|
||||
try {
|
||||
detach?.();
|
||||
} catch {
|
||||
/* store may already be destroyed by Provider */
|
||||
}
|
||||
setMedia?.(null);
|
||||
};
|
||||
}, [store, player, setMedia, container]);
|
||||
@@ -51,20 +60,26 @@ function PlayerBridge({ player }: { player: VideoPlayer }) {
|
||||
* The element is created in VideoPlayer constructor so it already has
|
||||
* source and event listeners attached.
|
||||
*/
|
||||
function VideoElement({ player, objectFit }: { player: VideoPlayer; objectFit: string }) {
|
||||
function VideoElement({
|
||||
player,
|
||||
objectFit,
|
||||
}: {
|
||||
player: VideoPlayer;
|
||||
objectFit: string;
|
||||
}) {
|
||||
const mountRef = useCallback(
|
||||
(container: HTMLDivElement | null) => {
|
||||
if (!container) return;
|
||||
const video = player.__getMedia();
|
||||
Object.assign(video.style, { width: "100%", height: "100%", objectFit });
|
||||
Object.assign(video.style, { width: '100%', height: '100%', objectFit });
|
||||
if (video.parentNode !== container) {
|
||||
container.appendChild(video);
|
||||
}
|
||||
},
|
||||
[player, objectFit],
|
||||
[player, objectFit]
|
||||
);
|
||||
|
||||
return <div ref={mountRef} style={{ width: "100%", height: "100%" }} />;
|
||||
return <div ref={mountRef} style={{ width: '100%', height: '100%' }} />;
|
||||
}
|
||||
|
||||
const VideoView = forwardRef<VideoViewRef, VideoViewProps>(
|
||||
@@ -72,24 +87,25 @@ const VideoView = forwardRef<VideoViewRef, VideoViewProps>(
|
||||
{
|
||||
player: nPlayer,
|
||||
controls = false,
|
||||
resizeMode = "none",
|
||||
pictureInPicture = false,
|
||||
autoEnterPictureInPicture = false,
|
||||
keepScreenAwake = true,
|
||||
resizeMode = 'none',
|
||||
// Destructured to exclude from ...props (not used on web)
|
||||
pictureInPicture: _pip = false,
|
||||
autoEnterPictureInPicture: _autoPip = false,
|
||||
keepScreenAwake: _keepAwake = true,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
ref
|
||||
) => {
|
||||
const player = nPlayer as unknown as VideoPlayer;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const objectFitMap: Record<string, CSSProperties["objectFit"]> = {
|
||||
contain: "contain",
|
||||
cover: "cover",
|
||||
stretch: "fill",
|
||||
none: "contain",
|
||||
const objectFitMap: Record<string, CSSProperties['objectFit']> = {
|
||||
contain: 'contain',
|
||||
cover: 'cover',
|
||||
stretch: 'fill',
|
||||
none: 'contain',
|
||||
};
|
||||
const objectFit = objectFitMap[resizeMode] ?? "contain";
|
||||
const objectFit = objectFitMap[resizeMode] ?? 'contain';
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
@@ -110,12 +126,12 @@ const VideoView = forwardRef<VideoViewRef, VideoViewProps>(
|
||||
document.pictureInPictureEnabled ?? false,
|
||||
addEventListener: <Event extends keyof VideoViewEvents>(
|
||||
_event: Event,
|
||||
_callback: VideoViewEvents[Event],
|
||||
_callback: VideoViewEvents[Event]
|
||||
): ListenerSubscription => {
|
||||
return { remove: () => {} };
|
||||
},
|
||||
}),
|
||||
[player],
|
||||
[player]
|
||||
);
|
||||
|
||||
const videoContent = <VideoElement player={player} objectFit={objectFit} />;
|
||||
@@ -126,22 +142,24 @@ const VideoView = forwardRef<VideoViewRef, VideoViewProps>(
|
||||
<PlayerBridge player={player} />
|
||||
<Player.Container
|
||||
ref={containerRef}
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
"--media-border-radius": "0",
|
||||
} as CSSProperties}
|
||||
style={
|
||||
{
|
||||
'position': 'absolute',
|
||||
'inset': '0',
|
||||
'width': '100%',
|
||||
'height': '100%',
|
||||
'--media-border-radius': '0',
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
{controls ? <VideoSkin>{videoContent}</VideoSkin> : videoContent}
|
||||
</Player.Container>
|
||||
</Player.Provider>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
VideoView.displayName = "VideoView";
|
||||
VideoView.displayName = 'VideoView';
|
||||
|
||||
export default memo(VideoView);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { CustomVideoMetadata } from "../types/VideoConfig";
|
||||
import type { MediaSessionStore } from "./VideoStore";
|
||||
import type { CustomVideoMetadata } from '../types/VideoConfig';
|
||||
import type { MediaSessionStore } from './VideoStore';
|
||||
|
||||
function getMediaSession(): MediaSession | undefined {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
if (typeof window === 'undefined') return undefined;
|
||||
return window.navigator?.mediaSession;
|
||||
}
|
||||
|
||||
@@ -20,25 +20,55 @@ export class MediaSessionHandler {
|
||||
|
||||
const defaultSkipTime = 15;
|
||||
|
||||
const actionHandlers: Array<[MediaSessionAction, MediaSessionActionHandler]> = [
|
||||
["play", () => { this.store.play(); }],
|
||||
["pause", () => { this.store.pause(); }],
|
||||
["stop", () => {
|
||||
this.store.pause();
|
||||
this.store.seek(0);
|
||||
}],
|
||||
["seekbackward", (details) => {
|
||||
const offset = (details as MediaSessionActionDetails).seekOffset || defaultSkipTime;
|
||||
this.store.seek(Math.max(0, this.store.currentTime - offset));
|
||||
}],
|
||||
["seekforward", (details) => {
|
||||
const offset = (details as MediaSessionActionDetails).seekOffset || defaultSkipTime;
|
||||
this.store.seek(Math.min(this.store.duration, this.store.currentTime + offset));
|
||||
}],
|
||||
["seekto", (details) => {
|
||||
const seekTime = (details as MediaSessionActionDetails).seekTime;
|
||||
if (seekTime != null) this.store.seek(seekTime);
|
||||
}],
|
||||
const actionHandlers: Array<
|
||||
[MediaSessionAction, MediaSessionActionHandler]
|
||||
> = [
|
||||
[
|
||||
'play',
|
||||
() => {
|
||||
this.store.play();
|
||||
},
|
||||
],
|
||||
[
|
||||
'pause',
|
||||
() => {
|
||||
this.store.pause();
|
||||
},
|
||||
],
|
||||
[
|
||||
'stop',
|
||||
() => {
|
||||
this.store.pause();
|
||||
this.store.seek(0);
|
||||
},
|
||||
],
|
||||
[
|
||||
'seekbackward',
|
||||
(details) => {
|
||||
const offset =
|
||||
(details as MediaSessionActionDetails).seekOffset ||
|
||||
defaultSkipTime;
|
||||
this.store.seek(Math.max(0, this.store.currentTime - offset));
|
||||
},
|
||||
],
|
||||
[
|
||||
'seekforward',
|
||||
(details) => {
|
||||
const offset =
|
||||
(details as MediaSessionActionDetails).seekOffset ||
|
||||
defaultSkipTime;
|
||||
this.store.seek(
|
||||
Math.min(this.store.duration, this.store.currentTime + offset)
|
||||
);
|
||||
},
|
||||
],
|
||||
[
|
||||
'seekto',
|
||||
(details) => {
|
||||
const seekTime = (details as MediaSessionActionDetails).seekTime;
|
||||
if (seekTime != null) this.store.seek(seekTime);
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
for (const [action, handler] of actionHandlers) {
|
||||
@@ -51,9 +81,12 @@ export class MediaSessionHandler {
|
||||
|
||||
// Subscribe to store for playback state and position updates
|
||||
const unsubscribe = this.store.subscribe(() => {
|
||||
mediaSession.playbackState = this.store.paused ? "paused" : "playing";
|
||||
mediaSession.playbackState = this.store.paused ? 'paused' : 'playing';
|
||||
|
||||
if ("setPositionState" in mediaSession && Number.isFinite(this.store.duration)) {
|
||||
if (
|
||||
'setPositionState' in mediaSession &&
|
||||
Number.isFinite(this.store.duration)
|
||||
) {
|
||||
try {
|
||||
mediaSession.setPositionState({
|
||||
duration: this.store.duration,
|
||||
|
||||
@@ -19,7 +19,12 @@ export interface VideoStore {
|
||||
readonly source: string | null;
|
||||
readonly buffered: [number, number][];
|
||||
readonly error: { code: number; message: string } | null;
|
||||
readonly textTrackList: Array<{ kind: string; label: string; language: string; mode: string }>;
|
||||
readonly textTrackList: Array<{
|
||||
kind: string;
|
||||
label: string;
|
||||
language: string;
|
||||
mode: string;
|
||||
}>;
|
||||
readonly destroyed: boolean;
|
||||
readonly target: unknown;
|
||||
readonly pipAvailability: string;
|
||||
@@ -38,7 +43,10 @@ export interface VideoStore {
|
||||
|
||||
// Lifecycle
|
||||
subscribe(callback: () => void): () => void;
|
||||
attach(target: { media: HTMLVideoElement; container: HTMLElement | null }): () => void;
|
||||
attach(target: {
|
||||
media: HTMLVideoElement;
|
||||
container: HTMLElement | null;
|
||||
}): () => void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
@@ -47,5 +55,12 @@ export interface VideoStore {
|
||||
*/
|
||||
export type MediaSessionStore = Pick<
|
||||
VideoStore,
|
||||
"paused" | "currentTime" | "duration" | "playbackRate" | "play" | "pause" | "seek" | "subscribe"
|
||||
| 'paused'
|
||||
| 'currentTime'
|
||||
| 'duration'
|
||||
| 'playbackRate'
|
||||
| 'play'
|
||||
| 'pause'
|
||||
| 'seek'
|
||||
| 'subscribe'
|
||||
>;
|
||||
|
||||
@@ -6,8 +6,8 @@ import type {
|
||||
onProgressData,
|
||||
onVolumeChangeData,
|
||||
TimedMetadata,
|
||||
} from "../types/Events";
|
||||
import type { TextTrack } from "../types/TextTrack";
|
||||
} from '../types/Events';
|
||||
import type { TextTrack } from '../types/TextTrack';
|
||||
import {
|
||||
type LibraryError,
|
||||
type PlayerError,
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
type UnknownError,
|
||||
VideoError,
|
||||
type VideoRuntimeError,
|
||||
} from "../types/VideoError";
|
||||
import type { VideoPlayerStatus } from "../types/VideoPlayerStatus";
|
||||
} from '../types/VideoError';
|
||||
import type { VideoPlayerStatus } from '../types/VideoPlayerStatus';
|
||||
import type {
|
||||
ListenerSubscription,
|
||||
VideoPlayerEventEmitterBase,
|
||||
} from "../types/EventEmitter";
|
||||
} from '../types/EventEmitter';
|
||||
|
||||
import type { VideoStore } from "./VideoStore";
|
||||
import type { VideoStore } from './VideoStore';
|
||||
|
||||
/**
|
||||
* WebEventEmitter bridges HTML5 media events to our event system.
|
||||
@@ -38,7 +38,7 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
|
||||
|
||||
constructor(
|
||||
store: VideoStore | null,
|
||||
private getMedia: () => HTMLVideoElement | null,
|
||||
private getMedia: () => HTMLVideoElement | null
|
||||
) {
|
||||
// Attach to video element immediately if available
|
||||
this._attachMediaListeners();
|
||||
@@ -72,124 +72,159 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
|
||||
|
||||
const cleanups: Array<() => void> = [];
|
||||
|
||||
cleanups.push(on("play", () => {
|
||||
this._emit("onPlaybackStateChange", {
|
||||
isPlaying: true,
|
||||
isBuffering: this._isBuffering,
|
||||
});
|
||||
}));
|
||||
|
||||
cleanups.push(on("pause", () => {
|
||||
this._emit("onPlaybackStateChange", {
|
||||
isPlaying: false,
|
||||
isBuffering: this._isBuffering,
|
||||
});
|
||||
}));
|
||||
|
||||
cleanups.push(on("waiting", () => {
|
||||
this._isBuffering = true;
|
||||
this._emit("onBuffer", true);
|
||||
this._emit("onStatusChange", "loading");
|
||||
}));
|
||||
|
||||
cleanups.push(on("canplay", () => {
|
||||
this._isBuffering = false;
|
||||
this._emit("onBuffer", false);
|
||||
this._emit("onStatusChange", "readyToPlay");
|
||||
}));
|
||||
|
||||
cleanups.push(on("timeupdate", () => {
|
||||
const buffered = video.buffered;
|
||||
const lastBuffered = buffered.length > 0 ? buffered.end(buffered.length - 1) : 0;
|
||||
this._emit("onProgress", {
|
||||
currentTime: video.currentTime,
|
||||
bufferDuration: lastBuffered,
|
||||
});
|
||||
}));
|
||||
|
||||
cleanups.push(on("durationchange", () => {
|
||||
if (video.duration > 0) {
|
||||
this._emit("onLoad", {
|
||||
currentTime: video.currentTime,
|
||||
duration: video.duration,
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight,
|
||||
orientation: "unknown",
|
||||
cleanups.push(
|
||||
on('play', () => {
|
||||
this._emit('onPlaybackStateChange', {
|
||||
isPlaying: true,
|
||||
isBuffering: this._isBuffering,
|
||||
});
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(on("ended", () => {
|
||||
this._emit("onEnd");
|
||||
this._emit("onStatusChange", "idle");
|
||||
}));
|
||||
cleanups.push(
|
||||
on('pause', () => {
|
||||
this._emit('onPlaybackStateChange', {
|
||||
isPlaying: false,
|
||||
isBuffering: this._isBuffering,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(on("ratechange", () => {
|
||||
this._emit("onPlaybackRateChange", video.playbackRate);
|
||||
}));
|
||||
cleanups.push(
|
||||
on('waiting', () => {
|
||||
this._isBuffering = true;
|
||||
this._emit('onBuffer', true);
|
||||
this._emit('onStatusChange', 'loading');
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(on("loadeddata", () => {
|
||||
this._emit("onReadyToDisplay");
|
||||
}));
|
||||
cleanups.push(
|
||||
on('canplay', () => {
|
||||
this._isBuffering = false;
|
||||
this._emit('onBuffer', false);
|
||||
this._emit('onStatusChange', 'readyToPlay');
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(on("seeked", () => {
|
||||
this._emit("onSeek", video.currentTime);
|
||||
}));
|
||||
cleanups.push(
|
||||
on('timeupdate', () => {
|
||||
const buffered = video.buffered;
|
||||
const lastBuffered =
|
||||
buffered.length > 0 ? buffered.end(buffered.length - 1) : 0;
|
||||
this._emit('onProgress', {
|
||||
currentTime: video.currentTime,
|
||||
bufferDuration: lastBuffered,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(on("volumechange", () => {
|
||||
this._emit("onVolumeChange", {
|
||||
volume: video.volume,
|
||||
muted: video.muted,
|
||||
});
|
||||
}));
|
||||
|
||||
cleanups.push(on("loadstart", () => {
|
||||
this._emit("onLoadStart", {
|
||||
sourceType: "network",
|
||||
source: {
|
||||
uri: video.currentSrc || video.src,
|
||||
config: {
|
||||
uri: video.currentSrc || video.src,
|
||||
externalSubtitles: [],
|
||||
},
|
||||
getAssetInformationAsync: async () => ({
|
||||
duration: video.duration || NaN,
|
||||
cleanups.push(
|
||||
on('durationchange', () => {
|
||||
if (video.duration > 0) {
|
||||
this._emit('onLoad', {
|
||||
currentTime: video.currentTime,
|
||||
duration: video.duration,
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight,
|
||||
orientation: "unknown",
|
||||
bitrate: NaN,
|
||||
fileSize: -1n,
|
||||
isHDR: false,
|
||||
isLive: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}));
|
||||
orientation: 'unknown',
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(on("error", () => {
|
||||
this._emit("onStatusChange", "error");
|
||||
const err = video.error;
|
||||
if (!err) {
|
||||
console.error("Unknown error occurred in player");
|
||||
return;
|
||||
}
|
||||
const codeMap: Record<number, LibraryError | PlayerError | SourceError | UnknownError> = {
|
||||
1: "player/asset-not-initialized",
|
||||
2: "player/not-initialized",
|
||||
3: "player/invalid-source",
|
||||
4: "source/unsupported-content-type",
|
||||
};
|
||||
this._emit("onError", new VideoError(codeMap[err.code] ?? "unknown/unknown", err.message));
|
||||
}));
|
||||
cleanups.push(
|
||||
on('ended', () => {
|
||||
this._emit('onEnd');
|
||||
this._emit('onStatusChange', 'idle');
|
||||
})
|
||||
);
|
||||
|
||||
this._mediaCleanup = () => { cleanups.forEach((fn) => fn()); };
|
||||
cleanups.push(
|
||||
on('ratechange', () => {
|
||||
this._emit('onPlaybackRateChange', video.playbackRate);
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(
|
||||
on('loadeddata', () => {
|
||||
this._emit('onReadyToDisplay');
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(
|
||||
on('seeked', () => {
|
||||
this._emit('onSeek', video.currentTime);
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(
|
||||
on('volumechange', () => {
|
||||
this._emit('onVolumeChange', {
|
||||
volume: video.volume,
|
||||
muted: video.muted,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(
|
||||
on('loadstart', () => {
|
||||
this._emit('onLoadStart', {
|
||||
sourceType: 'network',
|
||||
source: {
|
||||
uri: video.currentSrc || video.src,
|
||||
config: {
|
||||
uri: video.currentSrc || video.src,
|
||||
externalSubtitles: [],
|
||||
},
|
||||
getAssetInformationAsync: async () => ({
|
||||
duration: video.duration || NaN,
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight,
|
||||
orientation: 'unknown',
|
||||
bitrate: NaN,
|
||||
fileSize: -1n,
|
||||
isHDR: false,
|
||||
isLive: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
cleanups.push(
|
||||
on('error', () => {
|
||||
this._emit('onStatusChange', 'error');
|
||||
const err = video.error;
|
||||
if (!err) {
|
||||
console.error('Unknown error occurred in player');
|
||||
return;
|
||||
}
|
||||
const codeMap: Record<
|
||||
number,
|
||||
LibraryError | PlayerError | SourceError | UnknownError
|
||||
> = {
|
||||
1: 'player/asset-not-initialized',
|
||||
2: 'player/not-initialized',
|
||||
3: 'player/invalid-source',
|
||||
4: 'source/unsupported-content-type',
|
||||
};
|
||||
this._emit(
|
||||
'onError',
|
||||
new VideoError(codeMap[err.code] ?? 'unknown/unknown', err.message)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
this._mediaCleanup = () => {
|
||||
cleanups.forEach((fn) => fn());
|
||||
};
|
||||
}
|
||||
|
||||
// --- Listener infrastructure ---
|
||||
|
||||
private _addListener(
|
||||
event: string,
|
||||
listener: (...args: any[]) => void,
|
||||
listener: (...args: any[]) => void
|
||||
): ListenerSubscription {
|
||||
if (!this._listeners.has(event)) {
|
||||
this._listeners.set(event, new Set());
|
||||
@@ -209,111 +244,111 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
|
||||
// --- Listener registration (implements VideoPlayerEventEmitterBase) ---
|
||||
|
||||
addOnAudioBecomingNoisyListener(listener: () => void): ListenerSubscription {
|
||||
return this._addListener("onAudioBecomingNoisy", listener);
|
||||
return this._addListener('onAudioBecomingNoisy', listener);
|
||||
}
|
||||
|
||||
addOnAudioFocusChangeListener(
|
||||
listener: (hasAudioFocus: boolean) => void,
|
||||
listener: (hasAudioFocus: boolean) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onAudioFocusChange", listener);
|
||||
return this._addListener('onAudioFocusChange', listener);
|
||||
}
|
||||
|
||||
addOnBandwidthUpdateListener(
|
||||
listener: (data: BandwidthData) => void,
|
||||
listener: (data: BandwidthData) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onBandwidthUpdate", listener);
|
||||
return this._addListener('onBandwidthUpdate', listener);
|
||||
}
|
||||
|
||||
addOnBufferListener(
|
||||
listener: (buffering: boolean) => void,
|
||||
listener: (buffering: boolean) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onBuffer", listener);
|
||||
return this._addListener('onBuffer', listener);
|
||||
}
|
||||
|
||||
addOnControlsVisibleChangeListener(
|
||||
listener: (visible: boolean) => void,
|
||||
listener: (visible: boolean) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onControlsVisibleChange", listener);
|
||||
return this._addListener('onControlsVisibleChange', listener);
|
||||
}
|
||||
|
||||
addOnEndListener(listener: () => void): ListenerSubscription {
|
||||
return this._addListener("onEnd", listener);
|
||||
return this._addListener('onEnd', listener);
|
||||
}
|
||||
|
||||
addOnExternalPlaybackChangeListener(
|
||||
listener: (externalPlaybackActive: boolean) => void,
|
||||
listener: (externalPlaybackActive: boolean) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onExternalPlaybackChange", listener);
|
||||
return this._addListener('onExternalPlaybackChange', listener);
|
||||
}
|
||||
|
||||
addOnLoadListener(
|
||||
listener: (data: onLoadData) => void,
|
||||
listener: (data: onLoadData) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onLoad", listener);
|
||||
return this._addListener('onLoad', listener);
|
||||
}
|
||||
|
||||
addOnLoadStartListener(
|
||||
listener: (data: onLoadStartData) => void,
|
||||
listener: (data: onLoadStartData) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onLoadStart", listener);
|
||||
return this._addListener('onLoadStart', listener);
|
||||
}
|
||||
|
||||
addOnPlaybackStateChangeListener(
|
||||
listener: (data: onPlaybackStateChangeData) => void,
|
||||
listener: (data: onPlaybackStateChangeData) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onPlaybackStateChange", listener);
|
||||
return this._addListener('onPlaybackStateChange', listener);
|
||||
}
|
||||
|
||||
addOnPlaybackRateChangeListener(
|
||||
listener: (rate: number) => void,
|
||||
listener: (rate: number) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onPlaybackRateChange", listener);
|
||||
return this._addListener('onPlaybackRateChange', listener);
|
||||
}
|
||||
|
||||
addOnProgressListener(
|
||||
listener: (data: onProgressData) => void,
|
||||
listener: (data: onProgressData) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onProgress", listener);
|
||||
return this._addListener('onProgress', listener);
|
||||
}
|
||||
|
||||
addOnReadyToDisplayListener(listener: () => void): ListenerSubscription {
|
||||
return this._addListener("onReadyToDisplay", listener);
|
||||
return this._addListener('onReadyToDisplay', listener);
|
||||
}
|
||||
|
||||
addOnSeekListener(
|
||||
listener: (position: number) => void,
|
||||
listener: (position: number) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onSeek", listener);
|
||||
return this._addListener('onSeek', listener);
|
||||
}
|
||||
|
||||
addOnStatusChangeListener(
|
||||
listener: (status: VideoPlayerStatus) => void,
|
||||
listener: (status: VideoPlayerStatus) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onStatusChange", listener);
|
||||
return this._addListener('onStatusChange', listener);
|
||||
}
|
||||
|
||||
addOnTimedMetadataListener(
|
||||
listener: (data: TimedMetadata) => void,
|
||||
listener: (data: TimedMetadata) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onTimedMetadata", listener);
|
||||
return this._addListener('onTimedMetadata', listener);
|
||||
}
|
||||
|
||||
addOnTextTrackDataChangedListener(
|
||||
listener: (data: string[]) => void,
|
||||
listener: (data: string[]) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onTextTrackDataChanged", listener);
|
||||
return this._addListener('onTextTrackDataChanged', listener);
|
||||
}
|
||||
|
||||
addOnTrackChangeListener(
|
||||
listener: (track: TextTrack | null) => void,
|
||||
listener: (track: TextTrack | null) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onTrackChange", listener);
|
||||
return this._addListener('onTrackChange', listener);
|
||||
}
|
||||
|
||||
addOnVolumeChangeListener(
|
||||
listener: (data: onVolumeChangeData) => void,
|
||||
listener: (data: onVolumeChangeData) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onVolumeChange", listener);
|
||||
return this._addListener('onVolumeChange', listener);
|
||||
}
|
||||
|
||||
clearAllListeners(): void {
|
||||
@@ -321,8 +356,8 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
|
||||
}
|
||||
|
||||
addOnErrorListener(
|
||||
listener: (error: VideoRuntimeError) => void,
|
||||
listener: (error: VideoRuntimeError) => void
|
||||
): ListenerSubscription {
|
||||
return this._addListener("onError", listener);
|
||||
return this._addListener('onError', listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import type { VideoPlayerEventEmitter } from './VideoPlayerEventEmitter.nitro';
|
||||
import type { VideoPlayerSource } from './VideoPlayerSource.nitro';
|
||||
|
||||
export interface VideoPlayer
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }>,
|
||||
VideoPlayerBase {
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }>, VideoPlayerBase {
|
||||
// Override with (hybrid) VideoPlayerSource
|
||||
readonly source: VideoPlayerSource;
|
||||
|
||||
@@ -49,7 +48,9 @@ export interface VideoPlayer
|
||||
release(): void;
|
||||
}
|
||||
|
||||
export interface VideoPlayerFactory
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
||||
export interface VideoPlayerFactory extends HybridObject<{
|
||||
ios: 'swift';
|
||||
android: 'kotlin';
|
||||
}> {
|
||||
createPlayer(source: VideoPlayerSource): VideoPlayer;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ export interface ListenerSubscription {
|
||||
remove(): void;
|
||||
}
|
||||
|
||||
export interface VideoPlayerEventEmitter
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
||||
export interface VideoPlayerEventEmitter extends HybridObject<{
|
||||
ios: 'swift';
|
||||
android: 'kotlin';
|
||||
}> {
|
||||
/**
|
||||
* Adds a listener for the `onAudioBecomingNoisy` event.
|
||||
* @see {@link VideoPlayerEvents.onAudioBecomingNoisy}
|
||||
|
||||
@@ -8,11 +8,14 @@ import type { VideoPlayerSourceBase } from '../../core/types/VideoPlayerSourceBa
|
||||
* It provides functions to get information about the asset.
|
||||
*/
|
||||
export interface VideoPlayerSource
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }>,
|
||||
extends
|
||||
HybridObject<{ ios: 'swift'; android: 'kotlin' }>,
|
||||
VideoPlayerSourceBase {}
|
||||
|
||||
export interface VideoPlayerSourceFactory
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
||||
export interface VideoPlayerSourceFactory extends HybridObject<{
|
||||
ios: 'swift';
|
||||
android: 'kotlin';
|
||||
}> {
|
||||
fromUri(uri: string): VideoPlayerSource;
|
||||
fromVideoConfig(config: NativeVideoConfig): VideoPlayerSource;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import type { ListenerSubscription } from './VideoPlayerEventEmitter.nitro';
|
||||
export type SurfaceType = 'surface' | 'texture';
|
||||
|
||||
// @internal
|
||||
export interface VideoViewViewManager
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
||||
export interface VideoViewViewManager extends HybridObject<{
|
||||
ios: 'swift';
|
||||
android: 'kotlin';
|
||||
}> {
|
||||
player?: VideoPlayer;
|
||||
controls: boolean;
|
||||
pictureInPicture: boolean;
|
||||
@@ -86,7 +88,9 @@ export interface VideoViewViewManager
|
||||
}
|
||||
|
||||
// @internal
|
||||
export interface VideoViewViewManagerFactory
|
||||
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
||||
export interface VideoViewViewManagerFactory extends HybridObject<{
|
||||
ios: 'swift';
|
||||
android: 'kotlin';
|
||||
}> {
|
||||
createViewManager(nitroId: number): VideoViewViewManager;
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "dom"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user