live / index.html
vsmdvic's picture
Rename index-6.html to index.html
7b4e809 verified
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>STREAM</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
background: #000;
width: 100vw; height: 100vh;
overflow: hidden;
display: flex; align-items: center; justify-content: center;
}
#viewerVideo {
display: block;
max-width: 100vw; max-height: 100vh;
background: #000;
object-fit: contain;
}
.offline {
position: fixed; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: 16px; text-align: center;
background: #000;
}
.offline-ring {
width: 64px; height: 64px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,0.08);
display: flex; align-items: center; justify-content: center;
position: relative;
}
.offline-ring::before {
content: '';
position: absolute; inset: -10px; border-radius: 50%;
border: 1px solid rgba(255,255,255,0.04);
animation: rip 3s ease infinite;
}
@keyframes rip { 0%,100%{transform:scale(1);opacity:.5} 50%{transform:scale(1.12);opacity:.1} }
.offline h2 {
font-family: 'Helvetica Neue', sans-serif;
font-size: 13px; font-weight: 400;
letter-spacing: 5px;
color: rgba(255,255,255,0.2);
text-transform: uppercase;
}
.badge-live {
position: fixed; top: 18px; left: 18px;
display: none; align-items: center; gap: 7px;
padding: 5px 13px;
background: rgba(0,0,0,0.7);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,30,30,0.35);
border-radius: 3px;
font-family: 'Helvetica Neue', sans-serif;
font-size: 10px; font-weight: 500;
color: #ff2020; letter-spacing: 3px;
}
.badge-live.on { display: flex; }
.dot { width: 6px; height: 6px; border-radius: 50%; background: #ff2020; box-shadow: 0 0 7px #ff2020; animation: blink 1s infinite; }
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:.2} }
</style>
</head>
<body>
<video id="viewerVideo" autoplay playsinline></video>
<div class="offline" id="offline">
<div class="offline-ring">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.18)" stroke-width="1.5">
<circle cx="12" cy="12" r="10"/>
<polygon points="10,8 16,12 10,16" fill="rgba(255,255,255,0.07)" stroke="none"/>
</svg>
</div>
<h2>Offline</h2>
</div>
<div class="badge-live" id="badgeLive"><div class="dot"></div>LIVE</div>
<script>
const WS = (() => { const p = location.protocol === 'https:' ? 'wss' : 'ws'; return `${p}://${location.host}`; })();
const ICE = [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' }];
let ws, myVid, pc;
function connect() {
ws = new WebSocket(WS);
ws.onopen = () => ws.send(JSON.stringify({ type: 'viewer_join' }));
ws.onmessage = e => handle(JSON.parse(e.data));
ws.onclose = () => setTimeout(connect, 3000);
}
async function handle(msg) {
if (msg.type === 'viewer_welcome') {
myVid = msg.vid;
if (msg.isLive) goLive();
}
if (msg.type === 'stream_started') goLive();
if (msg.type === 'stream_ended') goOffline();
if (msg.type === 'offer') await doOffer(msg.sdp);
if (msg.type === 'candidate' && pc) pc.addIceCandidate(new RTCIceCandidate(msg.candidate));
}
function goLive() {
document.getElementById('offline').style.display = 'none';
document.getElementById('badgeLive').classList.add('on');
ws.send(JSON.stringify({ type: 'viewer_ready', vid: myVid }));
}
function goOffline() {
document.getElementById('offline').style.display = 'flex';
document.getElementById('badgeLive').classList.remove('on');
document.getElementById('viewerVideo').srcObject = null;
if (pc) { pc.close(); pc = null; }
}
async function doOffer(sdp) {
pc = new RTCPeerConnection({ iceServers: ICE });
pc.ontrack = e => {
const vid = document.getElementById('viewerVideo');
vid.srcObject = e.streams[0];
const track = e.streams[0].getVideoTracks()[0];
if (track) {
const s = track.getSettings();
if (s.height > s.width) {
vid.style.width = 'auto'; vid.style.height = '100vh';
} else {
vid.style.width = '100vw'; vid.style.height = 'auto';
}
}
};
pc.onicecandidate = e => {
if (e.candidate) ws.send(JSON.stringify({ type: 'candidate', candidate: e.candidate }));
};
await pc.setRemoteDescription(new RTCSessionDescription(sdp));
const ans = await pc.createAnswer();
await pc.setLocalDescription(ans);
ws.send(JSON.stringify({ type: 'answer', sdp: pc.localDescription }));
}
connect();
</script>
</body>
</html>