I am in a process of making a substitution for Twitch for coders, so they can freely pick/create a category they want to stream in and avoid people who are growing "grass" on Twitch in the "Science & Technology" category.
I've started with nginx-rtmp-module, and build my frontend with this module in mind, however, the problems and limitations i've encountered during the development, or actually, testing phase had me thinking about switching to another product. Specifically, there is no easy (or even possible) way to hide the private stream key from users (those who are going to watch the stream), but this is not the only one.
I've tried Nimble Streamer, however, their "proprietary" approach to this software, which is free to use, however requires you to use their panel to manage streams, which is paid, and to be honest counter intuitive feared me away.
I've tried the Ant Media Server, but yet again, most simple features like authentication is included only with Enterprise version of the software.
Don't get me wrong, I know that later in the "production" stage I will have to think about moving to something more "enterprise" oriented, however, right now, I am just looking for a product, which:
- Supports events (stream started, stream ended, stream updated) - nginx-rtmp-module does this already, but as I've already said, it comes with some limitations
- Supports REST (or at least presents the data in some way to the reader, which is a script to pull information)
- Best, if it is open source or free for development stage
And what this Media Server should be able to do:
- Accept RTMP/RTMPS as a source
- Produce valid DASH/HLS manifests (nginx-rtmp-module in some cases fails to do so)
- Great, but not needed right now, support for adaptive streaming
What I've already tested:
- nginx-rtmp-server (and its forks)
- Nimble Streamer
- Ant Media Server
- OSSRS/srs - had problem to even configure it properly, so many warnings on newer types of OSes
Update
I have the following config:
application live {
deny play all;
live on;
dash on;
on_publish http://app.local/api/stream/start;
on_publish_done http://app.local/api/stream/stop;
on_update http://app.local/api/stream/update;
dash_repetition on;
dash_fragment 5s;
dash_playlist_length 60s;
dash_cleanup on;
dash_nested on;
dash_clock_compensation http_head;
dash_clock_helper_uri http://live.local/time;
dash_path /tmp/dash/stream-dash;
}
The thing is, stream issued by the /stats page is actually the new public key, however, information about the fragments and manifest is still written in the folder of the private key.
Why stats page shows stream with public key but data is still written in the folder with private key name?
P.S. And here is the logic of new public key generation written in PHP
- Extract application and name from the request
- Check if request actually has these values (expecting to get two, so counting them)
- Plucking $key (name) and $app (application) from resulting array
- Caching channel information (here we are checking if channel with this streaming key exists and if it is, caching it in Redis)
- Checking if model is null, if it is, then incorrect stream key is provided
- Fetching User associated with the Channel
- Checking if User is banned from streaming
- Creating new Stream entry (with all data associated with RTMP stream and Live status)
- Generating cache key form the model (to eliminate further requests to database)
- Updating stream cache with default information (everything set to null)
- Sending out event to browser that stream has started
- Returning 301 response and setting Location to SHA-512 hash of the Stream UUID
The key resulted from step 12 is actually new public key, and as you can see, stats page actually returns this exact key, however, data is still written to the folder with the key name obtained from Step 3
UPDATE #2. Simplified version of key generation algorithm
/**
* Start stream
* @return Response
*/
public function start() : Response
{
$requiredStreamKey = 'live_1_B562zv8D6agoozRcXHiiYlvJ1EVVqkF7Q2GBYVE6XkqCsUCH';
$request = array_values(request()->only(['name', 'app']));
if (\count($request) < 2) {
return $this->sendError(Response::HTTP_BAD_REQUEST);
}
[$key, $app] = $request;
if ($requiredStreamKey !== $key) {
return response('', Response::HTTP_NOT_FOUND);
}
$exampleUUID = 'fcefd01c-f379-414c-95f8-c4e6e2b0ffc7';
$hash = hash('sha512', $exampleUUID);
return response('Generic Response', Response::HTTP_MOVED_PERMANENTLY)->withHeaders([
'Location' => sprintf('%s', $hash)
]);
}