Skip to content

c4g7-dev/blockparty-webplayer

Repository files navigation

BlockParty WebPlayer

A modern, self-hosted audio+lyrics companion for the CloudNet BlockParty minigame. Plays synchronised background music (and Spotify-style LRC lyrics) in the browser while a player is ingame — server-driven, so every client in the same room stays perfectly in sync.

 Minecraft (Paper + Skript)           Node.js (VPS)                 Browser
 ─────────────────────────────        ─────────────────────         ─────────────────
 BlockPartyAddon plugin  ─── WS ───▶  server.js         ─── WS ───▶  React player
 (Java, java-websocket)               (Express + ws)                 (Vite + Tailwind)
                                                                     + LRC lyrics

Stack

Layer Tech
Frontend React 18 · TypeScript · Vite 5 · Tailwind 3 · shadcn/ui · lucide-react
Backend Node.js · Express · ws
Plugin Java 17 · Paper 1.20.4 · Skript 2.9.2 · java-websocket 1.5.6 · Gson
Deployment pm2 (VPS) · CloudNet template plugins (MC cluster)

Features

  • 🎵 Server-side song id resolution — the Skript just sends "WeAreNumberOne", the Node server maps it to /music/wearenumberone.flac (lookup by normalised filename, title/artist metadata included).
  • 🎤 Spotify-style synced lyrics — sidecar .lrc files next to each audio file. Active line glows, neighbours fade out. Scrolling is locked to song position (user can't wheel/touch-scroll the panel).
  • 🔄 Resume on reload — the server tracks authoritative playback time per room. Reconnecting clients receive a sync message with the current position and seek into the track automatically.
  • 👥 Multi-client per player — multiple browser tabs for the same MC name stay in sync.
  • 🔌 Auto-reconnect on both the plugin (Paper side) and the browser (client side) with heartbeat + whoAmI re-identification.
  • 🛡️ Safety net — if the plugin forwards a raw song id instead of a URL, Node resolves it. If it forwards a full URL, Node passes it through unchanged.

Repo layout

├─ server.js                 Express + WS hub, song-id resolver, room state
├─ src/                      React frontend (TS)
│  ├─ App.tsx                WS client, audio element, rAF time tick
│  ├─ components/
│  │  ├─ PlayerView.tsx      Main card, TrackInfo + lyrics toggle
│  │  ├─ LyricsPanel.tsx     Locked-scroll LRC panel with masked fades
│  │  └─ …                   SkinAvatar, StatusBadge, VolumeSlider, Equalizer
│  └─ lib/lrc.ts             LRC parser + `findActiveIndex` (binary search)
├─ music/                    Sidecar `.lrc` files (FLACs are .gitignored)
├─ plugin/                   Maven project — BlockPartyAddon (Skript add-on)
│  ├─ pom.xml
│  ├─ resources/
│  │  ├─ plugin.yml
│  │  └─ songs.yml           id → file + title + artist mapping
│  └─ sources/net/skydinse/blockPartyAddon/
│     ├─ Main.java
│     ├─ other/              WS client, song registry, Bukkit events
│     └─ elements/webplayer/ Skript effects, events, expressions
└─ README.md

Running locally

npm i
npm run dev      # vite (5173) + server.js (3000) concurrently

Production:

npm i
npm run build
node server.js   # serves /dist + /music + WS on :3000

Drop audio files into music/ (any of .flac .mp3 .ogg .wav .m4a) and, if you want lyrics, a sidecar .lrc with the same basename.


Plugin build

cd plugin
mvn -B -DskipTests package
# → plugin/target/BlockPartyAddon-1.1.0.jar

Drop the jar into your MC server's plugins/ folder (or the CloudNet template). It expects Skript 2.9.2+ and a reachable WebSocket URL (hard-coded to ws://194.117.224.203:3000 in Main.java — edit this for your deployment).

songs.yml is auto-extracted on first enable (template below).


Configuring songs.yml

Lives in <server>/plugins/BlockPartyAddon/songs.yml:

url-prefix: "/music/"

songs:
  WeAreNumberOne:
    file: "wearenumberone.flac"
    title: "We Are Number One"
    artist: "LazyTown"

  FindThatSomeone:
    file: "findthatsomeone.flac"
    title: "Find That Someone"
    artist: "Televisor"

  RazorSharp:
    file: "razorsharp.flac"
    title: "Razor Sharp"
    artist: "Pegboard Nerds"

The key is the song id you reference from Skript. The plugin normalises ids to lowercase + strips non-alphanumerics, so WeAreNumberOne, we are number one and wearenumberone all resolve to the same entry.


Skript integration (IMPORTANT)

The bug we fixed

The stock BlockParty Skript stores the SoundCloud URL of the chosen web song in {...song.web.link} and passes it directly to the webplayer. That doesn't work for self-hosted audio — the browser can't stream from a random SoundCloud URL.

The fix — in game/bp-main.sk

Inside the function blockpartyMainDefineSongWinner(round: string):, change line ~179 so _link.%loop-value-1% is set to the song id, not the ::link URL from settings.sk:

  loop {bp.config.songs.%loop-value-1%::*}:
      if loop-value-2 = {_return.%loop-value-1%::2}:
          set {_display.%loop-value-1%} to {bp.config.songs.%loop-value-1%::%loop-index-2%::display}
          set {_volume.%loop-value-1%} to {bp.config.songs.%loop-value-1%::%loop-index-2%::volume}
-         set {_link.%loop-value-1%} to {bp.config.songs.%loop-value-1%::%loop-index-2%::link}
+         set {_link.%loop-value-1%} to {_return.%loop-value-1%::2}
          stop loop

One-liner:

cd ~/cloudnet/local/templates/BlockParty/default/plugins/Skript/scripts
sed -i 's|set {_link.%loop-value-1%} to {bp.config.songs.%loop-value-1%::%loop-index-2%::link}|set {_link.%loop-value-1%} to {_return.%loop-value-1%::2}|' ./game/bp-main.sk

How the id flows

settings.sk                       bp-main.sk (fixed)                  Plugin / Node
───────────                       ──────────────────                  ─────────────
{bp.config.songs.web::1::link}    {_return.web::2} = "WeAreNumberOne"  → "/music/wearenumberone.flac"
  = "https://…soundcloud…"        (the yml key / song id)
(no longer used for the
 webplayer path)

Required Skript effects (provided by BlockPartyAddon)

blockparty auth host "<label>"                # optional, identify as a host
blockparty join %player% to "<room>"          # put player's webplayer into the room
blockparty leave %player%                     # take them out
blockparty load song "<id>" in "<room>"       # resolve id → url via songs.yml
blockparty load track "<url or id>" in "<room>"  # legacy — takes either
blockparty play "<room>"
blockparty pause "<room>"
blockparty delete room "<room>"

Bukkit events

on webplayer connect:     # fires when a browser identifies via whoAmI
on webplayer disconnect:  # fires when last client of a player disconnects

Expression

webplayer name                # string — last-connected player's MC name

WebSocket protocol

All messages are JSON. type is the discriminator.

Plugin → Server

type payload
auth { host: "<label>" }
switchRoom { player, room } (assigns player to room)
load { room, track, title?, artist? }
play { room }
pause { room }
stateUpdate { room, state }

Browser → Server

type payload
whoAmI { player }
heartbeat { player }

Server → Browser

type payload
switchRoom { room } (or room: null to kick out)
sync { state: { track, title, artist, time, playing } }
load { track: "/music/<file>", title, artist }
play / pause {}
leave { player }

Server → Plugin

type payload
webplayerConnect { player }
webplayerDisconnect { player }

Playback-time resume

Server state per room:

{
  track: "/music/wearenumberone.flac",
  title, artist,
  time: <seconds accumulated while paused>,
  playing: true|false,
  playStartedAt: <Date.now() when last play, or null>,
}

When a client connects / switches room, the server sends a sync with time = state.time + (playing ? (Date.now() - playStartedAt)/1000 : 0). The client seeks to that position once metadata is loaded.


Deployment targets

Target Where How to update
Web (public) root@194.117.224.203:/root/c4g7/BlockPartyPlayer/ (pm2 server) npm run build + rsync
Plugin jar cloud@176.9.76.49:~/cloudnet/local/templates/BlockParty/default/plugins/ (port 2244) mvn package + scp
Skript (bp-main.sk, settings.sk) cloud@176.9.76.49:~/cloudnet/local/templates/BlockParty/default/plugins/Skript/scripts/game/ edit + restart BP service

Typical redeploy flow

# Frontend + Node server
npm run build
rsync -az dist/ root@194.117.224.203:/root/c4g7/BlockPartyPlayer/dist/
scp server.js root@194.117.224.203:/root/c4g7/BlockPartyPlayer/server.js
ssh root@194.117.224.203 'pm2 restart server'

# Plugin
cd plugin && mvn -B -DskipTests package
scp -P 2244 target/BlockPartyAddon-1.1.0.jar \
    cloud@176.9.76.49:cloudnet/local/templates/BlockParty/default/plugins/
# then restart the BlockParty services in CloudNet so the new jar loads

Credits

  • UI + webplayer code — @c4g7
  • Original BlockParty concept & Skript — Limeiyy
  • Built on Skript 2.9.2 with 💚

© 2026 — All rights reserved.

About

BlockParty Web Player - Node.js/Express + WebSocket server that syncs music playback for Minecraft BlockParty game players

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors