DEV Community

truongductri01
truongductri01

Posted on

A Simple streaming server/client application

Have you ever wonder how a streaming company display the video for you to watch in such a show amount of time?

Everything seems to happen immediately when you open a video on YouTube or Netflix, right? And you can just keep watching until the video finishes.

How did they do that?

My naive thought:

The company can just simply download the whole video in term of bytes, just like you fetch data using http request and display it on the screen, right?

Not really. This could work for a short video of seconds, but how about video of tens of minutes or even hours?

Real solution

Summarizing the idea explained in Netflix System Design, the video when stored in the database will be split up into small chunks. The chunk will usually include the data of 10 seconds of the video, YouTube may store just 5 seconds of the video in their chunks. This can depend on how many seconds they allow you to skip while watching a video.

So, when you start a video, a small chunk of 5 seconds will be downloaded for you to see. After that, whenever that downloaded chunk is being viewed, the client will download the next chunk and save it to be ready to display when you finish watching your current chunk.

Let's look at that in a detail coding example

The backed will be coded in Node.js and client will simply be HTML and JavaScript

Backend:

// movieDb.js
function generateData(desireSize, chunkSize) {
    let result = [];
    let tempo = [];

    for (let i = 1; i <= desireSize; i++) {
        if (tempo.length === chunkSize) {
            result.push(tempo);
            tempo = [i];
        } else {
            tempo.push(i);
        }
    }

    if (tempo.length > 0) {
        result.push(tempo);
    }

    return result;
}

const movieDb = {
    "Mission Impossible": {
        data: generateData(100, 10),
    },
};

export default movieDb;
Enter fullscreen mode Exit fullscreen mode

The movieDb represents the database of a streaming company. For example, there is a movie called "Mission Impossible" with a data contains 10 small chunks of size 10.

// server.js
import express from "express";
import cors from "cors";
import movieDb from "./movieDb.js";

const app = express();

app.use(express.json());
app.use(cors());

app.get("/", (req, res) => {
    res.send("Hello world");
});

function responseBody(data, errMsg, statusCode) {
    return {
        data,
        errMsg,
        statusCode,
    };
}

app.get("/movie-info/:movieName", (req, res) => {
    let { movieName } = req.params;
    console.log(movieDb[movieName].data.length);
    res.json(responseBody(movieDb[movieName].data.length, null, 200));
});

app.get("/movie/:movieName/:chunkIndex", (req, res) => {
    let { movieName, chunkIndex } = req.params;
    console.log(movieName, chunkIndex);
    if (!movieDb[movieName]) {
        res.json(responseBody(null, "Not a valid movie name", 404));
    } else {
        let { data } = movieDb[movieName];
        if (!data[chunkIndex]) {
            res.json(
                responseBody(
                    null,
                    "Not a valid chunk index for: " + movieName,
                    404
                )
            );
        } else {
            res.json(responseBody(data[chunkIndex], "", 200));
        }
    }
});
const port = 8080;

app.listen(port, () => {
    console.log("Listening to", port);
});
Enter fullscreen mode Exit fullscreen mode

The server.js just simply set up the server allowing client to get the information related to a specific movie and download needed chunk data of that movie.

Frontend

Now is the fun part, the frontend.

let disPlayDiv = document.getElementById("display-div");
let movieState = {
    movieName: "Mission Impossible",
    totalChunks: null,
    data: [],
    currentChunkIdx: 0,
    nextChunkIdx: 1,
};
let url = "http://localhost:8080";

// get the initial data
async function getInitialMovieData() {
    // create an element saying loading in the frontend
    let totalChunkSize = await fetch(
        url + "/movie-info" + "/" + movieState.movieName
    )
        .then((result) => {
            console.log(result);
            if (result.ok) {
                return result.json();
            }
        })
        .then((data) => {
            console.log(data);
            if (data.data) {
                return data.data;
            }
        });
    movieState.totalChunks = totalChunkSize;

    // query the initial chunk also
    let chunkData = await downloadData(movieState.currentChunkIdx);
    movieState.data.push(chunkData);

    console.log(movieState);
}

async function downloadData(index) {
    if (movieState.data[index]) {
        return movieState.data[index];
    }
    return await fetch(
        url + "/movie" + "/" + movieState.movieName + "/" + index
    )
        .then((result) => result.json())
        .then((data) => data.data);
}

async function startMovie() {
    let { data, currentChunkIdx, totalChunks } = movieState;
    if (currentChunkIdx >= totalChunks) {
        return;
    } else if (data[currentChunkIdx]) {
        console.log(data[currentChunkIdx]);
        movieState.currentChunkIdx = movieState.nextChunkIdx;
        movieState.nextChunkIdx += 1;
        setTimeout(startMovie, 1000);
    } else {
        let chunkData = await downloadData(currentChunkIdx);
        data[currentChunkIdx] = chunkData;
        await startMovie();
    }
}

// get the movie total number of chunks
async function main() {
    await getInitialMovieData();
    startMovie();
}

main();
Enter fullscreen mode Exit fullscreen mode

Conclusion

So that covers the simple explanation of how to stream a video to the client.

You can read the article Netflix System Design for a deeper understanding

Top comments (0)