arut/nginx-rtmp-module

Change stream file directory name to public name, instead of stream key

BlackBearFTW opened this issue · 16 comments

Hey, I made RTMP to HLS streaming work with OBS, if you pass a stream key, it creates a hls stream, in this case, the stream key is supersecret. What I would like to do is change that stream key name to some public name, so clients don't need to know the stream key of the publisher. For example supersecret -> player1. I looked at the directives and saw that on_publish should do this when I redirect the publisher. So I wrote this simple express endpoint for authentication

app.use(express.urlencoded());

app.post("/on_publish", function (req, res) {
   /* This server is only available to nginx */
   const streamkey = req.body.name; // req.body.name = test
   console.log(req.body)

   /* You can make a database of users instead :) */
   if (streamkey === "supersecret") {
      // res.location("/publish");

      res.setHeader('Location', "player1");

       res.status(301).send();
       return;
   }

   /* Reject the stream */
   res.status(403).send();
});

app.listen(8000, function () {
   console.log("Listening on port 8000!");
});

But instead of renaming the stream, it does nothing and still uses the stream key as a directive name (thereby I would have to call /supersecret/index.m3u8 to get access to the HLS stream.
image

How can I solve this issue?

events {}
rtmp {
    server {
        listen 1935; # Listen on standard RTMP port

        application live {
            live on;

            # disable rtmp playing, only HLS
            deny play all;

            hls on;
            hls_nested on;
            hls_path /tmp/hls;
            hls_fragment 10s; # default is 5s
            hls_playlist_length 60s; # default is 30s

            # once playlist length is reached it deletes the oldest fragments

            # authentication
            on_publish http://auth_server:8000/on_publish;
        }
    }
}

http {
    server {
        listen 8080;

        location / {
            root /www;
        }

        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                application/octet-stream ts;
            }
            root /tmp;
            add_header Cache-Control no-cache;

            # To avoid issues with cross-domain HTTP requests (e.g. during development)
            add_header Access-Control-Allow-Origin *;
        }

    }
}

Works: http://localhost:8080/live/supersecret/index.m3u8
Doesn't work: http://localhost:8080/live/player1/index.m3u8

I would like to avoid doing stuff like username?key=supersecret since that would allow the provider to change their username.

I did some digging, found a stackoverflow post that shows the redirect being a bit more
https://stackoverflow.com/questions/43568168/how-to-change-nginx-rtmp-stream-name-after-call-on-publish

You've got res.setHeader('Location', "player1");

However the stack overflow post has a full url

return redirect('rtmp://127.0.0.1/hls-live/testchannel1', code=302)

I tried this out, using older version of the module and ended up with supersecret and player1 being playable streams...

I did some digging, found a stackoverflow post that shows the redirect being a bit more
https://stackoverflow.com/questions/43568168/how-to-change-nginx-rtmp-stream-name-after-call-on-publish

You've got res.setHeader('Location', "player1");

However the stack overflow post has a full url

return redirect('rtmp://127.0.0.1/hls-live/testchannel1', code=302)

I tried this out, using older version of the module and ended up with supersecret and player1 being playable streams...

But isn't this only for RTMP streams and not HLS streams? Talking about the redirecting

Sequence for me is as follows

  1. I publish a stream from OBS using supersecret (flashver=FMLE/3.0 (compatible; FMSc/1.0))
  2. on_publish handler gets a call, sends redirect to rtmp://127.0.0.1/show/testchannel1, since this isn't a 40x error, I assume this is where nginx starts to create supersecret materials in hls directory (playlist and ts segments)
  3. nginx gets an RTMP connection from itself (flashver=LNX.11,1,102,55)
  4. on_publish handler has been told to return code 200 for this request that has addr=127.0.0.1, we now have testchannel1 materials in the hls directory

I'm not 100% sure if the notify_method being set to GET is important or not, I did find it easier to parse a GET request.

on_publish http://localhost:8000/on_publish;
notify_method get;

I tried reading the code that handles the response from the on_publish handler, but got nowhere.

I did confirm that if the on_publish handler returns 400 when queried about the connection from OBS, OBS will fail to connect

image

I like the testchannel1?key=supersecret idea.

I tried butts?key=notbutts

$ node onpublish.js
GET /on_publish?app=show&flashver=FMLE/3.0%20(compatible%3B%20FMSc/1.0)&swfurl=r
tmp://192.168.1.100/show&tcurl=rtmp://192.168.1.100/show&pageurl=&addr=192.168.1
.100&clientid=17&call=publish&name=butts&type=live&key=notbutts
We like the key, accept it.

Just make sure you verify both the key and name

Having the same problem!

Having the same problem!

Dont wanna use the name?key=streamkey, just the streamkey... Can't do this obviously due to the fact that it names the parent folder of the fragments to the streamkey itself! I've tried hls_nested off, this just doesnt work.

Having the same problem!

I found this first, but I didn't get it to work: https://benwilber.github.io/streamboat.tv/nginx/rtmp/streaming/2016/10/22/implementing-stream-keys-with-nginx-rtmp-and-django.html

The only solution I ever found is that you do something like username?key=token

If you take blackbearftw?key=supersecret, you end up with the full url being something like this: http://localhost:1935/live/blackbearftw?key=supersecret. It will then use the username as the foldername.

I personally foresaw issues with the username, since it's case sensitive, meaning that if the streamer types all caps, the folder name will be all caps aswell. To prevent this I used the on_publish to do an authentication request to my API (well first to nginx and then to my api as proxy to support https).

There I eventually had this code to validate the username and stream key

	public ErrorOr<Success> ValidateStreamKey(string username, string key) 	{ 		if (username != username.ToLower()) return Error.Failure("User.InvalidUsernameFormat", "Username should be lowercase."); 		User? user = _context.Users.FirstOrDefault(u => u.Username.ToLower() == username); 		if (user == null) return Error.NotFound("User.NotFound", "User not found."); 		if (user.StreamKey != key.Trim()) return Error.Validation("User.InvalidKey", "Streamkey is invalid."); 		return Result.Success; 	} 

this was my final nginx file

events {}
rtmp {
    server {
        listen 1935; # Listen on standard RTMP port

        application live {
            live on;

            # disable rtmp playing, only HLS
            deny play all;

            # authentication
            on_publish http://localhost:8080/on_publish;
            on_publish_done http://localhost:8080/on_publish_done;


            hls on;
            hls_nested on;
            hls_path /tmp/hls;
            hls_fragment 5s; # default is 5s
            hls_playlist_length 36000s; # default is 30s
            # once playlist length is reached it deletes the oldest fragments
        }

    }
}

http {
    server {
        listen 8080;

        location / {
            root /www;
        }

        location /on_publish {
            proxy_buffering off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass https://backend_api:443/api/streaming/start;
        }

        location /on_publish_done {
            proxy_buffering off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass https://backend_api:443/api/streaming/stop;
        }

        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                application/octet-stream ts;
            }
            root /tmp;
            add_header Cache-Control no-cache;

            # To avoid issues with cross-domain HTTP requests (e.g. during development)
            add_header Access-Control-Allow-Origin *;
        }
    }
}

lastly, I found a bunch of alternative tools that might be interesting: