你的第一个MP3 player

5 篇文章 0 订阅
3 篇文章 0 订阅

 

第一章:想放MP3?Mediaplayer足够了!

时不我待

或许你知道MP3是一种音乐格式,在这里,空声音的白占数据被删掉了,其余部分被压缩了。压缩效果是摄人的:一个64MB的声音文件压缩到4MB左右。

那些日子MP3是大势所趋。至于你信不信,MP3播放已经成为多媒体当中的重要任务之一。

还等什么?让我们看看如何在Delphi中,用控件TMediaplayer如何造一个播放器吧。这篇文章会告诉你,如何从MP3文件中解压出信息。最流行的一种标签加密就是ID3了。一首歌的标题、作者、专辑、年份等等都在这里存储。

切记:想写一个MP3播放器,我们有许多可能。首选,我们要写一段代码,它可以遥控一个像winamp这样的应用;次选,我们可以用一个第三方插件Xaudio;下策,我们可以用DirectX或者IMediaControl去写一个播放器。

 

用TMediaplayer?

 

还记得mediaplayer吗?咦,它不是不支持MP3播放吗?俱往矣!

VCL提供了TMediaplayer这个控件,它支持许多格式的媒体。阅读delphi帮助,你会发现:‘Mediaplayer准许你的应用去控制一个媒体录放设备,比如CD-ROM播放器,视频播放器,MIDI序列器’。

 

MediaPlayer on Screen
 

 

在Mediaplayer的属性中,有一个DeviceType。这个属性限制/约束了多媒体设备的种类。这个属性枚举了好几个选项,但没有一个是针对MP3的。每当我们把Mediaplayer拖到窗体上,默认地,这个属性就被设为Autoselect,意为随着文件扩展名作出自动选择。在设计时光,我们用“打开文件”对话框去设置“文件名”这个属性。在对话框的“文件类型”里,MP3类型也是没有的,只有AVI、MIDI、WAVE而已。由此我们就可以推出,Mediaplayer是无法播放MP3的——错!

 

第二章:使用工程GUI

手把手

新工程建立后,默认建立窗体名为Form1。这是我们播放器工程需要的唯一窗体。

我们的MP3播放器,绝不仅仅是播放MP3声音就行了的,还要它能显示(甚至改变)MP3文件的ID3标签。这还不算,播放时应该有个进度条。

请把以下控件拖进唯一窗体:

 

