@siteed/expo-audio-stream 1.0.2 → 1.0.3

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.
Files changed (113) hide show
  1. package/.size-limit.json +6 -0
  2. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +76 -0
  3. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
  4. package/build/AudioAnalysis/AudioAnalysis.types.js +3 -0
  5. package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  6. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +4 -0
  7. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
  8. package/build/AudioAnalysis/extractAudioAnalysis.js +101 -0
  9. package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  10. package/build/AudioAnalysis/extractWaveform.d.ts +8 -0
  11. package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -0
  12. package/build/AudioAnalysis/extractWaveform.js +14 -0
  13. package/build/AudioAnalysis/extractWaveform.js.map +1 -0
  14. package/build/AudioRecorder.provider.d.ts +14 -1
  15. package/build/AudioRecorder.provider.d.ts.map +1 -1
  16. package/build/AudioRecorder.provider.js +17 -4
  17. package/build/AudioRecorder.provider.js.map +1 -1
  18. package/build/ExpoAudioStream.types.d.ts +26 -84
  19. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  20. package/build/ExpoAudioStream.types.js.map +1 -1
  21. package/build/ExpoAudioStream.web.d.ts +6 -5
  22. package/build/ExpoAudioStream.web.d.ts.map +1 -1
  23. package/build/ExpoAudioStream.web.js +9 -8
  24. package/build/ExpoAudioStream.web.js.map +1 -1
  25. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  26. package/build/ExpoAudioStreamModule.js +5 -1
  27. package/build/ExpoAudioStreamModule.js.map +1 -1
  28. package/build/{WebRecorder.d.ts → WebRecorder.web.d.ts} +7 -3
  29. package/build/WebRecorder.web.d.ts.map +1 -0
  30. package/build/{WebRecorder.js → WebRecorder.web.js} +74 -29
  31. package/build/WebRecorder.web.js.map +1 -0
  32. package/build/constants.d.ts +11 -0
  33. package/build/constants.d.ts.map +1 -0
  34. package/build/constants.js +14 -0
  35. package/build/constants.js.map +1 -0
  36. package/build/events.d.ts +6 -0
  37. package/build/events.d.ts.map +1 -0
  38. package/build/events.js +15 -0
  39. package/build/events.js.map +1 -0
  40. package/build/index.d.ts +8 -16
  41. package/build/index.d.ts.map +1 -1
  42. package/build/index.js +6 -112
  43. package/build/index.js.map +1 -1
  44. package/build/logger.d.ts +9 -0
  45. package/build/logger.d.ts.map +1 -0
  46. package/build/logger.js +17 -0
  47. package/build/logger.js.map +1 -0
  48. package/build/{useAudioRecording.d.ts → useAudioRecorder.d.ts} +6 -7
  49. package/build/useAudioRecorder.d.ts.map +1 -0
  50. package/build/{useAudioRecording.js → useAudioRecorder.js} +69 -65
  51. package/build/useAudioRecorder.js.map +1 -0
  52. package/build/utils/convertPCMToFloat32.d.ts +11 -0
  53. package/build/utils/convertPCMToFloat32.d.ts.map +1 -0
  54. package/build/utils/convertPCMToFloat32.js +41 -0
  55. package/build/utils/convertPCMToFloat32.js.map +1 -0
  56. package/build/utils/encodingToBitDepth.d.ts +5 -0
  57. package/build/utils/encodingToBitDepth.d.ts.map +1 -0
  58. package/build/utils/encodingToBitDepth.js +13 -0
  59. package/build/utils/encodingToBitDepth.js.map +1 -0
  60. package/build/utils/getWavFileInfo.d.ts +25 -0
  61. package/build/utils/getWavFileInfo.d.ts.map +1 -0
  62. package/build/utils/getWavFileInfo.js +89 -0
  63. package/build/utils/getWavFileInfo.js.map +1 -0
  64. package/build/utils/writeWavHeader.d.ts +9 -0
  65. package/build/utils/writeWavHeader.d.ts.map +1 -0
  66. package/build/utils/writeWavHeader.js +41 -0
  67. package/build/utils/writeWavHeader.js.map +1 -0
  68. package/build/workers/InlineFeaturesExtractor.web.d.ts +2 -0
  69. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
  70. package/build/workers/InlineFeaturesExtractor.web.js +303 -0
  71. package/build/workers/InlineFeaturesExtractor.web.js.map +1 -0
  72. package/build/workers/inlineAudioWebWorker.web.d.ts +2 -0
  73. package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
  74. package/build/workers/inlineAudioWebWorker.web.js +243 -0
  75. package/build/workers/inlineAudioWebWorker.web.js.map +1 -0
  76. package/ios/AudioStreamManager.swift +39 -2
  77. package/ios/ExpoAudioStreamModule.swift +10 -0
  78. package/package.json +7 -6
  79. package/plugin/tsconfig.json +1 -1
  80. package/publish.sh +0 -0
  81. package/src/AudioAnalysis/AudioAnalysis.types.ts +85 -0
  82. package/src/AudioAnalysis/extractAudioAnalysis.ts +136 -0
  83. package/src/AudioAnalysis/extractWaveform.ts +25 -0
  84. package/src/AudioRecorder.provider.tsx +35 -7
  85. package/src/ExpoAudioStream.types.ts +33 -94
  86. package/src/ExpoAudioStream.web.ts +17 -16
  87. package/src/ExpoAudioStreamModule.ts +6 -1
  88. package/src/{WebRecorder.ts → WebRecorder.web.ts} +85 -33
  89. package/src/constants.ts +18 -0
  90. package/src/events.ts +25 -0
  91. package/src/index.ts +8 -169
  92. package/src/logger.ts +26 -0
  93. package/src/{useAudioRecording.tsx → useAudioRecorder.tsx} +141 -136
  94. package/src/utils/convertPCMToFloat32.ts +48 -0
  95. package/src/utils/encodingToBitDepth.ts +18 -0
  96. package/src/utils/getWavFileInfo.ts +125 -0
  97. package/src/utils/writeWavHeader.ts +56 -0
  98. package/src/workers/InlineFeaturesExtractor.web.tsx +302 -0
  99. package/src/workers/inlineAudioWebWorker.web.tsx +242 -0
  100. package/build/WebRecorder.d.ts.map +0 -1
  101. package/build/WebRecorder.js.map +0 -1
  102. package/build/inlineAudioWebWorker.d.ts +0 -3
  103. package/build/inlineAudioWebWorker.d.ts.map +0 -1
  104. package/build/inlineAudioWebWorker.js +0 -340
  105. package/build/inlineAudioWebWorker.js.map +0 -1
  106. package/build/useAudioRecording.d.ts.map +0 -1
  107. package/build/useAudioRecording.js.map +0 -1
  108. package/build/utils.d.ts +0 -31
  109. package/build/utils.d.ts.map +0 -1
  110. package/build/utils.js +0 -143
  111. package/build/utils.js.map +0 -1
  112. package/src/inlineAudioWebWorker.tsx +0 -340
  113. package/src/utils.ts +0 -189
