我在使用react-native-audio和react-native-sound实现聊天的发送语音信息的功能时遇到了录音完之后项目会从聊天页面跳到首页的bug



  • 如图 我现在实现这种效果
    0_1539229805555_497382237956099236.png
    0_1539229809763_766499931226474602.png
    但是当我手指松开之后就会卡住 并且项目就会从这个页面自动跳到项目的首页
    一下是我的代码:
    inputToolbar.js:

    /* eslint no-use-before-define: ["error", { "variables": false }] */
    
    import PropTypes from 'prop-types';
    import React from 'react';
    import { StyleSheet, View, Text, Keyboard, ViewPropTypes, TouchableOpacity, Animated, TextInput, Platform, PixelRatio } from 'react-native';
    
    // import Composer from './Composer';
    import { MIN_COMPOSER_HEIGHT, DEFAULT_PLACEHOLDER } from './Constant';
    import Send from './Send';
    import Actions from './Actions';
    import Color from './Color';
    import Audio from "./Audio";
    import Icon from 'react-native-vector-icons/MaterialIcons';
    
    const MODE_TEXT = "mode_text";
    const MODE_RECORD = "mode_record";
    
    export default class InputToolbar extends React.Component {
    
      constructor(props) {
        super(props);
    
        this.keyboardWillShow = this.keyboardWillShow.bind(this);
        this.keyboardWillHide = this.keyboardWillHide.bind(this);
    
        this.state = {
          position: 'absolute',
          mode: MODE_TEXT,
          focused: false,
          isEmoji: false,
          actionVisible: false,
          actionAnim: new Animated.Value(0),
          // selection: {start: 0, end: 0},
        };
      }
    
      componentWillMount() {
        this.keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
        this.keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
      }
    
      componentWillUnmount() {
        this.keyboardWillShowListener.remove();
        this.keyboardWillHideListener.remove();
      }
    
      keyboardWillShow() {
        if (this.state !== 'relative') {
          this.setState({
            position: 'relative',
          });
        }
      }
    
      keyboardWillHide() {
        if (this.state !== 'absolute') {
          this.setState({
            position: 'absolute',
          });
        }
      }
    
      renderActions() {
        if (this.props.renderActions) {
          return this.props.renderActions(this.props);
        } else if (this.props.onPressActionButton) {
          return <Actions {...this.props} />;
        }
        return null;
      }
    
      onActionsPress = () => {
        let actionVisible = this.state.actionVisible;
        if (actionVisible) {
          this.props.focusTextInput()
          /*Animated.timing(
            this.state.actionAnim,
            {toValue: 0,useNativeDriver: true,}
          ).start();*/
          // return;
        }else{
          this.props.blurTextInput()
        }
        
        actionVisible = !actionVisible;
        this.setState({actionVisible: actionVisible, isEmoji: false});
        if (actionVisible) {
          // this.actionBarHeight = ACTION_BUTTON_HEIGHT;
          // this.onHeightChange();
        }
        // Animated.timing(
        //   this.state.actionAnim,
        //   {toValue: 1,useNativeDriver: true,}
        // ).start();
      }
    
      _renderActions = () => {
        if (this.props.renderAccessory) {
          return this.props.renderAccessory(this.props)
        }
        return (
          <View style={{backgroundColor:'red',height:220}}>
            <Text>自定义</Text>
          </View>
        )
      }
    
      renderSend() {
        if (this.props.renderSend) {
          return this.props.renderSend(this.props);
        }
        return <Send onActionsPress={this.onActionsPress} {...this.props} />;
      }
    
      onContentSizeChange(e) {
        const { contentSize } = e.nativeEvent;
    
        // Support earlier versions of React Native on Android.
        if (!contentSize) return;
    
        if (
          !this.contentSize ||
          this.contentSize.width !== contentSize.width ||
          this.contentSize.height !== contentSize.height
        ) {
          this.contentSize = contentSize;
          this.props.onInputSizeChanged(this.contentSize);
        }
      }
    
      onChangeText(text) {
        this.props.onTextChanged(text);
      }
    
      renderComposer() {
        if (this.props.renderComposer) {
          return this.props.renderComposer(this.props);
        }
    
        return (
          <TextInput
            // ref = {search => console.warn(2222222)}
            // testID={this.props.placeholder}
            // accessible
            onFocus={this.onFocus}
            editable={true}
            maxLength={500}
            onBlur={this.handleBlurSearch.bind(this)}
            keyboardType='default'
            returnKeyType={'send'}
            autoCapitalize='none'
            accessibilityLabel={this.props.placeholder}
            placeholder={this.props.placeholder}
            placeholderTextColor={this.props.placeholderTextColor}
            multiline={this.props.multiline}
            onChange={(e) => this.onContentSizeChange(e)}
            onContentSizeChange={(e) => this.onContentSizeChange(e)}
            onChangeText={(text) => this.onChangeText(text)}
            style={[styles.textInput, this.props.textInputStyle, { height: this.props.composerHeight }]}
            autoFocus={this.state.focused}
            value={this.props.text}
            enablesReturnKeyAutomatically
            underlineColorAndroid="transparent"
            keyboardAppearance={this.props.keyboardAppearance}
            {...this.props.textInputProps}
            enablesReturnKeyAutomatically={true}
            // onSelectionChange={({nativeEvent: {selection}}) => this.setState({selection})}
          />
        )
      }
    
      onFocus = () => {
        this.setState({
          isEmoji: false,
          actionVisible: false,
        })
      }
    
      handleBlurSearch() {
        this.setState({focused: false});
      }
    
      renderAccessory() {
        if (this.props.renderAccessory) {
          return (
            <View style={[styles.accessory, this.props.accessoryStyle]}>{this.props.renderAccessory(this.props)}</View>
          );
        }
        return null;
      }
      
      renderRecordInput() {
        return <Audio onSend={this.props.onSend}/>
      }
    
      handleRecordMode() {
        const {isEmoji, actionVisible} = this.state;
        if (this.state.mode === MODE_RECORD) {
          return;
        }
        this.setState({
          // isEmoji: false,
          // actionVisible: false,
          focused: false,
          mode: MODE_RECORD
        });
        // if (isEmoji || actionVisible) {
        //   this.actionBarHeight = 0;
        //   this.onHeightChange();
        // }
        // NimUtils.onTouchVoice();
      }
      
      handleTextMode() {
        if (this.state.mode === MODE_TEXT) {
          return;
        }
        this.setState({mode: MODE_TEXT, focused: true,});
      }
    
      _renderEmojiButton = () => {
        const {isEmoji} = this.state;
        return (
          <TouchableOpacity style={{
            paddingLeft: 5,
            paddingRight: 5,
            alignSelf: "stretch",
            justifyContent: "center"
          }}
            onPress={this.handleEmojiOpen.bind(this)}>
            {
              isEmoji ? <Icon name={'keyboard'} size={30} color={'#666'}/>
                : <Icon name="insert-emoticon" size={30} color={'#666'}/>
            }
          </TouchableOpacity>
        )
      }
    
      handleEmojiOpen() {
        let isEmoji = this.state.isEmoji;
        if(isEmoji){
          this.props.focusTextInput()
        }else{
          this.props.blurTextInput()
        }
        isEmoji = !isEmoji;
        this.setState({
          isEmoji: isEmoji,
          // actionVisible: false,
          mode: MODE_TEXT
        });
      }
    
      render() {
        const { mode, actionVisible } = this.state;
        return (
          <View style={[styles.container, this.props.containerStyle, { position: this.state.position }]}>
            <View style={[styles.primary, this.props.primaryStyle]}>
              {mode === MODE_TEXT ?
              <TouchableOpacity style={{alignSelf: "stretch", justifyContent: "center", paddingLeft: 8}}
              onPress={this.handleRecordMode.bind(this)}>
              <Icon name={'keyboard-voice'} size={30} color={'#666'}/>
              </TouchableOpacity> :
              <TouchableOpacity style={{alignSelf: "stretch", justifyContent: "center", paddingLeft: 8}}
              onPress={this.handleTextMode.bind(this)}>
              <Icon name={'keyboard'} size={30} color={'#666'}/>
              </TouchableOpacity>}
              {mode === MODE_TEXT ? this.renderComposer() : this.renderRecordInput()}
              {this._renderEmojiButton()}
              {this.renderSend()}
            </View>
            { actionVisible && this._renderActions() }
          </View>
        );
      }
    
    }
    
    const styles = StyleSheet.create({
      container: {
        borderTopWidth: StyleSheet.hairlineWidth,
        borderTopColor: Color.defaultColor,
        backgroundColor: '#F4F4F6',
        bottom: 0,
        left: 0,
        right: 0,
      },
      primary: {
        flexDirection: 'row',
        alignItems: 'flex-end',
      },
      accessory: {
        height: 44,
      },
      textInput: {
        flex: 1,
        marginLeft: 5,
        marginRight: 5,
        fontSize: 16,
        lineHeight: 16,
        height: 35,
        marginTop: Platform.select({
          ios: 6,
          android: 0,
        }),
        marginBottom: Platform.select({
          ios: 5,
          android: 3,
        }),
        borderColor: '#DDDDDD',
        borderWidth: 1/PixelRatio.get(),
        marginTop: 5,
        borderRadius: 4,
        backgroundColor: '#fff'
      },
    });
    
    InputToolbar.defaultProps = {
      renderAccessory: null,
      renderActions: null,
      renderSend: null,
      renderComposer: null,
      containerStyle: {},
      primaryStyle: {},
      accessoryStyle: {},
      onPressActionButton: () => {},
    };
    
    InputToolbar.propTypes = {
      renderAccessory: PropTypes.func,
      renderActions: PropTypes.func,
      renderSend: PropTypes.func,
      renderComposer: PropTypes.func,
      onPressActionButton: PropTypes.func,
      containerStyle: ViewPropTypes.style,
      primaryStyle: ViewPropTypes.style,
      accessoryStyle: ViewPropTypes.style,
    };
    

    Audio.js:

    import React, {Component} from 'react';
    
    import {
    	StyleSheet,
    	Text,
    	View,
    	TouchableOpacity,
    	Platform,
    	PermissionsAndroid,
    	PanResponder
    } from 'react-native';
    
    import Sound from 'react-native-sound';
    import {AudioRecorder, AudioUtils} from 'react-native-audio';
    // import {Icon} from 'react-native-elements';
    import Icon from 'react-native-vector-icons/Ionicons';
    // import Styles from "./Styles/MessageScreenStyle";
    import toast from "./toast";
    import {Toast,Theme} from 'teaset'
    // import styleUtil from "../../../common/styleUtil";
    
    const maxDuration = 45;
    
    export default class Audio extends Component {
    	constructor(props) {
    		super(props)
    		this.state = {
    			paused: false,
    			recordingText: "",
    			opacity:'white',
    			recordingColor: "transparent",
    			text:'按住 说话',
    			currentTime: 0.0, //开始录音到现在的持续时间
    			recording: false, //是否正在录音
    			stoppedRecording: false, //是否停止了录音
    			finished: false, //是否完成录音
    			audioPath: AudioUtils.DocumentDirectoryPath + '/test.aac', //路径下的文件名
    			hasPermission: undefined, //是否获取权限
    		};
    		this.prepareRecordingPath = this.prepareRecordingPath.bind(this);
    		this._checkPermission = this._checkPermission.bind(this);
    		this._record = this._record.bind(this);
    		this._stop = this._stop.bind(this);
    		this._pause = this._pause.bind(this);
    		this._finishRecording = this._finishRecording.bind(this);
    		this._cancel = this._cancel.bind(this);
    	}
    	
    	prepareRecordingPath(audioPath) {
    		AudioRecorder.prepareRecordingAtPath(audioPath, {
    			SampleRate: 22050,
    			Channels: 1,
    			AudioQuality: "Low",
    			AudioEncoding: "aac",
    			AudioEncodingBitRate: 32000
    		});
    	}
    	
    	componentDidMount() {
    		this._checkPermission().then((hasPermission) => {
    			this.setState({hasPermission});
    			
    			if (!hasPermission) return;
    			
    			this.prepareRecordingPath(this.state.audioPath);
    			
    			AudioRecorder.onProgress = (data) => {
    				// console.log(data)
    				this.setState({
    					currentTime: Math.floor(data.currentTime)
    				}, () => {
    					if (this.state.currentTime >= maxDuration) {
    						toast.fail('说话时间太长了');
    						this._cancel(true)
    					}
    				});
    			};
    			AudioRecorder.onFinished = (data) => {
    				// Android callback comes in the form of a promise instead.
    				if (Platform.OS === 'ios') {
    					this._finishRecording(data.status === "OK", data.audioFileURL);
    				}
    			};
    		});
    	}
    	
    	componentWillUnmount() {
    		AudioRecorder.removeListeners()
    	}
    	
    	_checkPermission() {
    		if (Platform.OS !== 'android') {
    			return Promise.resolve(true);
    		}
    		
    		const rationale = {
    			'title': '访问麦克风',
    			'message': '是否允许软件访问您的麦克风以便可以录制音频'
    		};
    		
    		return PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, rationale)
    			.then((result) => {
    				console.log('Permission result:', result);
    				return (result === true || result === PermissionsAndroid.RESULTS.GRANTED);
    			});
    	}
    	
    	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.warn(error);
    		}
    	}
    	
    	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.warn(error);
    		}
    	}
    	
    	async _stop() {
    		if (!this.state.recording) {
    			console.warn('Can\'t stop, not recording!');
    			return;
    		}
    		
    		this.setState({stoppedRecording: true, recording: false, paused: false});
    		
    		try {
    			const filePath = await AudioRecorder.stopRecording();
    			
    			if (Platform.OS === 'android') {
    				this._finishRecording(true, filePath);
    			}
    			return filePath;
    		} catch (error) {
    			console.warn(error);
    		}
    	}
    	
    	static playVoice(audioPath) {
    		// These timeouts are a hacky workaround for some issues with react-native-sound.
    		// See https://github.com/zmxv/react-native-sound/issues/89.
    		setTimeout(() => {
    			let sound = new Sound(audioPath, '', (error) => {
    				if (error) {
    					console.warn('failed to load the sound', error);
    				}
    			});
    			
    			setTimeout(() => {
    				sound.play((success) => {
    					if (success) {
    						console.warn('successfully finished playing');
    					} else {
    						console.warn('playback failed due to audio decoding errors');
    					}
    				});
    			}, 100);
    		}, 100);
    	}
    	
    	async _record() {
    		if (this.state.recording) {
    			console.warn('Already recording!');
    			return;
    		}
    		
    		if (!this.state.hasPermission) {
    			console.warn('Can\'t record, no permission granted!');
    			return;
    		}
    		
    		if (this.state.stoppedRecording) {
    			this.prepareRecordingPath(this.state.audioPath);
    		}
    		
    		this.setState({
    			recording: true,
    			paused: false
    		});
    		
    		try {
    			const filePath = await AudioRecorder.startRecording();
    		} catch (error) {
    			console.warn(error);
    		}
    	}
    	
    	_finishRecording(didSucceed, filePath) {
    		this.setState({finished: didSucceed});
    		console.warn(`Finished recording of duration ${this.state.currentTime} seconds at path: ${filePath}`);
    	}
    	
    	_cancel(canceled) {
    		let filePath = this._stop();
    		if (canceled) {
    			return;
    		}
    		if (this.state.currentTime < 1) {
    			toast.info('说话时间太短了');
    			return;
    		}
    		
    		// console.warn(this.state.audioPath)
    		this.props.onSend({
    			voice: {
    				path: this.state.audioPath,
    				duration: this.state.currentTime
    			},
    			msgType: 'voice'
    		})
    	}
    	
    	handleLayout(e) {
    		this.refs.record.measure((x, y, w, h, px, py) => {
    			// console.log("record measure:", x, y, w, h, px, py);
    			this.recordPageX = px;
    			this.recordPageY = py;
    		});
    	}
    	
    	render() {
    		//android bug: https://github.com/facebook/react-native/issues/7221
    		let responder = {
    			onStartShouldSetResponder: (evt) => true,
    			onMoveShouldSetResponder: (evt) => true,
    			onResponderGrant: (evt) => {
    				this.setState({
    					opacity: "#c9c9c9",
    					recordingText: '手指上滑, 取消发送',
    					text:'松开 结束',
    					recordingColor: 'transparent'
    				}, _ => RecordView.show(this.state.recordingText, this.state.recordingColor));
    				this._record();
    			},
    			onResponderReject: (evt) => {
    			},
    			onResponderMove: (evt) => {
    				if (evt.nativeEvent.locationY < 0 ||
    					evt.nativeEvent.pageY < this.recordPageY) {
    					this.setState({
    						recordingText: '松开手指, 取消发送',
    						recordingColor: 'red'
    					}, _ => RecordView.show(this.state.recordingText, this.state.recordingColor));
    				} else {
    					this.setState({
    						recordingText: '手指上滑, 取消发送',
    						recordingColor: 'transparent'
    					}, _ => RecordView.show(this.state.recordingText, this.state.recordingColor));
    				}
    			},
    			onResponderRelease: (evt) => {
    				this.setState({
    					opacity: "white",
    					text:'按住 说话'
    				});
    				RecordView.hide();
    				let canceled;
    				if (evt.nativeEvent.locationY < 0 ||
    					evt.nativeEvent.pageY < this.recordPageY) {
    					canceled = true;
    				} else {
    					canceled = false;
    				}
    				this._cancel(canceled)
    			},
    			onResponderTerminationRequest: (evt) => true,
    			onResponderTerminate: (evt) => {
    				console.log("responder terminate")
    			},
    			
    		};
    		return (
    			<View style={{flex: 1}}>
    				<View style={[{flex: 1,flexDirection: 'column',justifyContent: 'center',marginLeft: 4,}, {padding: 4}]}>
    					<View
    						ref="record"
    						{...responder}
    						style={{
    							flex: 1,
    							justifyContent: 'center',
    							alignItems: 'center',
    							borderRadius: 5,
    							backgroundColor: this.state.opacity,
    							borderWidth: Theme.tvBarSeparatorWidth,
    							borderColor: '#CCC',
    						}}
    						onLayout={this.handleLayout.bind(this)}
    					>
    						<Text style={{lineHeight:43}}>{this.state.text}</Text>
    					</View>
    				</View>
    			</View>
    		);
    	}
    }
    
    class RecordView {
    	static key = null;
    	
    	static show(text, color) {
    		if (RecordView.key) RecordView.hide()
    		RecordView.key = Toast.show({
    			text: (
    				<Text style={{margin: 4, padding: 4, backgroundColor: color, color: '#fff'}}>
    					{text}
    				</Text>
    			),
    			icon: <Icon name={'ios-mic'} type={'ionicon'} size={45} color={'white'}/>,
    			position: 'center',
    			duration: 1000000,
    		});
    	}
    	
    	static hide() {
    		if (!RecordView.key) return;
    		Toast.hide(RecordView.key);
    		RecordView.key = null;
    	}
    }
    

    请问是我的代码造成的吗