@independo/capacitor-voice-recorder
Version:
Capacitor plugin for voice recording
212 lines • 9.07 kB
JavaScript
import { Filesystem } from '@capacitor/filesystem';
import write_blob from 'capacitor-blob-writer';
import getBlobDuration from "./helper/get-blob-duration";
import { alreadyRecordingError, couldNotQueryPermissionStatusError, deviceCannotVoiceRecordError, emptyRecordingError, failedToFetchRecordingError, failedToRecordError, failureResponse, missingPermissionError, recordingHasNotStartedError, successResponse, } from './predefined-web-responses';
// these mime types will be checked one by one in order until one of them is found to be supported by the current browser
const POSSIBLE_MIME_TYPES = {
'audio/aac': '.aac',
'audio/webm;codecs=opus': '.ogg',
'audio/mp4': '.mp3',
'audio/webm': '.ogg',
'audio/ogg;codecs=opus': '.ogg',
};
const neverResolvingPromise = () => new Promise(() => undefined);
export class VoiceRecorderImpl {
constructor() {
this.mediaRecorder = null;
this.chunks = [];
this.pendingResult = neverResolvingPromise();
}
static async canDeviceVoiceRecord() {
var _a;
if (((_a = navigator === null || navigator === void 0 ? void 0 : navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) == null || VoiceRecorderImpl.getSupportedMimeType() == null) {
return failureResponse();
}
else {
return successResponse();
}
}
async startRecording(options) {
if (this.mediaRecorder != null) {
throw alreadyRecordingError();
}
const deviceCanRecord = await VoiceRecorderImpl.canDeviceVoiceRecord();
if (!deviceCanRecord.value) {
throw deviceCannotVoiceRecordError();
}
const havingPermission = await VoiceRecorderImpl.hasAudioRecordingPermission().catch(() => successResponse());
if (!havingPermission.value) {
throw missingPermissionError();
}
return navigator.mediaDevices
.getUserMedia({ audio: true })
.then((stream) => this.onSuccessfullyStartedRecording(stream, options))
.catch(this.onFailedToStartRecording.bind(this));
}
async stopRecording() {
if (this.mediaRecorder == null) {
throw recordingHasNotStartedError();
}
try {
this.mediaRecorder.stop();
this.mediaRecorder.stream.getTracks().forEach((track) => track.stop());
return this.pendingResult;
}
catch (ignore) {
throw failedToFetchRecordingError();
}
finally {
this.prepareInstanceForNextOperation();
}
}
static async hasAudioRecordingPermission() {
// Safari does not support navigator.permissions.query
if (!navigator.permissions.query) {
if (navigator.mediaDevices !== undefined) {
return navigator.mediaDevices
.getUserMedia({ audio: true })
.then(() => successResponse())
.catch(() => {
throw couldNotQueryPermissionStatusError();
});
}
}
return navigator.permissions
.query({ name: 'microphone' })
.then((result) => ({ value: result.state === 'granted' }))
.catch(() => {
throw couldNotQueryPermissionStatusError();
});
}
static async requestAudioRecordingPermission() {
const havingPermission = await VoiceRecorderImpl.hasAudioRecordingPermission().catch(() => failureResponse());
if (havingPermission.value) {
return successResponse();
}
return navigator.mediaDevices
.getUserMedia({ audio: true })
.then(() => successResponse())
.catch(() => failureResponse());
}
pauseRecording() {
if (this.mediaRecorder == null) {
throw recordingHasNotStartedError();
}
else if (this.mediaRecorder.state === 'recording') {
this.mediaRecorder.pause();
return Promise.resolve(successResponse());
}
else {
return Promise.resolve(failureResponse());
}
}
resumeRecording() {
if (this.mediaRecorder == null) {
throw recordingHasNotStartedError();
}
else if (this.mediaRecorder.state === 'paused') {
this.mediaRecorder.resume();
return Promise.resolve(successResponse());
}
else {
return Promise.resolve(failureResponse());
}
}
getCurrentStatus() {
if (this.mediaRecorder == null) {
return Promise.resolve({ status: 'NONE' });
}
else if (this.mediaRecorder.state === 'recording') {
return Promise.resolve({ status: 'RECORDING' });
}
else if (this.mediaRecorder.state === 'paused') {
return Promise.resolve({ status: 'PAUSED' });
}
else {
return Promise.resolve({ status: 'NONE' });
}
}
static getSupportedMimeType() {
if ((MediaRecorder === null || MediaRecorder === void 0 ? void 0 : MediaRecorder.isTypeSupported) == null)
return null;
const foundSupportedType = Object.keys(POSSIBLE_MIME_TYPES).find((type) => MediaRecorder.isTypeSupported(type));
return foundSupportedType !== null && foundSupportedType !== void 0 ? foundSupportedType : null;
}
onSuccessfullyStartedRecording(stream, options) {
this.pendingResult = new Promise((resolve, reject) => {
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.onerror = () => {
this.prepareInstanceForNextOperation();
reject(failedToRecordError());
};
this.mediaRecorder.onstop = async () => {
var _a, _b, _c;
const mimeType = VoiceRecorderImpl.getSupportedMimeType();
if (mimeType == null) {
this.prepareInstanceForNextOperation();
reject(failedToFetchRecordingError());
return;
}
const blobVoiceRecording = new Blob(this.chunks, { type: mimeType });
if (blobVoiceRecording.size <= 0) {
this.prepareInstanceForNextOperation();
reject(emptyRecordingError());
return;
}
let uri = undefined;
let recordDataBase64 = '';
if (options === null || options === void 0 ? void 0 : options.directory) {
const subDirectory = (_c = (_b = (_a = options.subDirectory) === null || _a === void 0 ? void 0 : _a.match(/^\/?(.+[^/])\/?$/)) === null || _b === void 0 ? void 0 : _b[1]) !== null && _c !== void 0 ? _c : '';
const path = `${subDirectory}/recording-${new Date().getTime()}${POSSIBLE_MIME_TYPES[mimeType]}`;
await write_blob({
blob: blobVoiceRecording,
directory: options.directory,
fast_mode: true,
path,
recursive: true,
});
({ uri } = await Filesystem.getUri({ directory: options.directory, path }));
}
else {
recordDataBase64 = await VoiceRecorderImpl.blobToBase64(blobVoiceRecording);
}
const recordingDuration = await getBlobDuration(blobVoiceRecording);
this.prepareInstanceForNextOperation();
resolve({ value: { recordDataBase64, mimeType, msDuration: recordingDuration * 1000, uri } });
};
this.mediaRecorder.ondataavailable = (event) => this.chunks.push(event.data);
this.mediaRecorder.start();
});
return successResponse();
}
onFailedToStartRecording() {
this.prepareInstanceForNextOperation();
throw failedToRecordError();
}
static blobToBase64(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => {
const recordingResult = String(reader.result);
const splitResult = recordingResult.split('base64,');
const toResolve = splitResult.length > 1 ? splitResult[1] : recordingResult;
resolve(toResolve.trim());
};
reader.readAsDataURL(blob);
});
}
prepareInstanceForNextOperation() {
if (this.mediaRecorder != null && this.mediaRecorder.state === 'recording') {
try {
this.mediaRecorder.stop();
}
catch (ignore) {
console.warn('Failed to stop recording during cleanup');
}
}
this.pendingResult = neverResolvingPromise();
this.mediaRecorder = null;
this.chunks = [];
}
}
//# sourceMappingURL=VoiceRecorderImpl.js.map