Skip to content

Instantly share code, notes, and snippets.

@mekya
Created December 4, 2020 10:27
Show Gist options
  • Save mekya/126cd86bc3c88e20fddcff657141c923 to your computer and use it in GitHub Desktop.
Save mekya/126cd86bc3c88e20fddcff657141c923 to your computer and use it in GitHub Desktop.
This code makes three things. First, capture canvas stream, second mix html5 player's audio and mic audio into a single audio track, lastly publish the stream to the Ant Media Server
<%@ 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://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<style>
video {
width: 100%;
max-width: 640px;
}
/* Space out content a bit */
body {
padding-top: 20px;
padding-bottom: 20px;
}
/* Everything but the jumbotron gets side spacing for mobile first views */
.header, .marketing, .footer {
padding-right: 15px;
padding-left: 15px;
}
/* Custom page header */
.header {
padding-bottom: 20px;
border-bottom: 1px solid #e5e5e5;
}
/* Make the masthead heading the same height as the navigation */
.header h3 {
margin-top: 0;
margin-bottom: 0;
line-height: 40px;
}
/* Custom page footer */
.footer {
padding-top: 19px;
color: #777;
border-top: 1px solid #e5e5e5;
}
/* 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;
border-bottom: 1px solid #e5e5e5;
}
/* 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;
}
/* Space out the masthead */
.header {
margin-bottom: 30px;
}
/* Remove the bottom border on the jumbotron for visual effect */
.jumbotron {
border-bottom: 0;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header clearfix">
<nav>
<ul class="nav navbar-pills pull-right">
<li><a href="http://antmedia.io">Contact</a></li>
</ul>
</nav>
<h3 class="text-muted">WebRTC Publish</h3>
</div>
<div class="jumbotron">
<canvas id="canvas" width="150" height="150"></canvas>
<p>
<video id="localPlayingVideo" autoplay controls playsinline src="test_video_360p.mp4"></video>
</p>
<p>
<input type="text" class="form-control" value="stream1"
id="streamName" placeholder="Type stream name">
</p>
<p>
<button class="btn btn-info" disabled
id="start_publish_button">Start Publishing</button>
<button class="btn btn-info" disabled
id="stop_publish_button">Stop Publishing</button>
<button class="btn btn-info"
id="add_video_sound">Enable Mic and Player Audio</button>
<button class="btn btn-info"
id="remove_video_sound">Enable Mic Only</button>
</p>
<span class="label label-success" id="broadcastingInfo" style="font-size:14px;display:none"
style="display: none">Publishing</span>
</div>
<footer class="footer">
<p><a href="http://antmedia.io">Ant Media Server Enterprise Edition</a></p>
</footer>
</div>
</body>
<script type="module" lang="javascript">
import {WebRTCAdaptor} from "./js/webrtc_adaptor.js"
import {getUrlParameter} from "./js/fetch.stream.js"
var canvas = document.getElementById('canvas');
function draw() {
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);
ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 50, 50);
}
}
//update canvas for every 40ms
setInterval(function() { draw(); }, 40);
//capture stream from canvas
var localStream = canvas.captureStream(25);
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 install_extension_link = document.getElementById("install_chrome_extension_link");
var streamNameBox = document.getElementById("streamName");
var streamId;
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);
}
start_publish_button.addEventListener("click", startPublishing, false);
function stopPublishing() {
webRTCAdaptor.stop(streamId);
}
stop_publish_button.addEventListener("click", stopPublishing, false);
function switchMode(chbx) {
if(camera_checkbox == chbx && camera_checkbox.checked == true){
camera_checkbox.checked = true;
screen_share_checkbox.checked = false;
screen_share_with_camera_checkbox.checked = false;
webRTCAdaptor.switchVideoCapture(streamId);
}
else if(screen_share_checkbox == chbx && screen_share_checkbox.checked == true ){
camera_checkbox.checked = false;
screen_share_checkbox.checked = true;
screen_share_with_camera_checkbox.checked = false;
webRTCAdaptor.switchDesktopCapture(streamId);
}
else if(screen_share_with_camera_checkbox == chbx && screen_share_with_camera_checkbox.checked == true){
camera_checkbox.checked = false;
screen_share_checkbox.checked = false;
screen_share_with_camera_checkbox.checked = true;
webRTCAdaptor.switchDesktopCaptureWithCamera(streamId);
}
else {
chbx.checked = true;
}
}
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;
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") {
startPublishing();
}
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");
}
},
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);
}
});
}
$(function()
{
var id = getUrlParameter("id");
if(typeof id != "undefined") {
$("#streamName").val(id);
}
else {
id = getUrlParameter("name");
if (typeof id != "undefined") {
$("#streamName").val(id);
}
else {
$("#streamName").val("stream1");
}
}
$("#add_video_sound").click(function() {
console.log("add video sound");
navigator.mediaDevices.getUserMedia({video: false, audio:true}).then(function (audioStream)
{
var capturedStream = document.getElementById("localPlayingVideo").captureStream();
//add audio stream to the local stream
var mixedStream = webRTCAdaptor.mixAudioStreams(localStream, audioStream, streamId);
//add capture stream to the local stream
mixedStream = webRTCAdaptor.mixAudioStreams(mixedStream, capturedStream, streamId);
webRTCAdaptor.updateAudioTrack(mixedStream, streamId,null);
});
});
$("#remove_video_sound").click(function()
{
console.log("remove video sound");
navigator.mediaDevices.getUserMedia({video: false, audio:true}).then(function (audioStream)
{
var mixedStream = webRTCAdaptor.mixAudioStreams(localStream, audioStream, streamId);
webRTCAdaptor.updateAudioTrack(mixedStream, streamId,null);
});
});
//get audio with getUserMedia
navigator.mediaDevices.getUserMedia({video: false, audio:true}).then(function (audioStream) {
//add audio track to the localstream which is captured from canvas
localStream.addTrack(audioStream.getAudioTracks()[0]);
initWebRTCAdaptor(localStream);
});
});
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment