react-native-audiosprites 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,10 +22,22 @@ First, you need to generate an audio sprite and a JSON manifest file using the `
22
22
  Assuming you have [`audiosprite`](https://www.npmjs.com/package/audiosprite) installed globally:
23
23
 
24
24
  ```sh
25
- audiosprite --output audiosprite --format howler --path ./src/__tests__/ Sound_1.m4a Sound_2.m4a Sound_3.m4a Sound_4.m4a
25
+ audiosprite --output src/__tests__/sounds/mygameaudio --format howler --loop "bg_loop" src/__tests__/sounds/bg_loop.wav src/__tests__/sounds/Sound_1.m4a src/__tests__/sounds/Sound_2.m4a src/__tests__/sounds/Sound_3.m4a src/__tests__/sounds/Sound_4.m4a
26
26
  ```
27
27
 
28
- This command will generate `audiosprite.json`, `audiosprite.mp3`, `audiosprite.ogg`, `audiosprite.m4a`, and `audiosprite.ac3` in the `src/__tests__/` directory. The `--path` argument is important as it tells the player where to find the audio files relative to the JSON manifest.
28
+ This command will generate `mygameaudio.json`, `mygameaudio.mp3`, `mygameaudio.ogg`, `mygameaudio.m4a`, and `mygameaudio.ac3` in the `src/__tests__/sounds/` directory.
29
+
30
+ ### Looping Sounds
31
+
32
+ You can create looping sounds by using the `--loop` option with the `audiosprite` command. The value of the `--loop` option should be the name of the sound you want to loop.
33
+
34
+ For example, to loop the `bg_music` sound, you would use the following command:
35
+
36
+ ```sh
37
+ audiosprite --output audiosprite --format howler --loop "bg_music" --path ./src/__tests__/ Sound_1.m4a Sound_2.m4a Sound_3.m4a Sound_4.m4a bg_music.wav
38
+ ```
39
+
40
+ When you play a looping sound, it will play continuously until you stop it using the `player.stop()` method. The looping functionality is supported on both web and mobile platforms.
29
41
 
30
42
  Then, you can use the `AudioSpritePlayer` to play the sounds from the sprite.
31
43
 
@@ -42,7 +54,7 @@ async function playSound(soundName: string) {
42
54
  try {
43
55
  // Load the audio sprite manifest and audio files
44
56
  // Adjust the path to your audiosprite.json file
45
- await player.load('./src/__tests__/audiosprite.json');
57
+ await player.load('./src/__tests__/sounds/mygameaudio.json');
46
58
  console.log('Audio sprite loaded successfully.');
47
59
 
48
60
  // Play a sound from the spritemap
@@ -53,9 +65,16 @@ async function playSound(soundName: string) {
53
65
  }
54
66
  }
55
67
 
68
+ function stopSound() {
69
+ player.stop();
70
+ console.log('Stopped looping sound.');
71
+ }
72
+
56
73
  // Example usage:
57
74
  playSound('Sound_1');
58
75
  // playSound('Sound_2');
76
+ // To stop a looping sound:
77
+ // stopSound();
59
78
  ```
60
79
 
61
80
  ### React Native Environment
@@ -79,16 +98,16 @@ module.exports = wrapWithAudioAPIMetroConfig(config);
79
98
  Then, you can use it in your component:
80
99
 