@@ -1,18 +1,46 @@
1
+ // packages/expo-audio-stream/src/AudioRecorder.provider.tsx
1
2
  import React, { createContext, useContext } from "react";
2
3
 
4
+ import { AudioAnalysisData } from "./AudioAnalysis/AudioAnalysis.types";
3
5
  import {
4
- UseAudioRecorderProps,
5
- UseAudioRecorderState,
6
- useAudioRecorder,
7
- } from "./useAudioRecording";
6
+ AudioRecordingResult,
7
+ RecordingConfig,
8
+ StartRecordingResult,
9
+ } from "./ExpoAudioStream.types";
10
+ import { UseAudioRecorderProps, useAudioRecorder } from "./useAudioRecorder";
8
11
 
9
- const AudioRecorderContext = createContext<UseAudioRecorderState>({
12
+ export interface UseAudioRecorderState {
13
+ startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>;
14
+ stopRecording: () => Promise<AudioRecordingResult | null>;
15
+ pauseRecording: () => void;
16
+ resumeRecording: () => void;
17
+ isRecording: boolean;
18
+ isPaused: boolean;
19
+ durationMs: number; // Duration of the recording
20
+ size: number; // Size in bytes of the recorded audio
21
+ analysisData?: AudioAnalysisData;
22
+ }
23
+
24
+ const initContext: UseAudioRecorderState = {
10
25
  isRecording: false,
11
26
  isPaused: false,
12
27
  durationMs: 0,
13
28
  size: 0,
14
- // other properties filled on useAudioRecorder
15
- } as UseAudioRecorderState);
29
+ startRecording: async () => {
30
+ throw new Error("AudioRecorderProvider not found");
31
+ },
32
+ stopRecording: async () => {
33
+ throw new Error("AudioRecorderProvider not found");
34
+ },
35
+ pauseRecording: () => {
36
+ throw new Error("AudioRecorderProvider not found");
37
+ },
38
+ resumeRecording: () => {
39
+ throw new Error("AudioRecorderProvider not found");
40
+ },
41
+ };
42
+
43
+ const AudioRecorderContext = createContext<UseAudioRecorderState>(initContext);
16
44
 
