@onnie81/murdoku-spor 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/src/audio.js +55 -6
- package/client/src/components/PlayView.jsx +6 -1
- package/murdoku.bundle +0 -0
- package/package.json +1 -1
- package/scripts/npm-server-deploy.sh +11 -1
- package/server/public/assets/index-BKNXbze9.js +40 -0
- package/server/public/index.html +1 -1
- package/server/public/assets/index-CWjzD9nk.js +0 -40
package/client/src/audio.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// copyright entanglements. Autoplay-safe: the context starts on first gesture.
|
|
4
4
|
|
|
5
5
|
let ctx = null;
|
|
6
|
-
let master, sfxBus, musicBus;
|
|
6
|
+
let master, sfxBus, musicBus, analyser;
|
|
7
7
|
let enabled = { sfx: true, music: true };
|
|
8
8
|
let musicOn = false;
|
|
9
9
|
let schedTimer = null;
|
|
@@ -16,6 +16,9 @@ function ensureCtx() {
|
|
|
16
16
|
master = ctx.createGain();
|
|
17
17
|
master.gain.value = 0.9;
|
|
18
18
|
master.connect(ctx.destination);
|
|
19
|
+
analyser = ctx.createAnalyser();
|
|
20
|
+
analyser.fftSize = 2048;
|
|
21
|
+
master.connect(analyser);
|
|
19
22
|
sfxBus = ctx.createGain();
|
|
20
23
|
sfxBus.gain.value = 0.5;
|
|
21
24
|
sfxBus.connect(master);
|
|
@@ -37,18 +40,46 @@ function ensureCtx() {
|
|
|
37
40
|
return ctx;
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
// iOS: playing a (nearly) silent media element inside the gesture switches the
|
|
44
|
+
// audio session to "playback", so WebAudio stays audible even with the ring
|
|
45
|
+
// switch on silent. One-time, invisible.
|
|
46
|
+
const SILENT_WAV = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=';
|
|
47
|
+
let mediaUnlocked = false;
|
|
48
|
+
function unlockMediaSession() {
|
|
49
|
+
if (mediaUnlocked) return;
|
|
50
|
+
mediaUnlocked = true;
|
|
51
|
+
try {
|
|
52
|
+
const a = document.createElement('audio');
|
|
53
|
+
a.setAttribute('playsinline', '');
|
|
54
|
+
a.src = SILENT_WAV;
|
|
55
|
+
a.volume = 0.01;
|
|
56
|
+
const p = a.play();
|
|
57
|
+
if (p && p.catch) p.catch(() => { mediaUnlocked = false; });
|
|
58
|
+
} catch { mediaUnlocked = false; }
|
|
59
|
+
}
|
|
60
|
+
|
|
40
61
|
export function initAudioOnGesture() {
|
|
62
|
+
const EVENTS = ['pointerdown', 'touchend', 'mousedown', 'keydown'];
|
|
41
63
|
const kick = () => {
|
|
42
64
|
const c = ensureCtx();
|
|
43
65
|
if (!c) return;
|
|
44
|
-
|
|
66
|
+
unlockMediaSession();
|
|
67
|
+
const go = () => {
|
|
68
|
+
if (c.state !== 'running') return; // keep listeners until truly unlocked
|
|
69
|
+
EVENTS.forEach(ev => window.removeEventListener(ev, kick));
|
|
70
|
+
if (enabled.music) startMusic();
|
|
71
|
+
};
|
|
45
72
|
if (c.state === 'suspended') c.resume().then(go).catch(() => {});
|
|
46
73
|
else go();
|
|
47
|
-
window.removeEventListener('pointerdown', kick);
|
|
48
|
-
window.removeEventListener('keydown', kick);
|
|
49
74
|
};
|
|
50
|
-
window.addEventListener(
|
|
51
|
-
|
|
75
|
+
EVENTS.forEach(ev => window.addEventListener(ev, kick));
|
|
76
|
+
|
|
77
|
+
// phones suspend the context when the tab goes to background — resume on return
|
|
78
|
+
document.addEventListener('visibilitychange', () => {
|
|
79
|
+
if (!document.hidden && ctx && ctx.state === 'suspended') {
|
|
80
|
+
ctx.resume().then(() => { if (enabled.music) startMusic(); }).catch(() => {});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
52
83
|
}
|
|
53
84
|
|
|
54
85
|
export function setAudioPrefs({ sfx, music }) {
|
|
@@ -264,3 +295,21 @@ export function stopMusic() {
|
|
|
264
295
|
musicBus.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.8);
|
|
265
296
|
}
|
|
266
297
|
}
|
|
298
|
+
|
|
299
|
+
// diagnostics hook (used by the e2e test and handy in devtools)
|
|
300
|
+
if (typeof window !== 'undefined') {
|
|
301
|
+
window.__mdkAudio = {
|
|
302
|
+
state: () => (ctx ? ctx.state : 'none'),
|
|
303
|
+
musicOn: () => musicOn,
|
|
304
|
+
enabled: () => ({ ...enabled }),
|
|
305
|
+
rms() {
|
|
306
|
+
if (!analyser) return 0;
|
|
307
|
+
const a = new Float32Array(analyser.fftSize);
|
|
308
|
+
analyser.getFloatTimeDomainData(a);
|
|
309
|
+
let s = 0;
|
|
310
|
+
for (let i = 0; i < a.length; i++) s += a[i] * a[i];
|
|
311
|
+
return Math.sqrt(s / a.length);
|
|
312
|
+
},
|
|
313
|
+
ping: () => sfx('chime')
|
|
314
|
+
};
|
|
315
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
|
6
6
|
import { api } from '../api.js';
|
|
7
7
|
import { createGameStore, useGame } from '../game.js';
|
|
8
|
-
import { useApp, navigate, toast, getState } from '../store.js';
|
|
8
|
+
import { useApp, navigate, toast, getState, updateSettings } from '../store.js';
|
|
9
9
|
import { Board } from './Board.jsx';
|
|
10
10
|
import { Tray } from './Tray.jsx';
|
|
11
11
|
import { CluePanel } from './CluePanel.jsx';
|
|
@@ -44,10 +44,13 @@ export function PlayView({ gameId }) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function PlayInner({ loaded }) {
|
|
47
|
+
const app = useApp();
|
|
47
48
|
const store = useMemo(
|
|
48
49
|
() => createGameStore(loaded.game, loaded.puzzle, () => getState().settings, (key) => toast(t(key))),
|
|
49
50
|
[loaded]
|
|
50
51
|
);
|
|
52
|
+
const soundOn = app.settings.sfx !== false || app.settings.music !== false;
|
|
53
|
+
const soundToggle = () => updateSettings({ sfx: !soundOn, music: !soundOn });
|
|
51
54
|
const snap = useGame(store);
|
|
52
55
|
const boardRef = useRef(null);
|
|
53
56
|
const [drag, setDrag] = useState(null);
|
|
@@ -294,6 +297,7 @@ function PlayInner({ loaded }) {
|
|
|
294
297
|
<span className="stat-mist">✗ {snap.mistakes}</span>
|
|
295
298
|
</div>
|
|
296
299
|
<button className="icon-btn" onClick={() => setCaseOpen(true)} aria-label={t('caseFile')}>📜</button>
|
|
300
|
+
<button className="icon-btn" onClick={soundToggle} aria-label={t('sound')}>{soundOn ? '🔊' : '🔇'}</button>
|
|
297
301
|
{fsAvailable && (
|
|
298
302
|
<button className="icon-btn" onClick={fsToggle} aria-label={t('fullscreen')}>{isFs ? '🡼' : '⛶'}</button>
|
|
299
303
|
)}
|
|
@@ -358,6 +362,7 @@ function PlayInner({ loaded }) {
|
|
|
358
362
|
<span className="stat-time">{fmtTime(snap.timeMs)}</span>
|
|
359
363
|
<span className="stat-mist" title={t('mistakes')}>✗ {snap.mistakes}</span>
|
|
360
364
|
</div>
|
|
365
|
+
<button className="icon-btn" onClick={soundToggle} aria-label={t('sound')}>{soundOn ? '🔊' : '🔇'}</button>
|
|
361
366
|
{fsAvailable && (
|
|
362
367
|
<button className="icon-btn" onClick={fsToggle} aria-label={t('fullscreen')}>{isFs ? '🡼' : '⛶'}</button>
|
|
363
368
|
)}
|
package/murdoku.bundle
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -32,6 +32,12 @@ mkdir -p "$APP"
|
|
|
32
32
|
find "$APP" -mindepth 1 -maxdepth 1 ! -name .git ! -name .version -exec rm -rf {} +
|
|
33
33
|
cp -a "$src"/. "$APP"/
|
|
34
34
|
|
|
35
|
+
# the deploy runner updates itself from the package (effective next run)
|
|
36
|
+
if [ -f "$APP/scripts/npm-server-deploy.sh" ] && ! cmp -s "$APP/scripts/npm-server-deploy.sh" /usr/local/bin/murdoku-deploy; then
|
|
37
|
+
install -m 755 "$APP/scripts/npm-server-deploy.sh" /usr/local/bin/murdoku-deploy || true
|
|
38
|
+
echo "$(date -Is) deploy runner self-updated"
|
|
39
|
+
fi
|
|
40
|
+
|
|
35
41
|
# refresh real git history from the bundle carried in the package (optional)
|
|
36
42
|
if command -v git >/dev/null 2>&1 && [ -f "$src/murdoku.bundle" ]; then
|
|
37
43
|
if [ ! -d "$APP/.git" ]; then git -C "$APP" init -q; fi
|
|
@@ -43,7 +49,11 @@ cd "$APP"
|
|
|
43
49
|
GIT_SHA=$(git rev-parse --short main 2>/dev/null || echo "$ver")
|
|
44
50
|
export GIT_SHA
|
|
45
51
|
if docker compose version >/dev/null 2>&1; then DC="docker compose"; else DC="docker-compose"; fi
|
|
46
|
-
$DC up -d --build
|
|
52
|
+
if ! $DC up -d --build --remove-orphans; then
|
|
53
|
+
echo "$(date -Is) compose up failed — clearing stray containers and retrying"
|
|
54
|
+
docker rm -f murdoku murdoku-caddy >/dev/null 2>&1 || true
|
|
55
|
+
$DC up -d --build --remove-orphans
|
|
56
|
+
fi
|
|
47
57
|
|
|
48
58
|
ok=0
|
|
49
59
|
for _ in $(seq 1 45); do
|