@siteed/expo-audio-stream 1.15.1 → 1.17.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "1.15.1",
3
+ "version": "1.17.0",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -13,6 +13,8 @@ interface AudioStreamPluginOptions {
13
13
  iosConfig?: {
14
14
  allowBackgroundAudioControls?: boolean;
15
15
  backgroundProcessingTitle?: string;
16
+ microphoneUsageDescription?: string;
17
+ notificationUsageDescription?: string;
16
18
  };
17
19
  }
18
20
  declare const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions>;
@@ -21,29 +21,37 @@ const withRecordingPermission = (config, props) => {
21
21
  useLocation: false,
22
22
  useExternalAccessory: false,
23
23
  },
24
+ iosConfig: {
25
+ microphoneUsageDescription: MICROPHONE_USAGE,
26
+ notificationUsageDescription: NOTIFICATION_USAGE,
27
+ },
24
28
  ...(props || {}),
25
29
  };
26
30
  const { enablePhoneStateHandling, enableNotifications, enableBackgroundAudio, } = options;
27
31
  debugLog('📱 Configuring Recording Permissions Plugin...', options);
28
32
  // iOS Configuration
29
33
  config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
30
- // Base microphone permission (always required)
34
+ // Always set the microphone usage description from options first
31
35
  config.modResults['NSMicrophoneUsageDescription'] =
32
- config.modResults['NSMicrophoneUsageDescription'] ||
36
+ options.iosConfig?.microphoneUsageDescription ||
37
+ config.modResults['NSMicrophoneUsageDescription'] ||
33
38
  MICROPHONE_USAGE;
34
39
  if (enableNotifications) {
35
40
  config.modResults['NSUserNotificationsUsageDescription'] =
36
- NOTIFICATION_USAGE;
41
+ options.iosConfig?.notificationUsageDescription ||
42
+ config.modResults['NSUserNotificationsUsageDescription'] ||
43
+ NOTIFICATION_USAGE;
37
44
  config.modResults['NSUserNotificationAlertStyle'] = 'alert';
38
45
  }
39
46
  const existingBackgroundModes = config.modResults.UIBackgroundModes || [];
