mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-06 07:16:12 +00:00
Implement event handlers for web
This commit is contained in:
@@ -12,14 +12,16 @@ import {
|
|||||||
import type { VideoPlayerBase } from "./types/VideoPlayerBase";
|
import type { VideoPlayerBase } from "./types/VideoPlayerBase";
|
||||||
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
||||||
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
||||||
|
import { WebEventEmiter } from "./WebEventEmiter";
|
||||||
|
|
||||||
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||||
protected player = new shaka.Player();
|
protected player = new shaka.Player();
|
||||||
protected video = document.createElement("video");
|
protected video: HTMLVideoElement;
|
||||||
|
|
||||||
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
|
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
|
||||||
// Initialize events
|
const video = document.createElement("video");
|
||||||
super(player.eventEmitter);
|
super(new WebEventEmiter(video));
|
||||||
|
this.video = video;
|
||||||
this.player.attach(this.video);
|
this.player.attach(this.video);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export class VideoPlayerEvents {
|
|||||||
protected readonly supportedEvents: (keyof PlayerEvents)[] =
|
protected readonly supportedEvents: (keyof PlayerEvents)[] =
|
||||||
ALL_PLAYER_EVENTS;
|
ALL_PLAYER_EVENTS;
|
||||||
|
|
||||||
constructor(eventEmitter: VideoPlayerEventEmitter) {
|
constructor(eventEmitter: PlayerEvents) {
|
||||||
this.eventEmitter = eventEmitter;
|
this.eventEmitter = eventEmitter;
|
||||||
for (let event of this.supportedEvents) {
|
for (const event of this.supportedEvents) {
|
||||||
// @ts-expect-error we narrow the type of the event
|
// @ts-expect-error we narrow the type of the event
|
||||||
this.eventEmitter[event] = this.triggerEvent.bind(this, event);
|
this.eventEmitter[event] = this.triggerEvent.bind(this, event);
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ export class VideoPlayerEvents {
|
|||||||
...params: Parameters<PlayerEvents[Event]>
|
...params: Parameters<PlayerEvents[Event]>
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!this.eventListeners[event]?.size) return false;
|
if (!this.eventListeners[event]?.size) return false;
|
||||||
for (let fn of this.eventListeners[event]) {
|
for (const fn of this.eventListeners[event]) {
|
||||||
fn(...params);
|
fn(...params);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -34,7 +34,7 @@ export class VideoPlayerEvents {
|
|||||||
|
|
||||||
addEventListener<Event extends keyof PlayerEvents>(
|
addEventListener<Event extends keyof PlayerEvents>(
|
||||||
event: Event,
|
event: Event,
|
||||||
callback: PlayerEvents[Event]
|
callback: PlayerEvents[Event],
|
||||||
) {
|
) {
|
||||||
this.eventListeners[event] ??= new Set<PlayerEvents[Event]>();
|
this.eventListeners[event] ??= new Set<PlayerEvents[Event]>();
|
||||||
this.eventListeners[event].add(callback);
|
this.eventListeners[event].add(callback);
|
||||||
@@ -42,7 +42,7 @@ export class VideoPlayerEvents {
|
|||||||
|
|
||||||
removeEventListener<Event extends keyof PlayerEvents>(
|
removeEventListener<Event extends keyof PlayerEvents>(
|
||||||
event: Event,
|
event: Event,
|
||||||
callback: PlayerEvents[Event]
|
callback: PlayerEvents[Event],
|
||||||
) {
|
) {
|
||||||
this.eventListeners[event]?.delete(callback);
|
this.eventListeners[event]?.delete(callback);
|
||||||
}
|
}
|
||||||
|
|||||||
198
packages/react-native-video/src/core/WebEventEmiter.ts
Normal file
198
packages/react-native-video/src/core/WebEventEmiter.ts
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import type {
|
||||||
|
BandwidthData,
|
||||||
|
onLoadData,
|
||||||
|
onLoadStartData,
|
||||||
|
onPlaybackStateChangeData,
|
||||||
|
onProgressData,
|
||||||
|
onVolumeChangeData,
|
||||||
|
AllPlayerEvents as PlayerEvents,
|
||||||
|
TimedMetadata,
|
||||||
|
} from "./types/Events";
|
||||||
|
import type { TextTrack } from "./types/TextTrack";
|
||||||
|
import type { VideoRuntimeError } from "./types/VideoError";
|
||||||
|
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
||||||
|
|
||||||
|
export class WebEventEmiter implements PlayerEvents {
|
||||||
|
private _isBuferring = false;
|
||||||
|
|
||||||
|
constructor(private video: HTMLVideoElement) {
|
||||||
|
// TODO: add `onBandwithUpdate`
|
||||||
|
|
||||||
|
// on buffer
|
||||||
|
this.video.addEventListener("canplay", this._onCanPlay);
|
||||||
|
this.video.addEventListener("waiting", this._onWaiting);
|
||||||
|
|
||||||
|
// on end
|
||||||
|
this.video.addEventListener("ended", this._onEnded);
|
||||||
|
|
||||||
|
// on load
|
||||||
|
this.video.addEventListener("durationchange", this._onDurationChange);
|
||||||
|
|
||||||
|
// on load start
|
||||||
|
this.video.addEventListener("loadstart", this._onLoadStart);
|
||||||
|
|
||||||
|
// on playback state change
|
||||||
|
this.video.addEventListener("play", this._onPlay);
|
||||||
|
this.video.addEventListener("pause", this._onPause);
|
||||||
|
|
||||||
|
// on playback rate change
|
||||||
|
this.video.addEventListener("ratechange", this._onRateChange);
|
||||||
|
|
||||||
|
// on progress
|
||||||
|
this.video.addEventListener("timeupdate", this._onTimeUpdate);
|
||||||
|
|
||||||
|
// on ready to play
|
||||||
|
this.video.addEventListener("loadeddata", this._onLoadedData);
|
||||||
|
|
||||||
|
// on seek
|
||||||
|
this.video.addEventListener("seeked", this._onSeeked);
|
||||||
|
|
||||||
|
// on volume change
|
||||||
|
this.video.addEventListener("volumechange", this._onVolumeChange);
|
||||||
|
|
||||||
|
// on status change
|
||||||
|
this.video.addEventListener("error", this._onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.video.removeEventListener("canplay", this._onCanPlay);
|
||||||
|
this.video.removeEventListener("waiting", this._onWaiting);
|
||||||
|
|
||||||
|
this.video.removeEventListener("ended", this._onEnded);
|
||||||
|
|
||||||
|
this.video.removeEventListener("durationchange", this._onDurationChange);
|
||||||
|
|
||||||
|
this.video.removeEventListener("play", this._onPlay);
|
||||||
|
this.video.removeEventListener("pause", this._onPause);
|
||||||
|
|
||||||
|
this.video.removeEventListener("ratechange", this._onRateChange);
|
||||||
|
|
||||||
|
this.video.removeEventListener("timeupdate", this._onTimeUpdate);
|
||||||
|
|
||||||
|
this.video.removeEventListener("loadeddata", this._onLoadedData);
|
||||||
|
|
||||||
|
this.video.removeEventListener("seeked", this._onSeeked);
|
||||||
|
|
||||||
|
this.video.removeEventListener("volumechange", this._onVolumeChange);
|
||||||
|
|
||||||
|
this.video.removeEventListener("error", this._onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onTimeUpdate() {
|
||||||
|
this.onProgress({
|
||||||
|
currentTime: this.video.currentTime,
|
||||||
|
bufferDuration: this.video.buffered.length
|
||||||
|
? this.video.buffered.end(this.video.buffered.length - 1)
|
||||||
|
: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onCanPlay() {
|
||||||
|
this._isBuferring = false;
|
||||||
|
this.onBuffer(false);
|
||||||
|
this.onStatusChange("readyToPlay");
|
||||||
|
}
|
||||||
|
_onWaiting() {
|
||||||
|
this._isBuferring = true;
|
||||||
|
this.onBuffer(true);
|
||||||
|
this.onStatusChange("loading");
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDurationChange() {
|
||||||
|
this.onLoad({
|
||||||
|
currentTime: this.video.currentTime,
|
||||||
|
duration: this.video.duration,
|
||||||
|
width: this.video.width,
|
||||||
|
height: this.video.height,
|
||||||
|
orientation: "unknown",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onEnded() {
|
||||||
|
this.onEnd();
|
||||||
|
this.onStatusChange("idle");
|
||||||
|
}
|
||||||
|
|
||||||
|
_onLoadStart() {
|
||||||
|
this.onLoadStart({
|
||||||
|
sourceType: "network",
|
||||||
|
source: {
|
||||||
|
uri: this.video.currentSrc,
|
||||||
|
config: {
|
||||||
|
uri: this.video.currentSrc,
|
||||||
|
externalSubtitles: [],
|
||||||
|
},
|
||||||
|
getAssetInformationAsync: async () => {
|
||||||
|
return {
|
||||||
|
duration: BigInt(this.video.duration),
|
||||||
|
height: this.video.height,
|
||||||
|
width: this.video.width,
|
||||||
|
orientation: "unknown",
|
||||||
|
bitrate: NaN,
|
||||||
|
fileSize: BigInt(NaN),
|
||||||
|
isHDR: false,
|
||||||
|
isLive: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPlay() {
|
||||||
|
this.onPlaybackStateChange({
|
||||||
|
isPlaying: true,
|
||||||
|
isBuffering: this._isBuferring,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPause() {
|
||||||
|
this.onPlaybackStateChange({
|
||||||
|
isPlaying: false,
|
||||||
|
isBuffering: this._isBuferring,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRateChange() {
|
||||||
|
this.onPlaybackRateChange(this.video.playbackRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onLoadedData() {
|
||||||
|
this.onReadyToDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onSeeked() {
|
||||||
|
this.onSeek(this.video.currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onVolumeChange() {
|
||||||
|
this.onVolumeChange({ muted: this.video.muted, volume: this.video.volume });
|
||||||
|
}
|
||||||
|
|
||||||
|
_onError() {
|
||||||
|
this.onStatusChange("error");
|
||||||
|
}
|
||||||
|
|
||||||
|
NOOP = () => {};
|
||||||
|
|
||||||
|
onError: (error: VideoRuntimeError) => void = this.NOOP;
|
||||||
|
onAudioBecomingNoisy: () => void = this.NOOP;
|
||||||
|
onAudioFocusChange: (hasAudioFocus: boolean) => void = this.NOOP;
|
||||||
|
onBandwidthUpdate: (data: BandwidthData) => void = this.NOOP;
|
||||||
|
onBuffer: (buffering: boolean) => void = this.NOOP;
|
||||||
|
onControlsVisibleChange: (visible: boolean) => void = this.NOOP;
|
||||||
|
onEnd: () => void = this.NOOP;
|
||||||
|
onExternalPlaybackChange: (externalPlaybackActive: boolean) => void =
|
||||||
|
this.NOOP;
|
||||||
|
onLoad: (data: onLoadData) => void = this.NOOP;
|
||||||
|
onLoadStart: (data: onLoadStartData) => void = this.NOOP;
|
||||||
|
onPlaybackStateChange: (data: onPlaybackStateChangeData) => void = this.NOOP;
|
||||||
|
onPlaybackRateChange: (rate: number) => void = this.NOOP;
|
||||||
|
onProgress: (data: onProgressData) => void = this.NOOP;
|
||||||
|
onReadyToDisplay: () => void = this.NOOP;
|
||||||
|
onSeek: (seekTime: number) => void = this.NOOP;
|
||||||
|
onTimedMetadata: (metadata: TimedMetadata) => void = this.NOOP;
|
||||||
|
onTextTrackDataChanged: (texts: string[]) => void = this.NOOP;
|
||||||
|
onTrackChange: (track: TextTrack | null) => void = this.NOOP;
|
||||||
|
onVolumeChange: (data: onVolumeChangeData) => void = this.NOOP;
|
||||||
|
onStatusChange: (status: VideoPlayerStatus) => void = this.NOOP;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { VideoPlayerSource } from '../../spec/nitro/VideoPlayerSource.nitro';
|
|
||||||
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';
|
||||||
|
import type { VideoPlayerSourceBase } from './VideoPlayerSourceBase';
|
||||||
import type { VideoPlayerStatus } from './VideoPlayerStatus';
|
import type { VideoPlayerStatus } from './VideoPlayerStatus';
|
||||||
|
|
||||||
export interface VideoPlayerEvents {
|
export interface VideoPlayerEvents {
|
||||||
@@ -28,6 +28,7 @@ export interface VideoPlayerEvents {
|
|||||||
/**
|
/**
|
||||||
* Called when the video view's controls visibility changes.
|
* Called when the video view's controls visibility changes.
|
||||||
* @param visible Whether the video view's controls are visible.
|
* @param visible Whether the video view's controls are visible.
|
||||||
|
* @platform Android, Ios
|
||||||
*/
|
*/
|
||||||
onControlsVisibleChange: (visible: boolean) => void;
|
onControlsVisibleChange: (visible: boolean) => void;
|
||||||
/**
|
/**
|
||||||
@@ -72,15 +73,18 @@ export interface VideoPlayerEvents {
|
|||||||
onSeek: (seekTime: number) => void;
|
onSeek: (seekTime: number) => void;
|
||||||
/**
|
/**
|
||||||
* Called when player receives timed metadata.
|
* Called when player receives timed metadata.
|
||||||
|
* @platform Android, Ios
|
||||||
*/
|
*/
|
||||||
onTimedMetadata: (metadata: TimedMetadata) => void;
|
onTimedMetadata: (metadata: TimedMetadata) => void;
|
||||||
/**
|
/**
|
||||||
* Called when the text track (currently displayed subtitle) data changes.
|
* Called when the text track (currently displayed subtitle) data changes.
|
||||||
|
* @platform Android, Ios
|
||||||
*/
|
*/
|
||||||
onTextTrackDataChanged: (texts: string[]) => void;
|
onTextTrackDataChanged: (texts: string[]) => void;
|
||||||
/**
|
/**
|
||||||
* Called when the selected text track changes.
|
* Called when the selected text track changes.
|
||||||
* @param track - The newly selected text track, or null if no track is selected
|
* @param track - The newly selected text track, or null if no track is selected
|
||||||
|
* @platform Android, Ios
|
||||||
*/
|
*/
|
||||||
onTrackChange: (track: TextTrack | null) => void;
|
onTrackChange: (track: TextTrack | null) => void;
|
||||||
/**
|
/**
|
||||||
@@ -178,7 +182,7 @@ export interface onLoadStartData {
|
|||||||
/**
|
/**
|
||||||
* The source of the video.
|
* The source of the video.
|
||||||
*/
|
*/
|
||||||
source: VideoPlayerSource;
|
source: VideoPlayerSourceBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface onPlaybackStateChangeData {
|
export interface onPlaybackStateChangeData {
|
||||||
|
|||||||
Reference in New Issue
Block a user