fertig-classic-games/public/src/ui/MusicPlayer.js

146 lines
4.2 KiB
JavaScript

import * as Phaser from 'phaser';
import { COLORS, GAME_WIDTH } from '../config.js';
const BTN = 32;
const GAP = 6;
const PAD = 12;
const DEPTH = 90;
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
export class MusicPlayer {
constructor(scene, tracks) {
this.scene = scene;
this.tracks = shuffle(tracks);
this.idx = 0;
this.muted = false;
this._audio = null;
this._objs = [];
this._buildUI();
this._play(0);
scene.events.once('shutdown', () => this.destroy());
scene.events.once('destroy', () => this.destroy());
}
_buildUI() {
const s = this.scene;
const muteX = GAME_WIDTH - PAD - BTN / 2;
const skipX = muteX - GAP - BTN;
const btnY = PAD + BTN / 2;
const infoY = PAD + BTN + GAP + 8;
this._muteX = muteX;
this._muteY = btnY;
const skipBg = s.add.graphics().setDepth(DEPTH);
this._drawBg(skipBg, skipX, btnY, false);
skipBg.setInteractive(
new Phaser.Geom.Rectangle(skipX - BTN / 2, btnY - BTN / 2, BTN, BTN),
Phaser.Geom.Rectangle.Contains,
);
skipBg.input.cursor = 'pointer';
skipBg.on('pointerover', () => this._drawBg(skipBg, skipX, btnY, true));
skipBg.on('pointerout', () => this._drawBg(skipBg, skipX, btnY, false));
skipBg.on('pointerdown', () => this.next());
const skipIcon = s.add.text(skipX, btnY, '⏭', {
fontFamily: 'sans-serif', fontSize: '17px', color: COLORS.textHex,
}).setOrigin(0.5).setDepth(DEPTH + 1);
const muteBg = s.add.graphics().setDepth(DEPTH);
this._drawBg(muteBg, muteX, btnY, false);
muteBg.setInteractive(
new Phaser.Geom.Rectangle(muteX - BTN / 2, btnY - BTN / 2, BTN, BTN),
Phaser.Geom.Rectangle.Contains,
);
muteBg.input.cursor = 'pointer';
muteBg.on('pointerover', () => this._drawBg(muteBg, muteX, btnY, true));
muteBg.on('pointerout', () => this._drawBg(muteBg, muteX, btnY, false));
muteBg.on('pointerdown', () => this.toggleMute());
this._muteIcon = s.add.text(muteX, btnY, '♪', {
fontFamily: 'sans-serif', fontSize: '17px', color: COLORS.textHex,
}).setOrigin(0.5).setDepth(DEPTH + 1);
this._muteLine = s.add.graphics().setDepth(DEPTH + 2);
this._infoText = s.add.text(GAME_WIDTH - PAD, infoY, '', {
fontFamily: 'Righteous', fontSize: '13px', color: COLORS.mutedHex,
}).setOrigin(1, 0).setDepth(DEPTH);
this._objs.push(skipBg, skipIcon, muteBg, this._muteIcon, this._muteLine, this._infoText);
}
_drawBg(g, cx, cy, hover) {
g.clear();
g.fillStyle(COLORS.panel, 0.88);
g.fillRoundedRect(cx - BTN / 2, cy - BTN / 2, BTN, BTN, 6);
if (hover) {
g.lineStyle(1.5, COLORS.accent, 0.8);
g.strokeRoundedRect(cx - BTN / 2, cy - BTN / 2, BTN, BTN, 6);
}
}
_play(idx) {
if (this._audio) {
this._audio.pause();
this._audio.onended = null;
}
const track = this.tracks[idx];
this._audio = new Audio(`/assets/music/${track.file}`);
this._audio.muted = this.muted;
this._audio.volume = 0.15;
this._audio.play().catch(() => { });
this._audio.onended = () => this.next();
this._updateInfo();
}
_updateInfo() {
const t = this.tracks[this.idx];
this._infoText?.setText(`${t.artist}${t.title}`);
}
_updateMuteVisual() {
this._muteIcon?.setColor(this.muted ? COLORS.mutedHex : COLORS.textHex);
this._muteLine?.clear();
if (this.muted) {
const r = BTN / 2 - 5;
this._muteLine.lineStyle(2, COLORS.danger, 0.9);
this._muteLine.beginPath();
this._muteLine.moveTo(this._muteX + r, this._muteY - r);
this._muteLine.lineTo(this._muteX - r, this._muteY + r);
this._muteLine.strokePath();
}
}
next() {
this.idx = (this.idx + 1) % this.tracks.length;
this._play(this.idx);
}
toggleMute() {
this.muted = !this.muted;
if (this._audio) this._audio.muted = this.muted;
this._updateMuteVisual();
}
destroy() {
if (this._audio) {
this._audio.pause();
this._audio.onended = null;
this._audio = null;
}
for (const o of this._objs) { try { o.destroy(); } catch (_) { } }
this._objs = [];
}
}