81
100
  ```typescript
82
- import { StyleSheet, View, Text, Button, Platform } from 'react-native';
101
+ import { StyleSheet, View, Text, Platform, TouchableOpacity } from 'react-native';
83
102
  import { AudioSpritePlayer } from 'react-native-audiosprites';
84
103
  import { AudioManager, AudioContext } from 'react-native-audio-api';
85
104
  import { useEffect, useState, useRef } from 'react';
86
105
  import { Asset } from 'expo-asset';
87
106
  import { fetch } from 'expo/fetch';
88
- import manifest from '../assets/audiosprite.json';
107
+ import manifest from '../assets/mygameaudio.json';
89
108
 
90
109
  // Import the audio asset
91
- const audioAsset = require('../assets/audiosprite.mp3');
110
+ const audioAsset = require('../assets/mygameaudio.mp3');
92
111
 
93
112
  export default function App() {
94
113
  const [isLoaded, setIsLoaded] = useState(false);
@@ -147,19 +166,51 @@ export default function App() {
147
166
  }
148
167
  };
149
168
 
169
+ const stopBGM = () => {
170
+ const player = playerRef.current;
171
+ if (player) {
172
+ player.stop();
173
+ }
174
+ };
175
+
150
176
  return (
151
177
  <View style={styles.container}>
152
178
  <Text>AudioSprite Player Example</Text>
153
- <Button
154
- title="Play Sound 1"
179
+ <TouchableOpacity
180
+ onPress={() => loadPlayer()}
181
+ style={styles.button}
182
+ disabled={!isLoaded}
183
+ >
184
+ <Text style={styles.buttonText}>Load Player</Text>
185
+ </TouchableOpacity>
186
+ <TouchableOpacity
155
187
  onPress={() => playSound('Sound_1')}
188
+ style={styles.button}
156
189
  disabled={!isLoaded}
157
- />
158
- <Button
159
- title="Play Sound 2"
190
+ >
191
+ <Text style={styles.buttonText}>Play Sound 1</Text>
192
+ </TouchableOpacity>
193
+ <TouchableOpacity
160
194
  onPress={() => playSound('Sound_2')}
195
+ style={styles.button}
161
196
  disabled={!isLoaded}
162
- />
197
+ >
198
+ <Text style={styles.buttonText}>Play Sound 2</Text>
199
+ </TouchableOpacity>
200
+ <TouchableOpacity
201
+ onPress={() => playSound('bg_loop')}
202
+ style={styles.button}
203
+ disabled={!isLoaded}
204
+ >
205
+ <Text style={styles.buttonText}>Play Background Loop</Text>
206
+ </TouchableOpacity>
207
+ <TouchableOpacity
208
+ onPress={stopBGM}
209
+ style={styles.button}
210
+ disabled={!isLoaded}
211
+ >
212
+ <Text style={styles.buttonText}>Stop BGM</Text>
213
+ </TouchableOpacity>
163
214
  </View>
164
215
  );
165
216
  }
@@ -170,9 +221,28 @@ const styles = StyleSheet.create({
170
221
  alignItems: 'center',
171
222
  justifyContent: 'center',
172
223
  },
224
+ button: {
225
+ backgroundColor: '#DDDDDD',
226
+ padding: 10,
227
+ marginVertical: 5,
228
+ borderRadius: 5,
229
+ },
230
+ buttonText: {
231
+ color: '#000000',
232
+ textAlign: 'center',
233
+ },
173
234
  });
174
235
  ```
175
236
 
237
+ ## Inspiration
238
+
239
+ https://github.com/goldfire/howler.js
240
+ Generated json also works with new Howl({
241
+ sprite: {
242
+ key1: [offset, duration, (loop)]
243
+ },
244
+ });
245
+
176
246
  ## Contributing
177
247
 
