Quickstart: JavaScript
Streaming video with the LiveSwitch Web SDK is quick to implement and easy to manage. The easiest way to learn how to use the SDK is to follow these quickstart instructions.
Check Out Our Latest Vue Example App
Here is an example app that you can use as a starting point for your project!
A running version of the example can be found here and is setup to work on desktop and mobile.
You can find the supporting code in this GitHub repository.
Add an Application
Before you can work on your code, you must add an application in the LiveSwitch Console.
- Install LiveSwitch Server.
- Log in to your LiveSwitch Console at
https://{your-gateway-domain}:{port}/admin
. - Click Applications > New.
- Enter a name for your application.
- Click the application you just created to open its settings page. You can find your Application ID, Gateway URL, and Shared Secret on the settings page. This information is needed for generating your authorization token.
Try Out a Hello World Example on CodePen
To run the CodePen example, you need to add your Application ID and Shared Secret in the JavaScript code. You can enter them in the text boxes below and click Submit. This will populate your Application ID and Shared Secret to all the CodePen examples on this page, so that you don't have to enter them each time when you run a pen.
This Hello World example demonstrates the basics for streaming media using the LiveSwitch Web SDK.
Create HTML With Embedded JavaScript
If you're interested in the CodePen example above and want to get hands-on experience with using the LiveSwitch Web SDK, you can follow the instructions below to create a web page to stream video on it.
First, create a project and install the LiveSwitch Web SDK. Then, create a JavaScript file and an HTML file to stream video on.
In a location where you want to store your project files, create a directory named my-app.
Navigate to your project directory, initialize a project, and install the LiveSwitch Web SDK.
cd my-app npm init -y npm install fm.liveswitch
In your project directory, create a file named
MediaStreamingLogic.js
.Add the following content and replace with your own Application ID and Shared Secret. If you use your own LiveSwitch Server, replace the Gateway URL. This information is used for generating an authorization token.
var HelloWorld; (function (HelloWorld) { var MediaStreamingLogic = /** @class */ (function () { function MediaStreamingLogic() { //Replace the following with the values from the LiveSwitch Console this.applicationId = 'replace-with-your-app-id'; this.sharedSecret = 'replace-with-your-shared-secret'; this.channelId = 'hello-world-channel'; this.gatewayUrl = 'https://cloud.liveswitch.io/'; this.reRegisterBackoff = 200; this.maxRegisterBackoff = 60000; this.unregistering = false; this.layoutManager = new fm.liveswitch.DomLayoutManager(document.getElementById("my-container")); this.downstreamConnections = {}; // Create a new local media for screen capturing. this.localScreenMedia = new fm.liveswitch.LocalMedia(false, true, true); // Log to console. fm.liveswitch.Log.registerProvider(new fm.liveswitch.ConsoleLogProvider(fm.liveswitch.LogLevel.Debug)); } MediaStreamingLogic.getInstance = function () { if (MediaStreamingLogic.instance == null) { MediaStreamingLogic.instance = new MediaStreamingLogic(); } return MediaStreamingLogic.instance; }; MediaStreamingLogic.prototype.checkAppCredentialsSet = function () { if ( app.applicationId == "replace-with-your-app-id" || app.applicationId == undefined ) { alert( `ERROR: Your applicationId is not set. Find your Application ID by selecting your chosen Application at your LiveSwitch Admin Console and add it to config.js.` ); } if ( app.sharedSecret == "replace-with-your-shared-secret" || app.sharedSecret == undefined ) { alert( `ERROR: Your sharedSecret is not set. Find your Shared Secret by selecting your chosen Application at your LiveSwitch Admin Console and add it to config.js.` ); } }; MediaStreamingLogic.prototype.joinAsync = function () { const promise = new fm.liveswitch.Promise(); // Create a client. this.client = new fm.liveswitch.Client(this.gatewayUrl, this.applicationId); // Generate a token (do this on the server to avoid exposing your shared secret). const token = fm.liveswitch.Token.generateClientRegisterToken( this.applicationId, this.client.getUserId(), this.client.getDeviceId(), this.client.getId(), null, [new fm.liveswitch.ChannelClaim(this.channelId)], this.sharedSecret ); // Allow re-register. this.unregistering = false; this.client.addOnStateChange(() => { // Write registration state to log. fm.liveswitch.Log.debug( `Client is ${new fm.liveswitch.ClientStateWrapper( this.client.getState() )}.` ); if ( this.client.getState() === fm.liveswitch.ClientState.Unregistered && !this.unregistering ) { fm.liveswitch.Log.debug( `Registering with backoff = ${this.reRegisterBackoff}.` ); this.displayMessage( "Client unregistered unexpectedly, trying to re-register." ); // Re-register after a backoff. setTimeout(() => { // Incrementally increase register backoff to prevent runaway process. if (this.reRegisterBackoff <= this.maxRegisterBackoff) { this.reRegisterBackoff += this.reRegisterBackoff; } // Register client with token. this.client .register(token) .then((channels) => { // Reset re-register backoff after successful registration. this.reRegisterBackoff = 200; this.onClientRegistered(channels); promise.resolve(null); }) .fail((ex) => { fm.liveswitch.Log.error("Failed to register with Gateway."); promise.reject(ex); }); }, this.reRegisterBackoff); } }); // Register client with token. this.client .register(token) .then((channels) => { this.onClientRegistered(channels); promise.resolve(null); }) .fail((ex) => { fm.liveswitch.Log.error("Failed to register with Gateway."); promise.reject(ex); }); return promise; }; MediaStreamingLogic.prototype.leaveAsync = function () { // Disable re-register. this.unregistering = true; return this.client .unregister() .then(() => this.displayMessage( `Client ${this.client.getId()} has successfully unregistered from channel ${this.channel.getId()}.` ) ) .fail(() => fm.liveswitch.Log.error("Unregistration failed.")); }; MediaStreamingLogic.prototype.onClientRegistered = function (channels) { // Store our channel reference. this.channel = channels[0]; this.displayMessage( `Client ${this.client.getId()} has successfully connected to channel ${this.channel.getId()}, Hello World!` ); // Open a new SFU upstream connection. this.upstreamConnection = this.openSfuUpstreamConnection(this.localMedia); // Open a new SFU downstream connection when a remote upstream connection is opened. this.channel.addOnRemoteUpstreamConnectionOpen((remoteConnectionInfo) => this.openSfuDownstreamConnection(remoteConnectionInfo) ); }; MediaStreamingLogic.localMedia = undefined; MediaStreamingLogic.prototype.layoutManager = new fm.liveswitch.DomLayoutManager( document.getElementById("my-container") ); MediaStreamingLogic.prototype.startLocalMedia = function () { const promise = new fm.liveswitch.Promise(); if (this.localMedia == null) { // Create local media with audio and video enabled. const audioEnabled = true; const videoEnabled = true; this.localMedia = new fm.liveswitch.LocalMedia( audioEnabled, videoEnabled ); // Set local media in the layout. this.layoutManager.setLocalMedia(this.localMedia); } // Start capturing local media. this.localMedia .start() .then(() => { fm.liveswitch.Log.debug("Media capture started."); promise.resolve(null); }) .fail((ex) => { fm.liveswitch.Log.error(ex.message); promise.reject(ex); }); return promise; }; MediaStreamingLogic.prototype.stopLocalMedia = function () { const promise = new fm.liveswitch.Promise(); // Stop capturing local media. this.localMedia .stop() .then(() => { fm.liveswitch.Log.debug("Media capture stopped."); promise.resolve(null); }) .fail((ex) => { fm.liveswitch.Log.error(ex.message); promise.reject(ex); }); return promise; }; MediaStreamingLogic.upstreamConnection = undefined; MediaStreamingLogic.prototype.openSfuUpstreamConnection = function (localMedia) { // Create audio and video streams from local media. const audioStream = new fm.liveswitch.AudioStream(localMedia); const videoStream = new fm.liveswitch.VideoStream(localMedia); // Create a SFU upstream connection with local audio and video. const connection = this.channel.createSfuUpstreamConnection( audioStream, videoStream ); connection.addOnStateChange((conn) => { fm.liveswitch.Log.debug( `Upstream connection is ${new fm.liveswitch.ConnectionStateWrapper( conn.getState() ).toString()}.` ); }); connection.open(); return connection; }; MediaStreamingLogic.prototype.openSfuDownstreamConnection = function (remoteConnectionInfo) { // Create remote media. const remoteMedia = new fm.liveswitch.RemoteMedia(); const audioStream = new fm.liveswitch.AudioStream(remoteMedia); const videoStream = new fm.liveswitch.VideoStream(remoteMedia); // Add remote media to the layout. this.layoutManager.addRemoteMedia(remoteMedia); // Create a SFU downstream connection with remote audio and video. const connection = this.channel.createSfuDownstreamConnection( remoteConnectionInfo, audioStream, videoStream ); // Store the downstream connection. this.downstreamConnections[connection.getId()] = connection; connection.addOnStateChange((conn) => { fm.liveswitch.Log.debug( `Downstream connection is ${new fm.liveswitch.ConnectionStateWrapper( conn.getState() ).toString()}.` ); // Remove the remote media from the layout and destroy it if the remote is closed. if (conn.getRemoteClosed()) { delete this.downstreamConnections[connection.getId()]; this.layoutManager.removeRemoteMedia(remoteMedia); remoteMedia.destroy(); } }); connection.open(); return connection; }; MediaStreamingLogic.downstreamConnections = { }; MediaStreamingLogic.prototype.displayMessage = function (msg) { const textContainer = document.getElementById("text-container"); textContainer.appendChild(document.createTextNode(msg)); textContainer.appendChild(document.createElement("br")); }; return MediaStreamingLogic; }()); HelloWorld.MediaStreamingLogic = MediaStreamingLogic; })(HelloWorld || (HelloWorld = {}));
In your project directory, create a directory named
html
.In the
html
directory, create an HTML file namedhello-world.html
. In this file, import the LiveSwitch Web SDK to the project and create logic to stream video. For information about how to generate an authorization token, register a client to a channel and stream media, see Media Streaming Basics. The completed file should look like the following:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello World - JavaScript</title> </head> <body> <div style="width: 1210px"> <div style="float: left;"> <div id="my-container" style="width:800px; height:600px;"></div> <div style="float: left; margin-top: 5px;"> <div style="margin-left: 0"> <br /> </div> <br /> </div> </div> <div style="margin-left: 810px;"> <div id="text-container" style="box-sizing: border-box; width: 400px; height: 600px; border: 1px solid black; padding: 5px;"></div> </div> </div> <script src='./../node_modules/fm.liveswitch/fm.liveswitch.js'></script> <script type="text/javascript" src='../MediaStreamingLogic.js'></script> <script> var app = HelloWorld.MediaStreamingLogic.getInstance(); window.onload = () => { app.checkAppCredentialsSet(); app.startLocalMedia().then(() => { // Create and register the client. app.joinAsync(); }); }; </script> </body> </html>
Install a light-weight HTTP server:
npm install -g http-server
Start the server:
http-server
Open a browser and navigate to this URL: http://127.0.0.1:8080/html/hello-world.html
In your browser, you should see video streaming on the web page. You can open the page in multiple browser windows or tabs to emulate a video conference with multiple participants.
Media Streaming Basics
Now, let's go over the important steps you need for streaming media.
Step 1: Register clients to a channel with a token
In a video conference, you need some way for the participants to communicate with each other. In LiveSwitch, the Gateway fulfills this role. The Gateway connects everyone who wants to participate in a media session. It controls which individuals can communicate with each other and restricts unauthorized access.
A channel is a unique identifier that describes an audio or video conference. Generally, for each video conference that the participants join in, you will want to have a unique channel associated with it.
Before you can start a media session, you must create an authorization token and register the client to a channel with the token.
Step 2: Handle local and remote video and audio
To stream media in a video conference, you need to produce audio and video. Local media is the audio and video produced and sent by the current user, while remote media is the audio and video sent by other participants.
LiveSwitch provides local and remote media implementations that you can use directly with all the browsers. To stream media, you need to create a type of connection, such as an SFU connection, to upload and download the local and remote media, as well as create logic to start and stop the local media.
More Examples
With the LiveSwitch Web SDK, you can do so much more. Here are some examples that demonstrate how to reregister and unregister, share screen, mute local and remote media, send and receive text messages and files, change video and audio input and output devices, and broadcast.
Reregister and Unregister
This example shows how to reregister clients who disconnected and clean up resources by unregistering clients who left a channel. For more information, refer to the TypeScript tutorial on how to reregister and unregister.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Re-register and Unregister</title>
</head>
<body>
<div>
<div style="float: left;">
<div style="float: left; margin-top: 5px;">
<div style="margin-left: 0">
<button type="button" id="join-btn" style="margin: 2px;">Join</button>
<button type="button" id="leave-btn" style="margin: 2px; display: none;">Leave</button>
<br />
</div>
<br />
<div id="my-container" style="width:800px; height:600px;"></div>
</div>
</div>
<div style="margin-left: 810px;">
<div id="text-container"
style="box-sizing: border-box; width: 400px; height: 600px; border: 1px solid black; padding: 5px;"></div>
</div>
</div>
<script src='./../node_modules/fm.liveswitch/fm.liveswitch.js'></script>
<script type="text/javascript" src='../MediaStreamingLogic.js'></script>
<script>
var app = HelloWorld.MediaStreamingLogic.getInstance();
const joinBtn = document.getElementById("join-btn");
const leaveBtn = document.getElementById("leave-btn");
joinBtn.onclick = () => {
app.checkAppCredentialsSet();
app.startLocalMedia().then(() => {
// Create and register the client.
app.joinAsync().then(() => {
joinBtn.style.display = "none";
leaveBtn.style.display = "inline-block";
});
});
};
leaveBtn.onclick = () => {
app.stopLocalMedia().then(() => {
app.leaveAsync().then(() => {
joinBtn.style.display = "inline-block";
leaveBtn.style.display = "none";
});
});
};
</script>
</body>
</html>
Mute Media
This example demonstrates how to mute local media so that other participants can't see or hear you. It also demonstrates how to mute remote media so that you can't see or hear other participants. For more information, refer to the TypeScript tutorial on how to mute media.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mute Video and Audio</title>
</head>
<body>
<div>
<div style="float: left;">
<div style="float: left; margin-top: 5px; margin-bottom: 20px">
<div style="margin-left: 0">
<button type="button" id="join-btn" style="margin: 2px;">Join</button>
<button type="button" id="leave-btn" style="margin: 2px; display: none;">Leave</button>
</div>
<br />
<button type="button" id="mute-audio-btn" style="margin: 2px; margin-top: 10px;">Mute Audio</button>
<button type="button" id="disable-remote-audio-btn" style="margin: 2px;">Disable Remote Audio</button>
<br />
<button type="button" id="mute-video-btn" style="margin: 2px;">Mute Video</button>
<button type="button" id="disable-remote-video-btn" style="margin: 2px;">Disable Remote Video</button>
<div id="my-container" style="width:800px; height:600px;"></div>
</div>
</div>
<div style="margin-left: 810px;">
<div id="text-container"
style="box-sizing: border-box; width: 400px; height: 600px; border: 1px solid black; padding: 5px;"></div>
</div>
</div>
<script src='./../node_modules/fm.liveswitch/fm.liveswitch.js'></script>
<script type="text/javascript" src='../MediaStreamingLogic.js'></script>
<script>
var app = HelloWorld.MediaStreamingLogic.getInstance();
app.toggleMuteLocalAudio = function () {
// Retrieve and update the config of the upstream connection.
const config = this.upstreamConnection.getConfig();
config.setLocalAudioMuted(!config.getLocalAudioMuted());
return this.upstreamConnection.update(config);
};
app.toggleMuteLocalVideo = function () {
// Retrieve and update the config of the upstream connection.
const config = this.upstreamConnection.getConfig();
config.setLocalVideoMuted(!config.getLocalVideoMuted());
return this.upstreamConnection.update(config);
};
app.toggleDisableRemoteAudio = function () {
// Retrieve and update the config of each of the downstream connections.
for (const id in this.downstreamConnections) {
const connection = this.downstreamConnections[id];
const config = connection.getConfig();
config.setRemoteAudioDisabled(!config.getRemoteAudioDisabled());
connection.update(config);
}
};
app.toggleDisableRemoteVideo = function () {
// Retrieve and update the config of each of the downstream connections.
for (const id in this.downstreamConnections) {
const connection = this.downstreamConnections[id];
const config = connection.getConfig();
config.setRemoteVideoDisabled(!config.getRemoteVideoDisabled());
connection.update(config);
}
};
const joinBtn = document.getElementById("join-btn");
const leaveBtn = document.getElementById("leave-btn");
joinBtn.onclick = () => {
app.checkAppCredentialsSet();
app.startLocalMedia().then(() => {
// Create and register the client.
app.joinAsync().then(() => {
joinBtn.style.display = "none";
leaveBtn.style.display = "inline-block";
});
});
};
leaveBtn.onclick = () => {
app.stopLocalMedia().then(() => {
app.leaveAsync().then(() => {
joinBtn.style.display = "inline-block";
leaveBtn.style.display = "none";
});
});
};
const muteAudioBtn = document.getElementById("mute-audio-btn");
const muteVideoBtn = document.getElementById("mute-video-btn");
muteAudioBtn.onclick = () => {
app.toggleMuteLocalAudio().then(() => {
muteAudioBtn.innerText = app.localMedia.getAudioMuted() ?
"Unmute Audio" :
"Mute Audio";
});
};
muteVideoBtn.onclick = () => {
app.toggleMuteLocalVideo().then(() => {
muteVideoBtn.innerText = app.localMedia.getVideoMuted() ?
"Unmute Video" :
"Mute Video";
});
};
const disableRemoteAudioBtn = document.getElementById(
"disable-remote-audio-btn"
);
const disableRemoteVideoBtn = document.getElementById(
"disable-remote-video-btn"
);
disableRemoteAudioBtn.onclick = () => {
app.toggleDisableRemoteAudio();
disableRemoteAudioBtn.innerText =
disableRemoteAudioBtn.innerText.indexOf("Disable") !== -1 ?
"Enable Remote Audio" :
"Disable Remote Audio";
};
disableRemoteVideoBtn.onclick = () => {
app.toggleDisableRemoteVideo();
disableRemoteVideoBtn.innerText =
disableRemoteVideoBtn.innerText.indexOf("Disable") !== -1 ?
"Enable Remote Video" :
"Disable Remote Video";
};
</script>
</body>
</html>
Share Screens
This example demonstrates how to share screens at a conference. To share a user's screen, create an additional "screen share" local media object to capture the local user's screen. Then, create an additional upstream connection to upload the "screen share" local media. Other participants get the "screen share" downstream connection the same way as the other media downstream. For more information, refer to the TypeScript tutorial on how to share screens.
Change Media Devices
This example demonstrates how to implement the ability for your users to change their media devices in a video conference. For example, you can have users switch to a different camera or microphone. For more information, refer to the Change Media Devices topic.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Change Devices</title>
</head>
<body>
<div>
<div style="float: left;">
<div style="float: left; margin-top: 5px;">
<div style="margin-left: 0">
<button type="button" id="join-btn" style="margin: 2px;">
Join
</button>
<button type="button" id="leave-btn" style="margin: 2px; display: none;">
Leave
</button>
<br />
</div>
<br />
<label for="audioInputs">Audio Inputs:</label>
<select id="audioInputs" style="margin: 2px;"> </select>
<br />
<label for="videoInputs">Video Inputs:</label>
<select id="videoInputs" style="margin: 2px;"> </select>
<br />
<label for="audioOutputs">Audio Outputs:</label>
<select id="audioOutputs" style="margin: 2px;"> </select>
<br />
<div id="my-container" style="width:800px; height:600px;"></div>
</div>
</div>
<div style="margin-left: 810px;">
<div id="text-container"
style="box-sizing: border-box; width: 400px; height: 600px; border: 1px solid black; padding: 5px;"></div>
<div style="margin-top: 5px; width: 100%;"></div>
</div>
</div>
<script src="./../node_modules/fm.liveswitch/fm.liveswitch.js"></script>
<script type="text/javascript" src='../MediaStreamingLogic.js'></script>
<script>
var app = HelloWorld.MediaStreamingLogic.getInstance();
// Switch audio input device.
app.getAudioInputs = function () {
return this.localMedia.getAudioSourceInputs();
};
app.setAudioInput = function (input) {
this.localMedia.changeAudioSourceInput(input);
};
// Get a list of video input devices.
app.getVideoInputs = function () {
return this.localMedia.getVideoSourceInputs();
};
// Switch video input device.
app.setVideoInput = function (input) {
this.localMedia.changeVideoSourceInput(input);
};
// Switch audio output device.
app.getAudioOutputs = function () {
const remoteMedia = new fm.liveswitch.RemoteMedia(true, true);
return remoteMedia.getAudioSinkOutputs();
}
app.setAudioOutput = function (output) {
// Set the audio output device for each downstream connection.
for (const connectionID in this.downstreamConnections) {
const connection = this.downstreamConnections[connectionID];
const remoteMedia = connection.getAudioStream().getRemoteMedia();
remoteMedia.changeAudioSinkOutput(output);
}
}
const joinBtn = document.getElementById('join-btn');
const leaveBtn = document.getElementById('leave-btn');
joinBtn.onclick = () => {
app.checkAppCredentialsSet();
app.startLocalMedia().then(() => {
loadInputs();
// Create and register the client.
app.joinAsync().then(() => {
joinBtn.style.display = "none";
leaveBtn.style.display = "inline-block";
});
});
}
leaveBtn.onclick = () => {
app.stopLocalMedia().then(() => {
app.leaveAsync().then(() => {
clearInputs();
joinBtn.style.display = "inline-block";
leaveBtn.style.display = "none";
});
});
}
function loadInputs() {
app.getAudioInputs().then(audioInputs => {
const selectBox = document.getElementById('audioInputs')
for (const input of audioInputs) {
const option = document.createElement('option');
option.text = input.getName();
selectBox.add(option);
};
selectBox.onchange = () =>
app.setAudioInput(audioInputs[selectBox.selectedIndex]);
});
app.getVideoInputs().then(videoInputs => {
const selectBox = document.getElementById('videoInputs');
for (const input of videoInputs) {
const option = document.createElement('option');
option.text = input.getName();
selectBox.add(option);
}
selectBox.onchange = () =>
app.setVideoInput(videoInputs[selectBox.selectedIndex]);
})
app.getAudioOutputs().then(audioOutputs => {
const selectBox = document.getElementById('audioOutputs');
for (const output of audioOutputs) {
const option = document.createElement('option');
option.text = output.getName();
selectBox.add(option);
}
selectBox.onchange = () =>
app.setAudioOutput(audioOutputs[selectBox.selectedIndex]);
})
// </Change Devices>
}
function clearInputs() {
// Remove the lists of available devices.
removeOptions(document.getElementById('audioInputs'))
removeOptions(document.getElementById('videoInputs'))
removeOptions(document.getElementById('audioOutputs'))
}
function removeOptions(selectElement) {
const length = selectElement.options.length - 1
for (let i = length; i >= 0; i--) {
selectElement.remove(i)
}
}
</script>
</body>
</html>
Text Chat
This example demonstrates how to text chat in a conference. DataChannel
is used for sending and receiving text messages. DataChannel
supports the transfer of binary or text data. For more information, refer to the TypeScript tutorial on how to send and receive text messages.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Text Chat</title>
</head>
<body>
<div>
<div style="float: left;">
<div style="float: left; margin-top: 5px;">
<div style="margin-left: 0">
<button type="button" id="join-btn" style="margin: 2px;">Join</button>
<button type="button" id="leave-btn" style="margin: 2px; display: none;">Leave</button>
<br />
</div>
<div id="my-container" style="width:800px; height:600px;"></div>
<br />
</div>
</div>
<div style="margin-left: 810px;">
<div id="text-container"
style="box-sizing: border-box; width: 400px; height: 600px; border: 1px solid black; padding: 5px;"></div>
<div style="position: relative; margin-top: 5px; width: 100%;">
<div style="float: left;">
<input type="text" id="inputbox"
style="box-sizing: border-box; width: 300px; height: 30px; border: 1px solid black; padding: 4px; ">
</div>
<div style="margin-left: 71%;">
<button type="button" id="send-message-btn"
style="position: absolute; width: 100px; height: 30px; vertical-align: top;">Send</button>
</div>
</div>
</div>
</div>
<script src='./../node_modules/fm.liveswitch/fm.liveswitch.js'></script>
<script type="text/javascript" src='../MediaStreamingLogic.js'></script>
<script>
var app = HelloWorld.MediaStreamingLogic.getInstance();
app.openSfuUpstreamConnection = function (localMedia) {
// Create audio and video streams from local media.
const audioStream = new fm.liveswitch.AudioStream(localMedia);
const videoStream = new fm.liveswitch.VideoStream(localMedia);
// Create data channel and stream.
const dataChannelText = new fm.liveswitch.DataChannel("text-channel");
if (this.textChannel == null) {
this.textChannel = dataChannelText;
}
const dataStream = new fm.liveswitch.DataStream(dataChannelText);
// Create a SFU upstream connection with local audio and video.
const connection = this.channel.createSfuUpstreamConnection(
audioStream,
videoStream,
dataStream
);
connection.addOnStateChange((conn) => {
fm.liveswitch.Log.debug(
`Upstream connection is ${new fm.liveswitch.ConnectionStateWrapper(
conn.getState()
).toString()}.`
);
});
connection.open();
return connection;
}
app.openSfuDownstreamConnection = function (remoteConnectionInfo) {
// Create remote media.
const remoteMedia = new fm.liveswitch.RemoteMedia();
const audioStream = new fm.liveswitch.AudioStream(remoteMedia);
const videoStream = new fm.liveswitch.VideoStream(remoteMedia);
// Add remote media to the layout.
this.layoutManager.addRemoteMedia(remoteMedia);
// Create data channel and set onReceive.
const dataChannelText = new fm.liveswitch.DataChannel("text-channel");
dataChannelText.setOnReceive((e) => this.onTextReceive(e));
// Create data stream with the data channel.
const dataStream = new fm.liveswitch.DataStream(dataChannelText);
// Create a SFU downstream connection with remote audio and video.
const connection = this.channel.createSfuDownstreamConnection(
remoteConnectionInfo,
audioStream,
videoStream,
dataStream
);
// Store the downstream connection.
this.downstreamConnections[connection.getId()] = connection;
connection.addOnStateChange((conn) => {
fm.liveswitch.Log.debug(
`Downstream connection is ${new fm.liveswitch.ConnectionStateWrapper(
conn.getState()
).toString()}.`
);
// Remove the remote media from the layout and destroy it if the remote is closed.
if (conn.getRemoteClosed()) {
delete this.downstreamConnections[connection.getId()];
this.layoutManager.removeRemoteMedia(remoteMedia);
remoteMedia.destroy();
}
});
connection.open();
return connection;
}
app.textChannel = undefined;
app.sendMessage = function (msg) {
// Prepend user ID to the message.
const chatMsg = `${this.client.getUserId()}: ${msg}`;
// Display the message locally.
this.displayMessage(chatMsg);
// Send the message through the data channel.
this.textChannel.sendDataString(chatMsg);
}
app.onTextReceive = function (dataChannelReceiveArgs) {
this.displayMessage(dataChannelReceiveArgs.getDataString());
}
const joinBtn = document.getElementById("join-btn");
const leaveBtn = document.getElementById("leave-btn");
joinBtn.onclick = () => {
app.checkAppCredentialsSet();
app.startLocalMedia().then(() => {
// Create and register the client.
app.joinAsync().then(() => {
joinBtn.style.display = "none";
leaveBtn.style.display = "inline-block";
});
});
};
leaveBtn.onclick = () => {
app.stopLocalMedia().then(() => {
app.leaveAsync().then(() => {
joinBtn.style.display = "inline-block";
leaveBtn.style.display = "none";
});
});
};
const sendMessageBtn = document.getElementById("send-message-btn");
sendMessageBtn.onclick = () => {
const inputbox = document.getElementById("inputbox");
const msg = inputbox.value;
if (msg != null && msg.length > 0) {
inputbox.value = "";
app.sendMessage(msg);
}
};
</script>
</body>
</html>
Transfer Files
This example demonstrates how to add an additional DataChannel
to transfer binary data. For more information, refer to the TypeScript tutorial on how to transfer files.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Transfer</title>
</head>
<body>
<div>
<div style="float: left;">
<div style="float: left; margin-top: 5px;">
<div style="margin-left: 0">
<button type="button" id="join-btn" style="margin: 2px;">Join</button>
<button type="button" id="leave-btn" style="margin: 2px; display: none;">Leave</button>
<br />
</div>
<div id="my-container" style="width:800px; height:600px;"></div>
<br />
</div>
</div>
<div style="margin-left: 810px;">
<div id="text-container"
style="box-sizing: border-box; width: 400px; height: 600px; border: 1px solid black; padding: 5px;"></div>
<div style="margin-top: 5px; width: 100%;">
<button type="button" id="upload-file-btn" style="width: 400px; height: 30px;">📎</button>
<input type="file" id="fileId" style="display:none">
</div>
</div>
</div>
<script src='./../node_modules/fm.liveswitch/fm.liveswitch.js'></script>
<script type="text/javascript" src='../MediaStreamingLogic.js'></script>
<script>
var app = HelloWorld.MediaStreamingLogic.getInstance();
app.saveFile = function (fileName, data) {
// Prompt the user to download the file.
if (
confirm(
`You've received a file from this channel, do you wish to download ${fileName}?`
) === true
) {
const file = new Blob([data]);
if (window.navigator.msSaveOrOpenBlob) {
// For IE.
window.navigator.msSaveOrOpenBlob(file, fileName);
} else {
// For other browsers.
const a = document.createElement("a");
const url = URL.createObjectURL(file);
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
}
fileChannel: undefined,
app.sendFile = function (fileName, file) {
// Convert the file name to a byte array.
const fileNameBytes = DataBytesConverter.toDataBytes(fileName);
// Convert the file name byte array's length to a byte array.
const fileNameBytesLength = DataBytesConverter.intToDataBytes(
fileNameBytes.length
);
// Wrap the file into a data buffer.
const dataBuffer = fm.liveswitch.DataBuffer.wrap(file);
// Prepend the byte arrays to the data buffer.
dataBuffer.prepend(fm.liveswitch.DataBuffer.wrap(fileNameBytes));
dataBuffer.prepend(fm.liveswitch.DataBuffer.wrap(fileNameBytesLength));
// Send the data buffer through the data channel.
this.fileChannel.sendDataBytes(dataBuffer);
}
app.onFileReceive = function (dataChannelReceiveArgs) {
// Retrieve the data buffer.
const dataBuffer = dataChannelReceiveArgs.getDataBytes();
// Get the offset to the actual data.
const dataOffset = dataBuffer.getIndex();
// Get the file name byte array's length and retrieve the file name.
const fileNameBytesLength = DataBytesConverter.toInt(
dataBuffer.getData().slice(dataOffset)
);
const fileName = DataBytesConverter.toString(
dataBuffer
.getData()
.slice(dataOffset + 4, dataOffset + 4 + fileNameBytesLength)
);
// Get the offset to the file's data and the file size.
const fileOffset = dataOffset + 4 + fileNameBytesLength;
const fileSize = dataBuffer.getLength() - 4 - fileNameBytesLength;
// Retrieve the file's data using the offset and the file's size.
const dataBytes = dataBuffer
.getData()
.slice(fileOffset, fileOffset + fileSize);
// Save the file.
this.saveFile(fileName, dataBytes);
}
app.openSfuUpstreamConnection = function (localMedia) {
// Create audio and video streams from local media.
const audioStream = new fm.liveswitch.AudioStream(localMedia);
const videoStream = new fm.liveswitch.VideoStream(localMedia);
const dataChannelFile = new fm.liveswitch.DataChannel("file-channel");
if (this.fileChannel == null) {
this.fileChannel = dataChannelFile;
}
const dataStream = new fm.liveswitch.DataStream(dataChannelFile);
// Create a SFU upstream connection with local audio and video.
const connection = this.channel.createSfuUpstreamConnection(
audioStream,
videoStream,
dataStream
);
connection.addOnStateChange((conn) => {
fm.liveswitch.Log.debug(
`Upstream connection is ${new fm.liveswitch.ConnectionStateWrapper(
conn.getState()
).toString()}.`
);
});
connection.open();
return connection;
}
app.openSfuDownstreamConnection = function (remoteConnectionInfo) {
// Create remote media.
const remoteMedia = new fm.liveswitch.RemoteMedia();
const audioStream = new fm.liveswitch.AudioStream(remoteMedia);
const videoStream = new fm.liveswitch.VideoStream(remoteMedia);
// Add remote media to the layout.
this.layoutManager.addRemoteMedia(remoteMedia);
const dataChannelFile = new fm.liveswitch.DataChannel("file-channel");
dataChannelFile.setOnReceive((e) => this.onFileReceive(e));
// Create data stream with the data channel.
const dataStream = new fm.liveswitch.DataStream(dataChannelFile);
// Create a SFU downstream connection with remote audio and video.
const connection = this.channel.createSfuDownstreamConnection(
remoteConnectionInfo,
audioStream,
videoStream,
dataStream
);
// Store the downstream connection.
this.downstreamConnections[connection.getId()] = connection;
connection.addOnStateChange((conn) => {
fm.liveswitch.Log.debug(
`Downstream connection is ${new fm.liveswitch.ConnectionStateWrapper(
conn.getState()
).toString()}.`
);
// Remove the remote media from the layout and destroy it if the remote is closed.
if (conn.getRemoteClosed()) {
delete this.downstreamConnections[connection.getId()];
this.layoutManager.removeRemoteMedia(remoteMedia);
remoteMedia.destroy();
}
});
connection.open();
return connection;
}
const DataBytesConverter = {
/**
* Convert a string into Uint8Array
*/
toDataBytes: function (str) {
return new TextEncoder().encode(str);
},
/**
* Convert an int into Uint8Array
*/
intToDataBytes: function (x) {
return new Uint8Array([
(x & 0xff000000) >> 24,
(x & 0x00ff0000) >> 16,
(x & 0x0000ff00) >> 8,
x & 0x000000ff
]);
},
/**
* Convert Uint8Array into int
*/
toInt: function (dataBytes) {
return new DataView(dataBytes.buffer).getUint32(0);
},
/**
* Convert Uint8Array into string
*/
toString: function (dataBytes) {
return new TextDecoder().decode(dataBytes);
}
};
const joinBtn = document.getElementById("join-btn");
const leaveBtn = document.getElementById("leave-btn");
joinBtn.onclick = () => {
app.checkAppCredentialsSet();
app.startLocalMedia().then(() => {
// Create and register the client.
app.joinAsync().then(() => {
joinBtn.style.display = "none";
leaveBtn.style.display = "inline-block";
});
});
};
leaveBtn.onclick = () => {
app.stopLocalMedia().then(() => {
app.leaveAsync().then(() => {
joinBtn.style.display = "inline-block";
leaveBtn.style.display = "none";
});
});
};
const uploadFileBtn = document.getElementById("upload-file-btn");
const fileInput = document.getElementById("fileId");
uploadFileBtn.onclick = () => fileInput.click();
fileInput.onchange = (e) => {
const file = e.target.files[0];
if (!file) {
return;
}
// Read the file to an array buffer and send it through the data channel.
const reader = new FileReader();
reader.onload = (e) => {
const contents = new Uint8Array(e.target.result);
app.sendFile(file.name, contents);
alert(`You've sent ${file.name} to the channel!`);
};
reader.readAsArrayBuffer(file);
};
</script>
</body>
</html>
Broadcast
This example demonstrates how to do broadcasting. LiveSwitch supports massive-scale broadcasting of audio and video data from SFU upstream connections to SFU downstream connections. In broadcasting, there is one Broadcaster and multiple Receivers. For more information, refer to the TypeScript tutorial on how to do broadcasting.
Important
When generating tokens for a broadcast, set the disableSendAudio
and disableSendVideo
properties on the channel claim. This ensures that viewers can't override a broadcast when you use Media IDs. For more information, refer to the Set Connection Permission section.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Broadcaster and Receiver</title>
</head>
<body>
<div>
<div style="float: left;">
<div style="float: left; margin-top: 5px;">
<div style="margin-left: 0">
<button type="button" id="broadcast-btn" style="margin: 2px;">Broadcast</button>
<button type="button" id="receive-btn" style="margin: 2px;">Receive</button>
<br />
</div>
<div id="my-container" style="width:800px; height:600px;"></div>
<br />
</div>
</div>
<div style="margin-left: 810px;">
<div id="text-container"
style="box-sizing: border-box; width: 400px; height: 600px; border: 1px solid black; padding: 5px;"></div>
</div>
</div>
<script src='./../node_modules/fm.liveswitch/fm.liveswitch.js'></script>
<script type="text/javascript" src='../MediaStreamingLogic.js'></script>
<script>
let app = HelloWorld.MediaStreamingLogic.getInstance();
// <broadcast>
const broadcastBtn = document.getElementById("broadcast-btn");
const receiveBtn = document.getElementById("receive-btn");
broadcastBtn.onclick = () => {
app.checkAppCredentialsSet();
startAs(new Broadcaster());
}
receiveBtn.onclick = () => {
app.checkAppCredentialsSet();
startAs(new Receiver());
}
function startAs(participant) {
// Start capturing local media (broadcaster only).
participant.start().then(() => {
// Register and establish connection.
participant.joinAsync().then(() => {
broadcastBtn.disabled = true;
receiveBtn.disabled = true;
});
});
}
// </broadcast>
class Participant {
// Media Id, which will be used by both the broadcaster and receivers.
mediaId = "your-media-id";
// Layout manager
layoutManager = new fm.liveswitch.DomLayoutManager(
document.getElementById("my-container")
);
// Fields
client = undefined;
channel = undefined;
constructor() { }
joinAsync() {
const promise = new fm.liveswitch.Promise();
// Create the client.
this.client = new fm.liveswitch.Client(app.gatewayUrl, app.applicationId);
// Write registration state to log.
this.client.addOnStateChange(() =>
fm.liveswitch.Log.debug(
`Client is ${new fm.liveswitch.ClientStateWrapper(
this.client.getState()
)}.`
)
);
// Generate a token (do this on the server to avoid exposing your shared secret).
const token = fm.liveswitch.Token.generateClientRegisterToken(
app.applicationId,
this.client.getUserId(),
this.client.getDeviceId(),
this.client.getId(),
null,
[new fm.liveswitch.ChannelClaim(app.channelId)],
app.sharedSecret
);
// Register client with token.
this.client
.register(token)
.then((channels) => {
// Store our channel reference.
this.channel = channels[0];
fm.liveswitch.Log.info(
`Client ${this.client.getId()} has successfully connected to channel ${this.channel.getId()}, Hello World!`
);
app.displayMessage(
`Client ${this.client.getId()} has successfully connected to channel ${this.channel.getId()}, Hello World!`
);
this.establishConnection();
promise.resolve(null);
})
.fail((ex) => {
fm.liveswitch.Log.error("Failed to register with Gateway.");
promise.reject(ex);
});
return promise;
}
// Abstract methods
establishConnection() { }
start() { }
stop() { }
}
class Broadcaster extends Participant {
localMedia = undefined;
establishConnection() {
// Create a SFU upstream connection with local audio and video and the presentation ID.
const audioStream = new fm.liveswitch.AudioStream(this.localMedia);
const videoStream = new fm.liveswitch.VideoStream(this.localMedia);
const connection = this.channel.createSfuUpstreamConnection(
audioStream,
videoStream,
this.mediaId
);
connection.open();
}
start() {
const promise = new fm.liveswitch.Promise();
// Create local media with audio and video enabled.
const audioEnabled = true;
const videoEnabled = true;
this.localMedia = new fm.liveswitch.LocalMedia(audioEnabled, videoEnabled);
// Set local media in the layout.
this.layoutManager.setLocalMedia(this.localMedia);
// Start local media capturing.
this.localMedia
.start()
.then(() => {
fm.liveswitch.Log.debug("Media capture started.");
promise.resolve(null);
})
.fail((ex) => {
fm.liveswitch.Log.error(ex.message);
promise.reject(ex);
});
return promise;
}
stop() {
const promise = new fm.liveswitch.Promise();
// Stop local media capturing.
this.localMedia
.stop()
.then(() => {
fm.liveswitch.Log.debug("Media capture stopped.");
promise.resolve(null);
})
.fail((ex) => {
fm.liveswitch.Log.error(ex.message);
promise.reject(ex);
});
return promise;
}
}
class Receiver extends Participant {
remoteMedia = undefined;
establishConnection() {
// Create remote media.
this.remoteMedia = new fm.liveswitch.RemoteMedia();
const audioStream = new fm.liveswitch.AudioStream(this.remoteMedia);
const videoStream = new fm.liveswitch.VideoStream(this.remoteMedia);
// Add remote media to the layout.
this.layoutManager.addRemoteMedia(this.remoteMedia);
// Create a SFU downstream connection with remote audio and video and the presentation ID.
const connection = this.channel.createSfuDownstreamConnection(
this.mediaId,
audioStream,
videoStream
);
connection.addOnStateChange((conn) => {
// Remove the remote media from the layout if the remote is closed.
if (conn.getRemoteClosed()) {
this.layoutManager.removeRemoteMedia(this.remoteMedia);
}
});
connection.open();
}
// Not needed because receiver only receives media from the broadcaster.
start() {
return fm.liveswitch.Promise.resolveNow();
}
// Not needed because receiver only receives media from the broadcaster.
stop() {
return fm.liveswitch.Promise.resolveNow();
}
}
</script>
</body>
</html>
For the complete example apps that this page describes, check out this GitHub repository.
Learn More
To learn how to install and use the LiveSwitch Web SDK to develop an app using Vue, Angular, or React, refer to the following links: