Improve video streaming with dynamic quality adjustments

Integrate adaptive bitrate streaming using HLS.js, optimizing playback based on network conditions and buffer levels for a smoother user experience. Add a quality indicator component.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 11420304-80a9-4ef2-adff-cbdaa418ffa8
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/11420304-80a9-4ef2-adff-cbdaa418ffa8/xcLcvwj
This commit is contained in:
sebastjanartic 2025-08-07 09:09:02 +00:00
parent 9d55c83811
commit 3b648347cd
3 changed files with 166 additions and 9 deletions

View File

@ -0,0 +1,85 @@
import { useState, useEffect } from "react";
import { Signal } from "lucide-react";
interface QualityIndicatorProps {
hlsInstance: any;
className?: string;
}
export default function QualityIndicator({ hlsInstance, className = "" }: QualityIndicatorProps) {
const [currentQuality, setCurrentQuality] = useState<string>("");
const [networkType, setNetworkType] = useState<string>("");
const [bufferHealth, setBufferHealth] = useState<"good" | "medium" | "poor">("good");
useEffect(() => {
if (!hlsInstance) return;
// Monitor quality changes
const handleLevelSwitch = (event: any, data: any) => {
const level = hlsInstance.levels[data.level];
setCurrentQuality(`${level.height}p`);
};
// Monitor buffer health
const handleBufferAppending = () => {
const video = hlsInstance.media;
if (video && video.buffered.length > 0) {
const bufferLevel = video.buffered.end(video.buffered.length - 1) - video.currentTime;
if (bufferLevel > 10) {
setBufferHealth("good");
} else if (bufferLevel > 3) {
setBufferHealth("medium");
} else {
setBufferHealth("poor");
}
}
};
hlsInstance.on('hlsLevelSwitched', handleLevelSwitch);
hlsInstance.on('hlsBufferAppending', handleBufferAppending);
// Detect network connection
const connection = (navigator as any).connection;
if (connection) {
setNetworkType(connection.effectiveType || "unknown");
const handleConnectionChange = () => {
setNetworkType(connection.effectiveType || "unknown");
};
connection.addEventListener('change', handleConnectionChange);
return () => {
connection.removeEventListener('change', handleConnectionChange);
};
}
return () => {
if (hlsInstance) {
hlsInstance.off('hlsLevelSwitched', handleLevelSwitch);
hlsInstance.off('hlsBufferAppending', handleBufferAppending);
}
};
}, [hlsInstance]);
const getSignalColor = () => {
switch (bufferHealth) {
case "good": return "text-green-500";
case "medium": return "text-yellow-500";
case "poor": return "text-red-500";
default: return "text-gray-500";
}
};
if (!currentQuality) return null;
return (
<div className={`flex items-center gap-2 text-sm text-white bg-black/50 px-2 py-1 rounded ${className}`}>
<Signal className={`w-4 h-4 ${getSignalColor()}`} />
<span>{currentQuality}</span>
{networkType && (
<span className="text-xs opacity-75">({networkType})</span>
)}
</div>
);
}

View File

@ -60,7 +60,12 @@ export default function ThumbnailGenerator({
const hls = new Hls({
debug: false,
enableWorker: false,
lowLatencyMode: false
lowLatencyMode: false,
// Optimized for thumbnail generation - prioritize speed
startLevel: 0, // Start with lowest quality for faster loading
maxBufferLength: 10, // Smaller buffer for thumbnail generation
fragLoadingTimeOut: 10000,
manifestLoadingTimeOut: 5000
});
hls.loadSource(videoUrl);

View File

@ -10,6 +10,7 @@ import {
WhatsappIcon
} from "react-share";
import VideoEditModal from "./video-edit-modal";
import QualityIndicator from "./quality-indicator";
// HLS.js types for video streaming
@ -100,31 +101,89 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
const hls = new Hls({
debug: false,
enableWorker: false,
lowLatencyMode: true,
backBufferLength: 90
lowLatencyMode: false,
// Adaptive bitrate settings for optimal streaming
startLevel: -1, // Auto-select starting quality based on bandwidth
capLevelToPlayerSize: true, // Limit quality to actual player size
maxLoadingDelay: 4,
maxBufferLength: 30, // Keep 30 seconds buffered
maxBufferSize: 60 * 1000 * 1000, // 60MB buffer
maxBufferHole: 0.5,
// Network adaptive settings
abrEwmaFastLive: 3,
abrEwmaSlowLive: 9,
abrEwmaFastVoD: 3,
abrEwmaSlowVoD: 9,
abrMaxWithRealBitrate: false,
abrBandWidthFactor: 0.95, // Conservative bandwidth usage
abrBandWidthUpFactor: 0.7, // Slower quality upgrades
// Fragment loading settings
fragLoadingTimeOut: 20000,
manifestLoadingTimeOut: 10000,
levelLoadingTimeOut: 10000,
// Start with lower quality for faster initial load
testBandwidth: false
});
hls.loadSource(videoUrl);
hls.attachMedia(videoElement);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
console.log('HLS manifest loaded successfully');
hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
console.log('HLS manifest loaded - Available qualities:',
data.levels.map(l => `${l.height}p @ ${Math.round(l.bitrate/1000)}kbps`));
// Set initial quality based on connection
const connection = (navigator as any).connection;
if (connection) {
const effectiveType = connection.effectiveType;
console.log('Network type detected:', effectiveType);
// Adjust starting level based on network
if (effectiveType === 'slow-2g' || effectiveType === '2g') {
hls.startLevel = 0; // Start with lowest quality
} else if (effectiveType === '3g') {
hls.startLevel = Math.min(1, data.levels.length - 1);
}
}
});
// Quality level monitoring
hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
const level = hls.levels[data.level];
console.log(`Kakovost preklopljena na: ${level.height}p @ ${Math.round(level.bitrate/1000)}kbps`);
});
// Buffer monitoring for dynamic adjustment
hls.on(Hls.Events.BUFFER_APPENDING, () => {
const buffered = videoElement.buffered;
if (buffered.length > 0) {
const bufferLevel = buffered.end(buffered.length - 1) - videoElement.currentTime;
if (bufferLevel < 2) {
console.log('Nizek buffer zaznan, lahko zmanjšam kakovost');
}
}
});
// Network error handling with retries
hls.on(Hls.Events.ERROR, (event, data) => {
console.error('HLS error:', data);
console.error('HLS napaka:', data);
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log('Network error, trying to recover...');
console.log('Omrežna napaka, poskušam obnoviti...');
// Try to downgrade quality first
if (hls.currentLevel > 0) {
hls.currentLevel = hls.currentLevel - 1;
console.log('Zmanjšujem kakovost zaradi omrežnih težav');
}
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log('Media error, trying to recover...');
console.log('Medijska napaka, poskušam obnoviti...');
hls.recoverMediaError();
break;
default:
console.log('Fatal error, destroying HLS instance...');
console.log('Kritična napaka, uničujem HLS instanco...');
hls.destroy();
break;
}
@ -274,6 +333,14 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
Your browser does not support the video tag.
</video>
{/* Quality Indicator */}
{hlsRef.current && (
<QualityIndicator
hlsInstance={hlsRef.current}
className="absolute top-4 left-4"
/>
)}
{/* Video Controls and Share Menu */}
<div className="absolute top-4 right-4 flex gap-2">
{/* Edit Button */}