17
45
  interface AudioRecorderProviderProps {
18
46
  children: React.ReactNode;
@@ -1,33 +1,8 @@
1
- export interface AudioEventPayload {
2
- encoded?: string;
3
- buffer?: ArrayBuffer;
4
- fileUri: string;
5
- lastEmittedSize: number;
6
- position: number;
7
- deltaSize: number;
8
- totalSize: number;
9
- mimeType: string;
10
- streamUuid: string;
11
- }
12
-
13
- export interface AudioStreamResult {
14
- fileUri: string;
15
- webAudioUri?: string;
16
- durationMs: number;
17
- size: number;
18
- mimeType: string;
19
- channels?: number;
20
- bitDepth?: number;
21
- sampleRate?: number;
22
- }
23
-
24
- export interface StartAudioStreamResult {
25
- fileUri: string;
26
- mimeType: string;
27
- channels?: number;
28
- bitDepth?: number;
29
- sampleRate?: number;
30
- }
1
+ // packages/expo-audio-stream/src/ExpoAudioStream.types.ts
2
+ import {
3
+ AudioAnalysisData,
4
+ AudioFeaturesOptions,
5
+ } from "./AudioAnalysis/AudioAnalysis.types";
31
6
 
32
7
  export interface AudioStreamStatus {
33
8
  isRecording: boolean;
@@ -46,74 +21,41 @@ export interface AudioDataEvent {
46
21
  totalSize: number;
47
22
  }
48
23
 
49
- export interface AudioFeatures {
50
- energy: number;
51
- mfcc: number[];
52
- rms: number;
53
- minAmplitude: number;
54
- maxAmplitude: number;
55
- zcr: number;
56
- spectralCentroid: number;
57
- spectralFlatness: number;
58
- spectralRolloff: number;
59
- spectralBandwidth: number;
60
- chromagram: number[];
61
- tempo: number;
62
- hnr: number;
24
+ export interface AudioEventPayload {
25
+ encoded?: string;
26
+ buffer?: ArrayBuffer;
27
+ fileUri: string;
28
+ lastEmittedSize: number;
29
+ position: number;
30
+ deltaSize: number;
31
+ totalSize: number;
32
+ mimeType: string;
33
+ streamUuid: string;
63
34
  }
64
35
 
65
- export interface AudioFeaturesOptions {
66
- energy?: boolean;
67
- mfcc?: boolean;
68
- rms?: boolean;
69
- zcr?: boolean;
70
- spectralCentroid?: boolean;
71
- spectralFlatness?: boolean;
72
- spectralRolloff?: boolean;
73
- spectralBandwidth?: boolean;
74
- chromagram?: boolean;
75
- tempo?: boolean;
76
- hnr?: boolean;
77
- }
36
+ export type EncodingType = "pcm_32bit" | "pcm_16bit" | "pcm_8bit";
37
+ export type SampleRate = 16000 | 44100 | 48000;
38
+ export type BitDepth = 8 | 16 | 32;
78
39
 
79
- export interface DataPoint {
80
- id: number;
81
- amplitude: number;
82
- activeSpeech?: boolean;
83
- dB?: number;
84
- silent?: boolean;
85
- features?: AudioFeatures;
86
- startTime?: number;
87
- endTime?: number;
88
- // start / end position in bytes
89
- startPosition?: number;
90
- endPosition?: number;
91
- // number of audio samples for this point (samples size depends on bit depth)
92
- samples?: number;
93
- // Id of the speaker for this point
94
- speaker?: number;
40
+ export interface AudioRecordingResult {
41
+ fileUri: string;
42
+ webAudioUri?: string;
43
+ durationMs: number;
44
+ size: number;
45
+ mimeType: string;
46
+ channels: number;
47
+ bitDepth: BitDepth;
48
+ sampleRate: SampleRate;
95
49
  }
96
50
 
97
- export interface AudioAnalysisData {
98
- pointsPerSecond: number; // How many consolidated value per second
99
- durationMs: number; // Duration of the audio in milliseconds
100
- bitDepth: number; // Bit depth of the audio
101
- samples: number; // Size of the audio in bytes
102
- numberOfChannels: number; // Number of audio channels
103
- sampleRate: number; // Sample rate of the audio
104
- dataPoints: DataPoint[];
105
- amplitudeRange: {
106
- min: number;
107
- max: number;
108
- };
109
- speakerChanges?: {
110
- timestamp: number;
111
- speaker: number;
112
- }[];
51
+ export interface StartRecordingResult {
52
+ fileUri: string;
53
+ mimeType: string;
54
+ channels?: number;
55
+ bitDepth?: BitDepth;
56
+ sampleRate?: SampleRate;
113
57
  }
114
58
 
115
- export type EncodingType = "pcm_32bit" | "pcm_16bit" | "pcm_8bit";
116
- export type SampleRate = 16000 | 44100 | 48000;
117
59
  export interface RecordingConfig {
118
60
  sampleRate?: SampleRate; // Sample rate for recording
119
61
  channels?: 1 | 2; // 1 or 2 (MONO or STEREO)
@@ -121,15 +63,12 @@ export interface RecordingConfig {
121
63
  interval?: number; // Interval in milliseconds at which to emit recording data
122
64
 
123
65
  // Optional parameters for audio processing
124
- //TODO remove maxRecentDataDuration - should be replaced by maxDataPoints to 100.
125
- maxRecentDataDuration?: number; // Maximum duration of recent data to keep for processing (default is 10.0 seconds)
126
66
  enableProcessing?: boolean; // Boolean to enable/disable audio processing (default is false)
127
67
  pointsPerSecond?: number; // Number of data points to extract per second of audio (default is 1000)
128
68
  algorithm?: string; // Algorithm to use for extraction (default is "rms")
129
69
  features?: AudioFeaturesOptions; // Feature options to extract (default is empty)
130
70
 
131
71
  // Optional paramters from web
132
-
133
72
  onAudioStream?: (_: AudioDataEvent) => Promise<void>; // Callback function to handle audio stream
134
73
  onProcessingResult?: (_: AudioAnalysisData) => Promise<void>; // Callback function to handle processing results
135
74
  }
@@ -1,17 +1,18 @@
1
1
  // src/ExpoAudioStreamModule.web.ts
2
- import debug from "debug";
3
2
  import { EventEmitter } from "expo-modules-core";
4
3
 
4
+ import { AudioAnalysisData } from "./AudioAnalysis/AudioAnalysis.types";
5
5
  import {
6
- AudioAnalysisData,
7
6
  AudioEventPayload,
8
- AudioStreamResult,
7
+ AudioRecordingResult,
9
8
  AudioStreamStatus,
9
+ BitDepth,
10
10
  RecordingConfig,
11
- StartAudioStreamResult,
11
+ StartRecordingResult,
12
12
  } from "./ExpoAudioStream.types";
13
- import { WebRecorder } from "./WebRecorder";
14
- import { encodingToBitDepth } from "./utils";
13
+ import { WebRecorder } from "./WebRecorder.web";
14
+ import { getLogger } from "./logger";
15
+ import { encodingToBitDepth } from "./utils/encodingToBitDepth";
15
16
 
16
17
  export interface EmitAudioEventProps {
17
18
  data: ArrayBuffer;
@@ -25,8 +26,8 @@ export interface ExpoAudioStreamWebProps {
25
26
  featuresExtratorUrl: string;
26
27
  }
27
28
 
28
- // const log = debug("expo-audio-stream:useAudioRecording");
29
- const log = console;
29
+ const logger = getLogger("ExpoAudioStreamWeb");
30
+
30
31
  export class ExpoAudioStreamWeb extends EventEmitter {
31
32
  customRecorder: WebRecorder | null;
32
33
  audioChunks: ArrayBuffer[];
@@ -42,7 +43,7 @@ export class ExpoAudioStreamWeb extends EventEmitter {
42
43
  streamUuid: string | null;
43
44
  extension: "webm" | "wav" = "wav"; // Default extension is 'webm'
44
45
  recordingConfig?: RecordingConfig;
45
- bitDepth: number; // Bit depth of the audio
46
+ bitDepth: BitDepth; // Bit depth of the audio
46
47
  audioWorkletUrl: string;
47
48
  featuresExtratorUrl: string;
48
49
 
@@ -118,7 +119,7 @@ export class ExpoAudioStreamWeb extends EventEmitter {
118
119
  this.lastEmittedSize = this.currentSize;
119
120
  },
120
121
  emitAudioAnalysisCallback: (audioAnalysisData: AudioAnalysisData) => {
121
- console.log(`Emitted AudioAnalysis:`, audioAnalysisData);
122
+ logger.log(`Emitted AudioAnalysis:`, audioAnalysisData);
122
123
  this.emit("AudioAnalysis", audioAnalysisData);
123
124
  },
124
125
  });
@@ -127,7 +128,7 @@ export class ExpoAudioStreamWeb extends EventEmitter {
127
128
 
128
129
  // // Set a timer to stop recording after 5 seconds
129
130
  // setTimeout(() => {
130
- // console.log("AUTO Stopping recording");
131
+ // logger.log("AUTO Stopping recording");
131
132
  // this.customRecorder?.stopAndPlay();
132
133
  // this.isRecording = false;
133
134
  // }, 3000);
@@ -140,7 +141,7 @@ export class ExpoAudioStreamWeb extends EventEmitter {
140
141
  this.lastEmittedTime = 0;
141
142
  this.streamUuid = Date.now().toString();
142
143
  const fileUri = `${this.streamUuid}.${this.extension}`;
143
- const streamConfig: StartAudioStreamResult = {
144
+ const streamConfig: StartRecordingResult = {
144
145
  fileUri,
145
146
  mimeType: `audio/${this.extension}`,
146
147
  bitDepth: this.bitDepth,
@@ -167,14 +168,14 @@ export class ExpoAudioStreamWeb extends EventEmitter {
167
168
  }
168
169
 
169
170
  // Stop recording
170
- async stopRecording(): Promise<AudioStreamResult | null> {
171
+ async stopRecording(): Promise<AudioRecordingResult | null> {
171
172
  if (this.customRecorder) {
172
173
  const fullPcmBuffer = await this.customRecorder.stop();
173
- log.debug(`Stopped recording`, fullPcmBuffer);
174
+ logger.debug(`Stopped recording`, fullPcmBuffer);
174
175
  }
175
176
  this.isRecording = false;
176
177
  this.currentDurationMs = Date.now() - this.recordingStartTime;
177
- const result: AudioStreamResult = {
178
+ const result: AudioRecordingResult = {
178
179
  fileUri: `${this.streamUuid}.${this.extension}`,
179
180
  bitDepth: this.bitDepth,
180
181
  channels: this.recordingConfig?.channels ?? 1,
@@ -194,7 +195,7 @@ export class ExpoAudioStreamWeb extends EventEmitter {
194
195
  }
195
196
 
196
197
  if (this.customRecorder) {
197
- this.customRecorder.stop();
198
+ this.customRecorder.pause();
198
199
  }
199
200
  this.isPaused = true;
200
201
  this.pausedTime = Date.now();
@@ -9,8 +9,13 @@ import {
9
9
  let ExpoAudioStreamModule: any;
10
10
 
11
11
  if (Platform.OS === "web") {
12
+ let instance: ExpoAudioStreamWeb | null = null;
13
+
12
14
  ExpoAudioStreamModule = (webProps: ExpoAudioStreamWebProps) => {
13
- return new ExpoAudioStreamWeb(webProps);
15
+ if (!instance) {
16
+ instance = new ExpoAudioStreamWeb(webProps);
17
+ }
18
+ return instance;
14
19
  };
15
20
  } else {
16
21
  ExpoAudioStreamModule = requireNativeModule("ExpoAudioStream");
@@ -1,10 +1,15 @@
1
1
  // src/WebRecorder.ts
2
- import { AudioAnalysisData, RecordingConfig } from "./ExpoAudioStream.types";
2
+ import { AudioAnalysisData } from "./AudioAnalysis/AudioAnalysis.types";
3
+ import { RecordingConfig } from "./ExpoAudioStream.types";
3
4
  import {
4
5
  EmitAudioAnalysisFunction,
5
6
  EmitAudioEventFunction,
6
7
  } from "./ExpoAudioStream.web";
7
- import { encodingToBitDepth } from "./utils";
8
+ import { getLogger } from "./logger";
9
+ import { encodingToBitDepth } from "./utils/encodingToBitDepth";
10
+ import { InlineFeaturesExtractor } from "./workers/InlineFeaturesExtractor.web";
11
+ import { InlineAudioWebWorker } from "./workers/inlineAudioWebWorker.web";
12
+
8
13
  interface AudioWorkletEvent {
9
14
  data: {
10
15
  command: string;
@@ -25,12 +30,13 @@ const DEFAULT_WEB_POINTS_PER_SECOND = 10;
25
30
  const DEFAULT_WEB_INTERVAL = 500;
26
31
  const DEFAULT_WEB_NUMBER_OF_CHANNELS = 1;
27
32
 
28
- // const log = debug("expo-audio-stream:WebRecorder");
29
- const log = console.log;
33
+ const TAG = "WebRecorder";
34
+ const logger = getLogger(TAG);
35
+
30
36
  export class WebRecorder {
31
37
  private audioContext: AudioContext;
32
38
  private audioWorkletNode!: AudioWorkletNode;
33
- private featureExtractorWorker: Worker;
39
+ private featureExtractorWorker?: Worker;
34
40
  private source: MediaStreamAudioSourceNode;
35
41
  private audioWorkletUrl: string;
36
42
  private emitAudioEventCallback: EmitAudioEventFunction;
@@ -72,7 +78,7 @@ export class WebRecorder {
72
78
  const audioContextFormat = this.checkAudioContextFormat({
73
79
  sampleRate: this.audioContext.sampleRate,
74
80
  });
75
- log("Initialized WebRecorder with config:", {
81
+ logger.debug("Initialized WebRecorder with config:", {
76
82
  sampleRate: audioContextFormat.sampleRate,
77
83
  bitDepth: audioContextFormat.bitDepth,
78
84
  numberOfChannels: audioContextFormat.numberOfChannels,
@@ -101,26 +107,22 @@ export class WebRecorder {
101
107
  speakerChanges: [],
102
108
  };
103
109
 
104
- // Initialize the feature extractor worker
105
- //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library
106
- // We keep the url during dev and use the blob in production.
107
- this.featureExtractorWorker = new Worker(
108
- new URL(featuresExtratorUrl, window.location.href),
109
- );
110
- this.featureExtractorWorker.onmessage =
111
- this.handleFeatureExtractorMessage.bind(this);
110
+ if (recordingConfig.enableProcessing) {
111
+ this.initFeatureExtractorWorker();
112
+ }
112
113
  }
113
114
 
114
115
  async init() {
115
116
  try {
116
- // TODO: Use the inline processor script for the audio worklet if the script is not available
117
- // const blob = new Blob([InlineProcessorScrippt], {
118
- // type: "application/javascript",
119
- // });
120
- // const url = URL.createObjectURL(blob);
121
- // await this.audioContext.audioWorklet.addModule(url);
122
- await this.audioContext.audioWorklet.addModule(this.audioWorkletUrl);
123
-
117
+ if (!this.audioWorkletUrl) {
118
+ const blob = new Blob([InlineAudioWebWorker], {
119
+ type: "application/javascript",
120
+ });
121
+ const url = URL.createObjectURL(blob);
122
+ await this.audioContext.audioWorklet.addModule(url);
123
+ } else {
124
+ await this.audioContext.audioWorklet.addModule(this.audioWorkletUrl);
125
+ }
124
126
  this.audioWorkletNode = new AudioWorkletNode(
125
127
  this.audioContext,
126
128
  "recorder-processor",
@@ -134,7 +136,7 @@ export class WebRecorder {
134
136
  return;
135
137
  }
136
138
  // Handle the audio blob (e.g., send it to the server or process it further)
137
- log("Received audio blob from processor", event);
139
+ logger.debug("Received audio blob from processor", event);
138
140
  const pcmBuffer = event.data.recordedData;
139
141
 
140
142
  if (!pcmBuffer) {
@@ -154,7 +156,7 @@ export class WebRecorder {
154
156
  const otherDuration =
155
157
  pcmBuffer.byteLength /
156
158
  (otherSampleRate * (this.exportBitDepth / this.numberOfChannels)); // Calculate duration of the current buffer
157
- log(
159
+ logger.debug(
158
160
  `sampleRate=${sampleRate} Duration: ${duration} -- otherSampleRate=${otherSampleRate} Other duration: ${otherDuration}`,
159
161
  );
160
162
 
@@ -164,7 +166,7 @@ export class WebRecorder {
164
166
  });
165
167
  this.position += duration; // Update position
166
168
 
167
- this.featureExtractorWorker.postMessage(
169
+ this.featureExtractorWorker?.postMessage(
168
170
  {
169
171
  command: "process",
170
172
  channelData,
@@ -181,7 +183,7 @@ export class WebRecorder {
181
183
  );
182
184
  };
183
185
 
184
- log(
186
+ logger.debug(
185
187
  `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,
186
188
  this.config,
187
189
  );
@@ -200,10 +202,60 @@ export class WebRecorder {
200
202
  this.source.connect(this.audioWorkletNode);
201
203
  this.audioWorkletNode.connect(this.audioContext.destination);
202
204
  } catch (error) {
203
- console.error("Failed to initialize WebRecorder", error);
205
+ console.error(`[${TAG}] Failed to initialize WebRecorder`, error);
206
+ }
207
+ }
208
+
209
+ initFeatureExtractorWorker(featuresExtratorUrl?: string) {
210
+ try {
211
+ if (featuresExtratorUrl) {
212
+ // Initialize the feature extractor worker
213
+ //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library
214
+ // We keep the url during dev and use the blob in production.
215
+ this.featureExtractorWorker = new Worker(
216
+ new URL(featuresExtratorUrl, window.location.href),
217
+ );
218
+ this.featureExtractorWorker.onmessage =
219
+ this.handleFeatureExtractorMessage.bind(this);
220
+ this.featureExtractorWorker.onerror = this.handleWorkerError.bind(this);
221
+ } else {
222
+ // Fallback to the inline worker if the URL is not provided
223
+ this.initFallbackWorker();
224
+ }
225
+ } catch (error) {
226
+ console.error(
227
+ `[${TAG}] Failed to initialize feature extractor worker`,
228
+ error,
229
+ );
230
+ this.initFallbackWorker();
204
231
  }
205
232
  }
206
233
 
234
+ initFallbackWorker() {
235
+ try {
236
+ const blob = new Blob([InlineFeaturesExtractor], {
237
+ type: "application/javascript",
238
+ });
239
+ const url = URL.createObjectURL(blob);
240
+ this.featureExtractorWorker = new Worker(url);
241
+ this.featureExtractorWorker.onmessage =
242
+ this.handleFeatureExtractorMessage.bind(this);
243
+ this.featureExtractorWorker.onerror = (error) => {
244
+ console.error(`[${TAG}] Default Inline worker failed`, error);
245
+ };
246
+ logger.log("Inline worker initialized successfully");
247
+ } catch (error) {
248
+ console.error(
249
+ `[${TAG}] Failed to initialize Inline Feature Extractor worker`,
250
+ error,
251
+ );
252
+ }
253
+ }
254
+
255
+ handleWorkerError(error: ErrorEvent) {
256
+ console.error(`[${TAG}] Feature extractor worker error:`, error);
257
+ }
258
+
207
259
  handleFeatureExtractorMessage(event: AudioFeaturesEvent) {
208
260
  if (event.data.command === "features") {
209
261
  const segmentResult = event.data.result;
@@ -227,8 +279,8 @@ export class WebRecorder {
227
279
  };
228
280
  }
229
281
  // Handle the extracted features (e.g., emit an event or log them)
230
- log("features event segmentResult", segmentResult);
231
- log("features event audioAnalysisData", this.audioAnalysisData);
282
+ logger.debug("features event segmentResult", segmentResult);
283
+ logger.debug("features event audioAnalysisData", this.audioAnalysisData);
232
284
  this.emitAudioAnalysisCallback(segmentResult);
233
285
  }
234
286
  }
@@ -272,10 +324,10 @@ export class WebRecorder {
272
324
  rawPCMDataFull.byteLength /
273
325
  (this.audioContext.sampleRate *
274
326
  (this.exportBitDepth / this.numberOfChannels));
275
- log(
327
+ logger.debug(
276
328
  `Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`,
277
329
  );
278
- log(
330
+ logger.debug(
279
331
  `recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.buffers[0].byteLength}`,
280
332
  );
281
333
 
@@ -330,9 +382,9 @@ export class WebRecorder {
330
382
  bufferSource.buffer = audioBuffer;
331
383
  bufferSource.connect(this.audioContext.destination);
332
384
  bufferSource.start();
333
- log("Playing recorded data", recordedData);
385
+ logger.debug("Playing recorded data", recordedData);
334
386
  } catch (error) {
335
- console.error(`Failed to play recorded data:`, error);
387
+ console.error(`[${TAG}] Failed to play recorded data:`, error);
336
388
  }
337
389
  }
338
390
 
@@ -0,0 +1,18 @@
1
+ // packages/expo-audio-stream/src/constants.ts
2
+ import { Platform } from "react-native";
3
+
4
+ import { BitDepth, SampleRate } from "./ExpoAudioStream.types";
5
+
6
+ export const isWeb = Platform.OS === "web";
7
+ export const DEBUG_NAMESPACE = "expo-audio-stream";
8
+
9
+ // Constants for identifying chunks in a WAV file
10
+ export const RIFF_HEADER = 0x52494646; // "RIFF"
11
+ export const WAVE_HEADER = 0x57415645; // "WAVE"
12
+ export const FMT_CHUNK_ID = 0x666d7420; // "fmt "
13
+ export const DATA_CHUNK_ID = 0x64617461; // "data"
14
+ export const INFO_CHUNK_ID = 0x494e464f; // "INFO"
15
+
16
+ // Default values
17
+ export const DEFAULT_SAMPLE_RATE: SampleRate = 16000;
18
+ export const DEFAULT_BIT_DEPTH: BitDepth = 32;
package/src/events.ts ADDED
@@ -0,0 +1,25 @@
1
+ // packages/expo-audio-stream/src/events.ts
2
+
3
+ import { EventEmitter, type Subscription } from "expo-modules-core";
4
+
5
+ import { AudioAnalysisData } from "./AudioAnalysis/AudioAnalysis.types";
6
+ import { AudioEventPayload } from "./ExpoAudioStream.types";
7
+ import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
8
+ import { getLogger } from "./logger";
9
+
10
+ const emitter = new EventEmitter(ExpoAudioStreamModule);
11
+ const logger = getLogger("events");
12
+
13
+ export function addAudioEventListener(
14
+ listener: (event: AudioEventPayload) => Promise<void>,
15
+ ): Subscription {
16
+ logger.log("Adding listener for AudioData event");
17
+ return emitter.addListener<AudioEventPayload>("AudioData", listener);
18
+ }
19
+
20
+ export function addAudioAnalysisListener(
21
+ listener: (event: AudioAnalysisData) => Promise<void>,
22
+ ): Subscription {
23
+ logger.log("Adding listener for AudioAnalysis event");
24
+ return emitter.addListener<AudioAnalysisData>("AudioAnalysis", listener);
25
+ }