How to use hls.js with react - reactjs

I need some help trying to figure how to use hls.js in react.
Let me explain the situation I have to fetch the m3u8 from an api I'm able to make it work from a basic html with the <script> tag but when i try to implement it on react it doesn't work any help is appreciated. This is what I've got so far:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Typography from "#material-ui/core/Typography";
import Grid from "#material-ui/core/Grid";
import ButtonBase from "#material-ui/core/ButtonBase";
import CircularProgress from "#material-ui/core/CircularProgress";
import Hls from "hls.js";
const styles = theme => ({
root: {
flexGrow: 1,
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
marginBottom: 24,
marginLeft: 24,
marginRight: 60
},
image: {
marginLeft: 24,
width: 200,
height: 200
},
img: {
display: "block",
width: 200,
height: 200,
maxWidth: "100%",
maxHeight: "100%"
},
detail: {
marginLeft: 16
},
progress: {
display: "flex",
justifyContent: "center",
alignItems: "center"
}
});
class Video extends Component {
constructor(props) {
super(props);
}
componentWillReceiveProps(props) {
if (props.episode && this.player) {
var hlsUrl = props.episode.assets.hls;
var video = this.player;
if (video.canPlayType("application/vnd.apple.mpegurl")) {
// If HLS is natively supported, let the browser do the work!
video.src = "hlsUrl";
video.addEventListener("loadedmetadata", function() {
video.play();
});
} else if (Hls.isSupported()) {
// If the browser supports MSE, use hls.js to play the video
var hls = new Hls({
// This configuration is required to insure that only the
// viewer can access the content by sending a session cookie
// to api.video service
xhrSetup: function(xhr, url) {
xhr.withCredentials = true;
}
});
hls.loadSource(hlsUrl);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
} else {
alert("Please use a modern browser to play the video");
}
}
}
handleSerieClick = () => {
this.props.history.push("/" + this.props.serie.apiName);
};
_onTouchInsidePlayer() {
if (this.player.paused) {
this.player.play();
} else {
this.player.pause();
}
}
render() {
const { classes, theme } = this.props;
if (this.props.episode) {
const { assets, title, description, videoId } = this.props.episode;
return (
<Grid className={classes.root} item xs={12}>
<video
controls
onClick={this._onTouchInsidePlayer}
ref={player => (this.player = player)}
autoPlay={true}
/>
</Grid>
);
} else {
return (
<Grid className={classes.progress} item xs={12}>
<CircularProgress size={100} />
</Grid>
);
}
}
}
export default withStyles(styles, { withTheme: true })(Video);
This is the code that works with <script> tag
<script src="https://cdn.jsdelivr.net/npm/hls.js#latest"></script>
<video id="video"></video>
<script>
var hlsUrl = 'https://cdn.libcast.net/stream/3de8ff01-18f7-4262-a1f2-abeeb9bb962b/hls/manifest.hls';
var video = document.getElementById('video');
if (video.canPlayType('application/vnd.apple.mpegurl')) {
// If HLS is natively supported, let the browser do the work!
video.src = 'hlsUrl';
video.addEventListener('loadedmetadata',function() {
video.play();
});
} else if (Hls.isSupported()) {
// If the browser supports MSE, use hls.js to play the video
var hls = new Hls({
// This configuration is required to insure that only the
// viewer can access the content by sending a session cookie
// to api.video service
xhrSetup: function(xhr, url) { xhr.withCredentials = true; }
});
hls.loadSource(hlsUrl);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED,function() {
video.play();
});
} else {
alert('Please use a modern browser to play the video');
}
</script>
I pass the hls source from a parent component in the props and in componentWillRecieveProps i try to use the source to run the player
EDIT
THe problem seems to be that <video> tag is undefined when I try to apply the source.

Initing hls in componentWillReceiveProps is probably "too early". Refs are created during the render execution so your this.video is probably null at that time.
Try moving your logic into componentDidMount (if you pass proper props from the beginnig) or at least componentDidUpdate.

Related

REACT NATIVE MAPS : I am unable to render markers for my react native map?

