基于react-native-audio与react-native-sound实现的一个录音与播放功能

先贴一波代码

/**
 * 录音与播放
 */
import React, {Component} from 'react';
import {
    StyleSheet,
    Text,
    View,
    TouchableOpacity,
    Platform,
    Slider,
    ToastAndroid,
    Alert,
    Dimensions,
    BackHandler
} from 'react-native';
import {AudioRecorder, AudioUtils} from 'react-native-audio';
import Sound from 'react-native-sound';
import Svg from "react-native-svg";
import DateUtil from '@utils/DateUtil';
import FileUtil from '@utils/FileUtil';
import {connect} from "react-redux";
import IconLib from "../../assets/svg/IconLib";

const {width} = Dimensions.get('window');

class SoundRecording extends Component {
    constructor(props) {
        super(props);
        this.state = {
            currentTime: 0,//
            duration: 0,//总时长
            recording: false,//是否录音
            paused: false,//是否暂停
            stoppedRecording: false,
            finished: false,
            play: false,
            audioPath: AudioUtils.MusicDirectoryPath + this.getAacName(),//生成的录音
            hasPermission: undefined,
        };
    }

    static navigationOptions = ({navigation, navigationOptions}) => {
        navigationOptions.header = null;
        return {
            ...navigationOptions
        };
    }

    prepareRecordingPath(audioPath) {
        AudioRecorder.prepareRecordingAtPath(audioPath, {
            SampleRate: 22050,
            Channels: 1,
            AudioQuality: "Low",
            AudioEncoding: "aac",
            AudioEncodingBitRate: 32000
        });
    }

    /**
     * 获取录音文件名
     * @returns {string}
     */
    getAacName() {
        return "/" + DateUtil.getYMDHms() + ".aac";
    }

    componentWillMount() {
        AudioRecorder.removeListeners();
    }

    handleBackPress = () => {
        this._quitRandom();
        return false;
    }

    componentDidMount() {
        BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
        AudioRecorder.requestAuthorization().then((isAuthorised) => {
            this.setState({hasPermission: isAuthorised});

            if (!isAuthorised) return;

            this.prepareRecordingPath(this.state.audioPath);

            AudioRecorder.onProgress = (data) => {
                this.setState({currentTime: Math.floor(data.currentTime)});
            };

            AudioRecorder.onFinished = (data) => {
                console.log("data" + JSON.stringify(data));
                if (Platform.OS === 'ios') {
                    this._finishRecording(data.status === "OK", data.audioFileURL, data.audioFileSize);
                }
            };
        });
    }

    /**
     * 暂停录音
     * @returns {Promise<void>}
     * @private
     */
    async _pause() {
        if (!this.state.recording) {
            console.warn('Can\'t pause, not recording!');
            return;
        }
        try {
            const filePath = await AudioRecorder.pauseRecording();
            this.setState({paused: true});
        } catch (error) {
            console.error(error);
        }
    }

    /**
     * 恢复录音
     * @returns {Promise<void>}
     * @private
     */
    async _resume() {
        if (!this.state.paused) {
            console.warn('Can\'t resume, not paused!');
            return;
        }
        try {
            await AudioRecorder.resumeRecording();
            this.setState({paused: false});
        } catch (error) {
            console.error(error);
        }
    }

    /**
     * 删除录音,后台主动停止录音再删除
     * @returns {Promise<void>}
     * @private
     */
    async _deleteRandom() {
        if (this.state.recording) {
            await AudioRecorder.pauseRecording();
            this.setState({paused: true, stoppedRecording: false})
        }
        Alert.alert('温馨提示', '确认删除录音吗?',
            [
                {
                    text: "是", onPress: () => {
                        AudioRecorder.stopRecording();
                        this.setState({
                            finished: false,
                            currentTime: 0,
                            stoppedRecording: true,
                            paused:false,
                            duration: 0,
                            recording: false
                        })
                    }
                },
                {text: "否"}
            ])
    }

