Skip to content

[scratch] pre-commit check for project/exec_producer merge commit - #6409

Closed
callumlinden wants to merge 173 commits into
developfrom
callum/exec_producer-precheck
Closed

callumlinden wants to merge 173 commits into
developfrom
callum/exec_producer-precheck

Conversation

@callumlinden

Copy link
Copy Markdown
Contributor

Throwaway PR solely to trigger the required pre-commit status check on this commit SHA before pushing it to project/exec_producer directly (repo ruleset requires pre-commit to have run, which only fires on pull_request events). Will close without merging.

callumlinden and others added 30 commits July 13, 2026 15:13
…lled llembedded browser. Creates a floater than consumes it. Currently does next to nothing - next up is to push the llembeddedbrowser::update() calls into their own thread and then flesh out the interface so we can create, destroy, update etc. different 'browser tabs'
…this appears to work fine (might need to consider double buffering the output to avoid flicker) - then the next step is to fill in more of the browser manager and browser tab stuff
…re swatch by creating a next-POT texture and drawing the output ourselves with some UV scaling
…dBrowser

debug setting, defaulted on for this experimental viewer.

Makes llembeddedbrowser multi-instance (one tab/thread/buffer per media
source instead of a single global one), wires LLViewerMediaImpl's create/
destroy/update/resize/navigate lifecycle to it alongside the existing CEF
path, and adds backend-agnostic width/height/texture-size accessors so
LLMediaCtrl's draw/layout code doesn't need to reach into LLPluginClassMedia
directly. Also: fast row-based checkerboard fill and size-scaled update
rate to bound CPU/lock cost on large (up to 4096x4096, EmbeddedBrowserMax
Width/Height) buffers, and thread-safety fixes (shared_ptr-held tabs, an
atomic pixel+size snapshot handed to the async GL upload, per-tab RNG)
found in review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r, proving out real CEF frames over shared memory in place of the checkerboard placeholder.

LLEmbeddedBrowserTab now claims a view from an external cefshm_producer
over the control channel (see the sibling llcefshm-example/llshmframe/
llCefBrowser repos) and pumps read_latest() frames into its pixel buffer
each tick instead of generating a checkerboard; navigate()/resize() send
kSetUrl/kResize over the same channel. cefshm_protocol.h is a deliberate
byte-compatible copy of that repo's own protocol header.

Fixes found getting this working end-to-end:
- Source pixel format is BGRA (matching CEF's native OnPaint, same as the
  legacy plugin), not RGBA -- llviewermedia.cpp's embedded-browser texture
  setup had it hardcoded wrong.
- Prim-face rendering has no orientation compensation anywhere (unlike
  LLMediaCtrl's floater quad, which picks its UV winding based on the
  media source's self-reported coordinate convention) -- it just trusts
  the raw buffer's row order, which the legacy plugin has always supplied
  bottom-up. The shm pipeline hands back top-down rows, so
  LLEmbeddedBrowserTab::update() now flips on copy, and
  getMediaTextureCoordsOpenGL() now reports true for the embedded-browser
  backend to keep LLMediaCtrl's own UV winding in sync with that.
- LLViewerMediaImpl::navigateTo() could lose a race against the
  priority-driven idle pass: a UI-driven LLMediaCtrl calling navigateTo()
  before createMediaSource() ever ran for that impl would fall through to
  the legacy plugin path and permanently lock UseEmbeddedBrowser out via
  createMediaSource()'s own idempotency guard. navigateTo() now resolves
  the backend up front when neither has been decided yet.
- data: URIs need LLURI::escapePathAndData()'s payload re-escaping to
  parse correctly, same as the legacy loadURI() path already applies --
  the embedded-browser entry points were passing the raw string through
  unescaped.

Also repoints llfloaterembeddedbrowsertest.cpp's three test buttons (and
its initial create() call) at real URLs instead of the "red"/"green"/
"blue" tokens the checkerboard generator used to special-case, since those
have no meaning to a real CEF page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…browser.

destinations/avatar_welcome_pack/search/marketplace used to be
force-instantiated on login specifically to warm up their CEF instances
ahead of time -- worth revisiting whether that's still needed now that
those floaters can run on the embedded-browser backend instead of the
memory-heavier plugin.
…kend is wired into real LLMediaCtrl/prim media paths.

llfloaterembeddedbrowsertest predates LLViewerMediaImpl routing through
UseEmbeddedBrowser and was the only way to exercise create/destroy/
navigate/getPixels directly; that's now covered more realistically by the
Media Settings preview, the login screen, and real prim media, and its
own hand-rolled swatch-drawing code (unlike LLMediaCtrl) has no UV
compensation for the row-flip added in the previous commit, so it was
displaying upside-down. Removes the source files, XUI floater definition,
its two "Embedded Browser Test" menu entries, and the CMake/registration
references.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e entries, and menu items.

The previous commit deleted the floater's source/XUI files but missed
staging its LLFloaterReg::add() call, CMakeLists.txt entries, and the two
"Embedded Browser Test" menu items (menu_viewer.xml, menu_login.xml) --
completing that removal here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…vents to the embedded browser.

Extends cefshm_protocol.h (this repo's copy and llcefshm-example's
canonical one) with kScrollWheel/kKeyEvent (consumer->producer) and
kEventLoadStart/LoadEnd/TitleChanged/AddressChanged/CursorChanged
(producer->consumer). Keyboard rides LLWindowWin32::getNativeKeyData()'s
raw msg/wParam/lParam straight into llCefBrowserManager::SendKeyEvent(),
a clean fit since that's exactly the shape it already expects.

LLEmbeddedBrowserTab gains mouseMove/mouseButton/scrollWheel/keyEvent
senders and a popEvent() FIFO; LLViewerMediaImpl wires those into every
mouse/keyboard entry point and drains events each update() via
emitEvent(nullptr, ...) -- nullptr standing in for "no real
LLPluginClassMedia behind this," since a genuine plugin callback never
passes one.

That nullptr choice forced a real audit: grepped every
LLViewerMediaObserver::handleMediaEvent override for the 5 events now
emitted, and found genuine unconditional (non-debug-log) self-></
dereferences in llfloaterhelpbrowser.cpp, llfloaterwebcontent.cpp (x3),
llpaneldirweb.cpp, and llfloater360capture.cpp -- real crash risks once
embedded-browser events start flowing, not hypothetical. Patched with
fallbacks to cached state (LLMediaCtrl::getCurrentNavUrl(), and a new
backend-agnostic LLViewerMediaImpl::getMediaName()/LLMediaCtrl::
getMediaName() pair for title, mirroring the width/height/textureCoords
accessors already added). Also defensively guarded the debug-log-only
cases in llmediactrl.cpp and llviewerparcelmedia.cpp.

Two more bugs surfaced through actual interactive testing, both in
pre-existing code this session never touched before today:

- LLMediaCtrl::convertInputCoords() computed its OpenGL-coords flag via
  mMediaSource->getMediaPlugin()->getTextureCoordsOpenGL(), bypassing the
  backend-agnostic getMediaTextureCoordsOpenGL() accessor added earlier
  this session -- getMediaPlugin() is null for embedded-browser by
  construction, so this always read false, inverting mouse Y for every
  embedded-browser click/move/scroll regardless of window size.
- Once that was fixed, Y was still wrong whenever the widget was larger
  than its aspect-corrected content (mStretchToFill/mMaintainAspectRatio
  default true for every LLMediaCtrl): the flip used getRect().getHeight()
  (the full widget) instead of the locally computed, aspect-corrected
  height from calcOffsetsAndSize(), throwing Y off by exactly the
  centering/letterbox offset. Likely a longstanding latent bug for the
  legacy plugin path too whenever centering was actually active; just
  never reported.

Also raised LLEmbeddedBrowserUpdateThread::run()'s size-scaled fps floor
(10->30) and widened its full-60fps zone (512x512->1280x720): those
numbers were tuned for bounding the CPU cost of painting the old
checkerboard placeholder, not for real interactive latency, and made
real mouse-move feedback feel sluggish on anything larger than a small
thumbnail.

Scroll wheel needed one more fix on top: LLMediaCtrl::handleScrollWheel/
handleScrollHWheel gate on hasMedia(), which is hardcoded to "is there a
real plugin" (mMediaSource != NULL) and always false for embedded-browser,
silently dropping every scroll event before it could reach any of the
above. Added a narrow LLViewerMediaImpl::isUsingEmbeddedBrowser()
accessor rather than widening hasMedia() itself, which has ~60 call sites
and at least one (LLMediaCtrl::handleToolTip) that immediately follows it
with an unchecked getMediaPlugin()->getHoverText() -- widening hasMedia()
would have traded a silent scroll-wheel bug for a tooltip crash.

