Building a Basic Media Player

From RAD Studio
Jump to: navigation, search

Go Up to Tutorial: FireMonkey Audio-Video

This tutorial demonstrates how to build a basic media player using TMediaPlayer and TMediaPlayerControl with FireMonkey. The media player contains standard functions (play, pause, next and previous), and also offers the possibility to manipulate the current played media (volume and current position). This basic media player allows to load a set of media files into a TListBox and to start playing by double-clicking an item in the list.

Form Design

  1. Select File > New > Multi-Device Application - Delphi > Blank Application.
  2. Add a TLayout to the form. With the layout in focus, in the Object Inspector, set the Align property to Left and the Name property to MainLayout. Resize the MainLayout as you consider necessary to display video images.
  3. Add the following to the MainLayout:
    • A TLayout. Name it ControlPlayLayout and align it to the bottom of the MainLayout. This layout contains all components that control the media file.
    • A TLayout. Name it TextLayout and align it to the top of the MainLayout. This layout contains the labels used to display the name and duration of the current played media.
    • A TMediaPlayerControl. Set its Align property to Client.
  4. Add the following to the ControlPlayLayout:
    • Five TButton objects, one for each standard function of a media player. Change the names of the buttons to PreviousButton, PlayButton, PauseButton, StopButton and NextButton.
    • Two TTrackBar objects: one to control the volume and one to control the current played position. Change the names of the track bars to VolumeTrackBar and PositionTrackBar. Align the PositionTrackBar to the top of the ControlPlayLayout.
    • A TCheckBox and name it ShuffleCheckBox.
  5. Add two TLabel objects to the TextLayout: one to display the name of the current played media and one to display the total duration of the current played files, measured in minutes and seconds. Name the two labels NameLabel and DurationLabel. Set the DurationLabel to be aligned to the right and its AutoSize property to True. Set the Align property of the NameLabel to Client and its text to be displayed on the center by setting its TextSettings.HorzAlign property to Center.
  6. Add another TLayout to the form, set its Align property to Client and rename it as PlayListLayout.
  7. Add the following to the PlayListLayout:
    • A TLayout to group the buttons used to manipulate the playlist. Align the added layout to the bottom of its parent and rename it as ControlPlayListLayout.
    • A TListBox. Name it PlayListBox and set its Align property to Client.
  8. Add three TButton objects to the PlayListLayout. Name them LoadFileButton, ClearAllButton and ClearButton. Align all three buttons to the left of their parent.
  9. Add the following to the form:
    The form should look like this:
    Basic Media Player Form.png

Implementation

The implementation can be split in multiple steps:

  1. Associate standard actions to standard controls
  2. Build and manipulate the playlist
  3. Implement the basic functions of a media player
  4. Implement helpful methods and classes
  5. Customize the current played media file

Associate standard actions to standard controls

Double-click your TActionList and, on the Action List editor that opens:

  1. Select New > New Standard Action.
  2. Under Media Library, select the following actions:
    • TMediaPlayerStop
    • TMediaPlayerCurrentTime
    • TMediaPlayerVolume
  3. Click OK to add those actions to your action list component.

Now configure those actions on the Action property of the following controls:

  • StopButton
  • PositionTrackBar
  • VolumeTrackBar

Build and manipulate the playlist

1. Double-click the LoadFileButton button to attach OnClick event handlers to it, so as to populate the PlayListBox. When LoadFileButton is pressed, the open dialog window opens. Select one or more files to be added to the list when the OK button of the open dialog window is pressed.

Delphi:

procedure TForm1.LoadFileButtonClick(Sender: TObject);
var
  Files: TStrings;
  I: integer;
  MediaEntry: TMediaEntry;
begin
  // Sets the Filter so only the supported files to be displayed
  OpenDialog1.Filter := TMediaCodecManager.GetFilterString;
  if (OpenDialog1.Execute) then
  begin
    Files := OpenDialog1.Files;
    for I := 0 to Files.Count - 1 do
    begin
      MediaEntry := TMediaEntry.Create(Files[I]);
      PlayListBox.Items.AddObject(extractFileName(Files[I]), MediaEntry);
    end;
  end;
end;

C++Builder:

