UNPKG

6.65 kBJavaScriptView Raw
1// @flow
2
3import {
4 NativeModules,
5} from 'react-native';
6import Asset from './Asset';
7
8type SoundStatus = {
9 isPlaying: boolean,
10 positionMillis: number,
11 rate: number,
12 shouldCorrectPitch: boolean,
13 volume: number,
14 isMuted: boolean,
15 isLooping: boolean,
16}
17
18var _enabled: boolean = false;
19const _DISABLED_ERROR: Error = new Error('Cannot complete operation because audio is not enabled.');
20const _NOT_LOADED_ERROR: Error = new Error('Cannot complete operation because sound is not loaded.');
21const _DEFAULT_POLLING_TIMEOUT_MILLIS: number = 500;
22
23export async function setIsEnabledAsync(value: boolean): Promise<void> {
24 _enabled = value;
25 await NativeModules.ExponentAudio.setIsEnabled(value);
26}
27
28export class Sound {
29 uri: string;
30 loaded: boolean;
31 key: number;
32 durationMillis: number;
33 statusChangeCallback: ?(status: SoundStatus) => void;
34 userPlaybackFinishedCallback: ?() => void;
35 statusPollingTimeoutVariable: ?number;
36 statusPollingTimeoutMillis: number;
37
38 constructor({ source } : { source: number | string | Asset }) {
39 if (typeof source === 'number') { // source is an asset module
40 let asset = Asset.fromModule(source);
41 this.uri = asset.localUri || asset.uri;
42 } else if (typeof source === 'string') { // source is a remote URI
43 this.uri = source;
44 } else { // source is an Asset
45 this.uri = source.localUri || source.uri;
46 }
47
48 this.loaded = false;
49 this.key = -1;
50 this.durationMillis = 0;
51 this.statusChangeCallback = null;
52 this.userPlaybackFinishedCallback = null;
53 this.statusPollingTimeoutVariable = null;
54 this.statusPollingTimeoutMillis = _DEFAULT_POLLING_TIMEOUT_MILLIS;
55 }
56
57 _statusPollingLoop = () => {
58 if (!_enabled) {
59 return;
60 }
61 if (this.statusChangeCallback != null) {
62 this.getStatusAsync(); // Automatically calls this.statusChangeCallback.
63 }
64 if (this.loaded) {
65 this.statusPollingTimeoutVariable = setTimeout(this._statusPollingLoop, this.statusPollingTimeoutMillis);
66 }
67 }
68
69 _disableStatusPolling() {
70 if (this.statusPollingTimeoutVariable != null) {
71 clearTimeout(this.statusPollingTimeoutVariable);
72 this.statusPollingTimeoutVariable = null;
73 }
74 }
75
76 _enableStatusPolling() {
77 if (_enabled) {
78 this._disableStatusPolling();
79 this._statusPollingLoop();
80 }
81 }
82
83 _tryCallStatusChangeCallbackForStatus(status: SoundStatus) {
84 if (this.statusChangeCallback != null) {
85 this.statusChangeCallback(status);
86 }
87 }
88
89 _internalPlaybackFinishedCallback = (status: SoundStatus) => {
90 this._tryCallStatusChangeCallbackForStatus(status);
91 if (this.userPlaybackFinishedCallback != null) {
92 this.userPlaybackFinishedCallback();
93 }
94 this._setInternalPlaybackFinishedCallback(); // Callbacks are only called once and then released.
95 }
96
97 _setInternalPlaybackFinishedCallback() {
98 if (this.loaded) {
99 NativeModules.ExponentAudio.setPlaybackFinishedCallback(this.key, this._internalPlaybackFinishedCallback);
100 }
101 }
102
103 async _performOperationAndUpdateStatusAsync(operation: () => Promise<{ status: SoundStatus }>): Promise<SoundStatus> {
104 if (!_enabled) {
105 throw _DISABLED_ERROR;
106 }
107 if (this.loaded) {
108 const { status } : { status: SoundStatus } = await operation();
109 this._tryCallStatusChangeCallbackForStatus(status);
110 return status;
111 } else {
112 throw _NOT_LOADED_ERROR;
113 }
114 }
115
116 async loadAsync(): Promise<void> {
117 if (!_enabled) {
118 throw _DISABLED_ERROR;
119 }
120 if (!this.loaded) {
121 const result: {
122 key: number,
123 durationMillis: number,
124 status: SoundStatus
125 } = await NativeModules.ExponentAudio.load(this.uri);
126 this.key = result.key;
127 this.durationMillis = result.durationMillis;
128 this.loaded = true;
129 this._setInternalPlaybackFinishedCallback();
130 }
131 }
132
133 isLoaded(): boolean {
134 return this.loaded;
135 }
136
137 getDurationMillis(): ?number {
138 return this.loaded ? this.durationMillis : null;
139 }
140
141 setStatusChangeCallback(callback: ?(status: SoundStatus) => void) {
142 this.statusChangeCallback = callback;
143 if (callback == null) {
144 this._disableStatusPolling();
145 } else {
146 this._enableStatusPolling();
147 }
148 }
149
150 setPlaybackFinishedCallback(callback: ?() => void) {
151 this.userPlaybackFinishedCallback = callback;
152 }
153
154 setStatusPollingTimeoutMillis(value: number) {
155 this.statusPollingTimeoutMillis = value;
156 }
157
158 async unloadAsync(): Promise<void> {
159 if (this.loaded) {
160 this.loaded = false;
161 this._disableStatusPolling();
162 this.userPlaybackFinishedCallback = null;
163 this.statusChangeCallback = null;
164 return await NativeModules.ExponentAudio.unload(this.key);
165 }
166 }
167
168 async playAsync(): Promise<SoundStatus> {
169 return this._performOperationAndUpdateStatusAsync(
170 () => NativeModules.ExponentAudio.play(this.key));
171 }
172
173 async pauseAsync(): Promise<SoundStatus> {
174 return this._performOperationAndUpdateStatusAsync(
175 () => NativeModules.ExponentAudio.pause(this.key));
176 }
177
178 async stopAsync(): Promise<SoundStatus> {
179 return this._performOperationAndUpdateStatusAsync(
180 () => NativeModules.ExponentAudio.stop(this.key));
181 }
182
183 async setPositionAsync(millis: number): Promise<SoundStatus> {
184 return this._performOperationAndUpdateStatusAsync(
185 () => NativeModules.ExponentAudio.setPosition(this.key, millis));
186 }
187
188 async setRateAsync(value: number, shouldCorrectPitch: boolean): Promise<SoundStatus> {
189 if (value < 0.0 || value > 32.0) {
190 throw new Error('Rate value must be between 0.0 and 32.0.');
191 }
192 return this._performOperationAndUpdateStatusAsync(
193 () => NativeModules.ExponentAudio.setRate(this.key, value, shouldCorrectPitch));
194 }
195
196 async setVolumeAsync(value: number): Promise<SoundStatus> {
197 if (value < 0.0 || value > 1.0) {
198 throw new Error('Volume value must be between 0.0 and 1.0.');
199 }
200 return this._performOperationAndUpdateStatusAsync(
201 () => NativeModules.ExponentAudio.setVolume(this.key, value));
202 }
203
204 async setIsMutedAsync(value: boolean): Promise<SoundStatus> {
205 return this._performOperationAndUpdateStatusAsync(
206 () => NativeModules.ExponentAudio.setIsMuted(this.key, value));
207 }
208
209 async setIsLoopingAsync(value: boolean): Promise<SoundStatus> {
210 return this._performOperationAndUpdateStatusAsync(
211 () => NativeModules.ExponentAudio.setIsLooping(this.key, value));
212 }
213
214 async getStatusAsync(): Promise<SoundStatus> {
215 return this._performOperationAndUpdateStatusAsync(
216 () => NativeModules.ExponentAudio.getStatus(this.key));
217 }
218}