Search code examples
delphiscreenshotindyhttpservertedgebrowser

TEdgeBrowser for Delphi - capture tab content and relay it


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" class="fullscr">'+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;

Solution

  • 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" class="fullscr">'+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.