void __fastcall TForm1::LoadFileButtonClick(TObject *Sender) {
  int i;
  TMedia *Media;
  TStrings *Files = new TStringList();

  // Sets the Filter so only the supported files to be displayed
  OpenDialog1->Filter = TMediaCodecManager::GetFilterString();

  if (OpenDialog1->Execute()) {
    Files = OpenDialog1->Files;
    for (i = 0; i < Files->Count; i++) {
      Media = TMediaCodecManager::CreateFromFile(Files->operator[](i));
      PlayListBox->Items->AddObject
      (ExtractFileName((UnicodeString)Files->operator[](i)), Media);
    }
  }
}
2. Double-click the ClearAllButton and ClearButton buttons to attach OnClick event handlers to them.
ClearAllButton ClearButton

Delphi:

procedure TForm1.ClearAllButtonClick(Sender: TObject);
var
  I: integer;
begin
  if (PlayListBox.Count > 0) then
  begin
    for I := 0 to PlayListBox.Count - 1 do
      PlayListBox.Items.Objects[I].Free;
    PlayListBox.Clear;
    PlayListBox.ItemIndex := -1;
    SetLength(Played, 0);
    CurrentPlayedIndex := -1;
    MediaPlayer1.Clear;
    NameLabel.Text := '';
    DurationLabel.Text := '';
  end;
end;

C++Builder:

void __fastcall TForm1::ClearAllButtonClick(TObject *Sender) {
  int i;
  if (PlayListBox->Count > 0) {
    for (i = 0; i < PlayListBox->Count; i++) {
      PlayListBox->Items->Objects[i]->Free();
    }

    PlayListBox->Clear();
    PlayListBox->ItemIndex = -1;
    Played.resize(0);
    CurrentPlayedIndex = -1;
    MediaPlayer1->Clear();
    NameLabel->Text = "";
    DurationLabel->Text = "";
  }
}

Delphi:

procedure TForm1.ClearButtonClick(Sender: TObject);
var
  I, J: integer;
begin
  if PlayListBox.Count < 0 then
    Exit;
  PlayListBox.Items.Objects[PlayListBox.ItemIndex].Free;
  PlayListBox.Items.Delete(PlayListBox.ItemIndex);
  J := 0;
  // Makes sure that the deleted media is not on the Playlist
  while J < Length(Played) - 1 do
  begin
    if (Played[J] = PlayListBox.ItemIndex) then
    begin
      for I := J to Length(Played) - 1 do
        Played[I] := Played[I + 1];
      SetLength(Played, Length(Played) - 1);
    end;
    J := J + 1;
  end;
end;

C++Builder:

void __fastcall TForm1::ClearButtonClick(TObject *Sender) {
  unsigned int i, j;
  if (PlayListBox->Count < 0) {
    return;
  }

  PlayListBox->Items->Objects[PlayListBox->ItemIndex]->Free();
  PlayListBox->Items->Delete(PlayListBox->ItemIndex);
  j = 0;
  // Makes sure that the deleted media is not on the Playlist
  while (j < Played.size() - 1) {
    if (Played[j] == PlayListBox->ItemIndex) {
      for (i = j; i < Played.size(); i++) {
    Played[i] = Played[i + 1];
      }
      Played.resize(Played.size() - 1);
    }
    j += 1;
  }
}
3. To avoid keeping memory objects that are no longer used, make sure that every object added to PlayListBox is freed. When removing an item from PlayListBox, only the link between the list box item and the attached object is freed and the object remains allocated in memory.

Delphi:

procedure TForm1.FormDestroy(Sender: TObject);
var
  I: integer;
begin
  if (PlayListBox.Count > 0) then
    for I := 0 to PlayListBox.Count - 1 do
      PlayListBox.Items.Objects[I].Free;
end;

C++Builder:

void __fastcall TForm1::FormDestroy(TObject *Sender) {
  int i;
  if (PlayListBox->Count > 0) {
    for (i = 0; i < PlayListBox->Count; i++) {
      PlayListBox->Items->Objects[i]->Free();
    }
  }
}

Implement the basic functions of a media player

To start the player press the PlayButton, the NextButton or double-click an item in the playlist. Attach an OnDblClick event handler to the PlayListBox. Double-click each button in ControlPlayLayout to attach OnClick event handlers to each of them and to implement the basic functions of a media player.

PlayButton

PlayListBoxDblClick

Delphi:

