react-native-ffmpeg 将视频流保存为mp4
2020-12-07 本文已影响0人
物联白菜
https://www.npmjs.com/package/react-native-ffmpeg 注意这个库需要 IOS 更高的版本,可以到此修改,IOS 9.0的新版本不支持
官网有很多包名,可以指定自己想要的包
Android:
安装
1、yarn add react-native-ffmpeg
react-native link react-native-ffmpeg
2、添加权限
<uses-sdk tools:overrideLibrary="com.arthenica.reactnative,com.arthenica.mobileffmpeg"/>
图片.png
3、android/build.gradle指定包名
ext {
reactNativeFFmpegPackage = "video"
}
图片.png
IOS:
安装
1、yarn add react-native-ffmpeg (有安装过的不用再执行)
2、在ios/Podfile中指定要安装的包为video(注意IOS的版本,不然会报错)
pod 'react-native-ffmpeg/video', :podspec => '../node_modules/react-native-ffmpeg/react-native-ffmpeg.podspec'
3、添加完之后进入cd ios =>pod install 或npx pod-install
开始使用
import {LogLevel, RNFFmpeg} from 'react-native-ffmpeg';
//保存视频
_saveVideo(){
this.setState({
isSave:true
})
let { saveVideo } = this.state
console.warn('客户端发送保存视频指令===',saveVideo)
clientFn.clientWrite(saveVideo,'保存视频0xB0')
// Toast.show('正在保存视频',{position:Toast.positions.CENTER})
var path = ''
if (Platform.OS === 'android') {
path = 'sdcard';
}else{
path = RNFS.LibraryDirectoryPath;
}
RNFS.exists('/sdcard/JGDYvideo').then((isExsit)=>{
exchangeBasic.log('目录是否存在',isExsit)
if(isExsit){
let now = moment().format('X')
RNFFmpeg.execute(`-i rtmp://58.200.131.2:1935/livetv/hunantv -acodec copy -vcodec copy -f mp4 /${path}/JGDYvideo/${this.props.deviceInfo[2]}_${now}.mp4`)
.then(
result => exchangeBasic.log("FFmpeg process exited with rc " + result.rc)
);
}else{
RNFS.mkdir('/sdcard/JGDYvideo').then((ss)=>{
exchangeBasic.log('创建文件夹JGDYvideo成功,成功后保存视频',ss)
let now = moment().format('X')
RNFFmpeg.execute(`-i rtmp://58.200.131.2:1935/livetv/hunantv -acodec copy -vcodec copy -f mp4 /${path}/JGDYvideo/${this.props.deviceInfo[2]}_${now}.mp4`)
.then(
result => exchangeBasic.log("FFmpeg process exited with rc " + result.rc)
);
}).catch((ee)=>{
alert('保存失败',ee)
})
}
}).catch((e)=>{
exchangeBasic.log('不存在这个目录',e)
alert('手机不存在JGDYvideo这个目录')
})
}
下面附上整个js页面的代码,因为后面又不需要这个库了,所以在里面我注释了,重新打开注释即可,RNFFmpeg.cancel() 为停止保存视频
/*
*开始打印
* */
import React, {Component} from 'react';
import {
View,
Text,
StyleSheet,
Image,
TouchableOpacity,
ScrollView,
TextInput,
ImageBackground,
Alert,
PanResponder, NativeModules, NativeEventEmitter
} from 'react-native'
import Util from '../common/util'
import Header from '../common/header'
import Progress from '../common/progressBar'
import storage from "../common/storage";
import exchangeBasic from "../common/exchangeBasic";
import clientFn from "../common/client";
// import {LogLevel, RNFFmpeg} from 'react-native-ffmpeg';
import Loading1 from "../common/loading";
import Toast from "react-native-root-toast";
import {RtmpView} from "react-native-rtmpview";
import RNFS from "react-native-fs";
import {connect} from "react-redux";
import Orientation from "react-native-orientation";
import {VlCPlayerView} from "react-native-vlc-media-player";
const moment = require('moment');
class printing extends Component {
constructor(props) {
super(props);
this.player = null;
this.vlcPlayer = React.createRef()
this.state = {
progress: 0,
indeterminate: true,
animated: false,
username:'',
password:'',
startPrinting:'',
stopPrinting:'',
saveVideo:'',
stopVideo:'',
statusText:'开始打印',
printingStatus:'',
deviceName:'',
isLoading:true,
num:1,
isSave:false
};
// const RNRtmpEventManager =
// NativeModules.RNRtmpEventManager;
//
// if (!(typeof RNRtmpEventManager === "undefined")) {
// const RNRtmpEventManager = new NativeEventEmitter(
// NativeModules.RNRtmpEventManager
// );
//
// RNRtmpEventManager.addListener(
// "RNRtmpEvent",
// (data) => this.handleRNRtmpEvent(data)
// );
//
// }
}
componentDidMount() {
// Orientation.lockToPortrait();
this.getCurrentTime()
let UserID = this.props.UserID
let instruction = []
let header = [exchangeBasic.tenTurntoHex(85)]
let zero = exchangeBasic.tenTurntoHex(0)
let chartLength = exchangeBasic.tenTurntoHex(1)
let instructions1 = [exchangeBasic.tenTurntoHex(160)] //设备状态0xa0
let instructions = [exchangeBasic.tenTurntoHex(161)] //查询打印状态0xa1
let instructions2 = [exchangeBasic.tenTurntoHex(163)] //开始打印0xa3
let instructions3 = [exchangeBasic.tenTurntoHex(164)] // 停止打印0xa4
// let instructions4 = [exchangeBasic.tenTurntoHex(164)] // 保存视频0xb0
let instructions4 = [0xB0] // 保存视频0xb0
let instructions5 = [0xB1] // 保存视频0xb0
let check = [exchangeBasic.tenTurntoHex(226)]
let end = [exchangeBasic.tenTurntoHex(170)]
// <0x55><UserID><0xA2><CHK><0xAA> 预览打印
let data1 = instruction.concat(header,UserID,zero,chartLength,instructions1,check,end)
let deviceStatus = clientFn.clientWrite1(data1)
let data2 = instruction.concat(header,UserID,zero,chartLength,instructions,check,end)
let printingStatus = clientFn.clientWrite1(data2)
let data3 = instruction.concat(header,UserID,zero,chartLength,instructions2,check,end)
let startPrinting = clientFn.clientWrite1(data3)
let data4 = instruction.concat(header,UserID,zero,chartLength,instructions3,check,end)
let stopPrinting = clientFn.clientWrite1(data4)
let data5 = instruction.concat(header,UserID,zero,chartLength,instructions4,check,end)
let saveVideo = clientFn.clientWrite1(data5)
let data6 = instruction.concat(header,UserID,zero,chartLength,instructions5,check,end)
let stopVideo = clientFn.clientWrite1(data6)
this.setState({
printingStatus:printingStatus,
startPrinting:startPrinting,
stopPrinting:stopPrinting,
saveVideo:saveVideo,
stopVideo:stopVideo,
})
// write([0x55, 0x07, 0xe4,0x00,0x01,0x20,0x00,0xAA])
clientFn.clientWrite(deviceStatus,'设备状态0xa0')
}
componentWillUnmount() {
// this.player.pause()
this.timer && clearInterval(this.timer)
this.timerSend && clearInterval(this.timerSend)
}
getCurrentTime() {
let timestamp = new Date().getTime()
let time = Util.transformToDate(timestamp)
this.setState({
time: time
})
this.timerInterval()
}
timerInterval() {
let that = this
this.timer = setInterval(function () {
let timestamp = new Date().getTime()
let time = Util.transformToDate(timestamp)
that.setState({
time: time
})
}, 1000)
}
//每秒发送一次
everySecond(printingStatus,stopVideo){
storage.remove({
key: 'resData',
});
this.timerSend = setInterval(()=>{
clientFn.clientWrite(printingStatus,'查询打印状态0xa1')
storage.load({
key: 'resData',
autoSync: true,
syncInBackground: true,
syncParams: {
extraFetchOptions: {
},
someFlag: true
}
}).then(res => {
exchangeBasic.log("读取到resData的缓存",res,res[8]/100);
let isSave = this.state.isSave
let progress = this.state.progress
// progress += 0.05;
if (progress < 1) {
this.setState({
progress: res[8]/100
})
} else {
this.setState({
animated: false,
progress: 1,
num:5,
isSave:false,
statusText:'重新打印'
})
if(isSave){
clientFn.clientWrite(stopVideo,'停止保存视频0xB1')
// RNFFmpeg.cancel(); //打印完毕自动保存视屏
}
Toast.show('打印完成',{
position: Toast.positions.CENTER,
backgroundColor:'#ccc',
textColor:'#000'
})
this.timerSend && clearInterval(this.timerSend)
}
})
.catch(err => {
exchangeBasic.log(err.message);
switch (err.name) {
case 'NotFoundError':
break;
case 'ExpiredError':
break;
}
});
},1000)
}
startPrint() {
let {startPrinting,stopPrinting,animated,printingStatus,isSave,stopVideo} = this.state
// console.warn('write to sever=================',printingStatus)
let that = this
this.timerSend && clearInterval(this.timerSend)
//停止打印
if(animated){
let that = this
that.timer && clearInterval(that.timer)
that.timerSend && clearInterval(that.timerSend)
that.setState({
statusText:'开始打印',
animated: false,
isSave:false
})
if(isSave){
clientFn.clientWrite(stopVideo,'停止保存视频0xB1')
// RNFFmpeg.cancel();
}
clientFn.clientWrite(stopPrinting,'停止打印0xa4')
}
//开始打印
if(!animated){
that.setState({
animated: true,
progress: 0,
statusText:'停止打印',
})
clientFn.clientWrite(startPrinting,'开始打印0xa3')
this.everySecond(printingStatus,stopVideo)
}
}
//保存视频
_saveVideo(){
this.setState({
isSave:true
})
let { saveVideo } = this.state
console.warn('客户端发送保存视频指令===',saveVideo)
clientFn.clientWrite(saveVideo,'保存视频0xB0')
// Toast.show('正在保存视频',{position:Toast.positions.CENTER})
// var path = ''
// if (Platform.OS === 'android') {
// path = 'sdcard';
// }else{
// path = RNFS.LibraryDirectoryPath;
// }
//
//
// RNFS.exists('/sdcard/JGDYvideo').then((isExsit)=>{
// exchangeBasic.log('目录是否存在',isExsit)
// if(isExsit){
// let now = moment().format('X')
// RNFFmpeg.execute(`-i rtmp://58.200.131.2:1935/livetv/hunantv -acodec copy -vcodec copy -f mp4 /${path}/JGDYvideo/${this.props.deviceInfo[2]}_${now}.mp4`)
// .then(
// result => exchangeBasic.log("FFmpeg process exited with rc " + result.rc)
// );
// }else{
// RNFS.mkdir('/sdcard/JGDYvideo').then((ss)=>{
// exchangeBasic.log('创建文件夹JGDYvideo成功,成功后保存视频',ss)
//
// let now = moment().format('X')
// RNFFmpeg.execute(`-i rtmp://58.200.131.2:1935/livetv/hunantv -acodec copy -vcodec copy -f mp4 /${path}/JGDYvideo/${this.props.deviceInfo[2]}_${now}.mp4`)
// .then(
// result => exchangeBasic.log("FFmpeg process exited with rc " + result.rc)
// );
// }).catch((ee)=>{
// alert('保存失败',ee)
// })
//
// }
// }).catch((e)=>{
// exchangeBasic.log('不存在这个目录',e)
// alert('手机不存在JGDYvideo这个目录')
// })
}
render() {
let progress = this.state.progress
let isSave = this.state.isSave
let left = progress * (Util.size.width - 80) - 25
return (
<View style={{flex: 1, backgroundColor: "#fff"}}>
<Header navigation={this.props.navigation} bgColor={"#fe2500"} fontColor={'#fff'} title={'打印'} num={this.state.num}/>
<View style={{padding: 15, flex: 1, justifyContent: 'space-between'}}>
<View style={{flex: 1, alignItems: 'center'}}>
<View style={styles.monitor_view}>
<Loading1 isLoading={this.state.isLoading} loadingText={'直播加载中…'}/>
{/*zhibo shikou */}
<VlCPlayerView
autoplay={true} //视屏播放结束时调用this.vlcPlayer.resume(false)方法 这个参数需要true
ref={ref => (this.vlcPlayer = ref)}
url={`rtmp://${this.props.deviceInfo[0]}:2022/live`} //视频url
Orientation={Orientation}
//BackHandle={BackHandle}
ggUrl="" // 广告url
showGG={false} // 是否显示广告
showTitle={true} // 是否显示标题
title="" // 标题
showBack={false} // 是否显示返回按钮
paused={false}
onLeftPress={()=>{}} // 返回按钮点击事件
// onOpen={this.onOpen.bind(this)}
// onPlaying={this.onPlaying.bind(this)}
// onProgress={this.onProgress.bind(this)}
// onPaused={this.onPaused.bind(this)}
// onStopped={this.onStopped.bind(this)}
// onIsPlaying={this.onIsPlaying.bind(this)}
// onBuffering={this.onBuffering.bind(this)}
// onEnded={this.onEnded.bind(this)}
// onError={this.onError.bind(this)}
startFullScreen={() => {
this.setState({
isFull: true,
});
}}
closeFullScreen={() => {
this.setState({
isFull: false,
});
}}
/>
{/*<RtmpView*/}
{/* style={styles.player}*/}
{/* shouldMute={false}*/}
{/* playOnResume={false}*/}
{/* pauseOnStop={true}*/}
{/* onLoadState={(data) => {*/}
{/* // this.handleLoadState(data);*/}
{/* exchangeBasic.log("直播加载视频",data)*/}
{/* exchangeBasic.log(*/}
{/* "React Native Received LoadState " + data.nativeEvent["state"]*/}
{/* );*/}
{/* }}*/}
{/* onFirstVideoFrameRendered={(data) => {*/}
{/* this.setState({*/}
{/* isLoading:false*/}
{/* })*/}
{/* }}*/}
{/* ref={e => { this.player = e; }}*/}
{/* url={`rtmp://${this.props.deviceInfo[0]}:2022/live`} />*/}
<View style={styles.txt_fixed}>
{/*<Text style={{fontSize: 12, color: "#fff"}}>局域网在线</Text>*/}
<Text style={{fontSize:12,color:"#fff"}}>{`直播地址为:rtmp://${this.props.deviceInfo[0]}:2022/live`}</Text>
<Text style={{fontSize: 12, color: "#fff"}}>{this.state.time}</Text>
</View>
</View>
{
isSave?
<View style={{flex:1}}>
{Util.loading('正在保存视频…')}
</View>
:
<View style={{paddingVertical: 50}}>
<Text style={{fontSize: 14, color: "#333"}}>准备打印</Text>
</View>
}
<View style={styles.radio_view}>
<View style={[styles.percent_view, {left: left}]}>
<View style={styles.txt_value}>
<Text style={{
fontSize: 12,
color: "#fff",
fontWeight: 'bold'
}}>{Math.round(this.state.progress * 100)}%</Text>
</View>
<Image
source={require('../images/triangle.png')}
resizeMode={'cover'}
style={{width: 7.5, height: 5}}
/>
</View>
<Progress
animated={false}
width={Util.size.width - 80}
height={10}
borderRadius={5}
progress={this.state.progress}
borderWidth={0}
unfilledColor={"#eeeeee"}
/>
</View>
<View style={styles.opera_view}>
<View style={styles.opera_item}>
<TouchableOpacity
onPress={() => this.startPrint()}>
<Image
source={require('../images/start_print.png')}
resizeMode={'cover'}
style={{width: 70, height: 70}}
/>
<Text style={{fontSize: 14, color: "#696969"}}>{this.state.statusText}</Text>
</TouchableOpacity>
</View>
<View style={styles.opera_item}>
<TouchableOpacity
onPress={() => this._saveVideo()}>
<Image
source={require('../images/save_video.png')}
resizeMode={'cover'}
style={{width: 70, height: 70}}
/>
<Text style={{fontSize: 14, color: "#696969"}}>保存视频</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</View>
)
}
}
var styles = StyleSheet.create({
player: {
position:'absolute',
width: '100%',
height: '100%',
},
monitor_view: {
width: Util.size.width - 30,
height: 300,
borderRadius: 10,
// backgroundColor: "#353535",
marginBottom: 15
},
txt_fixed: {
position: 'absolute',
top: 15,
width: Util.size.width - 30,
paddingHorizontal: 15,
// flexDirection: 'row',
alignItems: 'center',
justifyContent: "space-between"
},
opera_view: {
backgroundColor: "#fff",
borderRadius: 10,
flexDirection: "row",
justifyContent: 'space-around',
alignItems: 'flex-start',
paddingBottom: 40,
},
btn_style: {
width: 100,
height: 35,
borderRadius: 5,
borderWidth: 1,
borderColor: "#e0e0e0",
alignItems: "center",
justifyContent: 'center'
},
btn_style2: {
width: 100,
height: 35,
borderRadius: 5,
borderWidth: 1,
borderColor: "#fe2500",
backgroundColor: "#fe2500",
alignItems: "center",
justifyContent: 'center'
},
radio_view: {
// paddingVertical: 30,
alignItems: 'center',
justifyContent: 'center',
width: Util.size.width - 80,
paddingBottom: 20,
},
percent_view: {
width: 50,
height: 30,
alignItems: 'center',
justifyContent: 'center',
top: -30,
position: 'absolute'
},
txt_value: {
width: 50,
height: 25,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: "#fe2500",
borderRadius: 3
},
opera_item: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}
})
const mapStateToStore = (state) => {
return {
sever_DataArr: state.sever_DataArr,
isLoading: state.isLoading,
UserID: state.UserID,
deviceInfo: state.deviceInfo,
usernameText: state.usernameText,
passwordText: state.passwordText,
}
}
const dispatchToProps = (dispatch) => {
return {
login() {
const action = {
type: "sever_DataArr",
sever_DataArr: [1,2,3,4]
}
dispatch(action);
},
handlePutIntodate() {
const action = {
type: "add_num",
value: 1
}
dispatch(action);
}
}
}
export default connect(mapStateToStore, dispatchToProps)(printing)