ShoutCastStream.pas
上传用户:hylc_2004
上传日期:2014-01-23
资源大小:46800k
文件大小:18k
源码类别:

Delphi控件源码

开发平台:

Delphi

  1. unit ShoutCastStream;
  2.    (*********************************************************************
  3.     * The contents of this file are used with permission, subject to    *
  4.     * the Mozilla Public License Version 1.1 (the "License"); you may   *
  5.     * not use this file except in compliance with the License. You may  *
  6.     * obtain a copy of the License at                                   *
  7.     * http://www.mozilla.org/MPL/MPL-1.1.html                           *
  8.     *                                                                   *
  9.     * Software distributed under the License is distributed on an       *
  10.     * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or    *
  11.     * implied. See the License for the specific language governing      *
  12.     * rights and limitations under the License.                         *
  13.     *                                                                   *
  14.     * (C) 2004 Martin Offenwanger: coder@dsplayer.de                    *
  15.     *********************************************************************)
  16. {
  17. @author(Martin Offenwanger: coder@dsplayer.de)
  18. @created(Apr 22, 2004)
  19. @lastmod(Sep 09, 2004)
  20. }
  21. interface
  22. uses
  23.   Windows, Controls, Sock, Forms, SysUtils, BaseClass, Dialogs,
  24.   WinSock, ICYParser;
  25. { how to use tip:
  26.   we are running a async. winsock
  27.   The Winsock sends its event handling trough the Windows Message Queue
  28.   You should create this class in a Thread and/or use
  29.   TApplication.processmessages in external buffering loops }
  30. type
  31.   TShoutcastStream = class
  32.   public
  33.     FApplication: TApplication;
  34.     constructor Create;
  35.     destructor Destroy; override;
  36.     procedure SetConnectToIp(Adress: string; Port: string;
  37.       Location: string; Meta: boolean);
  38.     function SetRipStream(RipStream: boolean; Path: string;
  39.       FileO: string): HRESULT;
  40.     function GetRipStream(out RipStream: boolean; out Path: string): HRESULT;
  41.   private
  42.     FLock: TBCCritSec;
  43.     FSock: TSock; // Winsock class
  44.     { sock message receiver
  45.       ( we are running a async winsock.
  46.       " requires a TForm listener "  ) }
  47.     FReceiveForm: TForm;
  48.     FLocation: string; // host Location (Path only)
  49.     FHeaderFound: boolean; // header flag
  50.     FICYHeader: string; // the header itself
  51.     // ripper feature Objects
  52.     FPath: string; // used filePath
  53.     FFile: string; // Location and filename
  54.     FFileNoMetaData: string; // file to record in NoMetaData Mode
  55.     FFileObject: TextFile; // FileObject
  56.     FRipStream: boolean; // ripper state flag
  57.     FFileCreated: boolean; // file state flag
  58.     // Metadata count
  59.     FMetaInterval: integer;
  60.     FMetaCount: integer;
  61.     FMetaStartFound: boolean;
  62.     FTempSave: string;
  63.     FOutOfSync: boolean;
  64.     FMetadataEnabled: boolean;
  65.     // connect message receiver
  66.     procedure OnSockConnect(Sender: TObject);
  67.     // read message receiver
  68.     procedure OnSockRead(Sender: TObject; Count: Integer);
  69.     procedure OnSockInfo(Sender: TObject; SocketInfo: TSocketInfo; Msg: string);
  70.     // metadata format: "StreamTitle='content;StreamURL='content';"
  71.     function getStreamTitle(Metadata: string): string;
  72.     function getStreamURl(Metadata: string): string;
  73.     // ripper
  74.     procedure createNewFileIfNeeded(Metadata: string);
  75.     procedure createFileNoMeataInt(FileO: string);
  76.   protected
  77.   end;
  78. implementation
  79. uses config;
  80. function TShoutcastStream.GetRipStream(out RipStream: boolean;
  81.   out Path: string): HRESULT;
  82. begin
  83.   FLock.Lock;
  84.   RipStream := FRipStream;
  85.   Path := copy(FPath, 1, length(FPath));
  86.   RESULT := S_OK;
  87.   FLock.UnLock;
  88. end;
  89. function TShoutcastStream.SetRipStream(RipStream: boolean; Path: string;
  90.   FileO: string): HRESULT;
  91. begin
  92.   FLock.Lock;
  93.   FRipStream := RipStream;
  94.   FPath := copy(Path, 1, length(Path));
  95.   FFileNoMetaData := copy(FileO, 1, length(FileO));
  96.   RESULT := S_OK;
  97.   FLock.UnLock;
  98. end;
  99. function TShoutcastStream.GetStreamTitle(Metadata: string): string;
  100. var
  101.   Pos1: integer;
  102.   Temp: string;
  103. begin
  104.   Pos1 := Pos('''', Metadata);
  105.   Temp := copy(Metadata, Pos1 + 1, length(Metadata) - Pos1 - 1);
  106.   Pos1 := Pos('''', Temp);
  107.   Result := copy(Temp, 1, Pos1 - 1);
  108. end;
  109. function TShoutcastStream.GetStreamURl(Metadata: string): string;
  110. var
  111.   Pos1: integer;
  112.   Temp: string;
  113. begin
  114.   // search for the first offset
  115.   Pos1 := Pos(';', Metadata);
  116.   Temp := copy(Metadata, Pos1 + 1, length(Metadata) - Pos1 - 1);
  117.   Result := getStreamTitle(Temp);
  118. end;
  119. procedure TShoutcastStream.createNewFileIfNeeded(metadata: string);
  120. var
  121.   Title: string;
  122.   Pos1: integer;
  123. begin
  124.   Title := getStreamTitle(Metadata);
  125.   Title := Title;
  126.   if (Title <> FFile) then
  127.     GFFileName := Title + '.mp3';
  128.   if FRipStream then
  129.   begin
  130.     if (Title <> FFile) then
  131.     begin
  132.       FFile := Title;
  133.       if FPath <> '' then
  134.         SetCurrentDir(FPath);
  135.       if FFileCreated then
  136.         CloseFile(FFileObject);
  137.       // check if the file name is supported ( /:*?"<>| )
  138.       Pos1 := Pos('', Title);
  139.       if Pos1 <> 0 then
  140.         Title := copy(Title, 1, Pos1 - 1);
  141.       Pos1 := Pos('/', Title);
  142.       if Pos1 <> 0 then
  143.         Title := copy(Title, 1, Pos1 - 1);
  144.       Pos1 := Pos(':', Title);
  145.       if Pos1 <> 0 then
  146.         Title := copy(Title, 1, Pos1 - 1);
  147.       Pos1 := Pos('*', Title);
  148.       if Pos1 <> 0 then
  149.         Title := copy(Title, 1, Pos1 - 1);
  150.       Pos1 := Pos('?', Title);
  151.       if Pos1 <> 0 then
  152.         Title := copy(Title, 1, Pos1 - 1);
  153.       Pos1 := Pos('"', Title);
  154.       if Pos1 <> 0 then
  155.         Title := copy(Title, 1, Pos1 - 1);
  156.       Pos1 := Pos('<', Title);
  157.       if Pos1 <> 0 then
  158.         Title := copy(Title, 1, Pos1 - 1);
  159.       Pos1 := Pos('>', Title);
  160.       if Pos1 <> 0 then
  161.         Title := copy(Title, 1, Pos1 - 1);
  162.       Pos1 := Pos('|', Title);
  163.       if Pos1 <> 0 then
  164.         Title := copy(Title, 1, Pos1 - 1);
  165.       // rewrite existing files to reduce overhead :/
  166.       if FileExists(Title + '.mp3') then
  167.         DeleteFile(Title + '.mp3');
  168.       try
  169.         AssignFile(FFileObject, Title + '.mp3');
  170.         ReWrite(FFileObject);
  171.         FFileCreated := true;
  172.       except
  173.         ShowMessage('A bug has been found in ASyncEx Filter' +
  174.           'please post the folowing line to: coder@dsplayer.de  : ' +
  175.           Title + '.mp3');
  176.       end;
  177.     end;
  178.   end
  179.   else
  180.   begin
  181.     if FFileCreated then
  182.       CloseFile(FFileObject);
  183.     FFileCreated := false;
  184.   end;
  185. end;
  186. procedure TShoutcastStream.createFileNoMeataInt(FileO: string);
  187. begin
  188.   if FRipStream then
  189.   begin
  190.     if not FFileCreated then
  191.     begin
  192.       if FileExists(FileO) then
  193.         // we rewrite existing files to reduce overhead :/
  194.         DeleteFile(FileO);
  195.       try
  196.         if FPath <> '' then
  197.           SetCurrentDir(FPath);
  198.         if FFileCreated then
  199.           CloseFile(FFileObject);
  200.         AssignFile(FFileObject, FileO);
  201.         ReWrite(FFileObject);
  202.         FFileCreated := true;
  203.       except
  204.         ShowMessage('A bug has been found in DSPlayer ASync.Source' +
  205.           'please post the folowing line to: coder@dsplayer.de  : ' +
  206.           FileO);
  207.       end;
  208.     end;
  209.   end
  210.   else
  211.   begin
  212.     if FFileCreated then
  213.       CloseFile(FFileObject);
  214.     FFileCreated := false;
  215.   end;
  216. end;
  217. procedure TShoutcastStream.OnSockRead(Sender: TObject; Count: Integer);
  218. var
  219.   Temp: string;
  220.   Temp2: string;
  221.   MyPos: integer;
  222.   Subi: integer;
  223.   Pos1, Pos2: integer;
  224.   MetaString: string;
  225.   LengthO: byte;
  226.   CharO: char;
  227.   TempSave: string;
  228.   MetaTitle: string;
  229.   MetaUrl: string;
  230.   ErrMsg: string;
  231. begin
  232.   { -> This section includes the streamparser,fileripper and buffer abilties <-
  233.                todo: - code cleaning
  234.                      - better helper functions or helper classes
  235.   }
  236.   try
  237.     FLock.Lock;
  238.     Temp := FSock.Receive; // get the received data from winsock buffer
  239.     // get the end of url header count
  240.     MyPos := 0;
  241.     if not FHeaderFound then
  242.     begin
  243.       FTempSave := FTempSave + Temp;
  244.       Temp := FTempSave;
  245.       MyPos := Pos(#13#10#13#10, Temp);
  246.     end;
  247.     if MyPos <> 0 then
  248.     begin
  249.       // cut the header and save it into FICYHeader
  250.       Temp2 := Temp;
  251.       Temp := Copy(Temp, MyPos + 4, StrLen(@MyPos) - 4); // get mp3 data
  252.       Temp2 := Copy(Temp2, 0, MyPos + 2); // get the URL header
  253.       FICYHeader := Temp2; // save the URL header
  254.       // get the Metadata count:
  255.       FMetaInterval := GetServerICYInt(Temp2);
  256.       // header callback
  257.       if GFFilterCallBack <> nil then
  258.       begin
  259.         if not GetICYSuccessfullyConnected(FICYHeader, ErrMsg) then
  260.         begin
  261.           GFFilterCallBack.AsyncExICYNotice(ICYError, PChar(ErrMsg));
  262.         end;
  263.         // try to get icy informations
  264.         GFFilterCallBack.AsyncExICYNotice(PChar(ICYName),
  265.           PChar(GetServerICYName(FICYHeader)));
  266.         GFFilterCallBack.AsyncExICYNotice(PChar(ICYGenre),
  267.           PChar(GetServerICYGenre(FICYHeader)));
  268.         GFFilterCallBack.AsyncExICYNotice(PChar(ICYURL),
  269.           PChar(GetServerICYURL(FICYHeader)));
  270.         GFFilterCallBack.AsyncExICYNotice(PChar(ICYBitrate),
  271.           PChar(GetServerICYBitRate(FICYHeader)));
  272.       end
  273.       else
  274.       begin
  275.         if not GetICYSuccessfullyConnected(FICYHeader, ErrMsg) then
  276.           showmessage('Can not receive the Stream.'#13#10#13#10 +
  277.             'Reason:'#13#10 + ErrMsg);
  278.       end;
  279.       // push the mp3 data to queue
  280.       if GFStringQueue <> nil then
  281.         GFStringQueue.Push(Temp);
  282.       if (not FMetadataEnabled) and (FMetaInterval = 0) then
  283.       begin
  284.         createFileNoMeataInt(FFileNoMetaData);
  285.         if FFileCreated and FRipStream then
  286.           Write(FFileObject, Temp);
  287.       end;
  288.       if FMetaInterval <> 0 then
  289.       begin
  290.         FMetaCount := length(Temp);
  291.       end;
  292.       // set header found state flag
  293.       FHeaderFound := true;
  294.       FMetaStartFound := false;
  295.       FTempSave := '';
  296.       FLock.UnLock;
  297.       exit;
  298.     end;
  299.     // if found and cutted the URLheader start to add mp3 data to the queue
  300.     if FHeaderFound then
  301.     begin
  302.       if FTempSave <> '' then
  303.       begin // completion of metadatablock is done here
  304.         TempSave := copy(FTempSave, 1, length(FTempSave));
  305.         Temp := copy(TempSave, 1, length(TempSave)) + copy(Temp, 1,
  306.           length(Temp));
  307.         FTempSave := '';
  308.       end;
  309.       Pos1 := Pos('StreamTitle', Temp);
  310.       if Pos1 <> 0 then
  311.       begin
  312.         CharO := (copy(Temp, Pos1 - 1, 1))[1];
  313.         LengthO := ((byte(CharO)) * 16);
  314.         if length(Temp) < Pos1 + LengthO - 1 then
  315.         begin
  316.           // found a incomlete metadata block
  317.           FTempSave := FTempSave + copy(Temp, 1, length(Temp));
  318.           FLock.UnLock;
  319.           exit;
  320.         end;
  321.       end;
  322.       if FMetaInterval <> 0 then
  323.       begin
  324.         FMetaCount := FMetaCount + length(Temp);
  325.         { some servers send the first Metatag at a unspezified point! ,
  326.           so try to get the first sended Meta Info, and count the received
  327.           mp3 data                                                          }
  328.         Pos1 := Pos('StreamTitle', Temp);
  329.         if Pos1 <> 0 then
  330.         begin
  331.           Pos2 := length(Temp) - Pos1;
  332.           CharO := (copy(Temp, Pos1 - 1, 1))[1];
  333.           LengthO := ((byte(CharO)) * 16);
  334.           MetaString := copy(Temp, Pos1, LengthO - 1);
  335.           if MetaString <> '' then
  336.           begin
  337.             // MetaData Callback
  338.             if GFFilterCallBack <> nil then
  339.             begin
  340.               // parse stream Title & streamUrl
  341.               MetaTitle := getStreamTitle(MetaString);
  342.               MetaUrl := getStreamURl(MetaString);
  343.               // Stream MetaData Callback (parsed)
  344.               if length(MetaTitle) = 0 then
  345.                 MetaTitle := 'N/A';
  346.               if length(MetaUrl) = 0 then
  347.                 MetaUrl := 'N/A';
  348.               GFFilterCallBack.AsyncExMetaData(PChar(MetaTitle),
  349.                 PChar(MetaUrl));
  350.             end;
  351.             FOutOfSync := false;
  352.             createNewFileIfNeeded(MetaString);
  353.           end;
  354.           // set the remaining data
  355.           Temp2 := copy(Temp, 0, Pos1 - 2);
  356.           Temp := Temp2 + copy(Temp, Pos1 - 1 + LengthO + 1, Pos2 - LengthO +
  357.             1);
  358.           // calculate the remaining mp3 data
  359.           FMetaCount := Pos2 - LengthO + 1;
  360.           if (GFStringQueue <> nil) and (not FOutOfSync) then
  361.             GFStringQueue.Push(Temp); // push the received mp3 data to the queue
  362.           if (FRipStream and FFileCreated and not FOutOfSync) then
  363.             Write(FFileObject, Temp);
  364.           FLock.UnLock;
  365.           exit;
  366.         end;
  367.         if FMetaCount > FMetaInterval then
  368.         begin
  369.           // calculate the start and end of the meta data in current block
  370.           Subi := FMetaCount - FMetaInterval;
  371.           Pos1 := length(Temp) - Subi + 1;
  372.           // get the length of the MetaData
  373.           CharO := (copy(Temp, Pos1, 1))[1];
  374.           LengthO := ((byte(CharO)) * 16);
  375.           if length(Temp) < Pos1 + LengthO - 1 then
  376.           begin
  377.             // found a incomlete metadata block
  378.             FTempSave := FTempSave + copy(Temp, 1, length(Temp));
  379.             FLock.UnLock;
  380.             exit;
  381.           end;
  382.           if LengthO <> 0 then
  383.             if Pos('Stream', MetaString) = 0 then
  384.             begin
  385.               // Server is out of Sync.!
  386.               if GFFilterCallBack <> nil then
  387.                 // >ToDO: error callback
  388.                 GFFilterCallBack.AsyncExMetaData('Server is out of sync, trying to resync', 'Server is out of sync, trying to resync');
  389.               FOutOfSync := true;
  390.               FLock.UnLock;
  391.               exit;
  392.             end;
  393.           MetaString := copy(Temp, Pos1, LengthO);
  394.           if MetaString <> '' then
  395.           begin // a metastring has been found
  396.             if GFFilterCallBack <> nil then
  397.             begin
  398.               // parse stream Title & streamUrl
  399.               MetaTitle := getStreamTitle(MetaString);
  400.               MetaUrl := getStreamURl(MetaString);
  401.               // Stream MetaData Callback (parsed)
  402.               if length(MetaTitle) = 0 then
  403.                 MetaTitle := 'N/A';
  404.               if length(MetaUrl) = 0 then
  405.                 MetaUrl := 'N/A';
  406.               GFFilterCallBack.AsyncExMetaData(PChar(MetaTitle), PChar(MetaUrl));
  407.             end;
  408.             if not (FOutOfSync) then
  409.               createNewFileIfNeeded(MetaString);
  410.           end;
  411.           // set the remaining data
  412.           Temp2 := copy(Temp, 0, Pos1 - 1);
  413.           Temp := Temp2 + copy(Temp, Pos1 + LengthO + 1, Subi - LengthO - 1);
  414.           // calculate the remaining mp3 data
  415.           FMetaCount := Subi - LengthO - 1
  416.         end;
  417.       end;
  418.       if (GFStringQueue <> nil) and (not FOutOfSync) then
  419.         GFStringQueue.Push(Temp); // pop the received mp3 data to the queue
  420.       // file ripper feature
  421.       if (not FMetadataEnabled) and (FMetaInterval = 0) then
  422.         createFileNoMeataInt(FFileNoMetaData);
  423.       if (FRipStream) and (FFileCreated) and (not FOutOfSync) then
  424.         Write(FFileObject, Temp);
  425.     end;
  426.     FLock.UnLock;
  427.   except
  428.     FLock.UnLock;
  429.     // no exception handling at present :(
  430.     // during prebuffering and during minimizing the app an exception is thrown:
  431.     // -> nil pointer acces
  432.   end;
  433. end;
  434. procedure TShoutcastStream.OnSockConnect(Sender: TObject);
  435. begin
  436.   FLock.Lock;
  437.   if FMetadataEnabled then
  438.     // send the official connect string (metadata)
  439.     FSock.Send('GET ' + FLocation + ' HTTP/1.0'#13#10
  440.       + 'User-Agent: DSPlayer'#13#10
  441.       + 'Host: '#13#10
  442.       + 'icy-MetaData:1'#13#10#13#10)
  443.   else
  444.     // send the official connect string (no metadata)
  445.     FSock.Send('GET ' + FLocation + ' HTTP/1.0'#13#10
  446.       + 'User-Agent: DSPlayer'#13#10
  447.       + 'Host: '#13#10#13#10#13#10);
  448.   FLock.UnLock;
  449. end;
  450. procedure TShoutcastStream.SetConnectToIP(Adress: string; Port: string;
  451.   Location: string; Meta: boolean);
  452. begin
  453.   FLock.Lock;
  454.   FMetadataEnabled := Meta;
  455.   FSock.HostName := Adress;
  456.   FSock.PortName := Port;
  457.   FLocation := Location;
  458.   FSock.Connected := true;
  459.   FLock.UnLock;
  460. end;
  461. procedure TShoutcastStream.OnSockInfo(Sender: TObject; SocketInfo: TSocketInfo;
  462.   Msg: string);
  463. begin
  464.   FLock.Lock;
  465.   FApplication.ProcessMessages;
  466.   if SocketInfo = siError then
  467.   begin
  468.     GFExit := true;
  469.     // Error Handling
  470.     //..
  471.     // Somtimes when we connect to a still well connected Adress
  472.     // the sock api is a little slow and needs some time to free the
  473.     // used address. -> error WSAEADDRINUSE
  474.     if Assigned(GFFilterCallBack) then
  475.       GFFilterCallBack.AsyncExSockError(PChar(Msg))
  476.     else
  477.       ShowMessage(Msg);
  478.   end;
  479.   FLock.UnLock;
  480. end;
  481. constructor TShoutcastStream.Create;
  482. begin
  483.   FMetadataEnabled := true;
  484.   FMetaCount := 0;
  485.   FMetaInterval := 0;
  486.   FFile := '';
  487.   FRipStream := false;
  488.   FFileCreated := false;
  489.   FLock := TBCCritSec.Create;
  490.   FReceiveForm := TForm.Create(nil);
  491.   FReceiveForm.Hide;
  492.   FSock := TSock.Create(FReceiveForm);
  493.   FSock.OnConnect := OnSockConnect;
  494.   FSock.OnInfo := OnSockInfo;
  495.   FSock.OnRead := OnSockRead;
  496.   FHeaderFound := false;
  497.   FOutOfSync := false;
  498.   FTempSave := '';
  499.   FFileNoMetaData := '';
  500.   FApplication := TApplication.Create(nil);
  501.   FICYHeader := 'No Header aviailble at present';
  502. end;
  503. destructor TShoutcastStream.Destroy;
  504. var
  505.   Application: TApplication;
  506. begin
  507.   FLock.Lock;
  508.   Application := TApplication.Create(nil);
  509.   FApplication.Destroy;
  510.   if FFileCreated then
  511.     CloseFile(FFileObject);
  512.   FSock.Destroy; //  buggy, if detroy is called Sock Adress might be still in use
  513.   Application.Destroy;
  514.   FReceiveForm.Free;
  515.   FLock.UnLock;
  516.   FLock.Free;
  517.   inherited Destroy;
  518. end;
  519. end.