3

Am following the blog at https://antmedia.io/how-to-merge-live-stream-and-canvas-in-webrtc-easily/ that explains how to embed a logo in antmedia live stream. However, I couldn't quite figure out to initialise a localStream with javascript SDK as illustrated in the blog. Specifically, where is the implementation of initWebRTCAdaptor():

     //initialize the webRTCAdaptor with the localStream created.

     //initWebRTCAdaptor method is implemented below

     initWebRTCAdaptor(localStream);

A complete working sample would be very helpful.

Selim Emre Toy
  • 995
  • 6
  • 18
davidwaf
  • 301
  • 2
  • 8

2 Answers2

2

It seems that blog post is not fully up to date. Let me share what to do to have this feature.

Just add a localStream parameter to the WebRTCAdaptor constructor. Secondly, use the below code in place of initWebRTCAdaptor

For the full code, please take a look at this gist. https://gist.github.com/mekya/d7d21f78e7ecb2c34d89bd6ec5bf5799

Make sure that you use your own image in image.src.(Use local images)

var canvas = document.getElementById('canvas');
    var vid = document.getElementById('localVideo');
    var image=new Image();

    image.src="images/play.png";

    function draw() {
        if (canvas.getContext) {
            var ctx = canvas.getContext('2d');
            ctx.drawImage(vid, 0, 0, 200, 150);
            ctx.drawImage(image,50, 10, 100, 30);
        }
    }


    setInterval(function() { draw(); }, 50);
    //capture stream from canvas
    var localStream = canvas.captureStream(25);
    navigator.mediaDevices.getUserMedia({video: true, audio:true}).then(function (stream) {
        var video = document.querySelector('video#localVideo');

        video.srcObject = stream;

        video.onloadedmetadata = function(e) {
            video.play();
        };

    //initialize the webRTCAdaptor with the localStream created.

    //initWebRTCAdaptor method is implemented below

    localStream.addTrack(stream.getAudioTracks()[0]);

    initWebRTCAdaptor(false, autoRepublishEnabled);
  });
faraway
  • 1,491
  • 10
  • 22
2

Here is the full code of the sample described in the blog post:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/webrtc_adaptor.js"></script>

<style>
video {
    width: 100%;
    max-width: 640px;
}

/* Everything but the jumbotron gets side spacing for mobile first views */
.header, .marketing, .footer {
    padding: 15px;
}

/* Custom page header */
.header {
    padding-bottom: 20px;
}

/* Customize container */
@media ( min-width : 768px) {
    .container {
        max-width: 730px;
    }
}

.container-narrow>hr {
    margin: 30px 0;
}

/* Main marketing message and sign up button */
.jumbotron {
    text-align: center;
}

/* Responsive: Portrait tablets and up */
@media screen and (min-width: 768px) {
    /* Remove the padding we set earlier */
    .header, .marketing, .footer {
        padding-right: 0;
        padding-left: 0;
    }
}
.options {
        display:none;
    }
</style>
</head>
<body>
    <div class="container">
        <div class="header clearfix">
            <div class="row">
                <h3 class="col text-muted">WebRTC Publish</h3>
                <nav class="col align-self-center">
                    <ul class="nav float-right">
                        <li><a href="http://antmedia.io">Contact</a></li>
                    </ul>
                </nav>
            </div>
        </div>

        <div class="jumbotron">
            <div class="col-sm-12 form-group">
                <canvas id="canvas" width="200" height="150"></canvas>
                <p>
                <video id="localVideo"  autoplay muted controls playsinline></video>
                </p>
            </div>
            <div class="form-group col-sm-12 text-left">
                <input type="text" class="form-control" value="stream1"
                        id="streamName" name="streamIdTextBox" placeholder="Type stream name">
            </div>
            <div class="col-sm-12 text-right">
                <button type="button" class="btn btn-outline-primary btn-sm" onclick="toggleOptions()">Options</button>
            </div>
            <div class="form-group col-sm-12 text-left options">
                <legend class="col-form-label">Video Source</legend>
                <div class="form-check form-check-inline">  

                    <input class="form-check-input" checked="true" name="videoSource" onchange="switchMode(event.target)" type="radio" value="" 
                        id="camera_checkbox">
                    <label class="form-check-label" for="camera_checkbox" style="font-weight:normal">
                            Camera
                    </label>    
                </div>
                <div class="form-check form-check-inline">  
                    <input class="form-check-input" disabled name="videoSource" onchange="switchMode(event.target)" type="radio" value="" 
                    id="screen_share_checkbox">
                    <label class="form-check-label" for="screen_share_checkbox" style="font-weight:normal">
                            Screen
                    </label>
                </div>
                <div class="form-check form-check-inline">
                    <input class="form-check-input" disabled name="videoSource" onchange="switchMode(event.target)" type="radio" value="" 
                        id="screen_share_with_camera_checkbox">
                    <label class="form-check-label" for="screen_share_with_camera_checkbox" style="font-weight:normal">
                            Screen with Camera
                    </label>
                        <a id="browser_screen_share_doesnt_support" href="https://caniuse.com/#search=getDisplayMedia">Your browser doesn't support screen share. You can see supported browsers in this link </a>
                </div>
            </div>  
            <div class="form-group col-sm-12 text-left options">
                <label>Data Channel Messages</label>
                <textarea class="form-control" id="dataMessagesTextarea" style="font-size:10px" rows="8"></textarea>
                <div class="form-row">
                    <div class="form-group col-sm-10">
                        <input type="text" class="form-control" id="dataTextbox" placeholder="Write your message to send players">
                    </div>
                    <div class="form-group col-sm-2">
                    <button type="button" class="btn btn-outline-primary btn-block" onclick="sendData()">Send</button>
                    </div>
                </div>
            </div>
                    

                <div class="form-group">    
                    <button onclick="startPublishing()" class="btn btn-primary" disabled
                    id="start_publish_button">Start Publishing</button>
                    <button onclick="stopPublishing()" class="btn btn-primary" disabled
                    id="stop_publish_button">Stop Publishing</button>
                </div>          

                <span class="badge badge-success" id="broadcastingInfo" style="font-size:14px;display:none"
                            style="display: none">Publishing</span>

        </div>
        <footer class="footer text-center">
            <p><a href="http://antmedia.io">Ant Media Server</a></p>
        </footer>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"  crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"  crossorigin="anonymous"></script> 
