TIdHTTPServer
does not natively support streaming media. You have to implement it manually. In your OnCommandGet
event handler, assign your desired values to the AResponseInfo
parameter as needed, such as ContentType
and TransferEncoding
, and leave the ContentText
and ContentStream
properties unassigned, then call AResponseInfo.WriteHeader()
to send just the response headers to the client, then enter a loop writing your video media data in chunks (according to the format described in RFC 2616 Section 3.6.1 Chunked Transfer Coding
) until the client disconnects or the end of the media is reached. For example:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
FS: TFileStream;
Buf: TIdBytes;
BufLen: Integer;
begin
if ARequestInfo.Document <> '/' then
begin
AResponseInfo.ResponseNo := 404;
Exit;
end;
FS := TFileStream.Create('video1.mpg', fmOpenRead or fmShareDenyWrite);
try
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'video/mpeg';
AResponseInfo.TransferEncoding := 'chunked';
AResponseInfo.WriteHeader;
SetLength(Buf, 1024);
repeat
BufLen := FS.Read(Buf[0], 1024);
if BufLen < 1 then Break;
AContext.Connection.IOHandler.WriteLn(IntToHex(BufLen, 1));
AContext.Connection.IOHandler.Write(Buf, BufLen);
AContext.Connection.IOHandler.WriteLn;
until False;
AContext.Connection.IOHandler.WriteLn('0');
AContext.Connection.IOHandler.WriteLn;
finally
FS.Free;
end;
end;
On the other hand, if you are trying to stream media from another server, it gets a bit more complicated. You have to send a request to the other server, receive the response, and then forward the data to your client. However, TIdHTTP
does not support streaming media, so it would be difficult to use it for this purpose. You will likely end up having to use TIdTCPClient
directly instead and implement the necessary portions of the HTTP protocol yourself, eg:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Client: TIdTCPClient;
Headers: TIdHeaderList;
S, ResponseCode, ResponseText: string;
Size: Int64;
Strm: TIdTCPStream;
begin
if ARequestInfo.Document <> '/' then
begin
AResponseInfo.ResponseNo := 404;
Exit;
end;
Client := TIdTCPClient.Create;
try
Client.Host := 'server_name_video';
Client.Port := port;
Client.Connect;
try
Client.IOHandler.WriteLn('GET /video1.mpg HTTP/1.0');
Client.IOHandler.WriteLn('Host: server_name_video');
Client.IOHandler.WriteLn;
ResponseText := Client.IOHandler.ReadLn;
Fetch(ResponseText);
ResponseText := TrimLeft(ResponseText);
ResponseCode := Fetch(ResponseText, ' ', False);
ResponseCode := Fetch(ResponseCode, '.', False);
if ResponseCode <> '200' then
begin
AResponseInfo.ResponseNo := StrToInt(ResponseCode);
AResponseInfo.ResponseText := ResponseText;
Exit;
end;
Headers := TIdHeaderList.Create(QuoteHTTP);
try
Headers.FoldLength := MaxInt;
repeat
s := Client.IOHandler.ReadLn;
if s = '' then Break;
Headers.Add(s);
until False;
Strm := TIdTCPStream.Create(AContext.Connection);
try
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := Headers.Values['Content-Type'];
if Pos('chunked', Headers.Values['Transfer-Encoding']) <> 0 then
begin
AResponse.TransferEncoding := 'chunked';
AResponseInfo.WriteHeader;
repeat
s := Client.IOHandler.ReadLn;
AContext.Connection.IOHandler.WriteLn(s);
Size := StrToInt64('$'+Fetch(s, ';'));
if Size = 0 then Break;
Client.IOHandler.ReadStream(Strm, Size, False);
s := Client.IOHandler.ReadLn;
AContext.Connection.IOHandler.WriteLn(s);
until false;
repeat
s := Client.IOHandler.ReadLn;
AContext.Connection.IOHandler.WriteLn(s);
until s = '';
end
else if Headers.IndexOfName('Content-Length') <> -1 then
begin
Size := StrToInt64(Headers.Values['Content-Length']);
AResponseInfo.ContentLength := Size;
AResponseInfo.WriteHeader;
if Size > 0 then
Client.IOHandler.ReadStream(Strm, Size, False);
end else
begin
AResponseInfo.CloseConnection := true;
AResponseInfo.WriteHeader;
try
Client.IOHandler.ReadStream(Strm, -1, True);
except
on E: EIdSocketError do begin
if not (E.LastError in [10053, 10054, 10058]) then
raise;
end;
end;
end;
finally
Strm.Free;
end;
finally
Headers.Free;
end;
finally
Client.Disconnect;
end;
finally
Client.Free;
end;
end;
Of course, if needed, you will have to also implement things like HTTP authentication, requests for byte ranges, etc.
Update: Or, rather than use TIdTCPClient
directly, you could use TIdHTTP
after all, just give it an output TStream
that writes back to the original client as it is being written to. You could use TIdEventStream
for that purpose, or write your own TStream
class, eg:
type
TMyStream = class(TIdBaseStream)
protected
FHTTP: TIdHTTP;
FClient: TIdIOHandler;
FResponse: TIdHTTPResponseInfo;
function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
procedure IdSetSize(ASize: Int64); override;
public
constructor Create(AHTTP: TIdHTTP; AClient: TIdIOHandler; AResponse: TIdHTTPResponseInfo);
destructor Destroy; override;
end;
constructor TMyStream.Create(AHTTP: TIdHTTP; AClient: TIdIOHandler; AResponse: TIdHTTPResponseInfo);
begin
inherited Create;
FHTTP := AHTTP;
FClient := AClient;
FResponse := AResponse;
end;
destructor TMyStream.Destroy;
begin
if FResponse.HeaderHasBeenWritten then
begin
FClient.WriteLn('0');
FClient.WriteLn('');
end;
end;
function TMyStream.IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
Result := 0;
end;
function TMyStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
if not FResponse.HeaderHasBeenWritten then
begin
FResponse.ResponseNo := 200;
FResponseInfo.ContentType := FHTTP.Response.ContentType;
FResponse.TransferEncoding := 'chunked';
FResponse.WriteHeader;
end;
FClient.WriteLn(IntToHex(IndyLength(ABuffer, ACount, AOffset)));
FClient.Write(ABuffer, ACount, AOffset);
FClient.WriteLn;
end;
function TMyStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
begin
Result := 0;
end;
procedure TMyStream.IdSetSize(ASize: Int64);
begin
end;
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
HTTP: TIdHTTP;
Strm: TMyStream;
begin
if ARequestInfo.Document <> '/' then
begin
AResponseInfo.ResponseNo := 404;
Exit;
end;
HTTP := TIdHTTP.Create;
try
HTTP.HTTPOptions := HTTP.HTTPOptions + [hoNoProtocolErrorException];
Strm := TMyStream.Create(HTTP, AContext.Connection.IOHandler, AResponseInfo);
try
HTTP.Get('http://server_name_video:'+IntToStr(port)+'/video1.mpg', Strm);
finally
Strm.Free;
end;
if not AResponseInfo.HeaderHasBeenWritten then
begin
AResponseInfo.ResponseNo := HTTP.ResponseCode;
AResponseInfo.ResponseText := HTTP.ResponseText;
end;
finally
HTTP.Free;
end;
end;
Alternatively, if the other server supports chunked
responses, you can either:
use the new TIdHTTP.OnChunkReceived
event to write each received chunk to the client, similar to above without using a custom TStream
(you still have to provide a TStream
to TIdHTTP.Get()
. You could use TIdEventStream
for that, and just not assign any event handlers to it so data gets discarded. This may change in the future).
enable TIdHTTP
's new hoNoReadChunked
flag, and then just tunnel the raw data from TIdHTTP.IOHandler
directly to the client, such as by using a TIdTCPStream
with AContext.Connection.IOHandler.WriteStream()
.
New TIdHTTP flags and OnChunkReceived event