From the Standard tab:

  • one ListBox component, name it 'mp3List'
  • one GroupBox component containing six Edit components (names: edTitle, edArtist, edAlbum, edYear, edGenre, edComment),
    From the Additional tab:
  • one BitBtn component, name it 'btnOpenFolder',
  • one StaticText component, name it 'txtFolder',
    From the System tab
  • one Timer component, name it 'ProgresTimer'.
  • one ProgressBar component, name it 'Progres'.
  • and of course, one MediaPlayer component, name it 'mp3Player'.

     

    mp3 player at design time
     

    提示,当你把Mediaplayer拖进窗体时,其9键都是可见的。这个播放器很简单,不是所有的按键都需要的。那么,你把VisibleButtons属性中的三个键设为不可见:Play、Pause、Stop。

    那个bitbtn和StaticText(统计框)是用来选择和显示MP3所在文件夹的。Listbox用来显示MP3文件名。我为什么用bitbtn而不用传统button呢,一言以蔽之:因为bitbtn属性丰富,上面可以画图。

    几个Edit用于显示ID3标签的不同项目。

    Timer(定时器),进度条需要它发出“移动”的信号。

     

     

    第三章:用工程代码

    即便是再简单的工程,也需要写代码。方才,我们已经写了GUI,现在是让程序动态起来的时候了。

    选择MP3占用的文件夹

    如前文所表,btnopenfolder和txtfolder是用来选择和显示MP3文件列表的。Delphi为用户提供了很多选择音乐的可能性。OpenDialog控件封装了一个对话框,这个对话框用于打开文件。另一种,就是用SHBrowseForFolder这个Windows API函数,调用一个Windows系统对话框,用来浏览文件及文件夹。第二种选择门儿清是btnOpenFolder的onclick事件。这里展示代码的最后一部分。

    指定文件夹内,所有的MP3文件都显示在MP3list(其控件类型是ListBox)上。要列所有的MP3文件,我们用 Searching For Files 一文中提到的方法。

     

    显示播放进度

    这个简单。

    TMediaPlayer的Position属性,就是一首歌登时的播放位置。Length属性表示了该歌的长度,以TimeFormat属性为标尺。当用户选择了一首歌,下面的配置就完成了。

    ...
     Progress.Max := 0;
     {code to open a mp3 song}
     Progress.Max := mp3player.Length;
    ...
    


     

    而在Timer(名字叫ProgressTimer)控件的ontime事件中,我们有:

    procedure TForm1.ProgresTimerTimer
             (Sender: TObject);
    begin
     if Progress.Max <> 0 then
       Progress.Position := mp3player.Position;
    end;
    


    演奏MP3文件

    你可以不信。这是最便捷的一条路。既然,TMediaPlayer已经生而知之,在用户按下Play、Stop(在MediaPlayer外观上)时候,该怎么做。那么,我们需要做的就是,准备MediaPlayer接受一个MP3文件。我会给你整个无返函数,每当用户选择一首歌时,它就会启动:

    procedure TForm1.mp3ListClick(Sender: TObject);
     var mp3File:string;
    begin
     //if the list is empty don't do anything
     if mp3List.Items.Count=0 then exit;
     //file is FolderName + FileName
     mp3File := Concat(txtFolder.Caption,
                       mp3List.Items.Strings
                           [mp3List.ItemIndex]);
    
      //Chechk again if it exists
     if not FileExists(mp3File) then begin
      ShowMessage('MP3 file does not exist?!');
      exit;
     end;
    
     //used to display the ID3 tag information
     FillID3TagInformation (mp3File,
                            edTitle,
                            edArtist,
                            edAlbum,
                            edYear,
                            edGenre,
                            edComment);
    
     Progress.Max:=0;
    
     mp3player.Close;
     mp3player.FileName:=mp3File;
     mp3player.Open;
    
     Progress.Max := mp3player.Length;
    end;
    


     

    mp3Player at run time

     



    第四章:MP3的ID3编辑

    有一件东西让MP3比CD更令人喜闻乐见,那就是ID3。每个正常的MP3文件,有自己的数据,关于艺术家、歌名、歌曲所属专辑、专辑出版年份。这个标签,大概要在MP3文件中占去128bytes的容量。

    ID3标签以字符串“TAG”开头,如果该数据缺席,意味着ID3已经被干掉了,或者从来就没写上过。但是不要着急,我们可以修补之。

    ID3标签是这样描写的:

    type
     TID3Rec = packed record
      Tag     : array[0..2] of Char;
      Title,
      Artist,
      Comment,
      Album   : array[0..29] of Char;
      Year    : array[0..3] of Char;
      Genre   : Byte;
    end;

    注意:上面代码中的Genre(体裁),只占用一个byte。这个byte可以如下翻译:


    const
      MaxID3Genre=147;
    
      ID3Genre: array[0..MaxID3Genre] of string = (
        'Blues', 'Classic Rock', 'Country', 'Dance', 
        ...
        'Synthpop'  {and probably more to come}
      );


    读取ID3


    欲读取之,我们必须使用IO例程。


    procedure FillID3TagInformation
    (mp3File: string;
    Title,Artist,Album,Year,Genre,Comment:TEdit);
    var ID3 : TID3Rec;
        fmp3: TFileStream;
    begin
      fmp3:=TFileStream.Create(mp3File, fmOpenRead);
      try
        fmp3.position:=fmp3.size-128;
        fmp3.Read(ID3,SizeOf(ID3));
      finally
        fmp3.free;
      end;
    
     if ID3.Tag <> 'TAG' then begin
       Title.Text:='Wrong or no ID3 tag information';
       Artist.Text:='Wrong or no ID3 tag information';
       Album.Text:='Wrong or no ID3 tag information';
       Year.Text:='Wrong or no ID3 tag information';
       Genre.Text:='Wrong or no ID3 tag information';
       Comment.Text:='Wrong or no ID3 tag information';
     end else begin
       Title.Text:=ID3.Title;
       Artist.Text:=ID3.Artist;
       Album.Text:=ID3.Album;
       Year.Text:=ID3.Year;
       if ID3.Genre in [0..MaxID3Genre] then
         Genre.Text:=ID3Genre[ID3.Genre]
       else
         Genre.Text:=IntToStr(ID3.Genre);
       Comment.Text:=ID3.Comment
     end;
    end;

    这段代码,使用了TFileStream,来接触MP3文件信息。当我们成功地打开一个MP3文件,将进度调到离末端还有128byte的位置,然后就读到了ID3。余下密码,就是先看ID3信息是否存在,只要存在就开始赋值。

    刻写ID3

    这个代码也简单。下面是无返函数:

    procedure ChangeID3Tag
      (NewID3: TID3Rec; mp3FileName: string);

    留神,在工程代码中,这段函数的呼唤,没有被覆盖。这个还是留给你自己去实施吧。


    尾声

    终于,我们做到了!你自己的坑钱播放器终于开动了!现在我要做的,就是要让它演奏,对它做一点小修补。加设几种皮肤怎么样?请参见我的custom shaped forms一文。你还可以有很多创意。让大家共赏你的制作吧,把它贴到Free Applications板块上来。




    {
    Article: Your first MP3 Delphi player
    
    http://delphi.about.com/library/weekly/aa112800a.htm
    
    See how to build a full-blown mp3 player with Delphi
    in just a few seconds. Even more: get the ID3 tag
    information from a mp3 file and change it!
    
    For the .zip file of this project click here.
    
    }
    
    
    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
      StdCtrls, Buttons, MPlayer, ComCtrls, ExtCtrls;
    
    type
      TForm1 = class(TForm)
        mp3player: TMediaPlayer;
        mp3List: TListBox;
        btnOpenFolder: TBitBtn;
        GroupBox1: TGroupBox;
        edTitle: TEdit;
        edArtist: TEdit;
        edAlbum: TEdit;
        edYear: TEdit;
        edGenre: TEdit;
        edComment: TEdit;
        Label1: TLabel;
        Label2: TLabel;
        Label3: TLabel;
        Label4: TLabel;
        Label5: TLabel;
        Label6: TLabel;
        txtFolder: TStaticText;
        Progress: TProgressBar;
        ProgresTimer: TTimer;
        procedure btnOpenFolderClick(Sender: TObject);
        procedure mp3ListClick(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure ProgresTimerTimer(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    type
      TID3Rec = packed record
        Tag     : array[0..2] of Char;
        Title,
        Artist,
        Comment,
        Album   : array[0..29] of Char;
        Year    : array[0..3] of Char;
        Genre   : Byte;
      end;
    
    const
      MaxID3Genre=147;
      ID3Genre: array[0..MaxID3Genre] of string = (
        'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge',
        'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B',
        'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial', 'Alternative', 'Ska',
        'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno', 'Ambient',
        'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance', 'Classical',
        'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip', 'Gospel',
        'Noise', 'AlternRock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative',
        'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic',
        'Darkwave', 'Techno-Industrial', 'Electronic', 'Pop-Folk',
        'Eurodance', 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta',
        'Top 40', 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American',
        'Cabaret', 'New Wave', 'Psychadelic', 'Rave', 'Showtunes', 'Trailer',
        'Lo-Fi', 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro',
        'Musical', 'Rock & Roll', 'Hard Rock', 'Folk', 'Folk-Rock',
        'National Folk', 'Swing', 'Fast Fusion', 'Bebob', 'Latin', 'Revival',
        'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock', 'Progressive Rock',
        'Psychedelic Rock', 'Symphonic Rock', 'Slow Rock', 'Big Band',
        'Chorus', 'Easy Listening', 'Acoustic', 'Humour', 'Speech', 'Chanson',
        'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Primus',
        'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba',
        'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle',
        'Duet', 'Punk Rock', 'Drum Solo', 'Acapella', 'Euro-House', 'Dance Hall',
        'Goa', 'Drum & Bass', 'Club-House', 'Hardcore', 'Terror', 'Indie',
        'BritPop', 'Negerpunk', 'Polsk Punk', 'Beat', 'Christian Gangsta Rap',
        'Heavy Metal', 'Black Metal', 'Crossover', 'Contemporary Christian',
        'Christian Rock', 'Merengue', 'Salsa', 'Trash Metal', 'Anime', 'Jpop',
        'Synthpop'  {and probably more to come}
      );
    
    implementation
    
    uses ShellAPI, ShlObj;  // needed for the BrowseForFolder function
    
    {$R *.DFM}
    
    procedure FillID3TagInformation(mp3File:string; Title,Artist,Album,Year,Genre,Comment:TEdit);
    var //fMP3: file of Byte;
        ID3 : TID3Rec;
        fmp3: TFileStream;
    begin
      fmp3:=TFileStream.Create(mp3File, fmOpenRead);
      try
        fmp3.position:=fmp3.size-128;
        fmp3.Read(ID3,SizeOf(ID3));
      finally
        fmp3.free;
      end;
    
     { or the non Stream approach - as in ChangeID3Tag procedure
     try
       AssignFile(fMP3, mp3File);
       Reset(fMP3);
       try
         Seek(fMP3, FileSize(fMP3) - 128);
         BlockRead(fMP3, ID3, SizeOf(ID3));
       finally
       end;
     finally
       CloseFile(fMP3);
     end;
     }
    
     if ID3.Tag <> 'TAG' then begin
       Title.Text:='Wrong or no ID3 tag information';
       Artist.Text:='Wrong or no ID3 tag information';
       Album.Text:='Wrong or no ID3 tag information';
       Year.Text:='Wrong or no ID3 tag information';
       Genre.Text:='Wrong or no ID3 tag information';
       Comment.Text:='Wrong or no ID3 tag information';
     end else begin
       Title.Text:=ID3.Title;
       Artist.Text:=ID3.Artist;
       Album.Text:=ID3.Album;
       Year.Text:=ID3.Year;
       if ID3.Genre in [0..MaxID3Genre] then
         Genre.Text:=ID3Genre[ID3.Genre]
       else
         Genre.Text:=IntToStr(ID3.Genre);
       Comment.Text:=ID3.Comment
     end;
    end;
    
    
    procedure ChangeID3Tag(NewID3: TID3Rec; mp3FileName: string);
    var
      fMP3: file of Byte;
      OldID3 : TID3Rec;
    begin
      try
        AssignFile(fMP3, mp3FileName);
        Reset(fMP3);
        try
          Seek(fMP3, FileSize(fMP3) - 128);
          BlockRead(fMP3, OldID3, SizeOf(OldID3));
          if OldID3.Tag = 'TAG' then
            { Replace old tag }
            Seek(fMP3, FileSize(fMP3) - 128)
          else
            { Append tag to file because it doesn't exist }
            Seek(fMP3, FileSize(fMP3));
          BlockWrite(fMP3, NewID3, SizeOf(NewID3));
        finally
        end;
      finally
        CloseFile(fMP3);
      end;
    end;
    
    
    procedure FillMP3FileList(Folder: string; sl: TStrings);
    var Rec : TSearchRec;
    begin
     sl.Clear;
     if SysUtils.FindFirst(Folder + '*.mp3', faAnyFile, Rec) = 0 then
      try
        repeat
          sl.Add(Rec.Name);
        until SysUtils.FindNext(Rec) <> 0;
      finally
        SysUtils.FindClose(Rec);
      end;
    end;
    
    function BrowseDialog(const Title: string; const Flag: integer): string;
    var
      lpItemID : PItemIDList;
      BrowseInfo : TBrowseInfo;
      DisplayName : array[0..MAX_PATH] of char;
      TempPath : array[0..MAX_PATH] of char;
    begin
      Result:='';
      FillChar(BrowseInfo, sizeof(TBrowseInfo), #0);
      with BrowseInfo do begin
        hwndOwner := Application.Handle;
        pszDisplayName := @DisplayName;
        lpszTitle := PChar(Title);
        ulFlags := Flag;
      end;
      lpItemID := SHBrowseForFolder(BrowseInfo);
      if lpItemId <> nil then begin
        SHGetPathFromIDList(lpItemID, TempPath);
        Result := IncludeTrailingBackslash(TempPath);
        GlobalFreePtr(lpItemID);
      end;
    end;
    
    
    procedure TForm1.btnOpenFolderClick(Sender: TObject);
    var mp3Folder : string;
    begin
     mp3Folder := BrowseDialog('Choose a folder with mp3 files', BIF_RETURNONLYFSDIRS);
     if mp3Folder = '' then Exit;
    
     txtFolder.Caption := mp3Folder;
    
     FillMP3FileList(mp3Folder, mp3List.Items);
    end;
    
    procedure TForm1.mp3ListClick(Sender: TObject);
     var mp3File: string;
    begin
      if mp3List.Items.Count=0 then exit;
      mp3File := Concat(txtFolder.Caption, mp3List.Items.Strings[mp3List.ItemIndex]);
      if not FileExists(mp3File) then begin
       ShowMessage('MP3 file '+#13#10+ mp3File +#13#10+'does not exist!');
       exit;
      end;
    
      FillID3TagInformation(mp3File, edTitle, edArtist, edAlbum, edYear, edGenre, edComment);
    
      Progress.Max:=0;
    
      mp3player.Close;
      mp3player.FileName:=mp3File;
      mp3player.Open;
    
      Progress.Max := mp3player.Length;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      txtFolder.Caption := ExtractFilePath(Application.ExeName);
      FillMP3FileList(txtFolder.Caption, mp3List.Items);
      Progress.Max:=0;
    end;
    
    procedure TForm1.ProgresTimerTimer(Sender: TObject);
    begin
      if Progress.Max<>0 then
        Progress.Position := mp3player.Position;
    end;
    end.
    
    {
    ********************************************
    Zarko Gajic
    About.com Guide to Delphi Programming
    http://delphi.about.com
    email: delphi.guide@about.com
    free newsletter: http://delphi.about.com/library/blnewsletter.htm
    forum: http://forums.about.com/ab-delphi/start/
    ********************************************
    }




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值