Spaces:
Sleeping
Sleeping
<html> | |
<head> | |
<title>V-Pod</title> | |
<link rel="stylesheet" href="index.css"> | |
<script src="/mpegts.js"></script> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
</head> | |
<body> | |
<div | |
class="h-screen w-auto grid place-content-center place-items-center overflow-hidden bg-gradient-to-b from-slate-900 to-black" | |
> | |
<div x-data="app()" x-init="init()" class="container mx-auto"> | |
<div class="flex w-full pt-16"> | |
<video | |
id="videoElement" | |
class="aspect-video mx-auto w-auto border-4 border-slate-900/10 rounded-full" | |
preload="auto" | |
muted | |
autoplay | |
></video> | |
</div> | |
<div class="inset-x-0 bottom-0 pb-4 z-10"> | |
<div class="container mx-auto px-2 opacity-85"> | |
<div class="flex items-center justify-between"> | |
<div | |
class="flex items-center space-x-4 text-xs focus:cursor-pointer" | |
> | |
<template x-for="(chan, index) in channels"> | |
<div | |
class="text-sm capitalize truncate mr-2" | |
:class="chan.id === channel.id ? 'font-semibold cursor-pointer text-white' : 'text-slate-100 cursor-pointer hover:underline'" | |
@click="window.location = `${window.location.origin}/?channel=${chan.id}`" | |
x-text="chan.label" | |
> | |
<span | |
class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-blue-500 opacity-75" | |
></span> | |
</div> | |
</template> | |
</div> | |
<div class="flex-col justify-center space-y-4 items-center"> | |
<div class="flex items-center justify-center space-x-1 text-lg"> | |
<span>🔴</span> | |
<a class="text-white font-bold" x-text="'LIVE'"></a> | |
</div> | |
<div | |
class="flex items-center justify-center text-white opacity-80 hover:opacity-100 cursor-pointer" | |
x-on:click="toggleAudio()" | |
> | |
<span x-show="muted">🔇</span> | |
<span x-show="!muted">🔈</span> | |
</div> | |
<div | |
class="flex items-center justify-center text-white hover:text-white opacity-80 hover:opacity-100 cursor-pointer" | |
> | |
<a href="https://vgony.tech"> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
class="h-6 w-6" | |
fill="none" | |
viewBox="0 0 24 24" | |
stroke="currentColor" | |
> | |
<path | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" | |
/> | |
</svg> | |
</a> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div | |
id="logo_container" | |
class="relative grid place-content-center place-items-center gap-2 before:bg-gradient-to-t before:from-teal-500/70 before:via-fuchsia-600 before:to-transparent before:blur-xl before:filter"> | |
> | |
<h1 class="title text-6xl font-black text-teal-300">VGФЙЧ</h1> | |
<h2 class="cursive text-6xl font-thin text-fuchsia-500">ҒФЯԐVԐЯ</h2> | |
</div> | |
</div> | |
</div> | |
<script> | |
// disable analytics (we don't use VideoJS yet anyway) | |
window.HELP_IMPROVE_VIDEOJS = false; | |
</script> | |
<script | |
defer | |
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" | |
></script> | |
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.contentWindow.min.js"></script> | |
<!--<script src="https://vjs.zencdn.net/8.3.0/video.min.js"></script>--> | |
<script> | |
function app() { | |
return { | |
enabled: false, | |
channels: { | |
/* | |
legacy: { | |
id: 'legacy', | |
label: '#older', | |
audience: 0, | |
online: false, | |
visible: false, | |
url: 'https://jbilcke-hf-media-server.hf.space/live/legacy.flv', | |
resolution: '576x320', | |
model: 'zeroscope_v2_576w', | |
modelUrl: 'https://huggingface.co/cerspense/zeroscope_v2_576w', | |
}, | |
*/ | |
/* | |
hdtv: { | |
id: 'hdtv', | |
label: '#old', | |
audience: 0, | |
online: false, | |
visible: true, | |
url: 'https://jbilcke-hf-media-server.hf.space/live/hdtv.flv', | |
resolution: '1024x576_8FPS', | |
model: 'zeroscope_v2_XL', | |
modelUrl: 'https://huggingface.co/cerspense/zeroscope_v2_XL', | |
}, | |
*/ | |
random: { | |
id: "random", | |
label: "#random", | |
audience: 0, | |
online: false, | |
visible: true, | |
url: "https://jbilcke-hf-media-server.hf.space/live/random.flv", | |
resolution: "1024x576_24FPS", | |
model: "zeroscope_v2_XL", | |
modelUrl: "https://huggingface.co/cerspense/zeroscope_v2_XL", | |
}, | |
comedy: { | |
id: "comedy", | |
label: "#comedy", | |
audience: 0, | |
online: false, | |
visible: true, | |
url: "https://jbilcke-hf-media-server.hf.space/live/comedy.flv", | |
resolution: "1024x576_24FPS", | |
model: "zeroscope_v2_XL", | |
modelUrl: "https://huggingface.co/cerspense/zeroscope_v2_XL", | |
}, | |
documentary: { | |
id: "documentary", | |
label: "#documentary", | |
audience: 0, | |
online: false, | |
visible: true, | |
url: "https://jbilcke-hf-media-server.hf.space/live/documentary.flv", | |
resolution: "1024x576_24FPS", | |
model: "zeroscope_v2_XL", | |
modelUrl: "https://huggingface.co/cerspense/zeroscope_v2_XL", | |
}, | |
}, | |
muted: true, | |
initialized: false, | |
activityTimeout: null, | |
defaultChannelId: "random", | |
video: null, | |
channel: {}, | |
wakeUp() { | |
this.showToolbar = true; | |
}, | |
toggleAudio() { | |
if (this.video.muted) { | |
this.video.muted = false; | |
this.muted = false; | |
} else { | |
this.video.muted = true; | |
this.muted = true; | |
} | |
}, | |
async checkAudience() { | |
let audience = {}; | |
try { | |
const res = await fetch("/stats"); | |
audience = await res.json(); | |
} catch (err) { | |
console.log("failed to check the audience, something is wrong"); | |
} | |
window.DEBUGME = Object.entries(this.channels); | |
this.channels = Object.entries(this.channels).reduce( | |
(acc, [channel, data]) => ( | |
console.log("debug:", { | |
...data, | |
audience: audience[channel] || 0, | |
}), | |
{ | |
...acc, | |
[channel]: { | |
...data, | |
audience: audience[channel] || 0, | |
}, | |
} | |
), | |
{} | |
); | |
this.channel = this.channels[this.channel.id]; | |
}, | |
fullscreen() { | |
if (this.video.requestFullscreen) { | |
this.video.requestFullscreen(); | |
} else if (this.video.mozRequestFullScreen) { | |
this.video.mozRequestFullScreen(); | |
} else if (this.video.webkitRequestFullscreen) { | |
this.video.webkitRequestFullscreen(); | |
} else if (this.video.msRequestFullscreen) { | |
this.video.msRequestFullscreen(); | |
} | |
}, | |
init() { | |
if (this.initialized) { | |
console.log("already initialized"); | |
return; | |
} | |
this.initialized = true; | |
console.log("initializing.."); | |
const urlParams = new URLSearchParams(window.location.search); | |
const requestedChannelId = `${ | |
urlParams.get("channel") || "random" | |
}`; | |
this.enabled = true; | |
// this.enabled = `${urlParams.get('beta') || 'false'}` === 'true' | |
if (!this.enabled) { | |
return; | |
} | |
this.video = document.getElementById("videoElement"); | |
const defaultChannel = this.channels[this.defaultChannelId]; | |
this.channel = this.channels[requestedChannelId] || defaultChannel; | |
console.log(`Selected channel: ${this.channel.label}`); | |
console.log(`Stream URL: ${this.channel.url}`); | |
const handleActivity = () => { | |
this.wakeUp(); | |
}; | |
handleActivity(); | |
this.checkAudience(); | |
setInterval(() => { | |
this.checkAudience(); | |
}, 1000); | |
// detect mute/unmute events | |
this.video.addEventListener("mute", () => { | |
this.muted = true; | |
}); | |
this.video.addEventListener("unmute", () => { | |
this.muted = false; | |
}); | |
// as a bonus, we also allow fullscreen on double click | |
this.video.addEventListener("dblclick", () => { | |
this.fullscreen(); | |
}); | |
// some devices such as the iPhone don't support MSE Live Playback | |
if (mpegts.getFeatureList().mseLivePlayback) { | |
var player = mpegts.createPlayer({ | |
type: "flv", // could also be mpegts, m2ts, flv | |
isLive: true, | |
url: this.channel.url, | |
}); | |
player.attachMediaElement(this.video); | |
player.on(mpegts.Events.ERROR, function (err) { | |
console.log("got an error:", err); | |
if (err.type === mpegts.ErrorTypes.NETWORK_ERROR) { | |
console.log("Network error"); | |
} | |
}); | |
player.load(); | |
// due to an issue with our stream when the FFMPEG playlist ends, | |
// the stream gets interrupted for ~1sec, which causes the frontend to hangs up | |
// the following code tries to restart the page when that happens, but in the long term | |
// we should fix the issue on the server side (fix our FFMPEG bash script) | |
this.video.addEventListener( | |
"ended", | |
function () { | |
console.log("Stream ended, trying to reload..."); | |
setTimeout(() => { | |
console.log("Reloading the page.."); | |
// Unloading and loading the source again isn't enough it seems | |
// player.unload() | |
// player.load() | |
window.location.reload(); | |
}, 1200); | |
}, | |
false | |
); | |
// Handle autoplay restrictions. | |
let promise = this.video.play(); | |
if (promise !== undefined) { | |
this.video.addEventListener("click", function () { | |
this.video.play(); | |
}); | |
} | |
player.play(); | |
} | |
}, | |
}; | |
} | |
</script> | |
</body> | |
</html> | |