# Creating a live streaming platform using Mux and Nuxt.js
The influence of live streaming on contemporary society cannot be overstated. Live streaming has evolved far beyond simply broadcasting events such as concerts and sporting events since its inception in the late 1990s and early 2000s. Platforms like Twitch, YouTube Live, Facebook Live, TikTok Live, and Instagram Live have made it a widely known mode of communication and entertainment. These platforms offer a wide range of activities, such as gaming, music, vlogging, and teaching, among others.
Live streaming has also become a powerful tool for businesses to increase brand awareness, foster customer trust and loyalty, and provide more authentic opportunities for engagement. Businesses can engage with their audiences in real-time, creating a sense of immediacy and authenticity that traditional marketing methods lack.
Given its popularity, there is an ever-growing demand for the development of live-streaming platforms. As such, this tutorial focuses on how we can build our very own live-streaming platform using powerful tools like [Mux](https://www.mux.com/) and [Nuxt.js](https://nuxt.com/)
## What is Mux?
[Mux](https://www.mux.com/) is a powerful API-based tool that enables developers to easily create and manage live streams and video content. With Mux, both developers and average users can leverage a range of APIs to manage their video content, as well as preview it in real-time.
In addition to live-streaming, Mux offers a variety of other video-related services, such as encoding, transcoding, and delivery. With Mux, developers can seamlessly integrate these functionalities into their applications, allowing for a highly customizable video experience.
Mux's intuitive API interface and developer-friendly documentation make it an ideal choice for teams of all sizes, regardless if they're building a small-scale application or a large-scale enterprise system. By using Mux, developers can focus on creating engaging and immersive video experiences for their users without having to worry about the underlying technical details.
*Following that, we'll learn about Nuxt.js, its advantages, and what it has to offer.*
## A Brief Overview of Nuxt.js
Nuxt.js is a cutting-edge JavaScript web framework designed for building [Vue.js](https://vuejs.org/) applications. With its powerful rendering strategies at the route level, including Server Side Rendering (SSR), Static Site Generation (SSG), and Client Side Rendering (CSR), Nuxt.js provides a structured code base for unparalleled flexibility and scalability.
Some of the features provided by Nuxt.js include:
- **SEO**: Due to its Server Side rendering capabilities, Nuxt.js websites are indexable by the search engine. This implies search engines can crawl and analyze its website contents and index its webpages. This improves online visibility and traffic on search engines.
- **Multiple Rendering Strategies**: As stated previously, Nuxt.js provides a wide variety of rendering strategies. These rendering strategies allow Nuxt.js to be flexible, provide optimizations, and foster low-cost hosting.
- **Ready-to-Use Modules**: There are several modules that as easy to integrate and readily available to be used in conjunction with Nuxt.js. These modules can facilitate faster development time. Examples of these [modules](https://nuxt.com/modules) include; vueuse, TailwindCSS, i18n, and many others.
- **Large and growing community**: Because of its popularity, Nuxt.js has a large community that can help developers using Nuxt.js resolve issues and improve their knowledge of Nuxt.js.
- **Built-In support for modern web technologies**: Nuxt.js allows using modern web technologies such as CSS preprocessors, ES6/7, Typescript, and many more. With these modern technologies, the speed, accuracy, and efficiency of the development process can be improved.
- **Modular architecture**: Building applications using Nuxt.js can allow developers to easily add and remove features.
*Next, we’ll start building our application using these tools.*
## Prerequisites
To build our live-streaming application, we’ll need to have the following:
- A code editor, preferably [Visual Studio Code (VS Code)](https://code.visualstudio.com/).
- Knowledge of [JavaScript](https://www.w3schools.com/js/) and [Vue.js 3](https://vuejs.org/).
- [FFmpeg](https://ffmpeg.org/) CLI tool installed.
- [Yarn package manager](https://yarnpkg.com/) installed.
- [Node.js v16](https://nodejs.org/en/blog/release/v16.16.0) and above installed.
## Platform Overview
To broadcast video to an [Real-Time Messaging Protocol (RTMP)](https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol) server, we'll need to use a medium that is capable of doing so, as most web browsers do not support this functionality. This involves sending the video streams to a specialized server (in this case, the Mux server) that is capable of both broadcasting to an RTMP server and receiving incoming streams. While there are several methods that we could use to connect to the server, the WebSocket protocol is typically the most popular option.
## Building the Client Section
The live-streaming application is split into two components: the server and the client. The server's primary function is broadcasting video streams from the client to the Mux RTMP server. The client records and sends the recorded streams to the server to be broadcasted via a web socket connection.
To get started, We need to first set up our Nuxt.js project by running the command:
```shell
npx nuxi init nuxt-live-streamer
```
After the command has been executed successfully, we then run:
```shell
cd nuxt-live-streamer && yarn
```
This creates a `Nuxt.js` project folder called `nuxt-live-streamer` in this form:
```shell
└── nuxt-live-streamer/
├── README.md
├── app.vue
├── list.md
├── nuxt.config.ts
├── package.json
├── public/
│ └── favicon.ico
└── tsconfig.json
```
Then we need to install the dependencies needed:
- TailwindCSS: Follow these [instructions](https://tailwindcss.nuxtjs.org/getting-started/setup/) to set up tailwind for our project.
- Mux: To install mux, run the following command in your terminal:
```shell
yarn add @mux/mux-player
```
After Tailwind has been successfully installed, we can proceed to open our projects folder in the code editor (preferably VS Code) and add the necessary pages for our Nuxt.js app.
To add these pages, firstly, we need to update our `app.vue` to support pages by replacing the content of this file with the following:
```javascript
<template>
<div>
<NuxtPage />
</div>
</template>
```
Next, create a folder called `pages` in the root directory of the project. This `pages` folder contains the pages for our application. Then we can proceed to create our first page --- the `landing page` which contains information about the application.
Before we proceed, it's essential to add a utils folder in the root directory to house the Timer class file. This `class` provides timing functionality for the application, enabling us to measure how long a certain operation takes to complete. Here's how to add the `Timer.js` class file to the utils folder:
- Create a new folder named `utils` in the root directory of your project.
- Inside the `utils` folder, create a new file named `Timer.js`.
- Add the code from the [link](https://github.com/Otrex/nuxt-live-stream/blob/main/utils/Timer.js) to the `Timer.js` file.
To add this `landing page`, we need to create the file `main.vue` in the `pages` directory and add the following code from [here](https://github.com/Otrex/nuxt-live-stream/blob/main/pages/main.vue) to the file.
The template in the file renders the video player for the recordeing feedback, the controls such as the switch camera, and mic controls, and also the enter stream key and start streaming button.
It also renders the `get key` button which displays when the user has not entered thier stream key. Once the `get stream key` is click, it makes a request to generate a stream key and displays its result containing the sharable playback ID and the key using the `{{ streamCredential?.playbackId[0] }}`.
For the `script` section, the provided code in the script tag, begins by importing the required dependencies and initializing variables for different elements and states. These include references to video elements, streaming status, `WebSocket` connection, input stream, device list visibility, stream key, media recorder, recorder settings, timer, and elapsed time. It also defines reactive objects to store information about available devices, selected devices, and the enabled state of each device type.
```javascript
import Timer from "~~/utils/Timer";
const videoRef = ref();
const isStreaming = ref();
const isWSconnected = ref();
const inputStreamRef = ref();
const showDeviceList = ref();
const streamKey = ref();
const websocket = ref();
const mediaRecorderRef = ref();
const recorderSettings = ref();
const timer = ref();
const elapsedTime = ref();
const devices = reactive({
video: [],
audio: [],
});
const selectedDevices = reactive({
video: null,
audio: null,
});
const isDeviceEnabled = reactive({
video: true,
audio: true,
});
const constraints = reactive({
audio: true,
video: true,
});
```
Upon mounting the component, the `timer` is initialized by creating a new instance of the `Timer` class. Then the `getRecorderSettings()` function is called, which retrieves the recorder settings. This determines the appropriate settings based on browser compatibility.
```javascript
function getRecorderSettings() {
const video = MediaRecorder.isTypeSupported("video/webm;codecs=h264")
? "h264"
: "vp8";
const codecs = `;codecs=${video}, opus`;
const result = MediaRecorder.isTypeSupported("video/mp4")
? {
format: "mp4",
video: "h264", // Know what this means
audio: "aac",
codecs: "",
mimeType: "video/mp4",
}
: {
video,
format: "webm",
audio: "opus",
codecs,
mimeType: `video/webm${codecs}`,
};
recorderSettings.value = result;
}
```
Then calls the `enableCamera()` function which request access to the user's media devices, specifically audio and video. The retrieved media stream is then assigned to the videoRef element as the source object, allowing the video to be played. Additionally, the `getDevices()` function is called to enumerate and store information about available media devices in the devices object.
```javascript
function getDevices() {
navigator.mediaDevices.enumerateDevices().then(function (d) {
devices.video = d.filter((e) => e.kind === "videoinput");
devices.audio = d.filter((e) => e.kind === "audioinput");
});
}
const enableCamera = async () => {
inputStreamRef.value = await navigator.mediaDevices.getUserMedia(constraints);
videoRef.value.srcObject = inputStreamRef.value;
await videoRef.value.play();
};
```
The `toggleDisableTrack(type)` function handles toggling the enabled state of a device track, either audio or video. If the track is currently enabled, it sets the constraints to false to disable it by calling the `setConstraints()` function. Conversely, if the track is disabled, it sets the constraints to the selected device or true to enable it again. The `isDeviceEnabled` object is updated accordingly.
```javascript
function toggleDisableTrack(type) {
if (isDeviceEnabled[type]) {
setConstraints(type, false);
isDeviceEnabled[type] = false;
} else {
setConstraints(type, selectedDevices[type] || true);
isDeviceEnabled[type] = true;
}
}
```
The `setConstraints(type, newValue)` function is responsible for setting constraints for a specific device type, either audio or video. It updates the constraints object with the new values and triggers the `enableCamera()` function to apply the updated constraints and refresh the media stream.
```javascript
function setConstraints(type, newValue) {
constraints[type] = newValue;
enableCamera();
}
```
The `getKeys()` function is an asynchronous function that fetches the stream credential from `/api/getKey`. It retrieves the stream credential value and stores it in the streamCredential and streamKey variables.
```javascript
async function getKeys() {
const { data } = await useFetch('/api/getKey')
streamCredential.value = data.value;
streamKey.value = data.value.key;
}
```
To select a specific device, the `selectDevice(type, deviceId)` function is used. It updates the `selectedDevices` object with the chosen device's ID, sets the appropriate constraints based on the selected device, and hides the device list.
```javascript
function selectDevice(type, deviceId) {
selectedDevices[type] = { deviceId };
setConstraints(type, { deviceId });
showDeviceList.value = false;
}
```
The `stopStreaming()` function is responsible for stopping the media recorder, resetting the `timer`, and updating the streaming state. If the media recorder is currently recording, it is stopped. The timer is then stopped and reset to its initial state. The elapsed time is set to `null`, and the isStreaming variable is set to false to indicate that streaming has stopped.
```javascript
function stopStreaming() {
if (mediaRecorderRef.value?.state === "recording") {
mediaRecorderRef.value.stop();
}
timer.value.stop()
timer.value.reset()
elapsedTime.value = null;
isStreaming.value = false;
}
```
To initiate streaming the function `startStreaming()` is called. The function checks if the stream key exists and establishes a WebSocket connection.
```javascript
...
if (!streamKey.value) return alert("Please enter a stream key");
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"
const wsUrl = new URL(`${protocol}//${window.location.host}`);
wsUrl.searchParams.set("key", streamKey.value);
wsUrl.searchParams.set("video", recorderSettings.value?.video);
wsUrl.searchParams.set("audio", recorderSettings.value?.audio);
websocket.value = new WebSocket(wsUrl);
websocket.value.addEventListener("open", () => {
isWSconnected.value = true;
});
websocket.value.addEventListener("close", () => {
isWSconnected.value = false;
stopStreaming();
});
...
```
Then it prepares the audio and video streams for recording, creates an output stream combining the audio and video.
```javascript
...
const audioStream = new MediaStream();
const audioTracks = inputStreamRef.value.getAudioTracks();
const videoStream = new MediaStream();
const videoTracks = inputStreamRef.value.getVideoTracks();
// Added tracks from the media input stream ref to the audioStrem
audioTracks.forEach(function (track) {
audioStream.addTrack(track);
});
videoTracks.forEach(function (track) {
videoStream.addTrack(track);
});
// created outputStream to add both the video and audiio streams together
const outputStream = new MediaStream();
[audioStream, videoStream].forEach(function (s) {
s.getTracks().forEach(function (t) {
outputStream.addTrack(t);
});
});
...
```
After that, it initializes a `MediaRecorder` object, and starts the recording process and sends the streams.
```javascript
...
mediaRecorderRef.value = new MediaRecorder(outputStream, {
mimeType: recorderSettings.value?.mimeType,
videoBitsPerSecond: 3000000,
audioBitsPerSecond: 64000,
});
mediaRecorderRef.value.addEventListener("dataavailable", (e) => {
console.log(e);
websocket.value.send(e.data);
});
mediaRecorderRef.value.addEventListener("stop", () => {
stopStreaming();
websocket.value.close();
});
mediaRecorderRef.value.start(1000);
...
```
It also handles event listeners for data availability and recording stoppage. Additionally, it manages the timer and provides error handling for a smooth streaming experience.
```javascript
...
timer.value.start(0, (time) => {
elapsedTime.value = Timer.toTimeString(time)
})
isStreaming.value = true;
...
```
After that, we'll proceed to add a page for viewing the streams. Create a new folder called `preview` in the `pages` folder, then a new file called `+[id].vue` inside it.
Then add the code below.
```javascript
<template>
<div class="flex h-screen items-center">
<mux-player
stream-type="on-demand"
:playback-id="playbackId"
/>
</div>
</template>
<script setup>
import "@mux/mux-player"
import { useRoute, useRouter } from "vue-router";
const $route = useRoute();
const $router = useRouter();
const playbackId = ref();
onMounted(() => {
playbackId.value = validateAndSanitizePlaybackId($route.params.id);
})
function validateAndSanitizePlaybackId(id) {
if (id || id.split().length > 10) return id.trim();
setTimeout(() => {
$router.push("/main")
}, 5000)
}
</script>
```
When the page is mounted, we extract the mux playback key from the route and attach it to the mux-player tag to ensure smooth video playback on this page. Furthermore, to protect against invalid playback IDs, we use the `validateAndSanitizePlaybackId()` function, which checks if the provided ID is valid and returns the trimmed ID if it is. If the ID is invalid, the browser will automatically redirect to the landing page.
## Building the Server
The backend section consists of a WebSocket server and the video converter that converts and forwards the stream to the mux servers.
To get started, create a `modules` folder in the root directory of the Nuxt project. Then create a `wss.ts` file. This file will contain our WebSocket server and setup.
To continue, we need to install the websocket package by running the command:
```shell
yarn add ws
```
On the `wss.ts` file, add the code [here](https://github.com/Otrex/nuxt-live-stream/blob/main/modules/wss.ts).
The provided code consists of several components that work together to set up a WebSocket server and stream data to an RTMP server using FFmpeg.
First, the necessary modules are imported. One of which is the `WebSocketServer`. The `WebSocketServer` class is imported from the "ws" module, which enables the creation of a WebSocket server. After that, the `defineNuxtModule` function is imported from the `@nuxt/kit` module, allowing the definition of a module for `Nuxt.js`.
```javascript
import { WebSocketServer } from "ws"
import { defineNuxtModule } from '@nuxt/kit'
import child_process from 'child_process';
import url from 'url';
...
```
Within the setup function, an event handler is attached to the `listen` hook of the Nuxt.js server. This event handler is triggered when the server starts listening. Inside this event handler, a `WebSocket` server instance is created using the imported `WebSocketServer` class, with the `server` object passed as an argument.
Another event handler is attached to the `connection` event of the `WebSocket` server. This event handler is executed whenever a new web socket connection is established.
Upon a connection, the code performs the following:
- Logs a message to the console and sends a message to the connected WebSocket client, indicating that the streaming socket is connected.
- Parses the query parameters from the request URL using the url module and extracts the values of 'key', 'video', and 'audio' parameters.
- Constructs an RTMP URL using the extracted 'key' parameter.
- Constructs an array of FFmpeg codec options based on the values of the 'video' and 'audio' parameters.
- Spawns a new FFmpeg process using the child_process module, passing the constructed command-line arguments.
- Sets up event handlers for the stdin, stderr, and close events of the FFmpeg process.
- Handles incoming WebSocket messages by writing them to the stdin of the FFmpeg process.
- Handles the WebSocket 'close' event by killing the FFmpeg process and logging a message.
Then we can proceed to register our module to the `nuxt.config.ts` file. Open the `nuxt.config.ts` file, and add the following to the `modules` array:
```javascript
'~/modules/wss.ts'
```
Here’s how your nuxt.config.ts file should look like:

After that, we proceed to add our endpoint for generating the stream keys.
To do this, create a `server` folder in the root directory of the project, create another fiolder `api` in the server folder, here we then create a file `getKey.ts` on that `api` folder.
Once this is done, add the following code:
```javascript
import Mux from "@mux/mux-node";
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const { Video } = new Mux(config.MUX_TOKEN_ID, config.MUX_TOKEN_SECRET);
const stream = await Video.LiveStreams.create({
new_asset_settings: { playback_policy: 'public' },
playback_policy: 'public',
});
return {
key: stream.stream_key,
playbackId: stream.playback_ids?.map((e) => e.id)
}
})
```
This creates the server event that handles the generation of the `stream keys` and `playback ID` requested to start streaming.
Then and the following to the nuxt config
```javascript
export default defineNuxtConfig({
runtimeConfig: {
MUX_TOKEN_ID: '******',
MUX_TOKEN_SECRET: '********',
},
...
})
```

## Testing
To test the application, We can run our nuxt.js application in the development environment using the command on our terminal.
```shell
yarn dev
```
This spins up the development server.

Then we can visit the url `http://localhost:3000` on our web browser.

Click on the "Get Started" button to navigate to the page to enter the mux key.

Click on the Generate Key button to generate the key, then copy the playback Id, as indicated below.

After that, ope a new browser tab or instance then go to `http://localhost:3000/preview/GfqR666oAsb1S4LLPf3V02VtoWhy5JQ1y1xmTDHzRDG4`, to preview the stream.

A screenshot showing the video stream.
This is currently not showing the video, as we havent started the stream yet.
Navigate back to the tab where we copied the plaback Id from, then click on "Start Stream" to start the stream.

Navigate back to the preview tab, to view our stream. We'll see that the play button is now available. Click on it start viewing the streams


## Conclusion
Finally! You’ve come to the end of this article where you learned how to create a live streaming platform using Mux and Nuxt. For business owners looking to foster customer trust and loyalty and live streaming is an excellent tool for communication and entertainment. In addition, developers can utilize Mux and Nuxt.js modular architecture to easily create and manage live streams as well as building highly customizable and engaging video experiences.
## Resources
You may find the following resources useful:
- [Nuxt.js Documentation](https://nuxt.com/docs)
- [Mux Documentation](https://docs.mux.com/)
- [RTMP](https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol)
- [FFmpeg](https://ffmpeg.org/ffmpeg.html)