init: initial commit

This commit is contained in:
Himadri Bhattacharjee
2025-09-07 18:02:06 +05:30
commit 2bca07ea85
4 changed files with 221 additions and 0 deletions

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/lavafroth/okiiparty
go 1.24.5
require github.com/gorilla/websocket v1.5.3

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

131
index.html Normal file
View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html>
<head>
<title>Okiiparty</title>
<style>
* {padding:0; margin: 0}
body {background: black}
video {
width: 100%;
height: 100%;
object-fit: content;
}
.banner {
padding: 1rem;
margin: 1rem;
outline: .1rem solid #fefefe;
border-radius: 1rem;
backdrop-filter: blur(10px) brightness(60%);
box-sizing: border-box;
color: #eee;
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 4;
font-family: monospace;
font-size: 1rem;
abbr {
text-decoration: none;
}
}
</style>
</head>
<body>
<div class="banner">
<abbr title="Okiiparty - Made with ❤️ for pookie">The stream is muted because autoplay requires audio to be switched off. Please unmute the stream and I will disappear.</abbr>
</div>
<video muted controls>
<source src="/stream" type="video/mp4">
</video>
<script>
const loc = window.location;
let proto = loc.protocol == "https:" ? "wss:" : "ws:";
let host = loc.host;
let wsUri = `${proto}//${host}/broker`;
const socket = new WebSocket(wsUri);
const video = document.querySelector("video");
const banner = document.querySelector(".banner");
let requestedWait = false
function broadcastSeek() {
broadcast({
action: "seek",
time: video.currentTime
})
}
function broadcastPlay() {
broadcast({action: "play"})
}
function broadcastPause() {
console.log("you paused")
broadcast({action: "pause"})
}
function messageHandler(event) {
let message = JSON.parse(event.data)
if (message.action == "play") {
video.removeEventListener("play", broadcastPlay)
console.log("someone played")
video.play()
video.addEventListener("play", () => { video.addEventListener("play", broadcastPlay) })
}
if (message.action == "pause") {
video.removeEventListener("pause", broadcastPause)
console.log("someone paused")
video.pause()
video.addEventListener("pause", () => { video.addEventListener("pause", broadcastPause) })
}
if (message.action == "wait") {
if (!video.paused) video.pause()
video.controls = false
}
if (message.action == "canplay") {
video.controls = true
}
if (message.action == "seek") {
video.removeEventListener("seeked", broadcastSeek)
video.currentTime = message.time
// this one time, let the event not propagate
video.addEventListener("seeked", () => { video.addEventListener("seeked", broadcastSeek) })
}
}
socket.addEventListener("message", messageHandler)
function broadcast(message) {
socket.send(JSON.stringify(message))
}
video.addEventListener("volumechange", () => {
banner.style.display = "none";
})
video.addEventListener("play", broadcastPlay)
video.addEventListener("pause", broadcastPause)
video.addEventListener("waiting", () => {
requestedWait = true
broadcast({action: "wait"})
video.controls = false
})
video.addEventListener("canplay", () => {
if (!requestedWait) {
return
}
broadcast({action: "canplay"})
video.controls = true
requestedWait = false
})
video.addEventListener("seeked", broadcastSeek)
</script>
</body>
</html>

83
main.go Normal file
View File

@@ -0,0 +1,83 @@
package main
import (
"flag"
"fmt"
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 128,
WriteBufferSize: 128,
}
var broker Broker
type Broker struct {
sockets map[*websocket.Conn]struct{}
mutex sync.Mutex
}
func newStreamHandler(movieFile string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, movieFile)
}
}
func playPauseBroker(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("failed to fulfill client request for playPauseBroker: %v", err)
return
}
broker.mutex.Lock()
broker.sockets[conn] = struct{}{}
log.Printf("broker currently handles %d clients", len(broker.sockets))
broker.mutex.Unlock()
for {
messageType, p, err := conn.ReadMessage()
if err != nil {
log.Printf("failed to read message from client %p: %v", conn, err)
log.Printf("will attempt to close connection %p", conn)
if err := conn.Close(); err != nil {
log.Printf("failed to close connection at %p", conn)
}
delete(broker.sockets, conn)
return
}
for client := range broker.sockets {
if client == conn {
continue // prevent self echo
}
if err := client.WriteMessage(messageType, p); err != nil {
log.Printf("while sending: %s to client %p: %v", p, client, err)
}
log.Printf("successfully sent %s", p)
}
}
}
func main() {
listenPort := flag.Uint("port", 8000, "port to listen on")
flag.Parse()
streamFile := flag.Arg(0)
if streamFile == "" {
log.Fatal("stream file is undefined")
}
broker.sockets = make(map[*websocket.Conn]struct{})
http.HandleFunc("/stream", newStreamHandler(streamFile))
http.HandleFunc("/broker", playPauseBroker)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
})
panic(http.ListenAndServe(fmt.Sprintf(":%d", *listenPort), nil))
}