Skip to content

Embedded web browser panel and web bridge API - #349

Open
bdeluca wants to merge 5 commits into
AcademySoftwareFoundation:developfrom
bdeluca:pr/web-panel
Open

bdeluca wants to merge 5 commits into
AcademySoftwareFoundation:developfrom
bdeluca:pr/web-panel

Conversation

@bdeluca

@bdeluca bdeluca commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Adds the Web Browser panel and the bridge API that lets a page loaded in it drive xSTUDIO. Experimental and not fully tested; the API may change. Off unless built with BUILD_WEBENGINE=ON; with it off the panel and bridge are never created. Built and used on Ubuntu 26.04 and Windows, not yet built on macOS or Rocky 9.

1. Web browser panel (BUILD_WEBENGINE, default OFF)

  • CMake option; ON links Qt WebEngineQuick, OFF links nothing and registers nothing.
  • XsWebBrowserPanel.qml: back, forward, reload/stop, URL field (trims pasted text), Home. Remembers the page it was on across runs, stored with the layout. Page console goes to the xSTUDIO log as [web].
  • Preferences tab "Web Browser": Home Page, Render Pages on CPU.
  • --web-disable-gpu flag, same effect as the preference. Needed on Linux/NVIDIA where WebEngine's Vulkan fallback leaves the panel black.
  • Docs: user guide page, Ubuntu build guide section.

2. Bridge stub

  • WebBridgeUI registered as xstudioWebBridge over QWebChannel, with ping().
  • qwebchannel.js is injected into every page, so http-served tools need no library.
  • Bundled webchannel_test.html as the default home page.

3. xstudio.css

  • xSTUDIO's palette, scrollbars and form controls as CSS custom properties named after XsStyleSheet, for pages that should look like part of the app.
  • The test page links it.

4. Bridge API v1: how a call works, and taking a file to a timeline

  • One call per Python API call, each returning a request id, outcome on result(id, res). Calls run in call order.
  • Handles are UUID strings; the bridge rejects any UUID it did not hand out.
  • findPlaylist, createPlaylist, createTimeline, insertVideoTrack, insertAudioTrack, addMedia, insertClip, showTimeline, selectLayout, selectPanel, version.

5. Bridge API v2

  • Build: setMediaRate, insertGap.
  • Read-back: listPlaylists, listContainers, listMedia, findTimeline, playheadState, selectedMedia.
  • Playback: play, setFrame, setLoopRange, setLoopMode, setCompareMode.
  • Editing: removeClips, moveClips, splitClip, clearTimeline, setItemEnabled, setItemName, setItemFlag.
  • Media and playlists: setMediaFlag, removeMedia, clearPlaylist, removePlaylist, setSelection.
  • Contact sheets: createContactSheet, addMediaToContactSheet.
  • Session and user: setViewedContainer, save, saveAs, notify, addNote.
  • Handles now include things the page did not create; the bridge still rejects any UUID it did not hand out.

Tested

  • Ubuntu 26.04, driven from a web review tool over a high-latency link. An earlier form of the bridge was also run on Windows.
  • Not every call has been exercised from a page yet.
  • Uses nothing beyond what the studio component already does (C++17-level features, Qt 6.5 APIs, CAF 1.0).

New "Web Browser" panel type built on Qt WebEngine, off by default.
With BUILD_WEBENGINE=OFF nothing links WebEngine, the panel is not
registered and its preferences are not installed. With it ON the
launcher enables shared OpenGL contexts and initialises WebEngine
before the QApplication, and the panel appears in the panel-type list.

The panel has back, forward, reload/stop, a URL field and Home. The URL
field trims its text, since a pasted URL with a leading or trailing
space failed to load. The home page is a preference, defaulting to
about:blank. The page the
panel is on is stored with the layout, so a panel comes back to it on
the next run rather than to the home page. The page's JavaScript
console is forwarded to the xSTUDIO log with a [web] prefix.
QML gates the registration on a webEngineAvailable context property,
since it cannot see the compile-time option.

Rendering. On the machine this was developed on (Linux, NVIDIA, Qt
6.10.2, GNOME on Wayland) the panel is black with default settings, and
Qt's qt.webenginecontext.debug logging reports "GBM is not supported
... Fallback to Vulkan rendering". These controls exist for that:

- "Render Pages on CPU" preference and the --web-disable-gpu flag: both
  append --disable-gpu to QTWEBENGINE_CHROMIUM_FLAGS before WebEngine
  initialises, keeping any flags already set. Meant for machines where
  the GPU path gives a black panel; pages render on the CPU.
- QTWEBENGINE_FORCE_USE_GBM=1: Qt's own environment variable to use GBM
  buffer sharing anyway. Meant to get GPU rendering on NVIDIA. It is
  documented in the user guide, not set by xSTUDIO.
- QTWEBENGINE_CHROMIUM_FLAGS: passes any other Chromium switch.

These have only been tried on that one machine: CPU rendering showed
pages; FORCE_USE_GBM showed pages on the Wayland platform and crashed
with QT_QPA_PLATFORM=xcb under Xwayland. Other GPUs, drivers, Qt
versions and a native X11 session have not been tested.

Signed-off-by: Ben de Luca <bdeluca@gmail.com>
Pages loaded in the web browser panel can now reach a native object,
xstudioWebBridge, via QWebChannel. It is a stub with a single ping()
that returns "pong", enough to prove the channel end to end; the real
API (load media, build timelines) comes later.

The panel's home page default changes from about:blank to a bundled
test page that does the QWebChannel handshake, calls ping() and shows
the reply.

WebBridgeUI is always compiled into the studio qml component because
that component globs its sources; it has no WebEngine dependency and is
only registered as a context property when BUILD_WEBENGINE is on. It
is registered on the channel explicitly by name because a context
property cannot carry the WebChannel.id attached property.

The panel injects Qt's qwebchannel.js into every page as a user script,
since a page served over http cannot load qrc: resources; a web tool
needs no library of its own.

Signed-off-by: Ben de Luca <bdeluca@gmail.com>
A page loaded in the panel is drawn by Chromium, so it gets a browser's
colours and controls unless told otherwise and reads as a web page
pasted into the application. Linking xstudio.css first gives it
xSTUDIO's palette, scrollbars and form controls; the page then writes
only its own layout.

The values are read off XsStyleSheet.qml at the shipped preference
defaults, and each custom property is named after the XsStyleSheet
property it carries, so refreshing the file is a re-read of that one.
It is self-contained so a web server can serve it next to its own
pages.

The bundled channel test page now links it instead of carrying its own
colours, so the panel's default page shows the file working.

Signed-off-by: Ben de Luca <bdeluca@gmail.com>
WebBridgeUI grows from a ping() stub into the JavaScript API a page in
the web browser panel uses to drive xSTUDIO. This sets how the API
works and adds enough calls to take a file to a timeline on screen.

How a call works: QWebChannel calls cannot block, so every method
returns a request id and delivers its outcome on a result(id, res)
signal emitted from the object's own thread. res always has ok and, on
failure, error; calls that create something add uuid. Calls run on one
worker thread, in call order. ping() and version() answer directly.

Handles: objects are named by UUID strings. The bridge keeps a map per
kind (playlist, timeline, track, media) of what it has handed out and
rejects any other UUID. The maps are behind a mutex, since addMedia
records its media from another thread.

addMedia is the one call that does not wait on the worker thread, since
a file can take a while to open: it goes on a queue, the mailbox of a
small actor, and is answered on result() when the playlist replies, so
a slow file does not hold up later calls. It is sent as
add_media(name, uri), the way files given on the command line are
added, so the playlist lists the item straight away, in call order, and
replies once the file has been read.

The calls: it does no more than the Python API does, one bridge call
per Python call: findPlaylist, createPlaylist, createTimeline,
insertVideoTrack, insertAudioTrack, addMedia, insertClip (which also
reports the clip's length) and showTimeline. Two calls hand the screen
back to the viewer afterwards: selectLayout switches the main window
layout and selectPanel brings the first tab of a panel type to the
front; both are relayed to QML through signals, since layouts and tabs
live there. version() reports the release and a bridge API number so a
web tool can ask for a newer xSTUDIO.

Workflow logic stays out of C++ on purpose: how a payload becomes
tracks and clips is the web page's job, since one web server is easier
to update than every installed xSTUDIO, and this API is public while
any given page is not. The bundled test page only proves the channel,
ping() and version(); no sample payload, media or recipe, those belong
to the tools built on the API.

Signed-off-by: Ben de Luca <bdeluca@gmail.com>
Bridge API version 2. Pages could take a file to a timeline but not
finish building one, see what was in the session, drive playback,
change what they had built, or tell the user anything. Every call here
works the way the first set does: a request id back, the outcome on
result(id, res), one Python API call each.

Reading back: listPlaylists, listContainers, listMedia, findTimeline,
playheadState and selectedMedia. These hand the page handles to things
it did not create, so a page that can call them can reach the whole
session; the bridge still rejects any UUID it did not hand out itself.
findPlaylist already gave out such handles. listMedia and selectedMedia
describe each item the same way, including the image source's URI as
xSTUDIO holds it, so a page can tell whether a file is already in a
playlist before adding it again; the URI is empty for an item still
being probed.

Building: setMediaRate, to be set before adding media, and insertGap;
a builder sizes gaps from the clip length insertClip reports.

Playback on a timeline's playhead: play, setFrame, setLoopRange,
setLoopMode and setCompareMode. Editing: removeClips, moveClips,
splitClip, clearTimeline, and setItemEnabled / setItemName /
setItemFlag on any track, clip or gap the bridge created. Media and
playlists: setMediaFlag, removeMedia, clearPlaylist, removePlaylist,
setSelection. Contact sheets, asked for by a review page that mirrors
a desktop script building one next to the timeline: createContactSheet
and addMediaToContactSheet, sharing the media the playlist already
holds. Session: setViewedContainer for any playlist, timeline or
contact sheet handle, save and saveAs. Telling the user: notify (info,
warn, processing) and addNote, which creates a bookmark on a media
item with all its fields in one call.

Results ride on the existing result(id, map) signal; list results are
arrays of maps, which QWebChannel serialises as JSON. Three lookups
were added once for all of these: timeline to playhead, playlist to
selection actor, and a map of every track, clip and gap the bridge
made so one call can address any of them.

Tested on Linux from a review page against a production media server.

Signed-off-by: Ben de Luca <bdeluca@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant