Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Client/server CEF plugin for **open.mp** and **SA-MP**.
- Pawn natives and callbacks
- JavaScript <-> Pawn/C# event bridge
- Focus, cursor, input and audio controls
- Game screen capture exposed to CEF interfaces as RGBA frames
- Custom ESC menu support
- Custom TAB menu (scoreboard) support
- Secure client/server handshake and UDP transport
Expand Down Expand Up @@ -83,6 +84,38 @@ Short version:
- Browser lifecycle
- Hidden browser navigation
- Stress testing
- In-game camera preview (`/camera` in the demo gamemode)

## Game screen capture

CEF pages can receive the current GTA frame through `window.cef.screen`. Frames
are captured before omp-cef overlays are rendered, preventing recursive capture
when a preview is displayed inside a browser.

```js
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d', { alpha: false });

cef.screen.start((frame) => {
canvas.width = frame.width;
canvas.height = frame.height;

const pixels = new Uint8ClampedArray(frame.data);
context.putImageData(
new ImageData(pixels, frame.width, frame.height),
0,
0
);
}, 640, 360, 15);

addEventListener('beforeunload', () => cef.screen.stop(), { once: true });
```

`cef.screen.start(callback, width, height, fps)` returns frames with `data`
(`ArrayBuffer`, RGBA), `width`, `height`, `sequence`, `timestamp`, and `format`.
Width and height are clamped to `16..1280` and `16..720`; FPS is clamped to
`1..30`. Defaults are `640x360` at `15 FPS`. High resolutions may reduce the
effective FPS, and capture stops automatically when the page or browser closes.

## Contributing

Expand Down
25 changes: 25 additions & 0 deletions pawn.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"user": "aurora-mp",
"repo": "omp-cef",
"website": "https://github.com/aurora-mp/omp-cef",
"preset": "openmp",
"include_path": "src/server",
"resources": [
{
"name": "^cef-omp-v(.*)-x86\\.zip$",
"platform": "windows",
"archive": true,
"plugins": [
"components/Cef.dll"
]
},
{
"name": "^cef-omp-v(.*)-x86\\.zip$",
"platform": "linux",
"archive": true,
"plugins": [
"components/Cef.so"
]
}
]
}
3 changes: 2 additions & 1 deletion src/client/core/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ void App::Tick()

focus_.Update();
browser_.TickGameData();
browser_.CaptureScreen();
browser_.RenderAll();

if (pending_clear_chat_)
Expand Down Expand Up @@ -605,4 +606,4 @@ void App::Shutdown()
audio_.Shutdown();

LOG_INFO("Good bye! See you soon.");
}
}
20 changes: 20 additions & 0 deletions src/client/core/browser/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,26 @@ bool BrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> /*browser*/,

const std::string msg_name = message->GetName();

if (msg_name == "cef_screen_capture_start")
{
CefRefPtr<CefListValue> args = message->GetArgumentList();
const int width = args->GetSize() > 0 && args->GetType(0) == VTYPE_INT
? args->GetInt(0) : 640;
const int height = args->GetSize() > 1 && args->GetType(1) == VTYPE_INT
? args->GetInt(1) : 360;
const int fps = args->GetSize() > 2 && args->GetType(2) == VTYPE_INT
? args->GetInt(2) : 15;

manager_.StartScreenCapture(browserId_, width, height, fps);
return true;
}

if (msg_name == "cef_screen_capture_stop")
{
manager_.StopScreenCapture(browserId_);
return true;
}

