@capgo/capacitor-audio-recorder 7.0.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.
@@ -0,0 +1,231 @@
1
+ import type { PluginListenerHandle } from '@capacitor/core';
2
+ /**
3
+ * Result returned by {@link CapacitorAudioRecorderPlugin.getRecordingStatus}.
4
+ *
5
+ * @since 1.0.0
6
+ */
7
+ export interface GetRecordingStatusResult {
8
+ /**
9
+ * The current recording status.
10
+ *
11
+ * @since 1.0.0
12
+ */
13
+ status: RecordingStatus;
14
+ }
15
+ /**
16
+ * Options accepted by {@link CapacitorAudioRecorderPlugin.startRecording}.
17
+ *
18
+ * @since 1.0.0
19
+ */
20
+ export interface StartRecordingOptions {
21
+ /**
22
+ * The audio session category options for recording. Only available on iOS.
23
+ *
24
+ * @since 1.0.0
25
+ */
26
+ audioSessionCategoryOptions?: AudioSessionCategoryOption[];
27
+ /**
28
+ * The audio session mode for recording. Only available on iOS.
29
+ *
30
+ * @since 1.0.0
31
+ */
32
+ audioSessionMode?: AudioSessionMode;
33
+ /**
34
+ * The audio bit rate in bytes per second.
35
+ * Only available on Android and iOS.
36
+ *
37
+ * @since 1.0.0
38
+ */
39
+ bitRate?: number;
40
+ /**
41
+ * The audio sample rate in Hz.
42
+ * Only available on Android and iOS.
43
+ *
44
+ * @since 1.0.0
45
+ */
46
+ sampleRate?: number;
47
+ }
48
+ /**
49
+ * Result returned by {@link CapacitorAudioRecorderPlugin.stopRecording}.
50
+ *
51
+ * @since 1.0.0
52
+ */
53
+ export interface StopRecordingResult {
54
+ /**
55
+ * The recorded audio as a Blob. Only available on Web.
56
+ *
57
+ * @since 1.0.0
58
+ */
59
+ blob?: Blob;
60
+ /**
61
+ * The duration of the recording in milliseconds.
62
+ *
63
+ * @since 1.0.0
64
+ */
65
+ duration?: number;
66
+ /**
67
+ * The URI pointing to the recorded file. Only available on Android and iOS.
68
+ *
69
+ * @since 1.0.0
70
+ */
71
+ uri?: string;
72
+ }
73
+ /**
74
+ * Permission information returned by {@link CapacitorAudioRecorderPlugin.checkPermissions}
75
+ * and {@link CapacitorAudioRecorderPlugin.requestPermissions}.
76
+ *
77
+ * @since 1.0.0
78
+ */
79
+ export interface PermissionStatus {
80
+ /**
81
+ * The permission state for audio recording.
82
+ *
83
+ * @since 1.0.0
84
+ */
85
+ recordAudio: PermissionState;
86
+ }
87
+ /**
88
+ * Event emitted when an error occurs during recording.
89
+ *
90
+ * @since 1.0.0
91
+ */
92
+ export interface RecordingErrorEvent {
93
+ /**
94
+ * The error message.
95
+ *
96
+ * @since 1.0.0
97
+ */
98
+ message: string;
99
+ }
100
+ /**
101
+ * Event emitted when a recording completes.
102
+ *
103
+ * @since 1.0.0
104
+ */
105
+ export type RecordingStoppedEvent = StopRecordingResult;
106
+ /**
107
+ * The recording status.
108
+ *
109
+ * @since 1.0.0
110
+ */
111
+ export declare enum RecordingStatus {
112
+ Inactive = "INACTIVE",
113
+ Recording = "RECORDING",
114
+ Paused = "PAUSED"
115
+ }
116
+ /**
117
+ * Audio session category options available on iOS.
118
+ *
119
+ * @since 1.0.0
120
+ */
121
+ export declare enum AudioSessionCategoryOption {
122
+ AllowAirPlay = "ALLOW_AIR_PLAY",
123
+ AllowBluetooth = "ALLOW_BLUETOOTH",
124
+ AllowBluetoothA2DP = "ALLOW_BLUETOOTH_A2DP",
125
+ DefaultToSpeaker = "DEFAULT_TO_SPEAKER",
126
+ DuckOthers = "DUCK_OTHERS",
127
+ InterruptSpokenAudioAndMixWithOthers = "INTERRUPT_SPOKEN_AUDIO_AND_MIX_WITH_OTHERS",
128
+ MixWithOthers = "MIX_WITH_OTHERS",
129
+ OverrideMutedMicrophoneInterruption = "OVERRIDE_MUTED_MICROPHONE_INTERRUPTION"
130
+ }
131
+ /**
132
+ * Audio session modes available on iOS.
133
+ *
134
+ * @since 1.0.0
135
+ */
136
+ export declare enum AudioSessionMode {
137
+ Default = "DEFAULT",
138
+ GameChat = "GAME_CHAT",
139
+ Measurement = "MEASUREMENT",
140
+ SpokenAudio = "SPOKEN_AUDIO",
141
+ VideoChat = "VIDEO_CHAT",
142
+ VideoRecording = "VIDEO_RECORDING",
143
+ VoiceChat = "VOICE_CHAT"
144
+ }
145
+ /**
146
+ * Platform permission states supported by Capacitor.
147
+ *
148
+ * @since 1.0.0
149
+ */
150
+ export type PermissionState = 'prompt' | 'prompt-with-rationale' | 'granted' | 'denied';
151
+ /**
152
+ * Capacitor plugin contract for recording audio.
153
+ *
154
+ * @since 1.0.0
155
+ */
156
+ export interface CapacitorAudioRecorderPlugin {
157
+ /**
158
+ * Start recording audio using the device microphone.
159
+ *
160
+ * @param options Recording configuration options.
161
+ * @since 1.0.0
162
+ */
163
+ startRecording(options?: StartRecordingOptions): Promise<void>;
164
+ /**
165
+ * Pause the ongoing recording. Only available on Android (API 24+), iOS, and Web.
166
+ *
167
+ * @since 1.0.0
168
+ */
169
+ pauseRecording(): Promise<void>;
170
+ /**
171
+ * Resume a previously paused recording.
172
+ *
173
+ * @since 1.0.0
174
+ */
175
+ resumeRecording(): Promise<void>;
176
+ /**
177
+ * Stop the current recording and persist the recorded audio.
178
+ *
179
+ * @returns Recording metadata such as duration and URI/blob.
180
+ * @since 1.0.0
181
+ */
182
+ stopRecording(): Promise<StopRecordingResult>;
183
+ /**
184
+ * Cancel the current recording and discard any captured audio.
185
+ *
186
+ * @since 1.0.0
187
+ */
188
+ cancelRecording(): Promise<void>;
189
+ /**
190
+ * Retrieve the current recording status.
191
+ *
192
+ * @since 1.0.0
193
+ */
194
+ getRecordingStatus(): Promise<GetRecordingStatusResult>;
195
+ /**
196
+ * Return the current permission state for accessing the microphone.
197
+ *
198
+ * @since 1.0.0
199
+ */
200
+ checkPermissions(): Promise<PermissionStatus>;
201
+ /**
202
+ * Request permission to access the microphone.
203
+ *
204
+ * @since 1.0.0
205
+ */
206
+ requestPermissions(): Promise<PermissionStatus>;
207
+ /**
208
+ * Listen for recording errors.
209
+ *
210
+ * @since 1.0.0
211
+ */
212
+ addListener(eventName: 'recordingError', listenerFunc: (event: RecordingErrorEvent) => void): Promise<PluginListenerHandle>;
213
+ /**
214
+ * Listen for pause events emitted when a recording is paused.
215
+ *
216
+ * @since 1.0.0
217
+ */
218
+ addListener(eventName: 'recordingPaused', listenerFunc: () => void): Promise<PluginListenerHandle>;
219
+ /**
220
+ * Listen for recording completion events.
221
+ *
222
+ * @since 1.0.0
223
+ */
224
+ addListener(eventName: 'recordingStopped', listenerFunc: (event: RecordingStoppedEvent) => void): Promise<PluginListenerHandle>;
225
+ /**
226
+ * Remove all registered listeners.
227
+ *
228
+ * @since 1.0.0
229
+ */
230
+ removeAllListeners(): Promise<void>;
231
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The recording status.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+ export var RecordingStatus;
7
+ (function (RecordingStatus) {
8
+ RecordingStatus["Inactive"] = "INACTIVE";
9
+ RecordingStatus["Recording"] = "RECORDING";
10
+ RecordingStatus["Paused"] = "PAUSED";
11
+ })(RecordingStatus || (RecordingStatus = {}));
12
+ /**
13
+ * Audio session category options available on iOS.
14
+ *
15
+ * @since 1.0.0
16
+ */
17
+ export var AudioSessionCategoryOption;
18
+ (function (AudioSessionCategoryOption) {
19
+ AudioSessionCategoryOption["AllowAirPlay"] = "ALLOW_AIR_PLAY";
20
+ AudioSessionCategoryOption["AllowBluetooth"] = "ALLOW_BLUETOOTH";
21
+ AudioSessionCategoryOption["AllowBluetoothA2DP"] = "ALLOW_BLUETOOTH_A2DP";
22
+ AudioSessionCategoryOption["DefaultToSpeaker"] = "DEFAULT_TO_SPEAKER";
23
+ AudioSessionCategoryOption["DuckOthers"] = "DUCK_OTHERS";
24
+ AudioSessionCategoryOption["InterruptSpokenAudioAndMixWithOthers"] = "INTERRUPT_SPOKEN_AUDIO_AND_MIX_WITH_OTHERS";
25
+ AudioSessionCategoryOption["MixWithOthers"] = "MIX_WITH_OTHERS";
26
+ AudioSessionCategoryOption["OverrideMutedMicrophoneInterruption"] = "OVERRIDE_MUTED_MICROPHONE_INTERRUPTION";
27
+ })(AudioSessionCategoryOption || (AudioSessionCategoryOption = {}));
28
+ /**
29
+ * Audio session modes available on iOS.
30
+ *
31
+ * @since 1.0.0
32
+ */
33
+ export var AudioSessionMode;
34
+ (function (AudioSessionMode) {
35
+ AudioSessionMode["Default"] = "DEFAULT";
36
+ AudioSessionMode["GameChat"] = "GAME_CHAT";
37
+ AudioSessionMode["Measurement"] = "MEASUREMENT";
38
+ AudioSessionMode["SpokenAudio"] = "SPOKEN_AUDIO";
39
+ AudioSessionMode["VideoChat"] = "VIDEO_CHAT";
40
+ AudioSessionMode["VideoRecording"] = "VIDEO_RECORDING";
41
+ AudioSessionMode["VoiceChat"] = "VOICE_CHAT";
42
+ })(AudioSessionMode || (AudioSessionMode = {}));
43
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAqHA;;;;GAIG;AACH,MAAM,CAAN,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,wCAAqB,CAAA;IACrB,0CAAuB,CAAA;IACvB,oCAAiB,CAAA;AACnB,CAAC,EAJW,eAAe,KAAf,eAAe,QAI1B;AAED;;;;GAIG;AACH,MAAM,CAAN,IAAY,0BASX;AATD,WAAY,0BAA0B;IACpC,6DAA+B,CAAA;IAC/B,gEAAkC,CAAA;IAClC,yEAA2C,CAAA;IAC3C,qEAAuC,CAAA;IACvC,wDAA0B,CAAA;IAC1B,iHAAmF,CAAA;IACnF,+DAAiC,CAAA;IACjC,4GAA8E,CAAA;AAChF,CAAC,EATW,0BAA0B,KAA1B,0BAA0B,QASrC;AAED;;;;GAIG;AACH,MAAM,CAAN,IAAY,gBAQX;AARD,WAAY,gBAAgB;IAC1B,uCAAmB,CAAA;IACnB,0CAAsB,CAAA;IACtB,+CAA2B,CAAA;IAC3B,gDAA4B,CAAA;IAC5B,4CAAwB,CAAA;IACxB,sDAAkC,CAAA;IAClC,4CAAwB,CAAA;AAC1B,CAAC,EARW,gBAAgB,KAAhB,gBAAgB,QAQ3B","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Result returned by {@link CapacitorAudioRecorderPlugin.getRecordingStatus}.\n *\n * @since 1.0.0\n */\nexport interface GetRecordingStatusResult {\n /**\n * The current recording status.\n *\n * @since 1.0.0\n */\n status: RecordingStatus;\n}\n\n/**\n * Options accepted by {@link CapacitorAudioRecorderPlugin.startRecording}.\n *\n * @since 1.0.0\n */\nexport interface StartRecordingOptions {\n /**\n * The audio session category options for recording. Only available on iOS.\n *\n * @since 1.0.0\n */\n audioSessionCategoryOptions?: AudioSessionCategoryOption[];\n\n /**\n * The audio session mode for recording. Only available on iOS.\n *\n * @since 1.0.0\n */\n audioSessionMode?: AudioSessionMode;\n\n /**\n * The audio bit rate in bytes per second.\n * Only available on Android and iOS.\n *\n * @since 1.0.0\n */\n bitRate?: number;\n\n /**\n * The audio sample rate in Hz.\n * Only available on Android and iOS.\n *\n * @since 1.0.0\n */\n sampleRate?: number;\n}\n\n/**\n * Result returned by {@link CapacitorAudioRecorderPlugin.stopRecording}.\n *\n * @since 1.0.0\n */\nexport interface StopRecordingResult {\n /**\n * The recorded audio as a Blob. Only available on Web.\n *\n * @since 1.0.0\n */\n blob?: Blob;\n\n /**\n * The duration of the recording in milliseconds.\n *\n * @since 1.0.0\n */\n duration?: number;\n\n /**\n * The URI pointing to the recorded file. Only available on Android and iOS.\n *\n * @since 1.0.0\n */\n uri?: string;\n}\n\n/**\n * Permission information returned by {@link CapacitorAudioRecorderPlugin.checkPermissions}\n * and {@link CapacitorAudioRecorderPlugin.requestPermissions}.\n *\n * @since 1.0.0\n */\nexport interface PermissionStatus {\n /**\n * The permission state for audio recording.\n *\n * @since 1.0.0\n */\n recordAudio: PermissionState;\n}\n\n/**\n * Event emitted when an error occurs during recording.\n *\n * @since 1.0.0\n */\nexport interface RecordingErrorEvent {\n /**\n * The error message.\n *\n * @since 1.0.0\n */\n message: string;\n}\n\n/**\n * Event emitted when a recording completes.\n *\n * @since 1.0.0\n */\nexport type RecordingStoppedEvent = StopRecordingResult\n\n/**\n * The recording status.\n *\n * @since 1.0.0\n */\nexport enum RecordingStatus {\n Inactive = 'INACTIVE',\n Recording = 'RECORDING',\n Paused = 'PAUSED',\n}\n\n/**\n * Audio session category options available on iOS.\n *\n * @since 1.0.0\n */\nexport enum AudioSessionCategoryOption {\n AllowAirPlay = 'ALLOW_AIR_PLAY',\n AllowBluetooth = 'ALLOW_BLUETOOTH',\n AllowBluetoothA2DP = 'ALLOW_BLUETOOTH_A2DP',\n DefaultToSpeaker = 'DEFAULT_TO_SPEAKER',\n DuckOthers = 'DUCK_OTHERS',\n InterruptSpokenAudioAndMixWithOthers = 'INTERRUPT_SPOKEN_AUDIO_AND_MIX_WITH_OTHERS',\n MixWithOthers = 'MIX_WITH_OTHERS',\n OverrideMutedMicrophoneInterruption = 'OVERRIDE_MUTED_MICROPHONE_INTERRUPTION',\n}\n\n/**\n * Audio session modes available on iOS.\n *\n * @since 1.0.0\n */\nexport enum AudioSessionMode {\n Default = 'DEFAULT',\n GameChat = 'GAME_CHAT',\n Measurement = 'MEASUREMENT',\n SpokenAudio = 'SPOKEN_AUDIO',\n VideoChat = 'VIDEO_CHAT',\n VideoRecording = 'VIDEO_RECORDING',\n VoiceChat = 'VOICE_CHAT',\n}\n\n/**\n * Platform permission states supported by Capacitor.\n *\n * @since 1.0.0\n */\nexport type PermissionState = 'prompt' | 'prompt-with-rationale' | 'granted' | 'denied';\n\n/**\n * Capacitor plugin contract for recording audio.\n *\n * @since 1.0.0\n */\nexport interface CapacitorAudioRecorderPlugin {\n /**\n * Start recording audio using the device microphone.\n *\n * @param options Recording configuration options.\n * @since 1.0.0\n */\n startRecording(options?: StartRecordingOptions): Promise<void>;\n\n /**\n * Pause the ongoing recording. Only available on Android (API 24+), iOS, and Web.\n *\n * @since 1.0.0\n */\n pauseRecording(): Promise<void>;\n\n /**\n * Resume a previously paused recording.\n *\n * @since 1.0.0\n */\n resumeRecording(): Promise<void>;\n\n /**\n * Stop the current recording and persist the recorded audio.\n *\n * @returns Recording metadata such as duration and URI/blob.\n * @since 1.0.0\n */\n stopRecording(): Promise<StopRecordingResult>;\n\n /**\n * Cancel the current recording and discard any captured audio.\n *\n * @since 1.0.0\n */\n cancelRecording(): Promise<void>;\n\n /**\n * Retrieve the current recording status.\n *\n * @since 1.0.0\n */\n getRecordingStatus(): Promise<GetRecordingStatusResult>;\n\n /**\n * Return the current permission state for accessing the microphone.\n *\n * @since 1.0.0\n */\n checkPermissions(): Promise<PermissionStatus>;\n\n /**\n * Request permission to access the microphone.\n *\n * @since 1.0.0\n */\n requestPermissions(): Promise<PermissionStatus>;\n\n /**\n * Listen for recording errors.\n *\n * @since 1.0.0\n */\n addListener(eventName: 'recordingError', listenerFunc: (event: RecordingErrorEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for pause events emitted when a recording is paused.\n *\n * @since 1.0.0\n */\n addListener(eventName: 'recordingPaused', listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for recording completion events.\n *\n * @since 1.0.0\n */\n addListener(\n eventName: 'recordingStopped',\n listenerFunc: (event: RecordingStoppedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Remove all registered listeners.\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { CapacitorAudioRecorderPlugin } from './definitions';
2
+ declare const CapacitorAudioRecorder: CapacitorAudioRecorderPlugin;
3
+ export * from './definitions';
4
+ export { CapacitorAudioRecorder };
@@ -0,0 +1,7 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+ const CapacitorAudioRecorder = registerPlugin('CapacitorAudioRecorder', {
3
+ web: () => import('./web').then((m) => new m.CapacitorAudioRecorderWeb()),
4
+ });
5
+ export * from './definitions';
6
+ export { CapacitorAudioRecorder };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,sBAAsB,GAAG,cAAc,CAA+B,wBAAwB,EAAE;IACpG,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,yBAAyB,EAAE,CAAC;CAC1E,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,sBAAsB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { CapacitorAudioRecorderPlugin } from './definitions';\n\nconst CapacitorAudioRecorder = registerPlugin<CapacitorAudioRecorderPlugin>('CapacitorAudioRecorder', {\n web: () => import('./web').then((m) => new m.CapacitorAudioRecorderWeb()),\n});\n\nexport * from './definitions';\nexport { CapacitorAudioRecorder };\n"]}
@@ -0,0 +1,34 @@
1
+ import { WebPlugin, type PluginListenerHandle } from '@capacitor/core';
2
+ import { CapacitorAudioRecorderPlugin, PermissionStatus, RecordingErrorEvent, RecordingStatus, RecordingStoppedEvent, StartRecordingOptions, StopRecordingResult } from './definitions';
3
+ export declare class CapacitorAudioRecorderWeb extends WebPlugin implements CapacitorAudioRecorderPlugin {
4
+ private mediaRecorder;
5
+ private mediaStream;
6
+ private recordedChunks;
7
+ private status;
8
+ private startTimestamp;
9
+ private pausedTimestamp;
10
+ private accumulatedPauseDuration;
11
+ private stopResolver;
12
+ private stopRejector;
13
+ startRecording(_options?: StartRecordingOptions): Promise<void>;
14
+ pauseRecording(): Promise<void>;
15
+ resumeRecording(): Promise<void>;
16
+ stopRecording(): Promise<StopRecordingResult>;
17
+ cancelRecording(): Promise<void>;
18
+ getRecordingStatus(): Promise<{
19
+ status: RecordingStatus;
20
+ }>;
21
+ checkPermissions(): Promise<PermissionStatus>;
22
+ requestPermissions(): Promise<PermissionStatus>;
23
+ addListener<T extends 'recordingError' | 'recordingPaused' | 'recordingStopped'>(eventName: T, listenerFunc: T extends 'recordingError' ? (event: RecordingErrorEvent) => void : T extends 'recordingStopped' ? (event: RecordingStoppedEvent) => void : () => void): Promise<PluginListenerHandle>;
24
+ removeAllListeners(): Promise<void>;
25
+ private supportsRecorderPause;
26
+ private pickMimeType;
27
+ private buildStopResult;
28
+ private resetStopHandlers;
29
+ private resetState;
30
+ private cleanupMediaStream;
31
+ private ensurePermission;
32
+ private getPermissionState;
33
+ private handleError;
34
+ }
@@ -0,0 +1,242 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ import { RecordingStatus, } from './definitions';
3
+ export class CapacitorAudioRecorderWeb extends WebPlugin {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.mediaRecorder = null;
7
+ this.mediaStream = null;
8
+ this.recordedChunks = [];
9
+ this.status = RecordingStatus.Inactive;
10
+ this.startTimestamp = null;
11
+ this.pausedTimestamp = null;
12
+ this.accumulatedPauseDuration = 0;
13
+ this.stopResolver = null;
14
+ this.stopRejector = null;
15
+ }
16
+ async startRecording(_options) {
17
+ var _a, _b;
18
+ if (this.status === RecordingStatus.Recording || this.status === RecordingStatus.Paused) {
19
+ throw this.unavailable('Recording already in progress.');
20
+ }
21
+ await this.ensurePermission(true);
22
+ try {
23
+ this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
24
+ }
25
+ catch (error) {
26
+ this.handleError(`Unable to acquire microphone: ${(_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error}`);
27
+ throw error;
28
+ }
29
+ const mimeType = this.pickMimeType();
30
+ try {
31
+ this.mediaRecorder = new MediaRecorder(this.mediaStream, mimeType ? { mimeType } : undefined);
32
+ }
33
+ catch (error) {
34
+ this.cleanupMediaStream();
35
+ this.handleError(`Unable to initialise MediaRecorder: ${(_b = error === null || error === void 0 ? void 0 : error.message) !== null && _b !== void 0 ? _b : error}`);
36
+ throw error;
37
+ }
38
+ this.recordedChunks = [];
39
+ this.startTimestamp = Date.now();
40
+ this.accumulatedPauseDuration = 0;
41
+ this.pausedTimestamp = null;
42
+ this.mediaRecorder.addEventListener('dataavailable', (event) => {
43
+ if (event.data.size > 0) {
44
+ this.recordedChunks.push(event.data);
45
+ }
46
+ });
47
+ this.mediaRecorder.addEventListener('stop', () => {
48
+ const result = this.buildStopResult();
49
+ if (this.stopResolver) {
50
+ this.stopResolver(result);
51
+ }
52
+ this.notifyListeners('recordingStopped', result);
53
+ this.resetStopHandlers();
54
+ this.resetState();
55
+ });
56
+ this.mediaRecorder.addEventListener('error', (event) => {
57
+ var _a, _b;
58
+ const message = (_b = (_a = event === null || event === void 0 ? void 0 : event.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : 'Recording error.';
59
+ this.handleError(message);
60
+ if (this.stopRejector) {
61
+ this.stopRejector(new Error(message));
62
+ }
63
+ this.resetStopHandlers();
64
+ this.resetState();
65
+ });
66
+ this.mediaRecorder.start();
67
+ this.status = RecordingStatus.Recording;
68
+ }
69
+ async pauseRecording() {
70
+ if (!this.mediaRecorder || this.status !== RecordingStatus.Recording) {
71
+ throw this.unavailable('No active recording to pause.');
72
+ }
73
+ if (this.supportsRecorderPause()) {
74
+ this.mediaRecorder.pause();
75
+ this.status = RecordingStatus.Paused;
76
+ this.pausedTimestamp = Date.now();
77
+ this.notifyListeners('recordingPaused', {});
78
+ }
79
+ else {
80
+ throw this.unavailable('Pausing recordings is not supported in this browser.');
81
+ }
82
+ }
83
+ async resumeRecording() {
84
+ if (!this.mediaRecorder || this.status !== RecordingStatus.Paused) {
85
+ throw this.unavailable('No paused recording to resume.');
86
+ }
87
+ if (this.supportsRecorderPause()) {
88
+ this.mediaRecorder.resume();
89
+ if (this.pausedTimestamp) {
90
+ this.accumulatedPauseDuration += Date.now() - this.pausedTimestamp;
91
+ }
92
+ this.pausedTimestamp = null;
93
+ this.status = RecordingStatus.Recording;
94
+ }
95
+ else {
96
+ throw this.unavailable('Resuming recordings is not supported in this browser.');
97
+ }
98
+ }
99
+ async stopRecording() {
100
+ var _a;
101
+ if (!this.mediaRecorder || this.status === RecordingStatus.Inactive) {
102
+ throw this.unavailable('No active recording to stop.');
103
+ }
104
+ if (this.status === RecordingStatus.Paused && this.pausedTimestamp) {
105
+ this.accumulatedPauseDuration += Date.now() - this.pausedTimestamp;
106
+ this.pausedTimestamp = null;
107
+ }
108
+ const stopPromise = new Promise((resolve, reject) => {
109
+ this.stopResolver = resolve;
110
+ this.stopRejector = reject;
111
+ });
112
+ try {
113
+ this.mediaRecorder.stop();
114
+ }
115
+ catch (error) {
116
+ this.resetStopHandlers();
117
+ this.resetState();
118
+ this.handleError(`Unable to stop recorder: ${(_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error}`);
119
+ throw error;
120
+ }
121
+ return stopPromise;
122
+ }
123
+ async cancelRecording() {
124
+ if (!this.mediaRecorder || this.status === RecordingStatus.Inactive) {
125
+ this.resetState();
126
+ return;
127
+ }
128
+ try {
129
+ this.mediaRecorder.stop();
130
+ }
131
+ catch (_a) {
132
+ // Ignored.
133
+ }
134
+ this.resetStopHandlers();
135
+ this.resetState();
136
+ }
137
+ async getRecordingStatus() {
138
+ return { status: this.status };
139
+ }
140
+ async checkPermissions() {
141
+ const state = await this.getPermissionState();
142
+ return { recordAudio: state };
143
+ }
144
+ async requestPermissions() {
145
+ const state = await this.ensurePermission(true);
146
+ return { recordAudio: state };
147
+ }
148
+ async addListener(eventName, listenerFunc) {
149
+ return super.addListener(eventName, listenerFunc);
150
+ }
151
+ async removeAllListeners() {
152
+ await super.removeAllListeners();
153
+ }
154
+ // Helpers
155
+ supportsRecorderPause() {
156
+ return !!this.mediaRecorder && typeof this.mediaRecorder.pause === 'function' && typeof this.mediaRecorder.resume === 'function';
157
+ }
158
+ pickMimeType() {
159
+ const preferred = ['audio/webm;codecs=opus', 'audio/ogg;codecs=opus', 'audio/mp4'];
160
+ for (const type of preferred) {
161
+ if (window.MediaRecorder && MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(type)) {
162
+ return type;
163
+ }
164
+ }
165
+ return undefined;
166
+ }
167
+ buildStopResult() {
168
+ const blob = this.recordedChunks.length > 0 ? new Blob(this.recordedChunks, { type: this.pickMimeType() || 'audio/webm' }) : undefined;
169
+ let duration;
170
+ if (this.startTimestamp) {
171
+ duration = Date.now() - this.startTimestamp - this.accumulatedPauseDuration;
172
+ }
173
+ return {
174
+ blob,
175
+ duration,
176
+ };
177
+ }
178
+ resetStopHandlers() {
179
+ this.stopResolver = null;
180
+ this.stopRejector = null;
181
+ }
182
+ resetState() {
183
+ this.status = RecordingStatus.Inactive;
184
+ this.startTimestamp = null;
185
+ this.pausedTimestamp = null;
186
+ this.accumulatedPauseDuration = 0;
187
+ this.recordedChunks = [];
188
+ this.cleanupMediaStream();
189
+ if (this.mediaRecorder) {
190
+ const recorder = this.mediaRecorder;
191
+ recorder.ondataavailable = null;
192
+ recorder.onstop = null;
193
+ recorder.onerror = null;
194
+ }
195
+ this.mediaRecorder = null;
196
+ }
197
+ cleanupMediaStream() {
198
+ if (this.mediaStream) {
199
+ this.mediaStream.getTracks().forEach((track) => track.stop());
200
+ }
201
+ this.mediaStream = null;
202
+ }
203
+ async ensurePermission(request) {
204
+ const currentState = await this.getPermissionState();
205
+ if (currentState === 'granted' || !request) {
206
+ return currentState;
207
+ }
208
+ try {
209
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
210
+ stream.getTracks().forEach((track) => track.stop());
211
+ return 'granted';
212
+ }
213
+ catch (error) {
214
+ return 'denied';
215
+ }
216
+ }
217
+ async getPermissionState() {
218
+ var _a;
219
+ if (!((_a = navigator.permissions) === null || _a === void 0 ? void 0 : _a.query)) {
220
+ return 'prompt';
221
+ }
222
+ try {
223
+ const result = await navigator.permissions.query({ name: 'microphone' });
224
+ switch (result.state) {
225
+ case 'granted':
226
+ return 'granted';
227
+ case 'denied':
228
+ return 'denied';
229
+ default:
230
+ return 'prompt';
231
+ }
232
+ }
233
+ catch (_b) {
234
+ return 'prompt';
235
+ }
236
+ }
237
+ handleError(message) {
238
+ this.status = RecordingStatus.Inactive;
239
+ this.notifyListeners('recordingError', { message });
240
+ }
241
+ }
242
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin, type PluginListenerHandle } from '@capacitor/core';\n\nimport {\n CapacitorAudioRecorderPlugin,\n PermissionState,\n PermissionStatus,\n RecordingErrorEvent,\n RecordingStatus,\n RecordingStoppedEvent,\n StartRecordingOptions,\n StopRecordingResult,\n} from './definitions';\n\nexport class CapacitorAudioRecorderWeb extends WebPlugin implements CapacitorAudioRecorderPlugin {\n private mediaRecorder: MediaRecorder | null = null;\n private mediaStream: MediaStream | null = null;\n private recordedChunks: BlobPart[] = [];\n private status: RecordingStatus = RecordingStatus.Inactive;\n private startTimestamp: number | null = null;\n private pausedTimestamp: number | null = null;\n private accumulatedPauseDuration = 0;\n private stopResolver: ((result: StopRecordingResult) => void) | null = null;\n private stopRejector: ((reason?: any) => void) | null = null;\n\n async startRecording(_options?: StartRecordingOptions): Promise<void> {\n if (this.status === RecordingStatus.Recording || this.status === RecordingStatus.Paused) {\n throw this.unavailable('Recording already in progress.');\n }\n\n await this.ensurePermission(true);\n\n try {\n this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (error) {\n this.handleError(`Unable to acquire microphone: ${(error as Error)?.message ?? error}`);\n throw error;\n }\n\n const mimeType = this.pickMimeType();\n try {\n this.mediaRecorder = new MediaRecorder(this.mediaStream, mimeType ? { mimeType } : undefined);\n } catch (error) {\n this.cleanupMediaStream();\n this.handleError(`Unable to initialise MediaRecorder: ${(error as Error)?.message ?? error}`);\n throw error;\n }\n\n this.recordedChunks = [];\n this.startTimestamp = Date.now();\n this.accumulatedPauseDuration = 0;\n this.pausedTimestamp = null;\n\n this.mediaRecorder.addEventListener('dataavailable', (event: BlobEvent) => {\n if (event.data.size > 0) {\n this.recordedChunks.push(event.data);\n }\n });\n\n this.mediaRecorder.addEventListener('stop', () => {\n const result = this.buildStopResult();\n if (this.stopResolver) {\n this.stopResolver(result);\n }\n this.notifyListeners('recordingStopped', result as RecordingStoppedEvent);\n this.resetStopHandlers();\n this.resetState();\n });\n\n this.mediaRecorder.addEventListener('error', (event: Event) => {\n const message = (event as any)?.error?.message ?? 'Recording error.';\n this.handleError(message);\n if (this.stopRejector) {\n this.stopRejector(new Error(message));\n }\n this.resetStopHandlers();\n this.resetState();\n });\n\n this.mediaRecorder.start();\n this.status = RecordingStatus.Recording;\n }\n\n async pauseRecording(): Promise<void> {\n if (!this.mediaRecorder || this.status !== RecordingStatus.Recording) {\n throw this.unavailable('No active recording to pause.');\n }\n if (this.supportsRecorderPause()) {\n this.mediaRecorder.pause();\n this.status = RecordingStatus.Paused;\n this.pausedTimestamp = Date.now();\n this.notifyListeners('recordingPaused', {});\n } else {\n throw this.unavailable('Pausing recordings is not supported in this browser.');\n }\n }\n\n async resumeRecording(): Promise<void> {\n if (!this.mediaRecorder || this.status !== RecordingStatus.Paused) {\n throw this.unavailable('No paused recording to resume.');\n }\n if (this.supportsRecorderPause()) {\n this.mediaRecorder.resume();\n if (this.pausedTimestamp) {\n this.accumulatedPauseDuration += Date.now() - this.pausedTimestamp;\n }\n this.pausedTimestamp = null;\n this.status = RecordingStatus.Recording;\n } else {\n throw this.unavailable('Resuming recordings is not supported in this browser.');\n }\n }\n\n async stopRecording(): Promise<StopRecordingResult> {\n if (!this.mediaRecorder || this.status === RecordingStatus.Inactive) {\n throw this.unavailable('No active recording to stop.');\n }\n\n if (this.status === RecordingStatus.Paused && this.pausedTimestamp) {\n this.accumulatedPauseDuration += Date.now() - this.pausedTimestamp;\n this.pausedTimestamp = null;\n }\n\n const stopPromise = new Promise<StopRecordingResult>((resolve, reject) => {\n this.stopResolver = resolve;\n this.stopRejector = reject;\n });\n\n try {\n this.mediaRecorder.stop();\n } catch (error) {\n this.resetStopHandlers();\n this.resetState();\n this.handleError(`Unable to stop recorder: ${(error as Error)?.message ?? error}`);\n throw error;\n }\n\n return stopPromise;\n }\n\n async cancelRecording(): Promise<void> {\n if (!this.mediaRecorder || this.status === RecordingStatus.Inactive) {\n this.resetState();\n return;\n }\n\n try {\n this.mediaRecorder.stop();\n } catch {\n // Ignored.\n }\n\n this.resetStopHandlers();\n this.resetState();\n }\n\n async getRecordingStatus(): Promise<{ status: RecordingStatus }> {\n return { status: this.status };\n }\n\n async checkPermissions(): Promise<PermissionStatus> {\n const state = await this.getPermissionState();\n return { recordAudio: state };\n }\n\n async requestPermissions(): Promise<PermissionStatus> {\n const state = await this.ensurePermission(true);\n return { recordAudio: state };\n }\n\n async addListener<T extends 'recordingError' | 'recordingPaused' | 'recordingStopped'>(\n eventName: T,\n listenerFunc: T extends 'recordingError'\n ? (event: RecordingErrorEvent) => void\n : T extends 'recordingStopped'\n ? (event: RecordingStoppedEvent) => void\n : () => void,\n ): Promise<PluginListenerHandle> {\n return super.addListener(eventName, listenerFunc as any);\n }\n\n async removeAllListeners(): Promise<void> {\n await super.removeAllListeners();\n }\n\n // Helpers\n\n private supportsRecorderPause(): boolean {\n return !!this.mediaRecorder && typeof this.mediaRecorder.pause === 'function' && typeof this.mediaRecorder.resume === 'function';\n }\n\n private pickMimeType(): string | undefined {\n const preferred = ['audio/webm;codecs=opus', 'audio/ogg;codecs=opus', 'audio/mp4'];\n for (const type of preferred) {\n if ((window as any).MediaRecorder && MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(type)) {\n return type;\n }\n }\n return undefined;\n }\n\n private buildStopResult(): StopRecordingResult {\n const blob = this.recordedChunks.length > 0 ? new Blob(this.recordedChunks, { type: this.pickMimeType() || 'audio/webm' }) : undefined;\n let duration: number | undefined;\n if (this.startTimestamp) {\n duration = Date.now() - this.startTimestamp - this.accumulatedPauseDuration;\n }\n return {\n blob,\n duration,\n };\n }\n\n private resetStopHandlers(): void {\n this.stopResolver = null;\n this.stopRejector = null;\n }\n\n private resetState(): void {\n this.status = RecordingStatus.Inactive;\n this.startTimestamp = null;\n this.pausedTimestamp = null;\n this.accumulatedPauseDuration = 0;\n this.recordedChunks = [];\n this.cleanupMediaStream();\n if (this.mediaRecorder) {\n const recorder = this.mediaRecorder;\n recorder.ondataavailable = null;\n recorder.onstop = null;\n recorder.onerror = null;\n }\n this.mediaRecorder = null;\n }\n\n private cleanupMediaStream(): void {\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => track.stop());\n }\n this.mediaStream = null;\n }\n\n private async ensurePermission(request: boolean): Promise<PermissionState> {\n const currentState = await this.getPermissionState();\n if (currentState === 'granted' || !request) {\n return currentState;\n }\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach((track) => track.stop());\n return 'granted';\n } catch (error) {\n return 'denied';\n }\n }\n\n private async getPermissionState(): Promise<PermissionState> {\n if (!navigator.permissions?.query) {\n return 'prompt';\n }\n\n try {\n const result = await navigator.permissions.query({ name: 'microphone' as PermissionName });\n switch (result.state) {\n case 'granted':\n return 'granted';\n case 'denied':\n return 'denied';\n default:\n return 'prompt';\n }\n } catch {\n return 'prompt';\n }\n }\n\n private handleError(message: string): void {\n this.status = RecordingStatus.Inactive;\n this.notifyListeners('recordingError', { message });\n }\n}\n"]}