one entry
This commit is contained in:
151
content/maker-portfolio/light-weight-video-streaming.md
Normal file
151
content/maker-portfolio/light-weight-video-streaming.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: Light Weight Video Streaming Server
|
||||
date: 2024-12-24
|
||||
---
|
||||
|
||||
> [!NOTE]
|
||||
> This is another installation in my *magnum opus*, "Random-Things-I-Make-for-Absolutely-no-Reason".
|
||||
|
||||
# The Problem
|
||||
|
||||
I've picked up watching portions of shows and dropping them on a whim, but generally don't want to stream them thanks to the network load caused by my other projects[^1]. This requires downloading and hosting the files myself, perfectly feasible given my resources.
|
||||
Hosting these files over the internet, however, would not be feasible due to the low outgoing upload speeds provided by my godforsaken ISP. The client, in cases would just be my phone or laptop. I will acknowledge, though, that options like Plex, Jellyfin, and others exist to manage almost the same requirements.
|
||||
The sole reason I've chosen not to go with these is the resource overhead demanded. I spun up an instance of Jellyfin to prove this point. At idle, $\ge0.5$ GB of memory was used alongside a not insignificant CPU/GPU allocation.
|
||||
Looking further into the processes involved, I found that servers like Jellyfin and Plex use the HSL video streaming paradigm, requiring server-side segmentation, encoding/decoding, and scaling. In my situation, bandwidth was an unlimited resource, so the overhead was unnecessary.
|
||||
|
||||
# The Solution
|
||||
|
||||
I really just had to write a script to present all the videos on a webserver in a drop-down list with a decent video player. Although there is nothing I hate more in the world than a Python network application, I'm too lazy to do anything else.
|
||||
|
||||
Flask python API/`html` proxy (`server.py`):
|
||||
|
||||
```python
|
||||
from flask import Flask, jsonify, send_from_directory, render_template
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Get the current working directory (where the Python script is run from)
|
||||
video_dir = os.getcwd() # Ensure this is where your .mp4 files and index.html are located
|
||||
|
||||
@app.route('/')
|
||||
def serve_index():
|
||||
"""Serve the index.html file."""
|
||||
return send_from_directory(video_dir, 'index.html')
|
||||
|
||||
@app.route('/videos')
|
||||
def list_videos():
|
||||
"""Return a list of .mp4 files in the directory."""
|
||||
files = [f for f in os.listdir(video_dir) if f.endswith('.mp4')]
|
||||
return jsonify(files)
|
||||
|
||||
@app.route('/videos/<filename>')
|
||||
def serve_file(filename):
|
||||
"""Serve video files to the browser from the videos directory."""
|
||||
return send_from_directory(video_dir, filename)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8080)
|
||||
|
||||
```
|
||||
|
||||
And the `html` front-end (`index.html`):
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>4K Video Player</title>
|
||||
<link href="https://unpkg.com/video.js/dist/video-js.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f4f4f9;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.video-container {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>4K Video Player</h1>
|
||||
<select id="videoList" onchange="loadVideo(this.value)">
|
||||
<option value="">Select a video</option>
|
||||
</select>
|
||||
<div class="video-container">
|
||||
<video id="player" class="video-js vjs-default-skin" controls>
|
||||
<source id="videoSource" src="" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/video.js/dist/video.min.js"></script>
|
||||
<script>
|
||||
var player = videojs('player', {
|
||||
controls: true,
|
||||
autoplay: false,
|
||||
preload: 'auto',
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
poster: 'path/to/your/poster-image.jpg', // Optional: add a thumbnail image for video preview
|
||||
fluid: true, // Ensures responsive video sizing
|
||||
playbackRates: [0.5, 1, 1.5, 2], // Allow the user to control playback speed
|
||||
controlBar: {
|
||||
playToggle: true,
|
||||
volumePanel: { inline: false }, // Displays volume control as a separate panel
|
||||
currentTimeDisplay: true,
|
||||
timeDivider: true,
|
||||
durationDisplay: true,
|
||||
remainingTimeDisplay: true,
|
||||
fullscreenToggle: true
|
||||
},
|
||||
sources: [{
|
||||
type: 'video/mp4',
|
||||
src: ''
|
||||
}]
|
||||
});
|
||||
|
||||
// Populate video dropdown list
|
||||
const videoList = document.getElementById('videoList');
|
||||
|
||||
fetch('/videos')
|
||||
.then(response => response.json())
|
||||
.then(files => {
|
||||
files.forEach(file => {
|
||||
const option = document.createElement('option');
|
||||
option.value = file;
|
||||
option.textContent = file;
|
||||
videoList.appendChild(option);
|
||||
});
|
||||
});
|
||||
|
||||
// Load the selected video into the player
|
||||
function loadVideo(src) {
|
||||
if (!src) return; // If no video selected, do nothing
|
||||
const videoSource = document.getElementById('videoSource');
|
||||
videoSource.src = '/videos/' + src.replace('.mkv', '.mp4'); // Set video source path
|
||||
|
||||
player.src({ type: 'video/mp4', src: '/videos/' + src.replace('.mkv', '.mp4') });
|
||||
player.play(); // Start playback automatically after video is loaded
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
Resulting in:
|
||||

|
||||
|
||||
|
||||
[^1]: I will write these up here at some point.
|
||||
BIN
content/maker-portfolio/maker-portfolio-1.png
Normal file
BIN
content/maker-portfolio/maker-portfolio-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
content/maker-portfolio/maker-portfolio-2.png
Normal file
BIN
content/maker-portfolio/maker-portfolio-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Reference in New Issue
Block a user