MOBILE-2314 core: Remove cordova-plugin-media

main
Noel De Martin 2023-02-02 12:36:39 +01:00
parent 4013855ae9
commit 495d395beb
10 changed files with 29 additions and 346 deletions

View File

@ -196,11 +196,6 @@
<param name="android-package" value="com.adobe.phonegap.push.PushPlugin" /> <param name="android-package" value="com.adobe.phonegap.push.PushPlugin" />
</feature> </feature>
</config-file> </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>
<config-file parent="/*" target="AndroidManifest.xml"> <config-file parent="/*" target="AndroidManifest.xml">
<uses-feature android:name="android.hardware.bluetooth" android:required="false" /> <uses-feature android:name="android.hardware.bluetooth" android:required="false" />
</config-file> </config-file>

20
package-lock.json generated
View File

@ -4167,21 +4167,6 @@
} }
} }
}, },
"@ionic-native/media": {
"version": "5.36.0",
"resolved": "https://registry.npmjs.org/@ionic-native/media/-/media-5.36.0.tgz",
"integrity": "sha512-WIDCeUlX7bCbse/x2Rr7mAIQJnLo18ZWcmsVgSTTBVS7ObU2DBl4ieqRx6y9PAAV+3tNZqMV4JAWDfMiFokpJg==",
"requires": {
"@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha512-rkiiTuf/z2wTd4RxFOb+clE7PF4AEJU0hsczbUdkHHBtkUmpWQpEddynNfJYKYtZFJKbq4F+brfekt1kx85IZA=="
}
}
},
"@ionic-native/media-capture": { "@ionic-native/media-capture": {
"version": "5.36.0", "version": "5.36.0",
"resolved": "https://registry.npmjs.org/@ionic-native/media-capture/-/media-capture-5.36.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/media-capture/-/media-capture-5.36.0.tgz",
@ -14340,11 +14325,6 @@
"resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz", "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz",
"integrity": "sha512-yDUG+9ieKVRitq5mGlNxjaZh/MgEhFFIgTIPhqSbUaQ8UuZbawy5mhJAVClqY97q8/rcQtL6dCDa7x2sEtCLcA==" "integrity": "sha512-yDUG+9ieKVRitq5mGlNxjaZh/MgEhFFIgTIPhqSbUaQ8UuZbawy5mhJAVClqY97q8/rcQtL6dCDa7x2sEtCLcA=="
}, },
"cordova-plugin-media": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cordova-plugin-media/-/cordova-plugin-media-5.0.4.tgz",
"integrity": "sha512-mAqincYqOT5gu5LWyfgJu3qmOq+lhLAKhnOZULpG622FvYiHjjfsoJ/fkI55WwI3FIcHeeyhToGvHXBCNJePZg=="
},
"cordova-plugin-media-capture": { "cordova-plugin-media-capture": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/cordova-plugin-media-capture/-/cordova-plugin-media-capture-3.0.3.tgz", "resolved": "https://registry.npmjs.org/cordova-plugin-media-capture/-/cordova-plugin-media-capture-3.0.3.tgz",

View File

@ -63,7 +63,6 @@
"@ionic-native/ionic-webview": "5.36.0", "@ionic-native/ionic-webview": "5.36.0",
"@ionic-native/keyboard": "5.36.0", "@ionic-native/keyboard": "5.36.0",
"@ionic-native/local-notifications": "5.36.0", "@ionic-native/local-notifications": "5.36.0",
"@ionic-native/media": "5.36.0",
"@ionic-native/media-capture": "5.36.0", "@ionic-native/media-capture": "5.36.0",
"@ionic-native/network": "5.36.0", "@ionic-native/network": "5.36.0",
"@ionic-native/push": "5.36.0", "@ionic-native/push": "5.36.0",
@ -104,7 +103,6 @@
"cordova-plugin-file": "6.0.2", "cordova-plugin-file": "6.0.2",
"cordova-plugin-geolocation": "4.1.0", "cordova-plugin-geolocation": "4.1.0",
"cordova-plugin-ionic-keyboard": "2.2.0", "cordova-plugin-ionic-keyboard": "2.2.0",
"cordova-plugin-media": "5.0.4",
"cordova-plugin-media-capture": "3.0.3", "cordova-plugin-media-capture": "3.0.3",
"cordova-plugin-network-information": "3.0.0", "cordova-plugin-network-information": "3.0.0",
"cordova-plugin-prevent-override": "1.0.1", "cordova-plugin-prevent-override": "1.0.1",
@ -225,9 +223,6 @@
"ANDROID_SUPPORT_V4_VERSION": "26.+" "ANDROID_SUPPORT_V4_VERSION": "26.+"
}, },
"cordova-plugin-media-capture": {}, "cordova-plugin-media-capture": {},
"cordova-plugin-media": {
"KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO"
},
"cordova-plugin-network-information": {}, "cordova-plugin-network-information": {},
"@moodlehq/cordova-plugin-qrscanner": {}, "@moodlehq/cordova-plugin-qrscanner": {},
"cordova-plugin-splashscreen": {}, "cordova-plugin-splashscreen": {},

