Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
42f5ac8
Add Smelter Cloud documentation section
Gawor270 Jul 23, 2026
ff4f6ac
Publish the generated Smelter Cloud OpenAPI spec and align docs
Gawor270 Jul 24, 2026
1129c86
Make the smelter-cloud spec source a submodule like the other APIs
Gawor270 Jul 24, 2026
2155c73
Address review comments
Gawor270 Jul 24, 2026
55d64ca
Remove em dashes from the Smelter Cloud docs
Gawor270 Jul 24, 2026
98a8544
Move the Smelter Cloud API reference under the API Reference tab
Gawor270 Jul 24, 2026
8954669
Add Smelter Cloud REST API to the API Reference navbar dropdown
Gawor270 Jul 24, 2026
b3456e0
Unify the API Reference entry point
Gawor270 Jul 27, 2026
511d4ae
Give the API Reference tab its own sidebar
Gawor270 Jul 27, 2026
df4bb7f
Integrate compositions into the core docs structure
Gawor270 Jul 27, 2026
95dfba8
Rename the composition submodule key to match its path
Gawor270 Jul 30, 2026
7246ddd
Update the composition spec to the merged template-workers tip
Gawor270 Jul 30, 2026
4446fb3
Drop the composition spec presentation overlay
Gawor270 Jul 30, 2026
a5b8106
Correct composition lifecycle and forwarding details
Gawor270 Jul 30, 2026
02d99cd
Fix composition docs against the live API and track foundry main
Gawor270 Aug 6, 2026
b3d41de
Show the composed stream's destination in the tutorial diagram
Gawor270 Aug 6, 2026
b8a9d32
Move scene updates into the tutorial and how-to
Gawor270 Aug 6, 2026
7ec83fc
Point the Smelter links at the right reference
Gawor270 Aug 6, 2026
2123a54
make main tutorial easier
Gawor270 Aug 6, 2026
8e59b39
improve livestream input introduction
Gawor270 Aug 6, 2026
d6a31f3
template tutorial make better
Gawor270 Aug 6, 2026
1857f74
make fishjam room compose tutorial better
Gawor270 Aug 6, 2026
8d03cd4
add warnings about cleaning up
Gawor270 Aug 6, 2026
f08ccbd
Add a markdownlint config
Gawor270 Aug 7, 2026
55814f5
Drop globs from the markdownlint config
Gawor270 Aug 7, 2026
d67067a
reduce reference to minimum
Gawor270 Aug 7, 2026
5c2d66c
update smelter version to 0.3.0
Gawor270 Aug 7, 2026
a3a6fcd
improve text
Gawor270 Aug 7, 2026
47f8895
Document the output resolution limits
Gawor270 Aug 7, 2026
1b3fe88
Refresh the composition spec from the pending API changes
Gawor270 Aug 11, 2026
2fe7deb
Fold the template pitch into the tutorial's next steps
Gawor270 Aug 11, 2026
8088000
Name the two API surfaces the way the reference does
Gawor270 Aug 11, 2026
1ee628f
Stop describing compositions in terms of the hardware under them
Gawor270 Aug 11, 2026
4460d20
Say plainly that a composition cannot be watched directly
Gawor270 Aug 11, 2026
14116a9
Name the destinations instead of calling them somewhere else
Gawor270 Aug 11, 2026
a92de2e
remove crappy text
Gawor270 Aug 11, 2026
c17e3bb
Fix the gaps found reviewing the composition docs
Gawor270 Aug 11, 2026
3795584
simple text corrects
Gawor270 Aug 13, 2026
ec227d6
Call the corner layout the same thing in all three places
Gawor270 Aug 13, 2026
43e2580
Show the composed frame instead of drawing it in ASCII
Gawor270 Aug 13, 2026
76542bd
Resolve the video path against the site base URL
Gawor270 Aug 13, 2026
d678217
Show only the starting layout in the tutorial clip
Gawor270 Aug 13, 2026
98fc7ae
Create the tutorial livestream through the Sandbox API
Gawor270 Aug 13, 2026
0482655
Revert "Create the tutorial livestream through the Sandbox API"
Gawor270 Aug 13, 2026
a8278c9
Trim the sentence describing what you should see
Gawor270 Aug 13, 2026
d056211
Create the tutorial livestream through the Sandbox API, unlisted
Gawor270 Aug 13, 2026
aeea135
Drop vdo.ninja and a playback claim the tutorial no longer supports
Gawor270 Aug 13, 2026
55435da
Call the token the same thing in the prose and the placeholder
Gawor270 Aug 13, 2026
05ed01e
Point the events how-to at deploying and at getting a livestream
Gawor270 Aug 13, 2026
33324e8
Define every variable the composition how-tos use
Gawor270 Aug 13, 2026
b00849f
Set up the composition and livestream right before deploying
Gawor270 Aug 13, 2026
ea5c515
Just say what to create, without the reasoning
Gawor270 Aug 13, 2026
475b800
Take the Sandbox API URL from the panel, not from the Fishjam ID
Gawor270 Aug 14, 2026
0abfd4f
Create the events composition after the template, not before
Gawor270 Aug 14, 2026
0f38708
Spell out the WHIP publish address correctly
Gawor270 Aug 14, 2026
1d4b8fa
Say what actually keeps a composition alive
Gawor270 Aug 14, 2026
8b15b30
Refresh the composition spec from main and drop HLS input
Gawor270 Aug 14, 2026
ddf5459
Refresh the composition spec and drop app from the RTMP input
Gawor270 Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "packages/python-server-sdk"]
path = packages/python-server-sdk
url = https://github.com/fishjam-cloud/python-server-sdk.git
[submodule "api/composition"]
path = api/composition
url = git@github.com:fishjam-cloud/foundry.git
49 changes: 49 additions & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Rule reference: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
//
// Prettier is the formatter of record in this repo, so every rule it already
// governs is turned off here. Leaving them on produces thousands of warnings
// that contradict `yarn format`.
{
"config": {
"default": true,

// Prose is deliberately left unwrapped: `.prettierrc` sets no `proseWrap`,
// so Prettier defaults to "preserve" and never reflows paragraphs. Wrapping
// would also make one-word edits reflow whole paragraphs in diffs.
"MD013": false,

// MDX is JSX: <Tabs>, <TabItem>, and friends are not stray inline HTML.
"MD033": false,

// Docusaurus frontmatter carries the title, so files legitimately start
// without a heading and may repeat the title as an H1.
"MD025": false,
"MD041": false,

// House style: explanation pages open with an italic one-line subtitle.
"MD036": false,

// Repeated headings are fine under different parents ("Clean up",
// "Prerequisites"), but not as siblings.
"MD024": { "siblings_only": true },

// Whitespace, list, and emphasis style: all owned by Prettier.
"MD004": false,
"MD007": false,
"MD012": false,
"MD022": false,
"MD031": false,
"MD032": false,
"MD046": false,
"MD048": false,
"MD050": false,
},
"ignores": [
// Generated by TypeDoc from SDK sources.
"docs/api/**",
// Snapshots of released versions; fix issues in `docs/` instead.
"versioned_docs/**",
"node_modules/**",
"build/**",
],
}
1 change: 1 addition & 0 deletions api/composition
Submodule composition added at 497635
6 changes: 6 additions & 0 deletions docs/_common/compositions/idle-cleanup.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
:::warning[A composition bills for the whole time it exists]
With that guard off, cleanup waits for the outputs to fall silent too, so one that keeps
publishing is never cleaned up for you. Delete it as soon as you are finished, and do not
leave one running after a test. See
[Cost and lifecycle](../../explanation/compositions#cost-and-lifecycle).
:::
14 changes: 13 additions & 1 deletion docs/api/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type: reference

Describes APIs for direct interaction with Fishjam.

Fishjam publishes documentation for the Sandbox API and Fishjam Server APIs.
Fishjam publishes documentation for the Sandbox API, Fishjam Server APIs, and the Composition API.

## Sandbox API

Expand Down Expand Up @@ -51,3 +51,15 @@ the first message that must be sent is an `AuthRequest`,
with a valid Management Token.

Next, you can should subscribe to notifications by sending `SubscribeRequest` event with `SERVER_NOTIFICATION` event type.

## Compositions

[Compositions](../explanation/compositions) are managed through the Composition API. All requests go to `https://rtc.fishjam.io`.

### REST API

[Composition REST API Reference](/api/compositions)

Calls authenticate with your Management Token, from the [**Fishjam developer panel**](https://fishjam.io/app).

Request bodies are snake_case and closed: a field the endpoint does not define is rejected with `422` rather than ignored, so a misspelling fails loudly instead of silently doing nothing.
112 changes: 112 additions & 0 deletions docs/explanation/compositions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
type: explanation
title: Compositions
sidebar_position: 4.5
description: Understand compositions, the Fishjam feature that mixes multiple live media streams into a single composed output in real time.
---

# Compositions

_Understanding real-time stream composition in Fishjam_

A **composition** mixes multiple live media streams into a single output stream in real time. You send inputs (WebRTC, RTMP, or MP4), describe how they should be laid out, and it produces one composed stream that it publishes to a destination of your choosing, all without running any rendering infrastructure yourself.

Compositions are built on [Smelter](https://smelter.dev), the source-available real-time video compositing engine by [Software Mansion](https://swmansion.com), and are a native part of Fishjam: they authenticate with the same Fishjam credentials and can compose the peers of a [Fishjam room](./rooms) directly.

## What you can build

- **Multi-party layouts**: arrange the cameras of a conference or livestream into grids, side-by-sides, or picture-in-picture.
- **Branded streams**: overlay logos, captions, lower-thirds, and backgrounds on top of live video.
- **Cross-protocol bridging**: take WebRTC inputs and republish the composed result over RTMP, for example broadcasting a conference to YouTube or Twitch, or the other way round.

## Core concepts

A **composition** is a single running compositing session. You register three kinds of things into it:

- **Inputs**: the live media sources being composed (WebRTC via WHIP/WHEP, RTMP, or MP4).
- **Outputs**: where the composed result is sent (WebRTC via WHIP, or RTMP). Each output carries a **scene** that describes the layout.
- **Renderers**: shared assets such as images and fonts you can place in a scene.

A composition produces video, it does not serve it. There is nowhere to point a player at a composition, so it always needs an output aimed at a destination your viewers can connect to instead: a [livestream](./livestreams) they watch over WHEP, or an RTMP service such as YouTube.

An output's scene can either be described directly in the API or rendered by a **template**: a React component, written with the layout components and the [`@fishjam-cloud/composition`](./../how-to/compositions/write-and-deploy-a-template) hooks, that updates the layout live as the room changes.

## Cost and lifecycle

A running composition holds a live rendering session for as long as it exists, and you are billed for that time whether or not anyone is watching.

Two defaults keep that in check. A composition auto-starts, and it cleans itself up after five minutes in which none of its inputs carry any media.

`cleanup_without_inputs: false` turns that guard off. It tightens the condition so cleanup needs both the inputs and the outputs to go quiet, which is what you want when inputs arrive late, such as a room whose peers have not joined yet, or when a composition legitimately has no inputs, such as an output that renders only text. The cost is that a composition whose output keeps publishing is no longer cleaned up for you, so anything created that way is yours to delete, and a forgotten one bills until you do.

Delete a composition as soon as you are done with it:

```bash
curl -X DELETE "$COMPOSITION_URL/api/composition/$COMPOSITION" \
-H "Authorization: Bearer $TOKEN"
```

## Scenes

Every video output carries a **scene**: a tree of components that describes how inputs, text, and images are arranged into the composed frame. Audio outputs carry an **audio scene** that describes which inputs are mixed together.

### The video scene tree

A video scene has a single `root` component. Each component has a `type` that determines how it lays out its children.

```json
{
"root": {
"type": "tiles",
"children": [
{ "type": "input_stream", "input_id": "camera_1" },
{ "type": "input_stream", "input_id": "camera_2" }
]
}
}
```

The available component types are:

| `type` | Purpose |
| -------------- | -------------------------------------------------------------------------------- |
| `input_stream` | Renders one registered input. Identified by `input_id`. |
| `view` | A container you position and style; the basic building block for custom layouts. |
| `tiles` | Automatically arranges its children into a grid. |
| `rescaler` | Fits a single child into a target area, preserving aspect ratio. |
| `text` | Renders a text string. |
| `image` | Renders a registered image. Identified by `image_id`. |

Components nest freely: a `tiles` of `rescaler`s wrapping `input_stream`s, a `view` with a `text` caption over an `input_stream`, and so on. The styling and full property set of each component come from Smelter itself; the [Smelter HTTP API reference](https://smelter.dev/http-api/overview) documents every component and its properties.

### The audio scene

An audio scene lists the inputs to mix and, optionally, their relative volume:

```json
{
"inputs": [
{ "input_id": "camera_1" },
{ "input_id": "camera_2", "volume": 0.5 }
]
}
```

`volume` defaults to `1.0`. Only the inputs you list are audible in the output.

### Setting a scene

You provide the initial scene when you register an output, under `video.initial` (a video scene) and `audio.initial` (an audio scene). See [Choose inputs and outputs](./../how-to/compositions/inputs-and-outputs) for the full output shape.

### Changing a scene over time

A scene is not fixed for the life of an output. You can replace it while the composition is running, immediately or at a chosen point on the composition timeline.

Either you push those updates yourself, or you hand the job to a **template**: a React component that receives the live room state and re-renders as the room changes. See [Choose inputs and outputs](./../how-to/compositions/inputs-and-outputs) to send an update, or [Write and deploy a template](./../how-to/compositions/write-and-deploy-a-template) to build one.

## Where to go next

- [Compositions tutorial](./../tutorials/compositions): create your first composition end to end.
- [Write and deploy a template](./../how-to/compositions/write-and-deploy-a-template): build a React layout with the composition SDK.
- [Compose a Fishjam room](./../how-to/compositions/compose-a-fishjam-room): turn a room's peers into one composed stream.
- [Composition API](./../api/reference#compositions): the full REST surface.
8 changes: 8 additions & 0 deletions docs/how-to/compositions/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Compositions",
"position": 5,
"link": {
"type": "generated-index",
"description": "Compose rooms and live streams into new outputs with templates, events, and multiple transports."
}
}
Loading
Loading