суббота, 23 ноября 2013 г.

FMX.Media.TMediaPlayer или пишем свой mp3-плеер для Android'а #1

Сегодня предлагаю вам написать простой mp3-плеер для Android. Сразу следует оговориться, что в папке c примерами кода есть уже готовый плеер, написанный с использованием Android API. Мы же напишем плеер, используя только компонент TMediaPlayer.

Для начала составим список задач, которые плеер должен уметь выполнять:
1) Выводить список всех mp3 файлов на устройстве
2) Воспроизводить, ставить на паузу и останавливать выбранный трек
3) Переключать треки (предыдущий, следующий)
4) Отображать текущую позицию воспроизведения, перематывать трек
5) Автоматически переключать следующий трек
6) Выводить минимальное количество информации (название файла, время (всего/сколько проиграно))

UPD. Новая версия плеера тут: FMX.Media.TMediaPlayer или пишем свой mp3-плеер для Android'а #2

Upd (23.04.14). Проверено на Delphi XE6

Код приложения не является идеальным, доработанным и рекомендуемым к использованию. Приложение писалось для примера работы с компонентом FMX.Media.TMediaPlayer под Android.

Дизайн (расположение элементов плеера), приведу список компонентов и их свойств, а также скриншот структуры проекта:
Свойства всех элементов из блока "структура":




Код приложения без описания, т.к. в нем полно комментариев и он сам по себе простой. Если возникнут вопросы, то добро пожаловать в комментарии.

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TabControl,
  FMX.StdCtrls, FMX.ListView.Types, FMX.ListView, FMX.Layouts, FMX.Media;

type
  TForm1 = class(TForm)
    TabControl1: TTabControl;
    ToolBar1: TToolBar;
    TrackBar1: TTrackBar;
    TabItem1: TTabItem;
    TabItem2: TTabItem;
    ListView1: TListView;
    PlayPrev: TSpeedButton;
    PlayNext: TSpeedButton;
    Layout1: TLayout;
    Play: TSpeedButton;
    Pause: TSpeedButton;
    Stop: TSpeedButton;
    Label1: TLabel;
    Label2: TLabel;
    MediaPlayer1: TMediaPlayer;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure ListView1Change(Sender: TObject);
    procedure PlayClick(Sender: TObject);
    procedure PauseClick(Sender: TObject);
    procedure StopClick(Sender: TObject);
    procedure PlayPrevClick(Sender: TObject);
    procedure PlayNextClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure StopClear();
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  System.IOUtils;

var
  PauseTime: integer = 0; // Пауза

procedure TForm1.FormCreate(Sender: TObject);
var
  LList: TStringDynArray; // внутренний список файлов
  LItem: TListViewItem; // список треков в ListView1
  path: string; // папка в которой будем искать файлы
  i: Integer;
begin

{ Определяем основную папку для поиска }
  if TDirectory.Exists('/storage/') then
     path := '/storage/'
  else path := '/sdcard/';

{ Поиск mp3 файлов }
  try
    LList := TDirectory.GetFiles(path, '*.mp3', TSearchOption.soAllDirectories);
  except
    ShowMessage('Ошибка #1!');
    Exit;
  end;

{ Обновляем список треков }
  ListView1.BeginUpdate;
  try
    for i := 0 to Length(LList) - 1 do
    begin
      LItem := ListView1.Items.Add;
      // Убираем расширение файла, оставляем имя файла
      LItem.Text := TPath.GetFileNameWithoutExtension(LList[I]);
      // Помещаем полный путь в Detail
      LItem.Detail := LList[I];
    end;
  finally
    ListView1.EndUpdate;
  end;

  // Трекбар для перемотки
  TrackBar1.Enabled := False;
end;

{ Список треков: Выбор трека }
procedure TForm1.ListView1Change(Sender: TObject);
begin
  PlayClick(Self);
end;

{ Кнопка: Pause }
procedure TForm1.PauseClick(Sender: TObject);
begin
  if (MediaPlayer1.State = TMediaState.Playing) then
  begin
    // Запоминаем текущее время воспроизведения трека
    PauseTime := MediaPlayer1.Media.CurrentTime;
    // Останавливаем проигрывание
    MediaPlayer1.Stop;
  end;
end;

{ Кнопка: Play }
procedure TForm1.PlayClick(Sender: TObject);
begin
  if ListView1.ItemIndex <> -1 then
  begin
    // Выключаем таймер
    Timer1.Enabled := False;

    MediaPlayer1.Clear;
    MediaPlayer1.FileName := ListView1.Items[ListView1.ItemIndex].Detail;

    // Трекбар для перемотки
    TrackBar1.Enabled := True;
    TrackBar1.Max := MediaPlayer1.Duration;

    // Проверка на Паузу
    if (MediaPlayer1.State = TMediaState.Stopped) AND (PauseTime <> 0) then
    begin
      MediaPlayer1.CurrentTime := PauseTime;
      PauseTime := 0;
    end
    else begin
      // Текущая позиция трекбара
      TrackBar1.Value := 0;
    end;

    MediaPlayer1.Play;
    Label1.Text := 'Трек: ' + ListView1.Items[ListView1.ItemIndex].Text;

    // Включаем таймер
    Timer1.Enabled := True;
  end;
end;

{ Кнопка: Следующий трек }
procedure TForm1.PlayNextClick(Sender: TObject);
begin
  if (ListView1.ItemIndex <> -1) AND (ListView1.ItemIndex < ListView1.Items.Count-1) then
  begin
    ListView1.ItemIndex := ListView1.ItemIndex + 1;
    PauseTime := 0;
    PlayClick(Self);
  end
  else begin
    StopClear;
  end;
end;

{ Кнопка: Предыдущий трек }
procedure TForm1.PlayPrevClick(Sender: TObject);
begin
  if (ListView1.ItemIndex <> -1) AND (ListView1.ItemIndex > 0) then
  begin
    ListView1.ItemIndex := ListView1.ItemIndex - 1;
    PauseTime := 0;
    PlayClick(Self);
  end
  else begin
    StopClear;
  end;
end;

{ Вспомогательная процедура }
procedure TForm1.StopClear;
begin
    MediaPlayer1.Stop;
    Timer1.Enabled := False;
    ListView1.ItemIndex := -1;
    TrackBar1.Value := 0;
    TrackBar1.Enabled := False;
    PauseTime := 0;
end;

{ Кнопка: Stop }
procedure TForm1.StopClick(Sender: TObject);
begin
  if (MediaPlayer1.State = TMediaState.Playing) then
  begin
    StopClear;
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  CurrentMin, CurrentSec, DurationMin, DurationSec: integer;
begin
  // Меняем позицию трекбара на текущую позицию воспроизведения
  if (MediaPlayer1.State = TMediaState.Playing) then
  begin
    TrackBar1.Tag := 1;
    TrackBar1.Value := MediaPlayer1.CurrentTime;
    TrackBar1.Tag := 0;

    CurrentMin := MediaPlayer1.CurrentTime div 1000 div 60;
    CurrentSec := MediaPlayer1.CurrentTime div 1000 mod 60;
    DurationMin := MediaPlayer1.Duration div 1000 div 60;
    DurationSec := MediaPlayer1.Duration div 1000 mod 60;
    Label2.Text := 'Длительность: ' + Format('%2.2d:%2.2d', [CurrentMin, CurrentSec]) + '/' + Format('%2.2d:%2.2d', [DurationMin, DurationSec]);

  end;

  //Если трек завершился, то начинаем играть следующий
  if (Round((MediaPlayer1.CurrentTime/MediaPlayer1.Duration)*100) = 100) then
  begin
    PlayNextClick(Self);
  end;
end;

procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  // Используем свойство Tag как флаг, чтобы избежать одновременное обновление
  // трекбара и MediaPlayer1.CurrentTime при воспроизведении
  if TrackBar1.Tag = 0 then
  begin
    // Проверка на Паузу
    if (MediaPlayer1.State = TMediaState.Stopped) AND (PauseTime <> 0) then
    begin
      PauseTime := Round(TrackBar1.Value);
    end
    else
    begin
      MediaPlayer1.CurrentTime := Round(TrackBar1.Value);
    end;
  end;
end;

end.

Теперь сохраняем и компилируем проект под Android.
Приложение выглядит так:
Samsung Galaxy S2 (v4.1.2)

HTC Rhime

Для тех, кому лень создавать весь проект с нуля или просто интересно сразу запустить приложение на своём девайсе, выкладываю исходник.

Возможно, через некоторое время добавлю ещё несколько функций. Например: Кнопку «Не повторять/повторять 1 трек/повторять все треки», воспроизведение случайного трека или нормальный вывод названия и автора трека через ID3. А вообще вы можете и сами это сделать :)

В этой статье я хотел показать, что работать с компонентом TMediaPlayer настолько просто, что за вечер (при желании), можно написать неплохой плеер для Android’а. Хотя есть и некоторые баги/особенности, но о них я напишу в следующей статье. 
Удачной разработки.

UPD. Если свернуть запущенный плеер, при помощи кнопки "Home", то он без проблем продолжит работать в фоне.

2 комментария:

  1. СпасиБО Андрею Ефимову.
    С Уважением. Олег.

    ОтветитьУдалить
  2. Здравствуйте Андрей.
    Может глупые вопросы но тем не менее.
    Расскажите пожалуйста как сделать чтобы воспроизвёлся mp3 файл при при открытии окна. Точнее я не понимаю куда файл поместить и следовательно куда прописать MediaPlayer1.FileName :=??. Вообще где храняться файлы которые задеплоины к проекту? Все ли они в одном месте на телефоне или в разных?
    Вы написали path := '/storage/'
    else path := '/sdcard/'; Как это понимать?
    С Уважением.
    Олег.

    ОтветитьУдалить