Question

I would like to have a playlist for my own music player in Delphi / Pascal.

I thought that it would be the best solution to have a TStringList with the path of the MP3 file and - additionally - a TListBox with the song names. The matching strings in both lists must be at the same position. So if the user chooses item 5 in TListBox I can just take the path at position 5 in the TStringList.

This works fine.

But now I need a playlist with two columns: "artist" and "song title". You should be able to sort the playlist by artist (ascending and descending) as well as by song title (ascending and descending) - alphabetically, of course.

How could I do this? Having two objects of TStringList - one sorted by artist and one sorted by song title?

Was it helpful?

Solution

I've done a few of these "lists" over time and in the end I've always found making the classes rather easy, but storing and especially reading the lists from disk has proven "challenging" to say the least.

The challenge has been in cases were users actually manipulate the lists with external editors, thus making reading the lists error prone.

For a universally accepted playlist format (M3U) have a look at http://schworak.com/programming/music/playlist_m3u.asp.

A VCL component with source that reads multiple formats is available at Torry's called "PlayList v.0.5.1". http://www.torry.net/quicksearchd.php?String=PlayList+v.0.5.1&Title=Yes

OTHER TIPS

I would do a TSong class containing at least the Artist and Title properties, and a TSongList providing 1 or more sort methods (can be generic) using the proper sort Field(s.
Certainly not maintaining 2 separate StringLists that you have to manage, keep in sync and reshuffle when sorting...

One cheap way to kinda implement that, could be to have an in memory DataSet with a record containing Artist and Path displayed in a grid that you can sort on different columns.
The current row will give both informations directly.

One simple solution would be to implement your song list/song information as a TCollection.

By using collections you can let the VCL handle the loading and saving to disk.

For example:

Please note this is not functionally complete, I'll leave that up to you, and since I wrote this from the top of my head I might have messed something up. It is only an example to get you started.

{...}
interface

Type
  TSongCollectionItem = class(TCollectionItem)
  public
    constructor create(Owner:TCollection); override;
    procedure assign(source : TPersistent); override;
  published
    property FileName : String read fFileName Write fFileName;
    property Artist : string read fArtist write fArtist;
    property Title : string read fTitle write fTitle;
    {...}
    property Album : string read fAlbum write fAlbum;
  end;

  TSongCollection = class(TOwnedCollection)
  private
    function GetItem(Index: Integer): TSongCollectionItem;
    procedure SetItem(Index: Integer; Value: TSongCollectionItem);
  public
    constructor Create(AOwner: TPersistent);
    function Add: TSongCollectionItem;
    property Songs[Index: Integer]: TSongCollectionItem read GetItem write SetItem; default;
  end;

  procedure SaveSongList(Songs : TSongCollection; FileName:string; Binary:boolean);
  procedure LoadSongList(Songs : TSongCollection; FileName:string; Binary:boolean);

{...}

implementation

{...}

type  
  TSongComponent = class(TComponent)
  published
    property SongList : TSongCollection read fsonglist write SetSongList;
  end;

  procedure SaveSongList(Songs : TSongCollection; FileName:string; Binary:boolean);
  var
    wFile : TFileStream;
    wConvert : TMemoryStream;
    wSongList : TSongComponent;
  begin
    RegisterClass(TSongComponent);
    Try
      wConvert := TMemoryStream.Create;
      wFile := TFileStream.Create(filename, fmcreate);
      wSongList := TSongComponent.create(nil);
      try
        wSongList.SongList.Assign(Songs);
        if not Binary then
        begin
          wConvert.WriteComponent(wSongList);
          wConvert.Position := 0;
          ObjectBinaryToText(wConvert, wFile);
        end
        else
          wFile.WriteComponent(wSongList);
      finally
        wConvert.Free;
        wFile.Free;
        wSongList.free;
      end;
    finally
      Unregisterclass(TSongComponent);
    end;
  end;

  procedure LoadSongList(Songs : TSongCollection; FileName:string; Binary:boolean);
  var
    wFile : TFileStream;
    wConvert : TMemoryStream;
    wSongList : TSongComponent;
  begin
    RegisterClass(TSongComponent);
    Try
      wConvert := TMemoryStream.Create;
      wFile := TFileStream.Create(filename, fmOpenRead);
      try
        if not Binary then
        begin
          ObjectTextToBinary(wFile, wConvert);
          wConvert.Position := 0;
          wSongList := TSongComponent(wConvert.ReadComponent(Nil));
        end
        else
          wSongList := TSongComponent(wFile.ReadComponent(Nil));

        if assigned(Songs) and assigned(wSongList) then
          Songs.Assign(wSongList.Songs);

        if assigned(wSongList) then
          wSongList.free; 
      finally
        wConvert.Free;
        wFile.Free;
      end;
    finally
      Unregisterclass(TSongComponent);
    end;
  end;

If you don't want to build an global object structure, you can allways use TlistView structure in report mode. You have there a list, with subitems. You can sort by column, and save to csv or whatever format. you can easily add icons etc.....

and you have the right events to trigger.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top