246 lines
6.2 KiB
JavaScript
246 lines
6.2 KiB
JavaScript
|
// @flow
|
||
|
|
||
|
import { RCTEvent, RCTView, type RCTBridge } from "react-native-dom";
|
||
|
|
||
|
import resizeModes from "./resizeModes";
|
||
|
import type { VideoSource } from "./types";
|
||
|
import RCTVideoEvent from "./RCTVideoEvent";
|
||
|
|
||
|
class RCTVideo extends RCTView {
|
||
|
playPromise: Promise<void> = Promise.resolve();
|
||
|
progressTimer: number;
|
||
|
videoElement: HTMLVideoElement;
|
||
|
|
||
|
onEnd: boolean = false;
|
||
|
onLoad: boolean = false;
|
||
|
onLoadStart: boolean = false;
|
||
|
onProgress: boolean = false;
|
||
|
|
||
|
_paused: boolean = false;
|
||
|
_progressUpdateInterval: number = 250.0;
|
||
|
_savedVolume: number = 1.0;
|
||
|
|
||
|
constructor(bridge: RCTBridge) {
|
||
|
super(bridge);
|
||
|
|
||
|
this.eventDispatcher = bridge.getModuleByName("EventDispatcher");
|
||
|
|
||
|
this.onEnd = this.onEnd.bind(this);
|
||
|
this.onLoad = this.onLoad.bind(this);
|
||
|
this.onLoadStart = this.onLoadStart.bind(this);
|
||
|
this.onPlay = this.onPlay.bind(this);
|
||
|
this.onProgress = this.onProgress.bind(this);
|
||
|
|
||
|
this.videoElement = this.initializeVideoElement();
|
||
|
this.videoElement.addEventListener("ended", this.onEnd);
|
||
|
this.videoElement.addEventListener("loadeddata", this.onLoad);
|
||
|
this.videoElement.addEventListener("loadstart", this.onLoadStart);
|
||
|
this.videoElement.addEventListener("pause", this.onPause);
|
||
|
this.videoElement.addEventListener("play", this.onPlay);
|
||
|
|
||
|
this.muted = false;
|
||
|
this.rate = 1.0;
|
||
|
this.volume = 1.0;
|
||
|
this.childContainer.appendChild(this.videoElement);
|
||
|
}
|
||
|
|
||
|
detachFromView(view: UIView) {
|
||
|
this.videoElement.removeEventListener("ended", this.onEnd);
|
||
|
this.videoElement.removeEventListener("loadeddata", this.onLoad);
|
||
|
this.videoElement.removeEventListener("loadstart", this.onLoadStart);
|
||
|
this.videoElement.removeEventListener("pause", this.onPause);
|
||
|
this.videoElement.removeEventListener("play", this.onPlay);
|
||
|
|
||
|
this.stopProgressTimer();
|
||
|
}
|
||
|
|
||
|
initializeVideoElement() {
|
||
|
const elem = document.createElement("video");
|
||
|
|
||
|
Object.assign(elem.style, {
|
||
|
display: "block",
|
||
|
position: "absolute",
|
||
|
top: "0",
|
||
|
left: "0",
|
||
|
width: "100%",
|
||
|
height: "100%"
|
||
|
});
|
||
|
|
||
|
return elem;
|
||
|
}
|
||
|
|
||
|
presentFullscreenPlayer() {
|
||
|
console.log("V PF");
|
||
|
this.videoElement.webkitRequestFullScreen();
|
||
|
}
|
||
|
|
||
|
set controls(value: boolean) {
|
||
|
if (value) {
|
||
|
this.videoElement.controls = true;
|
||
|
this.videoElement.style.pointerEvents = "auto";
|
||
|
} else {
|
||
|
this.videoElement.controls = false;
|
||
|
this.videoElement.style.pointerEvents = "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set muted(value: boolean) {
|
||
|
if (value) {
|
||
|
this.videoElement.muted = true;
|
||
|
} else {
|
||
|
this.videoElement.muted = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set paused(value: boolean) {
|
||
|
this.playPromise.then(() => {
|
||
|
if (value) {
|
||
|
this.videoElement.pause();
|
||
|
} else {
|
||
|
this.playPromise = this.videoElement.play().catch(console.error);
|
||
|
}
|
||
|
});
|
||
|
this._paused = value;
|
||
|
}
|
||
|
|
||
|
set progressUpdateInterval(value: number) {
|
||
|
this._progressUpdateInterval = value;
|
||
|
this.stopProgressTimer();
|
||
|
this.startProgressTimer();
|
||
|
}
|
||
|
|
||
|
set rate(value: number) {
|
||
|
this.videoElement.defaultPlaybackRate = value; // playbackRate doesn't work on Chrome
|
||
|
this.videoElement.playbackRate = value;
|
||
|
}
|
||
|
|
||
|
set repeat(value: boolean) {
|
||
|
if (value) {
|
||
|
this.videoElement.setAttribute("loop", "true");
|
||
|
} else {
|
||
|
this.videoElement.removeAttribute("loop");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set resizeMode(value: number) {
|
||
|
switch (value) {
|
||
|
case resizeModes.ScaleNone: {
|
||
|
this.videoElement.style.objectFit = "none";
|
||
|
break;
|
||
|
}
|
||
|
case resizeModes.ScaleToFill: {
|
||
|
this.videoElement.style.objectFit = "fill";
|
||
|
break;
|
||
|
}
|
||
|
case resizeModes.ScaleAspectFit: {
|
||
|
this.videoElement.style.objectFit = "contain";
|
||
|
break;
|
||
|
}
|
||
|
case resizeModes.ScaleAspectFill: {
|
||
|
this.videoElement.style.objectFit = "cover";
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set seek(value: number) {
|
||
|
this.videoElement.currentTime = value;
|
||
|
}
|
||
|
|
||
|
set source(value: VideoSource) {
|
||
|
let uri = value.uri;
|
||
|
|
||
|
if (uri.startsWith("blob:")) {
|
||
|
let blob = this.bridge.blobManager.resolveURL(uri);
|
||
|
if (blob.type === "text/xml") {
|
||
|
blob = new Blob([blob], { type: "video/mp4" });
|
||
|
}
|
||
|
uri = URL.createObjectURL(blob);
|
||
|
}
|
||
|
|
||
|
this.videoElement.setAttribute("src", uri);
|
||
|
if (!this._paused) {
|
||
|
this.playPromise = this.videoElement.play();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set volume(value: number) {
|
||
|
if (value === 0) {
|
||
|
this.muted = true;
|
||
|
} else {
|
||
|
this.videoElement.volume = value;
|
||
|
this.muted = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onEnd = () => {
|
||
|
this.onProgress();
|
||
|
this.sendEvent("topVideoEnd", null);
|
||
|
this.stopProgressTimer();
|
||
|
}
|
||
|
|
||
|
onLoad = () => {
|
||
|
// height & width are safe with audio, will be 0
|
||
|
const height = this.videoElement.videoHeight;
|
||
|
const width = this.videoElement.videoWidth;
|
||
|
const payload = {
|
||
|
currentPosition: this.videoElement.currentTime,
|
||
|
duration: this.videoElement.duration,
|
||
|
naturalSize: {
|
||
|
width,
|
||
|
height,
|
||
|
orientation: width >= height ? "landscape" : "portrait"
|
||
|
}
|
||
|
};
|
||
|
this.sendEvent("topVideoLoad", payload);
|
||
|
}
|
||
|
|
||
|
onLoadStart = () => {
|
||
|
const src = this.videoElement.currentSrc;
|
||
|
const payload = {
|
||
|
isNetwork: !src.match(/^https?:\/\/localhost/), // require is served from localhost
|
||
|
uri: this.videoElement.currentSrc
|
||
|
};
|
||
|
this.sendEvent("topVideoLoadStart", payload);
|
||
|
}
|
||
|
|
||
|
onPause = () => {
|
||
|
this.stopProgressTimer();
|
||
|
}
|
||
|
|
||
|
onPlay = () => {
|
||
|
this.startProgressTimer();
|
||
|
}
|
||
|
|
||
|
onProgress = () => {
|
||
|
const payload = {
|
||
|
currentTime: this.videoElement.currentTime,
|
||
|
duration: this.videoElement.duration
|
||
|
};
|
||
|
this.sendEvent("topVideoProgress", payload);
|
||
|
}
|
||
|
|
||
|
sendEvent(eventName, payload) {
|
||
|
const event = new RCTVideoEvent(eventName, this.reactTag, 0, payload);
|
||
|
this.eventDispatcher.sendEvent(event);
|
||
|
}
|
||
|
|
||
|
startProgressTimer() {
|
||
|
if (!this.progressTimer && this._progressUpdateInterval) {
|
||
|
this.onProgress();
|
||
|
this.progressTimer = setInterval(this.onProgress, this._progressUpdateInterval);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
stopProgressTimer() {
|
||
|
if (this.progressTimer) {
|
||
|
clearInterval(this.progressTimer);
|
||
|
this.progressTimer = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
customElements.define("rct-video", RCTVideo);
|
||
|
|
||
|
export default RCTVideo;
|