-
-
Save jdnichollsc/9d450ee36d9d75b201c01549f8f36fd7 to your computer and use it in GitHub Desktop.
import { Media, MediaObject } from '@ionic-native/media'; | |
import { File as NativeFile, FileEntry } from '@ionic-native/file'; | |
import { delay, getBlobFromFileEntry } from './utils' | |
// Detect platform device and select extension (Cordova platform, etc) | |
// Android | |
const AUDIO_EXTENSION = '.mp3' | |
// iOS | |
const AUDIO_EXTENSION = '.m4a' | |
export class PageDemo { | |
private audioFile: FileEntry | |
private audioMedia: MediaObject | |
// private audioMediaSrc: string | |
async onStartRecording() { | |
const path = NativeFile.dataDirectory; | |
const name = `record_${(new Date()).getTime() + AUDIO_EXTENSION}`; | |
this.audioFile = await NativeFile.createFile(path, name, true); | |
// Deprecated versions | |
// this.audioMediaSrc = this.audioFile.nativeURL.replace(/^file:[\/]+/, ''); | |
// this.audioMedia = Media.create(this.audioMediaSrc); | |
this.audioMedia = Media.create(this.audioFile.nativeURL); | |
this.audioMedia.startRecord(); | |
} | |
async onStopRecording() { | |
if (this.audioFile) { | |
try { | |
this.audioMedia.stopRecord(); | |
// It requires a little delay to get media file data | |
await delay(200); | |
const blob = await getBlobFromFileEntry(this.audioFile); | |
// SAVE AUDIO HERE! | |
// uploadFile(this.audioFile.name, blob) | |
} finally { | |
this.audioFile = null; | |
} | |
} | |
} | |
} |
import { FileEntry } from '@ionic-native/file'; | |
export const getBlobFromFileEntry = (fileEntry: FileEntry): Promise<Blob> => { | |
return new Promise((resolve, reject) => { | |
fileEntry.file((file) => { | |
const reader = new FileReader(); | |
reader.onloadend = function(e) { | |
try { | |
const { result: buffer } = e.target | |
const blob = new Blob( | |
[new Uint8Array(buffer as any)], | |
{ type: file.type } | |
); | |
resolve(blob); | |
} catch (error) { | |
reject(error); | |
} | |
}; | |
reader.onabort = (ev) => reject(ev); | |
reader.onerror = (ev) => reject(ev); | |
reader.readAsArrayBuffer(file); | |
}, reject) | |
}) | |
} | |
export const delay = (time: number) => | |
new Promise(resolve => setTimeout(() => resolve(), time)) |
@Mehulkb that function is exported here https://github.com/ionic-team/ionic-native/blob/master/src/%40ionic-native/plugins/file/index.ts#L993
Hey @jdnichollsc !
For me it says: "CreateDataFile failed", I am using NativeFile.dataDirectory as path but still get the error.
Is your code maybe only for Android, because I am developing for mainly iOS.
Appreciate if you could help me with this error! This is exactly what I need!
This example is inspired in my enterprise WebComponent with StencilJS/Cordova here, but I already have an example in Production working from iOS & Android using Ionic 4/5, the code is similar 🙂
I can help you with those kinds of issues if you are a sponsor, check the below repository if you have any concern https://github.com/proyecto26/sponsors
Best,
Juan
This example is inspired in my enterprise WebComponent with StencilJS/Cordova here, but I already have an example in Production working from iOS & Android using Ionic 4/5, the code is similar 🙂
I can help you with those kinds of issues if you are a sponsor, check the below repository if you have any concern https://github.com/proyecto26/sponsorsBest,
Juan
Alright, thanks! The only thing I am having problems with is to convert the mediaObject into a File that uploadable to firebase Storage. I think it is so strange how I feel like I am the only having wanting to achieve this nowadays. All the solutions are outdated but yours it seems like. What is the most simplest way, according to you, to convert MediaObject to Blob/File (I know you have already done that above, but I can't get it to work, although I have copied and pasted and made it suitable to my context).
Anything is appreciated!
Oh ok, I did that integration in another project with Ionic/Angular and Firebase too, something like this:
import { Component } from '@angular/core';
import { Platform } from '@ionic/angular';
import { Media, MediaObject } from '@ionic-native/media/ngx';
import { File as NativeFile, FileEntry } from '@ionic-native/file/ngx';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs';
import { MessageService } from '../api/message.service';
import { getBlobFromFileEntry, isImage, delay } from '../utils'
@Component({
selector: 'app-chat',
templateUrl: './chat.page.html',
styleUrls: ['./chat.page.scss'],
})
export class ChatPage {
audioExtension = this.platform.is('ios') ? '.m4a' : '.mp3';
audioFile: FileEntry;
audioMedia: MediaObject;
audioMediaSrc = '';
FILE_TYPES = {
TEXT: 'Text',
FILE: 'File',
AUDIO: 'Audio'
};
uploadPercent: Observable<number> = null;
constructor(
private readonly media: Media,
private readonly platform: Platform,
// firebase
private readonly db: AngularFirestore,
private readonly storage: AngularFireStorage,
private readonly file: NativeFile,
private readonly messageService: MessageService,
) { }
async uploadFile(
fileName: string,
data: Blob | File,
type: this.FileType.FILE | this.FileType.AUDIO,
) {
const filePath = `${this.connectionPath}/${fileName}`;
const fileRef = this.storage.ref(filePath);
// Firebase Storage
const task = this.storage.upload(filePath, data);
this.uploadPercent = task.percentageChanges();
await task.snapshotChanges().toPromise();
const fileUrl = await fileRef.getDownloadURL().toPromise();
const metadata = await fileRef.getMetadata().toPromise();
// Firebase Database for a chat or something like that
await this.messageService.addMessage({
type,
fileUrl,
fileMetadata: {
name: metadata.name,
size: metadata.size,
type: metadata.contentType,
path: metadata.fullPath,
lastModified: metadata.updated
}
}
);
this.uploadPercent = null;
}
async startRecordAudio() {
if (this.audioFile || !this.platform.is('cordova')) return
let path;
const name = `record_${(new Date().getTime()) + this.audioExtension}`;
if(this.platform.is("ios")){
path = this.file.tempDirectory;
this.audioFile = await this.file.createFile(path, name, true);
this.audioMedia = this.media.create(this.file.tempDirectory.replace(/^file:\/\//, '') + name);
} else {
path = this.file.dataDirectory;
this.audioFile = await this.file.createFile(path, name, true);
this.audioMediaSrc = this.audioFile.nativeURL.replace(/^file:[\/]+/, '');
this.audioMedia = this.media.create(this.audioFile.nativeURL);
}
this.audioMedia.startRecord();
}
async stopRecordAudio(detail: Blob) {
if (this.platform.is('cordova')) {
if (!this.audioFile) return
try {
this.audioMedia.stopRecord();
await delay(200);
const blob = await getBlobFromFileEntry(this.audioFile);
this.uploadFile(this.audioFile.name, blob, FileType.AUDIO);
} finally {
this.audioFile = null
}
} else {
const audioName = new Date().toTimeString() + '.webm';
this.uploadFile(audioName, detail, FileType.AUDIO);
}
}
}
Hope this can help you to have a better idea :)
Cheers!
Check that example and let me know :)
Check that example and let me know :)
You are the man! I'll try it right away!
Hey again!
So first of all, thanks for the demo. Now the "CreateDataFile failed" error disappears. Although, now there is something wrong with the getBlobFromFileEntry, which I copied from the code you posted first, do you use the same? I can show you my code flow, it is quite similar to yours!
// Start recording
async startRecord() {
var path: string = "";
if(this.platform.is("ios")){
path = this.file.tempDirectory;
this.audioFileName = new Date().getTime() + ".m4a";
this.audioFile = await this.file.createFile(path, this.audioFileName, true);
this.audioMedia = this.media.create(this.file.tempDirectory.replace(/^file:\/\//, '') + this.audioFileName);
} else {
path = this.file.dataDirectory;
this.audioFileName = new Date().getTime() + ".mp3";
this.audioFile = await this.file.createFile(path, this.audioFileName, true);
//this.audioMediaSrc = this.audioFile.nativeURL.replace(/^file:[\/]+/, '');
this.audioMedia = this.media.create(this.audioFile.nativeURL);
}
this.audioMedia.startRecord();
this.isRecordingAudio = true;
}
// Stop recording
async stopRecord() {
this.audioMedia.stopRecord();
this.isRecordingAudio = false;
console.log("Has stopped recording");
// Upload the new recording
await this.delay(200);
console.log("About to start converting to Blob");
const blob = await this.getBlobFromFileEntry(this.audioFile);
console.log("Finished Converting to Blob");
this.guideService.uploadFilesToFs([blob]).then(files => {
this.audioData = {name: files[0].name, url: files[0].url}
console.log("Finished uploading (url result): " + this.audioData.url);
});
}
getBlobFromFileEntry(fileEntry: FileEntry): Promise<Blob> {
return new Promise((resolve, reject) => {
console.log("About to resolve file from fiven FileEntry");
fileEntry.file((file) => {
console.log("Resolved file");
const reader = new FileReader();
reader.onloadend = function(e) {
console.log("Reader has loaded");
try {
const { result: buffer } = e.target
const blob = new Blob(
[new Uint8Array(buffer as any)],
{ type: file.type }
);
console.log(blob);
resolve(blob);
} catch (error) {
reject(error);
}
};
reader.onabort = (ev) => reject(ev);
reader.onerror = (ev) => reject(ev);
reader.readAsArrayBuffer(file);
}, reject)
});
}
The console.logs are there to debug, how far it came in the process. When running on iOS, it stops at console.log("Resolved file")
so there seems to be something wrong with reader.onloadend
? What do you think?
I solved managed to solve the errors and am now able to upload the audio file to firebase Storage.
Appreciate the help very much!
Now the code looks like:
// Audio recording management
// Start recording
async startRecord() {
var path: string = "";
if(this.platform.is("ios")){
path = this.file.tempDirectory;
this.audioFileName = new Date().getTime() + ".m4a";
this.audioFile = await this.file.createFile(path, this.audioFileName, true);
this.audioMedia = this.media.create(this.file.tempDirectory.replace(/^file:\/\//, '') + this.audioFileName);
} else {
path = this.file.dataDirectory;
this.audioFileName = new Date().getTime() + ".mp3";
this.audioFile = await this.file.createFile(path, this.audioFileName, true);
//this.audioMediaSrc = this.audioFile.nativeURL.replace(/^file:[\/]+/, '');
this.audioMedia = this.media.create(this.audioFile.nativeURL);
}
this.audioMedia.startRecord();
this.isRecordingAudio = true;
}
// Stop recording
async stopRecord() {
this.audioMedia.stopRecord();
this.isRecordingAudio = false;
// Upload the new recording
await this.delay(200);
this.audioFile.file(async (file) => {
const blob = await this.getBlobFromFile(file);
this.guideService.uploadFilesToFs([blob]).then(files => {
this.audioData = {name: files[0].name, url: files[0].url}
});
})
}
// Convert File to Blob
getBlobFromFile(file: File): Promise<Blob> {
return new Promise((resolve, reject) => {
let fileReader = this.mainService.getFileReader();
fileReader.onloadend = function() {
console.log(this.result);
var blob = new Blob([this.result], { type: file.type })
resolve(blob);
};
fileReader.onerror = function(err) {
reject(err);
};
fileReader.readAsArrayBuffer(file);
});
}
Because I am using capacitor I needed to fetch the FileReader instance in a different way. So this.mainService.getFileReader()
translates to:
getFileReader(): FileReader {
const fileReader = new FileReader();
const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
return zoneOriginalInstance || fileReader;
}
Thanks again! :)
Awesome, thanks for letting me know! <3
I am trying to implement the same solution but it's not working for, can you please help me out, below i am adding the code with the issue, Please let me, i will be grateful. Thank you
this.audioFile = await this.file.createFile(this.filePath, this.record.FileName, true);
this.mediaObject = this.media.create(this.audioFile.nativeURL);
async uploadFile() {
await delayTimer(200);
this.audioFile.file(async file => {
const blob = await this.getBlobFromFile(file) // fIle return of IFile not File
})
}
getBlobFromFile(file: File): Promise<Blob> {
return new Promise((resolve, reject) => {
let fileReader = this.utils.getFileReader();
fileReader.onloadend = function() {
console.log(this.result);
var blob = new Blob([this.result], { type: file.type })
resolve(blob);
};
fileReader.onerror = function(err) {
reject(err);
};
fileReader.readAsArrayBuffer(file); //(parameter) file: FileArgument of type 'File' is not assignable to parameter of type 'Blob'.
Type 'File' is missing the following properties from type 'Blob': size, type, arrayBuffer, slice, and 2
});
}
getFileReader(): FileReader {
const fileReader = new FileReader();
const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
return zoneOriginalInstance || fileReader;
}
That error is clear, type is wrong :)
Hi guys, great example, thanks, but I am running into a strange problem (although I am setting the file path manualy as above):
W/System.err: java.io.FileNotFoundException: /storage/emulated/0/tmprecording-1626497965957.3gp: open failed: EPERM (Operation not permitted)
Adding android:requestLegacyExternalStorage="true" solved the problem, but I would need to avoid that.
Any idea? Thanks!
Why are you using that format? 3gp
. Do you know if that codec is supported by that platform? Did you request the permissions for recording audio?
I am not using any specific format, it seems like it is the default. This is my entire code, I didn't manually request permissions, but the dialog for recording and file access does popup.
@Injectable()
export class MediaService {
private readonly logger = new Logger(this.constructor.name);
constructor(private readonly media: Media, private readonly file: File) {}
async recordAudio(): Promise<void> {
const path = this.file.dataDirectory;
const name = 'recording.mp3';
const file = await this.file.createFile(path, name, true);
console.log('file native url', file.nativeURL);
const mediaFile: MediaObject = this.media.create(file.nativeURL);
mediaFile.startRecord();
await wait(10000);
this.logger.debug('recording stopped');
mediaFile.stopRecord();
await wait(1000);
this.logger.debug('playing started');
mediaFile.play();
}
}
core.js:4197 ERROR Error: Uncaught (in promise): TypeError: ionic_native_file__WEBPACK_IMPORTED_MODULE_3_.File.createFile is not a function
TypeError: ionic_native_file__WEBPACK_IMPORTED_MODULE_3_.File.createFile is not a function
It is throwing this error. Do you have a complete working code of this snippet?