View File

@ -14,7 +14,7 @@
<core-loading [hideUntil]="readyToCapture"> <core-loading [hideUntil]="readyToCapture">
<div class="core-av-wrapper"> <div class="core-av-wrapper">
<!-- Video stream for image and video. --> <!-- Video stream for image and video. -->
<video *ngIf="!isAudio" [hidden]="hasCaptured" class="core-webcam-stream" autoplay #streamVideo></video> <video [hidden]="hasCaptured" class="core-webcam-stream" autoplay #streamVideo></video>
<!-- For video recording, use 2 videos and show/hide them because a CSS rule caused problems with the controls. --> <!-- For video recording, use 2 videos and show/hide them because a CSS rule caused problems with the controls. -->
<video *ngIf="isVideo" [hidden]="!hasCaptured" class="core-webcam-video-captured" controls #previewVideo <video *ngIf="isVideo" [hidden]="!hasCaptured" class="core-webcam-video-captured" controls #previewVideo
@ -25,21 +25,6 @@
<canvas *ngIf="isImage" class="core-webcam-image-canvas" #imgCanvas></canvas> <canvas *ngIf="isImage" class="core-webcam-image-canvas" #imgCanvas></canvas>
<img *ngIf="isImage" [hidden]="!hasCaptured" class="core-webcam-image" alt="{{ 'core.capturedimage' | translate }}" <img *ngIf="isImage" [hidden]="!hasCaptured" class="core-webcam-image" alt="{{ 'core.capturedimage' | translate }}"
#previewImage> #previewImage>
<!-- Recording audio. -->
<div *ngIf="isAudio" class="core-audio-record-container">
<!-- Canvas to show audio waves when recording audio in browser. -->
<canvas [hidden]="hasCaptured || isCordovaAudioCapture" class="core-audio-canvas" #streamAudio></canvas>
<!-- Button to start/stop in mobile devices. -->
<ion-button fill="clear" *ngIf="!hasCaptured && isCordovaAudioCapture" (click)="actionClicked()" [attr.aria-label]="title">
<ion-icon *ngIf="!isCapturing" name="fas-microphone" slot="icon-only" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="isCapturing" name="fas-square" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
<!-- Audio player to listen to the result. -->
<audio [hidden]="!hasCaptured" class="core-audio-captured" controls #previewAudio controlsList="nodownload"></audio>
</div>
</div> </div>
</core-loading> </core-loading>
</ion-content> </ion-content>
@ -48,8 +33,7 @@
<ion-row> <ion-row>
<ion-col></ion-col> <ion-col></ion-col>
<ion-col class="ion-text-center"> <ion-col class="ion-text-center">
<ion-button fill="clear" *ngIf="!hasCaptured && !isCordovaAudioCapture" (click)="actionClicked()" [attr.aria-label]="title"> <ion-button fill="clear" *ngIf="!hasCaptured" (click)="actionClicked()" [attr.aria-label]="title">
<ion-icon *ngIf="!isCapturing && isAudio" name="fas-microphone" slot="icon-only" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="!isCapturing && isVideo" name="fas-video" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon *ngIf="!isCapturing && isVideo" name="fas-video" slot="icon-only" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="isImage" name="fas-camera" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon *ngIf="isImage" name="fas-camera" slot="icon-only" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="isCapturing" name="fas-square" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon *ngIf="isCapturing" name="fas-square" slot="icon-only" aria-hidden="true"></ion-icon>

View File

