Playing Adverts into the stream

Max

Administrator
Staff member
We have previously implemented hooks to authenticate the normal webrtc streams but never manage to get that working for the roomAPI. Here is the link of the thread https://forum.flashphoner.com/threads/our-server-is-being-spammed.12098/post-20396
Let's decribe the case as we see it:
- authenticated users subscribe to the audio only stream
- paid users just play the stream
- free users should listen advertising audio over the main audio when speaker presses a key
- it is desireable the advertising audio to be per user
As we mentioned it that thread, the RoomApi is excessive for your case. One REST hook and one REST API query seems enough:
1. Collect client session Id while authenticating a user with /connect REST hook:
PHP:
<?php
$api_method = array_pop(explode("/", $_SERVER['REQUEST_URI']));
$incoming_data = json_decode(file_get_contents('php://input'), true);
$response_data = $incoming_data;

switch($api_method) {
    case"connect":
        // Authenticate user as you wish
        if (authenticateUser($incoming_data)) {
            error_log("User authorized");
        } else {
            error_log("User not authorized. Connection failed with 403 status.");
            ubnormalResponse(403);
        }
    break;
}
header('Content-Type: application/json');
echo json_encode($incoming_data);

// User authentication
function authenticateUser($incoming_data) {
     ...
     storeSessionId($user_key, $incoming_data['sessionId']);
}

// Store sessionId to some DB
function storeSessionId($user_key, $sessionId) {
     ...
}

function ubnormalResponse($code) {
    if ($code == 403) {
    header('HTTP/1.1 403 Forbidden', true, $code);
    } else {
    header(':', true, $code);
    }
    die();
}
?>
2. When speaker presses a key, get free users session Ids from DB and send message to them using REST API /data/send (this should be done from backend server, not from speakers client):
Bash:
for sessionId in ${sessionIds[@]}; do
     curl -H "Content-Type: application/json" -X POST http://wcs:8081/rest-api/data/send -d '{"nodeId":"", "sessionId":"$sessionId",  "operationId":"", "payload":{"message":"Play advert"}}'
done
3. When client receives the message, play advert as we recommend in this post. Note that message will be received on session level, not on stream level:
JavaScript:
Flashphoner.createSession(sessionOptions).on(SESSION_STATUS.ESTABLISHED, function(session){
     ...
}}.on(SESSION_STATUS.APP_DATA, function(data){
     if (data.payload.message == "Play advert") {
           playAdvert();
     }
});
 

Azhar Ali

Member
Hi Max,

Thanks again

To do this, I would need to change the appurl to my custom URL? Would that also work for the RoomAPI?


The reason we use the room api is because, our service is not live all the time but users can stay connected to it and when publisher goes live, there are events which sent to all participants that stream is started. It just seems easier to use that otherwise we would have to handle the connected state to the room when there is no stream live.

Its like radio service but only live few hours a day.
 

Max

Administrator
Staff member
To do this, I would need to change the appurl to my custom URL?
Yes
Would that also work for the RoomAPI?
No, RoomApi backend cannot be replaced as we discussed earlier. The task is to exclude RoomApi.
The reason we use the room api is because, our service is not live all the time but users can stay connected to it and when publisher goes live, there are events which sent to all participants that stream is started
This can easily be implemented as described in the post above, p 2-3:
1. User connects to server by sreating a session. To handle the events sent to user, SESSION_STATUS.APP_DATA handler should be implemented:
JavaScript:
Flashphoner.createSession(sessionOptions).on(SESSION_STATUS.ESTABLISHED, function(session){
     ...
}}.on(SESSION_STATUS.APP_DATA, function(data){
     if (data.payload.message == "Play stream") {
          playStream();
     } else if (data.payload.message == "Play advert") {
          playAdvert();
     }
});
...

var stream;

function playStream() {
     stream = session.createStream({
          streamName: "stream",
          ...
     }).on(STREAM_STATUS.PLAYING, function (playStream) {
          ...
     });
     stream.play();
}
...

function playAdvert() {
     stream.muteRemoteAudio();
     advertVideo.src="https://host/advert.mp4";
     advertVideo.muted = true;
     advertVideo.onplay = function() {
            advertVideo.muted = false;
     };
     advertVideo.onended = function() {
            stream.unmuteRemoteAudio();
     };
     adVertVideo.play();
}
2. When you start a stream or advert, the message should be sent to all the sessions from backend
Bash:
if $playStream; then
     restData='{"nodeId":"", "sessionId":"$sessionId",  "operationId":"", "payload":{"message":"Play stream"}}'
elif $playAdvert; then
     restData='{"nodeId":"", "sessionId":"$sessionId",  "operationId":"", "payload":{"message":"Play advert"}}'
fi
for sessionId in ${sessionIds[@]}; do
     curl -H "Content-Type: application/json" -X POST http://wcs:8081/rest-api/data/send -d $restData
done
3. To remove sessionId from backend database when users closes the session, please implement /ConnectionStatusEvent REST hook
PHP:
<?php
$api_method = array_pop(explode("/", $_SERVER['REQUEST_URI']));
$incoming_data = json_decode(file_get_contents('php://input'), true);
$response_data = $incoming_data;

switch($api_method) {
    case "connect":
        // Authenticate user as you wish
        if (authenticateUser($incoming_data)) {
            error_log("User authorized");
        } else {
            error_log("User not authorized. Connection failed with 403 status.");
            ubnormalResponse(403);
        }
    break;
    case "ConnectionStatusEvent":
        if ($incoming_data['status'] == "DISCONNECTED" || $incoming_data['status'] == "FAILED") {
            removeSessionId($incoming_data['sessionId']);
        }
    break;
}
header('Content-Type: application/json');
echo json_encode($incoming_data);

// User authentication
function authenticateUser($incoming_data) {
     ...
     storeSessionId($user_key, $incoming_data['sessionId']);
}

// Store sessionId to some DB
function storeSessionId($user_key, $sessionId) {
     ...
}

// Remove sessionId from some DB
function removeSessionId($sessionId) {
     ...
}

function ubnormalResponse($code) {
    if ($code == 403) {
    header('HTTP/1.1 403 Forbidden', true, $code);
    } else {
    header(':', true, $code);
    }
    die();
}
?>
 

Azhar Ali

Member
Thanks max.. I will look into this now.

Can you confirm we wouldn't have issue sending messages from large user base from server and it wouldn't create a bottlenecks?
 

Max

Administrator
Staff member
Can you confirm we wouldn't have issue sending messages from large user base from server and it wouldn't create a bottlenecks?
Yes, you wouldn't have issue sending signaling messages from server to clients.
 

Azhar Ali

Member
Hello Max,

Thank you for the help in recent posts. I can confirm we have successfully moved away from the roomAPi. We also used the rest-api to send the data events to start / stop / advert.

Couple of questions though
1 - Whats the best way to protect the rest-api? e.g someone can easily workout the domain and then find_all api to send messages to clients.

2 -
When we were using the roomAPI we were having the delay as 0.5 sec with on avg 700 clients connected.
Since moving from the roomAPI to normal API latency is just above 1s with only 300 users connected to it.

That was streaming from UK and the listener was in UK too. Our server is in US as most of our viewers are in US. Latency is very important for our service. We do not at that stage require any video streaming and want to know how best to optimize the server for audio only to get the latency as close zero as possible.
We are already sending the video:false constraint.

Would you be able to help out by looking at the config of the server and advise?

Thanks in advance for all the help

Regards
Azhar
 

Max

Administrator
Staff member
Good day.
Whats the best way to protect the rest-api? e.g someone can easily workout the domain and then find_all api to send messages to clients.
You should enable REST API authentication by setting the following parameter
Code:
disable_rest_auth=false
Then you should add user and password for REST API queries using CLI as described here
Bash:
ssh -p 2001 admin@localhost 
add user rest_user rest_user_password
After that, you should use basic authentication for every REST API query.
When we were using the roomAPI we were having the delay as 0.5 sec with on avg 700 clients connected.
Since moving from the roomAPI to normal API latency is just above 1s with only 300 users connected to it.
There is no difference between stream publishing with or without RoomAPI, WebRTC publishing and playback are the same in both cases.
That was streaming from UK and the listener was in UK too. Our server is in US as most of our viewers are in US. Latency is very important for our service.
This seems like channel issue between UK and US.
To reduce the latency, you can switch from TCP transport (if you're using it) to UDP, but in this case there can be a losses on long distance channels.
The best way to reduce the latency between continents is to use CDN:
- US Origin for US streamer + US Edge for US listeners
- UK Origin for UK streamer + UK Edge for UK listeners
Please read this article about simple CDN example. A detailed case description is here.
 

Azhar Ali

Member
Hi Max,

This solution has been working fine and connections get added and removed nicely. I noticed today we had few connections from same Ip but different ports.
Code:
/193.117.128.46:52072/xxxxxx:8443-be12046c-4673-4fc6-9a6f-6f4164ef4336
/193.117.128.46:50097/xxx:8443-44f83b68-2122-4351-99c8-f90c9845275a
/193.117.128.46:53298/xxxx:8443-7bcf4694-d654-4fe9-8d0a-9061588c16c4

.........
Does this mean, user has multiple connections opened to the stream? Or there could be a case when disconnect request was not sent to my rest-api
In the connectionstatusevent, I remove the rows which are marked as status = "DISCONNECTED" Or status = "FAILED, Is there any other cases which need to be checked to remove the rows?

if you do send a PlayAdvert message to all those sessions, they do not give any error back in the response. Does that mean, they are still connected?

I find it hard to believe, a user would have open 9 versions of our player but its not impossible.

I have few of these examples so its not isolated to the single user either.

Just want to understand a bit more about it.

thanks
 
Last edited:

Max

Administrator
Staff member
Good day.
I find it hard to believe, a user would have open 9 versions of our player but its not impossible.
Those are probably different users behind the same NAT. Every client can use grey (private) IP only, for example 192.168.0.100, and internet service provider can use one white (public) IP for all those users. In this case, only source port will differ for such clients.
 

Max

Administrator
Staff member
Good day.
Since build 5.2.841 it is possible to iject one stream published on server into another using REST API
Code:
POST /rest-api/stream/inject/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json
Content-Length: 60
{
 "localStreamName": "stream1",
 "remoteStreamName": "stream2"
}
Plaese read details here, there are some limits affecting your case.
 
Top