Playing Adverts into the stream

Azhar Ali

Member
Hi,

I wanted to ask if there is any way to inject the audio only adverts into the stream?
We have a voice only service which user subscribe to, we would like to play adverts where admin press a button and the advert is choose based on some business logic and it plays to the users who are listening.
We do not need to delay the voice, it can take over the main audio stream and reverts back when audio is finished?

Regards
Azhar
 

Max

Administrator
Staff member
Hello,

A stream can be injected to a call, but not to a stream.
Audio-only mixer can be tried for streams.
Setting for audio-only mixer in WCS_HOME/conf/flashphoner.properties
Code:
mixer_video_enabled=false
REST methods
- /mixer/startup
- /mixer/add
- /mixer/remove
For a broadcast, a mixer is created and the broadcaster's stream is added to the mixer; for an advert, the broadcaster's stream is removed, a stream with advert is added, and, when the advert is finished, the broadcaster's stream is added again.
 

Max

Administrator
Staff member
Hello,

how to achieve that with mixer?
Please try it using the REST methods
1. Create mixer using /mixer/startup (the mixer stream will be published)
2. Publish the "voice only service" stream
3. Add the stream to the mixer using /mixer/add
4. Subscribe to listen to the mixer stream
5. Remove the "service" stream from the mixer using /mixer/remove (or the stream can be left in the mixer but with audio muted by its publisher)
6. Publish an advert stream
7. Add the advert stream to the mixer using /mixer/add
8. When the advert is finished, remove its stream from the mixer using /mixer/remove
9. Add again the "service" stream to the mixer (or, unmute, if it was muted not removed)
There is no demo example for this specific case. (How the mixer REST methods are used can be seen in the SIP as RTMP example.)
Please clarify what is an advert source - MP3?

Also, internal ticket WCS-2654 has been created to consider injecting to a stream.
 

Azhar Ali

Member
Thanks for that..advert is of mp3 format. Ideally we would want to trigger an advert from client browser. In our case, we have provided our stream as white label to other sites. When publisher decides to play an advert we send a message to all connected clients who based on their site play their own advert. So something to trigger an advert with url to mp3 will be super helpful.
I will try mixer but sounds like quite a bit of re work of already working solution we have.
Thanks
 

Azhar Ali

Member
Also to add to that.. some users have paid subscription so they shouldn't get any adverts.. so triggering the advert from client browser also helps
 

Max

Administrator
Staff member
Good day.
You can manage advert fully on client side without mixer.
First, convert mp3 advert file to mp4 container with AAC sound track using ffmpeg or another reencoding tool. Then, workflow on client side should be like this:
1. When message is received to play an advert, mute HTML5 video element where audio is playing (using muteRemoteAudio() function for example)
Code:
stream.muteRemoteAudio();
2. Create a new HTML 5 video element dynamically over the existing element (with id advertVideo for example)
3. Play advert.mp4 as VOD in advertVideo element by setting stream name to vod://advert.mp4
Code:
var options = {
  name: "vod://advert.mp4",
  display: advertVideo,
  constraints: {audio: true, video: false}
}
session.createStream(options).on(STREAM_STATUS.STOPPED, function() { 
  advertVideo.remove();
}).on(STREAM_STATUS.FAILED, function() { 
  advertVideo.remove();
}).play();
4. Remove advertVideo element when advert is finished as shown in example above
5. Unmute main audio, for example
Code:
stream.unmuteRemoteAudio();
 

Azhar Ali

Member
Hi Max,
This is exactly how we have done it. Only issue with this method is calling play.. chrome often blocks the play due to auto play policy. Any way to avoid that?
 

Max

Administrator
Staff member
Only issue with this method is calling play.. chrome often blocks the play due to auto play policy. Any way to avoid that?
You should advertVideo element before play() call, then unmute it on STREAM_STATUS.PLAYING:
JavaScript:
var options = {
  name: "vod://advert.mp4",
  display: advertVideo,
  constraints: {audio: true, video: false}
}
advertVideo.muted = true;
session.createStream(options).on(STREAM_STATUS.PLAYING, function(stream) { 
  stream.unmuteRemoteVideo();
}).on(STREAM_STATUS.STOPPED, function() { 
  advertVideo.remove();
}).on(STREAM_STATUS.FAILED, function() { 
  advertVideo.remove();
}).play();
 

