Part 1: Wildcard streams
Currently, UGC platforms building on top of the Livepeer API would have to create a new Stream object for each user. That's not ideal; if I want to update my bitrate ladder site-wide, I'd have to iterate through every single stream definition! So, let's allow one stream to be used multiple times.
Streams get a new field: {"wildcard": true/false}
.
If false, current behavior:
Ingest URL: rmtp://${ server }/live/${ streamKey }
Playback URL: https://${ cdn }/hls/${ playbackKey }/index.m3u8
All is well.
If true, each streaming endpoint can be used multiple times. (And probably streaming to the root endpoint is not allowed.)
Ingest URL: rtmp://${ server }/live/${ streamKey }+${ anything }
Playback URL: https://${ cdn }/hls/${ playbackKey }%2B${ anything }/index.m3u8
To implement this part, all we need to do is add the field on the server then tweak the logic on mist-api-connector
to handle URLs in such a form.
Part 2: Playback configuration webhook
That alone will be sufficient for lots of use cases, but there's also a problem for the primary UGC use case... there's only one secret streamKey
. if they use ingest URLs of the form rtmp://example.com/live/streamKey+userName
, that means anyone can stream to any user by using rtmp://example.com/live/streamKey+otherUserName
!
The solution here is to allow for UGC platforms to provide internal stream keys to their users and then provide the playbackId to Livepeer during the resolution of the streamStarted
webhook. In this example, imagine I'm implementing a service called Stream.Town. The workflow will look like this:
- Before anyone streams, I create a Stream ` object with my desired renditions. It has:
POST /api/stream
{
...rendition configuration omitted
"wildcard": true
}
201 Created
{
"id": "livepeer-id",
"streamKey": "livepeer-stream-key",
"playbackId": "livepeer-playback-id"
}
- I also create a webhook for the streamStarted event of that stream:
POST /api/webhook
{
eventType: "streamStarted",
streamId: "livepeer-id",
url: "https://stream.town/api/handle-webhook"
}
- A user signs up on Stream.Town. When they sign up, I provide them with a stream key and save it in their user record in the Stream.Town database:
{
"username": "iameli",
"streamKey": "streamtown-stream-key"
}
- On the StreamTown frontend, I geolocate (
GET /api/ingest
) then provide them with their RTMP ingest URL: rtmp://some-origin.livepeer.com/live/livepeer-stream-key+streamtown-stream-key
.
- The user streams in. A series of webhooks fire, and stream.town returns the playbackId during the streamStarted event.
POST https://stream.town/api/handle-webhook
{
"stream": {
"id": "livepeer-id",
"playbackId": "livepeer-playback-id"
...
}
"streamKey": "streamtown-stream-key"
}
I proceed to check my database for streamtown-stream-key
, and find that it corresponds to the user iameli
. So I send that back from the webhook:
200 OK
{
"playbackId": "iameli"
}
- That playbackId gets inserted into the newly-created Stream Session as
livepeer-playback-id+iameli
and used for playback at https://example-cdn.livepeer.com/hls/livepeer-playback-id%2Biameli/index.m3u8
.