📄 unaicystreamer.pas
字号:
(*
----------------------------------------------
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 + -