И вот, после трех месяцев мы, наконец-то, оказались на финишной прямой. Последний бой – он трудный самый.
В предыдущих частях повествования стал проглядывать интерфейс и мы получили возможность управлять приложением. Пусть чуть-чуть, но это, все же, лучше чем ничего.
Что осталось? Вот основные пункты:
-
Добавление файлов в список плейлиста.
-
Проигрывание этих файлов.
-
Перемотка по плейлисту.
-
Управление звуком.
-
Вывод названия трека на экран.
-
Перемотка внутри песни.
Так как времени мало, а материала много, то именно по этим пунктам и пройдемся.
Для добавления файлов естественно понадобится кнопка. Увидеть ее можно, запустив исходники, которые можно получить со ссылки в конце статьи (она выглядит так же, как и кнопка сворачивания окна, только с тремя точками). А вот работу этой кнопки необходимо рассмотреть более подробно.
onMouseClicked: function(evt: MouseEvent): Void {
if ((evt.clickCount == 1) and (evt.button == MouseButton.PRIMARY))
then {
var dialogOpen : JFileChooser = new JFileChooser();
dialogOpen.setMultiSelectionEnabled(true);
dialogOpen.setFileFilter(new Mp3FileFilter());
dialogOpen.setAcceptAllFileFilterUsed(false);
if (JFileChooser.APPROVE_OPTION == dialogOpen.showOpenDialog(null)) {
for (file in dialogOpen.getSelectedFiles()){
insert file.getAbsolutePath() into fileList;
}
}
}
}
С первой парой строк все ясно из предыдущих статей, а дальше начинается непонятное. Разъясняю. Сначала создается диалоговое окно выбора файлов JFileChooser. Это окно – одно из самых часто используемых в работе, так как почти в каждой программе требуется выбор файлов и их открытие. JFileChooser имеет множество опций, одной из которых является использование фильтров, то есть возможность отображать только те файлы, которые подходят условию. В нашем случае должны быть видны только mp3 файлы и папки, поэтому мы создаем следующий класс:
class Mp3FileFilter extends FileFilter{
override public function accept(file : File) : Boolean{
if (file.getName().endsWith(".mp3"))
then { return true; };
if (file.isDirectory())
then { return true; };
return false;
}
override public function getDescription() : String{
return "Mp3 files";
}
}
И указываем, что этот фильтр необходимо использовать dialogOpen.setFileFilter(new Mp3FileFilter()). Все, теперь при запуске окна JFileChooser будут видны только папки и файлы с расширением mp3.
В рамках статьи нам понадобятся свойства setMultiSelectionEnabled(true), позволяющее выбирать несколько файлов одновременно, и setAcceptAllFileFilterUsed(false), убирающее возможность просмотра всех файлов (только mp3). Далее программа проверяет, была ли нажата кнопка Open, и если это так, в переменную fileList (а она, напомню, связана со списком песен) добавляются абсолютные пути выбранных файлов (file.getAbsolutePath()).
Вот теперь мы готовы к решительному шагу запуска медиафайла в проигрывателе. Будем это делать двойным кликом на файле в плейлисте, как это принято в больших плеерах. Для этого достаточно добавить пару строчек в код listviewPlaylist:
onMouseClicked: function(evt: MouseEvent): Void {
if ((evt.clickCount == 2) and (evt.button == MouseButton.PRIMARY))
then {
playMedia(fileList[listviewPlaylist.selectedIndex]);
}
}
Здесь вызывается функция playMedia(), которую рассмотрим чуть позже, так как она требует небольшой подготовки с нашей стороны. Добавить работоспособности интерфейсу можно назначив onMouseClicked для кнопок перемотки:
onMouseClicked: function(evt: MouseEvent): Void {
if ((evt.clickCount == 1) and (evt.button == MouseButton.PRIMARY)) {
if (listviewPlaylist.selectedIndex != sizeof listviewPlaylist.items - 1)
then {
listviewPlaylist.selectNextRow()
} else {
listviewPlaylist.selectFirstRow();
}
playMedia(fileList[listviewPlaylist.selectedIndex]);
}
}
Здесь представлен код для кнопки Вперед и он прост как две копейки – при нажатии на кнопку проверяется условие на конец списка, в зависимости от результата выбирается следующий или первый трек и запускается все та же незнакомая функция playMedia(). Кнопка Назад аналогична по функциональности и ее код приводиться здесь не будет.
Следующим пунктом списка мы должны рассмотреть звук, а точнее его регулировку. Для этого на форму положим Swing-компонент Slider:
sliderVolume = Slider {
layoutX: bind display.layoutX + 10
layoutY: bind display.layoutY + 10
min: 0
max: 9
value: 0
vertical: true
height: 80
}
Переменные layoutX и layoutY – положение слайдера на форме; min и max – минимальное и максимальное значение слайдера; value – значение по умолчанию; vertical – показывает, что слайдер имеет вертикальное расположение; height – высота в пикселах. Теперь привязка к громкости mediaPlayer будет выглядеть как одна строчка в переменных этого компонента:
volume: bind (9 - sliderVolume.value) / 10
Вывод названия трека на экран оказывается ничуть не сложнее, чем предыдущая задача. Добавим компонент Text на небольшой Rectangle в центре нашей сцены, который мы считаем экраном:
trackName = Text {
content: bind track;
wrappingWidth: 130
layoutX: bind display.layoutX + 30
layoutY: bind (display.height - trackName.layoutBounds.height) / 2
+ display.layoutY + trackName.font.size / 2
fill: Color.AQUA
textAlignment: TextAlignment.CENTER
font: Font{ size:14 }
effect: Bloom{ }
}
Самая главная строчка здесь – content: bind track. С помощью нее выводимый текст привязывается к переменной track типа String.
Чтоб не загружать статью еще одним огромным листингом предлагаю открыть исходные коды программы и найти место, где мы выводим на сцену lineProgress и rectProgress. LineProgress – линия, по которой будет двигаться слайдер перемотки. Более интересен другой компонент – rectProgress, являющийся тем самым ползунком перемотки песни. Здесь мы используем переменную blocksMouse, которая запрещает передавать событие вниз по слоям интерфейса. Эту возможность необходимо использовать для того, что бы при перетаскивании ползунка не начинала двигаться наша форма (напомню, что мы добавили возможность drag’n’drop для Stage в прошлой статье). Листинг обработчика событий для этого компонента необходим, так что вот и он:
onMousePressed: function(e: MouseEvent):Void {
timelineProgress.pause();
beginPointX = e.sceneX - rectProgress.width / 2;
}
onMouseDragged: function(e: MouseEvent):Void {
var minPosition = lineProgress.layoutX - rectProgress.width / 2;
var maxPosition = lineProgress.layoutX - rectProgress.width / 2 + 350;
var xPosition = e.sceneX - rectProgress.width / 2;
if(xPosition <= minPosition)
then {
xPosition = minPosition
} else if (xPosition >= maxPosition)
then { xPosition = maxPosition }
rectProgress.layoutX = xPosition;
}
onMouseReleased: function(e: MouseEvent):Void {
resumeMedia(e.sceneX - rectProgress.width / 2
- lineProgress.layoutX - rectProgress.width / 2);
}
В этом коде придется разобраться достаточно подробно. Во-первых – что такое timelineProgress?
var timelineProgress = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames : [
KeyFrame {
time : bind Duration.valueOf(mediaDurStep);
canSkip : true
action: function () {
if (rectProgress.layoutX >= lineProgress.layoutX - rectProgress.width / 2 + 350)
then { } else { rectProgress.layoutX++ }
}
}
]
}
TimelineProgress имеет тип TimeLine – один из базовых типов JavaFX, с которыми придется работать постоянно. TimeLine дает нам возможность изменения параметров нашего приложения в процессе выполнения приложения. Стоит рассмотреть его подробнее. Ключевые переменные этого типа следующие:
-
autoReverse – разрешает возврат анимации. После завершения анимации объект возвращается на исходную позицию по той же траектории.
-
keyFrames – содержит последовательность KeyFrame.
-
repeatCount – количество повторов TimeLine.
-
time – указывается с какого времени начать проигрывание анимации.
В нашем случае repeatCount получил значение Timeline.INDEFINITE. Это значит, что анимация будет повторяться бесконечное количество раз.
В переменной keyFrames мы используем только один компонент. KeyFrame – своеобразная единица анимации, которая выполняется необходимое количество раз с интервалом time. Action – функция, которая выполняется каждый период времени. Наша функция перемещает слайдер перемотки по линии и нельзя допустить, чтоб он вышел за пределы этой полосы перемотки, для этих целей и написана проверка на границы. Длина полосы перемотки – 350 пикселей.
TimeLine имеет четыре функции:
-
pause() – ставит анимацию на паузу, то есть временно ее приостанавливает.
-
play() – снимает анимацию с паузы, либо запускает с указанной позиции.
-
playFromStart() – запускает TimeLine с самого начала.
-
stop() – останавливается анимация и возвращает все значения в начальные позиции.
После всех этих строк теории становится немного понятнее практика. В Листинге 7 событие onMousePressed запоминает начальную позицию ползунка и останавливает анимацию. При перетаскивании вызывается onMouseDragged, в котором мы просто перемещаем ползунок по полосе перемотки. Опять же, постоянно следим за тем, что бы Rectangle не выходил за границы. И, наконец, при отпускании кнопки мыши вызываем resumeMedia() – функцию, которая продолжает выполнение timelineProgress и включает наш трек в выбранном месте. Делается это так:
function resumeMedia(position : Number){
mediaplayer.mediaPlayer.currentTime = Duration.valueOf(position * mediaDurStep);
timelineProgress.play();
}
Здесь position – положение слайдера на полосе перемотки. Duration.valueOf() переводит некоторое значение в тип данных Duration – время. В нашем контексте мы устанавливаем текущим временем внутри песни положение ползунка, умноженное на шаг выполнения TimeLine. После чего возобновляем выполнение timelineProgress().
Осталась малость – разобраться с нашим Неуловимым Джо – функцией playMedia(). Вот ее листинг:
function playMedia(mediaSrc : String){
mediaplayer.mediaPlayer.stop();
rectProgress.layoutX = lineProgress.layoutX - rectProgress.width / 2;
playPause = true;
mediaplayer.mediaPlayer.media = Media{source: "file:/{mediaSrc.replace("\\","/").replace(" ","%20")}"};
mediaDurStep = mediaplayer.mediaPlayer.media.duration.toMillis() / 350;
mediaplayer.mediaPlayer.play();
timelineProgress.playFromStart();
track = mediaSrc.substring(mediaSrc.lastIndexOf("\\") + 1);
}
Я не продемонстрировал эту функцию в самом начале, так как тогда она оставила бы больше вопросов, чем ответов и только занимала бы голову. Сейчас же все становится намного яснее. Эта функция вызывается при начале проигрывания песни. В качестве параметра в нее передается строка с путем и именем медиафайла. Далее по шагам:
-
Останавливается проигрывание предыдущей песни.
-
Слайдер перемотки устанавливается в начальное положение.
-
Переменная playPause (на нее, например, завязано переключение кнопки Пауза/Проигрывание) устанавливается в true.
-
Присваиваем переменной media новый медиафайл (здесь производится форматирование и приведение к принятому в JavaFX имени файла).
-
Вычисляем mediaDurStep – количество секунд, которые должны пройти между перемещением ползунка на один пиксел, для чего берем продолжительность песни и делим на длину полосы перемотки.
-
Запускаем проигрывание файла.
-
Запускаем наш TimeLine с начала.
-
Присваиваем переменной track (к ней привязано название песни, выводимое на экран) значение, которое будет выглядеть примерно как “название-песни.mp3”.
Ну, вот и все. Теперь наша программа готова к выполнению своих непосредственных задач. Компилируем код и проверяем.
JavaFX предоставляет огромные возможности всем разработчикам. Эта технология смогла совместить в себе всю мощь Java и простоту написания интерфейса для любых программ. Всего за несколько минут можно создать вполне функциональный код, а если потратить еще немного, то его будет не стыдно даже напечатать в журнале. Что я и продемонстрировал на своем примере.
Ну а исходники вы как всегда найдете по этой ссылке.