procedure TForm1.PlayButtonClick(Sender: TObject);
begin
  if MediaPlayer1.Media <> nil then
  begin
    if (MediaPlayer1.State = TMediaState.Stopped) and
      (MediaPlayer1.CurrentTime < MediaPlayer1.Duration) then
    begin
      MediaPlayer1.Play;
      DisplayText(extractFileName(MediaPlayer1.FileName));
    end
    else
    begin
      MediaPlayer1.CurrentTime := 0;
    end;
  end
  else
  begin
    if (PlayListBox.Count = 0) then
      Exit;
    if (PlayListBox.ItemIndex = -1) then
      PlayListBox.ItemIndex := 0;
    FindAndPlay(PlayListBox.ItemIndex);
    if (ShuffleCheckBox.IsChecked) then
      AddToPlayedList(PlayListBox.ItemIndex);
  end;
end;

C++Builder:

void __fastcall TForm1::PlaybuttonClick(TObject *Sender) {
  if (MediaPlayer1->Media != NULL) {
    if ((MediaPlayer1->State == TMediaState::Stopped) &&
     (MediaPlayer1->CurrentTime < MediaPlayer1->Duration)) {
      MediaPlayer1->Play();
      DisplayText(ExtractFileName(MediaPlayer1->FileName));
    }
    else
      MediaPlayer1->CurrentTime = 0;
  }
  else {
    if (PlayListBox->Count == 0) {
      return;
    }
    if (PlayListBox->ItemIndex == -1) {
      PlayListBox->ItemIndex = 0;
    }
    FindAndPlay(PlayListBox->ItemIndex);
    if (ShuffleCheckBox->IsChecked) {
      AddToPlayedList(PlayListBox->ItemIndex);
    }
  }
}

Delphi:

procedure TForm1.PlayListBoxDblClick(Sender: TObject);
begin
  if (PlayListBox.Count < 0) then
    Exit;
  MediaPlayer1.Clear;
  MediaPlayer1.FileName:= TMediaEntry(
    PlayListBox.Items.Objects[PlayListBox.ItemIndex]).Path;
  if MediaPlayer1.Media <> nil then
  begin
    MediaPlayer1.Play;
    DisplayText(extractFileName(MediaPlayer1.FileName));
    AddToPlayedList(PlayListBox.Items.IndexOf
      (extractFileName(MediaPlayer1.FileName)));
  end;
end;

C++Builder:

void __fastcall TForm1::PlayListBoxDblClick(TObject *Sender) {
  if (PlayListBox->Count < 0) {
    return;
  }
  MediaPlayer1->Clear();
  MediaPlayer1->FileName = dynamic_cast<TMedia *>
      (PlayListBox->Items->Objects[PlayListBox->ItemIndex])->FileName;

  if (MediaPlayer1->Media != NULL) {
    MediaPlayer1->Play();
    DisplayText(ExtractFileName(MediaPlayer1->FileName));
    AddToPlayedList(PlayListBox->Items->IndexOf
        (ExtractFileName(MediaPlayer1->FileName)));
  }
}

PauseButton

NextButton

Delphi:

procedure TForm1.PauseButtonClick(Sender: TObject);
begin
  if MediaPlayer1.Media <> nil then
    MediaPlayer1.Stop;
end;

C++Builder:

void __fastcall TForm1::PauseButtonClick(TObject *Sender) {
  if (MediaPlayer1->Media != NULL) {
    MediaPlayer1->Stop();
  }
}

Delphi:

procedure TForm1.NextButtonClick(Sender: TObject);
begin
  if (PlayListBox.Count > 0) then
  begin
    PlayNext;
  end;
end;

C++Builder:

void __fastcall TForm1::NextButtonClick(TObject *Sender) {
  if (PlayListBox->Count > 0) {
    PlayNext();
  }
}

PreviousButton

Delphi:

procedure TForm1.PreviousButtonClick(Sender: TObject);
begin
  if ShuffleCheckBox.IsChecked then
  begin
    if (Length(Played) > 0) and (CurrentPlayedIndex > 0) then
    begin
      CurrentPlayedIndex := CurrentPlayedIndex - 1;
      FindAndPlay(Played[CurrentPlayedIndex]);
      PlayListBox.ItemIndex := Played[CurrentPlayedIndex];
    end;
  end
  else if (PlayListBox.ItemIndex > 0) then
  begin
    FindAndPlay(PlayListBox.ItemIndex - 1);
    PlayListBox.ItemIndex := PlayListBox.ItemIndex - 1;
  end;
end;

C++Builder:

void __fastcall TForm1::PreviousButtonClick(TObject *Sender) {
  if (ShuffleCheckBox->IsChecked) {
    if ((Played.size() > 0) && (CurrentPlayedIndex > 0)) {
      CurrentPlayedIndex = CurrentPlayedIndex - 1;
      FindAndPlay(Played[CurrentPlayedIndex]);
      PlayListBox->ItemIndex = Played[CurrentPlayedIndex];
    }
  }
  else
    if (PlayListBox->ItemIndex > 0) {
      FindAndPlay(PlayListBox->ItemIndex - 1);
      PlayListBox->ItemIndex = PlayListBox->ItemIndex - 1;
    }
}

Implement helpful methods and classes

1. Add the following public members to the TForm1 class:

Delphi:

  public
    { Public declarations }
    Played: array of integer;
    CurrentPlayedIndex: integer;
    procedure AddToPlayedList(Index: integer);
    procedure DisplayText(str: string);
    procedure PlayNext();
    procedure FindAndPlay(Index: integer);
  end;

  TMediaEntry = class(TObject)
  public
    Path: String;
    constructor Create(FilePath: String);
  end;

C++Builder:

public:   // User declarations
  std::vector<int> Played;
  unsigned int CurrentPlayedIndex;
  void __fastcall AddToPlayedList(int index);
  void __fastcall DisplayText(String s);
  void __fastcall PlayNext();
  void __fastcall FindAndPlay(int index);
};

class TMediaEntry : public TObject
{
public:
  TMediaEntry(UnicodeString FilePath);
  UnicodeString Path;
};
2. Played array is used to keep the indexes of the played media files within the PlayListBox when the media player runs in Shuffle mode (ShuffleChekBox is checked). Played media is saved so as to be able to get the previous or next played media. CurrentPlayedIndex keeps the index of the current played media within Played. AddToPlayedList method adds a specified media to the Played list.

Delphi:

procedure TForm1.AddToPlayedList(Index: integer);
var
  Len: integer;
begin
  Len := Length(Played);
  SetLength(Played, Len + 1);
  Played[Len] := Index;
  CurrentPlayedIndex := Len;
  PlayListBox.ItemIndex := index;
end;

C++Builder:

void __fastcall TForm1::AddToPlayedList(int index) {
  int len;

  len = Played.size();
  Played.resize(len + 1);
  Played[len] = index;
  CurrentPlayedIndex = len;
  PlayListBox->ItemIndex = index;
}
3. FindAndPlay method identifies the media to be played using its index within PlayListBox and PlayNext gets the index of the media that should be played and then calls FindAndPlay to identify and play the media with the calculated index.
FindAndPlay PlayNext

Delphi:

procedure TForm1.FindAndPlay(Index: integer);
begin
  MediaPlayer1.Clear;
  PositionTrackBar.Value := PositionTrackBar.Min;
  if Index >= PlayListBox.Items.Count then
    Index := 0;
  MediaPlayer1.FileName := TMediaEntry(PlayListBox.Items.Objects[Index]).Path;
  if MediaPlayer1.Media <> nil then
  begin
    MediaPlayer1.Play;
    DisplayText(extractFileName(MediaPlayer1.FileName));
  end;
end;

C++Builder:

void __fastcall TForm1::FindAndPlay(int index) {
  MediaPlayer1->Clear();
  PositionTrackBar->Value = PositionTrackBar->Min;
  MediaPlayer1->FileName = dynamic_cast<TMedia*>
          (PlayListBox->Items->Objects[index])->FileName;
  if (MediaPlayer1->Media != NULL) {
    MediaPlayer1->Play();
    DisplayText(ExtractFileName(MediaPlayer1->FileName));
  }
}

Delphi:

procedure TForm1.PlayNext();
var
  Index: integer;
begin
  if (ShuffleCheckBox.IsChecked) then
  begin
    if (CurrentPlayedIndex < Length(Played) - 1) then
    begin
      CurrentPlayedIndex := CurrentPlayedIndex + 1;
      FindAndPlay(Played[CurrentPlayedIndex]);
    end
    else
    begin
      Index := Random(PlayListBox.Count - 1);
      FindAndPlay(Index);
      AddToPlayedList(Index);
    end;
    PlayListBox.ItemIndex := Played[CurrentPlayedIndex];
  end
  else
  begin
    FindAndPlay(PlayListBox.ItemIndex + 1);
    PlayListBox.ItemIndex := PlayListBox.ItemIndex + 1;
  end;