if (msg_name == "emit_event")
{
CefRefPtr<CefListValue> args = message->GetArgumentList();
Expand Down
97 changes: 97 additions & 0 deletions src/client/core/browser/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ void BrowserManager::Shutdown()

LOG_DEBUG("Shutting down CEF Browser manager...");

screen_capture_.StopAll();
screen_capture_.OnDeviceLost();
browsers_.clear();
worldRenderers_.clear();
entityToBrowserId_.clear();
Expand Down Expand Up @@ -1141,10 +1143,104 @@ void BrowserManager::OnBrowserCreated(int id, CefRefPtr<CefBrowser> browser)
void BrowserManager::OnBrowserClosed(int id)
{
player_stats_poll_.erase(id);
screen_capture_.Stop(id);
pending_.erase(id);
browsers_.erase(id);
}

void BrowserManager::StartScreenCapture(int browserId, int width, int height, int fps)
{
if (!GetBrowserInstance(browserId))
return;

const auto config = screen_capture_.Start(browserId, width, height, fps);
LOG_INFO("[ScreenCapture] Started for browser {} ({}x{} @ {} FPS)",
browserId, config.width, config.height, config.fps);
}

void BrowserManager::StopScreenCapture(int browserId)
{
screen_capture_.Stop(browserId);
LOG_INFO("[ScreenCapture] Stopped for browser {}", browserId);
}

void BrowserManager::CaptureScreen()
{
if (isCefUpdatesPaused_ || is_shutting_down_ || !screen_capture_.HasSubscriptions())
return;

auto* device = RenderManager::Instance().GetDevice();
if (!device)
return;

auto frames = screen_capture_.CaptureDueFrames(device, ::GetTickCount64());
for (auto& frame : frames)
{
const int browser_id = frame->browser_id;
const uint64_t generation = frame->generation;
if (!CefPostTask(TID_UI, base::BindOnce(
&BrowserManager::DispatchScreenFrameOnUi,
base::Unretained(this),
std::move(frame))))
{
screen_capture_.CompleteFrame(browser_id, generation);
}
}
}

void BrowserManager::DispatchScreenFrameOnUi(std::shared_ptr<CapturedScreenFrame> frame)
{
CEF_REQUIRE_UI_THREAD();

if (!frame)
return;

const auto complete = [this, &frame]() {
screen_capture_.CompleteFrame(frame->browser_id, frame->generation);
};

if (is_shutting_down_ ||
!screen_capture_.IsCurrent(frame->browser_id, frame->generation) ||
!frame->rgba || frame->rgba->empty())
{
complete();
return;
}

auto* instance = GetBrowserInstance(frame->browser_id);
if (!instance || !instance->browser || !instance->browser->IsValid())
{
complete();
return;
}

CefRefPtr<CefFrame> main_frame = instance->browser->GetMainFrame();
if (!main_frame || !main_frame->IsValid())
{
complete();
return;
}

CefRefPtr<CefBinaryValue> pixels = CefBinaryValue::Create(
frame->rgba->data(), frame->rgba->size());
if (!pixels)
{
complete();
return;
}

CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create("cef_screen_capture_frame");
CefRefPtr<CefListValue> args = message->GetArgumentList();
args->SetBinary(0, pixels);
args->SetInt(1, frame->width);
args->SetInt(2, frame->height);
args->SetInt(3, static_cast<int>(frame->sequence));
args->SetDouble(4, frame->timestamp_ms);
main_frame->SendProcessMessage(PID_RENDERER, message);

complete();
}

void BrowserManager::ClearPendingPaint(int id)
{
auto it = pending_.find(id);
Expand Down Expand Up @@ -1819,6 +1915,7 @@ void BrowserManager::OnDeviceLost()
// Stop CEF rendering during device reset
// This prevents CEF from trying to update textures while they're invalid
isCefUpdatesPaused_ = true;
screen_capture_.OnDeviceLost();

// Release all browser View resources (2D overlays)
for (auto& [id, instance] : browsers_)
Expand Down
7 changes: 7 additions & 0 deletions src/client/core/browser/manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "include/base/cef_callback.h"
#include "include/cef_app.h"
#include "include/cef_browser.h"
#include "rendering/screen_capture.hpp"
#include "rendering/view.hpp"
#include "rendering/world_renderer.hpp"
#include "shared/packet.hpp"
Expand Down Expand Up @@ -154,6 +155,10 @@ class BrowserManager
void OnGameFocusGained();
void OnGameFocusLost();

void StartScreenCapture(int browserId, int width, int height, int fps);
void StopScreenCapture(int browserId);
void CaptureScreen();

void ExitGame();

// Native GTA SA ESC/pause menu handling
Expand Down Expand Up @@ -225,6 +230,7 @@ class BrowserManager
void RequestVisibleBrowsersRepaint();
void SendExternalBeginFrames();
void DispatchExternalBeginFramesOnUi();
void DispatchScreenFrameOnUi(std::shared_ptr<CapturedScreenFrame> frame);
bool HandleDragMouseMove(const CefMouseEvent& event);
bool HandleDragMouseUp(const CefMouseEvent& event);
void HandleDragMouseMoveOnUi(CefMouseEvent event);
Expand Down Expand Up @@ -295,6 +301,7 @@ class BrowserManager
std::bitset<256> key_allowed_{};

std::unordered_map<int, PlayerStatsPollState> player_stats_poll_;
ScreenCaptureManager screen_capture_;

// Native GTA SA ESC/pause menu handling
EscapeMenuController escape_menu_;
Expand Down
Loading
Loading