И вот, после трех месяцев мы, наконец-то, оказались на финишной прямой. Последний бой – он трудный самый.

В предыдущих частях повествования стал проглядывать интерфейс и мы получили возможность управлять приложением. Пусть чуть-чуть, но это, все же, лучше чем ничего.

Что осталось? Вот основные пункты:

Так как времени мало, а материала много, то именно по этим пунктам и пройдемся.

Для добавления файлов естественно понадобится кнопка. Увидеть ее можно, запустив исходники, которые можно получить со ссылки в конце статьи (она выглядит так же, как и кнопка сворачивания окна, только с тремя точками). А вот работу этой кнопки необходимо рассмотреть более подробно.

Листинг 1
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 файлы и папки, поэтому мы создаем следующий класс:

Листинг 2
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()).

pic1

Вот теперь мы готовы к решительному шагу запуска медиафайла в проигрывателе. Будем это делать двойным кликом на файле в плейлисте, как это принято в больших плеерах. Для этого достаточно добавить пару строчек в код listviewPlaylist:

Листинг 3
onMouseClicked: function(evt: MouseEvent): Void {
  if ((evt.clickCount == 2) and (evt.button == MouseButton.PRIMARY))
  then {
    playMedia(fileList[listviewPlaylist.selectedIndex]);
  }
}

Здесь вызывается функция playMedia(), которую рассмотрим чуть позже, так как она требует небольшой подготовки с нашей стороны. Добавить работоспособности интерфейсу можно назначив onMouseClicked для кнопок перемотки:

Листинг 4
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:

Листинг 5
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 в центре нашей сцены, который мы считаем экраном:

Листинг 6
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 в прошлой статье). Листинг обработчика событий для этого компонента необходим, так что вот и он:

Листинг 7
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?

Листинг 8
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 дает нам возможность изменения параметров нашего приложения в процессе выполнения приложения. Стоит рассмотреть его подробнее. Ключевые переменные этого типа следующие:

В нашем случае repeatCount получил значение Timeline.INDEFINITE. Это значит, что анимация будет повторяться бесконечное количество раз.

В переменной keyFrames мы используем только один компонент. KeyFrame – своеобразная единица анимации, которая выполняется необходимое количество раз с интервалом time. Action – функция, которая выполняется каждый период времени. Наша функция перемещает слайдер перемотки по линии и нельзя допустить, чтоб он вышел за пределы этой полосы перемотки, для этих целей и написана проверка на границы. Длина полосы перемотки – 350 пикселей.

TimeLine имеет четыре функции:

После всех этих строк теории становится немного понятнее практика. В Листинге 7 событие onMousePressed запоминает начальную позицию ползунка и останавливает анимацию. При перетаскивании вызывается onMouseDragged, в котором мы просто перемещаем ползунок по полосе перемотки. Опять же, постоянно следим за тем, что бы Rectangle не выходил за границы. И, наконец, при отпускании кнопки мыши вызываем resumeMedia() – функцию, которая продолжает выполнение timelineProgress и включает наш трек в выбранном месте. Делается это так:

Листинг 9
function resumeMedia(position : Number){
  mediaplayer.mediaPlayer.currentTime = Duration.valueOf(position * mediaDurStep);
  timelineProgress.play();
}

Здесь position – положение слайдера на полосе перемотки. Duration.valueOf() переводит некоторое значение в тип данных Duration – время. В нашем контексте мы устанавливаем текущим временем внутри песни положение ползунка, умноженное на шаг выполнения TimeLine. После чего возобновляем выполнение timelineProgress().

Осталась малость – разобраться с нашим Неуловимым Джо – функцией playMedia(). Вот ее листинг:

Листинг 10
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);
}

Я не продемонстрировал эту функцию в самом начале, так как тогда она оставила бы больше вопросов, чем ответов и только занимала бы голову. Сейчас же все становится намного яснее. Эта функция вызывается при начале проигрывания песни. В качестве параметра в нее передается строка с путем и именем медиафайла. Далее по шагам:

Ну, вот и все. Теперь наша программа готова к выполнению своих непосредственных задач. Компилируем код и проверяем.

pic2

JavaFX предоставляет огромные возможности всем разработчикам. Эта технология смогла совместить в себе всю мощь Java и простоту написания интерфейса для любых программ. Всего за несколько минут можно создать вполне функциональный код, а если потратить еще немного, то его будет не стыдно даже напечатать в журнале. Что я и продемонстрировал на своем примере.

Ну а исходники вы как всегда найдете по этой ссылке.