end;

C++Builder:

void __fastcall TForm1::PlayNext() {
  unsigned int index;

  if (ShuffleCheckBox->IsChecked) {
    if (CurrentPlayedIndex < Played.size() - 1) {
      CurrentPlayedIndex = CurrentPlayedIndex + 1;
      FindAndPlay(Played[CurrentPlayedIndex]);
    }
    else {
      index = Random(PlayListBox->Count - 1);
      FindAndPlay(index);
      AddToPlayedList(index);
    }
    PlayListBox->ItemIndex = Played[CurrentPlayedIndex];
  }
  else {
    FindAndPlay(PlayListBox->ItemIndex + 1);
    PlayListBox->ItemIndex = PlayListBox->ItemIndex + 1;
  }
}
4. DisplayText displays the name and duration (in minutes and seconds) of the current played media.

Delphi:

procedure TForm1.DisplayText(str: string);
var
  DurationMin, DurationSec: integer;
begin
  NameLabel.Text := str;
  DurationMin := MediaPlayer1.Duration div 10000 div 60000;
  DurationSec := MediaPlayer1.Duration div 10000 mod 60000 div 1000;
  DurationLabel.Text := IntToStr(DurationMin) + ':' + IntToStr(DurationSec);
end;

C++Builder:

void __fastcall TForm1::DisplayText(String s) {
  int DurationMin, DurationSec;
  NameLabel->Text = s;
  DurationMin = MediaPlayer1->Duration / 10000 / 60000;
  DurationSec = MediaPlayer1->Duration / 10000 % 60000 / 1000;
  DurationLabel->Text = IntToStr(DurationMin) + ':' + IntToStr(DurationSec);
}
5. TMediaEntry wraps the absolute path of a media file from the playlist, as TListBox allows to store an instance of a TObject subclass along with each list box entry. Define its constructor as follows:

Delphi:

constructor TMediaEntry.Create(FilePath: String);
begin
  Path := FilePath;
end;

C++Builder:

TMediaPlayer::TMediaPlayer(UnicodeString FilePath)
: Path(FilePath)
{
}

Customize the current played media file

1. Play the next media file

To make the media player play the next media when the current played media ends, use the TTimer on a certain interval.

Double-click TTimer to attach OnTimer event handlers to it.

Delphi:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // When the current media ends the player starts playing
  // the next media in playlist
  if (MediaPlayer1.State = TMediaState.Playing) and
    (MediaPlayer1.CurrentTime = MediaPlayer1.Duration) then
    PlayNext;
end;

C++Builder:

void __fastcall TForm1::Timer1Timer(TObject *Sender) {
  PositionTrackBar->Tag = 1;
  PositionTrackBar->Value = MediaPlayer1->CurrentTime;
  PositionTrackBar->Tag = 0;
  // When the current media ends the player starts playing
  // the next media in playlist
  if ((MediaPlayer1->State == TMediaState::Playing) &&
        (MediaPlayer1->CurrentTime == MediaPlayer1->Duration)) {
    PlayNext();
  }
}
2. Shuffle on/off

If the ShuffleCheckBox is not checked, then the next media to be played is the next media in the PlayListBox. If it is checked, the next media to be played is a random media in the PlayListBox. If the media player is not set to shuffle mode, there is no need to use Played vector, as the playing order is the same as the order in PlayListBox.

Delphi:

procedure TForm1.ShuffleCheckBoxChange(Sender: TObject);
begin
  If (not ShuffleCheckBox.IsChecked) then
  begin
    SetLength(Played, 0);
    CurrentPlayedIndex := -1;
  end;
end;

C++Builder:

void __fastcall TForm1::ShuffleCheckBoxChange(TObject *Sender) {
  if (!ShuffleCheckBox->IsChecked) {
    Played.resize(0);
    CurrentPlayedIndex = -1;
  }
}

Run the Application

1. To run the project, press F9.
2. Load files to the PlayListBox by pressing the LoadFileButton button.
3. Start playing a media by pressing PlayButton or NextButton or by double-clicking an PlayListBox item.

Previous

See Also