178
248
  - [Development workflow](CONTRIBUTING.md#development-workflow)
@@ -183,6 +253,8 @@ const styles = StyleSheet.create({
183
253
 
184
254
  MIT
185
255
 
186
- ---
256
+ ## Credits
257
+
258
+ [Shaker, Woda, Conga, Bongo, Templeblock.wav](https://freesound.org/people/kwazi/sounds/34115/) by [kwazi](https://freesound.org/people/kwazi/) | License: [Attribution 3.0](http://creativecommons.org/licenses/by/3.0/)
187
259
 
188
260
  Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -10,6 +10,7 @@ export class AudioSpritePlayer {
10
10
 
11
11
  // Cache for the small, pre-split AudioBuffers used by mobile's QueueSourceNode
12
12
  spriteBufferCache = {};
13
+ loopingSource = null;
13
14
  constructor({
14
15
  audioContext,
15
16
  fetch,
@@ -44,20 +45,39 @@ export class AudioSpritePlayer {
44
45
  /**
45
46
  * Caches pre-split AudioBuffers for each sprite, which is necessary
46
47
  * for stable playback on mobile using the BufferQueueSourceNode.
48
+ *
49
+ * This method iterates through the audio sprite manifest and creates a separate
50
+ * AudioBuffer for each sound sprite. These smaller buffers are then stored
51
+ * in `this.spriteBufferCache` for efficient playback on mobile platforms,
52
+ * especially when using `AudioBufferQueueSourceNode`.
53
+ *
54
+ * The `audiosprite` manifest is expected to have the following structure for each sprite:
55
+ * `[start_time_ms, duration_ms, loop_boolean (optional)]`
56
+ *
57
+ * @example
58
+ * // Example audiosprite manifest structure:
59
+ * {
60
+ * "urls": ["audio.mp3", "audio.ogg"],
61
+ * "sprite": {
62
+ * "sound1": [0, 1000, false], // start at 0ms, duration 1000ms, no loop
63
+ * "sound2": [1500, 500, true], // start at 1500ms, duration 500ms, loop
64
+ * "background": [2000, 30000, true] // start at 2000ms, duration 30000ms, loop
65
+ * }
66
+ * }
47
67
  */
48
68
  _cacheSpriteBuffers() {
49
- if (!this.audioBuffer || !this.manifest || this.platform === 'web') {
69
+ if (!this.audioBuffer || !this.manifest) {
50
70
  return; // Only necessary for mobile platforms
51
71
  }
52
72
  const sampleRate = this.audioBuffer.sampleRate;
53
73
  const numChannels = this.audioBuffer.numberOfChannels;
54
74
  this.spriteBufferCache = {};
55
- for (const soundName in this.manifest.spritemap) {
56
- const sound = this.manifest.spritemap[soundName];
75
+ for (const soundName in this.manifest.sprite) {
76
+ const sound = this.manifest.sprite[soundName];
57
77
 
58
- // Calculate frame indices
59
- const startFrame = Math.floor(sound.start * sampleRate);
60
- const endFrame = Math.ceil(sound.end * sampleRate);
78
+ // Calculate frame indices based on audiosprite format: [start, duration, loop]
79
+ const startFrame = Math.floor(sound[0] * sampleRate / 1000); // Convert ms to frames
80
+ const endFrame = Math.ceil((sound[0] + sound[1]) * sampleRate / 1000); // Convert ms to frames
61
81
  const durationFrames = endFrame - startFrame;
62
82
  if (durationFrames <= 0) {
63
83
  console.warn(`Sprite "${soundName}" has zero or negative duration. Skipping.`);
@@ -91,10 +111,10 @@ export class AudioSpritePlayer {
91
111
  throw new Error(`Failed to fetch manifest: ${response.statusText}`);
92
112
  }
93
113
  this.manifest = await response.json();
94
- if (!this.manifest.resources || !this.manifest.spritemap) {
95
- throw new Error('Invalid audiosprite manifest format. Missing "resources" or "spritemap".');
114
+ if (!this.manifest.urls || !this.manifest.sprite) {
115
+ throw new Error('Invalid audiosprite manifest format. Missing "urls" or "sprite".');
96
116
  }
97
- const audioFileName = this.manifest.resources[0];
117
+ const audioFileName = this.manifest.urls[0];
98
118
  const audioUrl = new URL(audioFileName, response.url).href;
99
119
  const audioResponse = await this.fetch(audioUrl);
100
120
  if (!audioResponse.ok) {
@@ -104,8 +124,8 @@ export class AudioSpritePlayer {
104
124
  decodedBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
105
125
  } else {
106
126
  this.manifest = json;
107
- if (!this.manifest.resources || !this.manifest.spritemap) {
108
- throw new Error('Invalid audiosprite manifest format. Missing "resources" or "spritemap".');
127
+ if (!this.manifest.urls || !this.manifest.sprite) {
128
+ throw new Error('Invalid audiosprite manifest format. Missing "urls" or "sprite".');
109
129
  }
110
130
  let arrayBuffer;
111
131
  if (typeof audio === 'string') {
@@ -117,7 +137,13 @@ export class AudioSpritePlayer {
117
137
  } else {
118
138
  arrayBuffer = audio;
119
139
  }
120
- decodedBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
140
+ // 🔑 CRITICAL FIX: Create a typed array view before decoding
141
+ // This ensures the data is treated as a true ArrayBuffer in the audio context.
142
+ const typedArrayView = new Uint8Array(arrayBuffer);
143
+
144
+ // Pass the underlying ArrayBuffer of the typed view to the decoder
145
+ // Note: typedArrayView.buffer gives you the underlying ArrayBuffer
146
+ decodedBuffer = await this.audioContext.decodeAudioData(typedArrayView.buffer);
121
147
  }
122
148
  // --- End Fetching and Decoding Logic ---
123
149
 
@@ -143,21 +169,21 @@ export class AudioSpritePlayer {
143
169
  console.error('Failed to resume AudioContext:', e);
144
170
  });
145
171
  }
146
- const sound = this.manifest.spritemap[soundName];
172
+ const sound = this.manifest.sprite[soundName];
147
173
  if (!sound) {
148
174
  console.warn(`Sound "${soundName}" not found in spritemap.`);
149
175
  return;
150
176
  }
151
- const duration = sound.end - sound.start;
177
+ const duration = sound[1];
152
178
  if (duration <= 0) {
153
179
  console.warn(`Sound "${soundName}" has invalid duration.`);
154
180
  return;
155
181
  }
156
182
  let source;
183
+ const spriteBuffer = this.spriteBufferCache[soundName];
157
184
 
158
185
  // 🚨 MOBILE LOGIC: Use AudioBufferQueueSourceNode with cached split buffer
159
186
  if (this.platform !== 'web') {
160
- const spriteBuffer = this.spriteBufferCache[soundName];
161
187
  if (!spriteBuffer) {
162
188
  console.error(`RNAS Error: Split buffer for "${soundName}" not found in cache.`);
163
189
  return;
@@ -166,14 +192,33 @@ export class AudioSpritePlayer {
166
192
  console.error('RNAS Error: createBufferQueueSource is not available on this native platform.');
167
193
  return;
168
194
  }
169
- source = this.audioContext.createBufferQueueSource();
170
-
171
- // Mobile Implementation: Enqueue the specific, short sprite buffer
172
- source.enqueueBuffer(spriteBuffer);
173
- source.connect(this.audioContext.destination);
195
+ const loop = sound[2];
196
+ if (loop) {
197
+ // Always use AudioBufferQueueSourceNode
198
+ source = this.audioContext.createBufferQueueSource();
199
+ source.enqueueBuffer(spriteBuffer);
200
+ source.connect(this.audioContext.destination);
174
201
 
175
- // This will play the short buffer from its start to its end.
176
- source.start(1);
202
+ // Manual looping using onEnded
203
+ const loopHandler = () => {
204
+ // Only re-enqueue if this is still the active looping source
205
+ if (this.loopingSource === source) {
206
+ source.enqueueBuffer(spriteBuffer);
207
+ // Restart the source immediately after re-enqueueing
208
+ source.start(0);
209
+ }
210
+ };
211
+ source.onEnded = loopHandler;
212
+ source.start(0); // Start immediately
213
+ this.loopingSource = source; // Store reference to looping source
214
+ } else {
215
+ // For non-looping sounds on mobile, use AudioBufferQueueSourceNode
216
+ source = this.audioContext.createBufferQueueSource();
217
+ source.enqueueBuffer(spriteBuffer);
218
+ source.connect(this.audioContext.destination);
219
+ source.start(1);
220
+ console.log('non loop', soundName);
221
+ }
177
222
  } else {
178
223
  // 🌐 WEB LOGIC (Standard Web Audio API)
179
224
  source = this.audioContext.createBufferSource();
@@ -181,16 +226,24 @@ export class AudioSpritePlayer {
181
226
  console.error('RNAS Error: createBufferSource() returned an invalid object on web. Aborting playback.');
182
227
  return;
183
228
  }
184
- source.buffer = this.audioBuffer;
229
+ source.buffer = spriteBuffer;
185
230
  source.connect(this.audioContext.destination);
186
-
187
- // Use the 'audiosprite' format: start(when, offset, duration)
188
- source.start(0,
189
- // Start playing now
190
- sound.start,
191
- // The offset
192
- duration // The calculated duration
193
- );
231
+ const loop = sound[2]; // audiosprite stores loop as the third element in the array
232
+ if (loop) {
233
+ source.loop = true;
234
+ source.loopStart = 0; // Relative to the spriteBuffer
235
+ source.loopEnd = sound[1] / 1000; // Duration of the spriteBuffer
236
+ source.start(0); // Start immediately, no offset for the individual spriteBuffer
237
+ this.loopingSource = source; // Store reference to looping source
238
+ } else {
239
+ // Use the 'audiosprite' format: start(when, offset, duration)
240
+ source.start(0,
241
+ // Start playing now
242
+ 0,
243
+ // The offset in seconds (relative to the spriteBuffer)
244
+ sound[1] / 1000 // The calculated duration in seconds
245
+ );
246
+ }
194
247
  }
195
248
  console.log(`RNAS: played ${soundName} on ${this.platform}`);
196
249
  }
@@ -200,5 +253,19 @@ export class AudioSpritePlayer {
200
253
  getAudioBuffer() {
201
254
  return this.audioBuffer;
202
255
  }
256
+
257
+ /**
258
+ * Stops the currently looping audio sprite.
259
+ * If a looping sound is playing, it will be stopped immediately.
260
+ */
261
+ stop() {
262
+ if (this.loopingSource) {
263
+ this.loopingSource.stop();
264
+ this.loopingSource = null;
265
+ console.log('RNAS: Looping audio stopped.');
266
+ } else {
267
+ console.log('RNAS: No looping audio to stop.');
268
+ }
269
+ }
203
270
  }
204
271
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["AudioSpritePlayer","spriteBufferCache","constructor","audioContext","fetch","platform","AudioContext","Error","audioBuffer","manifest","createBufferSource","name","console","log","_cacheSpriteBuffers","sampleRate","numChannels","numberOfChannels","soundName","spritemap","sound","startFrame","Math","floor","start","endFrame","ceil","end","durationFrames","warn","spriteBuffer","createBuffer","i","sourceData","getChannelData","destinationData","segment","subarray","set","load","json","audio","decodedBuffer","response","ok","statusText","resources","audioFileName","audioUrl","URL","url","href","audioResponse","arrayBuffer","decodeAudioData","error","play","state","resume","catch","e","duration","source","createBufferQueueSource","enqueueBuffer","connect","destination","buffer","getManifest","getAudioBuffer"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["AudioSpritePlayer","spriteBufferCache","loopingSource","constructor","audioContext","fetch","platform","AudioContext","Error","audioBuffer","manifest","createBufferSource","name","console","log","_cacheSpriteBuffers","sampleRate","numChannels","numberOfChannels","soundName","sprite","sound","startFrame","Math","floor","endFrame","ceil","durationFrames","warn","spriteBuffer","createBuffer","i","sourceData","getChannelData","destinationData","segment","subarray","set","load","json","audio","decodedBuffer","response","ok","statusText","urls","audioFileName","audioUrl","URL","url","href","audioResponse","arrayBuffer","decodeAudioData","typedArrayView","Uint8Array","buffer","error","play","state","resume","catch","e","duration","source","createBufferQueueSource","loop","enqueueBuffer","connect","destination","loopHandler","start","onEnded","loopStart","loopEnd","getManifest","getAudioBuffer","stop"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -10,6 +10,7 @@ export declare class AudioSpritePlayer {
10
10
  manifest: any | null;
11
11
  platform: string;
12
12
  private spriteBufferCache;
13
+ private loopingSource;
13
14
  constructor({ audioContext, fetch, platform, }: {
14
15
  audioContext: any | null;
15
16
  fetch: any;
@@ -18,11 +19,35 @@ export declare class AudioSpritePlayer {
18
19
  /**
19
20
  * Caches pre-split AudioBuffers for each sprite, which is necessary
20
21
  * for stable playback on mobile using the BufferQueueSourceNode.
22
+ *
23
+ * This method iterates through the audio sprite manifest and creates a separate
24
+ * AudioBuffer for each sound sprite. These smaller buffers are then stored
25
+ * in `this.spriteBufferCache` for efficient playback on mobile platforms,
26
+ * especially when using `AudioBufferQueueSourceNode`.
27
+ *
28
+ * The `audiosprite` manifest is expected to have the following structure for each sprite:
29
+ * `[start_time_ms, duration_ms, loop_boolean (optional)]`
30
+ *
31
+ * @example
32
+ * // Example audiosprite manifest structure:
33
+ * {
34
+ * "urls": ["audio.mp3", "audio.ogg"],
35
+ * "sprite": {
36
+ * "sound1": [0, 1000, false], // start at 0ms, duration 1000ms, no loop
37
+ * "sound2": [1500, 500, true], // start at 1500ms, duration 500ms, loop
38
+ * "background": [2000, 30000, true] // start at 2000ms, duration 30000ms, loop
39
+ * }
40
+ * }
21
41
  */
22
42
  private _cacheSpriteBuffers;
23
43
  load(json: any, audio?: any): Promise<void>;
24
44
  play(soundName: string): void;
25
45
  getManifest(): any;
26
46
  getAudioBuffer(): any;
47
+ /**
48
+ * Stops the currently looping audio sprite.
49
+ * If a looping sound is playing, it will be stopped immediately.
50
+ */
51
+ stop(): void;
27
52
  }
28
53
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,YAAY,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;IAClB,WAAW,EAAE,GAAG,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,GAAG,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,iBAAiB,CAA2B;gBAExC,EACV,YAAY,EACZ,KAAK,EACL,QAAQ,GACT,EAAE;QACD,YAAY,EAAE,GAAG,GAAG,IAAI,CAAC;QACzB,KAAK,EAAE,GAAG,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;KAClB;IAkCD;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA8CrB,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG;IAkEjC,IAAI,CAAC,SAAS,EAAE,MAAM;IA+EtB,WAAW;IAIX,cAAc;CAGf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,YAAY,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;IAClB,WAAW,EAAE,GAAG,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,GAAG,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,aAAa,CAAoB;gBAE7B,EACV,YAAY,EACZ,KAAK,EACL,QAAQ,GACT,EAAE;QACD,YAAY,EAAE,GAAG,GAAG,IAAI,CAAC;QACzB,KAAK,EAAE,GAAG,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;KAClB;IAkCD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,mBAAmB;IA8CrB,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG;IA0EjC,IAAI,CAAC,SAAS,EAAE,MAAM;IA2GtB,WAAW;IAIX,cAAc;IAId;;;OAGG;IACH,IAAI;CASL"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-audiosprites",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "audio sprites ",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
package/src/index.tsx CHANGED
@@ -11,6 +11,7 @@ export class AudioSpritePlayer {
11
11
  platform: string;
12
12
  // Cache for the small, pre-split AudioBuffers used by mobile's QueueSourceNode
13
13
  private spriteBufferCache: Record<string, any> = {};
14
+ private loopingSource: any | null = null;
14
15
 
15
16
  constructor({
16
17
  audioContext,
@@ -57,9 +58,28 @@ export class AudioSpritePlayer {
57
58
  /**
58
59
  * Caches pre-split AudioBuffers for each sprite, which is necessary
59
60
  * for stable playback on mobile using the BufferQueueSourceNode.
61
+ *
62
+ * This method iterates through the audio sprite manifest and creates a separate
63
+ * AudioBuffer for each sound sprite. These smaller buffers are then stored
64
+ * in `this.spriteBufferCache` for efficient playback on mobile platforms,
65
+ * especially when using `AudioBufferQueueSourceNode`.
66
+ *
67
+ * The `audiosprite` manifest is expected to have the following structure for each sprite:
68
+ * `[start_time_ms, duration_ms, loop_boolean (optional)]`
69
+ *
70
+ * @example
71
+ * // Example audiosprite manifest structure:
72
+ * {
73
+ * "urls": ["audio.mp3", "audio.ogg"],
74
+ * "sprite": {
75
+ * "sound1": [0, 1000, false], // start at 0ms, duration 1000ms, no loop
76
+ * "sound2": [1500, 500, true], // start at 1500ms, duration 500ms, loop
77
+ * "background": [2000, 30000, true] // start at 2000ms, duration 30000ms, loop
78
+ * }
79
+ * }
60
80
  */
61
81
  private _cacheSpriteBuffers() {
62
- if (!this.audioBuffer || !this.manifest || this.platform === 'web') {
82
+ if (!this.audioBuffer || !this.manifest) {
63
83
  return; // Only necessary for mobile platforms
64
84
  }
65
85
 
@@ -67,12 +87,12 @@ export class AudioSpritePlayer {
67
87
  const numChannels = this.audioBuffer.numberOfChannels;
68
88
  this.spriteBufferCache = {};
69
89
 
70
- for (const soundName in this.manifest.spritemap) {
71
- const sound = this.manifest.spritemap[soundName];
90
+ for (const soundName in this.manifest.sprite) {
91
+ const sound = this.manifest.sprite[soundName];
72
92
 
73
- // Calculate frame indices
74
- const startFrame = Math.floor(sound.start * sampleRate);
75
- const endFrame = Math.ceil(sound.end * sampleRate);
93
+ // Calculate frame indices based on audiosprite format: [start, duration, loop]
94
+ const startFrame = Math.floor((sound[0] * sampleRate) / 1000); // Convert ms to frames
95
+ const endFrame = Math.ceil(((sound[0] + sound[1]) * sampleRate) / 1000); // Convert ms to frames
76
96
  const durationFrames = endFrame - startFrame;
77
97
 
78
98
  if (durationFrames <= 0) {
@@ -116,13 +136,13 @@ export class AudioSpritePlayer {
116
136
  }
117
137
  this.manifest = await response.json();
118
138
 
119
- if (!this.manifest.resources || !this.manifest.spritemap) {
139
+ if (!this.manifest.urls || !this.manifest.sprite) {
120
140
  throw new Error(
121
- 'Invalid audiosprite manifest format. Missing "resources" or "spritemap".'
141
+ 'Invalid audiosprite manifest format. Missing "urls" or "sprite".'
122
142
  );
123
143
  }
124
144
 
125
- const audioFileName = this.manifest.resources[0];
145
+ const audioFileName = this.manifest.urls[0];
126
146
  const audioUrl = new URL(audioFileName, response.url).href;
127
147
 
128
148
  const audioResponse = await this.fetch(audioUrl);
@@ -136,9 +156,9 @@ export class AudioSpritePlayer {
136
156
  decodedBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
137
157
  } else {
138
158
  this.manifest = json;
139
- if (!this.manifest.resources || !this.manifest.spritemap) {
159
+ if (!this.manifest.urls || !this.manifest.sprite) {
140
160
  throw new Error(
141
- 'Invalid audiosprite manifest format. Missing "resources" or "spritemap".'
161
+ 'Invalid audiosprite manifest format. Missing "urls" or "sprite".'
142
162
  );
143
163
  }
144
164
 
@@ -154,7 +174,15 @@ export class AudioSpritePlayer {
154
174
  } else {
155
175
  arrayBuffer = audio;
156
176
  }
157
- decodedBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
177
+ // 🔑 CRITICAL FIX: Create a typed array view before decoding
178
+ // This ensures the data is treated as a true ArrayBuffer in the audio context.
179
+ const typedArrayView = new Uint8Array(arrayBuffer);
180
+
181
+ // Pass the underlying ArrayBuffer of the typed view to the decoder
182
+ // Note: typedArrayView.buffer gives you the underlying ArrayBuffer
183
+ decodedBuffer = await this.audioContext.decodeAudioData(
184
+ typedArrayView.buffer
185
+ );
158
186
  }
159
187
  // --- End Fetching and Decoding Logic ---
160
188
 
@@ -183,24 +211,23 @@ export class AudioSpritePlayer {
183
211
  });
184
212
  }
185
213
 
186
- const sound = this.manifest.spritemap[soundName];
214
+ const sound = this.manifest.sprite[soundName];
187
215
  if (!sound) {
188
216
  console.warn(`Sound "${soundName}" not found in spritemap.`);
189
217
  return;
190
218
  }
191
219
 
192
- const duration = sound.end - sound.start;
220
+ const duration = sound[1];
193
221
  if (duration <= 0) {
194
222
  console.warn(`Sound "${soundName}" has invalid duration.`);
195
223
  return;
196
224
  }
197
225
 
198
226
  let source: any;
227
+ const spriteBuffer = this.spriteBufferCache[soundName];
199
228
 
200
229
  // 🚨 MOBILE LOGIC: Use AudioBufferQueueSourceNode with cached split buffer
201
230
  if (this.platform !== 'web') {
202
- const spriteBuffer = this.spriteBufferCache[soundName];
203
-
204
231
  if (!spriteBuffer) {
205
232
  console.error(
206
233
  `RNAS Error: Split buffer for "${soundName}" not found in cache.`
@@ -215,15 +242,35 @@ export class AudioSpritePlayer {
215
242
  return;
216
243
  }
217
244
 
218
- source = this.audioContext.createBufferQueueSource();
219
-
220
- // Mobile Implementation: Enqueue the specific, short sprite buffer
221
- source.enqueueBuffer(spriteBuffer);
222
-
223
- source.connect(this.audioContext.destination);
245
+ const loop = sound[2];
246
+
247
+ if (loop) {
248
+ // Always use AudioBufferQueueSourceNode
249
+ source = this.audioContext.createBufferQueueSource();
250
+ source.enqueueBuffer(spriteBuffer);
251
+ source.connect(this.audioContext.destination);
252
+
253
+ // Manual looping using onEnded
254
+ const loopHandler = () => {
255
+ // Only re-enqueue if this is still the active looping source
256
+ if (this.loopingSource === source) {
257
+ source.enqueueBuffer(spriteBuffer);
258
+ // Restart the source immediately after re-enqueueing
259
+ source.start(0);
260
+ }
261
+ };
262
+ source.onEnded = loopHandler;
224
263
 
225
- // This will play the short buffer from its start to its end.
226
- source.start(1);
264
+ source.start(0); // Start immediately
265
+ this.loopingSource = source; // Store reference to looping source
266
+ } else {
267
+ // For non-looping sounds on mobile, use AudioBufferQueueSourceNode
268
+ source = this.audioContext.createBufferQueueSource();
269
+ source.enqueueBuffer(spriteBuffer);
270
+ source.connect(this.audioContext.destination);
271
+ source.start(1);
272
+ console.log('non loop', soundName);
273
+ }
227
274
  } else {
228
275
  // 🌐 WEB LOGIC (Standard Web Audio API)
229
276
  source = this.audioContext.createBufferSource();
@@ -235,15 +282,24 @@ export class AudioSpritePlayer {
235
282
  return;
236
283
  }
237
284
 
238
- source.buffer = this.audioBuffer;
285
+ source.buffer = spriteBuffer;
239
286
  source.connect(this.audioContext.destination);
240
287
 
241
- // Use the 'audiosprite' format: start(when, offset, duration)
242
- source.start(
243
- 0, // Start playing now
244
- sound.start, // The offset
245
- duration // The calculated duration
246
- );
288
+ const loop = sound[2]; // audiosprite stores loop as the third element in the array
289
+ if (loop) {
290
+ source.loop = true;
291
+ source.loopStart = 0; // Relative to the spriteBuffer
292
+ source.loopEnd = sound[1] / 1000; // Duration of the spriteBuffer
293
+ source.start(0); // Start immediately, no offset for the individual spriteBuffer
294
+ this.loopingSource = source; // Store reference to looping source
295
+ } else {
296
+ // Use the 'audiosprite' format: start(when, offset, duration)
297
+ source.start(
298
+ 0, // Start playing now
299
+ 0, // The offset in seconds (relative to the spriteBuffer)
300
+ sound[1] / 1000 // The calculated duration in seconds
301
+ );
302
+ }
247
303
  }
248
304
 
249
305
  console.log(`RNAS: played ${soundName} on ${this.platform}`);
@@ -256,4 +312,18 @@ export class AudioSpritePlayer {
256
312
  getAudioBuffer() {
257
313
  return this.audioBuffer;
258
314
  }
315
+
316
+ /**
317
+ * Stops the currently looping audio sprite.
318
+ * If a looping sound is playing, it will be stopped immediately.
319
+ */
320
+ stop() {
321
+ if (this.loopingSource) {
322
+ this.loopingSource.stop();
323
+ this.loopingSource = null;
324
+ console.log('RNAS: Looping audio stopped.');
325
+ } else {
326
+ console.log('RNAS: No looping audio to stop.');
327
+ }
328
+ }
259
329
  }