📄 unaicystreamer.pas
字号:
// -- --
procedure unaIcyStreamProvider.handleStatus();
var
dataPort: word;
begin
case (status) of
iss_disconnected: begin
//
f_status := iss_connecting;
f_connStartMark := timeMark();
//
// start data connection
dataPort := str2intUnsigned(port, 8000) + 1;
f_socksId := f_socks.createConnection(host, int2str(dataPort), f_connId);
end;
iss_connecting: begin
// check if we had timed out
if (timeOut < timeElapsed32(f_connStartMark)) then
askStop();
end;
iss_connected: begin
//
end;
end;
end;
// -- --
function unaIcyStreamProvider.pushSongTitle(const title, url: string): HRESULT;
var
len: unsigned;
str: string;
//
socksIdPush: unsigned;
connIdPush: unsigned;
begin
result := HRESULT(-1);
//
// start HTTP-push connection
socksIdPush := f_socks.createConnection(host, port, connIdPush);
if (0 < socksIdPush) then begin
try
str := 'GET /admin.cgi?pass=' + url_encode(password) + '&mode=updinfo&song=' + url_encode(title) + '&url=' + url_encode(url) + ' HTTP/1.0'#10 +
'User-Agent: VC IcyStreamer (Mozilla Compatible)'#10#10;
//
len := length(str);
if (len = sendDataTo(@str[1], length(str), socksIdPush, connIdPush)) then
result := S_OK;
//
finally
f_socks.closeThread(socksIdPush);
end;
end;
end;
// -- --
procedure unaIcyStreamProvider.sendStreamMetadata();
var
data: string;
begin
data := 'icy-name:' + f_title + #10 +
'icy-genre:' + f_genre + #10 +
'icy-url:' + f_url + #10 +
'icy-irc:#none' + #10 +
'icy-icq:0' + #10 +
'icy-aim:N/A' + #10 +
'icy-pub:' + choice(f_allowPublishing, '1', '0') + #10 +
'icy-br:' + int2str(f_bitrate) + #10#10;
//
sendText(data);
end;
// -- --
procedure unaIcyStreamProvider.setPassword(const value: string);
begin
assert('' <> trim(value), 'Password must not be empty');
//
f_password := trim(value) + #10;
end;
// -- --
procedure unaIcyStreamProvider.startIn();
begin
f_passIsOK := true;
//
inherited;
end;
{ unaIcyStreamConsumer }
// -- --
procedure unaIcyStreamConsumer.afterConstruction();
begin
inherited;
//
f_dataBuf := unaMemoryStream.create();
f_dataBuf.maxCacheSize := 100; // give this stream lots of buffers
end;
// -- --
procedure unaIcyStreamConsumer.beforeDestruction();
begin
inherited;
//
freeAndNil(f_dataBuf);
end;
// -- --
procedure unaIcyStreamConsumer.checkMetadata(size: unsigned; ptext: pointer);
var
text: string;
function getValue(vstart: unsigned): string;
var
vend: unsigned;
begin
vend := vstart;
//
while ((vend < size) and (text[vend] <> '''')) do
inc(vend);
//
if ((vend < size) and (text[vend] = '''')) then
dec(vend);
//
result := copy(text, vstart, vend + 1 - vstart );
end;
var
vpos: int;
title: string;
url: string;
begin
if (0 < size) then begin
//
setLength(text, size);
move(pText^, text[1], size);
end
else
text := '';
//
vpos := pos('streamtitle=''', lowerCase(text));
if (0 < vpos) then
title := getValue(vpos + length('streamtitle='''));
//
vpos := pos('streamurl=''', lowerCase(text));
if (0 < vpos) then
url := getValue(vpos + length('streamurl='''));
//
if ('' = title) then
// try Ice 1.3 header
title := getServerHeaderValue('x-audiocast-name');
//
if ('' = url) then
// try Ice 1.3 header
url := getServerHeaderValue('x-audiocast-url');
//
updateSongInfo(title, url);
end;
// -- --
procedure unaIcyStreamConsumer.checkMetadata();
begin
checkMetaData(loadMetaDataFromBuf(), pChar(f_metaDataBuf));
end;
// -- --
procedure unaIcyStreamConsumer.dataAvail(data: pointer; size: unsigned);
begin
if (assigned(f_onDA)) then
f_onDA(self, data, size);
end;
// -- --
function unaIcyStreamConsumer.getServerHeaderValue(const key: string): string;
var
vpos: int;
vend: int;
len: int;
begin
result := '';
len := length(f_header);
//
if (0 < len) then begin
vpos := pos(lowerCase(key) + ':', lowerCase(f_header));
if (0 < vpos) then begin
//
inc(vpos, length(key) + 1);
vend := vpos;
//
while ((vend < len) and (f_header[vend] <> #13) and (f_header[vend] <> #10)) do
inc(vend);
//
result := trim(copy(f_header, vpos, vend + 1 - vpos));
end;
end;
end;
// -- --
procedure unaIcyStreamConsumer.handleSocketEvent(event: unaSocketEvent; id, connId: unsigned; data: pointer; len: unsigned);
var
text: pChar;
eoh: int;
blockSize: unsigned;
dataLeft: int;
begin
case (event) of
// -- client --
unaseClientConnect: begin
//
// check if we are waiting for channel connection
if (status = iss_connecting) then begin
//
// send hello (GET / HTTP/1.0) to server
sendHello(id, connId);
end;
end;
unaseClientData: begin
//
// got some data from server
if (0 < len) then begin
//
text := data;
dataLeft := len;
//
if (
(status = iss_connecting) and ((1 = pos('ICY 200', text)) or (1 = pos('HTTP/1.0 200', text)) or (1 = pos('HTTP/1.1 200', text)))
) then
f_status := iss_connected;
//
if (not f_headerIsDone) then begin
//
eoh := pos(#13#10#13#10, text);
if (0 < eoh) then
f_headerIsDone := true
else
eoh := len + 1;
//
f_header := f_header + copy(text, 1, eoh - 1);
//
inc(text, eoh + 3);
dec(dataLeft, eoh + 3);
//
if (f_headerIsDone) then begin
//
f_dataBuf.clear();
f_metaDataAlign := str2intInt(getServerHeaderValue('icy-metaint'), 0);
f_isMetaData := false; // stream starts with audio data
//
checkMetadata();
end;
end;
//
if (f_headerIsDone) then
//
while (0 < dataLeft) do begin
//
if (f_isMetaData) then begin
//
if (0 = f_metaDataSize) then begin
// we are at the beginning of metadata
f_metaDataSize := ord(text[0]) shl 4;
inc(text);
dec(dataLeft);
end;
//
if (int(f_dataBuf.getSize()) + dataLeft >= int(f_metaDataSize)) then begin
//
// all metadata is here
if (0 < f_metaDataSize) then begin
// notify about metadata
//
// check if we can notify directly from text
if ((1 > f_dataBuf.getSize()) and (dataLeft >= int(f_metaDataSize))) then
// yes, we can
checkMetadata(f_metaDataSize, text)
else begin
// no, notify from buffer
blockSize := f_dataBuf.write(text, f_metaDataSize - f_dataBuf.getSize());
inc(text, blockSize);
dec(dataLeft, blockSize);
//
checkMetadata();
end;
end;
//
// back to audio
inc(text, f_metaDataSize);
dec(dataLeft, f_metaDataSize);
//
f_isMetaData := false;
end
else begin
// not all metadata is here, save what we got now into buffer
blockSize := f_dataBuf.write(text, dataLeft);
inc(text, blockSize);
dec(dataLeft, blockSize);
end;
end
else begin
//
// check if we need to care about metadata
if (0 < f_metaDataAlign) then begin
//
if (f_dataBuf.getSize() + unsigned(dataLeft) >= f_metaDataAlign) then begin
// write rest of audio data into buffer
blockSize := f_dataBuf.write(text, f_metaDataAlign - f_dataBuf.getSize());
// notify audio data from buffer
notifyAudioFromBuf();
//
inc(text, blockSize);
dec(dataLeft, blockSize);
//
f_isMetaData := true;
f_metaDataSize := 0;
end
else begin
// store data for now
f_dataBuf.write(text, dataLeft);
dataLeft := 0;
end;
end
else begin
// simply notify audio data
dataAvail(text, dataLeft);
dataLeft := 0;
end;
end;
//
end; // WHILE (0 < dataLeft) ...
//
end;
end;
unaseClientDisconnect,
unaseThreadStartupError: begin
// have to stop due to connection error/disconnection
askStop();
end;
end;
end;
// -- --
procedure unaIcyStreamConsumer.handleStatus();
begin
case (status) of
iss_disconnected: begin
//
f_status := iss_connecting;
f_connStartMark := timeMark();
// start data connection
f_socksId := f_socks.createConnection(host, port, f_connId);
end;
iss_connecting: begin
// check if we had timed out
if (timeOut < timeElapsed32(f_connStartMark)) then
askStop();
end;
iss_connected: begin
//
end;
end;
end;
// -- --
function unaIcyStreamConsumer.loadMetaDataFromBuf(): unsigned;
begin
result := f_dataBuf.getSize();
if (f_metaDataBufSize < result) then begin
//
f_metaDataBufSize := result;
mrealloc(f_metaDataBuf, f_metaDataBufSize);
end;
//
result := f_dataBuf.read(f_metaDataBuf, result);
end;
// -- --
procedure unaIcyStreamConsumer.notifyAudioFromBuf();
var
size: unsigned;
begin
//
// WARNIGN! Since f_metaDataBuf can be modified by loadMetaDataFromBuf()
// we have to store the result (size) locally. That is because passing the
// return value directly as second parameter for dataAvail() may lead to
// passing invalid value as first parameter
//
size := loadMetaDataFromBuf();
//
dataAvail(f_metaDataBuf, size);
end;
// -- --
procedure unaIcyStreamConsumer.sendHello(id, connId: int);
var
hello: string;
socksId: unsigned;
begin
if (0 > id) then
socksId := f_socksId
else
socksId := unsigned(id);
//
if (0 > connId) then
connId := f_connId;
//
if ('' = trim(f_url)) then
f_url := '/';
//
hello := 'GET ' + f_url + ' HTTP/1.0'#13#10 +
'Host:' + f_host + #13#10 +
'Accept:*/*'#13#10 +
'User-Agent:VC 2.5 Listener 1.0'#13#10 +
'Icy-Metadata:1'#13#10 +
#13#10;
//
sendDataTo(@hello[1], length(hello), socksId, connId);
end;
// -- --
procedure unaIcyStreamConsumer.startIn();
begin
f_header := '';
f_headerIsDone := false;
f_songTitle := '';
f_songUrl := '';
//
inherited;
end;
// -- --
procedure unaIcyStreamConsumer.startOut();
begin
inherited;
//
mrealloc(f_metaDataBuf);
f_metaDataBufSize := 0;
end;
// -- --
procedure unaIcyStreamConsumer.updateSongInfo(const title, url: string);
begin
if ((trim(title) <> f_songTitle) or
(trim(url) <> f_songUrl)) then begin
//
f_songTitle := title;
f_songUrl := url;
//
if (assigned(f_onSIU)) then
f_onSIU(self, title, url);
end;
end;
{ unaIcyServerClientConnection }
// -- --
procedure unaIcyServerClientConnection.afterConstruction();
begin
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -