I am using the TEdgeBrowser
component in a browser/viewer Delphi application. I had few issues after a brief learning curve for the original implementation.
I am now at a point where I can experiment with some of the features (like screen capture).
To capture a screen to a file, you can simply add:
EdgeBrowser1.CapturePreview(ScrFileName);
Where ScrFileName
is the path of the file i.e. c:\pic\screenshot.png
I would like to capture the screen every second (and relay it using an Indy component (TIdHTTPServer
) to relay it internally (http).
I am able to do this as follows, but it's very inefficient. Can anyone recommend an alternative?
procedure TMainForm.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
Const
EOL = #13;
var
Strm: TMemoryStream;
begin
if ARequestInfo.Document = '' then
begin
AResponseInfo.Redirect('/');
end
else if ARequestInfo.Document = '/' then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'text/html';
AResponseInfo.ContentText := '<!DOCTYPE html>' EOL
'<html>' EOL
'<head>' EOL
'<meta name="viewport" content="width=device-width, initial-scale=5">' EOL
'<meta http-equiv="Refresh" content=1>' EOL
'<style type="text/css">' EOL
' img.fullscr {' EOL
' display:block;' EOL
' width:100%;' EOL
' }' EOL
'</style>' EOL
'</head>' EOL
'<body>' EOL
'<img src="/image" >' EOL
'</body>' EOL
'</html>' EOL;
end
else if ARequestInfo.Document = '/image' then
begin
Strm := TMemoryStream.Create;
try
Strm.Clear;
Strm.LoadFromFile(ScrFileName);
Strm.Position := 0;
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'image/png';
AResponseInfo.ContentStream := Strm;
except
Strm.Free;
raise;
end;
end
else begin
AResponseInfo.ResponseNo := 404;
end;
end;
CodePudding user response:
Have you considered simply not saving the captured image to disk at all? TEdgeBrowser.CapturePreview()
has an overload that saves to a TStream
. You can save the captures at regular intervals to a TMemoryStream
that you share with TIdHTTPServer
to send the latest capture to HTTP clients.
TIdHTTPServer
is a multi-threaded component, its OnCommand...
events are fired in the context of worker threads, so be sure to protect the TMemoryStream
with a thread-safe lock, such as TCriticalSection
.
For example:
private
// using 2 TMemoryStreams so Edge can be preparing a new capture
// while TIdHTTPServer continues serving the previous capture to
// clients until the new capture is ready...
CapturingImageStream: TMemoryStream;
ReadyImageStream: TMemoryStream;
ImageLock: TCriticalSection;
// TODO: keep track of last capture time and etag, in case clients
// want to use Conditional-GET requests ('If-Modified-Since' and
// 'If-None-Match' HTTP headers) to cache retrievals in case captures
// haven't changed yet in between multiple GET requests...
...
procedure TMainForm.FormCreate(Sender: TObject);
begin
CapturingImageStream := TMemoryStream.Create;
ReadyImageStream := TMemoryStream.Create;
ImageLock := TCriticalSection.Create;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
IdHTTPServer1.Active := False;
CapturingImageStream.Free;
ReadyImageStream.Free;
ImageLock.Free;
end;
procedure TMainForm.Timer1Timer(Sender: TObject);
var
Strm: TMemoryStream;
begin
CapturingImageStream.Clear;
EdgeBrowser1.CapturePreview(CapturingImageStream);
ImageLock.Enter;
try
Strm := ReadyImageStream;
ReadyImageStream := CapturingImageStream;
CapturingImageStream := Strm;
finally
ImageLock.Leave;
end;
end;
procedure TMainForm.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Document = '' then
begin
AResponseInfo.Redirect('/');
end
else if ARequestInfo.Document = '/' then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'text/html';
AResponseInfo.ContentText := '<!DOCTYPE html>' sLineBreak
'<html>' sLineBreak
'<head>' sLineBreak
'<meta name="viewport" content="width=device-width, initial-scale=5">' sLineBreak
'<meta http-equiv="Refresh" content=1>' sLineBreak
'<style type="text/css">' sLineBreak
' img.fullscr {' sLineBreak
' display:block;' sLineBreak
' width:100%;' sLineBreak
' }' sLineBreak
'</style>' sLineBreak
'</head>' sLineBreak
'<body>' sLineBreak
'<img src="/image" >' sLineBreak
'</body>' sLineBreak
'</html>' sLineBreak;
end
else if ARequestInfo.Document = '/image' then
begin
ImageLock.Enter;
try
// TODO: check request's 'If-Modified-Since' and 'If-None-Match'
// headers, send 304 if the current capture hasn't changed yet...
ReadyImageStream.Position := 0;
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'image/png';
AResponseInfo.ContentStream := ReadyImageStream;
AResponseInfo.FreeContentStream := False;
// need to send the ContentStream inside the read lock...
AResponseInfo.WriteHeader;
AResponseInfo.WriteContent;
finally
ImageLock.Leave;
end;
end
else begin
AResponseInfo.ResponseNo := 404;
end;
end;
To further optimize this, you might consider changing the HTML to use a client-side AJAX or WebSocket script to request and display the latest image at regular intervals without having to reload the entire HTML page each time.
For instance, if you were to use a WebSocket, you could add a WebSocket server to your code (Indy doesn't have one at this time, but there are 3rd party implementations floating around) and have the WebSocket server push out a new capture to the browser whenever it is ready, without having to wait for an HTTP request to arrive first.