Azhar Ali

Member
Hi Max,
I am trying to implement the solution. We have roughly 700 clients connected to our stream. I send all of them a message to say play advert.
To do that we look through all participants and send a message to them using a loop in javascript.
After that loop stream just fails and we get stream failed event fired.
It works perfectly fine in a test stream when 2 or 3 users are connected in a room

We are using roomApi for our setup. We found it provides better Api for user detection and stream going live at certain times of the day.
Any idea?
 

Max

Administrator
Staff member
Good day.
We have roughly 700 clients connected to our stream.
Every of 700 clients try to establish WebRTC connection to play VOD from server. Probably server free ports and other resources exhausted in this case. So VOD looks not so suitable for big subscribers amount.
Please try to place advert mp4 file to the separate web host or even to some advertisement CDN, then play it in separate video element as we discussed above:
Code:
stream.muteRemoteAudio();
advertVideo.src="https://host/advert.mp4";
advertVideo.muted = true;
advertVideo.onplay = function() {
    advertVideo.muted = false;
};
advertVideo.onended = function() {
    stream.unmuteRemoteAudio();
};
adVertVideo.play();
 

Azhar Ali

Member
Sorry I should have mentioned. I didnt use the above method you mentioned and tried to play the advert in the same video element.

Code:
var videoObj = document.getElementById(videoObjID);
        videoObj.src = 'https://urlformp3';
        tmpSourceObject = videoObj.srcObject;
        videoObj.srcObject = null;
        videoObj.load();
        videoObj.muted = false;

        videoObj.addEventListener('ended', function () {
            videoObj.muted = true;
            document.getElementById(videoObjID).src = '';
            document.getElementById(videoObjID).srcObject = tmpSourceObject;
            document.getElementById(videoObjID).load();

        })
I don't think the problem was playing the advert, it crashes while sending a message.

This is how from publisher side we press the button to send a message to all clients to play the advert.
Code:
function PlayAdvert() {
            var participants = roomObj.getParticipants();
            for (var i = 0; i < participants.length; i++) {
                participants[i].sendMessage('PlayAdvert');
            }
        }
After that loop, stream just Fails.


Above works perfectly fine if there is a handful of clients connected to room.
 

Max

Administrator
Staff member
Above works perfectly fine if there is a handful of clients connected to room.
The problem is not in client side code. The server resources (for example, media ports) is not enough to simultaneosly establish 700 WebRTC connections to play VOD if another 700 is already installed.
That's why we recommend to move advert file to separate hosting.
 

Azhar Ali

Member
You have misread my message, ignore the fact we are playing adverts.
Sending a message to all 700 clients Fails the streams.

var participants = roomObj.getParticipants();
for (var i = 0; i < participants.length; i++) {
participants.sendMessage('Hello');
}
After that loop stream crashes and had to be restarted.
 

Max

Administrator
Staff member
var participants = roomObj.getParticipants();
for (var i = 0; i < participants.length; i++) {
participants.sendMessage('Hello');
}
After that loop stream crashes and had to be restarted.
Please provide a message received handler code. Do you start advert playback on client side when message is received by client?
Also, please check server log files if clients requested VOD from server. If yes, this explains what's happens as we mentioned above.
If no playStream requests, and clients did not received message, this means RoomApi backend application is overloaded by 700 simultaneous requests. In this case we recommend to refactor the workflow by moving it to server side as follows:
1. Publish main stream to the server, for example "paid_stream"
2. Create a mixer and add main stream to it:
Code:
POST /rest-api/mixer/startup HTTP/1.1
{
    "uri": "mixer://mixer1",
    "localStreamName": "free_stream",
    "hasVideo": false
}
POST /rest-api/mixer/add HTTP/1.1
{
    "uri": "mixer://mixer1",
    "remoteStreamName": "paid_stream"
}
3. Free clients subscribe to free_stream, paid clients sibscribe to paid_stream
4. On "Play advert" click, capture VOD from advert.mp4, add this stream to the mixer and remove main stream from mixer:
Code:
POST /rest-api/vod/startup HTTP/1.1
{
    "uri":"vod://advert.mp4"
    "localStreamName": "advert_stream"
}
POST /rest-api/mixer/add HTTP/1.1
{
    "uri": "mixer://mixer1",
    "remoteStreamName": "advert_stream"
}
POST /rest-api/mixer/remove HTTP/1.1
{
    "uri": "mixer://mixer1",
    "remoteStreamName": "paid_stream"
}
4. Periodically check if VOD stream is playing:
Code:
POST /rest-api/vod/find HTTP/1.1
{
    "localStreamName": "advert_stream"
}
This will return 404 when advert stream is finished
5. Add main stream back to the mixer
Code:
POST /rest-api/mixer/add HTTP/1.1
{
    "uri": "mixer://mixer1",
    "remoteStreamName": "paid_stream"
}
In this case, paid subscribers will hear the main stream, free subscribers will hear advertising over the main stream without any client side action.
 

Azhar Ali

Member
Hi Max,

thanks for the detailed explanation. Only issue I see here is, we need to play the advert based on the user. That was the reason we were doing this client side e.g

Publisher presses the play advert button. A message is sent to all clients to play an advert.
User 1 - PlaysAdvert1.mp3
User 2 PlaysAdvert1.mp3
User 3 PlaysAdvert2.mp3

How do I get the error code on the Failed? stream.status() just says "FAILED"
 

Max

Administrator
Staff member
Publisher presses the play advert button. A message is sent to all clients to play an advert.
User 1 - PlaysAdvert1.mp3
User 2 PlaysAdvert1.mp3
User 3 PlaysAdvert2.mp3
In this case you should:
- get the more powerful server
- expand media ports range, for example
Code:
media_port_from=10001
media_port_to=50000
- (optional) implement your own backend to exclude RoomApi which requires WCS internal backend
How do I get the error code on the Failed? stream.status() just says "FAILED"
Please look at the Stream.getInfo() example:
Code:
if (status == "FAILED") {
        if (stream) {
            if (stream.published()) {
                ...
            } else {
                switch(stream.getInfo()){
                    case STREAM_STATUS_INFO.SESSION_DOES_NOT_EXIST:
                        $("#playInfo").text("Actual session does not exist").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.STOPPED_BY_PUBLISHER_STOP:
                        $("#playInfo").text("Related publisher stopped its stream or lost connection").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.SESSION_NOT_READY:
                        $("#playInfo").text("Session is not initialized or terminated on play ordinary stream").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.RTSP_STREAM_NOT_FOUND:
                        $("#playInfo").text("Rtsp stream not found where agent received '404-Not Found'").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.FAILED_TO_CONNECT_TO_RTSP_STREAM:
                        $("#playInfo").text("Failed to connect to rtsp stream").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.FILE_NOT_FOUND:
                        $("#playInfo").text("File does not exist, check filename").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.FILE_HAS_WRONG_FORMAT:
                        $("#playInfo").text("File has wrong format on play vod, this format is not supported").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.TRANSCODING_REQUIRED_BUT_DISABLED:
                        $("#playInfo").text("Transcoding required, but disabled in settings").attr("class", "text-muted");
                        break;
                    case STREAM_STATUS_INFO.NO_AVAILABLE_TRANSCODERS:
                        $("#playInfo").text("No available transcoders for stream").attr("class", "text-muted");
                        break;
                    default:
                        $("#playInfo").text("Other: "+stream.getInfo()).attr("class", "text-muted");
                        break;
                }
            }
        }
}
 

Azhar Ali

Member
n this case you should:
- get the more powerful server
- expand media ports range, for example
Our server is very powerful and during peak time with over 1K users connected, CPU stays about 3%.

stream.getInfo() returns "undefined"

Please ignore the advert altogether. My question regarding stream crashing is when sendMessage is called for every participant (when connected clients are larger), Failed event gets fired and stream.getInfo() returns "undefined"

The solution I have to play the adverts works nicely with no communication with the flash media server but only sending the message to clients is not scaling when users are high.
 

Max

Administrator
Staff member
but only sending the message to clients is not scaling when users are high.
Seems like the bottleneck is in WCS internal backend which implements RoomApi. Actually, you're creating such a big room for 700 participants, RoomApi is not intended for this.
The only way to solve it is to implement custom backend application using /rest-api/data/send to send a message from server to clients and /OnDataEvant REST hook to send messages from clients to server if needed.
 
Top