1import { diff } from 'deep-object-diff'; 2import { Asset } from 'expo-asset'; 3import { Audio, AVPlaybackStatus } from 'expo-av'; 4import React from 'react'; 5import { StyleProp, ViewStyle } from 'react-native'; 6 7import Player from './Player'; 8 9type PlaybackSource = 10 | number 11 | { 12 uri: string; 13 overrideFileExtensionAndroid?: string; 14 headers?: { 15 [fieldName: string]: string; 16 }; 17 } 18 | Asset; 19 20interface Props { 21 style?: StyleProp<ViewStyle>; 22 source: PlaybackSource; 23} 24 25interface State { 26 isLoaded: boolean; 27 isLooping: boolean; 28 isPlaying: boolean; 29 errorMessage?: string; 30 positionMillis: number; 31 durationMillis: number; 32 rate: number; 33 volume: number; 34 isMuted: boolean; 35 shouldCorrectPitch: boolean; 36} 37 38export default class AudioPlayer extends React.Component<Props, State> { 39 readonly state: State = { 40 isMuted: false, 41 isLoaded: false, 42 isLooping: false, 43 isPlaying: false, 44 positionMillis: 0, 45 durationMillis: 0, 46 rate: 1, 47 volume: 1, 48 shouldCorrectPitch: false, 49 }; 50 51 _sound?: Audio.Sound; 52 private prevStatus?: AVPlaybackStatus; 53 54 componentDidMount() { 55 this._loadSoundAsync(this.props.source); 56 } 57 58 componentDidUpdate(prevProps: Props) { 59 if (prevProps.source !== this.props.source) { 60 this._loadSoundAsync(this.props.source); 61 } 62 } 63 64 componentWillUnmount() { 65 if (this._sound) { 66 this._sound.unloadAsync(); 67 } 68 } 69 70 _loadSoundAsync = async (source: PlaybackSource) => { 71 const soundObject = new Audio.Sound(); 72 try { 73 await soundObject.loadAsync(source, { progressUpdateIntervalMillis: 150 }); 74 soundObject.setOnPlaybackStatusUpdate(this._updateStateToStatus); 75 const status = await soundObject.getStatusAsync(); 76 this._updateStateToStatus(status); 77 this._sound = soundObject; 78 } catch (error) { 79 this.setState({ errorMessage: error.message }); 80 } 81 }; 82 83 _updateStateToStatus = (status: any) => { 84 console.log('onPlaybackStatusUpdate: ', diff(this.prevStatus || {}, status)); 85 this.prevStatus = status; 86 this.setState(status); 87 }; 88 89 _playAsync = async () => this._sound!.playAsync(); 90 91 _pauseAsync = async () => this._sound!.pauseAsync(); 92 93 _replayAsync = async () => this._sound!.replayAsync(); 94 95 _setPositionAsync = async (position: number) => this._sound!.setPositionAsync(position); 96 97 _setIsLoopingAsync = async (isLooping: boolean) => this._sound!.setIsLoopingAsync(isLooping); 98 99 _setIsMutedAsync = async (isMuted: boolean) => this._sound!.setIsMutedAsync(isMuted); 100 101 _setVolumeAsync = async (volume: number) => this._sound!.setVolumeAsync(volume); 102 103 _setRateAsync = async ( 104 rate: number, 105 shouldCorrectPitch: boolean, 106 pitchCorrectionQuality = Audio.PitchCorrectionQuality.Low 107 ) => { 108 await this._sound!.setRateAsync(rate, shouldCorrectPitch, pitchCorrectionQuality); 109 }; 110 111 render() { 112 return ( 113 <Player 114 {...this.state} 115 style={this.props.style} 116 playAsync={this._playAsync} 117 pauseAsync={this._pauseAsync} 118 replayAsync={this._replayAsync} 119 setPositionAsync={this._setPositionAsync} 120 setIsLoopingAsync={this._setIsLoopingAsync} 121 setRateAsync={this._setRateAsync} 122 setIsMutedAsync={this._setIsMutedAsync} 123 setVolume={this._setVolumeAsync} 124 /> 125 ); 126 } 127} 128