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 interruptionModeIOS: number; 10 playsInSilentModeIOS: boolean; 11 allowsRecordingIOS: 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 interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS, 26 playsInSilentModeIOS: false, 27 allowsRecordingIOS: false, 28 staysActiveInBackground: false, 29 }, 30 setMode: { 31 interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS, 32 playsInSilentModeIOS: false, 33 allowsRecordingIOS: false, 34 staysActiveInBackground: false, 35 }, 36 }; 37 38 _applyMode = async () => { 39 try { 40 await Audio.setAudioModeAsync({ 41 ...this.state.modeToSet, 42 // Android values don't matter, this is iOS-only selector 43 shouldDuckAndroid: false, 44 interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX, 45 playThroughEarpieceAndroid: false, 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.interruptionModeIOS === modeB.interruptionModeIOS && 56 modeA.playsInSilentModeIOS === modeB.playsInSilentModeIOS && 57 modeA.allowsRecordingIOS === modeB.allowsRecordingIOS && 58 modeA.staysActiveInBackground === modeB.staysActiveInBackground; 59 60 _setMode = (interruptionModeIOS: number) => () => 61 this.setState(state => ({ modeToSet: { ...state.modeToSet, interruptionModeIOS } })); 62 63 _renderToggle = ({ 64 title, 65 disabled, 66 valueName, 67 value, 68 }: { 69 title: string; 70 disabled?: boolean; 71 valueName: 72 | 'interruptionModeIOS' 73 | 'playsInSilentModeIOS' 74 | 'allowsRecordingIOS' 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.interruptionModeIOS === value ? '✓ ' : ''}${title}`} 112 onPress={this._setMode(value)} 113 /> 114 ); 115 116 render() { 117 return ( 118 <View style={{ marginTop: 5 }}> 119 {this._renderToggle({ 120 title: 'Plays in silent mode', 121 valueName: 'playsInSilentModeIOS', 122 })} 123 {this._renderToggle({ 124 title: 'Allows recording', 125 valueName: 'allowsRecordingIOS', 126 disabled: !this.state.modeToSet.playsInSilentModeIOS, 127 value: !this.state.modeToSet.playsInSilentModeIOS ? false : undefined, 128 })} 129 {this._renderToggle({ 130 title: 'Stay active in background', 131 valueName: 'staysActiveInBackground', 132 disabled: !this.state.modeToSet.playsInSilentModeIOS, 133 value: !this.state.modeToSet.playsInSilentModeIOS ? false : undefined, 134 })} 135 {this._renderModeSelector({ 136 title: 'Mix with others', 137 value: Audio.INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS, 138 })} 139 {this._renderModeSelector({ 140 title: 'Do not mix', 141 value: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, 142 })} 143 {this._renderModeSelector({ 144 disabled: this.state.modeToSet.playsInSilentModeIOS === false, 145 title: 'Duck others', 146 value: Audio.INTERRUPTION_MODE_IOS_DUCK_OTHERS, 147 })} 148 <Button 149 title="Apply changes" 150 onPress={this._applyMode} 151 style={{ marginTop: 10 }} 152 disabled={this._modesEqual(this.state.modeToSet, this.state.setMode)} 153 /> 154 </View> 155 ); 156 } 157} 158