@siteed/expo-audio-stream 0.1.0 → 0.2.0

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/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
8
8
  "author": "Arthur Breton <abreton@siteed.net> (https://github.com/deeeed)",
9
9
  "homepage": "https://github.com/deeeed/expo-audio-stream#readme",
10
- "repository": "https://github.com/deeeed/expo-audio-stream",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/deeeed/expo-audio-stream.git"
13
+ },
11
14
  "bugs": {
12
15
  "url": "https://github.com/deeeed/expo-audio-stream/issues"
13
16
  },
@@ -31,11 +34,13 @@
31
34
  },
32
35
  "dependencies": {
33
36
  "base-64": "^1.0.0",
37
+ "debug": "^4.3.4",
34
38
  "expo-file-system": "^16.0.9"
35
39
  },
36
40
  "devDependencies": {
37
41
  "@expo/config-plugins": "^7.9.1",
38
42
  "@release-it/conventional-changelog": "^8.0.1",
43
+ "@types/debug": "^4.1.12",
39
44
  "@types/react": "^18.0.25",
40
45
  "@typescript-eslint/eslint-plugin": "^7.7.0",
41
46
  "@typescript-eslint/parser": "^7.7.0",
@@ -56,6 +61,7 @@
56
61
  "react-native": "*"
57
62
  },
58
63
  "publishConfig": {
59
- "access": "public"
64
+ "access": "public",
65
+ "registry": "https://registry.npmjs.org"
60
66
  }
61
67
  }
@@ -5,21 +5,30 @@ export interface AudioEventPayload {
5
5
  from: number,
6
6
  deltaSize: number,
7
7
  totalSize: number,
8
+ mimeType: string;
8
9
  streamUuid: string,
9
10
  };
10
11
 
12
+ export interface AudioStreamResult {
13
+ fileUri: string;
14
+ duration: number;
15
+ size: number;
16
+ mimeType: string;
17
+ }
18
+
11
19
  export interface AudioStreamStatus {
12
20
  isRecording: boolean;
13
21
  isPaused: boolean;
14
22
  duration: number;
15
23
  size: number;
16
24
  interval: number;
25
+ mimeType: string;
17
26
  }
18
27
 
19
28
  export interface RecordingOptions {
20
29
  // TODO align Android and IOS options
21
- sampleRate?: number;
22
- channelConfig?: number; // numberOfChannel
23
- audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)
30
+ // sampleRate?: number;
31
+ // channelConfig?: number; // numberOfChannel
32
+ // audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)
24
33
  interval?: number;
25
34
  }
@@ -1,6 +1,8 @@
1
1
  import { EventEmitter } from "expo-modules-core";
2
- import { AudioEventPayload, RecordingOptions } from "./ExpoAudioStream.types";
2
+ import { AudioEventPayload, AudioStreamResult, RecordingOptions } from "./ExpoAudioStream.types";
3
+ import debug from 'debug';
3
4
 