    /**
     * 删除录音,释放资源
     * @returns {Promise<void>}
     * @private
     */
    async _delete() {
        if (this.state.recording) {
            ToastAndroid.show("无法删除,正在录音", ToastAndroid.SHORT);
            console.log('Can\'t delete, recording!');
            return;
        }
        if (this.state.play) {
            ToastAndroid.show("无法删除,正在播放", ToastAndroid.SHORT);
            console.log('Can\'t delete, playing!');
            return;
        }
        if (!this.state.stoppedRecording) {
            ToastAndroid.show("无录音", ToastAndroid.SHORT);
            return;
        }
        var flag = await FileUtil.existsFile(this.state.audioPath);
        if (!flag) {
            ToastAndroid.show("无录音", ToastAndroid.SHORT);
            console.log('no file');
            return;
        }
        Alert.alert('温馨提示', '确认删除录音吗?',
            [
                {
                    text: "是", onPress: () => {
                        FileUtil.deleteFile(this.state.audioPath);
                        this.setState({currentTime: 0.0, duration: 0}, () => {
                            this.sound.release()
                        });
                    }
                },
                {text: "否"}
            ])
    }

    /**
     * 暂停播放
     * @private
     */
    _pausePlay = () => {
        if (this.sound) {
            this.sound.pause();
        }

        this.setState({play: false});
    }

    /**
     * 完成录音
     * @returns {Promise<void>}
     * @private
     */
    async _stop() {
        if (!this.state.recording) {
            console.warn('Can\'t stop, not recording!');
            return;
        }

        try {
            const filePath = await AudioRecorder.stopRecording();
            this.sound = new Sound(this.state.audioPath, '', (error) => {
                if (error) {
                    console.log('failed to load the sound', error);
                    return;
                } else {
                    this.setState({
                        currentTime: 0,
                        stoppedRecording: true,
                        finished: true,
                        recording: false,
                        paused: false,
                        duration: this.sound.getDuration()
                    });
                }
            });
            return filePath;
        } catch (error) {
            console.error(error);
        }
    }

    /**
     * 用户随意退出录音时处理函数
     * @returns {Promise<void>}
     * @private
     */
    async _quitRandom() {
        if (!this.state.recording) {
            console.warn('Can\'t stop, not recording!');
            return;
        }

        try {
            const filePath = await AudioRecorder.stopRecording();
            return filePath;
        } catch (error) {
            console.error(error);
        }
    }

    /**
     * 播放录音
     * @returns {Promise<void>}
     * @private
     */
    async _play() {
        if (this.state.recording) {
            await this._stop();
        }
        if (!this.state.stoppedRecording) {
            ToastAndroid.show("无录音", ToastAndroid.SHORT);
            return;
        }
        var flag = await FileUtil.existsFile(this.state.audioPath);
        if (!flag) {
            ToastAndroid.show("无录音", ToastAndroid.SHORT);
            return;
        }
        this.setState({play: true});
        this.sound.play((success) => {
            if (success) {
                console.log('successfully finished playing');
                this.setState({
                    play: false,
                    currentTime: 0
                })
            } else {
                console.log('playback failed due to audio decoding errors');
            }
        });

        this.timeout = setInterval(() => {
            if (this.sound && this.sound.isLoaded()) {
                this.sound.getCurrentTime((seconds, isPlaying) => {
                    if (!isPlaying) {
                        clearInterval(this.timeout)
                    } else {
                        this.setState({currentTime: seconds});
                    }
                })
            }
        }, 100);

    }

    /**
     * 开始录音
     * @returns {Promise<void>}
     * @private
     */
    async _record() {
        if (this.state.recording) {
            ToastAndroid.show("正在录音", ToastAndroid.SHORT);
            console.warn('Already recording!');
            return;
        }

        if (!this.state.hasPermission) {
            ToastAndroid.show("无法录音,请授予权限", ToastAndroid.SHORT);
            console.warn('Can\'t record, no permission granted!');
            return;
        }

        if (this.state.stoppedRecording) {
            this.prepareRecordingPath(this.state.audioPath);
        }

        this.setState({recording: true, paused: false, play: false});

        try {
            const filePath = await AudioRecorder.startRecording();
            console.warn(filePath);
        } catch (error) {
            console.error(error);
        }
    }

    _finishRecording(didSucceed, filePath, fileSize) {
        this.setState({finished: didSucceed});
        console.log(`Finished recording of duration ${this.state.currentTime} seconds at path: ${filePath} and size of ${fileSize || 0} bytes`);
    }

    /**
     * Slider 值变化时对应事件
     * @param value
     */
    onSliderEditing = value => {
        if (this.sound) {
            this.sound.setCurrentTime(value);
            this.setState({currentTime: value});
        }
    };

