# BrightScript Engine API

The engine worker library (`brs.worker.js`), has a programmable interface (`brs.api.js`), built in `packages/browser/lib/`, with the goal to facilitate the integration of the engine into web based applications.

Check the [documentation](./integrating.md) to learn how to start using it. The only pre-requisites are to expose, on the default `document`, a `canvas` object named `display` and a `video` object named `player`, and optionally, if you want to show the performance statistics, you also need to expose a `div` object named `stats`.

## Methods

| Method | Description | Parameters / Details|
| --- | --- | --- |
|async initialize(`customDeviceInfo?`, `options?`)|Initializes the engine simulated device|<ul><li>`customDeviceInfo?` (object): customized device information, see [/src/core/common.ts](/src/core/common.ts) for valid properties.</li><li>`options?` (object): init options, valid properties are:<br>- `showStats`(boolean): if `true` the performance statistics overlay will be shown over the display when the app is running, default is `false`.<br>- `disableDebug`(boolean): if `true` prevents any debug command/data to be sent or received by the engine, for production to avoid code injection, default is `false`.<br>- `debugToConsole`(boolean): if `false` prevents messages to be sent to the `console`, you still can debug messages via `debug` event, default is `true`.<br>- `disableKeys`(boolean): if the engine is running on a device with no keyboard, or you don't need keyboard control, set this option to `true` to disable keyboard control, default is `false`<br>- `customKeys`(Map): a custom map of keyboard keys to add/remove from the remote control simulation, see [/src/api/control.ts](/src/api/control.ts) for the default mappings.<br>- `disableGamePads`(boolean): Set this option to `true` if you want to disable the game pad control support, default is `false`<br>- `customPadButtons`(Map): a custom map of GamePad buttons (0-31) to add/remove from the remote control simulation, see [/src/api/control.ts](/src/api/control.ts) for the default mappings.</li>|
|subscribe(`observerId`, `observerCallback`)|Subscribes to the engine events (see list below) |<ul><li>`observerId` (string): identifier of the subscriber process.</li><li>`observerCallback` (function): callback function to receive the events from the engine.</li>|
|unsubscribe(`observerId`)|Unsubscribes to the engine events|<ul><li>`observerId` (string): identifier of the subscriber process.</li>|
|execute(`filePath`, `fileData`, `options?`, `deepLink?`)|Loads and run an app package (`.zip`/`.bpk`), a source code file (`.brs`) or plain text BrightScript code|<ul><li>`filePath` (string): path of the loaded file, make sure extension is `.zip` or `.bpk` if loading a full app.</li><li>`fileData` (ArrayBuffer/Blob/string): contents of the file or just a string with BrightScript code.</li><li>`options?` (object): execution options, valid properties are:<br>- `clearDisplayOnExit` (boolean): if `false` the display will keep the last image when app ends, default is `true`.<br>- `muteSound` (boolean): if `true` the engine will mute all audio but events are still raised, default is `false`.<br>- `entryPoint` (boolean): Change the property in `DeviceInfo` with same name, if `true` will raise a warning when the app has no entry point, functions `Main()` or `RunUserInterface()`, default is `true`.<br>- `debugOnCrash` (boolean): Change the property in `DeviceInfo` with same name, if `true` stops on the Micro Debugger when a crash happens, default is `false`.<br>- `logRendezvous` (boolean): if `true` traces every SceneGraph cross-thread rendezvous as `debug` events, same as `setRendezvousLog` below, default is `false`.<br>- `password` (string): the password to decrypt a `.bpk`, or encrypt a `.zip` package, default is "".</li></ul><ul><li>`deepLink?` (Map): Parameters to be passed to the application as deep link.</li></ul>|
|terminate(`reason`)|Terminates the current app/source code execution|<ul><li>`reason` (string): the reason for the termination to be shown on debug.</li>|
|redraw(`fullScreen`, `width?`, `height?`, `dpr?`)|Requests a display redraw (always keeps the aspect ratio based on display mode)|<ul><li>`fullScreen` (boolean): Flag to inform if the full screen mode is activated.</li><li>`width?` (number): width of the canvas, if not passed uses `globalThis.innerWidth`.</li><li>`height?` (number): height of the canvas, if not passed uses `globalThis.innerHeight`.</li><li>`dpr?` (number): device pixel ratio, if not passed uses `globalThis.devicePixelRatio`.</li>|
|setDisplayMode(`mode`)|Configures the display mode (if an app is running will reset the simulated device) |<ul><li>`mode` (string): supported modes are `"480p"` (SD), `"720p"` (HD) or `"1080p"`(FHD).</li>|
|getDisplayMode()|Returns the current display mode. ||
|setOverscanMode(`mode`)|Configures the overscan mode. |<ul><li>`mode` (string): supported modes are `"disabled"`, `"guidelines"` or `"overscan"`.</li>|
|getOverscanMode()|Returns the current overscan mode.||
|setCaptionMode(`mode`)|Configures the closed caption mode. |<ul><li>`mode` (string): supported modes are `"Off"`, `"On"`, `"Instant replay"` or `"When mute"`.</li>|
|getCaptionMode()|Returns the current closed caption mode.||
|setCaptionStyle(`style`) |Configures the closed caption style options. |<ul><li>`style` (object[]): The caption styles array with each option as `{id: string, style: string}`, see options in [Roku Documentation](https://developer.roku.com/dev/docs/ifdeviceinfo#getcaptionsoptionoption-as-string-as-string).</li></ul>|
|getScreenshot()|Returns an `ImageData` with the latest rendered screenshot or `null` if not available.||
|setFrameNotify(`enabled`)|Enables or disables the `framePainted` and `frameCleared` events (see list below). Disabled by default, so it costs nothing when unused. Enable it if you mirror the display elsewhere, for example into a video stream, and need to know when it changed instead of polling for it.|<ul><li>`enabled` (boolean): if `true` every display repaint is reported.</li>|
|getDisplayBuffer()|Returns the `OffscreenCanvas` the visible display is drawn from, or `null` if the display is not initialized. Unlike the `display` canvas it has no overscan guidelines and is at the resolution the app actually renders at, which makes it the canvas to copy when mirroring the screen. That resolution is the app's screen size (`roScreen`/`roSGScreen`), **not** necessarily the display mode's: `CreateObject("roScreen", true, 640, 480)` renders 640x480 in any mode. It is the live buffer, so treat it as read-only; the object stays valid for the session, but its dimensions change with the app's screen, so re-read them on each frame (a `resolution` event is raised before every change).||
|enableStats(`state`)|Enables or disables the performance stats overlay |<ul><li>`state` (boolean): if `true` performance statistics will be shown over the display, on the top left.</li>|
|setAudioMute(`mute`)|Mutes or un-mutes the audio during app execution|<ul><li>`mute` (boolean): if `true` the executing app audio and video will be muted.</li>|
|getAudioMute()|Returns `true` if the audio is muted ||
|setRendezvousLog(`enable`)|Enables or disables tracing of SceneGraph cross-thread rendezvous — the equivalent of Roku's `logrendezvous`. Each field read, field write and method call that crosses a thread reports its action, target and duration as a `debug` event to subscribers. Takes effect immediately, including on `Task` threads already running, and persists across `execute()` calls until changed. Verbose on task-heavy apps.|<ul><li>`enable` (boolean): if `true` cross-thread rendezvous will be traced.</li>|
|getRendezvousLog()|Returns `true` if rendezvous tracing is enabled ||
|getSerialNumber()|Returns the device serial number, this value changes when the `deviceData.deviceModel` is updated. ||
|setControlMode(`controls`)|Enable/Disable the remote control simulation|`controls` (object) contains following properties:<ul><li>`keyboard` (boolean): if `true` enables the keyboard control.</li><li>`gamePads` (boolean): if `true` enables game pad control.</li>|
|getControlMode()|Returns an `object` with the control mode flags | Same options sent to the `setControlMode` above.|
|setCustomKeys(`keysMap`)|Sends a custom map of keyboard keys to add/remove from the remote control simulation |<ul><li>`keysMap` (Map): see [/src/api/control.ts](/src/api/control.ts) for the default mappings.</li>|
|setCustomPadButtons(`buttonsMap`)|Sends a custom map of game pad buttons to add/remove from the remote control simulation |<ul><li>`buttonsMap` (Map): see [/src/api/control.ts](/src/api/control.ts) for the default mappings.</li>|
|sendKeyDown(`key`, `remote?`, `index?`)|Sends a remote control key down event to the engine|<ul><li>`key` (string): one of valid key codes (see [Roku doc](https://developer.roku.com/dev/docs/onkeyevent)).</li><li>`remote` (number): one of valid remote types (see [/src/core/common.ts](/src/core/common.ts)) default is `ECP`.</li><li>`index` (number): The index of the remote control, default is 0.</li>|
|sendKeyUp(`key`, `remote?`, `index?`)|Sends a remote control key up event to the engine|<ul><li>`key` (string): one of valid key codes (see [Roku doc](https://developer.roku.com/dev/docs/onkeyevent)).</li><li>`remote` (number): one of valid remote types (see [/src/core/common.ts](/src/core/common.ts)) default is `ECP`.</li><li>`index` (number): The index of the remote control, default is 0.</li>|
|sendKeyPress(`key`, `delay?`, `remote?`, `index?`)|Sends a remote control key press event to the engine|<ul><li>`key` (string): one of valid key codes (see [Roku doc](https://developer.roku.com/dev/docs/onkeyevent)).</li><li>`delay` (number): the delay (in milliseconds) between sending Key Up and Key Down (default is 300ms).<li>`remote` (number): one of valid remote types (see [/src/core/common.ts](/src/core/common.ts)) default is `ECP`.</li><li>`index` (number): The index of the remote control, default is 0.</li>|
|sendInput(`params`)|Sends custom events to the current application to be raised by `roInput` component | <ul><li>`params` (object): An object with the parameters in the format `{'string': 'string', ...}`</li></ul>|
|mountExt(`zipData`)|Mounts a `zip` file as the external volume `ext1:` | <ul><li>`zipData` (ArrayBuffer): The contents of a zip file to be mounted by the engine File System</li></ul>|
|umountExt()|Unmounts the external volume `ext1:` ||
|updateMemoryInfo(`used`, `total`)|A method for the host application to inject memory usage information to the engine. |<ul><li>`used` (number): Memory used by the app (in KB)</li><li>`total` (number): Total memory available for the app (in KB)</li></ul>|
|debug(`command`)|Sends a debug command to the Engine|<ul><li>`command` (string): the Micro Debugger can be enabled sending `break` command, and after that, any valid BrightScript expression or [debug commands](https://developer.roku.com/dev/docs/debugging#brightscript-console-port-8085-commands) can be sent. You can also send `pause` to interrupt the interpreter, for instance, when the app loses focus. The command `cont` restarts the app. You can use this method on the browser console to debug your app.|
|getDebugState()|Returns `true` if debug functionality is enabled, `false` if disabled||
|setDebugState(`enabled`)|Enables or disables the debug functionality|<ul><li>`enabled` (boolean): if `true` enables debug commands and data to be sent/received by the engine, if `false` disables debug functionality for production to avoid code injection.</li>|
|getVersion()|Returns the version of the API library ||
|isEncryptedPackage(`fileData`)|Returns `true` if the data is an encrypted package container created by `encryptPackage()` (a plain `.zip` or a legacy `.bpk` returns `false`)|<ul><li>`fileData` (Uint8Array): the raw package contents.</li></ul>|
|async encryptPackage(`zipData`, `password`)|Wraps a package in an AES-256-CTR container so even the plaintext assets are unreadable at rest. Used when creating a `.bpk`|<ul><li>`zipData` (Uint8Array): the package contents to encrypt.</li><li>`password` (string): the raw AES-256 key, **exactly 32 characters**.</li></ul>|
|async decryptPackage(`fileData`, `password`)|Unwraps a container produced by `encryptPackage()`. Plain zips and legacy `.bpk` files are returned untouched, so it is safe to call unconditionally|<ul><li>`fileData` (Uint8Array): the raw package contents.</li><li>`password` (string): the same 32-character key used to encrypt.</li></ul>|
|requestBitmaps(`timeoutMs`)|Requests the current texture-memory state (the list of bitmaps and registered fonts loaded into memory, plus memory totals), equivalent to a Roku device's [`query/r2d2-bitmaps`](https://developer.roku.com/dev/docs/external-control-api) ECP endpoint. Returns a `Promise` that resolves with the data (or `undefined` if no app is running or the request times out). The same data is also dispatched to subscribers via the `bitmaps` event. **Requires developer mode**: the app must run with `debugOnCrash: true` (see `execute`), otherwise the bitmap list is empty. |<ul><li>`timeoutMs` (number, optional): how long to wait for a response before resolving with `undefined` (default `2000`).</li></ul>|
|setRendezvousTracking(`enable`)|Enables or disables ECP-style SceneGraph rendezvous tracking — the equivalent of Roku's `sgrendezvous/track` and `sgrendezvous/untrack` ECP commands. Independent of `setRendezvousLog`: instead of printing console lines, it collects structured `RendezvousEvent` records (`{id, startTm, endTm, line, file}`) for `requestRendezvousEvents()` and the `rendezvous` event, so a host application can implement its own ECP `sgrendezvous` route by calling into this API. Enabling clears any previously queued events. **Requires developer mode**, matching Roku's requirement for this ECP command.|<ul><li>`enable` (boolean): if `true` rendezvous events will be tracked.</li></ul>|
|getRendezvousTracking()|Returns `true` if ECP-style rendezvous tracking is enabled ||
|requestRendezvousEvents()|Returns the rendezvous events queued since tracking was enabled or since the previous call to this function, then drains the queue — mirroring the "since tracking was enabled, or since the previous call" semantics of Roku's `query/sgrendezvous`.|Returns `{events: RendezvousEvent[], dropCount: number}`; `dropCount` counts events dropped because the queue exceeded its 1,000-event cap.|

## Common API exports

The browser API now re-exports a small set of shared types and constants so host applications can configure the simulated device and extension pipeline without digging into internal modules.

| Export | Description | Usage Examples |
| --- | --- | --- |
| `DeviceInfo` | Type describing the simulated Roku device, including new `extensions?: Map<SupportedExtension, string>` for host-provided extension wiring. | Use to type-check custom device overrides passed to `initialize(customDeviceInfo)` and set each value to the worker-accessible path (e.g., `"./brs-sg.js"`).
| `DefaultDeviceInfo` | Baseline `DeviceInfo` object used by the engine. | Clone or spread this object before applying overrides so you only touch the fields you care about.
| `Platform` | Runtime detection helper indicating whether the interpreter runs in the browser, worker, or Node. | Gate host-specific logic (e.g., only attach audio/video handlers when `Platform.inBrowser` is `true`).
| `AppExitReason` | Enum describing the reason the previous app exited. | Compare against `deviceData.appList` entries or populate deep-link metadata with the appropriate reason string.
| `SupportedExtension` | Enum of known first-party extensions (`brs-scenegraph`, future SDK1/BrightSign IDs). | Reference when registering extensions in `DeviceInfo.extensions` so you avoid typos.

## Events

| Event      | Description                                      | Data Type                         |
|------------|--------------------------------------------------|-----------------------------------|
| loaded     | Triggered when the source code data has finished loading | object: `{id: string, file: string, title: string, subtitle: string, version: string, running: boolean}`|
| icon       | Triggered when the zip file is loaded and manifest links to a valid icon for the app | base64: A base64 string of the app icon, extracted from the zip file. |
| registry   | Triggered when the app updates the registry | Map: the registry with all recent recent updates. |
| started    | Triggered when the engine started running the source code | object: `{id: string, file: string, title: string, subtitle: string, version: string, running: boolean}` |
| closed     | Triggered when the engine terminated the execution of the source code | string: the exit reason based on [Roku documentation](https://developer.roku.com/docs/developer-program/getting-started/architecture/dev-environment.md#lastexitorterminationreason-parameter) |
| reset      | Triggered when the `RebootSystem()` function is executed from the engine | null: Nothing is returned as data |
| control    | Triggered when a control key is sent to the engine | object: `{key: string, mod: number}`. The property `key` contains an [ECP key code](https://developer.roku.com/docs/developer-program/dev-tools/external-control-api.md#keypress-key-values) and `mod` contains `0` for key down or `100` for key up |
| captionMode| Triggered when the closed caption mode is changed | string: The new caption mode, options are "Off", "On", "Instant replay", "When mute" |
| redraw     | Triggered when the display canvas is redrawn/resized | boolean: If `true` the display canvas is in **full screen** mode |
| resolution | Triggered when the emulated screen resolution changes (controlled via BrightScript) | object: `{width: integer, height: integer}` |
| framePainted | Triggered when the display canvas is repainted from the display buffer, but only while `setFrameNotify(true)` is active. The engine repaints on demand rather than on a timer, so this is the only way to know when the screen actually changed. Raised after the repaint, so `getDisplayBuffer()` holds the frame just drawn. Named apart from the `brs-node` library's `frame` event, which reports the raw worker frame as an `ImageData` | number: a monotonic frame counter, restarted at each `setFrameNotify(true)` call |
| frameCleared | Triggered when the display canvas is blanked, for instance when an app exits or the app disables the display via `roAppManager.setDisplayDisabled()`, but only while `setFrameNotify(true)` is active. Separate from `framePainted` because the display buffer is deliberately left untouched, so it still holds the last drawn image: an embedder mirroring the screen has to blank its own copy rather than copy the buffer. Consecutive blanks are collapsed into one event | null: Nothing is returned as data |
| display    | Triggered when the display mode is changed via `setDisplayMode()` (a running app is terminated first) | string: the new display mode, options are `"480p"`, `"720p"` or `"1080p"` |
| launch     | Triggered when the methods `roAppManager.launchApp()` or `roNDK.start()` (with `SDKLauncher` app) are called from BrightScript | object: `{app: string, params: Map}`. |
| browser    | Triggered when the library sub `RokuBrowser` is called from BrightScript | object: `{url: string, width: integer, height: integer}`. The parameters `width` and `height` defines the dimensions of the browser window |
| debug      | Triggered when debug messages arrive from the worker library (BrightScript Interpreter) | object: `{level: string, content: string}`, levels are: `print`, `beacon`, `debug`, `warning`, `error`, `stop`, `pause`, `continue` |
| version    | Triggered when the worker library (BrightScript Interpreter) reports its version | string: the worker library version |
| bitmaps    | Triggered when texture-memory data arrives from the worker library in response to `requestBitmaps()` | object: `{timestamp: number, channelId: string, systemMemory: {used: number}, textureMemory: {used: number, available: number, max: number}, bitmaps: Array<{address: string, width: number, height: number, bpp: number, size: number, name: string}>}` |
| rendezvous | Triggered for each SceneGraph cross-thread rendezvous while `setRendezvousTracking(true)` is active | object: `{id: number, startTm: number, endTm: number, line: number, file: string}` |
| error      | Triggered when the any execution exception happens on the API library | string: The message describing the error |