import React from 'react';
import { StyleSheet, Text, View, Component, Dimensions } from 'react-native';
import { fb } from "./Firebase/data";
import firestore from 'firebase/firestore';
import MapView, { Marker } from 'react-native-maps';
export default class App extends React.Component {
state = {
nodes: []
}
componentDidMount(){
const db = fb.firestore()
.collection('nodes')
.get()
.then( snapshot => {
const nodes = []
snapshot.forEach( doc =>{
const data = doc.data()
nodes.push(data)
});
this.setState({
nodes : nodes
})
});
}
render() {
console.log(this.state.nodes.lat)
return (
<MapView
style={{ ...StyleSheet.absoluteFillObject }}
initialRegion={{
latitude: 28.5450,
longitude: 77.1926,
latitudeDelta: 0.0039922,
longitudeDelta: 0.0039421,
}} >
{this.state.nodes.map((marker, index) => {
<Mapview.Marker
key = {index}
coordinate = {{ latitude : marker.lat},
{ longitude : marker.lon}}
title = { marker.location }
/>
}
)
}
</MapView>
);
}
}
console.disableYellowBox = true;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
mapStyle: {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
},
})
This is my parent code for the react native project. If needed I can also show the export fb code but I am confident it is working
Can someone please tell me what's wrong here since the data from firebase is available on the console when I don't invoke Mapview but just when I invoke it Mapview the console shows Array [].
I just want to render the markers on the map
You did not return anything on map :
Wrong : this.state.nodes.map(()=>{}),
True : this.state.nodes.map(()=>())
True v2 : this.state.nodes.map(()=>{ return ... })
Final code should look like :
{this.state.nodes.map((marker, index) => (
<Mapview.Marker
key={index}
coordinate={({ latitude: marker.lat }, { longitude: marker.lon })}
title={marker.location}
/>
))}

Prevent Reload in React Native WebView

I'm running a separate web app inside a WebView component, within a React Native app, and I'm trying to get them communicating properly.
React Native to WebView works fine. I can call webView.postMessage(...) and receive it in document.addEventListener("message", ...) without any problems.
However, when I try to go the other way (WebView to Native) the call to window.postMessage triggers a url change via window.location which seems to reload the entire WebView, and breaks the routing solution inside it.
The react-native-community/react-native-webview component seems to have the same problem.
Is there any way to message the native app from inside a web view without changing the URL or causing a page reload?
You can use onShouldStartLoadWithRequest props for IOS Example:-
import React, {Component, useCallback} from 'react';
import {
BackHandler,
Platform,
SafeAreaView,
ActivityIndicator,
StyleSheet,
Dimensions,
Linking,
} from 'react-native';
import {WebView} from 'react-native-webview';
import Spinner from 'react-native-loading-spinner-overlay';
class SupportPayWebView extends Component {
constructor(props) {
super(props);
}
webView = {
canGoBack: false,
ref: null,
};
loader = {
show: true,
};
onAndroidBackPress = () => {
if (this.webView.canGoBack && this.webView.ref) {
this.webView.ref.goBack();
return true;
}
return false;
};
componentWillMount() {
if (Platform.OS === 'android') {
BackHandler.addEventListener(
'hardwareBackPress',
this.onAndroidBackPress,
);
} else {
BackHandler.addEventListener('hardwareBackPress', this.backHandler);
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
BackHandler.removeEventListener('hardwareBackPress');
} else {
BackHandler.removeEventListener('hardwareBackPress', this.backHandler);
}
}
backHandler = () => {
this.webView.ref.goBack();
return true;
};
ActivityIndicatorLoadingView() {
return (
<ActivityIndicator
color="#009688"
size="large"
style={styles.ActivityIndicatorStyle}
/>
);
}
onShouldStartLoadWithRequest = (navigator) => {
this.webView.ref.stopLoading();
return false;
};
render() {
return (
<>
<WebView
style={styles.WebViewStyle}
onLoadEnd={() => {
this.loader.show = false;
}}
injectedJavaScript={
"const meta = document.createElement('meta'); meta.setAttribute('content', 'width=device-width, initial-scale=0.5, maximum-scale=0.5, user-scalable=0'); meta.setAttribute('name', 'viewport'); document.getElementsByTagName('head')[0].appendChild(meta); "
}
scalesPageToFit={true}
automaticallyAdjustContentInsets={true}
scrollEnabled={true}
javaScriptEnabled={true}
domStorageEnabled={true}
renderLoading={this.ActivityIndicatorLoadingView}
startInLoadingState={true}
bounces={false}
source={{uri: '}}
ref={(webView) => {
this.webView.ref = webView;
}}
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
sharedCookiesEnabled={true}
// scalesPageToFit={false}
javaScriptEnabledAndroid={true}
userAgent="Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
// decelerationRate="normal"
/>
</>
// </SafeAreaView>
);
}
}
const styles = StyleSheet.create({
WebViewStyle: {
justifyContent: 'center',
alignItems: 'center',
marginTop: Platform.OS === 'ios' ? 35 : 0,
width: '100%',
height: '100%',
resizeMode: 'cover',
flex: 1,
},
ActivityIndicatorStyle: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
},
});

FineUploader not working after first upload

I've implemented FineUploader in my React app to upload files to my Azure Blob Storage and it's working fine except for one issue.
After uploading a file successfully, if I try uploading another one, FineUploader is not allowing me to select a new file. Clicking the select button opens up the dialog box to let me select a file but clicking the file to select it simply does nothing. I don't see any errors either.
Any idea what could be causing this issue?
Here's my implementation of FineUploader in my app:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import FineUploaderAzure from 'fine-uploader-wrappers/azure'
// Components
import Gallery from './gallery/index';
// Actions
import * as fileActions from '../../../../actions/file-actions';
// Instanciate FineUploader
const uploader = new FineUploaderAzure({
options: {
cors: {
expected: true,
sendCredentials: false
},
signature: {
endpoint: 'https://api.myapp.com/files/get/sas'
},
request: {
endpoint: 'https://myaccount.blob.core.windows.net/my-container'
},
validation: {
itemLimit: 1
}
}
})
class FileUploader extends Component {
constructor(props) {
super(props);
this.saveFileInfo = this.saveFileInfo.bind(this);
}
componentDidMount() {
uploader.on('complete', (id, name, responseJSON, xhr) => {
const originalName = uploader.methods.getName(id);
const blobName = uploader.methods.getBlobName(id);
const fileSize = uploader.methods.getSize(id);
this.saveFileInfo(originalName, blobName, fileSize);
})
}
saveFileInfo(fileName, blobName, fileSize) {
// Gather necessary information
const accountId = this.props.accountId;
const id = this.props.id;
const folderId = this.props.activeFolder.id;
const files = [
{
fileName: blobName,
displayName: fileName,
fileSize: fileSize
}
];
// Call backend API to save file info in database
this.props.actions.createFileRecords(accountId, bizObject, id, privilegeLevel, folderId, files);
// Close modal
const modalId = this.props.modalId;
return this.props.handleClose(modalId, false);
}
render() {
return (
<div style={{ position: 'fixed', zIndex: 250000990 }}>
<div className="modal-wrapper">
<div className="height-100 width-100" style={{ background: 'transparent', position: 'absolute', left: '0', top: '0' }}></div>
<div className="modal-window vertical-center">
<div className="modal-controls padding-right-20 padding-top-10" style={{ height: '50px', position: 'absolute', right: '0', top: '0', lineHeight: '40px', borderColor: 'transparent !important' }}>
<a className="modal-control mc-help"></a>
<a className="modal-control mc-close" onClick={e => this.props.handleClose(this.props.modalId, false)}></a>
</div>
<div className="width-100 height-100 border-radius border-black-w1-1" style={{ overflow: 'hidden', background: 'black !important', borderColor: 'black' }}>
<Gallery uploader={uploader} onComplete={this.handleFileUpload} />
</div>
<div style={{ position: 'absolute', bottom: '17px', left: '17px' }}>
{/* Privilege Level Selector */}
{this.renderPrivilegeLevels()}
<span className="app-btn app-btn-lg margin-left-20">Uploading into Folder: <strong>{this.props.activeFolder.name}</strong></span>
</div>
</div>
</div>
</div>
)
}
}
function mapStateToProps(state, ownProps) {
return {
modalId: ownProps.modalId,
accountId: ownProps.accountId,
id: ownProps.id,
folders: ownProps.folders,
activeFolder: ownProps.activeFolder,
fileUpload: state.files.fileUpload,
errors: state.files.errors,
handleClose: ownProps.handleClose
}
}
function mapDispatchToProps(dispatch, ownProps) {
return {
actions: bindActionCreators(fileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader)
As explained in the comment the itemLimit: 1 option is limiting the total number of upload for that Uploader to 1.
Since what you are trying to achieve is actually avoiding multiple upload at once you can use the option multiple: false to block selection of multiple files.
Also, to avoid users adding more files while others are still uploading, you can use a custom validation where you check if some other files are in progress, for example:
options: {
..., //other options
callbacks: {
onValidate: function (file) {
if(getInProgress() > 0){
return false;
}
return true;
}
}
Note that the onValidate events is called before the default Fine Uploader validators

Why won't my Custom React Native Component Import Correctly

Currently, I have a simple React Native Expo app setup. I have two components App and QRreader.
I am trying to import the QRreader component into my main App component.
The Main App component code...
import React, { Component } from 'react';
import { Button, Text, View, StyleSheet } from 'react-native';
import { Constants, WebBrowser } from 'expo';
import QRreader from './qr';
export default class App extends Component {
state = {
result: null,
};
render() {
return (
<View style={styles.container}>
<QRreader/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});
The QR component code...
import React, { Component } from 'react';
import { Text, View, StyleSheet, Alert } from 'react-native';
import { Constants, BarCodeScanner, Permissions } from 'expo';
export default class QRreader extends Component {
state = {
hasCameraPermission: null
};
componentDidMount() {
this._requestCameraPermission();
}
_requestCameraPermission = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({
hasCameraPermission: status === 'granted',
});
};
_handleBarCodeRead = data => {
Alert.alert(
'Scan successful!',
JSON.stringify(data)
);
};
render() {
return (
<View style={styles.container}>
{this.state.hasCameraPermission === null ?
<Text>Requesting for camera permission</Text> :
this.state.hasCameraPermission === false ?
<Text>Camera permission is not granted</Text> :
<BarCodeScanner
onBarCodeRead={this._handleBarCodeRead}
style={{ height: 200, width: 200 }}
/>
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
}
});
I tried different variations of the import using "./" "." "qr.js" "qr"
Im getting an error Unable to resolve module "qr.js" Module does not exist in the main module map.
My file structure is Here
You haven't registered your main module yet.
AppRegistry.registerComponent('Main', () => App); Please add this line to the bottom of your class and check if the problem persists.
hmm...so it looked like I had to restart the Expo project for it to work without adding any additional code.
Just out of curiosity?
Where would I add AppRegistry.registerComponent('Main', () => App); exactly? and why would I have to do this?

react-dnd not detecting hover and drop events

I'm having trouble getting react-dnd to to work. Specifically, while I can confirm dragging is being detected properly, my droppable targets are not detecting hover or drop events. I've been following the example at http://survivejs.com/react/implementing-kanban/drag-and-drop/ to create a draggable item. I've tried to use a combination of the same examples and the official examples from the official repo to create a DropTarget to accept the draggable. However, my DropTarget is giving no indication that it is detecting the draggable. My code below has multiple debugger statements to indicate if code is being reached, but none of them ever are.
I suspect that the compose call at the end might be the problem, but I'm following Dan Abramov's example here. Just to add to the problem, the React inspector in Chrome dev tools lets me see the itemType state variable change as I drag an item. However, both the canDrop and isOver state variables remain false. I'd appreciate any help to get this to work.
import { findDOMNode } from 'react-dom';
import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Paper from 'material-ui/Paper';
import FaDelete from 'react-icons/lib/fa/trash-o';
import RaisedButton from 'material-ui/RaisedButton';
import FaEdit from 'react-icons/lib/fa/star';
import actions from '../actions/actions';
import TextField from 'material-ui/TextField';
import { connect } from 'react-redux';
//import EmojiPickerPopup from './EmojiPickerPopup';
import RenderIf from 'render-if';
import globals from '../globals';
import { DropTarget } from 'react-dnd';
import { compose } from 'redux';
const locationItemContainer = {
display: 'flex',
flexDirection: 'column',
backgroundColor: 'lightgoldenrodyellow',
border: '1px solid Moccasin',
width: "33%",
maxHeight: "15em"
}
const controlsContainer = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-around',
width: "100%"
}
const paperStyle = {
padding: '8px 4px',
display: 'flex',
flexDirection: "column",
alignItems: 'center',
justifyContent: 'center',
width: "100%",
height: "100%"
};
class LocationItemComponent extends Component {
constructor(props, context) {
super(props, context);
this.state = {
locationMarkers: []
}
}
componentWillReceiveProps(nextProps) {
if (!this.props.isOver && nextProps.isOver) {
// You can use this as enter handler
debugger
}
if (this.props.isOver && !nextProps.isOver) {
// You can use this as leave handler
debugger
}
if (this.props.isOverCurrent && !nextProps.isOverCurrent) {
// You can be more specific and track enter/leave
// shallowly, not including nested targets
debugger
}
}
nameChanged = (id, event, value) => {
this.props.dispatch(actions.storyMapActions.updateMarkerName(value, id));
}
deleteMarker = (id) => {
this.props.dispatch(actions.storyMapActions.deleteMarker(id));
}
showEmojiPicker = (id, event) => {
this.props.dispatch(actions.modalsActions.showEmojiPicker(id, event.currentTarget))
}
render() {
const { isOver, canDrop, connectDropTarget } = this.props;
if (isOver) {
console.log("is over");
}
return connectDropTarget(
<div style={locationItemContainer}>
<MuiThemeProvider>
<Paper zDepth={5}
style={paperStyle}
rounded={false}>
<TextField
id="markerName"
hintText="marker Name"
onChange={this.nameChanged.bind(this, this.props.marker.id)}
value={this.props.marker.name}
underlineFocusStyle={{ color: globals.textUnderlineColor }}
/>
<div style={controlsContainer}>
<RaisedButton
icon={<FaEdit />}
primary={true}
onClick={this.showEmojiPicker.bind(this, this.props.marker.id)} />
<RaisedButton
icon={<FaDelete />}
secondary={true}
onClick={this.deleteMarker.bind(this, this.props.marker.id)} />
</div>
</Paper>
</MuiThemeProvider>
</div>
);
}
}
const mapStateToProps = (state) => {
return Object.assign({}, { state: state });
}
const locationTarget = {
canDrop(props, monitor) {
debugger;
// You can disallow drop based on props or item
const item = monitor.getItem();
return true;
},
hover(props, monitor, component) {
debugger;
// This is fired very often and lets you perform side effects
// in response to the hover. You can't handle enter and leave
// hereā€”if you need them, put monitor.isOver() into collect() so you
// can just use componentWillReceiveProps() to handle enter/leave.
// You can access the coordinates if you need them
const clientOffset = monitor.getClientOffset();
const componentRect = findDOMNode(component).getBoundingClientRect();
// You can check whether we're over a nested drop target
const isJustOverThisOne = monitor.isOver({ shallow: true });
// You will receive hover() even for items for which canDrop() is false
const canDrop = monitor.canDrop();
},
drop(props, monitor, component) {
debugger;
if (monitor.didDrop()) {
// If you want, you can check whether some nested
// target already handled drop
debugger
return;
}
// Obtain the dragged item
const item = monitor.getItem();
// You can do something with it
//ChessActions.movePiece(item.fromPosition, props.position);
// You can also do nothing and return a drop result,
// which will be available as monitor.getDropResult()
// in the drag source's endDrag() method
return { moved: true };
}
};
const collect = (connect, monitor) => {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
itemType: monitor.getItemType()
};
}
export default compose(
connect(mapStateToProps),
DropTarget(globals.itemTypes.LOCATION_ITEM, locationTarget, collect)
)(LocationItemComponent);

Resources