@onnie81/murdoku-spor 1.2.2 → 1.2.4
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
CHANGED
|
@@ -7,6 +7,12 @@ let master, sfxBus, musicBus, analyser;
|
|
|
7
7
|
let enabled = { sfx: true, music: true };
|
|
8
8
|
let musicOn = false;
|
|
9
9
|
let schedTimer = null;
|
|
10
|
+
let outEl = null; // iOS: audio leaves through a media element, not ctx.destination
|
|
11
|
+
|
|
12
|
+
// iPadOS pretends to be MacIntel, hence the maxTouchPoints check
|
|
13
|
+
const IS_IOS = typeof navigator !== 'undefined' &&
|
|
14
|
+
(/iP(hone|ad|od)/.test(navigator.userAgent) ||
|
|
15
|
+
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1));
|
|
10
16
|
|
|
11
17
|
function ensureCtx() {
|
|
12
18
|
if (ctx) return ctx;
|
|
@@ -15,7 +21,21 @@ function ensureCtx() {
|
|
|
15
21
|
ctx = new AC();
|
|
16
22
|
master = ctx.createGain();
|
|
17
23
|
master.gain.value = 0.9;
|
|
18
|
-
|
|
24
|
+
if (IS_IOS) {
|
|
25
|
+
// Direct WebAudio output is unreliable on iOS (silent-switch category and
|
|
26
|
+
// sample-rate mismatches can mute it entirely while the graph keeps
|
|
27
|
+
// "playing"). Routing through a MediaStream + <audio> element uses the
|
|
28
|
+
// real media playback path: audible, and immune to the ring switch.
|
|
29
|
+
const dest = ctx.createMediaStreamDestination();
|
|
30
|
+
master.connect(dest);
|
|
31
|
+
outEl = document.createElement('audio');
|
|
32
|
+
outEl.setAttribute('playsinline', '');
|
|
33
|
+
outEl.autoplay = true;
|
|
34
|
+
outEl.srcObject = dest.stream;
|
|
35
|
+
document.body.appendChild(outEl);
|
|
36
|
+
} else {
|
|
37
|
+
master.connect(ctx.destination);
|
|
38
|
+
}
|
|
19
39
|
analyser = ctx.createAnalyser();
|
|
20
40
|
analyser.fftSize = 2048;
|
|
21
41
|
master.connect(analyser);
|
|
@@ -40,22 +60,8 @@ function ensureCtx() {
|
|
|
40
60
|
return ctx;
|
|
41
61
|
}
|
|
42
62
|
|
|
43
|
-
|
|
44
|
-
|
|
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; }
|
|
63
|
+
function unlocked() {
|
|
64
|
+
return ctx && ctx.state === 'running' && (!IS_IOS || (outEl && !outEl.paused));
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
export function initAudioOnGesture() {
|
|
@@ -63,22 +69,31 @@ export function initAudioOnGesture() {
|
|
|
63
69
|
const kick = () => {
|
|
64
70
|
const c = ensureCtx();
|
|
65
71
|
if (!c) return;
|
|
66
|
-
|
|
72
|
+
// both calls must happen inside the user gesture
|
|
73
|
+
if (outEl && outEl.paused) {
|
|
74
|
+
const p = outEl.play();
|
|
75
|
+
if (p && p.catch) p.catch(() => {});
|
|
76
|
+
}
|
|
67
77
|
const go = () => {
|
|
68
|
-
if (
|
|
78
|
+
if (!unlocked()) return; // keep listening until truly audible
|
|
69
79
|
EVENTS.forEach(ev => window.removeEventListener(ev, kick));
|
|
70
80
|
if (enabled.music) startMusic();
|
|
71
81
|
};
|
|
72
82
|
if (c.state === 'suspended') c.resume().then(go).catch(() => {});
|
|
73
|
-
else go
|
|
83
|
+
else setTimeout(go, 50);
|
|
74
84
|
};
|
|
75
85
|
EVENTS.forEach(ev => window.addEventListener(ev, kick));
|
|
76
86
|
|
|
77
|
-
// phones suspend
|
|
87
|
+
// phones suspend audio when the tab goes to background — restore on return
|
|
78
88
|
document.addEventListener('visibilitychange', () => {
|
|
79
|
-
if (
|
|
89
|
+
if (document.hidden || !ctx) return;
|
|
90
|
+
if (ctx.state === 'suspended') {
|
|
80
91
|
ctx.resume().then(() => { if (enabled.music) startMusic(); }).catch(() => {});
|
|
81
92
|
}
|
|
93
|
+
if (outEl && outEl.paused) {
|
|
94
|
+
const p = outEl.play();
|
|
95
|
+
if (p && p.catch) p.catch(() => {});
|
|
96
|
+
}
|
|
82
97
|
});
|
|
83
98
|
}
|
|
84
99
|
|
|
@@ -144,6 +159,10 @@ export function sfx(name) {
|
|
|
144
159
|
const c = ensureCtx();
|
|
145
160
|
if (!c) return;
|
|
146
161
|
if (c.state === 'suspended') c.resume();
|
|
162
|
+
if (outEl && outEl.paused) {
|
|
163
|
+
const p = outEl.play();
|
|
164
|
+
if (p && p.catch) p.catch(() => {});
|
|
165
|
+
}
|
|
147
166
|
switch (name) {
|
|
148
167
|
case 'place': // soft wooden tap + warm blip
|
|
149
168
|
noise({ d: 0.05, peak: 0.25, freq: 900, q: 0.8, type: 'lowpass' });
|
|
@@ -302,6 +321,8 @@ if (typeof window !== 'undefined') {
|
|
|
302
321
|
state: () => (ctx ? ctx.state : 'none'),
|
|
303
322
|
musicOn: () => musicOn,
|
|
304
323
|
enabled: () => ({ ...enabled }),
|
|
324
|
+
route: () => (IS_IOS ? (outEl ? (outEl.paused ? 'ios-media:paused' : 'ios-media:playing') : 'ios-media:missing') : 'direct'),
|
|
325
|
+
rate: () => (ctx ? ctx.sampleRate : 0),
|
|
305
326
|
rms() {
|
|
306
327
|
if (!analyser) return 0;
|
|
307
328
|
const a = new Float32Array(analyser.fftSize);
|
package/murdoku.bundle
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -32,17 +32,29 @@ 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
|
|
35
|
+
# the deploy runner updates itself from the package and re-executes so the
|
|
36
|
+
# rest of THIS deploy runs with the fresh logic (cmp guard prevents looping)
|
|
36
37
|
if [ -f "$APP/scripts/npm-server-deploy.sh" ] && ! cmp -s "$APP/scripts/npm-server-deploy.sh" /usr/local/bin/murdoku-deploy; then
|
|
37
38
|
install -m 755 "$APP/scripts/npm-server-deploy.sh" /usr/local/bin/murdoku-deploy || true
|
|
38
|
-
echo "$(date -Is) deploy runner self-updated"
|
|
39
|
+
echo "$(date -Is) deploy runner self-updated — re-executing"
|
|
40
|
+
rm -rf "$tmp"
|
|
41
|
+
exec env FORCE=1 /usr/local/bin/murdoku-deploy "$APP" "$BRANCH"
|
|
39
42
|
fi
|
|
40
43
|
|
|
41
|
-
# refresh real git history from the bundle carried in the package
|
|
44
|
+
# refresh real git history from the bundle carried in the package.
|
|
45
|
+
# NOTE: fetch into a side ref — git refuses to fetch into the checked-out
|
|
46
|
+
# branch, which is what froze the reported sha at the first deploy.
|
|
42
47
|
if command -v git >/dev/null 2>&1 && [ -f "$src/murdoku.bundle" ]; then
|
|
43
48
|
if [ ! -d "$APP/.git" ]; then git -C "$APP" init -q; fi
|
|
44
|
-
git -C "$APP" fetch -f "$src/murdoku.bundle" 'refs/heads/main:refs/heads/
|
|
45
|
-
|
|
49
|
+
if git -C "$APP" fetch -f "$src/murdoku.bundle" 'refs/heads/main:refs/heads/incoming' 2>/dev/null; then
|
|
50
|
+
NEWREF=$(git -C "$APP" rev-parse --verify incoming 2>/dev/null || true)
|
|
51
|
+
if [ -n "$NEWREF" ]; then
|
|
52
|
+
git -C "$APP" update-ref refs/heads/main "$NEWREF"
|
|
53
|
+
git -C "$APP" symbolic-ref HEAD refs/heads/main 2>/dev/null || true
|
|
54
|
+
git -C "$APP" reset --hard main >/dev/null 2>&1 || true
|
|
55
|
+
git -C "$APP" update-ref -d refs/heads/incoming 2>/dev/null || true
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
46
58
|
fi
|
|
47
59
|
|
|
48
60
|
cd "$APP"
|