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