40
- // Only add background modes if explicitly enabled
41
- if (options.iosBackgroundModes?.useAudio &&
42
- enableBackgroundAudio &&
47
+ // Only add background modes if explicitly enabled and set to true
48
+ if (options.iosBackgroundModes?.useAudio === true &&
49
+ enableBackgroundAudio === true &&
43
50
  !existingBackgroundModes.includes('audio')) {
44
51
  existingBackgroundModes.push('audio');
45
52
  }
46
- if (options.iosBackgroundModes?.useVoIP && enablePhoneStateHandling) {
53
+ if (options.iosBackgroundModes?.useVoIP === true &&
54
+ enablePhoneStateHandling === true) {
47
55
  if (!existingBackgroundModes.includes('voip')) {
48
56
  existingBackgroundModes.push('voip');
49
57
  }
@@ -55,22 +63,22 @@ const withRecordingPermission = (config, props) => {
55
63
  config.modResults.UIRequiredDeviceCapabilities =
56
64
  existingCapabilities;
57
65
  }
58
- // Add additional background modes if enabled
59
- if (options.iosBackgroundModes?.useProcessing) {
66
+ // Add additional background modes only if explicitly set to true
67
+ if (options.iosBackgroundModes?.useProcessing === true) {
60
68
  if (!existingBackgroundModes.includes('processing')) {
61
69
  existingBackgroundModes.push('processing');
62
70
  }
63
71
  // Add processing info if enabled
64
72
  config.modResults.BGTaskSchedulerPermittedIdentifiers = [
65
- 'com.siteed.audiostream.processing'
73
+ 'com.siteed.audiostream.processing',
66
74
  ];
67
75
  }
68
- if (options.iosBackgroundModes?.useLocation) {
76
+ if (options.iosBackgroundModes?.useLocation === true) {
69
77
  if (!existingBackgroundModes.includes('location')) {
70
78
  existingBackgroundModes.push('location');
71
79
  }
72
80
  }
73
- if (options.iosBackgroundModes?.useExternalAccessory) {
81
+ if (options.iosBackgroundModes?.useExternalAccessory === true) {
74
82
  if (!existingBackgroundModes.includes('external-accessory')) {
75
83
  existingBackgroundModes.push('external-accessory');
76
84
  }
@@ -84,7 +92,7 @@ const withRecordingPermission = (config, props) => {
84
92
  if (options.iosConfig?.allowBackgroundAudioControls) {
85
93
  config.modResults.UIBackgroundModes = [
86
94
  ...existingBackgroundModes,
87
- 'remote-notification'
95
+ 'remote-notification',
88
96
  ];
89
97
  config.modResults.MPNowPlayingInfoPropertyPlaybackRate = true;
90
98
  }
@@ -17,7 +17,7 @@ function debugLog(message: string, ...args: unknown[]): void {
17
17
  }
18
18
 
19
19
  interface AudioStreamPluginOptions {
20
- enablePhoneStateHandling?: boolean // Controls READ_PHONE_STATE permission
20
+ enablePhoneStateHandling?: boolean // Controls READ_PHONE_STATE permission
21
21
  enableNotifications?: boolean
22
22
  enableBackgroundAudio?: boolean
23
23
  iosBackgroundModes?: {
@@ -30,6 +30,8 @@ interface AudioStreamPluginOptions {
30
30
  iosConfig?: {
31
31
  allowBackgroundAudioControls?: boolean
32
32
  backgroundProcessingTitle?: string
33
+ microphoneUsageDescription?: string
34
+ notificationUsageDescription?: string
33
35
  }
34
36
  }
35
37
 
@@ -38,7 +40,7 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
38
40
  props: AudioStreamPluginOptions | void
39
41
  ) => {
40
42
  const options: AudioStreamPluginOptions = {
41
- enablePhoneStateHandling: true, // Default to true for backward compatibility
43
+ enablePhoneStateHandling: true, // Default to true for backward compatibility
42
44
  enableNotifications: true,
43
45
  enableBackgroundAudio: true,
44
46
  iosBackgroundModes: {
@@ -48,6 +50,10 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
48
50
  useLocation: false,
49
51
  useExternalAccessory: false,
50
52
  },
53
+ iosConfig: {
54
+ microphoneUsageDescription: MICROPHONE_USAGE,
55
+ notificationUsageDescription: NOTIFICATION_USAGE,
56
+ },
51
57
  ...(props || {}),
52
58
  }
53
59
 
@@ -61,13 +67,16 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
61
67
 
62
68
  // iOS Configuration
63
69
  config = withInfoPlist(config as any, (config) => {
64
- // Base microphone permission (always required)
70
+ // Always set the microphone usage description from options first
65
71
  config.modResults['NSMicrophoneUsageDescription'] =
72
+ options.iosConfig?.microphoneUsageDescription ||
66
73
  config.modResults['NSMicrophoneUsageDescription'] ||
67
74
  MICROPHONE_USAGE
68
75
 
69
76
  if (enableNotifications) {
70
77
  config.modResults['NSUserNotificationsUsageDescription'] =
78
+ options.iosConfig?.notificationUsageDescription ||
79
+ config.modResults['NSUserNotificationsUsageDescription'] ||
71
80
  NOTIFICATION_USAGE
72
81
  config.modResults['NSUserNotificationAlertStyle'] = 'alert'
73
82
  }
@@ -75,16 +84,19 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
75
84
  const existingBackgroundModes =
76
85
  config.modResults.UIBackgroundModes || []
77
86
 
78
- // Only add background modes if explicitly enabled
87
+ // Only add background modes if explicitly enabled and set to true
79
88
  if (
80
- options.iosBackgroundModes?.useAudio &&
81
- enableBackgroundAudio &&
89
+ options.iosBackgroundModes?.useAudio === true &&
90
+ enableBackgroundAudio === true &&
82
91
  !existingBackgroundModes.includes('audio')
83
92
  ) {
84
93
  existingBackgroundModes.push('audio')
85
94
  }
86
95
 
87
- if (options.iosBackgroundModes?.useVoIP && enablePhoneStateHandling) {
96
+ if (
97
+ options.iosBackgroundModes?.useVoIP === true &&
98
+ enablePhoneStateHandling === true
99
+ ) {
88
100
  if (!existingBackgroundModes.includes('voip')) {
89
101
  existingBackgroundModes.push('voip')
90
102
  }
@@ -97,24 +109,24 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
97
109
  existingCapabilities
98
110
  }
99
111
 
100
- // Add additional background modes if enabled
101
- if (options.iosBackgroundModes?.useProcessing) {
112
+ // Add additional background modes only if explicitly set to true
113
+ if (options.iosBackgroundModes?.useProcessing === true) {
102
114
  if (!existingBackgroundModes.includes('processing')) {
103
115
  existingBackgroundModes.push('processing')
104
116
  }
105
117
  // Add processing info if enabled
106
118
  config.modResults.BGTaskSchedulerPermittedIdentifiers = [
107
- 'com.siteed.audiostream.processing'
119
+ 'com.siteed.audiostream.processing',
108
120
  ]
109
121
  }
110
122
 
111
- if (options.iosBackgroundModes?.useLocation) {
123
+ if (options.iosBackgroundModes?.useLocation === true) {
112
124
  if (!existingBackgroundModes.includes('location')) {
113
125
  existingBackgroundModes.push('location')
114
126
  }
115
127
  }
116
128
 
117
- if (options.iosBackgroundModes?.useExternalAccessory) {
129
+ if (options.iosBackgroundModes?.useExternalAccessory === true) {
118
130
  if (!existingBackgroundModes.includes('external-accessory')) {
119
131
  existingBackgroundModes.push('external-accessory')
120
132
  }
@@ -122,7 +134,7 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
122
134
 
123
135
  // Configure background processing info if enabled
124
136
  if (options.iosConfig?.backgroundProcessingTitle) {
125
- config.modResults.BGProcessingTaskTitle =
137
+ config.modResults.BGProcessingTaskTitle =
126
138
  options.iosConfig.backgroundProcessingTitle
127
139
  }
128
140
 
@@ -130,7 +142,7 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
130
142
  if (options.iosConfig?.allowBackgroundAudioControls) {
131
143
  config.modResults.UIBackgroundModes = [
132
144
  ...existingBackgroundModes,
133
- 'remote-notification'
145
+ 'remote-notification',
134
146
  ]
135
147
  config.modResults.MPNowPlayingInfoPropertyPlaybackRate = true
136
148
  }
@@ -20,6 +20,7 @@ export interface AudioStreamStatus {
20
20
  durationMs: number
21
21
  size: number
22
22
  interval: number
23
+ intervalAnalysis: number
23
24
  mimeType: string
24
25
  compression?: CompressionInfo
25
26
  }
@@ -36,7 +37,7 @@ export interface AudioDataEvent {
36
37
  }
37
38
 
38
39
  export type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'
39
- export type SampleRate = 16000 | 44100 | 48000
40
+ export type SampleRate = 16000 | 44100 | 48000
40
41
  export type BitDepth = 8 | 16 | 32
41
42
 
42
43
  export type ConsoleLike = {
@@ -147,7 +148,10 @@ export interface RecordingConfig {
147
148
  // Interval in milliseconds at which to emit recording data
148
149
  interval?: number
149
150
 
150
- // Continue recording when app is in background (default is true)
151
+ // Interval in milliseconds at which to emit analysis data
152
+ intervalAnalysis?: number
153
+
154
+ // Keep the device awake while recording (default is false)
151
155
  keepAwake?: boolean
152
156
 
153
157
  // Show a notification during recording (default is false)
@@ -46,9 +46,11 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
46
46
  currentDurationMs: number
47
47
  currentSize: number
48
48
  currentInterval: number
49
+ currentIntervalAnalysis: number
49
50
  lastEmittedSize: number
50
51
  lastEmittedTime: number
51
52
  lastEmittedCompressionSize: number
53
+ lastEmittedAnalysisTime: number
52
54
  streamUuid: string | null
53
55
  extension: 'webm' | 'wav' = 'wav' // Default extension is 'wav'
54
56
  recordingConfig?: RecordingConfig
@@ -87,10 +89,12 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
87
89
  this.currentSize = 0
88
90
  this.bitDepth = 32 // Default
89
91
  this.currentInterval = 1000 // Default interval in ms
92
+ this.currentIntervalAnalysis = 500 // Default analysis interval in ms
90
93
  this.lastEmittedSize = 0
91
94
  this.lastEmittedTime = 0
92
95
  this.latestPosition = 0
93
96
  this.lastEmittedCompressionSize = 0
97
+ this.lastEmittedAnalysisTime = 0
94
98
  this.streamUuid = null // Initialize UUID on first recording start
95
99
  this.audioWorkletUrl = audioWorkletUrl
96
100
  this.featuresExtratorUrl = featuresExtratorUrl
@@ -172,6 +176,9 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
172
176
  this.lastEmittedSize = 0
173
177
  this.lastEmittedTime = 0
174
178
  this.lastEmittedCompressionSize = 0
179
+ this.currentInterval = recordingConfig.interval ?? 1000
180
+ this.currentIntervalAnalysis = recordingConfig.intervalAnalysis ?? 500
181
+ this.lastEmittedAnalysisTime = Date.now()
175
182
 
176
183
  // Use custom filename if provided, otherwise fallback to timestamp
177
184
  if (recordingConfig.filename) {
@@ -335,6 +342,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
335
342
  durationMs: this.currentDurationMs,
336
343
  size: this.currentSize,
337
344
  interval: this.currentInterval,
345
+ intervalAnalysis: this.currentIntervalAnalysis,
338
346
  mimeType: `audio/${this.extension}`,
339
347
  compression: this.recordingConfig?.compression?.enabled
340
348
  ? {
@@ -193,6 +193,7 @@ export class WebRecorder {
193
193
  fullAudioDurationMs: this.position * 1000,
194
194
  numberOfChannels: this.numberOfChannels,
195
195
  features: this.config.features,
196
+ intervalAnalysis: this.config.intervalAnalysis,
196
197
  },
197
198
  []
198
199
  )
@@ -1,6 +1,8 @@
1
1
  export const InlineFeaturesExtractor = `
2
2
  // Unique ID counter
3
3
  let uniqueIdCounter = 0
4
+ let accumulatedDataPoints = [] // Move outside message handler
5
+ let lastEmitTime = Date.now() // Move outside message handler
4
6
 
5
7
  self.onmessage = function (event) {
6
8
  const {
@@ -12,6 +14,7 @@ self.onmessage = function (event) {
12
14
  fullAudioDurationMs,
13
15
  numberOfChannels,
14
16
  features: _features,
17
+ intervalAnalysis = 500, // Use intervalAnalysis instead of interval
15
18
  } = event.data
16
19
  const features = _features || {}
17
20
 
@@ -295,10 +298,24 @@ self.onmessage = function (event) {
295
298
  pointsPerSecond,
296
299
  algorithm
297
300
  )
298
- self.postMessage({
299
- command: 'features',
300
- result,
301
- })
301
+
302
+ // Accumulate data points
303
+ accumulatedDataPoints = accumulatedDataPoints.concat(result.dataPoints)
304
+
305
+ const currentTime = Date.now()
306
+ const shouldEmitAccumulated = currentTime - lastEmitTime >= intervalAnalysis
307
+
308
+ if (shouldEmitAccumulated) {
309
+ self.postMessage({
310
+ command: 'features',
311
+ result: {
312
+ ...result,
313
+ dataPoints: accumulatedDataPoints
314
+ }
315
+ })
316
+ accumulatedDataPoints = [] // Reset accumulator
317
+ lastEmitTime = currentTime
318
+ }
302
319
  } catch (error) {
303
320
  console.error('[AudioFeaturesExtractor] Error in processing', error)
304
321
  self.postMessage({ error: error.message })