Known gaps, out of scope for this pass: prim-face media has no scroll
wheel support at all today, for either backend (not a regression); mouse
double-click is sent as a plain click, relying on CEF's own
consecutive-click timing rather than an explicit wire signal; horizontal
scroll is dropped (SendMouseWheelEvent has no deltaX equivalent); and
input-to-display latency still feels sluggish to the user even after the
fps-floor fix, not yet root-caused further.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverses 1537870 -- keeping destinations/avatar_welcome_pack/search/
marketplace preloaded on login after all, a deliberate call independent
of the embedded-browser work.
…lifeviewer, not secondlife-bin.

secondlife-bin is this experimental build's own binary name; the actual
release viewer (used as the legacy comparison baseline) runs as
secondlifeviewer. Also trims the example DurationMinutes from 10 to 5.
…ugin behavior.

CEF's SendMouseWheelEvent expects wheel-delta units (~120/notch), not SL's raw
per-notch click value, and its sign convention for scroll direction is the
opposite of Dullahan's for the legacy plugin path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Forwards MEDIA_EVENT_CLICK_LINK_HREF/CLICK_LINK_NOFOLLOW so links that want
a new window/tab or navigate to a custom URL scheme (e.g. secondlife://) get
handled the same way as the legacy CEF plugin, and forwards focus/blur to the
embedded browser so CEF drives caret blink and focus/blur page JS on click,
matching the plugin path's existing behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Forwards MEDIA_EVENT_PICK_FILE_REQUEST/FILE_DOWNLOAD via a new
LLEmbeddedMediaFilePicker (mirrors LLMediaFilePicker but keyed to
LLViewerMediaImpl instead of LLPluginClassMedia, since embedded browser has
no plugin pointer) so native OS open/save dialogs work the same way as the
legacy CEF plugin -- this is what llfloater360capture.cpp's image-save flow
needs once it moves to the embedded backend. Also forwards
MEDIA_EVENT_STATUS_TEXT_CHANGED (e.g. showing a hovered link's URL), guarding
every LLViewerMediaObserver::handleMediaEvent override across the viewer that
previously dereferenced self unconditionally for that event.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LLViewerMediaImpl::executeJavaScript() had no embedded-browser branch, and
LLMediaCtrl::executeJavaScript()'s hasMedia() gate is plugin-only-false, so
neither backend-agnostic path actually worked for embedded browser -- this
is what silently broke both the WebGL preview (init(...) never ran) and the
Save button (saveAsEqrImage(...) never ran) in llfloater360capture.cpp, which
bypassed both and called getMediaPlugin()->executeJavaScript() directly.
Wires the new kExecuteJavaScript command through LLEmbeddedBrowser/Tab and
the producer, widens LLMediaCtrl's gate, and simplifies llfloater360capture.cpp
to the backend-agnostic LLMediaCtrl::executeJavaScript() call it should have
used all along.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Forwards console.log/warn/error calls via a new kEventConsoleMessage command,
composed into the same "Console message: <msg> in file(<source>) at line
<n>" text MediaPluginCEF::onConsoleMessageCallback produces, so
LLMediaCtrlListener::getMediaText()'s PAGE_TEXT_EXTRACT_MARKER search keeps
working unmodified. Also stops getMediaText() hard-failing when there's no
LLPluginClassMedia, since executeJavaScript() already gates correctly for
either backend.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LLEmbeddedBrowserTab gains a mHadDisconnected flag making disconnect/
reconnect edge-triggered (fires once per outage, not once per retry tick):
update()'s existing LLReadResult::Disconnected handling pushes one
ProducerDisconnected event, connectToProducer() pushes one
ProducerReconnected event the next time it succeeds. LLViewerMediaImpl
treats ProducerDisconnected like its own MEDIA_EVENT_PLUGIN_FAILED handling
(mMediaSourceFailed, resetPreviousMediaState) but with the notification left
enabled -- unlike that case, whose notification is disabled after a past
"fires every frame" spam incident -- since edge-triggering guarantees this
one fires at most once. The popup names "Embedded Browser Provider" rather
than the legacy plugin's mime-type-derived name, since no such plugin is
involved in this failure path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the untracked local-path hack with proper use_prebuilt_binary()
installables (llshmframe, llcefbrowser), modeled on the existing
dullahan/CEFPlugin.cmake pattern. EmbeddedBrowser.cmake now defines real
ll::shmframe and ll::cefbrowser interface targets, each with an optional
LLSHMFRAME_LOCAL_BUILD_DIR/LLCEFBROWSER_LOCAL_BUILD_DIR override that
skips the autobuild fetch and links straight against a local `stage`
build, to keep fast local iteration on those repos.

llembeddedbrowser now links ll::shmframe explicitly instead of relying
on whatever was previously injecting the raw include path.

Note: the two installables currently point at local file:// archives for
testing, not a real published package -- publishing is a separate,
explicit follow-up step.
Real autobuild publishing hit a wall: both repos are private, so their
GitHub Release assets 404 for a plain unauthenticated autobuild install,
unlike cef-bin's public S3-hosted package. Until that's resolved
(pending confirmation on making the repos public), stay on local
file:// packages for both -- refreshed to match current source (the
llcefbrowser cygpath fix, llshmframe's canonical_repo correction).

canonical_repo for llshmframe now points at its real current location
(secondlife/llshmframe, moved from a personal account) regardless of
where its package is actually hosted.
Adds kEventVersionInfo to the cefshm wire protocol: cefshm_producer now
sends its CEF/Chromium version string once per slot right after
allocation, since the Viewer doesn't link CEF/llcefbrowser directly for
this path and has no other way to know which build is actually in play.
LLEmbeddedBrowser stores the most recently reported version and exposes
it alongside llshmframe's own (build-time) version.

Both now appear in the About box's Libraries section as "Embedded
Browser llShmFrame Version" / "Embedded Browser CEF Version", falling
back to "Not connected" if no producer has completed the handshake yet.
The old legacy-plugin CEF/Chromium version block (Dullahan-based) is no
longer relevant now that the embedded-browser path exists, so drop it
entirely (including the now-unused dullahan_version.h include) and
show llCefBrowser's/CEF's/Chromium's versions from the wire message in
its place instead.

Also: getShmFrameVersion() drops its redundant "llshmframe " prefix
(the About-box label already says that), and the CEF version block now
includes llCefBrowser's own version, formatted like the block it
replaces.

Note: only the English strings.xml was updated -- other locales still
reference [LIBCEF_VERSION], which will now render as a literal
unsubstituted placeholder. Pre-existing localization drift in this
fork, not something this change fixes.
Adds indra/llcefproducer (SLCefProducer.exe), a direct port of
llcefshm-example's cefshm_producer.cpp: windowless by default via a
WinMain wrapper (forwarding the MSVC CRT's __argc/__argv, since CEF's
subprocess re-exec model needs real argc/argv and WinMain's own
lpCmdLine can't provide them), with an opt-in --console debug console
toggleable via the new CefProducerShowConsole setting.

LLEmbeddedBrowser::init()/reset() (previously vestigial -- nothing
called them) now launch/kill SLCefProducer via LLProcess, gated on
UseEmbeddedBrowser; called from LLAppViewer::init()/cleanup(). The
launch is eager, not lazy, since the login screen itself renders
through this same path and needs a live producer immediately.
connectToProducer()'s existing "no producer reachable" branch now
triggers a debounced, capped auto-relaunch, reusing the disconnect
detection already built and tested on llshmframe's own heartbeat
protocol rather than adding a second liveness mechanism.

SLCefProducer.exe is packaged into llplugin/ alongside libcef.dll and
its other CEF runtime files (new LLDir::getSLCefProducerLauncher(),
mirroring getLLPluginLauncher()) rather than next to secondlife-bin.exe
-- unlike a DLL loaded from there, a standalone .exe only checks its
own directory in the default DLL search order, so it needs to actually
live where those files are.

Also refreshes the local llshmframe package registration in
autobuild.xml: a clean rebuild had fallen back to a stale local
package predating yesterday's versioning work, since the
LLSHMFRAME_LOCAL_BUILD_DIR cache override doesn't survive a clean
build-dir wipe.
LLMediaCtrl::calcOffsetsAndSize() letterboxed/pillarboxed whenever
getMediaWidth()/getMediaHeight() didn't match the widget's own aspect
ratio -- correct for a video plugin's fixed intrinsic resolution, but
for the embedded-browser (CEF) backend that value is just "the size of
the last frame that happened to arrive," not a property of the
content: a CEF browser always renders at exactly the size it was last
told to.

This showed up as the login screen's HTML widget intermittently not
filling its panel: getMediaWidth()/getMediaHeight() only updates once
a frame at a newly-requested size round-trips through the producer,
and now that the producer starts asynchronously at Viewer launch
instead of being pre-launched by hand, that round trip can take
several real seconds -- long enough for a post-construction reshape
(e.g. the window settling to its restored size) to be visibly
letterboxed against the stale reported size until the next frame
lands. Skip the aspect-ratio branch entirely for this backend and
always fill the rect; the in-flight resize converges the actual
browser size to match regardless.
Replaces duckduckgo.com with an S3-hosted page listing bookmarks useful
for testing the embedded-browser work, in both the login-screen and
main-viewer copies of this debug menu item.
New log_info()/log_connect()/log_disconnect() helpers (yellow/green/red,
via ANSI escape codes -- explicitly enabling ENABLE_VIRTUAL_TERMINAL_
PROCESSING, since a freshly allocated Windows console doesn't interpret
them by default). Deliberately ad hoc rather than a logging framework:
enough structure that adding another call site later is a one-line
thing.

Logs: slot connect (green), URL navigation (green), disconnect with a
reason -- "crashed consumer" or "idle timeout" (red), plus the existing
startup/shutdown banners now go through log_info() for consistency.

Also adds an edge-triggered "viewer detached" log using the had_subscriber
field (previously written but never read) at the moment a subscriber's
has_subscriber() flips false, separate from the slot's actual teardown
below it -- which deliberately still waits out the idle grace period in
case the same consumer reconnects shortly.

CefProducerShowConsole now defaults to true so this is visible during
active development; flip back to false once this settles down.
callumlinden and others added 19 commits September 23, 2026 15:13
Picks up llcefbrowser's --no-zygote fix for CEF's GPU process failing a
zygote handshake when no_sandbox is set on Linux -- the actual root cause
of the "Linux Viewer runs but no media" report. Confirmed end-to-end
under WSL2/Ubuntu-22.04 against this exact published package (not a
local-build override): full healthy CEF process tree (GPU process,
network/storage utilities, two renderers, all alive, --no-zygote present
on the renderer command lines) and real media rendering in the login
page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
libcef.so depends on these NSS/NSPR libraries at runtime but our
Linux packaging doesn't bundle them (matches CEF's own upstream
convention, not a packaging gap) -- a system without them fails
silently, with the media producer exiting almost instantly and no
media ever appearing. Both the packaged secondlife launcher
(wrapper.sh) and the optional installer (install.sh) now check for
them and print an install hint if either is missing.

Also updates doc/Embedded_Browser.md's Linux status, which was
still saying "not yet verified on real hardware" -- now confirmed
end to end (including the GPU zygote fix) under a real Linux
environment against the published package.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LLWindowSDL::getNativeKeyData() only returned the old pre-embedded-
browser Qt/WebKit-era fields, so LLViewerMediaImpl's key handlers
(gated on has("cef_windows_key_code")) silently dropped every key
event -- Linux embedded-browser media had no keyboard input at all.

Added the same cef_* field translation Windows and macOS already
had: a new SDL-specific getCefKeyModifiers() (SDL's own modifier
mask already distinguishes left/right shift/ctrl/alt directly,
unlike Win32 which needs a live GetKeyState() query), reusing the
existing LLKeyboardSDL::mapSDLtoWin() table for windows_key_code.
is_system_key is always false, matching macOS -- CEF's own docs
note that concept is Windows-only.

Confirmed working: real typed text in the Media Monitor floater's
own web page, under WSL2/WSLg.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Unlike libcef.so's NSS/NSPR dependency (which only kills the separate
media producer process), libvlc is linked directly into the main
viewer binary itself -- LLStreamingAudio_LibVLC (parcel/streaming
audio) and SLMediaProducer's LibVlcTabManager (RTSP/RTMP prim media)
both need it, and Linux links against the system's own libvlc rather
than vendoring it the way Windows/macOS do. A machine without
libvlc5/libvlccore9 installed would fail to launch the whole viewer,
not just lose one feature, with only the dynamic linker's own
cryptic error to go on.

Same check-and-warn mechanism as the libnss3/libnspr4 check
(eb45ff0), extended to also cover libvlc.so.5/libvlccore.so.9 in
both wrapper.sh (runs before the launch attempt) and install.sh.
Real package names (libvlc5/libvlccore9) confirmed via dpkg -S
against a real Ubuntu install, not guessed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Legal flagged a real licensing tension: the vendored LibVLC package
(vlc-bin) declares itself GPL v2 in its own autobuild package
metadata, while this project is LGPL v2.1. Having CEF and LibVLC
linked into one combined producer process meant Linden Lab's own
CEF-integration code sat in the same binary as a GPL-declared
library, widening what a maximalist GPL reading could sweep in.

Phase 1 of the remediation: split the producer into two standalone
processes, each reached only via IPC --

- SLCefProducer.exe (indra/llcefproducer) -- CEF only, reverting the
  2026-09-02 SLCefProducer->SLMediaProducer rename now that the
  reason for it (hosting both backends) no longer applies.
- SLVlcProducer.exe (indra/llvlcproducer, new) -- LibVLC only, owns
  RTSP/RTMP/MMS prim media (LibVlcTabManager, moved here unchanged --
  it was already self-contained, built from day one as a sibling to
  the CEF manager, not a dependent).

Wire protocol: cefshm_protocol.h becomes a 4th hand-synced copy
(matching the existing 3-copy convention, including one in an
external repo that can't share a CMake target anyway). New
kVlcControlChannelName/kVlcChannelPrefix constants so the two
producers, now separate OS processes, don't collide over the same
shared-memory channel names. The kRequestSlot backend byte stays on
the wire unused -- zero format churn -- since routing now happens by
which producer/channel a tab connects to, not an in-payload value.

Viewer-side: LLEmbeddedBrowser gains a small per-backend
ProducerHandle array (process + independent relaunch bookkeeping)
instead of a single shared one; both producers launch eagerly at
init(), matching today's exact behavior (lazy-launching SLVlcProducer
only on first RTSP/RTMP use is a reasonable future optimization,
deliberately deferred); reset() shuts both down concurrently rather
than serially. LLDir gains two separate launcher-path getters per
platform. viewer_manifest.py splits the single packaging block into
two disjoint directories on all three platforms.

Verified in increasing order of rigor: both executables build clean;
dumpbin confirms zero cross-linkage (SLCefProducer imports only
libcef.dll, SLVlcProducer only libvlc.dll); the real packaging
script (not a simulation) produces the correct disjoint directory
structure; a full local clean build confirmed both producers working
end-to-end -- ordinary CEF-backed web media, and RTSP media (via a
local MediaMTX+ffmpeg test stream) playing correctly through the new
SLVlcProducer.exe.

Phase 2 (moving LLStreamingAudio_LibVLC/parcel audio off
secondlife-bin.exe behind IPC too, for the full "zero libvlc in the
main Viewer binary" goal) is separate, not part of this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v2.1.6 fixes the deep-signing signee list, which still referenced the
pre-split SLMediaProducer path and silently skipped signing both new
SLCefProducer and SLVlcProducer executables, causing notarization to
reject the archive (run 36809821723).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LLStreamingAudio_LibVLC (parcel/streaming music audio) no longer links
libvlc directly into secondlife-bin.exe. It's rewritten as a non-blocking
IPC client of the already-running SLVlcProducer process, using a new
frame-less "audio-only" slot type (LibVlcTabManager::CreateAudioTrack(),
Slot::isAudioOnly) that reuses the same control-channel/per-view-channel
handshake and wire protocol prim/RTSP media already uses, extended with
one new backward-compatible byte (kRequestSlot's audioOnly flag).

The public LLStreamingAudioInterface is unchanged, so no caller anywhere
in llvieweraudio.cpp/llviewerparcelmgr.cpp/llviewermedia.cpp/
llpanelnearbymedia.cpp needed to change. Local state (URL/gain/pause) is
cached and replayed on every (re)connect, so a SLVlcProducer crash or
relaunch now self-heals instead of silently losing the stream.

ll::libvlc/include(LibVLCPlugin) removed entirely from
indra/newview/CMakeLists.txt -- confirmed via dumpbin that
secondlife-bin.exe no longer imports any libvlc symbol at all, closing
the GPL v2/LGPL v2.1 licensing gap Phase 1 (50708b1) deliberately left
open. viewer_manifest.py's now-unnecessary libvlc copy alongside the main
executable (Windows and macOS) is removed too, and wrapper.sh/install.sh's
dependency-check wording is corrected to say only SLVlcProducer needs
libvlc now, not the main binary.

Also deletes the confirmed-dead LLStreamingAudio_MediaPlugins
(llviewermedia_streamingaudio.{h,cpp}), the old plugin-process-backed
streaming-audio implementation this project had already superseded.

Verified via a real local build and manual test: parcel audio plays
correctly end-to-end through SLVlcProducer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ll::libvlc is no longer defined by newview/CMakeLists.txt (removed in
97c95f2) -- llvlcproducer/CMakeLists.txt is now the only place that
still links it. Comment-only, no logic change: this file's own
include(LibVLCPlugin) at line 18 was never conditioned on newview having
done it first, so nothing here actually broke.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A freed slot index is reused by whichever of CreateTab()/
CreateAudioTrack() asks for one next (both scan for the same first free
index). CreateTab() never reset audioOnly back to false, so a video tab
landing on an index a parcel-audio track previously held would silently
inherit audioOnly=true -- Open() would then skip
libvlc_video_set_callbacks()/libvlc_video_set_format(), so the player ran
but never produced a single video frame. Audio-only playback looked and
behaved completely normally, which is exactly why this stayed hidden
through all of Phase 2's own local Windows testing (parcel audio and RTSP
video were never tested in the specific slot-reuse sequence needed to
trigger it) and only surfaced testing RTSP video on macOS.

Also has CreateAudioTrack() release (not just leave untouched) a reused
slot's previous video-tab pixel buffer, which could otherwise sit around
at its ceiling size, several MB, doing nothing for an audio-only tab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes a real Linux build failure: LLStatus::MappingFailed collided with
X11's own Xlib.h macro of the same name, exposed for the first time by
llstreamingaudio_libvlc.cpp (Phase 2 parcel-audio IPC work) being the
first file in secondlife-bin itself to include <shmframe/llshmframe.h>
directly. Fixed upstream by renaming it to MappingFailure
(secondlife/llshmframe@46220d5, tagged v1.22.0). Hashes verified by
downloading all three platform packages and computing SHA1 directly,
not copied from the release page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed via real testing (2026-10-02): this exact invocation makes
libvlc_new() fail outright ("vlc: unknown option or missing mandatory
argument `--file-logging'") on both macOS and Linux -- identical failure
on two platforms with completely unrelated libvlc distributions
(vendored vlc-bin on macOS, the system's own package on Linux), ruling
out a packaging/signing/missing-plugin-package cause specific to either
one. This is libvlc's own CLI-parsing behavior, apparently only
tolerated by whatever this vendored build's Windows behavior happens to
be.

A diagnostic logging option must never cost this process ALL of libvlc.
Retry libvlc_new() once without the file-logging args if the first
attempt fails, rather than leave mLibVLC permanently null for the rest
of the process's life (every CreateTab()/CreateAudioTrack() call
silently failing forever after -- no RTSP/RTMP prim media and no parcel
audio ever playing, with nothing else in this process's own startup
banner revealing why).

Also adds LibVlcTabManager::IsReady(), logged once at startup, so this
exact ambiguity (did libvlc actually initialize, or is everything after
it silently broken) is never invisible again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v2.1.7 fixes a macOS code-signing gap: SLVlcProducer's own bundled
libvlc*.dylib*/plugins/*.dylib (~346 loose files) were never covered by
any explicit signee pattern in sign.sh, unlike CEF's own Libraries/*.dylib
-- relying entirely on the generic codesign --deep pass, which this
script's own comment already documents as unreliable for deeply nested
loose files. A plugin dylib's still-live com.apple.quarantine attribute,
even after the app itself was already launched/trusted, was the real
clue (secondlife/viewer-build-util#32).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Windows' libvlc.dll auto-discovers its own sibling plugins\ folder via
the OS's standard DLL search-path convention. The vendored
libvlc.dylib/libvlccore.dylib bundled for macOS makes no such
assumption on its own -- without VLC_PLUGIN_PATH set, libvlc_new()
can't locate any of its plugin modules at all.

Confirmed via real testing (2026-10-02) that even a minimal,
argument-safe libvlc_new() call (no custom CLI options at all) still
failed outright on macOS, ruling out the --file-logging CLI argument
itself as the real root cause (that was a real but secondary finding --
see the previous commit). This looks like the more fundamental
explanation: no plugin (demux/access/logger) ever loads at all without
this set, matching every symptom seen so far.

Scoped to macOS only -- Linux links the system's own installed libvlc
with its own correct default plugin path already compiled in, no
bundled plugins/ directory sits next to this executable there, and
pointing VLC_PLUGIN_PATH at a nonexistent local directory would make
things worse, not better. Linux's identical symptom (confirmed even
with vlc-plugin-base installed) needs its own separate investigation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers today's real findings: libvlc_new() failing outright on macOS
without VLC_PLUGIN_PATH set (RTSP/RTMP prim media and parcel audio both
completely non-functional, with no crash and nothing visible in
secondlife.log), the real but secondary SLVlcProducer plugin-dylib
codesigning gap (viewer-build-util v2.1.7), and confirmation that RTSP
video and parcel audio now both work end to end on a real signed/
notarized macOS build for the first time in this project's history.

Also corrects a since-stale "Windows and macOS vendor LibVLC for both
secondlife-bin.exe and SLVlcProducer.exe" line (no longer true after
Phase 2 moved parcel audio off secondlife-bin.exe entirely), and adds an
explicit caveat to the cross-platform-support summary: LibVLC media
specifically (unlike CEF media) is not yet confirmed working on Linux,
which has the identical "no plugin loads" symptom via a still-open,
separate root cause, plus an unrelated post-login crash -- both
deliberately parked for now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Left over from before Phase 2 -- this is the same new LLStreamingAudio_LibVLC()
call site, now an IPC client of SLVlcProducer, not linked directly at all.
Caused real confusion mid-debugging last week ("is old plugin code taking
over?").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
radianceGenF.glsl, irradianceGenF.glsl, and reflectionProbeF.glsl all
declare a `samplerCubeArray` uniform without the extension that type
requires in a #version 150 shader. NVIDIA/AMD/Apple drivers apparently
tolerate this leniently, but Mesa's GLSL compiler correctly rejects it
per spec:

  0:39(26): error: syntax error, unexpected NEW_IDENTIFIER, expecting '{'

Confirmed as the real cause of a Linux-only crash immediately after
login (unrelated to any of the recent LibVLC/IPC work): radianceGenF's
shader program object never gets created when this fails, and a later
bind() call on it hits ASSERT (mProgramObject != 0), which on this
platform manifests as a real SIGSEGV rather than a clean, controlled
abort.

Only radianceGenF (class1, the baseline tier every client attempts) has
actually been observed crashing; irradianceGenF (class2) and
reflectionProbeF (class3) have the identical latent bug but likely
haven't been exercised yet on this test hardware's GPU class.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Picks up the vendored libvlc update merged in secondlife/3p-vlc-bin#8
(v3.0.24.22b238b release).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
About floater's LIBVLC_VERSION was a placeholder since Phase 2 moved
libvlc out of secondlife-bin -- wires it up the same way
EMBEDDED_LLCEFBROWSER_VERSION already works for SLCefProducer, over a
new SLVlcProducer use of the existing kEventVersionInfo opcode
(libvlc_get_version(), called from inside the already-isolated GPL
producer process, nothing new linked into the main binary). Both
prim-media RTSP/RTMP tabs and parcel audio's own separate IPC client
feed this.

libvlc's own --logfile= opens in append mode and was never reset,
unlike its sibling slvlcproducer_log.txt -- deleted before each launch
so it starts fresh every session instead of growing unbounded.

Also updates doc/Embedded_Browser.md's known-limitations section with
the team's decision on Linux RTSP-via-LibVLC (documented as not yet
implemented, PRs welcome, rather than vendoring a from-source Linux
libvlc build) and the already-landed shader-crash fix and confirmed
Linux parcel audio.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Starting point for the embedded-browser Project Viewer: CEF/LibVLC
dual-backend media via SLCefProducer/SLVlcProducer, IPC'd over
llshmframe, confirmed working end to end on Windows, macOS, and Linux.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

# Conflicts:
#	indra/newview/CMakeLists.txt
#	indra/newview/llfloater360capture.cpp
#	indra/newview/llviewerfloaterreg.cpp
- name: Sign and package Mac viewer
if: env.SIGNING_CERT_MACOS && env.SIGNING_CERT_MACOS_IDENTITY && env.SIGNING_CERT_MACOS_PASSWORD && steps.note-creds.outputs.note_user && steps.note-creds.outputs.note_pass && steps.note-creds.outputs.note_team
uses: secondlife/viewer-build-util/sign-pkg-mac@v2.1.0
uses: secondlife/viewer-build-util/sign-pkg-mac@v2.1.7

- name: Sign Mac viewer (diagnostic, unnotarized)
if: env.SIGNING_CERT_MACOS && env.SIGNING_CERT_MACOS_IDENTITY && env.SIGNING_CERT_MACOS_PASSWORD && steps.note-creds.outputs.note_user && steps.note-creds.outputs.note_pass && steps.note-creds.outputs.note_team
uses: secondlife/viewer-build-util/sign-pkg-mac@v2.1.7
@callumlinden
callumlinden deleted the callum/exec_producer-precheck branch October 6, 2026 21:02
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants