capacitor-plugin-recorder 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorPluginRecorder.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +395 -0
- package/android/build.gradle +58 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/capacitor/recorderplayer/RecorderPlayer.java +524 -0
- package/android/src/main/java/com/capacitor/recorderplayer/RecorderPlayerPlugin.java +302 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +562 -0
- package/dist/esm/definitions.d.ts +125 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +30 -0
- package/dist/esm/web.js +279 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +293 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +296 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/RecorderPlayerPlugin/RecorderPlayer.swift +318 -0
- package/ios/Sources/RecorderPlayerPlugin/RecorderPlayerPlugin.swift +249 -0
- package/ios/Tests/RecorderPlayerPluginTests/RecorderPlayerTests.swift +15 -0
- package/package.json +80 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { registerPlugin } from '@capacitor/core';
|
|
2
|
+
const RecorderPlayer = registerPlugin('RecorderPlayer', {
|
|
3
|
+
web: () => import('./web').then((m) => new m.RecorderPlayerWeb()),
|
|
4
|
+
});
|
|
5
|
+
export * from './definitions';
|
|
6
|
+
export { RecorderPlayer };
|
|
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,cAAc,GAAG,cAAc,CAAuB,gBAAgB,EAAE;IAC5E,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,CAAC;CAClE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { RecorderPlayerPlugin } from './definitions';\n\nconst RecorderPlayer = registerPlugin<RecorderPlayerPlugin>('RecorderPlayer', {\n web: () => import('./web').then((m) => new m.RecorderPlayerWeb()),\n});\n\nexport * from './definitions';\nexport { RecorderPlayer };\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
import type { RecorderPlayerPlugin, StartRecordOptions, StopRecordResult, PlayOptions, PreparePlayResult, SeekOptions, RecordingStatusChangeEvent, PlaybackStatusChangeEvent, PermissionStatus, PlayerOptions } from './definitions';
|
|
3
|
+
export declare class RecorderPlayerWeb extends WebPlugin implements RecorderPlayerPlugin {
|
|
4
|
+
private mediaRecorder;
|
|
5
|
+
private audioChunks;
|
|
6
|
+
private recordingStartTime;
|
|
7
|
+
private pausedDuration;
|
|
8
|
+
private recordingStatus;
|
|
9
|
+
private players;
|
|
10
|
+
private playerIdCounter;
|
|
11
|
+
private generatePlayerId;
|
|
12
|
+
requestPermission(): Promise<PermissionStatus>;
|
|
13
|
+
checkPermission(): Promise<PermissionStatus>;
|
|
14
|
+
startRecord(options?: StartRecordOptions): Promise<void>;
|
|
15
|
+
stopRecord(): Promise<StopRecordResult>;
|
|
16
|
+
pauseRecord(): Promise<void>;
|
|
17
|
+
resumeRecord(): Promise<void>;
|
|
18
|
+
preparePlay(options: PlayOptions): Promise<PreparePlayResult>;
|
|
19
|
+
play(options: PlayerOptions): Promise<void>;
|
|
20
|
+
pausePlay(options: PlayerOptions): Promise<void>;
|
|
21
|
+
resumePlay(options: PlayerOptions): Promise<void>;
|
|
22
|
+
stopPlay(options: PlayerOptions): Promise<void>;
|
|
23
|
+
seekTo(options: SeekOptions): Promise<void>;
|
|
24
|
+
getPlaybackStatus(options: PlayerOptions): Promise<PlaybackStatusChangeEvent>;
|
|
25
|
+
getRecordingStatus(): Promise<RecordingStatusChangeEvent>;
|
|
26
|
+
destroyPlayer(options: PlayerOptions): Promise<void>;
|
|
27
|
+
private setupAudioEventListeners;
|
|
28
|
+
private notifyRecordingStatusChange;
|
|
29
|
+
private notifyPlaybackStatusChange;
|
|
30
|
+
}
|
package/dist/esm/web.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
export class RecorderPlayerWeb extends WebPlugin {
|
|
3
|
+
constructor() {
|
|
4
|
+
super(...arguments);
|
|
5
|
+
this.mediaRecorder = null;
|
|
6
|
+
this.audioChunks = [];
|
|
7
|
+
this.recordingStartTime = 0;
|
|
8
|
+
this.pausedDuration = 0;
|
|
9
|
+
this.recordingStatus = 'stopped';
|
|
10
|
+
// Multiple players support
|
|
11
|
+
this.players = new Map();
|
|
12
|
+
this.playerIdCounter = 0;
|
|
13
|
+
}
|
|
14
|
+
generatePlayerId() {
|
|
15
|
+
return `player-${++this.playerIdCounter}-${Date.now()}`;
|
|
16
|
+
}
|
|
17
|
+
async requestPermission() {
|
|
18
|
+
try {
|
|
19
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
20
|
+
// Stop the stream immediately, we just needed permission
|
|
21
|
+
stream.getTracks().forEach(track => track.stop());
|
|
22
|
+
return { microphone: 'granted' };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// Check if it's a permission denied error
|
|
26
|
+
if (error.name === 'NotAllowedError') {
|
|
27
|
+
return { microphone: 'denied' };
|
|
28
|
+
}
|
|
29
|
+
return { microphone: 'denied' };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async checkPermission() {
|
|
33
|
+
try {
|
|
34
|
+
const result = await navigator.permissions.query({ name: 'microphone' });
|
|
35
|
+
if (result.state === 'granted') {
|
|
36
|
+
return { microphone: 'granted' };
|
|
37
|
+
}
|
|
38
|
+
else if (result.state === 'denied') {
|
|
39
|
+
return { microphone: 'denied' };
|
|
40
|
+
}
|
|
41
|
+
return { microphone: 'prompt' };
|
|
42
|
+
}
|
|
43
|
+
catch (_a) {
|
|
44
|
+
// Fallback for browsers that don't support permissions API for microphone
|
|
45
|
+
return { microphone: 'prompt' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async startRecord(options) {
|
|
49
|
+
console.log('Starting recording with options:', options);
|
|
50
|
+
if (this.recordingStatus === 'recording') {
|
|
51
|
+
throw new Error('Recording already in progress');
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
55
|
+
this.audioChunks = [];
|
|
56
|
+
this.pausedDuration = 0;
|
|
57
|
+
this.mediaRecorder = new MediaRecorder(stream);
|
|
58
|
+
this.mediaRecorder.ondataavailable = (event) => {
|
|
59
|
+
if (event.data.size > 0) {
|
|
60
|
+
this.audioChunks.push(event.data);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
this.mediaRecorder.start(100);
|
|
64
|
+
this.recordingStartTime = Date.now();
|
|
65
|
+
this.recordingStatus = 'recording';
|
|
66
|
+
this.notifyRecordingStatusChange();
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new Error('Failed to start recording: ' + error.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async stopRecord() {
|
|
73
|
+
if (!this.mediaRecorder || this.recordingStatus === 'stopped') {
|
|
74
|
+
throw new Error('No recording in progress');
|
|
75
|
+
}
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
if (!this.mediaRecorder) {
|
|
78
|
+
reject(new Error('No recording in progress'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.mediaRecorder.onstop = () => {
|
|
82
|
+
var _a;
|
|
83
|
+
const duration = Date.now() - this.recordingStartTime - this.pausedDuration;
|
|
84
|
+
const blob = new Blob(this.audioChunks, { type: 'audio/webm' });
|
|
85
|
+
const path = URL.createObjectURL(blob);
|
|
86
|
+
(_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.stream.getTracks().forEach(track => track.stop());
|
|
87
|
+
this.recordingStatus = 'stopped';
|
|
88
|
+
this.notifyRecordingStatusChange();
|
|
89
|
+
resolve({ path, duration });
|
|
90
|
+
};
|
|
91
|
+
this.mediaRecorder.stop();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async pauseRecord() {
|
|
95
|
+
if (!this.mediaRecorder || this.recordingStatus !== 'recording') {
|
|
96
|
+
throw new Error('No active recording to pause');
|
|
97
|
+
}
|
|
98
|
+
this.mediaRecorder.pause();
|
|
99
|
+
this.pausedDuration = Date.now() - this.recordingStartTime;
|
|
100
|
+
this.recordingStatus = 'paused';
|
|
101
|
+
this.notifyRecordingStatusChange();
|
|
102
|
+
}
|
|
103
|
+
async resumeRecord() {
|
|
104
|
+
if (!this.mediaRecorder || this.recordingStatus !== 'paused') {
|
|
105
|
+
throw new Error('No paused recording to resume');
|
|
106
|
+
}
|
|
107
|
+
this.mediaRecorder.resume();
|
|
108
|
+
this.recordingStartTime = Date.now() - this.pausedDuration;
|
|
109
|
+
this.recordingStatus = 'recording';
|
|
110
|
+
this.notifyRecordingStatusChange();
|
|
111
|
+
}
|
|
112
|
+
async preparePlay(options) {
|
|
113
|
+
if (!options.path) {
|
|
114
|
+
throw new Error('Audio path is required');
|
|
115
|
+
}
|
|
116
|
+
const playerId = this.generatePlayerId();
|
|
117
|
+
const audioElement = new Audio(options.path);
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
audioElement.onloadedmetadata = () => {
|
|
120
|
+
var _a;
|
|
121
|
+
const duration = Math.round(((_a = audioElement.duration) !== null && _a !== void 0 ? _a : 0) * 1000);
|
|
122
|
+
const playerState = {
|
|
123
|
+
audioElement,
|
|
124
|
+
status: 'stopped',
|
|
125
|
+
path: options.path,
|
|
126
|
+
};
|
|
127
|
+
this.players.set(playerId, playerState);
|
|
128
|
+
this.setupAudioEventListeners(playerId);
|
|
129
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
130
|
+
resolve({ playerId, duration });
|
|
131
|
+
};
|
|
132
|
+
audioElement.onerror = () => {
|
|
133
|
+
reject(new Error('Failed to load audio file'));
|
|
134
|
+
};
|
|
135
|
+
audioElement.load();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async play(options) {
|
|
139
|
+
const player = this.players.get(options.playerId);
|
|
140
|
+
if (!player) {
|
|
141
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await player.audioElement.play();
|
|
145
|
+
player.status = 'playing';
|
|
146
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
throw new Error('Failed to play audio: ' + error.message);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async pausePlay(options) {
|
|
153
|
+
const player = this.players.get(options.playerId);
|
|
154
|
+
if (!player) {
|
|
155
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
156
|
+
}
|
|
157
|
+
if (player.status !== 'playing') {
|
|
158
|
+
throw new Error('No audio playing to pause');
|
|
159
|
+
}
|
|
160
|
+
player.audioElement.pause();
|
|
161
|
+
player.status = 'paused';
|
|
162
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
163
|
+
}
|
|
164
|
+
async resumePlay(options) {
|
|
165
|
+
const player = this.players.get(options.playerId);
|
|
166
|
+
if (!player) {
|
|
167
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
168
|
+
}
|
|
169
|
+
if (player.status !== 'paused') {
|
|
170
|
+
throw new Error('No paused audio to resume');
|
|
171
|
+
}
|
|
172
|
+
await player.audioElement.play();
|
|
173
|
+
player.status = 'playing';
|
|
174
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
175
|
+
}
|
|
176
|
+
async stopPlay(options) {
|
|
177
|
+
const player = this.players.get(options.playerId);
|
|
178
|
+
if (!player) {
|
|
179
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
180
|
+
}
|
|
181
|
+
player.audioElement.pause();
|
|
182
|
+
player.audioElement.currentTime = 0;
|
|
183
|
+
player.status = 'stopped';
|
|
184
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
185
|
+
}
|
|
186
|
+
async seekTo(options) {
|
|
187
|
+
const player = this.players.get(options.playerId);
|
|
188
|
+
if (!player) {
|
|
189
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
190
|
+
}
|
|
191
|
+
const positionInSeconds = options.position / 1000;
|
|
192
|
+
if (positionInSeconds < 0 || positionInSeconds > player.audioElement.duration) {
|
|
193
|
+
throw new Error('Invalid seek position');
|
|
194
|
+
}
|
|
195
|
+
player.audioElement.currentTime = positionInSeconds;
|
|
196
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
197
|
+
}
|
|
198
|
+
async getPlaybackStatus(options) {
|
|
199
|
+
const player = this.players.get(options.playerId);
|
|
200
|
+
if (!player) {
|
|
201
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
playerId: options.playerId,
|
|
205
|
+
status: player.status,
|
|
206
|
+
currentPosition: Math.round(player.audioElement.currentTime * 1000),
|
|
207
|
+
duration: Math.round(player.audioElement.duration * 1000),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async getRecordingStatus() {
|
|
211
|
+
let duration = 0;
|
|
212
|
+
if (this.recordingStatus === 'recording') {
|
|
213
|
+
duration = Date.now() - this.recordingStartTime;
|
|
214
|
+
}
|
|
215
|
+
else if (this.recordingStatus === 'paused') {
|
|
216
|
+
duration = this.pausedDuration;
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
status: this.recordingStatus,
|
|
220
|
+
duration,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async destroyPlayer(options) {
|
|
224
|
+
const player = this.players.get(options.playerId);
|
|
225
|
+
if (!player) {
|
|
226
|
+
return; // Already destroyed or never existed
|
|
227
|
+
}
|
|
228
|
+
player.audioElement.pause();
|
|
229
|
+
player.audioElement.src = '';
|
|
230
|
+
player.audioElement.load();
|
|
231
|
+
this.players.delete(options.playerId);
|
|
232
|
+
}
|
|
233
|
+
setupAudioEventListeners(playerId) {
|
|
234
|
+
const player = this.players.get(playerId);
|
|
235
|
+
if (!player)
|
|
236
|
+
return;
|
|
237
|
+
player.audioElement.onplay = () => {
|
|
238
|
+
const p = this.players.get(playerId);
|
|
239
|
+
if (p) {
|
|
240
|
+
p.status = 'playing';
|
|
241
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
player.audioElement.onpause = () => {
|
|
245
|
+
const p = this.players.get(playerId);
|
|
246
|
+
// Only set to paused if currently playing (not stopped or ended)
|
|
247
|
+
if (p && p.status === 'playing') {
|
|
248
|
+
p.status = 'paused';
|
|
249
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
player.audioElement.onended = () => {
|
|
253
|
+
const p = this.players.get(playerId);
|
|
254
|
+
if (p) {
|
|
255
|
+
p.status = 'ended';
|
|
256
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
player.audioElement.ontimeupdate = () => {
|
|
260
|
+
const p = this.players.get(playerId);
|
|
261
|
+
if (p && p.status === 'playing') {
|
|
262
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
notifyRecordingStatusChange() {
|
|
267
|
+
this.getRecordingStatus().then(status => {
|
|
268
|
+
this.notifyListeners('recordingStatusChange', status);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
notifyPlaybackStatusChange(playerId) {
|
|
272
|
+
this.getPlaybackStatus({ playerId }).then(status => {
|
|
273
|
+
this.notifyListeners('playbackStatusChange', status);
|
|
274
|
+
}).catch(() => {
|
|
275
|
+
// Player may have been destroyed
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# 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 } from '@capacitor/core';\n\nimport type {\n RecorderPlayerPlugin,\n StartRecordOptions,\n StopRecordResult,\n PlayOptions,\n PreparePlayResult,\n SeekOptions,\n RecordingStatusChangeEvent,\n PlaybackStatusChangeEvent,\n RecordingStatus,\n PlaybackStatus,\n PermissionStatus,\n PlayerOptions,\n} from './definitions';\n\ninterface PlayerState {\n audioElement: HTMLAudioElement;\n status: PlaybackStatus;\n path: string;\n}\n\nexport class RecorderPlayerWeb extends WebPlugin implements RecorderPlayerPlugin {\n private mediaRecorder: MediaRecorder | null = null;\n private audioChunks: Blob[] = [];\n private recordingStartTime = 0;\n private pausedDuration = 0;\n private recordingStatus: RecordingStatus = 'stopped';\n\n // Multiple players support\n private players: Map<string, PlayerState> = new Map();\n private playerIdCounter = 0;\n\n private generatePlayerId(): string {\n return `player-${++this.playerIdCounter}-${Date.now()}`;\n }\n\n async requestPermission(): Promise<PermissionStatus> {\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n // Stop the stream immediately, we just needed permission\n stream.getTracks().forEach(track => track.stop());\n return { microphone: 'granted' };\n } catch (error) {\n // Check if it's a permission denied error\n if ((error as Error).name === 'NotAllowedError') {\n return { microphone: 'denied' };\n }\n return { microphone: 'denied' };\n }\n }\n\n async checkPermission(): Promise<PermissionStatus> {\n try {\n const result = await navigator.permissions.query({ name: 'microphone' as PermissionName });\n if (result.state === 'granted') {\n return { microphone: 'granted' };\n } else if (result.state === 'denied') {\n return { microphone: 'denied' };\n }\n return { microphone: 'prompt' };\n } catch {\n // Fallback for browsers that don't support permissions API for microphone\n return { microphone: 'prompt' };\n }\n }\n\n async startRecord(options?: StartRecordOptions): Promise<void> {\n console.log('Starting recording with options:', options);\n\n if (this.recordingStatus === 'recording') {\n throw new Error('Recording already in progress');\n }\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n this.audioChunks = [];\n this.pausedDuration = 0;\n\n this.mediaRecorder = new MediaRecorder(stream);\n\n this.mediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.audioChunks.push(event.data);\n }\n };\n\n this.mediaRecorder.start(100);\n this.recordingStartTime = Date.now();\n this.recordingStatus = 'recording';\n\n this.notifyRecordingStatusChange();\n } catch (error) {\n throw new Error('Failed to start recording: ' + (error as Error).message);\n }\n }\n\n async stopRecord(): Promise<StopRecordResult> {\n if (!this.mediaRecorder || this.recordingStatus === 'stopped') {\n throw new Error('No recording in progress');\n }\n\n return new Promise((resolve, reject) => {\n if (!this.mediaRecorder) {\n reject(new Error('No recording in progress'));\n return;\n }\n\n this.mediaRecorder.onstop = () => {\n const duration = Date.now() - this.recordingStartTime - this.pausedDuration;\n const blob = new Blob(this.audioChunks, { type: 'audio/webm' });\n\n const path = URL.createObjectURL(blob);\n\n this.mediaRecorder?.stream.getTracks().forEach(track => track.stop());\n\n this.recordingStatus = 'stopped';\n this.notifyRecordingStatusChange();\n\n resolve({ path, duration });\n };\n\n this.mediaRecorder.stop();\n });\n }\n\n async pauseRecord(): Promise<void> {\n if (!this.mediaRecorder || this.recordingStatus !== 'recording') {\n throw new Error('No active recording to pause');\n }\n\n this.mediaRecorder.pause();\n this.pausedDuration = Date.now() - this.recordingStartTime;\n this.recordingStatus = 'paused';\n this.notifyRecordingStatusChange();\n }\n\n async resumeRecord(): Promise<void> {\n if (!this.mediaRecorder || this.recordingStatus !== 'paused') {\n throw new Error('No paused recording to resume');\n }\n\n this.mediaRecorder.resume();\n this.recordingStartTime = Date.now() - this.pausedDuration;\n this.recordingStatus = 'recording';\n this.notifyRecordingStatusChange();\n }\n\n async preparePlay(options: PlayOptions): Promise<PreparePlayResult> {\n if (!options.path) {\n throw new Error('Audio path is required');\n }\n\n const playerId = this.generatePlayerId();\n const audioElement = new Audio(options.path);\n\n return new Promise((resolve, reject) => {\n audioElement.onloadedmetadata = () => {\n const duration = Math.round((audioElement.duration ?? 0) * 1000);\n\n const playerState: PlayerState = {\n audioElement,\n status: 'stopped',\n path: options.path,\n };\n\n this.players.set(playerId, playerState);\n this.setupAudioEventListeners(playerId);\n this.notifyPlaybackStatusChange(playerId);\n\n resolve({ playerId, duration });\n };\n\n audioElement.onerror = () => {\n reject(new Error('Failed to load audio file'));\n };\n\n audioElement.load();\n });\n }\n\n async play(options: PlayerOptions): Promise<void> {\n const player = this.players.get(options.playerId);\n if (!player) {\n throw new Error(`No player found with ID: ${options.playerId}`);\n }\n\n try {\n await player.audioElement.play();\n player.status = 'playing';\n this.notifyPlaybackStatusChange(options.playerId);\n } catch (error) {\n throw new Error('Failed to play audio: ' + (error as Error).message);\n }\n }\n\n async pausePlay(options: PlayerOptions): Promise<void> {\n const player = this.players.get(options.playerId);\n if (!player) {\n throw new Error(`No player found with ID: ${options.playerId}`);\n }\n\n if (player.status !== 'playing') {\n throw new Error('No audio playing to pause');\n }\n\n player.audioElement.pause();\n player.status = 'paused';\n this.notifyPlaybackStatusChange(options.playerId);\n }\n\n async resumePlay(options: PlayerOptions): Promise<void> {\n const player = this.players.get(options.playerId);\n if (!player) {\n throw new Error(`No player found with ID: ${options.playerId}`);\n }\n\n if (player.status !== 'paused') {\n throw new Error('No paused audio to resume');\n }\n\n await player.audioElement.play();\n player.status = 'playing';\n this.notifyPlaybackStatusChange(options.playerId);\n }\n\n async stopPlay(options: PlayerOptions): Promise<void> {\n const player = this.players.get(options.playerId);\n if (!player) {\n throw new Error(`No player found with ID: ${options.playerId}`);\n }\n\n player.audioElement.pause();\n player.audioElement.currentTime = 0;\n player.status = 'stopped';\n this.notifyPlaybackStatusChange(options.playerId);\n }\n\n async seekTo(options: SeekOptions): Promise<void> {\n const player = this.players.get(options.playerId);\n if (!player) {\n throw new Error(`No player found with ID: ${options.playerId}`);\n }\n\n const positionInSeconds = options.position / 1000;\n if (positionInSeconds < 0 || positionInSeconds > player.audioElement.duration) {\n throw new Error('Invalid seek position');\n }\n\n player.audioElement.currentTime = positionInSeconds;\n this.notifyPlaybackStatusChange(options.playerId);\n }\n\n async getPlaybackStatus(options: PlayerOptions): Promise<PlaybackStatusChangeEvent> {\n const player = this.players.get(options.playerId);\n if (!player) {\n throw new Error(`No player found with ID: ${options.playerId}`);\n }\n\n return {\n playerId: options.playerId,\n status: player.status,\n currentPosition: Math.round(player.audioElement.currentTime * 1000),\n duration: Math.round(player.audioElement.duration * 1000),\n };\n }\n\n async getRecordingStatus(): Promise<RecordingStatusChangeEvent> {\n let duration = 0;\n if (this.recordingStatus === 'recording') {\n duration = Date.now() - this.recordingStartTime;\n } else if (this.recordingStatus === 'paused') {\n duration = this.pausedDuration;\n }\n\n return {\n status: this.recordingStatus,\n duration,\n };\n }\n\n async destroyPlayer(options: PlayerOptions): Promise<void> {\n const player = this.players.get(options.playerId);\n if (!player) {\n return; // Already destroyed or never existed\n }\n\n player.audioElement.pause();\n player.audioElement.src = '';\n player.audioElement.load();\n this.players.delete(options.playerId);\n }\n\n private setupAudioEventListeners(playerId: string): void {\n const player = this.players.get(playerId);\n if (!player) return;\n\n player.audioElement.onplay = () => {\n const p = this.players.get(playerId);\n if (p) {\n p.status = 'playing';\n this.notifyPlaybackStatusChange(playerId);\n }\n };\n\n player.audioElement.onpause = () => {\n const p = this.players.get(playerId);\n // Only set to paused if currently playing (not stopped or ended)\n if (p && p.status === 'playing') {\n p.status = 'paused';\n this.notifyPlaybackStatusChange(playerId);\n }\n };\n\n player.audioElement.onended = () => {\n const p = this.players.get(playerId);\n if (p) {\n p.status = 'ended';\n this.notifyPlaybackStatusChange(playerId);\n }\n };\n\n player.audioElement.ontimeupdate = () => {\n const p = this.players.get(playerId);\n if (p && p.status === 'playing') {\n this.notifyPlaybackStatusChange(playerId);\n }\n };\n }\n\n private notifyRecordingStatusChange(): void {\n this.getRecordingStatus().then(status => {\n this.notifyListeners('recordingStatusChange', status);\n });\n }\n\n private notifyPlaybackStatusChange(playerId: string): void {\n this.getPlaybackStatus({ playerId }).then(status => {\n this.notifyListeners('playbackStatusChange', status);\n }).catch(() => {\n // Player may have been destroyed\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
const RecorderPlayer = core.registerPlugin('RecorderPlayer', {
|
|
6
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.RecorderPlayerWeb()),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
class RecorderPlayerWeb extends core.WebPlugin {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.mediaRecorder = null;
|
|
13
|
+
this.audioChunks = [];
|
|
14
|
+
this.recordingStartTime = 0;
|
|
15
|
+
this.pausedDuration = 0;
|
|
16
|
+
this.recordingStatus = 'stopped';
|
|
17
|
+
// Multiple players support
|
|
18
|
+
this.players = new Map();
|
|
19
|
+
this.playerIdCounter = 0;
|
|
20
|
+
}
|
|
21
|
+
generatePlayerId() {
|
|
22
|
+
return `player-${++this.playerIdCounter}-${Date.now()}`;
|
|
23
|
+
}
|
|
24
|
+
async requestPermission() {
|
|
25
|
+
try {
|
|
26
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
27
|
+
// Stop the stream immediately, we just needed permission
|
|
28
|
+
stream.getTracks().forEach(track => track.stop());
|
|
29
|
+
return { microphone: 'granted' };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// Check if it's a permission denied error
|
|
33
|
+
if (error.name === 'NotAllowedError') {
|
|
34
|
+
return { microphone: 'denied' };
|
|
35
|
+
}
|
|
36
|
+
return { microphone: 'denied' };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async checkPermission() {
|
|
40
|
+
try {
|
|
41
|
+
const result = await navigator.permissions.query({ name: 'microphone' });
|
|
42
|
+
if (result.state === 'granted') {
|
|
43
|
+
return { microphone: 'granted' };
|
|
44
|
+
}
|
|
45
|
+
else if (result.state === 'denied') {
|
|
46
|
+
return { microphone: 'denied' };
|
|
47
|
+
}
|
|
48
|
+
return { microphone: 'prompt' };
|
|
49
|
+
}
|
|
50
|
+
catch (_a) {
|
|
51
|
+
// Fallback for browsers that don't support permissions API for microphone
|
|
52
|
+
return { microphone: 'prompt' };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async startRecord(options) {
|
|
56
|
+
console.log('Starting recording with options:', options);
|
|
57
|
+
if (this.recordingStatus === 'recording') {
|
|
58
|
+
throw new Error('Recording already in progress');
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
62
|
+
this.audioChunks = [];
|
|
63
|
+
this.pausedDuration = 0;
|
|
64
|
+
this.mediaRecorder = new MediaRecorder(stream);
|
|
65
|
+
this.mediaRecorder.ondataavailable = (event) => {
|
|
66
|
+
if (event.data.size > 0) {
|
|
67
|
+
this.audioChunks.push(event.data);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
this.mediaRecorder.start(100);
|
|
71
|
+
this.recordingStartTime = Date.now();
|
|
72
|
+
this.recordingStatus = 'recording';
|
|
73
|
+
this.notifyRecordingStatusChange();
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
throw new Error('Failed to start recording: ' + error.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async stopRecord() {
|
|
80
|
+
if (!this.mediaRecorder || this.recordingStatus === 'stopped') {
|
|
81
|
+
throw new Error('No recording in progress');
|
|
82
|
+
}
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
if (!this.mediaRecorder) {
|
|
85
|
+
reject(new Error('No recording in progress'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.mediaRecorder.onstop = () => {
|
|
89
|
+
var _a;
|
|
90
|
+
const duration = Date.now() - this.recordingStartTime - this.pausedDuration;
|
|
91
|
+
const blob = new Blob(this.audioChunks, { type: 'audio/webm' });
|
|
92
|
+
const path = URL.createObjectURL(blob);
|
|
93
|
+
(_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.stream.getTracks().forEach(track => track.stop());
|
|
94
|
+
this.recordingStatus = 'stopped';
|
|
95
|
+
this.notifyRecordingStatusChange();
|
|
96
|
+
resolve({ path, duration });
|
|
97
|
+
};
|
|
98
|
+
this.mediaRecorder.stop();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async pauseRecord() {
|
|
102
|
+
if (!this.mediaRecorder || this.recordingStatus !== 'recording') {
|
|
103
|
+
throw new Error('No active recording to pause');
|
|
104
|
+
}
|
|
105
|
+
this.mediaRecorder.pause();
|
|
106
|
+
this.pausedDuration = Date.now() - this.recordingStartTime;
|
|
107
|
+
this.recordingStatus = 'paused';
|
|
108
|
+
this.notifyRecordingStatusChange();
|
|
109
|
+
}
|
|
110
|
+
async resumeRecord() {
|
|
111
|
+
if (!this.mediaRecorder || this.recordingStatus !== 'paused') {
|
|
112
|
+
throw new Error('No paused recording to resume');
|
|
113
|
+
}
|
|
114
|
+
this.mediaRecorder.resume();
|
|
115
|
+
this.recordingStartTime = Date.now() - this.pausedDuration;
|
|
116
|
+
this.recordingStatus = 'recording';
|
|
117
|
+
this.notifyRecordingStatusChange();
|
|
118
|
+
}
|
|
119
|
+
async preparePlay(options) {
|
|
120
|
+
if (!options.path) {
|
|
121
|
+
throw new Error('Audio path is required');
|
|
122
|
+
}
|
|
123
|
+
const playerId = this.generatePlayerId();
|
|
124
|
+
const audioElement = new Audio(options.path);
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
audioElement.onloadedmetadata = () => {
|
|
127
|
+
var _a;
|
|
128
|
+
const duration = Math.round(((_a = audioElement.duration) !== null && _a !== void 0 ? _a : 0) * 1000);
|
|
129
|
+
const playerState = {
|
|
130
|
+
audioElement,
|
|
131
|
+
status: 'stopped',
|
|
132
|
+
path: options.path,
|
|
133
|
+
};
|
|
134
|
+
this.players.set(playerId, playerState);
|
|
135
|
+
this.setupAudioEventListeners(playerId);
|
|
136
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
137
|
+
resolve({ playerId, duration });
|
|
138
|
+
};
|
|
139
|
+
audioElement.onerror = () => {
|
|
140
|
+
reject(new Error('Failed to load audio file'));
|
|
141
|
+
};
|
|
142
|
+
audioElement.load();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async play(options) {
|
|
146
|
+
const player = this.players.get(options.playerId);
|
|
147
|
+
if (!player) {
|
|
148
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
await player.audioElement.play();
|
|
152
|
+
player.status = 'playing';
|
|
153
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new Error('Failed to play audio: ' + error.message);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async pausePlay(options) {
|
|
160
|
+
const player = this.players.get(options.playerId);
|
|
161
|
+
if (!player) {
|
|
162
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
163
|
+
}
|
|
164
|
+
if (player.status !== 'playing') {
|
|
165
|
+
throw new Error('No audio playing to pause');
|
|
166
|
+
}
|
|
167
|
+
player.audioElement.pause();
|
|
168
|
+
player.status = 'paused';
|
|
169
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
170
|
+
}
|
|
171
|
+
async resumePlay(options) {
|
|
172
|
+
const player = this.players.get(options.playerId);
|
|
173
|
+
if (!player) {
|
|
174
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
175
|
+
}
|
|
176
|
+
if (player.status !== 'paused') {
|
|
177
|
+
throw new Error('No paused audio to resume');
|
|
178
|
+
}
|
|
179
|
+
await player.audioElement.play();
|
|
180
|
+
player.status = 'playing';
|
|
181
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
182
|
+
}
|
|
183
|
+
async stopPlay(options) {
|
|
184
|
+
const player = this.players.get(options.playerId);
|
|
185
|
+
if (!player) {
|
|
186
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
187
|
+
}
|
|
188
|
+
player.audioElement.pause();
|
|
189
|
+
player.audioElement.currentTime = 0;
|
|
190
|
+
player.status = 'stopped';
|
|
191
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
192
|
+
}
|
|
193
|
+
async seekTo(options) {
|
|
194
|
+
const player = this.players.get(options.playerId);
|
|
195
|
+
if (!player) {
|
|
196
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
197
|
+
}
|
|
198
|
+
const positionInSeconds = options.position / 1000;
|
|
199
|
+
if (positionInSeconds < 0 || positionInSeconds > player.audioElement.duration) {
|
|
200
|
+
throw new Error('Invalid seek position');
|
|
201
|
+
}
|
|
202
|
+
player.audioElement.currentTime = positionInSeconds;
|
|
203
|
+
this.notifyPlaybackStatusChange(options.playerId);
|
|
204
|
+
}
|
|
205
|
+
async getPlaybackStatus(options) {
|
|
206
|
+
const player = this.players.get(options.playerId);
|
|
207
|
+
if (!player) {
|
|
208
|
+
throw new Error(`No player found with ID: ${options.playerId}`);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
playerId: options.playerId,
|
|
212
|
+
status: player.status,
|
|
213
|
+
currentPosition: Math.round(player.audioElement.currentTime * 1000),
|
|
214
|
+
duration: Math.round(player.audioElement.duration * 1000),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async getRecordingStatus() {
|
|
218
|
+
let duration = 0;
|
|
219
|
+
if (this.recordingStatus === 'recording') {
|
|
220
|
+
duration = Date.now() - this.recordingStartTime;
|
|
221
|
+
}
|
|
222
|
+
else if (this.recordingStatus === 'paused') {
|
|
223
|
+
duration = this.pausedDuration;
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
status: this.recordingStatus,
|
|
227
|
+
duration,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async destroyPlayer(options) {
|
|
231
|
+
const player = this.players.get(options.playerId);
|
|
232
|
+
if (!player) {
|
|
233
|
+
return; // Already destroyed or never existed
|
|
234
|
+
}
|
|
235
|
+
player.audioElement.pause();
|
|
236
|
+
player.audioElement.src = '';
|
|
237
|
+
player.audioElement.load();
|
|
238
|
+
this.players.delete(options.playerId);
|
|
239
|
+
}
|
|
240
|
+
setupAudioEventListeners(playerId) {
|
|
241
|
+
const player = this.players.get(playerId);
|
|
242
|
+
if (!player)
|
|
243
|
+
return;
|
|
244
|
+
player.audioElement.onplay = () => {
|
|
245
|
+
const p = this.players.get(playerId);
|
|
246
|
+
if (p) {
|
|
247
|
+
p.status = 'playing';
|
|
248
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
player.audioElement.onpause = () => {
|
|
252
|
+
const p = this.players.get(playerId);
|
|
253
|
+
// Only set to paused if currently playing (not stopped or ended)
|
|
254
|
+
if (p && p.status === 'playing') {
|
|
255
|
+
p.status = 'paused';
|
|
256
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
player.audioElement.onended = () => {
|
|
260
|
+
const p = this.players.get(playerId);
|
|
261
|
+
if (p) {
|
|
262
|
+
p.status = 'ended';
|
|
263
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
player.audioElement.ontimeupdate = () => {
|
|
267
|
+
const p = this.players.get(playerId);
|
|
268
|
+
if (p && p.status === 'playing') {
|
|
269
|
+
this.notifyPlaybackStatusChange(playerId);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
notifyRecordingStatusChange() {
|
|
274
|
+
this.getRecordingStatus().then(status => {
|
|
275
|
+
this.notifyListeners('recordingStatusChange', status);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
notifyPlaybackStatusChange(playerId) {
|
|
279
|
+
this.getPlaybackStatus({ playerId }).then(status => {
|
|
280
|
+
this.notifyListeners('playbackStatusChange', status);
|
|
281
|
+
}).catch(() => {
|
|
282
|
+
// Player may have been destroyed
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
288
|
+
__proto__: null,
|
|
289
|
+
RecorderPlayerWeb: RecorderPlayerWeb
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
exports.RecorderPlayer = RecorderPlayer;
|
|
293
|
+
//# sourceMappingURL=plugin.cjs.js.map
|