    /**
     * 转换秒的时间格式
     * @param seconds
     * @returns {string}
     */
    getAudioTimeString(seconds) {
        const h = parseInt(seconds / (60 * 60));
        const m = parseInt(seconds % (60 * 60) / 60);
        const s = parseInt(seconds % 60);
        return ((h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s));
    }

    render() {
        let _this = this;
        const currentTimeString = this.getAudioTimeString(this.state.currentTime);
        const durationString = this.getAudioTimeString(this.state.duration);
        return (
            !_this.state.finished ?
                <View style={styles.container}>
                    <View style={{flex: 1}}>
                        <View style={styles.svgBackground}>
                            <Svg height={width} width={width}
                                 viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_BACKGROUND}</Svg>
                            <View style={{position: "absolute"}}>
                                <Text style={{fontSize: 40}}>{this.getAudioTimeString(_this.state.currentTime)}</Text>
                            </View>
                        </View>
                        <View
                            style={[styles.bottomContainer, {justifyContent: _this.state.recording ? "space-between" : "center"}]}>
                            {_this.state.recording ?
                                <View style={styles.bottomSvg}>
                                    <TouchableOpacity onPress={() => {
                                        this._deleteRandom();
                                    }}>
                                        <Svg height={34} width={34}
                                             viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_DELETE}</Svg>
                                        <Text style={styles.bottomText}>删除</Text>
                                    </TouchableOpacity>
                                </View> : null}
                            <View style={styles.bottomSvg}>
                                {_this.state.recording ?
                                    (_this.state.paused ?
                                        <TouchableOpacity onPress={() => {
                                            _this._resume();
                                        }}>
                                            <Svg height={87} width={87}
                                                 viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_START}</Svg>
                                            <Text style={styles.bottomText}>点击继续录音</Text>
                                        </TouchableOpacity> :
                                        <TouchableOpacity onPress={() => {
                                            _this._pause();
                                        }}>
                                            <Svg height={87} width={87}
                                                 viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING}</Svg>
                                            <Text style={styles.bottomText}>点击暂停录音</Text>
                                        </TouchableOpacity>)
                                    :
                                    (<TouchableOpacity onPress={() => {
                                        _this._record()
                                    }}>
                                        <Svg height={87} width={87}
                                             viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_START}</Svg>
                                        <Text style={styles.bottomText}>点击开始录音</Text>
                                    </TouchableOpacity>)}
                            </View>
                            {_this.state.recording ?
                                <View style={styles.bottomSvg}>
                                    <TouchableOpacity onPress={() => {
                                        _this._stop();
                                    }}>
                                        <Svg height={34} width={34}
                                             viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_SAVE}</Svg>
                                        <Text style={styles.bottomText}>保存</Text>
                                    </TouchableOpacity>
                                </View> : null}
                        </View>
                    </View>
                </View> :
                <View style={styles.container}>
                    <View style={{flex: 1}}>
                        <View style={styles.svgBackground}>
                            <Svg height={width} width={width}
                                 viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_BACKGROUND}</Svg>
                            <View style={[styles.playContainer, {display: _this.state.finished ? "flex" : "none"}]}>
                                <View style={{height: 116, top: 36}}>
                                    <Text style={{fontSize: 15}}>新录音</Text>
                                </View>
                                <View style={{flexDirection: "row"}}>
                                    <Slider style={styles.slider}
                                            onTouchStart={this.onSliderEditStart}
                                            onTouchEnd={this.onSliderEditEnd}
                                            onValueChange={this.onSliderEditing}
                                            value={_this.state.currentTime}
                                            thumbTintColor={"#2683ea"}
                                            minimumTrackTintColor={"#2683ea"}
                                            maximumTrackTintColor={"#dbdbdb"}
                                            maximumValue={_this.state.duration}/>
                                </View>
                                <View style={styles.timeContainer}>
                                    <Text style={styles.bottomText}>{currentTimeString}</Text>
                                    <Text style={styles.bottomText}>{durationString}</Text>
                                </View>
                                <View style={{height: 40}}/>
                                <View style={styles.bottomContainer2}>
                                    <View style={styles.bottomSvg}>
                                        <TouchableOpacity onPress={() => {
                                            _this._delete();
                                        }}>
                                            <Svg height={34} width={34}
                                                 viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_DELETE}</Svg>
                                            <View><Text style={styles.bottomText}>删除</Text></View>
                                        </TouchableOpacity>
                                    </View>
                                    <View style={styles.bottomSvg}>
                                        {_this.state.play ?
                                            <TouchableOpacity onPress={() => {
                                                _this._pausePlay();
                                            }}>
                                                <Svg height={34} width={34}
                                                     viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_PLAY}</Svg>
                                                <Text style={styles.bottomText}>暂停</Text>
                                            </TouchableOpacity> :
                                            <TouchableOpacity onPress={() => {
                                                _this._play();
                                            }}>
                                                <Svg height={34} width={34}
                                                     viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_PAUSE}</Svg>
                                                <Text style={styles.bottomText}>播放</Text>
                                            </TouchableOpacity>
                                        }

                                    </View>
                                    <View style={styles.bottomSvg}>
                                        <TouchableOpacity onPress={() => {
                                            _this.setState({
                                                finished: false,
                                                currentTime: 0,
                                                duration: 0
                                            })
                                        }}>
                                            <Svg height={34} width={34}
                                                 viewBox={"0 0 1024 1024"}>{IconLib.IC_SOUND_RECORDING_RERECORD}</Svg>
                                            <Text style={styles.bottomText}>重录</Text>
                                        </TouchableOpacity>
                                    </View>
                                </View>
                            </View>
                        </View>
                        <View style={styles.controls}>
                            <TouchableOpacity style={[styles.bottom, {backgroundColor: "#2683ea"}]}
                                              onPress={() => {
                                                  if (this.sound.isLoaded()) {
                                                      let audioPath = _this.state.audioPath;
                                                      let fileName = audioPath.substring(audioPath.lastIndexOf("/") + 1);
                                                      let data = {
                                                          fileName: fileName,
                                                          path: audioPath,
                                                          type: fileName.substring(fileName.lastIndexOf(".") + 1)
                                                      };
                                                      _this.props.navigation.state.params.callBack(data);
                                                  }
                                                  _this.props.navigation.goBack();
                                              }}>
                                <Text style={{fontSize: 16, color: "#ffffff"}}>确认</Text>
                            </TouchableOpacity>
                            <View style={{height: 16}}/>
                            <TouchableOpacity style={[styles.bottom, {backgroundColor: "#e2effc"}]}
                                              onPress={() => _this.props.navigation.goBack()}>
                                <Text style={{fontSize: 16, color: "#2683ea"}}>取消</Text>
                            </TouchableOpacity>
                        </View>
                    </View>
                </View>
        );
    }

    componentWillUnmount() {
        BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
        if (this.timeout) {
            clearInterval(this.timeout);
        }
        AudioRecorder.removeListeners()
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: "#fff",
        flexDirection: "column",
    },
    svgBackground: {
        height: width,
        backgroundColor: "#d0d0d0",
        justifyContent: "center",
        alignItems: "center"
    },
    bottomContainer: {
        flex: 1,
        paddingHorizontal: 50,
        width: width,
        backgroundColor: "#fff",
        flexDirection: "row",
        alignItems: "center"
    },
    bottomContainer2: {
        paddingHorizontal: 50,
        justifyContent: "space-between",
        alignItems: "center",
        flexDirection: "row"
    },
    bottomSvg: {justifyContent: "center", alignItems: "center"},
    bottomText: {fontSize: 15, color: "#606060"},
    playContainer: {
        width: width - 32,
        height: width,
        position: "absolute"
    },
    slider: {
        flex: 1,
        alignSelf: 'center',
        marginHorizontal: Platform.select({ios: 5})
    },
    timeContainer: {
        paddingHorizontal: 10,
        justifyContent: "space-between",
        alignItems: "center",
        flexDirection: "row"
    },
    controls: {
        flex: 1,
        paddingHorizontal: 14,
        width: width,
        backgroundColor: "#fff",
        justifyContent: "center",
        alignItems: "center"
    },
    bottom: {
        width: width - 28,
        height: 45,
        borderRadius: 10,
        justifyContent: "center",
        alignItems: "center"
    }
});

export default connect((state) => ({}), (dispatch) => ({}))(SoundRecording)

再来一波效果图

苦于不能上传视频,只好用图片代替了(刚开始用简书,不太清楚怎么添加视频,可惜我录好的视频了)


开始录音

录音中

录音播放

要点

1.防止用户随时退出录音时的处理

    componentDidMount() {
        BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
    }
    componentWillUnmount() {
        BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
    }

2.录音中用户删除录音的处理
相关处理请自行查找

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

推荐阅读更多精彩内容