Статьи

Запись и сохранение звука в приложениях Cordova

Ах, глядя на этот заголовок, вы, вероятно, думаете: «Конечно, это простой вопрос в Кордове, и, конечно же, это Раймонд, еще раз, блоггинг чего-то невероятно очевиден и прост, просто чтобы привлечь людей к его списку пожеланий Amazon ». Да, я тоже так думал. В эти выходные я начал работу над простым маленьким приложением Cordova для моего сына. Я думал, что это будет отличный пост в блоге тоже. Но работая над этим, я столкнулся с проблемой со звуковыми записями, которая на несколько часов сводила меня с ума от дерьма, поэтому я подумал, что лучше поделиться, чтобы другим тоже не приходилось биться головой об стену.

Поскольку эта проблема превратилась в королевский кластер — знаете, я решил подробно рассказать об этом здесь и рассказать о самом приложении позже на этой неделе. А пока рассмотрим этот вариант использования:

«Вы хотите, чтобы пользователь мог сделать аудиозапись. Позже пользователь может воспроизвести эту запись ».

Просто, правда? Поэтому я начал работать над формой, которая позволила бы пользователю сделать запись и назвать ее. Я хотел, чтобы они могли делать запись и воспроизводить ее, чтобы она им понравилась.

Снимок экрана симулятора iOS 27 июля 2015 г., 13:38.09

Нажатие кнопки «Запись» запускает вызов плагина Media Capture:

var captureError = function(e) {
console.log('captureError' ,e);
}

var captureSuccess = function(e) {
console.log('captureSuccess');console.dir(e);
$scope.sound.file = e[0].localURL;
$scope.sound.filePath = e[0].fullPath;
}

$scope.record = function() {
navigator.device.capture.captureAudio(
captureSuccess,captureError,{duration:10});
}

Это здесь использование Media Capture. Я храню URL и путь к файлу в $ scope, чтобы потом сохранить его. Функция Play также довольно тривиальна:

$scope.play = function() {
if(!$scope.sound.file) {
navigator.notification.alert("Record a sound first.", null, "Error");
return;
}
var media = new Media($scope.sound.file, function(e) {
media.release();
}, function(err) {
console.log("media err", err);
});
media.play();
}

Это хорошо работало в Android и iOS. Но я заметил что-то странное. Когда я посмотрел на объект Media Capture в captureSuccess, я увидел это в Android:

Shot1

Видишь ту часть, которую я там назвал? Стойкие. Прохладно. Это дает мне теплые пушистики. Однако в iOS я увидел это:

Shot2

Как видите, он хранится во временном местоположении, что не очень хорошо. К сожалению, в плагине Media Capture нет ничего, что можно изменить, чтобы изменить это поведение. Поэтому — ответ был ясен.

Файловая система!

onesimply

Так что в теории это должно быть легко. Сначала мы выбираем постоянное местоположение, которое охватывает как Android, так и iOS. Плагин File предоставляет такой псевдоним:cordova.file.dataDirectory

У нас есть папка, верно? Так что буквально все, что нам нужно сделать, это скопировать файл из одного места в другое. Копировать. Проклятие. Файл.

Но у нас есть проблема. Во-первых, мы должны дать файлу уникальное имя. Чтобы справиться с этим, я просто использовал время и существующее расширение.

var extension = $scope.sound.file.split(".").pop();
var filepart = Date.now();
var filename = filepart + "." + extension;
console.log("new filename is "+filename);

Затем я потратил около часа, пытаясь заставить команду копирования работать. Я начал с добавления многочисленных сообщений console.log со словом F в нем. Если вы не знаете, что это за слово, спросите своих подростков. Я думал, что с путем к файлу, я мог бы просто сделать это:

var file = new FileEntry(some damn path);

Но нет, это было бы слишком легко. Вы должны использовать в window.resolveLocalFileSYstemURLпервую очередь. А так как команде File copy требуется путь, вам придется сделать это дважды. Вот код, которым я закончил. Я удалил несколько сообщений console.log, которые могут обидеть некоторых читателей. Если вам интересно, я пошел путь за пределы просто произнеся F-слово.

window.resolveLocalFileSystemURL(loc, function(d) {
window.resolveLocalFileSystemURL($scope.sound.file, function(fe) {
fe.copyTo(d, filename, function(e) {
console.log('success inc opy');
console.dir(e);
$scope.sound.file = e.nativeURL;
$scope.sound.path = e.fullPath;

Sounds.save($scope.sound).then(function() {
$ionicHistory.nextViewOptions({
    disableBack: true
});
$state.go("home");
});

}, function(e) {
console.log('error in coipy');console.dir(e);
});
}, function(e) {
console.log("error in inner bullcrap");
console.dir(e);
});

В общем, все не так плохо. Несколько вложенных обратных вызовов, и почти половина моего кода там связана с моим приложением, а не с реальной проблемой, мне потребовалось некоторое время, чтобы добраться туда.

К счастью, все работало отлично.

да правильно

Итак, на данный момент я сохранил местоположение моего аудиофайла, чтобы я мог использовать его в API Media, но новое расположение больше не работает в плагине Media. Зачем? Я не волнуюсь, знаю. В прошлом году я писал в блоге о том, что когда вы используете плагин Media и относительный путь, вы должны сделать что-то напуганное на Android, в основном с префиксом вашего относительного URL. В моем случае я использую файл: // url, и я просто предполагал, что это будет работать.

И вот тут-то все стало круто — он отлично работал в Android, но не в iOS. Потому что — причины. Я рассказал об этом на канале Cordova Slack, и @purplecabbage упомянул, что относительный путь может работать. В моих инструментах разработчика я пытался вручную создать объект Media через консоль. Я обнаружил , что — учитывая имя файла — я мог бы получить доступ к файлу с помощью плагина мультимедиа, используя это в качестве корня: "../Library/NoCloud/".

Так что теперь мой игровой код выглядит так:

var playSound = function(x) {
getSounds().then(function(sounds) {
var sound = sounds[x];

/*
Ok, so on Android, we just work.
On iOS, we need to rewrite to ../Library/NoCloud/FILE
*/
var mediaUrl = sound.file;
if(device.platform.indexOf("iOS") >= 0) {
mediaUrl = "../Library/NoCloud/" + mediaUrl.split("/").pop();
}
var media = new Media(mediaUrl, function(e) {
media.release();
}, function(err) {
console.log("media err", err);
});
media.play();
});
}

Так что да. Вот и все. Мое приложение теперь работает. Я могу записывать и тестировать аудио и сохранять его в постоянной файловой системе. Как я уже сказал, я поделюсь реальным приложением позже на этой неделе.