Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке приложений для мобильной платформы Sailfish OS. В этот раз речь пойдет о разработке приложения для отсчета дней до события (например, до выпускного, Нового Года или Дня Рождения), выбранного пользователем. К каждому событию пользователь может добавить описание и записать аудиозаметку. Начнем статью с описания пользовательского интерфейса, а затем разберем работу приложения.
Пользовательский интерфейс приложения
Главный экран приложения представляет собой список событий пользователя. Каждый элемент списка содержит название и дату события, по нажатию на него открывается экран с информацией о событии. Элементы списка также имеют контекстное меню, позволяющее удалить событие.
Главный экран приложения также содержит вытягиваемое меню с одним пунктом, открывающим диалог для добавления новой заметки.
Диалог для добавления/редактирования события содержит текстовые поля для ввода названия и описания, кнопки для установки даты и времени события и компонент для записи и проигрывания аудиозаметки, который мы подробно рассмотрим в данной статье.
Количество оставшихся дней отображается на «обложке» приложения, а при наступлении даты и времени события пользователю приходит уведомление.
Работа с аудио
Заранее заметим, что для того, чтобы в эмуляторе можно было записывать и воспроизводить звуковые файлы, в том числе через QML, необходимо, чтобы на нем были установлены пакеты qt5-qtmultimedia-plugin-mediaservice-gstmediacapture и qt5-qtmultimedia-plugin-mediaservice-gstmediaplayer.
Самый простой способ сделать это — добавить их в раздел Requires файла .yaml, чтобы они устанавливались вместе с приложением:
Requires:
# Following packages are needed to playback and record audio on the Sailfish OS Emulator.
- qt5-qtmultimedia-plugin-mediaservice-gstmediaplayer
- qt5-qtmultimedia-plugin-mediaservice-gstmediacapture
Кроме того, в последних версиях эмулятора уровень громкости выставлен на минимум по умолчанию, поэтому, чтобы услышать воспроизводимые звуковые файлы, необходимо выполнить на эмуляторе следующие команды:
pactl set-sink-mute sink.emulator 0
pactl set-sink-volume sink.emulator 40000
Выполнить их можно, подключившись к эмулятору по ssh:
ssh nemo@localhost -p 2223 -i <Путь к SailfishOS SDK>/vmshare/ssh/private_keys/SailfishOS_Emulator/nemo
Сам функционал записи и прослушивания голосовой заметки мы вынесли в отдельный встраиваемый в страницу QML-компонент AudioPlayer.
Описание работы компонента AudioPlayer
Работа с аудио происходит со страниц просмотра и редактирования события. На странице редактирования компонент отображается с кнопкой записи, у которой два состояния: начать и остановить запись (стандартная и красная кнопка record, соответственно), кнопкой проигрывания записи (play), которая меняется на кнопку stop в процессе прослушивания, и полосой загрузки.
Для начала записи была выбрана иконка icon-m-dot, а для удобства пользователя была создана собственная кнопка, останавливающая запись (red-dot). Для начала и остановки прослушивания были выбраны кнопки icon-m-media и icon-m-tabs, соответственно. Доступные к использованию иконки можно посмотреть на официальном сайте Jolla или в документации, поставляемой с SDK.
Запись начинается при нажатии на кнопку и заканчивается при повторном нажатии на кнопку или по истечении установленного времени (по умолчанию 120 секунд), после записи становится доступна кнопка для прослушивания, также заметку можно перезаписывать неограниченное количество раз.
На странице просмотра в компоненте AudioPlayer недоступна возможность записи, работа всех остальных элементов не изменяется.
Для хранения наших голосовых заметок используется папка "DayTimer", расположенная в каталоге "/home/nemo/", который является системным в Sailfish OS. Однако, пользователи могут прослушивать и удалять свои аудиозаметки вне приложения. Приложение проверяет доступность созданных ранее файлов и в случае их отсутствия блокирует функцию прослушивания голосовой заметки для данного события.
Дополнительные настройки проекта
Для записи голосовой заметки используется класс QAudioRecorder, а для прослушивания QML-тип MediaPlayer из библиотеки Qt Multimedia. Для работы с библиотекой нужно прописать в pro-файле: QT += multimedia
. Также не лишним будет указать зависимость в yaml-файле (раздел Requires): qt5-qtmultimedia.
Дополнительные параметры при работе с аудио
Стоит отметить, что при разработке под Sailfish OS доступен и весь функционал настроек аудио, предоставляемый классом QAudioEncoderSettings, который содержит в себе множество полезных методов, например, метод bitRate(), возвращающий значение битрейта звукового файла. При желании мы можем исправить имеющийся битрейт на нужный нам, используя метод установки битрейта по значению setBitRate().
Узнать все доступные к использованию кодеки можно с помощью метода supportedAudioCodecs() класса QAudioEncoderSettingsControl, в котором также есть метод codecDescription(), дающий описание каждого кодека в отдельности. Сменить аудиокодек можно методом setCodec() того же класса.
Кроме этого в классе можно найти методы для настройки качества, кодировки и многого другого.
Запись аудио в файл
Разберем, как происходит работа с базой звуковых файлов. При открытии страницы добавления события создается временный файл, в котором сохраняются создаваемые записи. Дальнейшие действия зависят от решения пользователя, так как страница является диалоговым окном. Файл удаляется, если пользователь отменил изменения, либо его имя записывается (или перезаписывается — при перезаписи голосовой заметки) в базу данных приложения, если пользователь подтвердил изменения.
Так как не существует стандартного QML-компонента, осуществляющего запись звука в файл, то мы создали и зарегистрировали собственный QML-компонент VoiceRecorder, использующий методы класса QAudioRecorder, как было описано в одной из предыдущих статей. Помимо подробно разобранных ниже методов, класс содержит свойство audioInput, которое хранит в себе актуальное название аудиофайла (изменить его можно методом setAudioInput()). Также используются свойства и некоторые методы класса QMediaRecorder.
Далее перейдем к разбору используемых нами методов класса QAudioRecorder. Этот класс содержит объект audioRecorder, с помощью которого осуществляется работа методов record() и stop(), отвечающих за запись голосовой заметки. Также в VoiceRecorder определено свойство isRecording, по которому определяется, идет ли запись в данный момент или нет. Сигнал recordingChanged оповещает об изменении значения этого свойства.
class VoiceRecorder : public QObject {
Q_OBJECT
Q_PROPERTY(bool isRecording READ isRecording NOTIFY recordingChanged)
public:
explicit VoiceRecorder(QObject *parent = nullptr);
bool isRecording() const;
// ...
signals:
void recordingChanged();
private:
QAudioRecorder *m_audioRecorder;
bool m_recording;
};
Класс обладает методами для записи (record()) и остановки (stop()), которые используют состояния из класса QMediaRecorder.
void VoiceRecorder::record(const QString &name) {
audioRecorder->setOutputLocation(QUrl(name));
if (audioRecorder->state() == QMediaRecorder::StoppedState) {
audioRecorder->record();
b_recording = true;
emit recordingChanged();
}
}
void VoiceRecorder::stop () {
if (audioRecorder->state() == QMediaRecorder::RecordingState) {
audioRecorder->stop();
b_recording = false;
emit recordingChanged();
}
}
Для перезаписи голосовой заметки или удаления события в классе используются методы проверки наличия аудио (isExistVoice()) и его удаления (removeVoice()), использующие стандартные методы класса QFile (exists() и remove(), соответственно).
В методе createVoiceName() с помощью метода mkpath() класса QDir создается папка, в которой будут храниться голосовые заметки. Для создания названий аудиофайлов используется класс QUuid, поэтому названия будут уникальными.
QString VoiceRecorder::createVoiceName() {
QDir path = QDir::home();
const QString storagePath = QStringLiteral("%1/DayTimer").arg(QDir::homePath());
if (!path.exists(storagePath)) {
path.mkpath(storagePath);
}
QUuid u = QUuid::createUuid();
QString str = QStringLiteral("%1/DTVoice%2.wav").arg(storagePath).arg(u.toString());
return str;
}
Работа с аудио с QML-страницы
Подключим компонент AudioPlayer на страницах просмотра и редактирования и перейдем к разбору его QML-компонентов, для работы которых и был создан класс VoiceRecorder.
AudioPlayer {
id: voice
property bool flagRecordButtonVisible: true
}
Вся работа по записи голосовой заметки привязана непосредственно к кнопке recordButton, код которой приведен ниже. Стоит отметить свойство visible: оно определяется исходя из того, на какой странице мы подключаем AudioPlayer, и устанавливает доступность кнопки для записи с помощью flagRecordButtonVisible.
IconButton {
id: recordButton
icon.source: "image://theme/icon-m-dot"
visible: flagRecordButtonVisible
onClicked: {
progressBar.value = 0;
isRecord = true;
}
// ...
}
В компоненте AudioPlayer мы создали объект типа MediaPlayer, а также задали свойство isRecord, которое используется в качестве флага для установки максимального значения ProgressBar. Если флаг принимает значение true, то максимальное значение ProgressBar приравнивается 120 секундам, иначе — длительности аудиофайла. Подробное описание работы ProgressBar будет приведено ниже.
Item {
id: audioPlayer
property bool isRecord: false
MediaPlayer {
id: player
}
}
Далее приведен код кнопки для прослушивания голосовой заметки. Метод player.source() в функции setSource() устанавливает путь к файлу, который нужно проиграть.
IconButton {
id: playButton
// ...
onClicked: {
module.setSource();
isRecord = false;
timer.stop();
progressBar.value = 0;
player.seek(player.duration);
recordButton.enabled = true;
playButton.icon.source = "image://theme/icon-m-media";
// ...
}
}
// ...
function setSource() {
if (voiceRecorder.isExistVoice(tempName)) {
player.source = tempName;
} else {
player.source = voiceName;
}
}
При реализации мы столкнулись с тем, что при первом прослушивании перезаписанной голосовой заметки воспроизводилась старая запись, т. к. стандартный метод player.stop() не выгружал из буфера старый файл. Для решения этой проблемы мы воспользовались методом seek(), устанавливающим позицию, с которой будет начинаться прослушивание.
Для визуализации записи и воспроизведения голосовой заметки используется ProgressBar, в котором идет отсчет времени с помощью компонента Timer. Максимальное значение ProgressBar устанавливает в зависимости от того, записывается или прослушивается голосовая заметка (используется свойство компонента isRecord). В первом случае это упомянутые ранее 120 секунд, а во втором — длина прослушиваемой голосовой заметки. Стоит отметить, что само значение задается в миллисекундах, деленных на интервал таймера. По достижении максимального значения ProgressBar заканчивается запись или прослушивание.
ProgressBar {
id: progressBar
// ...
maximumValue: {
if (player.duration > 0 && !isRecord) {
return player.duration / 100;
} else { return 1200 }
}
Timer {
id: timer
interval: 100
repeat: true
onTriggered: {
if (progressBar.value >= progressBar.maximumValue) {
// ...
timer.stop();
progressBar.value = 0;
// ...
} else {
progressBar.value += 1
}
}
}
Заключение
В результате было создано приложение, позволяющее создавать события с голосовыми заметками и отслеживать оставшееся количество дней до них. Исходники приложения DayTimer опубликованы на BitBucket, а само приложение доступно для скачивания в OpenRepos всем желающим.
Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.
Авторы: Светлана Юркина, Марина Кучма, Ирина Московкина
Автор: FRUCT