5
+ const log = debug("expo-audio-stream:useAudioRecording");
4
6
  class ExpoAudioStreamWeb extends EventEmitter {
5
7
  mediaRecorder: MediaRecorder | null;
6
8
  audioChunks: Blob[];
@@ -17,10 +19,10 @@ class ExpoAudioStreamWeb extends EventEmitter {
17
19
  constructor() {
18
20
  const mockNativeModule = {
19
21
  addListener: (eventName: string) => {
20
- console.log(`Web addListener called for ${eventName}`);
22
+ // Not used on web
21
23
  },
22
24
  removeListeners: (count: number) => {
23
- console.log(`Web removeListeners called, count: ${count}`);
25
+ // Not used on web
24
26
  }
25
27
  };
26
28
  super(mockNativeModule); // Pass the mock native module to the parent class
@@ -64,6 +66,8 @@ class ExpoAudioStreamWeb extends EventEmitter {
64
66
  this.pausedTime = 0;
65
67
  this.lastEmittedSize = 0;
66
68
  this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
69
+ const fileUri = `${this.streamUuid}.webm`;
70
+ return fileUri;
67
71
  }
68
72
 
69
73
  // Setup listeners for the MediaRecorder
@@ -80,7 +84,7 @@ class ExpoAudioStreamWeb extends EventEmitter {
80
84
 
81
85
  this.mediaRecorder.onstop = () => {
82
86
  this.isRecording = false;
83
- console.log('Recording stopped', this.audioChunks);
87
+ log('Recording stopped', this.audioChunks);
84
88
  };
85
89
 
86
90
  this.mediaRecorder.onpause = () => {
@@ -94,9 +98,10 @@ class ExpoAudioStreamWeb extends EventEmitter {
94
98
  }
95
99
 
96
100
  emitAudioEvent(data: Blob) {
97
- const fileUri = `${this.streamUuid}.pcm`;
101
+ const fileUri = `${this.streamUuid}.webm`;
98
102
  const audioEventPayload: AudioEventPayload = {
99
103
  fileUri: fileUri,
104
+ mimeType: 'audio/webm',
100
105
  from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
101
106
  deltaSize: data.size,
102
107
  totalSize: this.currentSize,
@@ -117,12 +122,18 @@ class ExpoAudioStreamWeb extends EventEmitter {
117
122
  }
118
123
 
119
124
  // Stop recording
120
- async stopRecording() {
121
- console.debug('Stopping recording', this);
125
+ async stopRecording(): Promise<AudioStreamResult | null> {
122
126
  this.mediaRecorder?.stop();
123
127
  this.isRecording = false;
124
128
  this.currentDuration = (Date.now() - this.recordingStartTime) / 1000;
125
- return this.currentDuration;
129
+ const result: AudioStreamResult = {
130
+ fileUri: `${this.streamUuid}.webm`,
131
+ duration: this.currentDuration,
132
+ size: this.currentSize,
133
+ mimeType: 'audio/webm',
134
+ }
135
+
136
+ return result;
126
137
  }
127
138
 
128
139
  // Pause recording
package/src/index.ts CHANGED
@@ -8,11 +8,6 @@ import { useAudioRecorder } from './useAudioRecording';
8
8
 
9
9
  const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
10
10
 
11
- // Function to get the recording duration
12
- export function getRecordingDuration(): Promise<number> {
13
- return ExpoAudioStreamModule.getRecordingDuration();
14
- }
15
-
16
11
  export function listAudioFiles(): Promise<string[]> {
17
12
  return ExpoAudioStreamModule.listAudioFiles();
18
13
  }
@@ -21,9 +16,8 @@ export function clearAudioFiles(): Promise<void> {
21
16
  return ExpoAudioStreamModule.clearAudioFiles();
22
17
  }
23
18
 
24
- export function addChangeListener(listener: (event: AudioEventPayload) => void): Subscription {
19
+ export function addAudioEventListener(listener: (event: AudioEventPayload) => void): Subscription {
25
20
  return emitter.addListener<AudioEventPayload>('AudioData', listener);
26
21
  }
27
22
 
28
-
29
23
  export { AudioEventPayload, useAudioRecorder };
@@ -2,16 +2,19 @@ import { NativeModulesProxy, EventEmitter, type Subscription, Platform } from 'e
2
2
 
3
3
  import { useCallback, useEffect, useState } from "react";
4
4
  import ExpoAudioStreamModule from './ExpoAudioStreamModule';
5
- import { AudioEventPayload, AudioStreamStatus, RecordingOptions } from "./ExpoAudioStream.types";
6
- import { addChangeListener } from '.';
5
+ import { AudioEventPayload, AudioStreamResult, AudioStreamStatus, RecordingOptions } from "./ExpoAudioStream.types";
6
+ import { addAudioEventListener } from '.';
7
7
  import * as FileSystem from 'expo-file-system';
8
8
  import { decode as atob } from 'base-64';
9
+ import debug from 'debug';
9
10
 
10
11
  const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
11
12
 
13
+ const log = debug("expo-audio-stream:useAudioRecording");
14
+
12
15
  interface UseAudioRecorderState {
13
- startRecording: (_: RecordingOptions) => Promise<void>;
14
- stopRecording: () => Promise<number>;
16
+ startRecording: (_: RecordingOptions) => Promise<string | null>;
17
+ stopRecording: () => Promise<AudioStreamResult | null>;
15
18
  pauseRecording: () => void;
16
19
  isRecording: boolean;
17
20
  isPaused: boolean;
@@ -19,6 +22,7 @@ interface UseAudioRecorderState {
19
22
  size: number; // Size in bytes of the recorded audio
20
23
  }
21
24
 
25
+
22
26
  export function useAudioRecorder({onAudioStream}: {onAudioStream?: (buffer: Blob) => void}): UseAudioRecorderState {
23
27
  const [isRecording, setIsRecording] = useState(false);
24
28
  const [isPaused, setIsPaused] = useState(false);
@@ -40,34 +44,43 @@ export function useAudioRecorder({onAudioStream}: {onAudioStream?: (buffer: Blob
40
44
 
41
45
 
42
46
  useEffect(() => {
43
- const subscribe = addChangeListener(async ({fileUri, deltaSize, totalSize, from, streamUuid, encoded, buffer}) => {
44
- console.debug(`Received audio event:`, {fileUri, deltaSize, totalSize, from, streamUuid, encodedLength: encoded?.length})
47
+ const subscribe = addAudioEventListener(async ({fileUri, deltaSize, totalSize, from, streamUuid, encoded, mimeType, buffer}) => {
48
+ log(`Received audio event:`, {fileUri, deltaSize, totalSize, mimeType, from, streamUuid, encodedLength: encoded?.length})
45
49
  if(deltaSize > 0) {
46
- // Fetch the audio data from the fileUri
47
- const options = {
48
- encoding: FileSystem.EncodingType.Base64,
49
- position: from,
50
- length: deltaSize,
51
- };
52
-
50
+ // Coming from native ( ios / android ) otherwise buffer is set
53
51
  if(Platform.OS !== 'web') {
54
52
  // Read the audio file as a base64 string for comparison
55
53
  try {
56
- const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
57
- const binaryData = atob(base64Content);
54
+ // convert encoded string to binary data
55
+ const binaryData = atob(encoded);
58
56
  const content = new Uint8Array(binaryData.length);
59
57
  for (let i = 0; i < binaryData.length; i++) {
60
- content[i] = binaryData.charCodeAt(i);
58
+ content[i] = binaryData.charCodeAt(i);
61
59
  }
60
+ const audioBlob = new Blob([content], { type: mimeType });
61
+
62
+ // Below code is optional, used to compare encoded data to audio on file system
63
+ // Fetch the audio data from the fileUri
64
+ // const options = {
65
+ // encoding: FileSystem.EncodingType.Base64,
66
+ // position: from,
67
+ // length: deltaSize,
68
+ // };
69
+ // const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
70
+ // const binaryData = atob(base64Content);
71
+ // const content = new Uint8Array(binaryData.length);
72
+ // for (let i = 0; i < binaryData.length; i++) {
73
+ // content[i] = binaryData.charCodeAt(i);
74
+ // }
75
+ // const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
76
+ // console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
62
77
 
63
- // TODO: get the filetype based on audio setting and encoding
64
- const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
65
- console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
66
78
  onAudioStream?.(audioBlob);
67
79
  } catch (error) {
68
80
  console.error('Error reading audio file:', error);
69
81
  }
70
82
  } else if(buffer) {
83
+ // Coming from web
71
84
  onAudioStream?.(buffer);
72
85
  }
73
86
  }
@@ -77,57 +90,37 @@ export function useAudioRecorder({onAudioStream}: {onAudioStream?: (buffer: Blob
77
90
 
78
91
 
79
92
  const startRecording = useCallback(async (recordingOptions: RecordingOptions) => {
80
- if (!isRecording) {
81
- setIsRecording(true);
82
- setIsPaused(false);
83
- setSize(0);
84
- setDuration(0);
85
- const startTime = Date.now();
86
-
87
- console.log(`module shims`, ExpoAudioStreamModule)
88
- try {
89
- console.log(`start recoding`, recordingOptions)
90
- await ExpoAudioStreamModule.startRecording(recordingOptions);
91
-
92
- } catch (error) {
93
- console.error('Error starting recording:', error);
94
- setIsRecording(false);
95
- }
96
- }
97
- }, [isRecording]);
98
-
99
- const stopRecording = useCallback(async (): Promise<number> => {
100
- if (isRecording) {
93
+ setIsRecording(true);
94
+ setIsPaused(false);
95
+ setSize(0);
96
+ setDuration(0);
97
+ try {
98
+ log(`start recoding`, recordingOptions)
99
+ const fileUrl = await ExpoAudioStreamModule.startRecording(recordingOptions);
100
+
101
+ return fileUrl;
102
+ } catch (error) {
103
+ console.error('Error starting recording:', error);
101
104
  setIsRecording(false);
102
- setIsPaused(false);
103
- try {
104
- const recordedDuration = await ExpoAudioStreamModule.stopRecording();
105
- setDuration(recordedDuration);
106
- return recordedDuration;
107
- } catch (error) {
108
- console.error('Error stopping recording:', error);
109
- return 0;
110
- }
111
105
  }
112
- return 0;
113
- }, [isRecording]);
114
-
115
- const pauseRecording = useCallback(() => {
116
- if (isRecording) {
117
- ExpoAudioStreamModule.stopRecording().catch(console.error);
106
+ }, []);
107
+
108
+ const stopRecording = useCallback(async () => {
109
+ setIsRecording(false);
110
+ setIsPaused(false);
111
+ const result: AudioStreamResult = await ExpoAudioStreamModule.stopRecording();
112
+ return result;
113
+ }, []);
114
+
115
+ const pauseRecording = useCallback(async () => {
116
+ try {
117
+ await ExpoAudioStreamModule.stopRecording()
118
118
  setIsPaused(true);
119
119
  setIsRecording(false);
120
+ } catch (error) {
121
+ console.error('Error pausing recording:', error);
120
122
  }
121
- }, [isRecording]);
122
-
123
- // Cleanup listener on unmount to prevent memory leaks
124
- useEffect(() => {
125
- return () => {
126
- if (isRecording) {
127
- ExpoAudioStreamModule.stopRecording().catch(console.error);
128
- }
129
- };
130
- }, [isRecording]);
123
+ }, []);
131
124
 
132
125
  return {
133
126
  startRecording,
package/yarn-error.log DELETED
@@ -1,72 +0,0 @@
1
- Arguments:
2
- /Users/arthurbreton/.asdf/installs/nodejs/18.19.1/bin/node /Users/arthurbreton/.cache/node/corepack/yarn/1.22.19/bin/yarn.js
3
-
4
- PATH:
5
- /Users/arthurbreton/.asdf/plugins/nodejs/shims:/Users/arthurbreton/.asdf/installs/nodejs/18.19.1/.npm/bin:/Users/arthurbreton/.asdf/installs/nodejs/18.19.1/bin:/Users/arthurbreton/.docker/bin:/Users/arthurbreton/.asdf/shims:/opt/homebrew/opt/asdf/libexec/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/X11/bin:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS:/opt/homebrew/opt/libpq/bin:/Users/arthurbreton/Library/Android/sdk/platform-tools:/Users/arthurbreton/.docker/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/arthurbreton/.cargo/bin:/anaconda3/bin:/Users/arthurbreton/.foundry/bin:/opt/homebrew/anaconda3/bin
6
-
7
- Yarn version:
8
- 1.22.19
9
-
10
- Node version:
11
- 18.19.1
12
-
13
- Platform:
14
- darwin arm64
15
-
16
- Trace:
17
- SyntaxError: /Users/arthurbreton/Library/Caches/Yarn/v6/npm-xcode-3.0.1-3efb62aac641ab2c702458f9a0302696146aa53c-integrity/node_modules/xcode/package.json: Unexpected end of JSON input
18
- at JSON.parse (<anonymous>)
19
- at /Users/arthurbreton/.cache/node/corepack/yarn/1.22.19/lib/cli.js:1629:59
20
- at Generator.next (<anonymous>)
21
- at step (/Users/arthurbreton/.cache/node/corepack/yarn/1.22.19/lib/cli.js:310:30)
22
- at /Users/arthurbreton/.cache/node/corepack/yarn/1.22.19/lib/cli.js:321:13
23
-
24
- npm manifest:
25
- {
26
- "name": "expo-audio-stream",
27
- "version": "0.1.0",
28
- "description": "stream audio crossplatform",
29
- "main": "build/index.js",
30
- "types": "build/index.d.ts",
31
- "scripts": {
32
- "build": "expo-module build",
33
- "clean": "expo-module clean",
34
- "lint": "expo-module lint",
35
- "test": "expo-module test",
36
- "prepare": "expo-module prepare",
37
- "prepublishOnly": "expo-module prepublishOnly",
38
- "expo-module": "expo-module",
39
- "open:ios": "open -a \"Xcode\" example/ios",
40
- "open:android": "open -a \"Android Studio\" example/android"
41
- },
42
- "keywords": [
43
- "react-native",
44
- "expo",
45
- "expo-audio-stream",
46
- "ExpoAudioStream"
47
- ],
48
- "repository": "https://github.com/deeeed/expo-audio-stream",
49
- "bugs": {
50
- "url": "https://github.com/deeeed/expo-audio-stream/issues"
51
- },
52
- "author": "Arthur Breton <abreton@siteed.net> (https://github.com/deeeed)",
53
- "license": "MIT",
54
- "homepage": "https://github.com/deeeed/expo-audio-stream#readme",
55
- "dependencies": {},
56
- "devDependencies": {
57
- "@types/react": "^18.0.25",
58
- "expo-module-scripts": "^3.4.1",
59
- "expo-modules-core": "^1.11.12"
60
- },
61
- "peerDependencies": {
62
- "expo": "*",
63
- "react": "*",
64
- "react-native": "*"
65
- }
66
- }
67
-
68
- yarn manifest:
69
- No manifest
70
-
71
- Lockfile:
72
- No lockfile