react-native-ffmpeg 将视频流保存为mp4

https://www.npmjs.com/package/react-native-ffmpeg 注意这个库需要 IOS 更高的版本,可以到此修改,IOS 9.0的新版本不支持

图片.png

官网有很多包名,可以指定自己想要的包

图片.png

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容