@blueyerobotics/multibeam
v0.1.0
Published
Reusable multibeam sonar viewer components for Blueye drones.
Readme
@blueyerobotics/multibeam
React components and utilities for rendering and playing back Blueye multibeam sonar (.mbez) files in the browser via WebGL.
Installation
npm install @blueyerobotics/multibeam @blueyerobotics/blueye-tsPeer dependencies: react >=18, @blueyerobotics/blueye-ts >=3
Palette image
All rendering functions require a palette image that maps sonar intensity values to colours. The image must be exactly 256 pixels wide. Each palette occupies a 10-pixel-high row — the image height determines the number of available palettes (e.g. the bundled Palettes.png is 256 × 90 px, providing 9 palettes).
The palette image is not bundled with the package — it must be supplied by the application. The Blunux web app ships one at frontend/public/Palettes.png. You can use that file directly, or provide your own image following the same format.
Call loadPaletteFromUrl once at app startup before rendering any frames:
import { loadPaletteFromUrl } from "@blueyerobotics/multibeam";
// The image must be served from your app — it is not bundled with the package.
// The Blunux app's Palettes.png can be found at frontend/public/Palettes.png.
loadPaletteFromUrl("/Palettes.png");The palette is loaded into a shared singleton WebGL context so it only needs to be called once.
Components
MultibeamPlayer
High-level component that handles file loading, decompression, parsing, and timing-accurate playback. It renders the canvas and exposes playback state to your UI via a render prop.
import { MultibeamPlayer } from "@blueyerobotics/multibeam";
<MultibeamPlayer
file={blob} // Blob — the .mbez file
paletteIndex={0} // number — which palette row to use (default: 0)
overlayOptions={{
// optional range/sector overlay
rangeLines: true,
sectors: true,
labels: true,
}}
fadeFarEdge={false} // fade the far edge of the fan (default: false)
className="w-full h-full" // optional — outer wrapper class
rendererClassName="drop-shadow-xl" // optional — passed to inner MultibeamRenderer
onDiscovery={(d) => {
// fired if a MultibeamDiscoveryTel message exists
console.log("sonar model:", d.modelName);
}}
onReady={() => {
// fired after parsing — use to hide loading spinners
setLoading(false);
}}
>
{(state) => (
// state: PlayerState — see type below
<MyControls state={state} />
)}
</MultibeamPlayer>;PlayerState
| Field | Type | Description |
| ------------- | --------------------- | ----------------------------------------- |
| messages | Message[] | Decoded ping frames |
| index | number | Currently displayed frame index |
| playing | boolean | Whether playback is running |
| loading | boolean | Whether the file is still loading/parsing |
| error | string \| null | Parse or fetch error message |
| play() | () => void | Start playback |
| pause() | () => void | Pause playback |
| toggle() | () => void | Toggle play/pause |
| seek(index) | (n: number) => void | Jump to a specific frame |
| reset() | () => void | Return to frame 0 and pause |
MultibeamRenderer
Low-level component that renders a single frame to a canvas. Each instance owns its own WebGL context and renders directly — no shared state or blit overhead. Overlays are drawn on a separate 2D canvas layered on top. Palette URL is sourced automatically from MultibeamPaletteProvider (or an optional paletteUrl prop override).
import { MultibeamRenderer } from "@blueyerobotics/multibeam";
<MultibeamRenderer
frame={message} // Message | null — a decoded ping frame
paletteIndex={0}
overlayOptions={{ rangeLines: true, sectors: true, labels: true }}
fadeFarEdge={false}
paletteUrl="/Palettes.png" // optional — overrides MultibeamPaletteProvider
className="w-full h-full"
/>;QueuedMultibeamRenderer
Thumbnail renderer that uses a shared off-screen WebGL context with a serialised render queue. Uses IntersectionObserver to lazy-load thumbnails when they scroll into view. Ideal for media grids with many sonar thumbnails.
import { QueuedMultibeamRenderer } from "@blueyerobotics/multibeam";
<QueuedMultibeamRenderer
url="https://device/webdav/dive_001_thumbnail.mbez"
paletteIndex={0}
width={600} // optional — canvas width in px (default: 600)
height={400} // optional — canvas height in px (default: 400)
fallback={<span>No preview</span>}
className="w-full h-full"
/>;MultibeamPaletteProvider
Context provider that loads a palette image and makes the URL available to all MultibeamRenderer and QueuedMultibeamRenderer instances in the tree. Also loads the palette into the shared WebGL context for thumbnail rendering. Place near the app root.
import { MultibeamPaletteProvider } from "@blueyerobotics/multibeam";
<MultibeamPaletteProvider url="/Palettes.png">
<App />
</MultibeamPaletteProvider>;Use the useMultibeamPaletteUrl() hook to read the current palette URL from context.
Utilities
enqueueThumbnailLoad(url: string): Promise<Message>
Downloads a _thumbnail.mbez file and returns the single decoded frame. Requests are serialised through an internal queue to avoid flooding the browser with concurrent fetches — safe to call for every item in a large media grid.
import { enqueueThumbnailLoad } from "@blueyerobotics/multibeam";
const frame = await enqueueThumbnailLoad("https://device/webdav/dive_001_thumbnail.mbez");loadPaletteFromUrl(url: string): void
Loads a palette image into the shared WebGL context. Must be called before any frame is rendered. See the Palette image section above.
renderFrameToCanvas(message, canvas, paletteIndex, overlayOptions?, fadeFarEdge?): void
Renders a decoded frame directly onto a provided HTMLCanvasElement. Lower-level escape hatch for non-React usage or custom canvas management.
import { renderFrameToCanvas } from "@blueyerobotics/multibeam";
renderFrameToCanvas(message, myCanvas, 0, { rangeLines: true });renderOverlays(ctx, message, options?): void
Draws range arcs, sector lines, and distance labels onto an existing CanvasRenderingContext2D. Useful if you want to composite the overlay on a separate canvas layer.
createDirectRenderer(canvas): DirectRenderer
Creates a renderer that owns its own WebGL context on the given canvas. Returns an object with draw(frame, paletteIndex?, fadeFarEdge?), loadPalette(url, onLoaded?), and destroy() methods. The optional onLoaded callback fires after palette textures are created — use it to trigger a redraw so the first frame doesn't render with a fallback palette. Used internally by MultibeamRenderer but available for advanced use.
getPaletteSwatchStyle(imageUrl, index, count, swatchHeight): PaletteSwatchStyle
Returns CSS background properties (backgroundImage, backgroundSize, backgroundPosition, backgroundRepeat) needed to display a single palette swatch from the palette sprite image. Useful for building palette selector UIs.
paletteCount
The number of palettes available in the loaded palette sprite image.
Device utilities
SONAR_DEVICE_INFO: Record<number, SonarDeviceInfo>
Device capability data for all supported multibeam sonar models, keyed by device ID. Includes name, frequency ranges, valid beam counts, and feature support flags.
MULTIBEAM_DEVICE_IDS: Set<number>
Set of all known multibeam device IDs. Use for quick membership checks (e.g. to determine whether a guest-port device is a multibeam sonar).
MULTIBEAM_DEVICE_NAMES: Record<number, string>
Map from device ID to a human-readable model name (e.g. 16 → "Gemini 720im").
DEFAULT_MULTIBEAM_CONFIG
Sensible default values for a MultibeamConfig protobuf message. Useful as a starting point when the drone hasn't reported its current configuration yet.
getRangeInfo(deviceId, frequencyMode): SonarRangeInfo
Returns the min/max range for a given device and frequency mode.
getValidBeams(deviceId): number[]
Returns the valid beam count enum values for a given device.
getConnectedMultibeam(droneInfo): ConnectedMultibeam | null
Extracts the connected multibeam device from a DroneInfoTel telemetry message.
OverlayOptions
type OverlayOptions = {
rangeLines?: boolean; // draw concentric range arcs (default: true)
sectors?: boolean; // draw sector boundary lines (default: true)
labels?: boolean; // draw distance labels on range arcs (default: true)
formatRange?: (meters: number) => string; // custom label formatter
labelFontSize?: number; // override auto-scaled label font size in pixels
labelColor?: string; // label text fill colour (default: "rgba(255,255,255,0.8)")
labelStrokeColor?: string | null; // label stroke colour, or null to disable (default: "rgba(0,0,0,0.6)")
};Examples
Ready-to-use example components are provided in src/examples/:
ThumbnailExample
Lazy-loading thumbnail using QueuedMultibeamRenderer. Only fetches the _thumbnail.mbez when the element scrolls into view. Requires a MultibeamPaletteProvider ancestor.
import ThumbnailExample from "@blueyerobotics/multibeam/examples/ThumbnailExample";
<div style={{ width: 320, height: 213 }}>
<ThumbnailExample
url="https://device/webdav/dive_001_thumbnail.mbez"
paletteIndex={0}
fallback={<span>No preview</span>}
/>
</div>;PlayerExample
Minimal playback UI with scrubber, play/pause, reset, overlay lines, and far-edge fade. Use as a starting point for a custom player.
import PlayerExample from "@blueyerobotics/multibeam/examples/PlayerExample";
// file is a Blob — e.g. from a fetch or <input type="file">
<PlayerExample file={blob} paletteIndex={0} onDiscovery={(d) => console.log(d)} />;Publishing
The package is published automatically from CI when a tag matching multibeam/vX.Y.Z is pushed:
git tag multibeam/v0.2.0
git push origin multibeam/v0.2.0The publishConfig.exports field in package.json remaps the entry point from src/index.ts (used in the workspace) to dist/multibeam.js (used by npm consumers).
