snapshot of https://github.com/jitsi/jibri.git @ 3b45602c539ec69d128ea152ae587d0b6582191a (2026-09-15)
Some checks failed
Java CI with Maven / Java 17 (push) Failing after 4m40s
Java CI with Maven / Java 21 (push) Failing after 4m41s

This commit is contained in:
upstream-mirror
2026-09-15 16:13:58 +05:00
commit 650a143b63
166 changed files with 14301 additions and 0 deletions

15
doc/apis.md Normal file
View File

@@ -0,0 +1,15 @@
# Jibri APIs
Jibri has two types of APIs:
1. One is for 'external' use and is used to control stopping and starting of Jibri services, as well as querying Jibri's health.
1. The other is 'internal' and is used to notify Jibri that there has been a change to its config file.
There is an HTTP implementation of the 'internal' API which lives in `InternalHttpApi.kt`, documentation for it can be found [here](internal_http_api.md).
The external API has both XMPP and HTTP implementations, however the HTTP implementation is not fully developed (though it's close to complete functionality). Detailed documentation for the XMPP API can be found [here](xmpp_api.md), and for the HTTP API [here](http_api.md).
In general, the 'external' APIs boil down to the following available actions:
1. Start a service
1. Stop a service
1. Query the current 'status' of Jibri (is it busy or is it available to start a new service)
At this time, Jibri only runs a single service at a time, so if it has one running it will consider itself "busy".

View File

@@ -0,0 +1,178 @@
Jibri is moving to a new config file. The old config file/command-line-arguments will be supported for a while and eventually removed. This doc is a guide on how to migrate from an "old" configuration to a new one.
The new configuration leverages the [Typesafe config](https://github.com/lightbend/config) library. Default configuration values are now stored in [reference.conf](src/main/resources/reference.conf). Overrides are written to a file and passed via a JVM parameter:
```
-Dconfig.file=/path/to/config.conf
```
### Property migrations
---
#### Notes
* No default values have been changed, so if you weren't specifying a property before you can continue to leave it unspecified and the value will not change.
* The new configuration library supports different formats and many different features in configuration (substitutions, includes, overrides via environment variables, etc.). The text here describes the 'plainest' way to define a new config file, but other methods are possible. Read about the Typesafe config to learn more.
* The example below assume that you will define a new configuration file called `jibri.conf`
---
##### Configuration file
Old method: path to a JSON file via command line argument `--config=/path/to/config.json`
New method: path to a HOCON .conf file passed via JVM arg: `-Dconfig.file=/path/to/config.conf`
---
##### Internal and external HTTP API ports
Old method: command line arguments `--http-api-port` and `--internal-http-port`
New method: in `jibri.conf`:
```hocon
jibri {
api {
http {
external-api-port = <number>
internal-api-port = <number>
}
}
}
```
---
##### Single use mode
Old method: in `config.json`:
```json
{
"single_use_mode": <Boolean>
}
```
New method: in `jibri.conf`:
```hocon
jibri {
single-use-mode = <boolean>
}
```
---
##### Recordings directory
Old method: in `config.json`:
```json
{
"recording_directory": <path>
}
```
New method: in `jibri.conf`:
```hocon
jibri {
recording {
recordings-directory = <path>
}
}
```
---
##### Finalize script path
Old method: in `config.json`:
```json
{
"finalize_recording_script_path": <path>
}
```
New method: in `jibri.conf`:
```hocon
jibri {
recording {
finalize-script = <path>
}
}
```
---
##### Enabling statsD
Old method: in `config.json`:
```json
{
"enable_stats_d": <boolean>
}
```
New method: in `jibri.conf`:
```hocon
jibri {
stats {
enable-stats-d = <boolean>
host = <string>
port = <int>
}
}
```
---
##### XMPP environment config
Old method: in `config.json`:
```json
{
"xmpp_environments": [
{
"name": <String>,
"xmpp_server_hosts": List<String>,
"xmpp_domain": <String>,
"control_login": {
"domain": <String>,
"username": <String>,
"password": <String>
},
"control_muc": {
"domain": <String>,
"room_name": <String>,
"nickname": <String>
},
"call_login": {
"domain": <String>,
"username": <String>,
"password": <String>
},
"room_jid_domain_string_to_strip_from_start": <String>,
"usage_timeout": <number>
},
{ <more> },
]
}
```
New method: in `jibri.conf`:
```hocon
jibri {
api {
xmpp {
environments = [
{
name = <String>
xmpp-server-hosts = <List<String>>
xmpp-domain = <String>
control-muc {
domain = <String>
room-name = <String>
nickname = <String>
}
control-login {
domain = <String>
username = <String>
password = <String>
}
sip-control-muc {
domain = <String>
room-name = <String>
nickname = <String>
}
call-login {
domain = <String>
username = <String>
password = <String>
}
strip-from-room-domain = <String>
usage-timeout = <Duration String i.e. '5 minutes' or '1 hour'>
trust-all-xmpp-certs = <boolean>
},
{ <more> }
]
}
}
}
```
An example of a new environments config can be found [here](example_xmpp_envs.conf).

15
doc/debian_package.md Normal file
View File

@@ -0,0 +1,15 @@
# Building the Jibri debian package
Building the debian package has been tested on Ubuntu Xenial.
### Steps
* Install the prerequisites:
* `sudo apt install git maven openjdk-8-jdk`
* (`openjdk-8-jdk` specifically is not necessarily required, any java 8 jdk will probably work)
* Clone the repo:
* `git clone https://github.com/jitsi/jibri.git`
* Create the jar:
* `cd jibri`
* `mvn package`
* Call the `create_debian_package_script` and pass it the location of the jar:
* `` export WORKSPACE=`pwd` ``
* `resources/jenkins/release.sh Minor`

16
doc/development.md Normal file
View File

@@ -0,0 +1,16 @@
# Jibri Development
## Style
Code should follow the Kotlin style guide. This style is enforced with ktlint in the project itself, linting can be executed by running `mvn verify` (which will build, run tests and lint) or `mvn exec:exec@ktlint-check` (which will run just the linting; `mvn exec:exec@ktlint-format` auto-formats). Jibri is a Kotlin codebase, so Kotlin should be used for development (save for extreme circumstances where falling back to Java is acceptable). The linter can be run automatically by installing a pre-commit script, you can run [this script](resources/add_git_pre_commit_script.sh) to create/install this hook automatically.
## Versioning
Jibri uses (annotated) tagged versions and follows [semantic versioning](https://semver.org/). Adding an annotated tag is done as follows:
```
git tag -a v1.4 -m "my version 1.4"
```
Tags are not pushed by default when doing `git push`, but a tag can be pushed like a remote branch:
```
git push origin v1.4
```
NOTE: Tagging should not be done as part of a PR, it will be handled separately.
More can be read about git tagging [here](https://git-scm.com/book/en/v2/Git-Basics-Tagging).

View File

@@ -0,0 +1,65 @@
// An example file showing the fields for the XMPP environment config.
{
environments = [
{
// A user-friendly name for this environment
name = "env name"
// A list of XMPP server hosts to which we'll connect
xmpp-server-hosts = [ "host1", "host2" ]
// The base XMPP domain
xmpp-domain = "xmpp-domain"
// An (optional) base url the Jibri will join if it is set
// base-url = "https://meet.example.com"
// The MUC we'll join to announce our presence for
// recording and streaming services
control-muc {
domain = "domain"
room-name = "room-name"
nickname = "nickname"
}
// The login information for the control MUC
control-login {
domain = "domain"
// Optional port, defaults to 5222.
port = 6222
username = "username"
password = "password"
// Whether to use `username` as is or add a random suffix to it.
randomize-username = false
}
// An (optional) MUC configuration where we'll
// join to announce SIP gateway services
sip-control-muc {
domain = "domain"
room-name = "room-name"
nickname = "nickname"
}
// The login information the selenium web client will use
call-login {
domain = "domain"
username = "username"
password = "password"
}
// The value we'll strip from the room JID domain to derive
// the call URL
strip-from-room-domain = ""
// How long Jibri sessions will be allowed to last before
// they are stopped. A value of 0 allows them to go on
// indefinitely
usage-timeout = 1 hour
// Whether or not we'll automatically trust any cert on
// this XMPP domain
trust-all-xmpp-certs = true
}
]
}

82
doc/http_api.md Normal file
View File

@@ -0,0 +1,82 @@
# Jibri HTTP API
Jibri has a somewhat-implemented HTTP API to mirror the XMPP API with the following endpoints:
##### URL
`/jibri/api/v1.0/health`
##### Method
`GET`
##### URL Params
None
##### Data Params
None
##### Response
Code: `200`
Body:
```
{
"status":{
"busyStatus": String // "IDLE", "BUSY" or "EXPIRED"
"health":{
"healthStatus": String // "HEALTHY" or "UNHEALTHY"
"details": Map<String,HealthStatus> // Hash of component -> healthStatus above - only valid is "JibriManager" at present.
}
}
}
```
This call should always respond with a `200` and the status encoded in the response body. The lack of any response would represent an error of some sort
##### URL
`/jibri/api/v1.0/startService`
##### Method
`POST`
##### URL Params
None
##### Data Params
```
{
"sessionId": String, // the recording operation session (e.g. RecordTest)
"callParams": {
"callUrlInfo": {
"baseUrl": String, // the base url of the call (e.g. https://meet.jit.si)
"callName": String // the call name to be appended to the base url
}
},
"callLoginParams": {
"domain": String, // The xmpp domain the Jibri client should log into when joining the call
"username": String, // The username to use for logging in to the above domain
"password": String // The password to use for logging in to the above domain
},
"sinkType": String, // "stream" for streaming, "file" for recording
"youTubeStreamKey": String // If using "stream" above, this is the YouTube stream key to use
}
```
##### Success Response
Code: `200`
Body: None
##### Error Response
Code: `412 Precondition Failed` // When the Jibri is already busy
Body: None
Code: `500 Internal Server Error` // When an internal error occurs
Body: None
##### URL
`/jibri/api/v1.0/stopService`
##### Method
`POST`
##### URL Params
None
##### Data Params
None
##### Response
Code: `200`
Body: None
This call should always respond with a `200`. The lack of any response would represent an error of some sort.
Known HTTP API limitations:
* No push status updates.
* No way currently exists for interested parties to be notified of Jibri status changes. Queries must be made to the `health` endpoint.
* No way of passing Jitsi Meet call credentials.
* When Jibri joins a call in order to capture the media, it can use a special set of credentials so that it doesn't show up like a normal participant in the meeting. The HTTP API does not currently have a way of passing-in or configuring these credentials.

13
doc/internal_http_api.md Normal file
View File

@@ -0,0 +1,13 @@
# Jibri Internal HTTP API
At startup, Jibri reads from a configuration file to determine which (if any) xmpp enviroments to connect to and to read some other configuration data. Jibri takes a simple approach of only reading this file at startup, so if changes are made to the file and you want Jibri to read them, Jibri needs to be restarted. Obviously one would prefer not to restart Jibri while it's busy, so Jibri has an internal API to notify it of config file changes. When the API is called, Jibri will schedule a shutdown for the next time it's idle. It is up for whatever is managing Jibri to restart it.
##### URL
`/jibri/api/internal/v1.0/notifyConfigChanged`
##### Method
`POST`
##### URL Params
None
##### Data Params
None
##### Response
If Jibri is currently idle, no respose will be sent as Jibri will shutdown immediately. If Jibri is currently busy, it will respond with a `200`

310
doc/local-run/README.md Normal file
View File

@@ -0,0 +1,310 @@
# Running jibri locally against a docker-jitsi-meet deployment
This directory contains a script and a docker-compose override that let you
run jibri **from this source tree** while the rest of the Jitsi stack
(prosody, jicofo, jvb, web) runs in docker via
[docker-jitsi-meet](https://github.com/jitsi/docker-jitsi-meet). Useful for
iterating on jibri code changes without rebuilding the docker image.
## Files
- `run-jibri-local.sh` — generates `jibri.conf` + `logging.properties` from
your `docker-jitsi-meet/.env`, builds jibri if needed, and launches it.
- `prosody.override.yml` — publishes prosody's port 5222 on `127.0.0.1` so
the host-side jibri can reach it. The default compose only `expose:`s
this port inside the docker network.
## Prerequisites
- `docker` + `docker compose`
- `mvn` and Java 17 (to build jibri)
- `ffmpeg` on `PATH` (jibri shells out to it for capture)
- A clone of [docker-jitsi-meet](https://github.com/jitsi/docker-jitsi-meet)
(defaults to `~/dev/docker-jitsi-meet`; override with `DOCKER_JITSI_DIR`)
- A `chromedriver` binary at the jibri repo root, matching your installed
Chrome milestone (download from <https://googlechromelabs.github.io/chrome-for-testing/>)
## Step by step
### 1. Set up docker-jitsi-meet
```sh
cd ~/dev/docker-jitsi-meet
cp env.example .env
./gen-passwords.sh
mkdir -p ~/.jitsi-meet-cfg/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
```
### 2. Edit `.env`: enable recording and set `PUBLIC_URL`
Two changes in `~/dev/docker-jitsi-meet/.env`:
**Enable recording.** This is what tells prosody to provision the `jibri`
and `recorder` accounts (using `JIBRI_XMPP_PASSWORD` /
`JIBRI_RECORDER_PASSWORD` from `gen-passwords.sh`) and what makes the web
UI surface the record button. Without it, jibri can't even log into
prosody.
```sh
ENABLE_RECORDING=true
```
**Point `PUBLIC_URL` at your LAN IP.** Jibri opens the conference URL in
Chrome from the host. `localhost` won't do, because Chrome's WebRTC needs
an address that resolves the same way for jibri *and* the other browsers
joining the call. Use the host's LAN address.
Find your local IP:
```sh
# macOS
ipconfig getifaddr en0
# Linux
hostname -I | awk '{print $1}'
```
Then set (HTTP/HTTPS port already exist in env.example — adjust if you
moved them):
```sh
HTTP_PORT=8000
HTTPS_PORT=8443
PUBLIC_URL=https://192.168.1.42:8443 # ← your IP and the HTTPS_PORT
```
The script copies `PUBLIC_URL` into jibri's `base-url`, so jibri opens
`https://192.168.1.42:8443/<room>` instead of the default `https://meet.jitsi/`.
### 3. Bring the stack up with prosody 5222 published
The host-side jibri talks to prosody over XMPP, so we need 5222 reachable
from the host. Apply the override from this directory:
```sh
cd ~/dev/docker-jitsi-meet
docker compose \
-f docker-compose.yml \
-f ~/dev/jibri/doc/local-run/prosody.override.yml \
up -d
```
Confirm prosody is listening on the host:
```sh
nc -zv 127.0.0.1 5222 # → "succeeded!"
```
### 4. Build jibri
```sh
cd ~/dev/jibri
mvn -B -DskipTests clean package
```
The script will also build automatically if no jar is found in `target/`.
### 5. Drop a matching `chromedriver` next to the jibri repo
```sh
# look up your Chrome major version
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version
# fetch the matching chromedriver and place it at ~/dev/jibri/chromedriver
# (see https://googlechromelabs.github.io/chrome-for-testing/)
chmod +x ~/dev/jibri/chromedriver
```
### 6. (Linux only) Set up audio routing for ffmpeg
Jibri's default ffmpeg config captures audio from the ALSA PCM `plug:bsnoop`,
which is a custom loopback device defined in `/etc/jitsi/jibri/asoundrc` and
backed by the kernel `snd-aloop` module. The .deb postinst sets this up for
the system `jibri` user; if you're running locally as your own user, you
need to do it yourself, or switch to PulseAudio capture.
**Option A — ALSA loopback (matches the deb):**
```sh
# 1. Load the kernel loopback module (creates ALSA card "Loopback")
sudo modprobe snd-aloop
# Persist across reboots
echo snd-aloop | sudo tee /etc/modules-load.d/snd-aloop.conf
# 2. Drop jibri's asoundrc as your user's ALSA config
cp ~/dev/jibri/resources/debian-package/etc/jitsi/jibri/asoundrc ~/.asoundrc
# Verify
aplay -L | grep bsnoop
arecord -D plug:bsnoop -f cd -d 1 /tmp/t.wav && echo OK
```
Caveat: this changes your default ALSA device to a loopback, and on a
modern Ubuntu desktop Chrome routes audio through PulseAudio/PipeWire — so
even with `bsnoop` defined, Chrome won't play into the loopback unless you
also force Chrome to ALSA (e.g. `PULSE_SERVER=/dev/null`).
**Option B — PulseAudio capture (recommended on a desktop dev box):**
Skip the asoundrc/snd-aloop setup entirely and override the audio knobs
when invoking the script:
```sh
JIBRI_AUDIO_SOURCE=pulse \
JIBRI_AUDIO_DEVICE=default \
~/dev/jibri/doc/local-run/run-jibri-local.sh
```
`default` reads from Pulse's default source. To capture system audio
specifically, find the right monitor source with `pactl list short sources`
and pass it as `JIBRI_AUDIO_DEVICE` (e.g. `alsa_output.pci-…analog-stereo.monitor`).
### 6b. (macOS only) Capture the screen instead of the camera
Jibri's default macOS ffmpeg input is `-i 0:0` — whatever avfoundation
device 0 is (usually a camera + the mic). To record the conference you
want a **screen** device for video and a **loopback** device for audio.
1. **Grant Screen Recording permission** to the terminal app you launch
the script from (System Settings → Privacy & Security → Screen
Recording), then fully quit and relaunch the terminal. Without it
avfoundation silently produces black frames.
2. **Install a loopback audio device**, e.g. [BlackHole](https://github.com/ExistentialAudio/BlackHole)
(`brew install blackhole-2ch`), and route the call audio into it:
Audio MIDI Setup → `+` → *Create Multi-Output Device* → check both
BlackHole and your speakers, then select that multi-output device as
the system sound output. (Selecting BlackHole alone also works, but
you won't hear anything.)
3. **Find the device indices**:
```sh
ffmpeg -f avfoundation -list_devices true -i ""
# e.g. video: [2] Capture screen 0 audio: [2] BlackHole 2ch
```
4. **Run with the indices**:
```sh
JIBRI_MAC_VIDEO_DEVICE=2 JIBRI_MAC_AUDIO_DEVICE=2 \
~/dev/jibri/doc/local-run/run-jibri-local.sh
```
Note avfoundation screen devices capture the *whole* display at native
(retina) resolution — there is no per-window capture in ffmpeg on macOS.
The script scales the output down to `JIBRI_RECORDING_RESOLUTION` width,
but whatever else is on that screen ends up in the recording, so keep the
jibri Chrome window in front (or point it at a secondary display, e.g.
`Capture screen 1`).
### 7. Run jibri
```sh
~/dev/jibri/doc/local-run/run-jibri-local.sh
```
You should see:
```
config: /Users/you/dev/jibri/.local/jibri.conf
jar: /Users/you/dev/jibri/target/jibri-*-jar-with-dependencies.jar
chromedriver: /Users/you/dev/jibri/chromedriver
xmpp: 127.0.0.1:5222 (domain=meet.jitsi)
… INFO: Jibri starting up …
… INFO: Connecting to xmpp environment on 127.0.0.1 …
… INFO: Joined MUC: jibribrewery@internal-muc.meet.jitsi
```
Now open `https://192.168.1.42:8443/<room>` in two browsers, click the
record button — jibri will pick up the start IQ and join the call.
## Where things land
Generated files live under `~/dev/jibri/.local/`:
| File | Purpose |
|---|---|
| `jibri.conf` | HOCON config built from `.env` |
| `logging.properties` | JUL config (console + ffmpeg/browser file handlers) |
| `recordings/<session-id>/*.mp4` | recorded files |
| `ffmpeg.0.txt` | ffmpeg stderr (JUL captures it; not on console) |
| `browser.0.txt` | chrome browser logs |
| `chromedriver.log` | chromedriver's own log |
## Configuration knobs
All overridable as env vars when invoking the script:
| Var | Default | Notes |
|---|---|---|
| `JIBRI_DIR` | resolved from script path | jibri repo root |
| `DOCKER_JITSI_DIR` | `$HOME/dev/docker-jitsi-meet` | source of `.env` |
| `XMPP_HOST` | `127.0.0.1` | where prosody is reachable |
| `XMPP_PORT` | `5222` | matches `.env` if set there |
| `JIBRI_INSTANCE_ID` | `jibri-local-$$` | shows up as MUC nickname |
| `CHROMEDRIVER` | `$JIBRI_DIR/chromedriver` | path to chromedriver binary |
| `JIBRI_AUDIO_SOURCE` | `alsa` | Linux: ffmpeg `-f` for audio (`alsa`, `pulse`) |
| `JIBRI_AUDIO_DEVICE` | `plug:bsnoop` | Linux: ffmpeg audio input device (e.g. `default` for pulse) |
| `JIBRI_MAC_VIDEO_DEVICE` | unset | macOS: avfoundation video index (use a `Capture screen N` device) |
| `JIBRI_MAC_AUDIO_DEVICE` | unset | macOS: avfoundation audio index (use a loopback device, e.g. BlackHole) |
## Troubleshooting
**`permission denied while trying to connect to the docker API at unix:///var/run/docker.sock`**
— Linux only; your user isn't in the `docker` group. Fix:
```sh
sudo usermod -aG docker $USER
newgrp docker # apply the new group in this shell, or log out/in
docker ps # verify
```
Avoid `sudo docker compose …` — files written to `~/.jitsi-meet-cfg` end up
root-owned and bite you later.
**`cannot reach prosody at 127.0.0.1:5222`** — you brought up the stack
without `prosody.override.yml`. Re-run step 3.
**`This version of ChromeDriver only supports Chrome version N`** — your
chromedriver and Chrome milestones don't match. Re-fetch chromedriver for
your Chrome version (step 5).
**Jibri opens `https://meet.jitsi/<room>` instead of your IP** — `PUBLIC_URL`
isn't set in `.env`, or you didn't restart the script after editing `.env`.
**`No such file or directory: /var/log/jitsi/jibri/chromedriver.log`** —
older jibri pinned that path unconditionally. Pull a tree where
`webdriver.chrome.logfile` is overridable, or `mkdir -p` the path with
write permissions.
**ffmpeg quits with `Unknown PCM bsnoop` / `cannot open audio device plug:bsnoop`**
on Linux — you don't have the ALSA loopback set up. Either follow step 6
Option A (snd-aloop + `~/.asoundrc`) or skip ALSA and use Pulse via
`JIBRI_AUDIO_SOURCE=pulse JIBRI_AUDIO_DEVICE=default`.
**ffmpeg quits abruptly with "Error opening output files: Invalid argument"**
on macOS — usually an avfoundation arg conflict. Inspect
`~/dev/jibri/.local/ffmpeg.0.txt` for the real ffmpeg stderr. Note that the
default macOS ffmpeg input is `-i 0:0` (camera + system mic), so the
resulting mp4 records your camera, **not** the chrome window — see step 6b
for screen capture via `JIBRI_MAC_VIDEO_DEVICE`/`JIBRI_MAC_AUDIO_DEVICE`.
**Black video on macOS** — the terminal app that launched the script lacks
the Camera (default `0:0` input) or Screen Recording (screen-capture input)
permission. avfoundation doesn't error on a denied device; it delivers
black frames. Grant the permission in System Settings → Privacy & Security
and fully restart the terminal app.
**Black video on Linux** — you're on a Wayland session
(`echo $XDG_SESSION_TYPE`), where `x11grab` cannot see the screen and
records black. Either log into an Xorg session, or run a dedicated virtual
display and point both Chrome and ffmpeg at it:
```sh
Xvfb :1 -screen 0 1280x720x24 &
DISPLAY=:1 ~/dev/jibri/doc/local-run/run-jibri-local.sh
```
(the second part — overriding ffmpeg's hardcoded `-i :0.0+0,0` grab target —
currently requires editing `input-linux` in the generated conf).

View File

@@ -0,0 +1,4 @@
services:
prosody:
ports:
- '127.0.0.1:${XMPP_PORT:-5222}:${XMPP_PORT:-5222}'

238
doc/local-run/run-jibri-local.sh Executable file
View File

@@ -0,0 +1,238 @@
#!/usr/bin/env bash
#
# Generate a jibri.conf from a docker-jitsi-meet .env and run jibri from
# this source tree, connecting it to the running docker deployment.
#
# Defaults:
# JIBRI_DIR -> repo root (resolved from this script's location)
# DOCKER_JITSI_DIR -> $HOME/dev/docker-jitsi-meet
#
# Pre-req: prosody port 5222 must be reachable from the host. The default
# docker-jitsi-meet compose only `expose:`s it, so apply prosody.override.yml
# from this directory:
#
# docker compose -f docker-compose.yml \
# -f /path/to/jibri/doc/local-run/prosody.override.yml up -d
#
# See README.md alongside this script for a full walkthrough.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
JIBRI_DIR="${JIBRI_DIR:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
DOCKER_JITSI_DIR="${DOCKER_JITSI_DIR:-$HOME/dev/docker-jitsi-meet}"
ENV_FILE="$DOCKER_JITSI_DIR/.env"
[ -f "$ENV_FILE" ] || { echo "missing $ENV_FILE (set DOCKER_JITSI_DIR)" >&2; exit 1; }
[ -d "$JIBRI_DIR" ] || { echo "missing $JIBRI_DIR" >&2; exit 1; }
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
# Defaults match jibri/rootfs/defaults/xmpp.conf and jibri.conf templates.
XMPP_DOMAIN="${XMPP_DOMAIN:-meet.jitsi}"
XMPP_AUTH_DOMAIN="${XMPP_AUTH_DOMAIN:-auth.meet.jitsi}"
XMPP_INTERNAL_MUC_DOMAIN="${XMPP_INTERNAL_MUC_DOMAIN:-internal-muc.meet.jitsi}"
XMPP_HIDDEN_DOMAIN="${XMPP_HIDDEN_DOMAIN:-hidden.meet.jitsi}"
XMPP_MUC_DOMAIN="${XMPP_MUC_DOMAIN:-muc.meet.jitsi}"
JIBRI_BREWERY_MUC="${JIBRI_BREWERY_MUC:-jibribrewery}"
JIBRI_RECORDER_USER="${JIBRI_RECORDER_USER:-recorder}"
JIBRI_XMPP_USER="${JIBRI_XMPP_USER:-jibri}"
JIBRI_USAGE_TIMEOUT="${JIBRI_USAGE_TIMEOUT:-0}"
XMPP_PORT="${XMPP_PORT:-5222}"
XMPP_HOST="${XMPP_HOST:-127.0.0.1}"
JIBRI_RECORDING_RESOLUTION="${JIBRI_RECORDING_RESOLUTION:-1280x720}"
JIBRI_AUDIO_SOURCE="${JIBRI_AUDIO_SOURCE:-alsa}"
JIBRI_AUDIO_DEVICE="${JIBRI_AUDIO_DEVICE:-plug:bsnoop}"
# macOS only: avfoundation device indices for screen + audio capture.
# Find them with: ffmpeg -f avfoundation -list_devices true -i ""
# When unset, jibri's default input ("0:0", usually camera + mic) is used.
JIBRI_MAC_VIDEO_DEVICE="${JIBRI_MAC_VIDEO_DEVICE:-}"
JIBRI_MAC_AUDIO_DEVICE="${JIBRI_MAC_AUDIO_DEVICE:-}"
JIBRI_INSTANCE_ID="${JIBRI_INSTANCE_ID:-jibri-local-$$}"
JIBRI_STRIP_DOMAIN_JID="${JIBRI_STRIP_DOMAIN_JID:-${XMPP_MUC_DOMAIN%%.*}}"
: "${JIBRI_XMPP_PASSWORD:?JIBRI_XMPP_PASSWORD not set in .env (run gen-passwords.sh)}"
: "${JIBRI_RECORDER_PASSWORD:?JIBRI_RECORDER_PASSWORD not set in .env}"
BASE_URL_LINE=""
[ -n "${PUBLIC_URL:-}" ] && BASE_URL_LINE=" base-url = \"$PUBLIC_URL\""
WORKDIR="$JIBRI_DIR/.local"
JIBRI_CONF="$WORKDIR/jibri.conf"
LOGGING="$WORKDIR/logging.properties"
RECORDINGS="$WORKDIR/recordings"
mkdir -p "$WORKDIR" "$RECORDINGS"
# Probe prosody on the host before generating config / starting jibri.
# Run the /dev/tcp probe in a subshell so neither fd 3 nor stderr leak back here.
if ! (exec 3<>"/dev/tcp/$XMPP_HOST/$XMPP_PORT") 2>/dev/null; then
cat >&2 <<EOF
cannot reach prosody at $XMPP_HOST:$XMPP_PORT.
The default compose only exposes 5222 inside the docker network. Apply the
override shipped next to this script and restart:
docker compose -f docker-compose.yml -f $SCRIPT_DIR/prosody.override.yml up -d
EOF
exit 1
fi
# macOS screen capture: avfoundation's screen devices only deliver the native
# (retina) resolution, so drop -video_size from the input and scale on the
# encoder side instead.
FFMPEG_MAC_OVERRIDES=""
if [ -n "$JIBRI_MAC_VIDEO_DEVICE" ]; then
[ -n "$JIBRI_MAC_AUDIO_DEVICE" ] || {
echo "JIBRI_MAC_VIDEO_DEVICE is set but JIBRI_MAC_AUDIO_DEVICE is not (pick one from: ffmpeg -f avfoundation -list_devices true -i \"\")" >&2
exit 1
}
FFMPEG_MAC_OVERRIDES=$(cat <<EOG
input-mac = [
"-thread_queue_size", "4096",
"-f", "avfoundation",
"-capture_cursor", "0",
"-framerate", "30",
"-i", "$JIBRI_MAC_VIDEO_DEVICE:$JIBRI_MAC_AUDIO_DEVICE"
]
video-params = [
"-vf", "scale=${JIBRI_RECORDING_RESOLUTION%%x*}:-2",
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-r", "30",
"-crf", "25"
]
EOG
)
fi
FFMPEG_LINUX_OVERRIDES=$(cat <<EOG
input-linux = [
"-f", "x11grab",
"-i", "${DISPLAY:-:0}",
]
EOG
)
cat > "$JIBRI_CONF" <<EOF
jibri {
id = "$JIBRI_INSTANCE_ID"
single-use-mode = false
recording {
recordings-directory = "$RECORDINGS"
finalize-script = "$JIBRI_DIR/resources/finalize_recording.sh"
}
ffmpeg {
resolution = "$JIBRI_RECORDING_RESOLUTION"
audio-source = "$JIBRI_AUDIO_SOURCE"
audio-device = "$JIBRI_AUDIO_DEVICE"
$FFMPEG_MAC_OVERRIDES
$FFMPEG_LINUX_OVERRIDES
}
chrome {
flags = [
"--use-fake-ui-for-media-stream",
"--enabled",
"--autoplay-policy=no-user-gesture-required",
"--ignore-certificate-errors",
"--ozone-platform=x11" # Force X11, required for x11grab to capture video
]
}
stats {
prometheus.enabled = false
}
api {
xmpp {
environments = [
{
name = "docker-local"
xmpp-server-hosts = ["$XMPP_HOST"]
xmpp-domain = "$XMPP_DOMAIN"
$BASE_URL_LINE
control-muc {
domain = "$XMPP_INTERNAL_MUC_DOMAIN"
room-name = "$JIBRI_BREWERY_MUC"
nickname = "$JIBRI_INSTANCE_ID"
}
control-login {
domain = "$XMPP_AUTH_DOMAIN"
port = "$XMPP_PORT"
username = "$JIBRI_XMPP_USER"
password = "$JIBRI_XMPP_PASSWORD"
}
call-login {
domain = "$XMPP_HIDDEN_DOMAIN"
username = "$JIBRI_RECORDER_USER"
password = "$JIBRI_RECORDER_PASSWORD"
}
strip-from-room-domain = "$JIBRI_STRIP_DOMAIN_JID."
usage-timeout = "$JIBRI_USAGE_TIMEOUT"
trust-all-xmpp-certs = true
}
]
}
}
}
EOF
# Console-only logging. Root .level must allow FINE through to the handler.
cat > "$LOGGING" <<EOF
handlers = java.util.logging.ConsoleHandler
.level = FINE
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = org.jitsi.utils.logging2.JitsiLogFormatter
# ffmpeg/browser are attached to dedicated JUL loggers with parent propagation
# disabled, so their output never reaches the console. Pin their FileHandlers
# to the workdir so we can read them.
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.pattern = $WORKDIR/ffmpeg.%g.txt
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.formatter = org.jitsi.utils.logging2.JitsiLogFormatter
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.limit = 10000000
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.count = 5
org.jitsi.jibri.selenium.util.BrowserFileHandler.pattern = $WORKDIR/browser.%g.txt
org.jitsi.jibri.selenium.util.BrowserFileHandler.formatter = org.jitsi.utils.logging2.JitsiLogFormatter
org.jitsi.jibri.selenium.util.BrowserFileHandler.limit = 10000000
org.jitsi.jibri.selenium.util.BrowserFileHandler.count = 5
org.jitsi.level = FINE
org.jitsi.jibri.config.level = INFO
org.glassfish.level = INFO
jdk.internal.net.http.level = INFO
EOF
JAR="$(ls -t "$JIBRI_DIR"/target/jibri-*-jar-with-dependencies.jar 2>/dev/null | head -n1 || true)"
if [ -z "$JAR" ]; then
echo "building jibri..."
(cd "$JIBRI_DIR" && mvn -B -DskipTests clean package)
JAR="$(ls -t "$JIBRI_DIR"/target/jibri-*-jar-with-dependencies.jar | head -n1)"
fi
CHROMEDRIVER="${CHROMEDRIVER:-$JIBRI_DIR/chromedriver}"
[ -x "$CHROMEDRIVER" ] || { echo "missing chromedriver at $CHROMEDRIVER" >&2; exit 1; }
echo "config: $JIBRI_CONF"
echo "jar: $JAR"
echo "chromedriver: $CHROMEDRIVER"
echo "xmpp: $XMPP_HOST:$XMPP_PORT (domain=$XMPP_DOMAIN)"
echo
exec java \
-Djava.util.logging.config.file="$LOGGING" \
-Dconfig.file="$JIBRI_CONF" \
-Dwebdriver.chrome.driver="$CHROMEDRIVER" \
-Dwebdriver.chrome.logfile="$WORKDIR/chromedriver.log" \
-jar "$JAR"

4
doc/sip_gateway.md Normal file
View File

@@ -0,0 +1,4 @@
# SIP Gateway
Currently the XMPP API is the only officially supported API, so this doc will discuss how to configure Jibri to allow SIP gateway calls via the XMPP API.
To use the SIP gateway functionality of Jibri, you'll need to set the `sip_control_muc` field in `config.json`, it can be found within the `XmppEnvironment` field. Jibri will join this MUC to announce itself of being capable of providing SIP gateway services to Jicofo.

9
doc/xmpp_api.md Normal file
View File

@@ -0,0 +1,9 @@
# Jibri XMPP API
Jibri can be configured to join multiple XMPP environments which can be used as a control surface for leveraging Jibri services. The configuration for these environments lives in config.json (a documented sample of config.json can be seen in the code [here](TODO)). For a given XMPP environment, Jibri will:
* Connect to the provided XMPP domain on the provided XMPP host
* Login to a given auth domain with the given credentials
* Join a MUC on the given MUC domain with the given MUC jid using the given MUC nickname
* Publish its status (defined by the status packet extension [here](https://github.com/jitsi/jitsi/blob/master/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java)
At this point it will await an IQ message (defined by the custom IQ [here](https://github.com/jitsi/jitsi/blob/master/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java)) asking it to start or stop a given service. Whenever Jibri's status changes, it will send a new presence to reflect its current state.