Feature: Custom poster for standby

This commit is contained in:
Dejvino 2026-01-03 22:56:30 +00:00
parent e7931174de
commit 03474298a9
4 changed files with 106 additions and 37 deletions

View File

@ -1,5 +1,5 @@
const DB_NAME = 'PartyMediaDB';
const DB_VERSION = 1;
const DB_VERSION = 2;
export const MediaStorage = {
open: () => {
@ -9,6 +9,7 @@ export const MediaStorage = {
const db = e.target.result;
if (!db.objectStoreNames.contains('music')) db.createObjectStore('music');
if (!db.objectStoreNames.contains('tapes')) db.createObjectStore('tapes');
if (!db.objectStoreNames.contains('poster')) db.createObjectStore('poster');
};
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e);
@ -42,10 +43,24 @@ export const MediaStorage = {
req.onerror = () => resolve([]);
});
},
savePoster: async (file) => {
const db = await MediaStorage.open();
const tx = db.transaction('poster', 'readwrite');
tx.objectStore('poster').put(file, 'currentPoster');
},
getPoster: async () => {
const db = await MediaStorage.open();
return new Promise((resolve) => {
const req = db.transaction('poster', 'readonly').objectStore('poster').get('currentPoster');
req.onsuccess = () => resolve(req.result);
req.onerror = () => resolve(null);
});
},
clear: async () => {
const db = await MediaStorage.open();
const tx = db.transaction(['music', 'tapes'], 'readwrite');
const tx = db.transaction(['music', 'tapes', 'poster'], 'readwrite');
tx.objectStore('music').clear();
tx.objectStore('tapes').clear();
tx.objectStore('poster').clear();
}
};

View File

@ -136,6 +136,40 @@ export class ConfigUI extends SceneFeature {
});
statusContainer.appendChild(this.tapeList);
// Load Poster Button
const loadPosterBtn = document.createElement('button');
loadPosterBtn.innerText = 'Load Poster';
Object.assign(loadPosterBtn.style, {
marginTop: '10px',
padding: '8px',
cursor: 'pointer',
backgroundColor: '#555',
color: 'white',
border: 'none',
borderRadius: '4px',
fontSize: '14px'
});
const posterInput = document.createElement('input');
posterInput.type = 'file';
posterInput.accept = 'image/*';
posterInput.style.display = 'none';
posterInput.onchange = (e) => {
const file = e.target.files[0];
if (file) {
if (state.posterImage) URL.revokeObjectURL(state.posterImage);
state.posterImage = URL.createObjectURL(file);
MediaStorage.savePoster(file);
showStandbyScreen();
}
};
document.body.appendChild(posterInput);
loadPosterBtn.onclick = () => {
posterInput.click();
};
statusContainer.appendChild(loadPosterBtn);
// Load Tapes Button
const loadTapesBtn = document.createElement('button');
loadTapesBtn.id = 'loadTapeButton';
@ -235,6 +269,11 @@ export class ConfigUI extends SceneFeature {
}
}
if (state.posterImage) {
URL.revokeObjectURL(state.posterImage);
state.posterImage = null;
}
showStandbyScreen();
const defaults = {
@ -259,6 +298,14 @@ export class ConfigUI extends SceneFeature {
document.body.appendChild(container);
this.container = container;
this.updateStatus();
// Restore poster
MediaStorage.getPoster().then(file => {
if (file) {
state.posterImage = URL.createObjectURL(file);
showStandbyScreen();
}
});
}
updateStatus() {

View File

@ -298,47 +298,53 @@ export class ProjectionScreen extends SceneFeature {
export function showStandbyScreen() {
if (projectionScreenInstance) projectionScreenInstance.isVisualizerActive = false;
const canvas = document.createElement('canvas');
canvas.width = 1024;
canvas.height = 576;
const ctx = canvas.getContext('2d');
let texture;
// Draw Color Bars
const colors = ['#ffffff', '#ffff00', '#00ffff', '#00ff00', '#ff00ff', '#ff0000', '#0000ff'];
const barWidth = canvas.width / colors.length;
colors.forEach((color, i) => {
ctx.fillStyle = color;
ctx.fillRect(i * barWidth, 0, barWidth, canvas.height);
});
if (state.posterImage) {
texture = new THREE.TextureLoader().load(state.posterImage);
} else {
const canvas = document.createElement('canvas');
canvas.width = 1024;
canvas.height = 576;
const ctx = canvas.getContext('2d');
// Semi-transparent overlay
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw Color Bars
const colors = ['#ffffff', '#ffff00', '#00ffff', '#00ff00', '#ff00ff', '#ff0000', '#0000ff'];
const barWidth = canvas.width / colors.length;
colors.forEach((color, i) => {
ctx.fillStyle = color;
ctx.fillRect(i * barWidth, 0, barWidth, canvas.height);
});
// Text settings
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Semi-transparent overlay
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Song Title
let text = "PLEASE STAND BY";
if (state.music && state.music.songTitle) {
text = state.music.songTitle.toUpperCase();
// Text settings
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Song Title
let text = "PLEASE STAND BY";
if (state.music && state.music.songTitle) {
text = state.music.songTitle.toUpperCase();
}
ctx.font = 'bold 60px monospace';
if (ctx.measureText(text).width > canvas.width * 0.9) {
ctx.font = 'bold 40px monospace';
}
ctx.fillText(text, canvas.width / 2, canvas.height / 2 - 20);
// Subtext
ctx.font = '30px monospace';
ctx.fillStyle = '#cccccc';
ctx.fillText("WAITING FOR PARTY START", canvas.width / 2, canvas.height / 2 + 50);
texture = new THREE.CanvasTexture(canvas);
}
ctx.font = 'bold 60px monospace';
if (ctx.measureText(text).width > canvas.width * 0.9) {
ctx.font = 'bold 40px monospace';
}
ctx.fillText(text, canvas.width / 2, canvas.height / 2 - 20);
// Subtext
ctx.font = '30px monospace';
ctx.fillStyle = '#cccccc';
ctx.fillText("WAITING FOR PARTY START", canvas.width / 2, canvas.height / 2 + 50);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide

View File

@ -44,6 +44,7 @@ export function initState() {
videoUrls: [],
videoFilenames: [],
currentVideoIndex: -1,
posterImage: null,
// Scene constants
originalLampIntensity: 0.3,