Sharing LSL streams over the network: bridge and relay

Lab Streaming Layer finds streams with multicast, so it only works where multicast reaches: usually one local network. A web browser can't use LSL at all, because it has no raw sockets. The LSL bridge in lsl_tools carries streams over a WebSocket instead:

You can run it from the LSL Viewer desktop app (no command line needed) or with the lsl command line tool.

Contents

Which setup do I need?

You want to… Run this where the streams are Clients connect with
See lab streams in a browser lsl share (or viewer: Share) Viewer: LSL > Connect to an LSL bridge…
Get lab streams into LSL on another network lsl share lsl bridge ws://host:8765 (or viewer: Publish its streams on LSL here)
Get a browser's streams (serial device, replayed XDF) into LSL lsl share --accept (or viewer: Share + accept) Viewer in the browser: Forward… or Replay…
Pass streams between browsers, no LSL anywhere lsl relay (or viewer: Relay only) Viewers: Connect, then Forward/Replay and View
Send lab streams out to a relay on a server (the lab accepts no incoming connections) lsl publish wss://relay… in the lab Anyone connected to the relay
Setup 1   [LSL network A] ──lsl share──▶ ws ──▶ lsl bridge ──▶ [LSL network B]
                                          └───▶ browser (LSL Viewer on the web)

Setup 2   browser ──publish──▶ ws ──▶ lsl share --accept ──▶ [LSL on that computer]
                                                        └──▶ other clients

Setup 3   browser A ──publish──▶ ws ──▶ lsl relay ──▶ ws ──▶ browser B, C, lsl bridge…
          [LSL lab] ──lsl publish──▶ ┘

Streams that clients publish are always shared with every other client. On a share or share + accept server they also become ordinary LSL streams on that computer. A relay only passes them between clients, so it doesn't need LSL.

Getting the tools

In the examples below, lsl means whichever of these you use.

Quick start: view lab streams in a browser

On a computer on the lab network (where the LSL streams are):

lsl share --token choose-a-secret
# Sharing EEG, Markers on port 8765 (address 0.0.0.0)

Or in LSL Viewer: LSL > Share or relay streams…, choose Share, set a token, then click Share. The panel lists the addresses to connect to.

Then, in a browser, open LSL Viewer:

Click View next to a stream to open it in a tab.

Setup 1: streams from one LSL network to another

On network A (where the streams are):

lsl share -s EEG -s Markers --token s3cret

On network B:

lsl bridge ws://lab-pc.example.org:8765 --token s3cret --suffix " (lab)"

Port 8765 (or the one you pick with -p) must be reachable from network B: open it in the firewall, forward it on the router, or use a VPN.

Setup 2: a browser publishes into LSL

On the computer that should receive the streams:

lsl share --accept --token s3cret

In the browser, Connect to an LSL bridge… that computer, then:

The streams appear as LSL streams on the --accept computer (look for them with lsl list). Other clients of the bridge see them too.

Setup 3: a relay between browsers

lsl relay --token s3cret

Every client connects to the relay:

A relay needs no LSL on its computer, so it can run on a server with no lab network (next sections). In the viewer, use Share or relay streams… and choose Relay only.

Security: tokens, origins and TLS

The bridge is built for a trusted group, such as a lab or a class. It is not meant to be exposed to the open internet unattended.

Running a relay on a server (wss://)

Browsers on https:// pages, including the hosted viewer, need wss://. The bridge speaks plain ws://. Put a reverse proxy with a certificate in front of it.

On the server:

lsl relay --host 127.0.0.1 --port 8765 --token "$(openssl rand -hex 16)" \
  --allow-origin https://nexusdynamic.org

Caddy gets and renews the certificate itself. In the Caddyfile:

relay.example.org {
    reverse_proxy 127.0.0.1:8765
}

nginx (with a certificate from e.g. certbot):

server {
    listen 443 ssl;
    server_name relay.example.org;
    # ssl_certificate / ssl_certificate_key ...

    location / {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 1h;
        access_log off;   # the token is in the URL
    }
}

Clients then connect to wss://relay.example.org with the token.

To keep the relay running, use a systemd unit such as:

[Service]
ExecStart=/opt/lsl-cli/bin/lsl relay --host 127.0.0.1 --token <token>
Restart=always

To send a lab's LSL streams to the relay, run this on a lab computer. It connects out, so the lab needs no open ports:

lsl publish wss://relay.example.org --token <token> -s EEG -s Markers --rescan 5

Everyone connected to the relay then sees them. lsl bridge wss://relay.example.org elsewhere turns them back into LSL streams.

Command reference

lsl share shares LSL streams, and with --accept takes published ones.

Option Default Meaning
-s, --stream all Streams by name or type (* wildcard); repeatable
-p, --port 8765 Port (0: any free port)
--host 0.0.0.0 Address to listen on (127.0.0.1: this computer only)
--token none Clients must give it
--allow-origin any Browser origins allowed; repeatable
--accept off Clients may publish streams: shared with the others and published on LSL here
--[no-]local-outlets on With --accept, publish clients' streams on LSL here
--relay off Same as lsl relay
--rescan off Every this many seconds, also share new matching streams

lsl relay passes streams between clients only. It takes -p, --host, --token and --allow-origin.

lsl publish <ws://host:port> publishes LSL streams from here on a bridge that accepts them (--accept or a relay). It takes -s (default: all), --token and --rescan (see above). It skips streams that came from a bridge, so they don't go round in circles.

lsl bridge <ws://host:port> publishes a bridge's streams on LSL here, and follows them as they come and go.

Option Default Meaning
-s, --stream all Stream names (* wildcard); repeatable
--token none The bridge's token
--suffix none Added to the names published here

--peer <address> goes before any command and looks for LSL streams on a computer multicast doesn't reach, e.g. lsl --peer 10.0.0.5 share.

The library API is LslBridgeServer.start, LslBridgeClient.connect and LslBridgeRepublisher.start in package:lsl_tools/lsl_tools.dart. The protocol (JSON control messages and binary sample frames) is documented in lib/src/bridge/protocol.dart.

Limitations

Troubleshooting