-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Live VP9 WebM streaming via Icecast2 using Plyr.js #2864
dosgr
started this conversation in
Show and tell
-
Hi everyone,
We’re broadcasting a live 1080p/2K WebM stream using Icecast2 and Plyr.js for the player. This setup is fully browser-native (VP9 / WebM), plugin-free, and works across modern browsers.
Here’s a minimal example showing just the player:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebM Stream Player</title>
<!-- Plyr CSS from CDN -->
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
<style>
body {
margin: 0;
padding: 0;
background: #000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.video-wrapper {
position: relative;
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding-bottom: 56.25%;
background: #000;
}
.plyr {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.status-message {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 1000;
font-size: 14px;
border-left: 4px solid #3b82f6;
transition: opacity 0.3s;
}
.play-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
cursor: pointer;
}
.play-button {
background: #3b82f6;
color: white;
border: none;
padding: 15px 30px;
border-radius: 8px;
font-size: 18px;
cursor: pointer;
transition: background 0.3s;
}
.play-button:hover {
background: #2563eb;
}
.loading-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 3px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top-color: #3b82f6;
animation: spin 1s ease-in-out infinite;
z-index: 999;
}
@keyframes spin {
to { transform: translate(-50%, -50%) rotate(360deg); }
}
</style>
</head>
<body>
<div class="video-wrapper">
<video id="player"
playsinline
controls
autoplay
muted
preload="auto"
crossorigin="anonymous">
<source src="https://rdst.win:59000/dos.webm" type="video/webm">
Your browser does not support WebM video streaming.
</video>
</div>
<!-- Plyr JS from CDN -->
<script src="https://cdn.plyr.io/3.7.8/plyr.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const video = document.getElementById('player');
const videoWrapper = document.querySelector('.video-wrapper');
// Initialize Plyr player
const player = new Plyr('#player', {
muted: true,
autoplay: true,
hideControls: false,
controls: [
'play-large',
'play',
'progress',
'current-time',
'mute',
'volume',
'fullscreen'
],
ratio: '16:9'
});
// Show status message
function showStatusMessage(text, type = 'info') {
const existingMessage = videoWrapper.querySelector('.status-message');
if (existingMessage) existingMessage.remove();
const message = document.createElement('div');
message.className = 'status-message';
message.textContent = text;
if (type === 'error') {
message.style.borderLeftColor = '#ef4444';
} else if (type === 'success') {
message.style.borderLeftColor = '#22c55e';
}
videoWrapper.appendChild(message);
setTimeout(() => {
if (message.parentNode) {
message.style.opacity = '0';
setTimeout(() => message.remove(), 300);
}
}, 4000);
}
// Show loading spinner
function showLoadingSpinner() {
const existingSpinner = videoWrapper.querySelector('.loading-spinner');
if (existingSpinner) existingSpinner.remove();
const spinner = document.createElement('div');
spinner.className = 'loading-spinner';
videoWrapper.appendChild(spinner);
return spinner;
}
// Hide loading spinner
function hideLoadingSpinner() {
const spinner = videoWrapper.querySelector('.loading-spinner');
if (spinner) spinner.remove();
}
// Show play button overlay
function showPlayButton() {
const existingOverlay = videoWrapper.querySelector('.play-overlay');
if (existingOverlay) existingOverlay.remove();
const overlay = document.createElement('div');
overlay.className = 'play-overlay';
overlay.innerHTML = `
<button class="play-button">
▶️ Play Stream
</button>
`;
overlay.querySelector('.play-button').addEventListener('click', async function() {
overlay.remove();
showLoadingSpinner();
showStatusMessage('🔄 Connecting to stream...');
try {
await video.play();
showStatusMessage('🟢 Stream connected successfully!', 'success');
} catch (err) {
showStatusMessage('❌ Failed to play stream', 'error');
showPlayButton();
} finally {
hideLoadingSpinner();
}
});
videoWrapper.appendChild(overlay);
}
// Attempt autoplay
async function attemptAutoplay() {
showLoadingSpinner();
showStatusMessage('🔄 Attempting autoplay...');
try {
await new Promise(resolve => setTimeout(resolve, 1000));
await video.play();
showStatusMessage('🟢 Live stream connected!', 'success');
hideLoadingSpinner();
return true;
} catch (err) {
hideLoadingSpinner();
showStatusMessage('🔇 Click play to start stream');
showPlayButton();
return false;
}
}
// Video event listeners
video.addEventListener('loadstart', () => {
showStatusMessage('📥 Loading stream...');
});
video.addEventListener('canplay', () => {
hideLoadingSpinner();
});
video.addEventListener('waiting', () => {
showStatusMessage('⏳ Buffering...');
});
video.addEventListener('playing', () => {
showStatusMessage('🟢 Live stream playing', 'success');
hideLoadingSpinner();
});
video.addEventListener('error', (e) => {
hideLoadingSpinner();
showStatusMessage('❌ Stream error - Click play to retry', 'error');
showPlayButton();
});
// Initialize
setTimeout(attemptAutoplay, 500);
// Click fallback for autoplay
document.addEventListener('click', async () => {
if (video.paused) {
try {
await video.play();
} catch (err) {
console.log('Click-triggered play failed:', err);
}
}
}, { once: true });
});
</script>
</body>
</html>
Live stream preview URL: https://webm.win
Feedback and discussion are welcome!
Note:
This page demonstrates a live WebM stream via Icecast (VP9 video with Vorbis audio, fully playable in modern browsers using the HTML5 video element) and provides instructions for anyone interested in setting up similar broadcasting.
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment