UNPKG

react-native-webview-video

Version:

react-native-webview-video is a React Native library that provides a Video component that renders media content such as videos with custom controls for react-native and react-native-web. Support Youtube and Vimeo.

586 lines (561 loc) 27.7 kB
import { Component, createRef } from 'react'; import { Animated, Platform, View, ActivityIndicator, Pressable, Text, AppState } from 'react-native'; import { DEFAULT_USER_AGENT, DEFAULT_YOUTUBE_URL, DEFAULT_VIMEO_URL, DEFAULT_VIDEOJS_URL, getTime } from './src/helpers'; import { youtubeHTML } from './src/sources/Youtube'; import { vimeoHTML } from './src/sources/Vimeo'; import { videoJSHTML } from './src/sources/VideoJS'; import PlaySVG from './src/icons/PlaySVG'; import PauseSVG from './src/icons/PauseSVG'; import VolumeOffSVG from './src/icons/VolumeOffSVG'; import VolumeOnSVG from './src/icons/VolumeOnSVG'; import WebView from 'react-native-webview'; import ReplaySVG from './src/icons/ReplaySVG'; import LivingSVG from './src/icons/LivingSVG'; export default class Video extends Component { constructor(props) { super(props); this.state = { onPlay: false, playerReady: false, playerPlaying: false, progressBar: "0%", progressTime: "00:00", totalTime: "00:00", duration: 0, volumeOn: true }; this.webVideoRef = createRef(); this.imageAnimated = new Animated.Value(0); } componentDidMount() { this.AppVideoState = AppState.addEventListener('change', this._handleAppStateChange); } componentWillUnmount() { clearInterval(this.timer); this.AppVideoState.remove(); } componentDidUpdate(prevProps, prevState) { } _handleAppStateChange = currentAppState => { if (typeof this.props.debug != "undefined" && this.props.debug) { console.log("_handleAppStateChange ", this.state); } if (currentAppState === 'background' && this.state.playerPlaying) { if ( typeof this.props.liveVideo == "undefined" || (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == false) ) { this.setPause(); } } } onImageLoad = () => { Animated.timing(this.imageAnimated, { toValue: 1, useNativeDriver: Platform.OS === "web" ? false : true }).start(({ finished }) => { }); } onMessageRecieved = event => { if (typeof this.props.debug != "undefined" && this.props.debug) { console.log("Webview onMessage event ", event); } try { let message; if (typeof event.nativeEvent.data == "object") { message = event.nativeEvent.data; } else { message = JSON.parse(event.nativeEvent.data); } const info = typeof message.eventType != "undefined" ? message.eventType : ""; switch (info) { case 'playerStateChange': if (message.data == 0) { this.setState({ progressTime: this.state.totalTime }); if (typeof this.props.videoEnd != "undefined") { this.props.videoEnd(); } this.setStop(); } else if (message.data == 2) { if (typeof this.props.videoPause != "undefined") { this.props.videoPause(); } } break; case 'infoDelivery': this.getProgressTime(message); break; case "initialDelivery": this.getProgressTime(message, true); break; case 'playerReady': if (typeof this.props.videoReady != "undefined") { this.props.videoReady(); } this.setPlayerReady(); break; case 'fullScreenChange': // console.log("fullScreenChange ", message); if (typeof this.props.onFullScreenChange != "undefined") { this.props.onFullScreenChange(message.data); } break; case 'playerQualityChange': // console.log("playerQualityChange ", message); if (typeof this.props.videoQualityChange != "undefined") { this.props.videoQualityChange(message.data); } break; case 'playerError': // console.log("playerError ", message); if (typeof this.props.playerError != "undefined") { this.props.playerError(); } break; case 'playbackRateChange': // console.log("playbackRateChange ", message); if (typeof this.props.videoRateChange != "undefined") { this.props.videoRateChange(message.data); } break; default: // console.log("default ", message); break; } } catch (error) { console.warn('[react-native-video-webview]', error); } } getContent() { let loadContent = null; const videoId = typeof this.props.videoId != "undefined" ? this.props.videoId : ""; const videoType = typeof this.props.videoType != "undefined" ? this.props.videoType : ""; const videoSource = typeof this.props.videoSource != "undefined" ? this.props.videoSource : ""; let liveVideo = typeof this.props.liveVideo != "undefined" ? this.props.liveVideo : false; switch (videoSource) { case "youtube": let isLiveVideo = liveVideo ? 1 : 0; if (typeof this.props.useRemote != "undefined" && this.props.useRemote == true) { loadContent = { uri: DEFAULT_YOUTUBE_URL + '?videoId=' + videoId + '&videoType=' + videoType + '&liveVideo=' + isLiveVideo }; } else { loadContent = { html: youtubeHTML(videoId, isLiveVideo, videoType) }; } break; case "vimeo": if (typeof this.props.useRemote != "undefined" && this.props.useRemote == true) { loadContent = { uri: DEFAULT_VIMEO_URL + '?videoId=' + videoId + '&videoType=' + videoType + '&liveVideo=' + liveVideo }; } else { loadContent = { html: vimeoHTML(videoId, liveVideo, videoType) }; } break; case "direct": if (typeof this.props.useRemote != "undefined" && this.props.useRemote == true) { loadContent = { uri: DEFAULT_VIDEOJS_URL + '?videoId=' + videoId + '&videoType=' + videoType + '&liveVideo=' + liveVideo }; } else { loadContent = { html: videoJSHTML(videoId, liveVideo) }; } break; default: break; } return loadContent; } render() { const content = this.getContent(); const buttonColor = typeof this.props.buttonColor != "undefined" ? this.props.buttonColor : "#FFFFFF"; const largeButtonWidth = typeof this.props.largeButtonWidth != "undefined" ? this.props.largeButtonWidth : 60; return (<View pointerEvents={this.state.playerReady ? "auto" : "none"} style={{ alignItems: 'flex-start', alignSelf: 'flex-start', textAlign: 'flex-start', width: '100%' }}> <View pointerEvents="none" style={{ alignItems: 'flex-start', alignSelf: 'flex-start', textAlign: 'flex-start', width: '100%', aspectRatio: 16 / 9 }}> {this.renderWebview(content)} {this.renderLoading()} </View> { this.props.poster != "undefined" && this.state.playerPlaying == false ? <Pressable style={{ width: '100%', aspectRatio: 16 / 9, alignItems: 'center', justifyContent: 'center', position: "absolute", left: 0, right: 0, bottom: 0, top: 0 }} onPress={() => { if (typeof this.props.liveVideo == "undefined" || (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == false)) { this.setPlaying(); } }}> <Animated.Image source={{ uri: this.props.poster }} style={[{ width: '100%', aspectRatio: 16 / 9 }, { opacity: this.imageAnimated }]} onLoad={this.onImageLoad()} /> <View style={{ backgroundColor: content ? "#00000030" : "transparent", width: '100%', aspectRatio: 16 / 9, alignItems: 'center', justifyContent: 'center', position: "absolute", left: 0, right: 0, bottom: 0, top: 0 }}> { content ? <View style={{ width: largeButtonWidth, height: largeButtonWidth, flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', }}> { ( typeof this.props.liveVideo == "undefined" || (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == false) ) ? (this.state.playerPlaying == false && this.state.onPlay ? <ReplaySVG width={largeButtonWidth} color={buttonColor} /> : <PlaySVG width={largeButtonWidth} color={buttonColor} />) : null } </View> : null } </View> </Pressable> : null } {content != null ? this.renderControls() : null} </View>); } renderWebview(content) { return (content != null ? <WebView {...this.props} ref={this.webVideoRef} allowsFullscreenVideo={false} allowsInlineMediaPlayback scalesPageToFit={false} onMessage={this.onMessageRecieved} mediaPlaybackRequiresUserAction={false} originWhitelist={['*']} overScrollMode={"never"} nestedScrollEnabled={true} automaticallyAdjustContentInsets={true} javaScriptEnabled={true} mixedContentMode="compatibility" source={content} userAgent={ typeof this.props.forceAndroidAutoplay != "undefined" ? Platform.select({ android: DEFAULT_USER_AGENT, ios: '' }) : '' } onShouldStartLoadWithRequest={event => { return true; }} /> : null) } renderControls() { const buttonWidth = typeof this.props.buttonWidth != "undefined" ? this.props.buttonWidth : 30; const progressBarHeight = typeof this.props.progressBarHeight != "undefined" ? this.props.progressBarHeight : 8; const progressBarPadding = typeof this.props.progressBarPadding != "undefined" ? this.props.progressBarPadding : 2; const progressBarContainerHeight = typeof this.props.progressBarContainerHeight != "undefined" ? this.props.progressBarContainerHeight : 12; const progressBarContainerColor = typeof this.props.progressBarContainerColor != "undefined" ? this.props.progressBarContainerColor : "#171a1d"; const buttonColor = typeof this.props.buttonColor != "undefined" ? this.props.buttonColor : "#FFFFFF"; const textColor = typeof this.props.textColor != "undefined" ? this.props.textColor : "#a8afb5"; const textSize = typeof this.props.textSize != "undefined" ? this.props.textSize : 12; const textContainerMinWidth = typeof this.props.textContainerMinWidth != "undefined" ? this.props.textContainerMinWidth : 38; const progressBarColor = typeof this.props.progressBarColor != "undefined" ? this.props.progressBarColor : "#5fcf80" const spacing = typeof this.props.spacing != "undefined" ? this.props.spacing : 5 return <View style={[{ backgroundColor: "#55616c", padding: 8, paddingTop: 6, paddingBottom: 6 }, typeof this.props.controlsStyle != "undefined" ? this.props.controlsStyle : {}, { flex: 1, width: "100%", marginTop: -1, }]}> <View style={{ justifyContent: 'space-between', flexDirection: "row", flex: 1 }}> <Pressable style={{ width: buttonWidth, height: buttonWidth, flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', }} onPress={() => { if (typeof this.props.liveVideo == "undefined" || (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == false)) { if (this.state.playerPlaying) { this.setPause(); } else { this.setPlaying(); } } }}> {( typeof this.props.liveVideo == "undefined" || (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == false) ) ? (this.state.playerPlaying ? <PauseSVG width={buttonWidth} color={buttonColor} /> : (this.state.playerPlaying == false && this.state.onPlay ? <ReplaySVG width={buttonWidth} color={buttonColor} /> : <PlaySVG width={buttonWidth} color={buttonColor} />) ) : <LivingSVG width={buttonWidth} color={buttonColor} />} </Pressable> <Pressable style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', width: buttonWidth, height: buttonWidth, marginLeft: spacing, marginRight: spacing }} onPress={() => { this.setVolume(); }}> { this.state.volumeOn ? <VolumeOnSVG width={buttonWidth} color={buttonColor} /> : <VolumeOffSVG width={buttonWidth} color={buttonColor} /> } </Pressable> <View style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', flexGrow: 1, flexBasis: 0 }}> <View style={{ justifyContent: 'space-between', flexDirection: "row", flex: 1, width: '100%' }}> <View style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', marginLeft: spacing, marginRight: spacing, minWidth: textContainerMinWidth }}> <Text style={{ fontSize: textSize, fontWeight: '600', color: textColor }}>{ (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == true) ? "--:--" : this.state.progressTime}</Text> </View> <View style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', paddingLeft: spacing, paddingRight: spacing, flexGrow: 1, flexBasis: 0 }}> <View style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', backgroundColor: progressBarContainerColor, borderRadius: progressBarContainerHeight / 2, width: "100%", height: progressBarContainerHeight }}> <View style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', borderRadius: progressBarContainerHeight / 2, width: "100%", paddingLeft: progressBarPadding, paddingRight: progressBarPadding }}> <View style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', backgroundColor: progressBarColor, borderRadius: progressBarHeight / 2, width: this.state.progressBar, height: progressBarHeight }} /> </View> </View> </View> <View style={{ flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', marginLeft: spacing, marginRight: spacing, fontWeight: '600', minWidth: textContainerMinWidth }}> <Text style={{ fontSize: textSize, color: textColor }}> { (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == true) ? "--:--" : this.state.totalTime } </Text> </View> </View> </View> </View> </View> } seekVideo(seekTo) { if (this.webVideoRef && this.webVideoRef.current) { this.webVideoRef.current.postMessage(JSON.stringify({ event: "seekVideo", data: { seekTo: seekTo } }), '*'); } } setVideoStop() { if (this.webVideoRef && this.webVideoRef.current) { this.webVideoRef.current.postMessage(JSON.stringify({ event: "stopVideo", data: null }), '*'); if (typeof this.props.videoStop != "undefined") { this.props.videoStop(); } } this.setState({ playerPlaying: false, onPlay: false, progressBar: "0%", progressTime: "00:00" }); } setVideoPause() { if (this.webVideoRef && this.webVideoRef.current) { this.webVideoRef.current.postMessage(JSON.stringify({ event: "pauseVideo", data: null }), '*'); if (this.state.playerPlaying && typeof this.props.videoPause != "undefined") { this.props.videoPause(); } } this.setState({ playerPlaying: false, onPlay: false }); } setVideoPlay() { if (this.webVideoRef && this.webVideoRef.current) { if (this.state.playerPlaying == false && this.state.onPlay) { this.setState({ progressBar: "0%", progressTime: "00:00" }); } this.webVideoRef.current.postMessage(JSON.stringify({ event: "playVideo", data: null }), '*'); if (this.state.playerPlaying == false && typeof this.props.videoPlaying != "undefined") { this.props.videoPlaying(); } } this.setState({ playerPlaying: true, onPlay: true }); } setPlayerReady() { this.setState({ playerReady: true, playerPlaying: false, onPlay: false, progressBar: "0%", progressTime: "00:00" }, this.living()); } setStop() { this.webVideoRef.current.postMessage(JSON.stringify({ event: "stopVideo", data: null }), '*'); this.setState({ playerPlaying: false, progressBar: "100%" }); } living() { if (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == true) { this.setState({ progressBar: "0%", progressTime: "00:00" }); let state = "playVideo"; this.webVideoRef.current.postMessage(JSON.stringify({ event: state, data: null }), '*'); if (typeof this.props.videoPlaying != "undefined") { this.props.videoPlaying(); } this.setState({ playerPlaying: true, onPlay: true }); } } setPause() { this.webVideoRef.current.postMessage(JSON.stringify({ event: "pauseVideo", data: null }), '*'); if (typeof this.props.videoPause != "undefined") { this.props.videoPause(); } this.setState({ playerPlaying: false, onPlay: false }); } setPlaying() { if (typeof this.props.debug != "undefined" && this.props.debug) { console.log("setPlaying ", this.state); } // let playerPlaying = true; // let onPlay = true; let progressBar = this.state.progressBar; let progressTime = this.state.progressTime; if (this.state.playerPlaying == false && this.state.onPlay) { progressTime = "00:00"; progressBar = "0%"; // this.setState({ progressBar: "0%", progressTime: "00:00" }); } this.webVideoRef.current.postMessage(JSON.stringify({ event: "playVideo", data: null }), '*'); // if (this.state.playerPlaying == false && typeof this.props.videoPlaying != "undefined") { if (typeof this.props.videoPlaying != "undefined") { this.props.videoPlaying(); } // playerPlaying = true; // onPlay = true; // } // if (this.state.playerPlaying && typeof this.props.videoPause != "undefined") { // this.props.videoPause(); // } this.setState({ playerPlaying: true, onPlay: true, progressBar: progressBar, progressTime: progressTime }); // let playerPlaying = false; // let onPlay = false; // if (this.state.playerPlaying == false && this.state.onPlay) { // this.setState({ progressBar: "0%", progressTime: "00:00" }); // } // let state = this.state.playerPlaying ? "pauseVideo" : "playVideo"; // this.webVideoRef.current.postMessage(JSON.stringify({ event: state, data: null }), '*'); // if (this.state.playerPlaying == false && typeof this.props.videoPlaying != "undefined") { // this.props.videoPlaying(); // playerPlaying = true; // onPlay = true; // } // if (this.state.playerPlaying && typeof this.props.videoPause != "undefined") { // this.props.videoPause(); // } // this.setState({ playerPlaying: playerPlaying, onPlay: onPlay }); } setVolume() { let state = this.state.volumeOn == false ? "volumeOn" : "volumeOff"; this.webVideoRef.current.postMessage(JSON.stringify({ event: state, data: null }), '*'); this.setState({ volumeOn: !this.state.volumeOn }); } getProgressTime(message, isInit = false) { if (typeof message.data != "undefined") { let totalTime = typeof message.data.duration != "undefined" ? message.data.duration : 0; let currentTime = typeof message.data.currentTime != "undefined" ? message.data.currentTime : 0; let progressTime = getTime(currentTime); if ((progressTime != "00:00" && progressTime != this.state.progressTime) || isInit) { if (typeof this.props.videoDuration != "undefined") { this.props.videoDuration({ currentTime: currentTime, duration: totalTime, isInit: isInit }); } if (currentTime > 0) { let progressBar = ((currentTime * 100) / this.state.duration) + "%"; if (typeof this.props.liveVideo != "undefined" && this.props.liveVideo == true) { progressBar = "100%"; let playerPlaying = true; let onPlay = true; if (this.state.playerPlaying != playerPlaying || this.state.onPlay != onPlay) { this.setState({ playerPlaying: playerPlaying, onPlay: onPlay }); } } this.setState({ progressBar: progressBar }); } this.setState({ progressTime: progressTime }); if (isInit) { const duration = getTime(totalTime); this.setState({ totalTime: duration, duration: totalTime }); } } } } renderLoading() { return this.state.playerReady == false && ( <View style={{ flex: 1, zIndex: 1000, width: "100%", aspectRatio: 16 / 9, backgroundColor: typeof this.props.loadingBackgoundColor != "undefined" ? this.props.loadingBackgoundColor : '#FEFEFE', alignItems: 'center', justifyContent: 'center', position: "absolute" }}> <ActivityIndicator animating={true} style={{ flex: 1, alignItems: 'center', justifyContent: 'center', alignSelf: "center" }} size={Platform.OS === "web" ? "large" : "small"} color={typeof this.props.loadingColor != "undefined" ? this.props.loadingColor : "#DDDDDD"} /> </View> ) } }