</body>
<script>
    var canvas = document.getElementById('canvas');
    var vid = document.getElementById('localVideo');
    var image=new Image();
    image.src="antmedia.png";
     function draw() {
      if (canvas.getContext) {
        var ctx = canvas.getContext('2d');
        ctx.drawImage(vid, 0, 0, 200, 150);
        ctx.drawImage(image,50, 10, 100, 30)

      }
    }

    //update canvas for every 40ms
    setInterval(function() { draw(); }, 25);
 
    //capture stream from canvas
    var localStream = canvas.captureStream(25);

    //get audio with getUserMedia
    navigator.mediaDevices.getUserMedia({video: true, audio:true}).then(function (stream) {
        var video = document.querySelector('video#localVideo');
         video.srcObject = stream;
         video.onloadedmetadata = function(e) {
           video.play();
         };

        //initialize the webRTCAdaptor with the localStream created.
        //initWebRTCAdaptor method is implemented below
        initWebRTCAdaptor(localStream);
    });
     
    var token = "<%= request.getParameter("token") %>";

    var camera_checkbox = document.getElementById("camera_checkbox");
    var screen_share_checkbox = document.getElementById("screen_share_checkbox");
    var screen_share_with_camera_checkbox = document.getElementById("screen_share_with_camera_checkbox");

    var start_publish_button = document.getElementById("start_publish_button");
    var stop_publish_button = document.getElementById("stop_publish_button");
    
    var streamNameBox = document.getElementById("streamName");
    
    /**
     * If publishing stops for any reason, it tries to republish again.
     */
    var autoRepublishEnabled = true;
    /**
     * Timer job that checks the WebRTC connection 
     */
    var autoRepublishIntervalJob = null;
    
    var streamId;
    
    function getUrlParameter(sParam) {
        var sPageURL = decodeURIComponent(window.location.search.substring(1)),
            sURLVariables = sPageURL.split('&'),
            sParameterName,
            i;

        for (i = 0; i < sURLVariables.length; i++) {
            sParameterName = sURLVariables[i].split('=');

            if (sParameterName[0] === sParam) {
                return sParameterName[1] === undefined ? true : sParameterName[1];
            }
        }
    };
    
    var name = getUrlParameter("name");
    if(name !== "undefined")
    {
        streamNameBox.value = name;
    }
    
    // It should be true
    var rtmpForward = getUrlParameter("rtmpForward");

    function startPublishing() {
        streamId = streamNameBox.value;
        webRTCAdaptor.publish(streamId, token);
    }

    function stopPublishing() {
        if (autoRepublishIntervalJob != null) {
            clearInterval(autoRepublishIntervalJob);
            autoRepublishIntervalJob = null;
        }
        webRTCAdaptor.stop(streamId);
    }
    
    function switchMode(chbx) {

        if(camera_checkbox == chbx){
            webRTCAdaptor.switchVideoCapture(streamId);
        }
        else if(screen_share_checkbox == chbx) {
            webRTCAdaptor.switchDesktopCapture(streamId);
        }
        else if(screen_share_with_camera_checkbox == chbx){
            webRTCAdaptor.switchDesktopCaptureWithCamera(streamId);
        }
        else {
            chbx.checked = true;
        }
    }
    function toggleOptions() {
        $(".options").toggle();
    }
    
    function sendData() {
        try {
            var iceState = webRTCAdaptor.iceConnectionState(streamId);
            if (iceState != null && iceState != "failed" && iceState != "disconnected") {
            
                webRTCAdaptor.sendData($("#streamName").val(), $("#dataTextbox").val());
                $("#dataMessagesTextarea").append("Sent: " + $("#dataTextbox").val() + "\r\n");
                $("#dataTextbox").val("");
            }
            else {
                alert("WebRTC publishing is not active. Please click Start Publishing first")
            }
        }
        catch (exception) {
            console.error(exception);
            alert("Message cannot be sent. Make sure you've enabled data channel on server web panel");
        }
    }
      
    
    function checkAndRepublishIfRequired() {
        var iceState = webRTCAdaptor.signallingState(streamId);
        if (iceState == null || iceState == "failed" || iceState == "disconnected"){
            console.log("Publish has stopped and will try to re-publish");
            webRTCAdaptor.stop(streamId);
            webRTCAdaptor.closePeerConnection(streamId);
            webRTCAdaptor.closeWebSocket();
            initWebRTCAdaptor(true, autoRepublishEnabled);
        }
        
    }

    function startAnimation() {

        $("#broadcastingInfo").fadeIn(800, function () {
          $("#broadcastingInfo").fadeOut(800, function () {
                var state = webRTCAdaptor.signallingState(streamId);
            if (state != null && state != "closed") {
                var iceState = webRTCAdaptor.iceConnectionState(streamId);
                if (iceState != null && iceState != "failed" && iceState != "disconnected") {
                    startAnimation();
                }
            }
          });
        });
      }

    var pc_config = null;

    var sdpConstraints = {
        OfferToReceiveAudio : false,
        OfferToReceiveVideo : false
    };
    
    var mediaConstraints = {
        video : true,
        audio : true
    };

    var appName = location.pathname.substring(0, location.pathname.lastIndexOf("/")+1);
    var path =  location.hostname + ":" + location.port + appName + "websocket?rtmpForward=" + rtmpForward;
    var websocketURL =  "ws://" + path;
    
    if (location.protocol.startsWith("https")) {
        websocketURL = "wss://" + path;
    }

    var webRTCAdaptor = null;
    
    function initWebRTCAdaptor(stream) 
    {
        webRTCAdaptor = new WebRTCAdaptor({
                websocket_url : websocketURL,
                mediaConstraints : mediaConstraints,
                peerconnection_config : pc_config,
                sdp_constraints : sdpConstraints,
                localVideoId : "localVideo",
                localStream: stream,
                debug:true,
                callback : function(info, obj) {
                    if (info == "initialized") {
                        console.log("initialized");
                        start_publish_button.disabled = false;
                        stop_publish_button.disabled = true;
                        
                    } else if (info == "publish_started") {
                        //stream is being published
                        console.log("publish started");
                        start_publish_button.disabled = true;
                        stop_publish_button.disabled = false;
                        startAnimation();
                    } else if (info == "publish_finished") {
                        //stream is being finished
                        console.log("publish finished");
                        start_publish_button.disabled = false;
                        stop_publish_button.disabled = true;
                    }
                    else if (info == "browser_screen_share_supported") {
                        camera_checkbox.disabled = false;
                        screen_share_checkbox.disabled = false;
                        screen_share_with_camera_checkbox.disabled = false;
                        console.log("browser screen share supported");
                        browser_screen_share_doesnt_support.style.display = "none";
                    }
                    else if (info == "screen_share_stopped") {
                        camera_checkbox.checked = true;
                        screen_share_checkbox.checked = false;
                        screen_share_with_camera_checkbox.checked = false;
                        console.log("screen share stopped");
                    }
                    else if (info == "closed") {
                        //console.log("Connection closed");
                        if (typeof obj != "undefined") {
                            console.log("Connecton closed: " + JSON.stringify(obj));
                        }
                    }
                    else if (info == "pong") {
                        //ping/pong message are sent to and received from server to make the connection alive all the time
                        //It's especially useful when load balancer or firewalls close the websocket connection due to inactivity
                    }
                    else if (info == "refreshConnection") {
                        checkAndRepublishIfRequired();
                    }
                    else if (info == "ice_connection_state_changed") {
                        console.log("iceConnectionState Changed: ",JSON.stringify(obj));
                    }
                    else if (info == "updated_stats") {
                        //obj is the PeerStats which has fields
                         //averageOutgoingBitrate - kbits/sec
                        //currentOutgoingBitrate - kbits/sec
                        console.log("Average outgoing bitrate " + obj.averageOutgoingBitrate + " kbits/sec"
                                + " Current outgoing bitrate: " + obj.currentOutgoingBitrate + " kbits/sec");
    
                    }
                    else if (info == "data_received") {
                        console.log("Data received: " + obj.event.data + " type: " + obj.event.type + " for stream: " + obj.streamId);
                        $("#dataMessagesTextarea").append("Received: " + obj.event.data + "\r\n");
                    }
                    else {
                        console.log( info + " notification received");
                    }
                },
                callbackError : function(error, message) {
                    //some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
    
                    console.log("error callback: " +  JSON.stringify(error));
                    var errorMessage = JSON.stringify(error);
                    if (typeof message != "undefined") {
                        errorMessage = message;
                    }
                    var errorMessage = JSON.stringify(error);
                    if (error.indexOf("NotFoundError") != -1) {
                        errorMessage = "Camera or Mic are not found or not allowed in your device";
                    }
                    else if (error.indexOf("NotReadableError") != -1 || error.indexOf("TrackStartError") != -1) {
                        errorMessage = "Camera or Mic is being used by some other process that does not let read the devices";
                    }
                    else if(error.indexOf("OverconstrainedError") != -1 || error.indexOf("ConstraintNotSatisfiedError") != -1) {
                        errorMessage = "There is no device found that fits your video and audio constraints. You may change video and audio constraints"
                    }
                    else if (error.indexOf("NotAllowedError") != -1 || error.indexOf("PermissionDeniedError") != -1) {
                        errorMessage = "You are not allowed to access camera and mic.";
                    }
                    else if (error.indexOf("TypeError") != -1) {
                        errorMessage = "Video/Audio is required";
                    }
                    else if (error.indexOf("ScreenSharePermissionDenied") != -1) {
                        errorMessage = "You are not allowed to access screen share";
                        camera_checkbox.checked = true;
                        screen_share_checkbox.checked = false;
                        screen_share_with_camera_checkbox.checked = false;
                    }
                    else if (error.indexOf("WebSocketNotConnected") != -1) {
                        errorMessage = "WebSocket Connection is disconnected.";
                    }
                    alert(errorMessage);
                }
            });
    }
</script>
</html>
Murat
  • 2,891
  • 32
  • 47
  • Nice! I borrowed from this example, But when I do this it's still using my webcam instead of the canvas.. I can't see where I can change that – Tim Davis Sep 09 '20 at 20:32