📄 unaicystreamer.pas
字号:
f_metadata := unaStringList.create();
//
inherited;
end;
// -- --
procedure unaIcyServerClientConnection.beforeDestruction();
begin
inherited;
//
freeAndNil(f_metadata);
end;
// -- --
function unaIcyServerClientConnection.checkTimeout(timeout: unsigned): bool;
begin
result := (2 <= verLevel) or (timeout > timeElapsed32(f_timeMark));
end;
// -- --
constructor unaIcyServerClientConnection.create(server: unaIcyServer; serverId, connId, metaDataAlign: unsigned);
begin
f_server := server;
f_serverId := serverId;
f_connId := connId;
f_metaDataAlign := metaDataAlign;
//
f_timeMark := timeMark();
f_streamPos := 0;
//
inherited create();
end;
// -- --
procedure unaIcyServerClientConnection.write(data: pArray; len: unsigned);
var
md: string;
sz: unsigned;
b: byte;
begin
if (2 <= verLevel) then begin
//
if ((0 < f_metaDataAlign) and (f_metaDataAlign <= f_streamPos)) then begin
//
// write metadata
if (0 < f_metadata.count) then begin
//
md := f_metadata.get(f_metadata.count - 1);
f_metadata.clear();
end
else
md := ''; // no metadata, but we still need to write "0"
//
sz := length(md);
//
if (0 = sz) then
b := 0
else
b := ((sz - 1) and $FFFFFFF0) shr 4 + 1;
//
if (0 < sz) then
md := adjust(md, b shl 4, ' ', false);
//
if (1 > b) then begin
// simply send b as it is
inc(f_server.f_servedBytes, f_server.f_socks.sendData(f_serverId, @b, 1, f_connId));
end
else begin
//
md := char(b) + md;
//
inc(f_server.f_servedBytes, f_server.f_socks.sendData(f_serverId, @md[1], 1 + b shl 4, f_connId));
end;
//
f_streamPos := 0; // reset offset
end;
//
if (0 < f_metaDataAlign) then
sz := min(f_metaDataAlign - f_streamPos, len)
else
sz := len;
//
inc(f_server.f_servedBytes, f_server.f_socks.sendData(f_serverId, data, sz, f_connId));
//
inc(f_streamPos, sz); // shift the offset
//
if (sz < len) then begin
//
// still have some data in buffer, let's recorse
write(@data[sz], len - sz);
end;
end;
end;
{ unaIcyServerClientList }
// -- --
constructor unaIcyServerClientList.create();
begin
// simply tell the parent we are dealing with objects
inherited create(true);
end;
// -- --
function unaIcyServerClientList.getId(item: pointer): int64;
begin
if (nil <> item) then
result := unaIcyServerClientConnection(item).f_connId
else
result := 0;
end;
{ unaIcyServer }
// -- --
procedure unaIcyServer.afterConstruction();
begin
f_clients := unaIcyServerClientList.create();
f_dataStream := unaMemoryStream.create();
f_metaDataStream := unaMemoryStream.create();
//
inherited;
end;
// -- --
procedure unaIcyServer.beforeDestruction();
begin
inherited;
//
f_dataBufSize := 0;
mrealloc(f_dataBuf);
//
freeAndNil(f_clients);
freeAndNil(f_dataStream);
freeAndNil(f_metaDataStream);
end;
// -- --
constructor unaIcyServer.create(const port: string; maxClients, metaDataAlign: unsigned; bitrate: int; const special: string);
begin
f_maxClients := min(maxClients, c_maxClientsPerServer);
f_metaDataAlign := metaDataAlign;
f_bitrate := bitrate;
f_special := special;
//
inherited create('', port);
end;
// -- --
procedure unaIcyServer.handleSocketEvent(event: unaSocketEvent; id, connId: unsigned; data: pointer; len: unsigned);
var
index: int;
begin
//
case (event) of
// -- client --
// do not care about client, we have only server here
// -- server --
unaseServerConnect: begin
//
if (f_clients.count < f_maxClients) then begin
//
// accept new client
f_clients.add(unaIcyServerClientConnection.create(self, id, connId, f_metaDataAlign));
//
if (iss_connecting = status) then
f_status := iss_connected;
end
else
// drop the client
f_socks.removeConnection(id, connId);
end;
unaseServerData: begin
//
onNewClientData(connId, data, len);
end;
unaseServerDisconnect: begin
//
// drop the client
index := f_clients.locateById(connId);
//
if (0 <= index) then
f_clients.removeByIndex(index);
//
if ((1 > f_clients.count) and (iss_connected = status)) then
f_status := iss_connecting; // return to sleepy mode
end;
// thread
unaseThreadStartupError: begin
//
askStop();
end;
end;
end;
{
Possible server replies:
ICY 401 Service Unavailable
icy-notice1:<BR>SHOUTcast Distributed Network Audio Server/win32 v1.9.2<BR>
icy-notice2:The resource requested is currently unavailable<BR>
ICY 200 OK
icy-notice1:<BR>This stream requires <a href="http://www.winamp.com/">Winamp</a><BR>
icy-notice2:SHOUTcast Distributed Network Audio Server/win32 v1.9.2<BR>
icy-name:VC 2.5 Streamer
icy-genre:Rock2
icy-url:http://lakeofsoft.com/vc/
Content-Type:audio/mpeg
icy-pub:0
icy-metaint:8192
icy-br:96
Metadata example:
StreamTitle='';StreamUrl='';
}
// -- --
procedure unaIcyServer.handleStatus();
var
i: unsigned;
sz: unsigned;
begin
//
case (status) of
iss_disconnected: begin
//
f_status := iss_connecting;
//
// start data connection
f_socksId := f_socks.createServer(port, f_connId);
end;
iss_connecting: begin
//
end;
iss_connected: begin
//
// check if we have metadata to send
sz := f_metaDataStream.getFirstChunkSize();
//
if ((0 < sz) and (0 < f_clients.count)) then begin
//
setLength(f_metaDataBuf, sz);
sz := f_metaDataStream.read(@f_metaDataBuf[1], sz);
//
if ((0 < sz) and lockNonEmptyList(f_clients, 100)) then begin
//
try
//
for i := 0 to f_clients.count - 1 do begin
//
unaIcyServerClientConnection(f_clients[i]).metadata.add(f_metaDataBuf);
end;
finally
f_clients.unlock();
end;
end;
//
end;
//
sz := f_dataStream.getSize();
//
if ((f_metaDataAlign < sz) and (0 < f_clients.count)) then begin
//
if (sz > f_dataBufSize) then begin
//
f_dataBufSize := sz;
mrealloc(f_dataBuf, f_dataBufSize);
end;
//
sz := f_dataStream.read(f_dataBuf, sz);
//
if ((0 < sz) and lockNonEmptyList(f_clients, 100)) then begin
//
try
//
for i := 0 to f_clients.count - 1 do begin
//
with (unaIcyServerClientConnection(f_clients[i])) do begin
//
if (checkTimeout(5000)) then begin // give client 5 sec to report player ID
//
write(f_dataBuf, sz);
end
else begin
// drop this client
if (f_clientsToDropCount < high(f_clientsToDrop)) then begin
//
f_clientsToDrop[f_clientsToDropCount] := connId;
inc(f_clientsToDropCount);
end;
end;
//
end;
//
end;
finally
f_clients.unlock();
end;
end;
//
end;
//
//
if (0 < f_clientsToDropCount) then begin
//
for i := 0 to f_clientsToDropCount - 1 do
//
f_socks.removeConnection(f_socksId, f_clientsToDrop[i]);
//
f_clientsToDropCount := 0;
end;
end;
end;
end;
// -- --
procedure unaIcyServer.onNewClientData(connId: unsigned; data: pArray; len: unsigned);
var
index: int;
hello: string;
p: int;
client: unaIcyServerClientConnection;
label
levm1, lev1, lev2;
begin
index := f_clients.locateById(connId);
//
if (0 <= index) then begin
//
client := unaIcyServerClientConnection(f_clients[index]);
with (client) do begin
//
{
possible client hello:
GET / HTTP/1.0
Host:192.168.1.1
Accept:*/*
User-Agent:VC 2.5 Listener 1.0
Icy-Metadata:1
#13#10
}
if (2 > verLevel) then
// add tcp data to client request header
header := header + pChar(data);
//
case (verLevel) of
-1: begin
levm1:
//
// send goodbye to client
hello := 'ICY 401 Service Unavailable'#13#10 +
''#13#10;
//
inc(f_servedBytes, f_socks.sendData(f_socksId, @hello[1], length(hello), connId));
//
end;
0: begin
//
if (1 = pos('GET /', header)) then begin
//
verLevel := 1;
goto lev1;
end;
end;
1: begin
lev1:
//
p := pos('User-Agent:', header);
//
if (0 < p) then begin
//
if (('' = f_special) or (0 < pos(f_special, pChar(@header[p])))) then begin
//
verLevel := 2;
goto lev2;
end
else begin
// check if all header is here
if (0 < pos(#13#10#13#10, header)) then begin
//
// say goodbye to client
verLevel := -1;
goto levm1;
end;
end;
//
end;
end;
2: begin
lev2:
//
verLevel := 3; // do not send hello again
//
// send hello to client
hello := 'ICY 200 OK'#13#10 +
'icy-notice1:<BR>This stream requires Winamp or compatible player<BR>'#13#10 +
'icy-notice2:SHOUTcast compatible Audio Server v1.0 (c) Lake of Soft, Ltd<BR>'#13#10 +
'icy-name:Live Streamer 1.0'#13#10 +
'icy-genre:Rock'#13#10 +
'icy-url:http://lakeofsoft.com/vc/'#13#10 +
'Content-Type:audio/mpeg'#13#10 +
'icy-pub:0'#13#10 +
'icy-metaint:' + int2str(f_metaDataAlign) + #13#10 +
'icy-br:' + int2str(f_bitrate) + #13#10#13#10;
//
inc(f_servedBytes, f_socks.sendData(f_socksId, @hello[1], length(hello), connId));
end;
end; // case
//
end; // with
end;
end;
// -- --
function unaIcyServer.write(data: pArray; len: unsigned): unsigned;
begin
result := 0;
//
if (0 < f_clients.count) then begin
//
result := f_dataStream.write(data, len);
//
wakeUp();
end
else
f_dataStream.clear();
end;
// -- --
function unaIcyServer.writeMetadata(const title, url: string): unsigned;
var
data: string;
begin
data := 'StreamTitle=''' + title + ''';StreamUrl=''' + url + '''';
//
result := f_metaDataStream.write(@data[1], length(data));
//
wakeUp();
end;
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -