1import { Audio } from 'expo-av'; 2import React from 'react'; 3import { PixelRatio, Switch, Text, View } from 'react-native'; 4 5import Button from '../../components/Button'; 6import ListButton from '../../components/ListButton'; 7 8interface Mode { 9 interruptionModeAndroid: number; 10 shouldDuckAndroid: boolean; 11 playThroughEarpieceAndroid: boolean; 12 staysActiveInBackground: boolean; 13} 14 15interface State { 16 modeToSet: Mode; 17 setMode: Mode; 18} 19 20// See: https://github.com/expo/expo/pull/10229#discussion_r490961694 21// eslint-disable-next-line @typescript-eslint/ban-types 22export default class AudioModeSelector extends React.Component<{}, State> { 23 readonly state: State = { 24 modeToSet: { 25 interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS, 26 shouldDuckAndroid: true, 27 playThroughEarpieceAndroid: false, 28 staysActiveInBackground: false, 29 }, 30 setMode: { 31 interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS, 32 shouldDuckAndroid: true, 33 playThroughEarpieceAndroid: false, 34 staysActiveInBackground: false, 35 }, 36 }; 37 38 _applyMode = async () => { 39 try { 40 await Audio.setAudioModeAsync({ 41 ...this.state.modeToSet, 42 // iOS values don't matter, this is Android-only selector 43 allowsRecordingIOS: false, 44 playsInSilentModeIOS: false, 45 interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, 46 }); 47 const { modeToSet } = this.state; 48 this.setState({ setMode: modeToSet }); 49 } catch (error) { 50 alert(error.message); 51 } 52 }; 53 54 _modesEqual = (modeA: Mode, modeB: Mode) => 55 modeA.interruptionModeAndroid === modeB.interruptionModeAndroid && 56 modeA.playThroughEarpieceAndroid === modeB.playThroughEarpieceAndroid && 57 modeA.shouldDuckAndroid === modeB.shouldDuckAndroid && 58 modeA.staysActiveInBackground === modeB.staysActiveInBackground; 59 60 _setMode = (interruptionModeAndroid: number) => () => 61 this.setState(state => ({ modeToSet: { ...state.modeToSet, interruptionModeAndroid } })); 62 63 _renderToggle = ({ 64 title, 65 disabled, 66 valueName, 67 value, 68 }: { 69 title: string; 70 disabled?: boolean; 71 valueName: 72 | 'interruptionModeAndroid' 73 | 'shouldDuckAndroid' 74 | 'playThroughEarpieceAndroid' 75 | 'staysActiveInBackground'; 76 value?: boolean; 77 }) => ( 78 <View 79 style={{ 80 flexDirection: 'row', 81 alignItems: 'center', 82 justifyContent: 'space-between', 83 paddingVertical: 5, 84 borderBottomWidth: 1.0 / PixelRatio.get(), 85 borderBottomColor: '#cccccc', 86 }}> 87 <Text style={{ flex: 1, fontSize: 16 }}>{title}</Text> 88 <Switch 89 disabled={disabled} 90 value={value !== undefined ? value : Boolean(this.state.modeToSet[valueName])} 91 onValueChange={() => 92 this.setState(state => ({ 93 modeToSet: { ...state.modeToSet, [valueName]: !state.modeToSet[valueName] }, 94 })) 95 } 96 /> 97 </View> 98 ); 99 100 _renderModeSelector = ({ 101 title, 102 disabled, 103 value, 104 }: { 105 title: string; 106 disabled?: boolean; 107 value: number; 108 }) => ( 109 <ListButton 110 disabled={disabled} 111 title={`${this.state.modeToSet.interruptionModeAndroid === value ? '✓ ' : ''}${title}`} 112 onPress={this._setMode(value)} 113 /> 114 ); 115 116 render() { 117 return ( 118 <View style={{ marginTop: 5 }}> 119 {this._renderToggle({ 120 title: 'Should be ducked', 121 valueName: 'shouldDuckAndroid', 122 })} 123 {this._renderToggle({ 124 title: 'Play through earpiece', 125 valueName: 'playThroughEarpieceAndroid', 126 })} 127 {this._renderToggle({ 128 title: 'Stay active in background', 129 valueName: 'staysActiveInBackground', 130 })} 131 {this._renderModeSelector({ 132 title: 'Do not mix', 133 value: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX, 134 })} 135 {this._renderModeSelector({ 136 title: 'Duck others', 137 value: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS, 138 })} 139 <Button 140 title="Apply changes" 141 onPress={this._applyMode} 142 style={{ marginTop: 10 }} 143 disabled={this._modesEqual(this.state.modeToSet, this.state.setMode)} 144 /> 145 </View> 146 ); 147 } 148} 149