Building a Basic Media Player
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
- Select File > New > Multi-Device Application - Delphi > Blank Application.
- 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. - 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
.
- 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.
- 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 toCenter
. - Add another TLayout to the form, set its Align property to
Client
and rename it as PlayListLayout. - Add the following to the PlayListLayout:
- Add three TButton objects to the PlayListLayout. Name them LoadFileButton, ClearAllButton and ClearButton. Align all three buttons to the left of their parent.
- Add the following to the form:
- A TMediaPlayer
- A TOpenDialog
- A TTimer
- A TActionList
- The form should look like this:
Implementation
The implementation can be split in multiple steps:
- Associate standard actions to standard controls
- Build and manipulate the playlist
- Implement the basic functions of a media player
- Implement helpful methods and classes
- Customize the current played media file
Associate standard actions to standard controls
Double-click your TActionList and, on the Action List editor that opens:
- Select New > New Standard Action.
- Under Media Library, select the following actions:
- TMediaPlayerStop
- TMediaPlayerCurrentTime
- TMediaPlayerVolume
- 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.