@ -21,30 +21,6 @@
width: 100%; 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 {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}
} }
video, img { video, img {

View File

@ -13,23 +13,20 @@
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef, Input } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef, Input } from '@angular/core';
import { MediaObject } from '@ionic-native/media/ngx';
import { FileEntry } from '@ionic-native/file/ngx';
import { MediaFile } from '@ionic-native/media-capture/ngx'; import { MediaFile } from '@ionic-native/media-capture/ngx';
import { CoreFile, CoreFileProvider } from '@services/file'; import { CoreFile, CoreFileProvider } from '@services/file';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreTimeUtils } from '@services/utils/time'; import { CoreTimeUtils } from '@services/utils/time';
import { ModalController, Media, Translate } from '@singletons'; import { ModalController, Translate } from '@singletons';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { CoreCaptureError } from '@classes/errors/captureerror'; import { CoreCaptureError } from '@classes/errors/captureerror';
import { CoreCanceledError } from '@classes/errors/cancelederror'; import { CoreCanceledError } from '@classes/errors/cancelederror';
import { CorePath } from '@singletons/path'; import { CorePath } from '@singletons/path';
import { CorePlatform } from '@services/platform';
/** /**
* Page to capture media in browser, or to capture audio in mobile devices. * Page to capture media in browser.
*/ */
@Component({ @Component({
selector: 'core-emulator-capture-media', selector: 'core-emulator-capture-media',
@ -38,7 +35,7 @@ import { CorePlatform } from '@services/platform';
}) })
export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
@Input() type?: 'audio' | 'video' | 'image' | 'captureimage'; @Input() type?: 'video' | 'image' | 'captureimage';
@Input() maxTime?: number; // Max time to capture. @Input() maxTime?: number; // Max time to capture.
@Input() facingMode?: string; // Camera facing mode. @Input() facingMode?: string; // Camera facing mode.
@Input() mimetype?: string; @Input() mimetype?: string;
@ -50,30 +47,20 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
@ViewChild('previewVideo') previewVideo?: ElementRef; @ViewChild('previewVideo') previewVideo?: ElementRef;
@ViewChild('imgCanvas') imgCanvas?: ElementRef; @ViewChild('imgCanvas') imgCanvas?: ElementRef;
@ViewChild('previewImage') previewImage?: ElementRef; @ViewChild('previewImage') previewImage?: ElementRef;
@ViewChild('streamAudio') streamAudio?: ElementRef;
@ViewChild('previewAudio') previewAudio?: ElementRef;
title?: string; // The title of the page. title?: string; // The title of the page.
isAudio?: boolean; // Whether it should capture audio.
isVideo?: boolean; // Whether it should capture video. isVideo?: boolean; // Whether it should capture video.
isImage?: boolean; // Whether it should capture image. isImage?: boolean; // Whether it should capture image.
readyToCapture?: boolean; // Whether it's ready to capture. readyToCapture?: boolean; // Whether it's ready to capture.
hasCaptured?: boolean; // Whether it has captured something. hasCaptured?: boolean; // Whether it has captured something.
isCapturing?: boolean; // Whether it's capturing. isCapturing?: boolean; // Whether it's capturing.
resetChrono?: boolean; // Boolean to reset the chrono. resetChrono?: boolean; // Boolean to reset the chrono.
isCordovaAudioCapture?: boolean; // Whether it's capturing audio using Cordova plugin.
protected isCaptureImage?: boolean; // To identify if it's capturing an image using media capture plugin (instead of camera). protected isCaptureImage?: boolean; // To identify if it's capturing an image using media capture plugin (instead of camera).
protected mediaRecorder?: MediaRecorder; // To record video/audio. protected mediaRecorder?: MediaRecorder; // To record video.
protected previewMedia?: HTMLAudioElement | HTMLVideoElement; // The element to preview the audio/video captured. protected previewMedia?: HTMLVideoElement; // The element to preview the video captured.
protected mediaBlob?: Blob; // A Blob where the captured data is stored. protected mediaBlob?: Blob; // A Blob where the captured data is stored.
protected localMediaStream?: MediaStream; protected localMediaStream?: MediaStream;
protected audioDrawer?: {start: () => void; stop: () => void }; // To start/stop the display of audio sound.
// Variables for Cordova Media capture.
protected mediaFile?: MediaObject;
protected filePath?: string;
protected fileEntry?: FileEntry;
constructor( constructor(
protected changeDetectorRef: ChangeDetectorRef, protected changeDetectorRef: ChangeDetectorRef,
@ -84,12 +71,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.initVariables(); this.initVariables();
this.initHtmlCapture();
if (this.isCordovaAudioCapture) {
this.initCordovaMediaPlugin();
} else {
this.initHtmlCapture();
}
} }
/** /**
@ -108,71 +90,10 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
if (this.type == 'video') { if (this.type == 'video') {
this.isVideo = true; this.isVideo = true;
this.title = 'core.capturevideo'; this.title = 'core.capturevideo';
} else if (this.type == 'audio') {
this.isAudio = true;
this.title = 'core.captureaudio';
} else if (this.type == 'image') { } else if (this.type == 'image') {
this.isImage = true; this.isImage = true;
this.title = 'core.captureimage'; this.title = 'core.captureimage';
} }
this.isCordovaAudioCapture = CorePlatform.isMobile() && this.isAudio;
if (this.isCordovaAudioCapture) {
this.extension = CorePlatform.is('ios') ? 'wav' : 'aac';
this.returnDataUrl = false;
}
}
/**
* Init recording with Cordova media plugin.
*
* @returns Promise resolved when ready.
*/
protected async initCordovaMediaPlugin(): Promise<void> {
try {
await this.createFileAndMediaInstance();
this.readyToCapture = true;
this.previewMedia = this.previewAudio?.nativeElement;
} catch (error) {
this.dismissWithError(-1, error.message || error);
}
}
/**
* Create a file entry and the cordova media instance.
*/
protected async createFileAndMediaInstance(): Promise<void> {
this.filePath = this.getFilePath();
// First create the file.
this.fileEntry = await CoreFile.createFile(this.filePath);
// Now create the media instance.
let absolutePath = CorePath.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath);
if (CorePlatform.is('ios')) {
// In iOS we need to remove the file:// part.
absolutePath = absolutePath.replace(/^file:\/\//, '');
}
this.mediaFile = Media.create(absolutePath);
}
/**
* Reset the file and the cordova media instance.
*/
protected async resetCordovaMediaCapture(): Promise<void> {
if (this.filePath) {
// Remove old file, don't block the user for this.
CoreFile.removeFile(this.filePath);
}
this.mediaFile?.release();
await this.createFileAndMediaInstance();
} }
/** /**
@ -183,8 +104,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
*/ */
protected async initHtmlCapture(): Promise<void> { protected async initHtmlCapture(): Promise<void> {
const constraints = { const constraints = {
video: this.isAudio ? false : { facingMode: this.facingMode }, video: { facingMode: this.facingMode },
audio: !this.isImage,
}; };
try { try {
@ -196,22 +116,18 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
if (!this.isImage) { if (!this.isImage) {
if (this.isVideo) { if (this.isVideo) {
this.previewMedia = this.previewVideo?.nativeElement; this.previewMedia = this.previewVideo?.nativeElement;
} else {
this.previewMedia = this.previewAudio?.nativeElement;
this.initAudioDrawer(this.localMediaStream);
this.audioDrawer?.start();
} }
this.mediaRecorder = new MediaRecorder(this.localMediaStream, { mimeType: this.mimetype }); this.mediaRecorder = new MediaRecorder(this.localMediaStream, { mimeType: this.mimetype });
// When video or audio is recorded, add it to the list of chunks. // When video is recorded, add it to the list of chunks.
this.mediaRecorder.ondataavailable = (e): void => { this.mediaRecorder.ondataavailable = (e): void => {
if (e.data.size > 0) { if (e.data.size > 0) {
chunks.push(e.data); chunks.push(e.data);
} }
}; };
// When recording stops, create a Blob element with the recording and set it to the video or audio. // When recording stops, create a Blob element with the recording and set it to the video.
this.mediaRecorder.onstop = (): void => { this.mediaRecorder.onstop = (): void => {
this.mediaBlob = new Blob(chunks); this.mediaBlob = new Blob(chunks);
chunks = []; chunks = [];
@ -267,91 +183,6 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
} }
} }
/**
* Initialize the audio drawer. This code has been extracted from MDN's example on MediaStream Recording:
* https://github.com/mdn/web-dictaphone
*
* @param stream Stream returned by getUserMedia.
*/
protected initAudioDrawer(stream: MediaStream): void {
if (!this.streamAudio) {
return;
}
let skip = true;
let running = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const audioCtx = new (window.AudioContext || (<any> window).webkitAudioContext)();
const canvasCtx = this.streamAudio.nativeElement.getContext('2d');
const source = audioCtx.createMediaStreamSource(stream);
const analyser = audioCtx.createAnalyser();
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const width = this.streamAudio.nativeElement.width;
const height = this.streamAudio.nativeElement.height;
const drawAudio = (): void => {
if (!running) {
return;
}
// Update the draw every animation frame.
requestAnimationFrame(drawAudio);
// Skip half of the frames to improve performance, shouldn't affect the smoothness.
skip = !skip;
if (skip) {
return;
}
const sliceWidth = width / bufferLength;
let x = 0;
analyser.getByteTimeDomainData(dataArray);
canvasCtx.fillStyle = 'rgb(200, 200, 200)';
canvasCtx.fillRect(0, 0, width, height);
canvasCtx.lineWidth = 1;
canvasCtx.strokeStyle = 'rgb(0, 0, 0)';
canvasCtx.beginPath();
for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * height / 2;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(width, height / 2);
canvasCtx.stroke();
};
analyser.fftSize = 2048;
source.connect(analyser);
this.audioDrawer = {
start: (): void => {
if (running) {
return;
}
running = true;
drawAudio();
},
stop: (): void => {
running = false;
},
};
}
/** /**
* Main action clicked: record or stop recording. * Main action clicked: record or stop recording.
*/ */
@ -369,15 +200,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
this.isCapturing = true; this.isCapturing = true;
this.resetChrono = false; this.resetChrono = false;
if (this.isCordovaAudioCapture) { this.mediaRecorder?.start();
this.mediaFile?.startRecord();
if (this.previewMedia) {
this.previewMedia.src = '';
}
} else {
this.mediaRecorder?.start();
}
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
} else { } else {
if (!this.imgCanvas) { if (!this.imgCanvas) {
@ -419,11 +242,6 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
// Send a "cancelled" error like the Cordova plugin does. // Send a "cancelled" error like the Cordova plugin does.
this.dismissWithCanceledError('Canceled.', 'Camera cancelled'); this.dismissWithCanceledError('Canceled.', 'Camera cancelled');
if (this.isCordovaAudioCapture && this.filePath) {
// Delete the tmp file.
CoreFile.removeFile(this.filePath);
}
} }
/** /**
@ -432,11 +250,6 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
async discard(): Promise<void> { async discard(): Promise<void> {
this.previewMedia?.pause(); this.previewMedia?.pause();
this.streamVideo?.nativeElement.play(); this.streamVideo?.nativeElement.play();
this.audioDrawer?.start();
if (this.isCordovaAudioCapture) {
await this.resetCordovaMediaCapture();
}
this.hasCaptured = false; this.hasCaptured = false;
this.isCapturing = false; this.isCapturing = false;
@ -492,30 +305,23 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
return; return;
} }
if (!this.mediaBlob && !this.isCordovaAudioCapture) { if (!this.mediaBlob) {
// Shouldn't happen. // Shouldn't happen.
CoreDomUtils.showErrorModal('Please capture the media first.'); CoreDomUtils.showErrorModal('Please capture the media first.');
return; return;
} }
let fileEntry = this.fileEntry;
const loadingModal = await CoreDomUtils.showModalLoading(); const loadingModal = await CoreDomUtils.showModalLoading();
try { try {
if (!this.isCordovaAudioCapture) { // Capturing in browser. Write the blob in a file.
// Capturing in browser. Write the blob in a file. if (!this.mediaBlob) {
if (!this.mediaBlob) { // Shouldn't happen.
// Shouldn't happen. throw new Error('Please capture the media first.');
throw new Error('Please capture the media first.');
}
fileEntry = await CoreFile.writeFile(this.getFilePath(), this.mediaBlob);
} }
if (!fileEntry) { const fileEntry = await CoreFile.writeFile(this.getFilePath(), this.mediaBlob);
throw new CoreError('File not found.');
}
if (this.isImage && !this.isCaptureImage) { if (this.isImage && !this.isCaptureImage) {
this.dismissWithData(fileEntry.toURL()); this.dismissWithData(fileEntry.toURL());
@ -560,30 +366,20 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
} }
/** /**
* Stop capturing. Only for video and audio. * Stop capturing. Only for video.
*/ */
stopCapturing(): void { stopCapturing(): void {
this.isCapturing = false; this.isCapturing = false;
this.hasCaptured = true; this.hasCaptured = true;
if (this.isCordovaAudioCapture) { this.streamVideo && this.streamVideo.nativeElement.pause();
this.mediaFile?.stopRecord(); this.mediaRecorder && this.mediaRecorder.stop();
if (this.previewMedia && this.fileEntry) {
this.previewMedia.src = CoreFile.convertFileSrc(this.fileEntry.toURL());
}
} else {
this.streamVideo && this.streamVideo.nativeElement.pause();
this.audioDrawer && this.audioDrawer.stop();
this.mediaRecorder && this.mediaRecorder.stop();
}
} }
/** /**
* Page destroyed. * Page destroyed.
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.mediaFile?.release();
if (this.localMediaStream) { if (this.localMediaStream) {
const tracks = this.localMediaStream.getTracks(); const tracks = this.localMediaStream.getTracks();
tracks.forEach((track) => { tracks.forEach((track) => {
@ -592,14 +388,13 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
} }
this.streamVideo?.nativeElement.pause(); this.streamVideo?.nativeElement.pause();
this.previewMedia?.pause(); this.previewMedia?.pause();
this.audioDrawer?.stop();
delete this.mediaBlob; delete this.mediaBlob;
} }
} }
export type CaptureMediaComponentInputs = { export type CaptureMediaComponentInputs = {
type: 'audio' | 'video' | 'image' | 'captureimage'; type: 'video' | 'image' | 'captureimage';
maxTime?: number; // Max time to capture. maxTime?: number; // Max time to capture.
facingMode?: string; // Camera facing mode. facingMode?: string; // Camera facing mode.
mimetype?: string; mimetype?: string;

View File

@ -14,23 +14,18 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CameraOptions } from '@ionic-native/camera/ngx'; import { CameraOptions } from '@ionic-native/camera/ngx';
import { CaptureAudioOptions, CaptureImageOptions, CaptureVideoOptions, MediaFile } from '@ionic-native/media-capture/ngx'; import { CaptureImageOptions, CaptureVideoOptions, MediaFile } from '@ionic-native/media-capture/ngx';
import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { makeSingleton, ModalController } from '@singletons'; import { makeSingleton, ModalController } from '@singletons';
import { CaptureMediaComponentInputs, CoreEmulatorCaptureMediaComponent } from '../components/capture-media/capture-media'; import { CaptureMediaComponentInputs, CoreEmulatorCaptureMediaComponent } from '../components/capture-media/capture-media';
/** /**
* Helper service with some features to capture media (image, audio, video). * Helper service with some features to capture media (image, video).
*/ */
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class CoreEmulatorCaptureHelperProvider { export class CoreEmulatorCaptureHelperProvider {
protected possibleAudioMimeTypes = {
'audio/webm': 'weba',
'audio/ogg': 'ogg',
};
protected possibleVideoMimeTypes = { protected possibleVideoMimeTypes = {
'video/webm;codecs=vp9': 'webm', 'video/webm;codecs=vp9': 'webm',
'video/webm;codecs=vp8': 'webm', 'video/webm;codecs=vp8': 'webm',
@ -38,22 +33,20 @@ export class CoreEmulatorCaptureHelperProvider {
}; };
videoMimeType?: string; videoMimeType?: string;
audioMimeType?: string;
/** /**
* Capture media (image, audio, video). * Capture media (image, video).
* *
* @param type Type of media: image, audio, video. * @param type Type of media: image, video.
* @param options Optional options. * @param options Optional options.
* @returns Promise resolved when captured, rejected if error. * @returns Promise resolved when captured, rejected if error.
*/ */
captureMedia(type: 'image', options?: MockCameraOptions): Promise<string>; captureMedia(type: 'image', options?: MockCameraOptions): Promise<string>;
captureMedia(type: 'captureimage', options?: MockCaptureImageOptions): Promise<MediaFile[]>; captureMedia(type: 'captureimage', options?: MockCaptureImageOptions): Promise<MediaFile[]>;
captureMedia(type: 'audio', options?: MockCaptureAudioOptions): Promise<MediaFile[]>;
captureMedia(type: 'video', options?: MockCaptureVideoOptions): Promise<MediaFile[]>; captureMedia(type: 'video', options?: MockCaptureVideoOptions): Promise<MediaFile[]>;
async captureMedia( async captureMedia(
type: 'image' | 'captureimage' | 'audio' | 'video', type: 'image' | 'captureimage' | 'video',
options?: MockCameraOptions | MockCaptureImageOptions | MockCaptureAudioOptions | MockCaptureVideoOptions, options?: MockCameraOptions | MockCaptureImageOptions | MockCaptureVideoOptions,
): Promise<MediaFile[] | string> { ): Promise<MediaFile[] | string> {
options = options || {}; options = options || {};
@ -67,10 +60,6 @@ export class CoreEmulatorCaptureHelperProvider {
const mimeAndExt = this.getMimeTypeAndExtension(type, options.mimetypes); const mimeAndExt = this.getMimeTypeAndExtension(type, options.mimetypes);
params.mimetype = mimeAndExt.mimetype; params.mimetype = mimeAndExt.mimetype;
params.extension = mimeAndExt.extension; params.extension = mimeAndExt.extension;
} else if (type == 'audio') {
const mimeAndExt = this.getMimeTypeAndExtension(type, options.mimetypes);
params.mimetype = mimeAndExt.mimetype;
params.extension = mimeAndExt.extension;
} else if (type == 'image') { } else if (type == 'image') {
if ('sourceType' in options && options.sourceType !== undefined && options.sourceType != 1) { if ('sourceType' in options && options.sourceType !== undefined && options.sourceType != 1) {
return Promise.reject('This source type is not supported in browser.'); return Promise.reject('This source type is not supported in browser.');
@ -121,7 +110,7 @@ export class CoreEmulatorCaptureHelperProvider {
/** /**
* Get the mimetype and extension to capture media. * Get the mimetype and extension to capture media.
* *
* @param type Type of media: image, audio, video. * @param type Type of media: image, video.
* @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported.
* @returns An object with mimetype and extension to use. * @returns An object with mimetype and extension to use.
*/ */
@ -148,10 +137,6 @@ export class CoreEmulatorCaptureHelperProvider {
// No mimetype found, use default extension. // No mimetype found, use default extension.
result.mimetype = this.videoMimeType; result.mimetype = this.videoMimeType;
result.extension = this.possibleVideoMimeTypes[result.mimetype!]; result.extension = this.possibleVideoMimeTypes[result.mimetype!];
} else if (type == 'audio') {
// No mimetype found, use default extension.
result.mimetype = this.audioMimeType;
result.extension = this.possibleAudioMimeTypes[result.mimetype!];
} }
return result; return result;
@ -170,20 +155,12 @@ export class CoreEmulatorCaptureHelperProvider {
* Initialize the mimetypes to use when capturing. * Initialize the mimetypes to use when capturing.
*/ */
protected initMimeTypes(): void { protected initMimeTypes(): void {
// Determine video and audio mimetype to use.
for (const mimeType in this.possibleVideoMimeTypes) { for (const mimeType in this.possibleVideoMimeTypes) {
if (window.MediaRecorder.isTypeSupported(mimeType)) { if (window.MediaRecorder.isTypeSupported(mimeType)) {
this.videoMimeType = mimeType; this.videoMimeType = mimeType;
break; break;
} }
} }
for (const mimeType in this.possibleAudioMimeTypes) {
if (window.MediaRecorder.isTypeSupported(mimeType)) {
this.audioMimeType = mimeType;
break;
}
}
} }
/** /**
@ -209,9 +186,6 @@ export interface MockCameraOptions extends CameraOptions {
export interface MockCaptureImageOptions extends CaptureImageOptions { export interface MockCaptureImageOptions extends CaptureImageOptions {
mimetypes?: string[]; // Allowed mimetypes. mimetypes?: string[]; // Allowed mimetypes.
} }
export interface MockCaptureAudioOptions extends CaptureAudioOptions {
mimetypes?: string[]; // Allowed mimetypes.
}
export interface MockCaptureVideoOptions extends CaptureVideoOptions { export interface MockCaptureVideoOptions extends CaptureVideoOptions {
mimetypes?: string[]; // Allowed mimetypes. mimetypes?: string[]; // Allowed mimetypes.
} }

View File

@ -15,7 +15,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
MediaCapture, MediaCapture,
CaptureAudioOptions,
CaptureImageOptions, CaptureImageOptions,
CaptureVideoOptions, CaptureVideoOptions,
MediaFile, MediaFile,
@ -29,16 +28,6 @@ import { CoreEmulatorCaptureHelper } from './capture-helper';
@Injectable() @Injectable()
export class MediaCaptureMock extends MediaCapture { export class MediaCaptureMock extends MediaCapture {
/**
* Start the audio recorder application and return information about captured audio clip files.
*
* @param options Options.
* @returns Promise resolved when captured.
*/
captureAudio(options: CaptureAudioOptions): Promise<MediaFile[]> {
return CoreEmulatorCaptureHelper.captureMedia('audio', options);
}
/** /**
* Start the camera application and return information about captured image files. * Start the camera application and return information about captured image files.
* *

View File

@ -29,7 +29,6 @@ import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { WebView } from '@ionic-native/ionic-webview/ngx'; import { WebView } from '@ionic-native/ionic-webview/ngx';
import { Keyboard } from '@ionic-native/keyboard/ngx'; import { Keyboard } from '@ionic-native/keyboard/ngx';
import { LocalNotifications } from '@ionic-native/local-notifications/ngx'; import { LocalNotifications } from '@ionic-native/local-notifications/ngx';
import { Media } from '@ionic-native/media/ngx';
import { MediaCapture } from '@ionic-native/media-capture/ngx'; import { MediaCapture } from '@ionic-native/media-capture/ngx';
import { Push } from '@ionic-native/push/ngx'; import { Push } from '@ionic-native/push/ngx';
import { QRScanner } from '@ionic-native/qr-scanner/ngx'; import { QRScanner } from '@ionic-native/qr-scanner/ngx';
@ -54,7 +53,6 @@ export const CORE_NATIVE_SERVICES = [
InAppBrowser, InAppBrowser,
Keyboard, Keyboard,
LocalNotifications, LocalNotifications,
Media,
MediaCapture, MediaCapture,
Push, Push,
QRScanner, QRScanner,
@ -82,7 +80,6 @@ export const CORE_NATIVE_SERVICES = [
InAppBrowser, InAppBrowser,
Keyboard, Keyboard,
LocalNotifications, LocalNotifications,
Media,
MediaCapture, MediaCapture,
Push, Push,
QRScanner, QRScanner,

View File

@ -52,7 +52,6 @@ import { InAppBrowser as InAppBrowserService } from '@ionic-native/in-app-browse
import { WebView as WebViewService } from '@ionic-native/ionic-webview/ngx'; import { WebView as WebViewService } from '@ionic-native/ionic-webview/ngx';
import { Keyboard as KeyboardService } from '@ionic-native/keyboard/ngx'; import { Keyboard as KeyboardService } from '@ionic-native/keyboard/ngx';
import { LocalNotifications as LocalNotificationsService } from '@ionic-native/local-notifications/ngx'; import { LocalNotifications as LocalNotificationsService } from '@ionic-native/local-notifications/ngx';
import { Media as MediaService } from '@ionic-native/media/ngx';
import { MediaCapture as MediaCaptureService } from '@ionic-native/media-capture/ngx'; import { MediaCapture as MediaCaptureService } from '@ionic-native/media-capture/ngx';
import { Push as PushService } from '@ionic-native/push/ngx'; import { Push as PushService } from '@ionic-native/push/ngx';
import { QRScanner as QRScannerService } from '@ionic-native/qr-scanner/ngx'; import { QRScanner as QRScannerService } from '@ionic-native/qr-scanner/ngx';
@ -184,7 +183,6 @@ export const Geolocation = makeSingleton(GeolocationService);
export const InAppBrowser = makeSingleton(InAppBrowserService); export const InAppBrowser = makeSingleton(InAppBrowserService);
export const Keyboard = makeSingleton(KeyboardService); export const Keyboard = makeSingleton(KeyboardService);
export const LocalNotifications = makeSingleton(LocalNotificationsService); export const LocalNotifications = makeSingleton(LocalNotificationsService);
export const Media = makeSingleton(MediaService);
export const MediaCapture = makeSingleton(MediaCaptureService); export const MediaCapture = makeSingleton(MediaCaptureService);
export const NativeHttp = makeSingleton(HTTP); export const NativeHttp = makeSingleton(HTTP);
export const Push = makeSingleton(PushService); export const Push = makeSingleton(PushService);