Migration Guide: nanoStream Webcaster v5 to v6
This guide provides step-by-step instructions on how to migrate your application from nanoStream Webcaster version 5 to the new and improved version 6.
Table of Contents
Introduction
nanoStream Webcaster is a JavaScript client library designed for low latency live streaming. It operates in the browser and uses WebRTC for broadcasting, ingesting streams into the nanoStream Cloud. The new version 6 brings a host of improvements and new features that make it easier to use, more robust, and more efficient.
Motivation for Migration
Here are some compelling reasons to migrate to nanoStream Webcaster v6:
Easier to Use: The new version has been designed with simplicity in mind. It offers a more intuitive interface and streamlined functions, making it easier for developers to use.
Write Less Code: With v6, you can achieve the same results with less effort. This makes your application more efficient and easier to maintain. The new configuration options in the library have a flatter structure that simplifies coding, ultimately leading to less code being written.
More Robust: The new version is more robust than its predecessor. It offers improved error handling and stability, ensuring your live streams run smoothly. Because the standardized WHIP protocol is now used for negotiation of connections into the cloud, it addresses stream start issues caused by the proprietary WebSocket based protocol in version 5.
New Features: v6 introduces a range of new features that will enhance your live streaming capabilities. These features will roll out progressively, offering new possibilities for your application.
Easier Integration of MediaStreams: The new version simplifies the integration of MediaStreams, making it easier to use custom devices and virtual sources.
Exposes TypeScript Definitions: For TypeScript users, v6 exposes TypeScript definitions, making it easier to integrate with TypeScript projects and benefit from features like static typing and IntelliSense. This also means that the API contract is directly specified by the source code inherently, and therefore documents itself. Further the reference docs we ship with the library are generated by leveraging this.
New Semantics: From Event-Based to Promise-Based Code
In nanoStream Webcaster v6, we have made a significant change in the way the library handles asynchronous operations. We have moved away from the event-based model used in v5 and adopted a promise-based model. This change affects how you write and structure your code when using the nanoStream Webcaster.
In the previous version (v5), the library used an event-based model. This means that when you called a method on the client, it would trigger an event when it completed. Your code would then handle these events to determine whether the operation was successful or if an error occurred.
In version 6, we have switched to a promise-based model. Now, each client method returns a Promise. This Promise resolves when the operation is successful and rejects when an error occurs. This approach aligns with modern JavaScript practices and makes it easier to handle asynchronous operations. This also means that you can call any promise-returning method using the async/await pattern that comes with modern JS runtimes. In this scenario, you can use a try/catch clause to handle errors. This is not a feature specific to this library, but a general language idiom coming with native runtime support nowadays.
To adapt your code for v6, you'll need to change your client method calls. Instead of attaching event listeners, you'll need to handle the returned promise.
Here's an general example of how to adapt your code:
// v5: Event-based
client.on('BroadcastSuccess', () => {
console.log('Broadcast started');
});
client.on('BroadcastError', (error) => {
console.error('An error occurred:', error);
});
client.startBroadcast();
// v6: Promise-based
client.startBroadcast()
.then(() => {
console.log('Broadcast started');
})
.catch((error) => {
console.error('An error occurred:', error);
});
You can also use async/await (as with any Promise-returning function), to avoid having to use closure scopes, or any callbacks at all. Clearly, both is functionally equivalent, and one or the other can be used interchangeably at any moment, depending on what fits better in your given context.
// v6: Promise-based with async/await
(async () => {
try {
await client.startBroadcast();
console.log('Broadcast started');
} catch (error) {
console.error('An error occurred:', error);
}
})();
This new promise-based model makes your code more robust and easier to understand, helping you build better live streaming applications with nanoStream Webcaster v6.
Documentation
Please find the documentation for v6 of the Webcaster API below:
https://nanocosmos.github.io/webcaster/docs/classes/webcaster.Webcaster.html
Migration Steps
Follow these steps to migrate your application from nanoStream Webcaster v5 to v6:
- Update the Library Reference: Replace the reference to the v5 library in your HTML file with the v6 library.
<!DOCTYPE html>
<html>
<head>
<script src="path_to/nanostream.webcaster.js"></script>
</head>
<body>
<!--create a video element four previewing your MediaStream-->
<video id="preview" muted autoplay playsinline></video>
</body>
</html>
- Update the Initialization Code: Update the initialization code to use the new v6 syntax. Refer to the v6 documentation for the new initialization syntax.
const { Webcaster, HelperUtils, DeviceUtils } = window.WebcasterApiV6;
let client;
// Config for the NanoWebcaster library
let initConfig = {
inputCfg: {
// Either you create a MediaStream and pass a reference here (see in next section),
// or let the client create one for you by omitting the property:
mediaStream: null,
mediaStreamCfg: {
maxFramerate: 30,
resolution: [1280, 720],
audioVideoOnly: false,
audioConstraints: {
autoGainControl: true,
channelCount: 2,
echoCancellation: true,
noiseSuppression: true
},
},
broadcastCfg: {
transcodeAudioBitrateBps: HelperUtils.kbps(128),
maxAudioBitrateBps: HelperUtils.kbps(128),
maxVideoBitrateBps: HelperUtils.kbps(2000),
maxEncodingFramerate: 30,
}
},
ingestUrl: 'rtmp://bintu-stream.nanocosmos.de:1935/live',
streamName: 'YOUR_STREAM_NAME',
serverUrl: 'https://bintu-webrtc.nanocosmos.de/p/webrtc',
previewVideoElId: 'preview',
};
// do the initialization in DOMContentLoaded, if you can not guarantee otherwise
// that the JavaScript code will execute only after the DOM has fully loaded.
document.addEventListener('DOMContentLoaded', async () => {
// pass the config when instantiating the client
client = window.client = new Webcaster(initConfig);
client.setup().then(() => {
console.log('Webcaster.setup done');
});
}
- Creating a MediaStream from a Selection of Audio & Video Devices
You pass device ids with the initConfig in order to create a MediaStream from connected devices that you have selected beforehand. In order to do so, you can utilize our DeviceUtils helper functions. Please find a sample below.
const { Webcaster, HelperUtils, DeviceUtils } = window.WebcasterApiV6;
let client;
document.addEventListener('DOMContentLoaded', async () => {
let devices = await DeviceUtils.getAvailableMediaDevices();
let videoDevices = DeviceUtils.filterDevices(devices, ['videoinput']);
let audioDevices = DeviceUtils.filterDevices(devices, ['audioinput']);
let videoDeviceId = videoDevices[0]?.deviceId; // we select the first video device from the list
let audioDeviceId = audioDevices[0]?.deviceId; // we select the first audio device from the list
let initConfig = {
inputCfg: {
mediaStream: null,
mediaStreamCfg: {
audioDeviceId: audioDeviceId,
videoDeviceId: videoDeviceId,
maxFramerate: 30,
resolution: [1280, 720],
audioVideoOnly: false,
audioConstraints: {
autoGainControl: true,
channelCount: 2,
echoCancellation: true,
noiseSuppression: true
},
},
broadcastCfg: {
transcodeAudioBitrateBps: HelperUtils.kbps(128),
maxAudioBitrateBps: HelperUtils.kbps(128),
maxVideoBitrateBps: HelperUtils.kbps(2000),
maxEncodingFramerate: 30,
}
},
ingestUrl: 'rtmp://bintu-stream.nanocosmos.de:1935/live',
streamName: 'YOUR_STREAM_NAME',
serverUrl: 'https://bintu-webrtc.nanocosmos.de/p/webrtc',
previewVideoElId: 'preview',
};
// pass the config when instantiating the client
client = window.client = new Webcaster(initConfig);
client.setup().then(() => {
console.log('Webcaster.setup done');
});
});
- Creating Custom MediaStreams with Native Browser JavaScript
In nanoStream Webcaster v6, you have the ability to create custom MediaStreams using native browser JavaScript and pass them to the Webcaster on initialization. Here's how you can do it:
// do the initialization in DOMContentLoaded, if you can not guarantee otherwise
// that our JavaScript code will execute only after the DOM has fully loaded.
document.addEventListener('DOMContentLoaded', async () => {
let stream;
try {
// create your audio/video stream from camera or microphone sources:
stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
// or create a screen share:
/*
// First create two MediaStreams and access the regarding MediaStreamTracks
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
const videoStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
const audioTrack = audioStream.getAudioTracks()[0];
const videoTrack = videoStream.getVideoTracks()[0];
// Then construct a new MediaStream from a MediaStreamTrack for video (screen share)
// and a MediaStreamTrack for audio (microphone).
stream = new MediaStream([audioTrack, videoTrack]);
*/
} catch(error) {
alert('Error creating stream:', error);
return;
}
let initConfig = {
inputCfg: {
// Use the just created stream
mediaStream: stream,
broadcastCfg: {
transcodeAudioBitrateBps: HelperUtils.kbps(128),
maxAudioBitrateBps: HelperUtils.kbps(128),
maxVideoBitrateBps: HelperUtils.kbps(2000),
maxEncodingFramerate: 30,
}
}
ingestUrl: 'rtmp://bintu-stream.nanocosmos.de:1935/live',
streamName: 'YOUR_STREAM_NAME',
serverUrl: 'https://bintu-webrtc.nanocosmos.de/p/webrtc',
previewVideoElId: 'preview',
};
// pass your config when instantiating the client
client = window.client = new Webcaster(initConfig);
client.setup().then(() => {
console.log('Webcaster.setup done');
});
}
- Connect your UI: Update your code to trigger v6 methods through your UI.
<body>
<!--create a video element four previewing your MediaStream-->
<video id="preview" muted autoplay playsinline></video>
<!--create UI elements for calling client methods-->
<button onclick="startPreview()">startPreview</button>
<button onclick="startBroadcast()">startBroadcast</button>
<button onclick="stopBroadcast()">stopBroadcast</button>
<button onclick="dispose()">dispose</button>
<button onclick="recover()">recover</button>
<button onclick="setMuted()">setMuted</button>
</body>
<script>
function assertCreated() {
if (!client) {
alert('Create client instance first');
return false;
}
return true;
}
let startPreview = async () => {
if (!assertCreated()) return;
try {
await client.startPreview();
} catch (err) {
console.error('Error starting preview:', err);
}
};
let startBroadcast = async () => {
if (!assertCreated()) return;
try {
await client.startBroadcast();
} catch (err) {
console.error('Error starting broadcast:', err);
}
};
let stopBroadcast = async () => {
if (!assertCreated()) return;
try {
await client.stopBroadcast();
} catch (err) {
console.error('Error stopping broadcast:', err);
}
};
let dispose = async () => {
if (!assertCreated()) return;
try {
await client.dispose();
} catch (err) {
console.error('Error disposing client:', err);
} finally {
client = null;
}
};
let recover = async () => {
if (!assertCreated()) return;
try {
await client.recover();
} catch (err) {
console.error('Error recovering client:', err);
}
};
let setMuted = async () => {
if (!assertCreated()) return;
try {
await client.setMuted({
audio: true,
video: true
});
} catch (err) {
console.error('Error recovering client:', err);
}
}
</script>
- Update Error Handling: Review your error handling code to ensure it is compatible with the new error handling in v6.
client.onError = (err) => {
console.error('Webcaster.onError:', err);
};
- Listen For State Events: Update your code to listen to the new v6 events about Webcaster state.
client.onStateChange = () => {
console.log('Webcaster.onStateChange:', client.getUpstreamStatus());
};
client.onMetrics = (metrics) => {
console.log('Webcaster.onMetrics:', metrics);
};
- Test Your Application: Thoroughly test your application to ensure it works correctly with the new v6 library.
Conclusion
Migrating to nanoStream Webcaster v6 offers numerous benefits and enhancements. While the migration process requires some effort, the payoff in terms of improved usability, efficiency, and robustness makes it a worthwhile investment. Happy streaming!