Skip to main content

Example: Broadcasting

Using API.stream scene composition, you can build fully api-driven broadcasts from your backend. The following code uses our JavaScript client @api.stream/sdk however all API calls are 1:1 with our REST API and GRPC bindings. A pre-built project for this example can be found on github.

0. Prerequisites

This example makes the assumption that you're using our JavaScript SDK. If not, all methods used can be accessed via our REST APIs.

npm install @api.stream/sdk

You'll also need an API key (development or production) to access our APIs and walk through this example.

1. Authentication

Start by authenticating the SDK using your API key. This will allow you to generate access tokens, allowing you to interact with the Live and Layout APIs.

caution

Using with your API key in the browser is fine for testing however your API key should never be exposed. In production your API key should always be used server side when dealing with an API key or issuing authentication tokens.

import { ApiStream } from '@api.stream/sdk';

const sdk = new ApiStream({ apiKey: 'your-api-key' });

sdk.LiveApi().backendAuthentication?.createAccessToken({
// This is an opaque string. This should map to a user within your
// backend, e.g a users primary key. All resources returned from
// the API will be scoped to this.
serviceUserId: 'my-test-user',
}).then(client => {
// Load the access credentials into the SDK
return sdk.load(client);
})

2. Create the collection and project

Creating a collection is the first step. A collection is a container for all projects and also contains any video sources. Each project can be considered a broadcastable unit and has it's own set of layouts. Layouts cannot be transferred between projects.

import { LiveAPIModel } from '@api.stream/sdk';

const collection = sdk.LiveApi().collection?.createCollection({})
.then(response => response.collection);

const project = sdk.LiveApi().project?.createProject({
collectionId: state.collection.collectionId,
rendering: {
// This is the output resolution of the broadcast.
video: {
width: 1280,
height: 720,
framerate: 30,
},
quality: LiveApiModel.RenderingQuality.RENDERING_QUALITY_STANDARD,
},
composition: { scene: {} },
}).then(response => response.project);

3. Create a layout

Create one (or more layouts) to be used in the broadcast. Each layout can have it's own set of layers which can be transitioned between. When creating the layout you should specify the type to be LAYOUT_TYPE_SCENE to ensure we treat it as such. (If you wish to use the Layout API outside of scene composition, you can also use type LAYOUT_TYPE_SCENELESS for free-form layouts).

caution

The resolution defined for the layout should match the resolution defined for the project.

import { LayoutApiModel } from '@api.stream/sdk';

const layoutA = sdk.LayoutApi().createLayout({
layout: {
// This should match the project render resolution.
width: 1280,
height: 720,

// This references the project and collection we created earlier.
// All layouts must be assigned to the same project for layout switching
// to work.
collectionId: project.collectionId,
projectId: project.projectId,

// Important: this layout must be defined as LAYOUT_TYPE_SCENE for it to
// be composable.
type: LayoutApiModel.LayoutType.LAYOUT_TYPE_SCENE,
}
});

4. Add an RTMP source

In order to use an RTMP source in a layout, it must first be defined on the collection and assigned to the project. Once done, this source can be used within any layout in the project.

import { LiveAPIModel } from '@api.stream/sdk';

// Create a new RTMP source we can publish to.
const source = await sdk.LiveApi().source?.createSource({
collectionId: project.collectionId,
address: {
rtmpPush: {
enabled: true
}
}
}).then(response => response.source);

// Assign the RTMP source to our previously created project.
await sdk.LiveApi().source?.addSourceToProject({
collectionId: project.collectionId,
projectId: project.projectId,
sourceId: source.sourceId,
});

When interacting with Layout API it's important to keep in mind every layer must exist beneath a root layer. When the layout type is LAYOUT_TYPE_SCENE this root layer will automatically be created with the layout. For greater detail, see the scene composition model

tip

We support a selection of different layer types that can be inserted into the layout. These are all interacted in a similar way. See the Layer type documentation for more detail on what we support.

// Important: find the "root" layer all children should be assigned to
const root = await api.LayoutApi().layer.listLayers({ layoutId: layoutA.id })
// note: This will exist by default as long as the layout is created as LAYOUT_TYPE_SCENE
.then(response => response.layers.find(layer => layer.type === 'root'));

// Add the source to our layout.
await sdk.LayoutApi().layer.createLayer( {
layoutId: layoutA.id,
layer: {
type: 'source',
data: {
source: {
// The source ID specified here matches the source we registered.
id: source.id,

// Volume can be between 0 and 1, 0.5 for half.
volume: 0.5,
}
}

// Specifying parentId when creating a layer will create it top-most on the layout.
parentId: root.id,

// Position the layer
x: 0,
y: 0,

// This should _at least_ be the same aspect ratio as the source feed.
// Consider setting it to be the same resolution and using the `scale`
property to adjust size.
width: 1280,
height: 720,
},
});

5. Selecting the layout

To ensure the correct layout is loaded, you should now make a call to update the project with the updated layout ID. You will also use this in the future when switching between multiple layouts.

await sdk.LiveApi().project?.updateProject( {
collectionId: collection.collectionId,
projectId: project.projectId,

composition: {
scene: {
selectedLayoutId: layoutA.id
},
},
updateMask: ["composition.scene.selectedLayoutId"],
});

6. Set the destination

Finally, you should add a destination for where to output the broadcast to.

await sdk.LiveApi().destination?.createDestination({
collectionId: state.collection?.collectionId,
projectId: state.project?.projectId,
address: {
rtmpPush: {
url: "rtmp://your-rtmp-server/app",
key: "the-secret-key",
},
},
enabled: true,
});

7. Go Live

With the project set up, we're now able to go live and broadcast to the destination.

warning

A charge will be incurred for the stream per minute when using the production API Key. Ensure you're testing with the development API Key or stop the stream once done testing.

await sdk.LiveApi().project?.startProjectBroadcast({
collectionId: project?.collectionId,
projectId: project?.projectId,
});

8. Done

The final step is to stop broadcasting.

await sdk.LiveApi().project?.stopProjectBroadcast({
collectionId: project?.collectionId,
projectId: project?.projectId,
});

For a fully functioning demo, complete with animations, take a look at the example project.