基于react-native-audio與react-native-sound實現(xiàn)的一個錄音與播放功能

先貼一波代碼

/**
 * 錄音與播放
 */
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);
        }
    }

    /**
     * 恢復(fù)錄音
     * @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('溫馨提示', '確認(rèn)刪除錄音嗎?',
            [
                {
                    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('溫馨提示', '確認(rèn)刪除錄音嗎?',
            [
                {
                    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);
        }
    }

    /**
     * 用戶隨意退出錄音時處理函數(shù)
     * @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("無法錄音植兰,請授予權(quán)限", 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 值變化時對應(yīng)事件
     * @param value
     */
    onSliderEditing = value => {
        if (this.sound) {
            this.sound.setCurrentTime(value);
            this.setState({currentTime: value});
        }
    };

    /**
     * 轉(zhuǎn)換秒的時間格式
     * @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}>點擊繼續(xù)錄音</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"}}>確認(rèn)</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.錄音中用戶刪除錄音的處理
相關(guān)處理請自行查找

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末废境,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌噩凹,老刑警劉巖朦促,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異栓始,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)血当,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門幻赚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人臊旭,你說我怎么就攤上這事落恼。” “怎么了离熏?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵佳谦,是天一觀的道長。 經(jīng)常有香客問我滋戳,道長钻蔑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任奸鸯,我火速辦了婚禮咪笑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娄涩。我一直安慰自己窗怒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布蓄拣。 她就那樣靜靜地躺著扬虚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪球恤。 梳的紋絲不亂的頭發(fā)上辜昵,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音碎捺,去河邊找鬼路鹰。 笑死,一個胖子當(dāng)著我的面吹牛收厨,可吹牛的內(nèi)容都是我干的晋柱。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼诵叁,長吁一口氣:“原來是場噩夢啊……” “哼雁竞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤碑诉,失蹤者是張志新(化名)和其女友劉穎彪腔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體进栽,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡德挣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了快毛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片格嗅。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖唠帝,靈堂內(nèi)的尸體忽然破棺而出屯掖,到底是詐尸還是另有隱情,我是刑警寧澤襟衰,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布贴铜,位于F島的核電站,受9級特大地震影響瀑晒,放射性物質(zhì)發(fā)生泄漏绍坝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一苔悦、第九天 我趴在偏房一處隱蔽的房頂上張望陷嘴。 院中可真熱鬧,春花似錦间坐、人聲如沸灾挨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劳澄。三九已至,卻和暖如春蜈七,著一層夾襖步出監(jiān)牢的瞬間秒拔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工飒硅, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留砂缩,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓三娩,卻偏偏與公主長得像庵芭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雀监,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容