⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 unaicystreamer.pas

📁 Voice Commnucation Components for Delphi
💻 PAS
📖 第 1 页 / 共 3 页
字号:

(*
	----------------------------------------------

	  unaIcyStreamer.pas
	  Icy-compatible streamers

	----------------------------------------------
	  This source code cannot be used without
	  proper license granted to you as a private
	  person or an entity by the Lake of Soft, Ltd

	  Visit http://lakeofsoft.com/ for more information.

	  Copyright (c) 2001, 2006 Lake of Soft, Ltd
		     All rights reserved
	----------------------------------------------

	  created by:
		Lake, 20 May 2003

	  modified by:
		Lake, May 2003
		Lake, Aug 2004
		Lake, Feb 2006

	----------------------------------------------
*)

{$I unaDef.inc }

unit
  unaIcyStreamer;

interface

uses
  Windows, unaTypes, unaUtils, unaClasses, unaSockets;


type
  // --  --
  tunaIcyStreamerStatus = (iss_disconnected, iss_connecting, iss_connected);


  //
  // -- unaIcyStreamers --
  //
  unaIcyStreamers = class(unaThread)
  private
    f_host: string;
    f_port: string;
    f_URL: string;
    //
    f_status: tunaIcyStreamerStatus;
    //
    f_socks: unaSocks;
    f_socksId: unsigned;
    f_connId: unsigned;
    f_timeOut: unsigned;
    f_connStartMark: int64;
  protected
    procedure handleStatus(); virtual; abstract;
    procedure handleSocketEvent(event: unaSocketEvent; id, connId: unsigned; data: pointer; len: unsigned); virtual; abstract;
    //
    function execute(threadIndex: unsigned): int; override;
    procedure onSocksEvent(sender: tObject; event: unaSocketEvent; id, connId: unsigned; data: pointer; size: unsigned);
    //
    procedure startIn(); override;
    procedure startOut(); override;
    //
    function sendDataTo(data: pointer; len: unsigned; id, connId: unsigned): unsigned;
  public
    constructor create(const host, port: string);
    //
    procedure AfterConstruction(); override;
    procedure BeforeDestruction(); override;
    //
    function sendData(data: pointer; len: unsigned): unsigned;
    function sendText(const text: string): unsigned;
    //
    property host: string read f_host write f_host;
    property port: string read f_port write f_port;
    property URL: string read f_URL write f_URL;
    //
    property status: tunaIcyStreamerStatus read f_status;
    property timeOut: unsigned read f_timeOut write f_timeOut;
  end;


  //
  // -- ICY stream provider --
  //
  unaIcyStreamProvider = class(unaIcyStreamers)
  private
    f_password: string;	// must not be empty
    f_title: string;
    f_genre: string;
    f_allowPublishing: bool;
    f_bitrate: int;
    f_passIsOK: bool;
    //
    f_icyCaps: int;
    //
    procedure setPassword(const value: string);
    function getPassword(): string;
    procedure extractCaps(const data: string);
    procedure sendStreamMetadata();
  protected
    procedure handleStatus(); override;
    procedure handleSocketEvent(event: unaSocketEvent; id, connId: unsigned; data: pointer; len: unsigned); override;
    procedure startIn(); override;
  public
    function pushSongTitle(const title: string; const url: string = ''): HRESULT;
    //
    property password: string read getPassword write setPassword;
    property title: string read f_title write f_title;
    property genre: string read f_genre write f_genre;
    property bitrate: int read f_bitrate write f_bitrate;
    property allowPublishing: bool read f_allowPublishing write f_allowPublishing;
    //
    property icyCaps: int read f_icyCaps;
    property passwordIsOK: bool read f_passIsOK;
  end;


  //
  tunaIcySongInfoUpdate = procedure(sender: tObject; const newTitle, newUrl: string) of object;
  tunaIcyDataAvailable = procedure(sender: tObject; data: pointer; size: unsigned) of object;

  //
  // -- ICY stream consumer --
  //
  unaIcyStreamConsumer = class(unaIcyStreamers)
  private
    f_header: string;
    f_headerIsDone: bool;
    f_songTitle: string;
    f_songUrl: string;
    //
    f_metaDataAlign: unsigned;
    f_dataBuf: unaMemoryStream;
    f_isMetaData: bool;
    f_metaDataSize: unsigned;
    f_metaDataBuf: pointer;
    f_metaDataBufSize: unsigned;
    //
    f_onSIU: tunaIcySongInfoUpdate;
    f_onDA: tunaIcyDataAvailable;
    //
    procedure sendHello(id: int = -1; connId: int = -1);
    procedure checkMetadata(size: unsigned; ptext: pointer); overload;
    procedure checkMetadata(); overload;
    procedure notifyAudioFromBuf();
    function loadMetaDataFromBuf(): unsigned;
  protected
    procedure handleStatus(); override;
    procedure handleSocketEvent(event: unaSocketEvent; id, connId: unsigned; data: pointer; len: unsigned); override;
    procedure startIn(); override;
    procedure startOut(); override;
    //
    procedure updateSongInfo(const title, url: string); virtual;
    procedure dataAvail(data: pointer; size: unsigned); virtual;
  public
    procedure AfterConstruction(); override;
    procedure BeforeDestruction(); override;
    //
    function getServerHeaderValue(const key: string): string;
    //
    property serverHeader: string read f_header;
    property songTitle: string read f_songTitle;
    property songUrl: string read f_songUrl;
    //
    property onSongInfoUpdate: tunaIcySongInfoUpdate read f_onSIU write f_onSIU;
    property onDataAvailable: tunaIcyDataAvailable read f_onDA write f_onDA;
  end;


const
  // --  --
  c_maxClientsPerServer	= 4096;

type
  unaIcyServer = class;
		     
  //
  // -- unaIcyServerClientConnection --
  //
  unaIcyServerClientConnection = class
  private
    f_server: unaIcyServer;
    f_serverId: unsigned;
    f_connId: unsigned;
    f_timeMark: int64;
    f_streamPos: unsigned;
    f_metadata: unaStringList;
    f_metaDataAlign: unsigned;
    //
    f_verLevel: int;	// if 2 or greater, that means OK
    f_header: string;
  public
    constructor create(server: unaIcyServer; serverId, connId, metaDataAlign: unsigned);
    procedure AfterConstruction(); override;
    procedure BeforeDestruction(); override;
    //
    procedure write(data: pArray; len: unsigned);
    function checkTimeout(timeout: unsigned): bool;
    //
    property connId: unsigned read f_connId;
    property metadata: unaStringList read f_metadata;
    property verLevel: int read f_verLevel write f_verLevel;
    property header: string read f_header write f_header;
  end;

  //
  // -- unaIcyServerClientList --
  //
  unaIcyServerClientList = class(unaIdList)
  protected
    function getId(item: pointer): int64; override;
  public
    constructor create();
  end;

  //
  // -- ICY server --
  //
  unaIcyServer = class(unaIcyStreamers)
  private
    f_maxClients: unsigned;
    f_metaDataAlign: unsigned;
    f_bitrate: int;
    f_special: string;
    //
    f_clients: unaIcyServerClientList;
    f_dataStream: unaMemoryStream;
    f_dataBuf: pArray;
    f_dataBufSize: unsigned;
    //
    f_metaDataStream: unaMemoryStream;
    f_metaDataBuf: string;
    //
    f_clientsToDrop: array[byte] of unsigned;	// connIDs
    f_clientsToDropCount: unsigned;
    //
    f_servedBytes: int64;
    //
    procedure onNewClientData(connId: unsigned; data: pArray; len: unsigned);
  protected
    procedure handleStatus(); override;
    procedure handleSocketEvent(event: unaSocketEvent; id, connId: unsigned; data: pointer; len: unsigned); override;
  public
    constructor create(const port: string; maxClients: unsigned = 3; metaDataAlign: unsigned = $2000; bitrate: int = 128; const special: string = '');
    procedure AfterConstruction(); override;
    procedure BeforeDestruction(); override;
    //
    function write(data: pArray; len: unsigned): unsigned;
    function writeMetadata(const title, url: string): unsigned;
    //
    property clients: unaIcyServerClientList read f_clients;
    property servedBytes: int64 read f_servedBytes;
  end;


{DP:METHOD
  for lazy localizers :)
}
function iss2str(status: tunaIcyStreamerStatus): string;


implementation


uses
{$IFDEF __SYSUTILS_H_ }
  Math, SysUtils,
{$ENDIF}
  unaPlacebo;

function iss2str(status: tunaIcyStreamerStatus): string;
const
  statusStr: array[tunaIcyStreamerStatus] of string =
    ('disconnected', 'connecting', 'connected');
begin
  result := statusStr[status];
end;

{ unaIcyStreamers }

// --  --
procedure unaIcyStreamers.afterConstruction();
begin
  f_socks := unaSocks.create();
  f_socks.onEvent := onSocksEvent;
  //
  inherited;
end;

// --  --
procedure unaIcyStreamers.beforeDestruction();
begin
  inherited;
  //
  freeAndNil(f_socks);
end;

// --  --
constructor unaIcyStreamers.create(const host, port: string);
begin
  f_host := host;
  f_port := port;
  f_timeOut := 5000;	// 5 sec.
  //
  inherited create();
end;

// --  --
function unaIcyStreamers.execute(threadIndex: unsigned): int;
begin
  while (not shouldStop) do begin
    //
    handleStatus();
    //
    sleep(100);	// sleep while nothing to do
  end;
  //
  f_status := iss_disconnected;
  result := 0;
end;

// --  --
procedure unaIcyStreamers.onSocksEvent(sender: tObject; event: unaSocketEvent; id, connId: unsigned; data: pointer; size: unsigned);
begin
  handleSocketEvent(event, id, connId, data, size);
  //
  wakeUp();
end;

// --  --
function unaIcyStreamers.sendData(data: pointer; len: unsigned): unsigned;
begin
  result := sendDataTo(data, len, f_socksId, f_connId);
end;

// --  --
function unaIcyStreamers.sendDataTo(data: pointer; len, id, connId: unsigned): unsigned;
begin
  if (0 < id) then
    result := f_socks.sendData(id, data, len, connId)
  else
    result := 0
end;

function unaIcyStreamers.sendText(const text: string): unsigned;
begin
  if (0 < length(text)) then
    result := sendData(@text[1], length(text))
  else
    result := 0;   
end;

// --  --
procedure unaIcyStreamers.startIn();
begin
  f_socksId := 0;
  f_status := iss_disconnected;
  //
  inherited;
end;

// --  --
procedure unaIcyStreamers.startOut();
var
  id: unsigned;
begin
  if (0 < f_socksId) then begin
    //
    id := f_socksId;
    f_socksId := 0;
    f_socks.closeThread(id);
  end;
  //
  inherited;
end;

{ unaIcyStreamProvider }

// --  --
procedure unaIcyStreamProvider.extractCaps(const data: string);
var
  capsPos: int;
  caps: string;
  i: int;
begin
  capsPos := pos('icy-caps:', lowerCase(data));
  //
  if (0 < capsPos) then begin
    //
    inc(capsPos, length('icy-caps:'));
    caps := copy(data, capsPos, maxInt);
    //
    i := 1;
    while (i < length(caps)) and (caps[i] in ['0'..'9']) do
      inc(i);
    //
    caps := copy(caps, 1, i - 1);
    f_icyCaps := str2intInt(caps, 0);
  end;
end;

// --  --
function unaIcyStreamProvider.getPassword(): string;
begin
  result := trim(f_password);
end;

// --  --
procedure unaIcyStreamProvider.handleSocketEvent(event: unaSocketEvent; id, connId: unsigned; data: pointer; len: unsigned);
var
  reply: pChar;
begin
  case (event) of

    // -- client --

    unaseClientConnect: begin
      //
      // check if we are waiting for data channel connection
      if (status = iss_connecting) then begin
	//
	// send hello (password) to data server
	sendDataTo(@f_password[1], length(f_password), id, connId);
      end;
    end;

    unaseClientData: begin
      //
      // check if we are waiting for server authorization reply
      if ((status = iss_connecting) and (id = f_socksId)) then begin
	//
	// check reply
	reply := data;
	if (1 = pos('OK2', reply)) then begin
	  //
	  // get server caps
	  extractCaps(reply);
	  // send metadata
	  sendStreamMetadata();
	  // indicate we are ready
	  f_status := iss_connected;
	end
	else
	  if (1 = pos('invalid password', reply)) then begin
	    //
	    // password is invalid, stop thread
	    f_passIsOK := false;
	    askStop();
	  end;
      end;
    end;

    unaseClientDisconnect,
    unaseThreadStartupError: begin
      //
      if (id = f_socksId) then
	// have to stop due to connection error/disconnection
	askStop()
    end;

  end;
end;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -