MOBILE-3490 core: Record audio in app if no recording app installed

main
Dani Palou 2020-08-03 15:37:26 +02:00
parent b67ea14abb
commit 384c4372fe
9 changed files with 322 additions and 108 deletions

View File

@ -214,6 +214,11 @@
</intent-filter>
</service>
</config-file>
<config-file parent="/*" target="res/xml/config.xml">
<feature name="Media">
<param name="android-package" value="org.apache.cordova.media.AudioHandler" />
</feature>
</config-file>
</platform>
<platform name="ios">
<resource-file src="GoogleService-Info.plist" />

18
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "moodlemobile",
"version": "3.9.2",
"version": "3.9.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -172,6 +172,11 @@
"resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-4.20.0.tgz",
"integrity": "sha512-Ht/0zau8/2+G/bH/okXXhhWB6YrkCNL2QxVJHQ2dophXFGxQPOZAN3CKWhuQSjfbr76fa2nvQXF6jsXLpIR/ng=="
},
"@ionic-native/media": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@ionic-native/media/-/media-4.20.0.tgz",
"integrity": "sha512-uhuTvy7MT6zFMSTDX/0aIrGu8IeRGi2FWJbWE+6o5wttAeVA6hNISSbtj4OQZhL3sUXYNCczDayV1VsOcXbdUg=="
},
"@ionic-native/media-capture": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@ionic-native/media-capture/-/media-capture-4.20.0.tgz",
@ -3169,9 +3174,9 @@
"dev": true
},
"compare-func": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz",
"integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.4.tgz",
"integrity": "sha512-sq2sWtrqKPkEXAC8tEJA1+BqAH9GbFkGBtUOqrUX57VSfwp8xyktctk+uLoRy5eccTdxzDcVIztlYDpKs3Jv1Q==",
"requires": {
"array-ify": "^1.0.0",
"dot-prop": "^3.0.0"
@ -3728,6 +3733,11 @@
"version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#0bb96b757fb484553ceabf35a59802f7983a2836",
"from": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle"
},
"cordova-plugin-media": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/cordova-plugin-media/-/cordova-plugin-media-5.0.3.tgz",
"integrity": "sha512-UQPFlpk1zL4BY44zGi8RVmYCvcKBCN4Dyf8ovxqGYCC8zR1yhbTRWYDdO9vJdERwbfgWV7+z7FMWiSUfqWm9bQ=="
},
"cordova-plugin-media-capture": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/cordova-plugin-media-capture/-/cordova-plugin-media-capture-3.0.3.tgz",

View File

@ -70,6 +70,7 @@
"@ionic-native/in-app-browser": "^4.20.0",
"@ionic-native/keyboard": "^4.20.0",
"@ionic-native/local-notifications": "^4.20.0",
"@ionic-native/media": "^4.20.0",
"@ionic-native/media-capture": "^4.20.0",
"@ionic-native/network": "^4.20.0",
"@ionic-native/push": "^4.20.0",
@ -105,6 +106,7 @@
"cordova-plugin-ionic-keyboard": "2.1.3",
"cordova-plugin-ionic-webview": "git+https://github.com/moodlemobile/cordova-plugin-ionic-webview.git#500-moodle",
"cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle",
"cordova-plugin-media": "^5.0.3",
"cordova-plugin-media-capture": "^3.0.3",
"cordova-plugin-network-information": "^2.0.2",
"cordova-plugin-qrscanner": "git+https://github.com/moodlemobile/cordova-plugin-qrscanner.git#dist",
@ -213,7 +215,10 @@
"cordova-plugin-wkwebview-cookies": {},
"cordova-plugin-qrscanner": {},
"cordova-plugin-chooser": {},
"cordova-plugin-wkuserscript": {}
"cordova-plugin-wkuserscript": {},
"cordova-plugin-media": {
"KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO"
}
}
},
"main": "desktop/electron.js",
@ -276,4 +281,4 @@
"engines": {
"node": ">=11.x"
}
}
}

View File

@ -29,6 +29,7 @@ import { Globalization } from '@ionic-native/globalization';
import { InAppBrowser } from '@ionic-native/in-app-browser';
import { Keyboard } from '@ionic-native/keyboard';
import { LocalNotifications } from '@ionic-native/local-notifications';
import { Media } from '@ionic-native/media';
import { MediaCapture } from '@ionic-native/media-capture';
import { Network } from '@ionic-native/network';
import { Push } from '@ionic-native/push';
@ -196,6 +197,7 @@ export const IONIC_NATIVE_PROVIDERS = [
return appProvider.isMobile() ? new MediaCapture() : new MediaCaptureMock(captureHelper);
}
},
Media,
{
provide: Network,
deps: [Platform],

View File

@ -24,9 +24,18 @@
<canvas *ngIf="isImage" class="core-webcam-image-canvas" #imgCanvas></canvas>
<img *ngIf="isImage" [hidden]="!hasCaptured" class="core-webcam-image" alt="{{ 'core.capturedimage' | translate }}" #previewImage>
<!-- Canvas to show audio waves when recording audio and audio player to listen to the result. -->
<!-- Recording audio. -->
<div *ngIf="isAudio" class="core-audio-record-container">
<canvas [hidden]="hasCaptured" class="core-audio-canvas" #streamAudio></canvas>
<!-- Canvas to show audio waves when recording audio in desktop. -->
<canvas [hidden]="hasCaptured || isCordovaAudioCapture" class="core-audio-canvas" #streamAudio></canvas>
<!-- Button to start/stop in mobile devices. -->
<button ion-button icon-only clear *ngIf="!hasCaptured && isCordovaAudioCapture" (click)="actionClicked()" [attr.aria-label]="title">
<ion-icon *ngIf="!isCapturing" name="microphone"></ion-icon>
<ion-icon *ngIf="isCapturing" name="square"></ion-icon>
</button>
<!-- Audio player to listen to the result. -->
<audio [hidden]="!hasCaptured" class="core-audio-captured" controls #previewAudio></audio>
</div>
</div>
@ -37,7 +46,7 @@
<ion-row *ngIf="readyToCapture">
<ion-col></ion-col>
<ion-col text-center>
<button ion-button icon-only clear *ngIf="!hasCaptured" (click)="actionClicked()" [attr.aria-label]="title">
<button ion-button icon-only clear *ngIf="!hasCaptured && !isCordovaAudioCapture" (click)="actionClicked()" [attr.aria-label]="title">
<ion-icon *ngIf="!isCapturing && isAudio" name="microphone"></ion-icon>
<ion-icon *ngIf="!isCapturing && isVideo" name="videocam"></ion-icon>
<ion-icon *ngIf="isImage" name="camera"></ion-icon>

View File

@ -23,9 +23,34 @@ ion-app.app-root page-core-emulator-capture-media {
.core-audio-captured {
width: 100%;
}
.button {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
height: 120px;
width: 120px;
.icon {
font-size: 120px;
width: auto;
}
}
}
audio, video, img {
audio {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}
video, img {
width: 100%;
height: 100%;
display: table-cell;

View File

@ -13,15 +13,20 @@
// limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
import { IonicPage, ViewController, NavParams } from 'ionic-angular';
import { IonicPage, ViewController, NavParams, Platform } from 'ionic-angular';
import { CoreApp } from '@providers/app';
import { CoreFileProvider } from '@providers/file';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreMimetypeUtils } from '@providers/utils/mimetype';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { FileEntry } from '@ionic-native/file';
import { MediaFile } from '@ionic-native/media-capture';
import { Media, MediaObject } from '@ionic-native/media';
/**
* Page to capture media in browser or desktop.
* Page to capture media in browser or desktop, or to capture audio in mobile devices.
*/
@IonicPage({ segment: 'core-emulator-capture-media' })
@Component({
@ -45,6 +50,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
isCapturing: boolean; // Whether it's capturing.
maxTime: number; // The max time to capture.
resetChrono: boolean; // Boolean to reset the chrono.
isCordovaAudioCapture: boolean; // Whether it's capturing audio using Cordova plugin.
protected type: string; // The type to capture: audio, video, image, captureimage.
protected isCaptureImage: boolean; // To identify if it's capturing an image using media capture plugin (instead of camera).
@ -60,9 +66,20 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
protected mediaBlob: Blob; // A Blob where the captured data is stored.
protected localMediaStream: MediaStream;
constructor(private viewCtrl: ViewController, params: NavParams, private domUtils: CoreDomUtilsProvider,
private timeUtils: CoreTimeUtilsProvider, private fileProvider: CoreFileProvider,
private textUtils: CoreTextUtilsProvider, private cdr: ChangeDetectorRef) {
// Variables for Cordova Media capture.
protected mediaFile: MediaObject;
protected filePath: string;
protected fileEntry: FileEntry;
constructor(protected viewCtrl: ViewController,
params: NavParams,
protected domUtils: CoreDomUtilsProvider,
protected timeUtils: CoreTimeUtilsProvider,
protected fileProvider: CoreFileProvider,
protected textUtils: CoreTextUtilsProvider,
protected cdr: ChangeDetectorRef,
protected plaform: Platform,
protected media: Media) {
this.window = window;
this.type = params.get('type');
this.maxTime = params.get('maxTime');
@ -79,12 +96,52 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
ngOnInit(): void {
this.initVariables();
if (this.isCordovaAudioCapture) {
this.initCordovaMediaPlugin();
} else {
this.initHtmlCapture();
}
}
/**
* Init recording with Cordova media plugin.
*
* @return Promise resolved when ready.
*/
protected async initCordovaMediaPlugin(): Promise<void> {
this.filePath = this.getFilePath();
let absolutePath = this.textUtils.concatenatePaths(this.fileProvider.getBasePathInstant(), this.filePath);
if (this.plaform.is('ios')) {
// In iOS we need to remove the file:// part.
absolutePath = absolutePath.replace(/^file:\/\//, '');
}
try {
// First create the file.
this.fileEntry = await this.fileProvider.createFile(this.filePath);
// Now create the media instance.
this.mediaFile = this.media.create(absolutePath);
this.readyToCapture = true;
this.previewMedia = this.previewAudio.nativeElement;
} catch (error) {
this.dismissWithError(-1, error.message || error);
}
}
/**
* Init HTML recorder, for desktop apps.
*
* @return Promise resolved when done.
*/
protected initHtmlCapture(): Promise<void> {
const constraints = {
video: this.isAudio ? false : { facingMode: this.facingMode },
audio: !this.isImage
};
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
return navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
let chunks = [];
this.localMediaStream = stream;
@ -254,6 +311,13 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
this.isImage = true;
this.title = 'core.captureimage';
}
this.isCordovaAudioCapture = CoreApp.instance.isMobile() && this.isAudio;
if (this.isCordovaAudioCapture) {
this.extension = this.plaform.is('ios') ? 'wav' : 'aac';
this.returnDataUrl = false;
}
}
/**
@ -269,7 +333,14 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
// Start the capture.
this.isCapturing = true;
this.resetChrono = false;
this.mediaRecorder.start();
if (this.isCordovaAudioCapture) {
this.mediaFile.startRecord();
this.previewMedia.src = '';
} else {
this.mediaRecorder && this.mediaRecorder.start();
}
this.cdr.detectChanges();
} else {
// Get the image from the video and set it to the canvas, using video width/height.
@ -299,6 +370,11 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
cancel(): void {
// Send a "cancelled" error like the Cordova plugin does.
this.dismissWithError(3, 'Canceled.', 'Camera cancelled');
if (this.isCordovaAudioCapture) {
// Delete the tmp file.
this.fileProvider.removeFile(this.filePath);
}
}
/**
@ -341,7 +417,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
/**
* Done capturing, write the file.
*/
done(): void {
async done(): Promise<void> {
if (this.returnDataUrl) {
// Return the image as a base64 string.
this.dismissWithData(this.imgCanvas.nativeElement.toDataURL(this.mimetype, this.quality));
@ -349,64 +425,98 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
return;
}
if (!this.mediaBlob) {
if (!this.mediaBlob && !this.isCordovaAudioCapture) {
// Shouldn't happen.
this.domUtils.showErrorModal('Please capture the media first.');
return;
}
// Create the file and return it.
const fileName = this.type + '_' + this.timeUtils.readableTimestamp() + '.' + this.extension,
path = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName),
loadingModal = this.domUtils.showModalLoading();
let fileEntry = this.fileEntry;
const loadingModal = this.domUtils.showModalLoading();
try {
if (!this.isCordovaAudioCapture) {
// Capturing in browser. Write the blob in a file.
if (!this.mediaBlob) {
// Shouldn't happen.
throw new Error('Please capture the media first.');
}
fileEntry = await this.fileProvider.writeFile(this.getFilePath(), this.mediaBlob);
}
this.fileProvider.writeFile(path, this.mediaBlob).then((fileEntry) => {
if (this.isImage && !this.isCaptureImage) {
this.dismissWithData(fileEntry.toURL());
} else {
// The capture plugin should return a MediaFile, not a FileEntry. Convert it.
return this.fileProvider.getMetadata(fileEntry).then((metadata) => {
const mediaFile: MediaFile = {
name: fileEntry.name,
fullPath: fileEntry.fullPath,
type: null,
lastModifiedDate: metadata.modificationTime,
size: metadata.size,
getFormatData: (successFn, errorFn): void => {
// Nothing to do.
}
};
const metadata = await this.fileProvider.getMetadata(fileEntry);
this.dismissWithData([mediaFile]);
});
let mimetype = null;
if (this.extension) {
mimetype = CoreMimetypeUtils.instance.getMimeType(this.extension);
}
const mediaFile: MediaFile = {
name: fileEntry.name,
fullPath: fileEntry.nativeURL || fileEntry.fullPath,
type: mimetype,
lastModifiedDate: metadata.modificationTime,
size: metadata.size,
getFormatData: (successFn, errorFn): void => {
// Nothing to do.
}
};
this.dismissWithData([mediaFile]);
}
}).catch((err) => {
} catch (err) {
this.domUtils.showErrorModal(err);
}).finally(() => {
} finally {
loadingModal.dismiss();
});
}
}
/**
* Get path to the file where the media will be stored.
*
* @return Path.
*/
protected getFilePath(): string {
const fileName = this.type + '_' + this.timeUtils.readableTimestamp() + '.' + this.extension;
return this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName);
}
/**
* Stop capturing. Only for video and audio.
*/
stopCapturing(): void {
this.streamVideo && this.streamVideo.nativeElement.pause();
this.audioDrawer && this.audioDrawer.stop();
this.mediaRecorder && this.mediaRecorder.stop();
this.isCapturing = false;
this.hasCaptured = true;
if (this.isCordovaAudioCapture) {
this.mediaFile.stopRecord();
this.previewMedia.src = this.fileProvider.convertFileSrc(this.fileEntry.toURL());
} else {
this.streamVideo && this.streamVideo.nativeElement.pause();
this.audioDrawer && this.audioDrawer.stop();
this.mediaRecorder && this.mediaRecorder.stop();
}
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
const tracks = this.localMediaStream.getTracks();
tracks.forEach((track) => {
track.stop();
});
this.mediaFile && this.mediaFile.release();
if (this.localMediaStream) {
const tracks = this.localMediaStream.getTracks();
tracks.forEach((track) => {
track.stop();
});
}
this.streamVideo && this.streamVideo.nativeElement.pause();
this.previewMedia && this.previewMedia.pause();
this.audioDrawer && this.audioDrawer.stop();

View File

@ -13,7 +13,7 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { Platform } from 'ionic-angular';
import { Platform, ModalController } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';
import { MediaCapture, MediaFile, CaptureError, CaptureAudioOptions, CaptureVideoOptions } from '@ionic-native/media-capture';
import { TranslateService } from '@ngx-translate/core';
@ -53,11 +53,19 @@ export class CoreFileUploaderProvider {
onAudioCapture: Subject<boolean> = new Subject<boolean>();
onVideoCapture: Subject<boolean> = new Subject<boolean>();
constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, private textUtils: CoreTextUtilsProvider,
private utils: CoreUtilsProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider,
private mimeUtils: CoreMimetypeUtilsProvider, private filepoolProvider: CoreFilepoolProvider,
private platform: Platform, private translate: TranslateService, private mediaCapture: MediaCapture,
private camera: Camera) {
constructor(logger: CoreLoggerProvider,
protected fileProvider: CoreFileProvider,
protected textUtils: CoreTextUtilsProvider,
protected utils: CoreUtilsProvider,
protected sitesProvider: CoreSitesProvider,
protected timeUtils: CoreTimeUtilsProvider,
protected mimeUtils: CoreMimetypeUtilsProvider,
protected filepoolProvider: CoreFilepoolProvider,
protected platform: Platform,
protected translate: TranslateService,
protected mediaCapture: MediaCapture,
protected camera: Camera,
protected modalCtrl: ModalController) {
this.logger = logger.getInstance('CoreFileUploaderProvider');
}
@ -110,6 +118,29 @@ export class CoreFileUploaderProvider {
});
}
/**
* Record an audio file without using an external app.
*
* @return Promise resolved with the file.
*/
captureAudioInApp(): Promise<MediaFile> {
return new Promise((resolve, reject): any => {
const params = {
type: 'audio',
};
const modal = this.modalCtrl.create('CoreEmulatorCaptureMediaPage', params, { enableBackdropDismiss: false });
modal.present();
modal.onDidDismiss((data: any, role: string) => {
if (role == 'success') {
resolve(data[0]);
} else {
reject(data);
}
});
});
}
/**
* Start the video recorder application and return information about captured video clip files.
*
@ -215,13 +246,14 @@ export class CoreFileUploaderProvider {
*/
getMediaUploadOptions(mediaFile: MediaFile): CoreFileUploaderOptions {
const options: CoreFileUploaderOptions = {};
let filename = mediaFile.name,
split;
let filename = mediaFile.name;
// Add a timestamp to the filename to make it unique.
split = filename.split('.');
split[0] += '_' + this.timeUtils.readableTimestamp();
filename = split.join('.');
if (!filename.match(/_\d{14}(\..*)?$/)) {
// Add a timestamp to the filename to make it unique.
const split = filename.split('.');
split[0] += '_' + this.timeUtils.readableTimestamp();
filename = split.join('.');
}
options.fileName = filename;
options.deleteAfterUpload = true;

View File

@ -455,37 +455,37 @@ export class CoreFileUploaderHelperProvider {
/**
* Treat a capture audio/video error.
*
* @param error Error returned by the Cordova plugin. Can be a string or an object.
* @param error Error returned by the Cordova plugin.
* @param defaultMessage Key of the default message to show.
* @return Rejected promise. If it doesn't have an error message it means it was cancelled.
* @return Rejected promise.
*/
protected treatCaptureError(error: any, defaultMessage: string): Promise<any> {
protected treatCaptureError(error: any, defaultMessage: string): void {
// Cancelled or error. If cancelled, error is an object with code = 3.
if (error) {
if (typeof error === 'string') {
this.logger.error('Error while recording audio/video: ' + error);
if (error.indexOf('No Activity found') > -1) {
// User doesn't have an app to do this.
return Promise.reject(this.translate.instant('core.fileuploader.errornoapp'));
} else {
return Promise.reject(this.translate.instant(defaultMessage));
}
if (error.code != 3) {
// Error, not cancelled.
this.logger.error('Error while recording audio/video', error);
const message = this.isNoAppError(error) ? this.translate.instant('core.fileuploader.errornoapp') :
(error.message || this.translate.instant(defaultMessage));
throw new Error(message);
} else {
if (error.code != 3) {
// Error, not cancelled.
this.logger.error('Error while recording audio/video', error);
const message = error.code == 20 ? this.translate.instant('core.fileuploader.errornoapp') :
(error.message || this.translate.instant(defaultMessage));
return Promise.reject(message);
} else {
return Promise.reject(this.domUtils.createCanceledError());
}
throw this.domUtils.createCanceledError();
}
}
return Promise.reject(null);
throw new Error('Error capturing media');
}
/**
* Check if a capture error is because there is no app to capture.
*
* @param error Error.
* @return Whether it's because there is no app.
*/
protected isNoAppError(error: any): boolean {
return error && error.code == 20;
}
/**
@ -522,41 +522,57 @@ export class CoreFileUploaderHelperProvider {
* @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported.
* @return Promise resolved when done.
*/
uploadAudioOrVideo(isAudio: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]): Promise<any> {
async uploadAudioOrVideo(isAudio: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]): Promise<any> {
this.logger.debug('Trying to record a ' + (isAudio ? 'audio' : 'video') + ' file');
const options = { limit: 1, mimetypes: mimetypes },
promise = isAudio ? this.fileUploaderProvider.captureAudio(options) : this.fileUploaderProvider.captureVideo(options);
// The mimetypes param is only for desktop apps, the Cordova plugin doesn't support it.
return promise.then((medias) => {
// We used limit 1, we only want 1 media.
const media: MediaFile = medias[0];
let path = media.fullPath;
const error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, path); // Verify that the mimetype is supported.
const captureOptions = { limit: 1, mimetypes: mimetypes };
let media: MediaFile;
if (error) {
return Promise.reject(error);
}
try {
const medias = isAudio ? await this.fileUploaderProvider.captureAudio(captureOptions) :
await this.fileUploaderProvider.captureVideo(captureOptions);
// Make sure the path has the protocol. In iOS it doesn't.
if (this.appProvider.isMobile() && path.indexOf('file://') == -1) {
path = 'file://' + path;
}
media = medias[0]; // We used limit 1, we only want 1 media.
} catch (error) {
const options = this.fileUploaderProvider.getMediaUploadOptions(media);
if (isAudio && this.isNoAppError(error) && this.appProvider.isMobile() &&
(!this.platform.is('android') || this.platform.version().major < 10)) {
// No app to record audio, fallback to capture it ourselves.
// In Android it will only be done in Android 9 or lower because there's a bug in the plugin.
try {
media = await this.fileUploaderProvider.captureAudioInApp();
} catch (error) {
this.treatCaptureError(error, 'core.fileuploader.errorcapturingaudio'); // Throw the right error.
}
if (upload) {
return this.uploadFile(path, maxSize, true, options);
} else {
// Copy or move the file to our temporary folder.
return this.copyToTmpFolder(path, true, maxSize, undefined, options);
}
}, (error) => {
const defaultError = isAudio ? 'core.fileuploader.errorcapturingaudio' : 'core.fileuploader.errorcapturingvideo';
const defaultError = isAudio ? 'core.fileuploader.errorcapturingaudio' : 'core.fileuploader.errorcapturingvideo';
return this.treatCaptureError(error, defaultError);
});
this.treatCaptureError(error, defaultError); // Throw the right error.
}
}
let path = media.fullPath;
const error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, path); // Verify that the mimetype is supported.
if (error) {
throw new Error(error);
}
// Make sure the path has the protocol. In iOS it doesn't.
if (this.appProvider.isMobile() && path.indexOf('file://') == -1) {
path = 'file://' + path;
}
const options = this.fileUploaderProvider.getMediaUploadOptions(media);
if (upload) {
return this.uploadFile(path, maxSize, true, options);
} else {
// Copy or move the file to our temporary folder.
return this.copyToTmpFolder(path, true, maxSize, undefined, options);
}
}
/**