|
@ -0,0 +1,83 @@ |
|
|
|
|
|
Two years ago, I created a video streaming server from scratch in Node; looking back, I realized that I must have had way too much free time. |
|
|
|
|
|
I wanted something that I could use to embed videos in websites and aggregate all of my public videos. |
|
|
|
|
|
In the end, I created a lightweight node application with an administration interface, API tokens, and the ability to stream videos. |
|
|
|
|
|
Now, I can embed videos in my blog like this: |
|
|
|
|
|
|
|
|
|
|
|
<customHTML /> |
|
|
|
|
|
|
|
|
|
|
|
Although I could have accomplished the same thing with zero effort by using Youtube-- that's now how I roll. |
|
|
|
|
|
I wanted to get better at programming with Node, and I wanted a self-hostable lightweight open-source project. |
|
|
|
|
|
|
|
|
|
|
|
Let's take a quick look at the website I ended up building: |
|
|
|
|
|
|
|
|
|
|
|
![](media/diy-video-hosting/video3.PNG) |
|
|
|
|
|
|
|
|
|
|
|
The videos project's main page is a gallery that displays all of the videos on the same page with a search bar at the top for filtering. |
|
|
|
|
|
What is notable about this page is that all of the icons displayed are gifs generated by the backend using [ffmpeg](https://ffmpeg.org/). |
|
|
|
|
|
After you click on a video, it takes you to a separate page to watch the video or download it. |
|
|
|
|
|
Additionally, the video page lets you copy a streamable link to the video so you can embed it in a website or open it in VLC. |
|
|
|
|
|
|
|
|
|
|
|
The next notable feature of the website is the user management and privilege aspect of the system. |
|
|
|
|
|
Regular users can log into the website and watch non-listed videos. |
|
|
|
|
|
Additionally, administrators can manage other users and generate API tokens. |
|
|
|
|
|
API tokens get used to generate video share links to watch a non-listed video without log-into the website. |
|
|
|
|
|
|
|
|
|
|
|
![](media/diy-video-hosting/video2.PNG) |
|
|
|
|
|
|
|
|
|
|
|
There is also a page listed "System Controls." |
|
|
|
|
|
The system page enables admin users to update some environment variables like where the videos are located and the server's URL. |
|
|
|
|
|
The server URL gets used when generating share links for end-users to copy and share. |
|
|
|
|
|
The re-index button tells the system to scan for new videos and generate icons. |
|
|
|
|
|
|
|
|
|
|
|
![](media/diy-video-hosting/video1.PNG) |
|
|
|
|
|
|
|
|
|
|
|
Being able to download a full video is straightforward since you just serve the entire file. |
|
|
|
|
|
However, to stream a video, you have to send it to the client chunk by chunk, and which chunk gets downloaded gets determined by the end client-- enabling things like buffering and skipping forward. |
|
|
|
|
|
The following code snippet is the core node code that I ended up using to provide this functionality. |
|
|
|
|
|
The client sends a byte array indicating the range in which they want to download. |
|
|
|
|
|
The server then responds by reading that portion of the video file and sending it to the client. |
|
|
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
|
const parts = request.headers.range.replace(/bytes=/, "").split("-"); |
|
|
|
|
|
const start = parseInt(parts[0], 10); |
|
|
|
|
|
const end = parts[1] |
|
|
|
|
|
? parseInt(parts[1], 10) |
|
|
|
|
|
: fileSize-1; |
|
|
|
|
|
|
|
|
|
|
|
const chunksize = (end-start)+1; |
|
|
|
|
|
const file = fs.createReadStream(path, {start, end}); |
|
|
|
|
|
const head = |
|
|
|
|
|
{ |
|
|
|
|
|
'Content-Range': `bytes ${start}-${end}/${fileSize}`, |
|
|
|
|
|
'Accept-Ranges': 'bytes', |
|
|
|
|
|
'Content-Length': chunksize, |
|
|
|
|
|
'Content-Type': 'video/mp4', |
|
|
|
|
|
}; |
|
|
|
|
|
result.writeHead(206, head); |
|
|
|
|
|
file.pipe(result); |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
Another fun tidbit from this project is that I just created a Docker script to run the project. |
|
|
|
|
|
The Docker container gets derived from a generic node container, and then I installed the ffmpeg and gifski packages-- enabling me to generate the video icons. |
|
|
|
|
|
|
|
|
|
|
|
```bash |
|
|
|
|
|
FROM node:buster-slim |
|
|
|
|
|
|
|
|
|
|
|
COPY package.json package.json |
|
|
|
|
|
|
|
|
|
|
|
RUN apt-get update && \ |
|
|
|
|
|
apt-get install ffmpeg -y && \ |
|
|
|
|
|
apt-get install wget -y && \ |
|
|
|
|
|
cd /root && \ |
|
|
|
|
|
wget https://github.com/jrtechs/static-storage/raw/master/gifski.deb && \ |
|
|
|
|
|
dpkg -i /root/gifski.deb && \ |
|
|
|
|
|
rm /root/gifski.deb |
|
|
|
|
|
|
|
|
|
|
|
RUN npm install |
|
|
|
|
|
|
|
|
|
|
|
EXPOSE 4000 |
|
|
|
|
|
WORKDIR /src/ |
|
|
|
|
|
CMD npm start |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
Although I don't use this project a lot, I maintain it since it is effortless to host and allows me to share videos without using a centralized service like YouTube. If you are interested in the project, check it out on [Github](https://github.com/jrtechs/HomeBrewPlex) and contribute by submitting issues and creating pull requests. |