Ambient: Ton auf Android über den normalen Medienweg; dioras Lautstärke gilt für Drift; sw v77
Some checks failed
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Has been cancelled

- Drift: AudioContext mit latencyHint 'playback' – der Niedriglatenz-Weg (AAudio/MMAP)
  blieb auf dem Samsung des Nutzers stumm, obwohl Drift lief
- Drift: cmd 'volume' (0..1) von der Seite ist die Lautstärke; eingebettet ist der eigene
  Regler ausgeblendet
- app.js: setVolume() und das Laden des Frames schicken die Lautstärke an Drift (nicht in
  der Android-App: dort gelten die Lautstärketasten)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrwnslz 2026-10-04 00:01:03 +02:00
parent 3ff998f9cf
commit 16a1e23fcc
3 changed files with 32 additions and 8 deletions

View file

@ -16,6 +16,7 @@
overflow-x:hidden; overflow-y:auto; }
.embed header { display:none; }
.embed #share { display:none; }
.embed .m-vol { display:none; } /* eingebettet regelt die Seite die Lautstärke (cmd "volume") */
.embed .state { padding-right:58px; } /* oben rechts liegt in diora ein Knopf */
.noise .m-music, .noise .meters { display:none !important; }
html:not(.noise) .m-noise { display:none; }
@ -83,7 +84,7 @@
<label class="m-music">Tuning
<select id="tuning"><option value="just">Rein</option><option value="equal">Gleichstufig</option></select></label>
<div class="sep"></div>
<label>Lautstärke <input type="range" id="vol" min="0" max="1" step="0.01" value="0.7"></label>
<label class="m-vol">Lautstärke <input type="range" id="vol" min="0" max="1" step="0.01" value="0.7"></label>
<label class="m-noise"><span id="lla">A</span> <input type="range" id="la" min="0" max="1" step="0.01" value="0.5"></label>
<label class="m-noise"><span id="llb">B</span> <input type="range" id="lb" min="0" max="1" step="0.01" value="0.5"></label>
<label class="m-music">Dichte <input type="range" id="density" min="0" max="1" step="0.01" value="0.5"></label>
@ -174,6 +175,8 @@ const S = {
ring: null, wp: 0, written: 0, grainBuf: null,
};
let rafId = 0; // Zeichenschleife (siehe wake/draw)
S.hostVol = null; // Lautstärke der einbettenden Seite (cmd "volume"), sonst gilt der eigene Regler
const outVol = () => S.hostVol != null ? S.hostVol : +$("vol").value;
const speed = () => 0.35 + S.drift * 2.2;
// Spannungsbogen: 0 = ruhig, 1 = Höhepunkt, über mehrere Minuten
@ -216,7 +219,8 @@ function satCurve(k) {
}
function buildGraph() {
const ctx = S.ctx = new (window.AudioContext || window.webkitAudioContext)();
// "playback": the normal media path (on Android the low-latency one can stay silent, e.g. on Samsung)
const ctx = S.ctx = new (window.AudioContext || window.webkitAudioContext)({ latencyHint: "playback" });
const g = S.g = {};
g.voiceBus = ctx.createGain();
@ -1030,6 +1034,10 @@ addEventListener("message", e => {
if (d.cmd === "start" && !S.running) start();
else if (d.cmd === "stop" && S.running) stop();
else if (d.cmd === "mode" && MODES[d.mode]) setMode(d.mode);
else if (d.cmd === "volume" && isFinite(d.value)) { // die einbettende Seite (diora) gibt die Lautstärke vor
S.hostVol = clamp(+d.value, 0, 1);
if (S.running) S.g.master.gain.setTargetAtTime(outVol(), S.ctx.currentTime, .1);
}
});
function startMusic() {
@ -1053,7 +1061,7 @@ function start() {
S.ctx.resume().then(() => { if (S.running && S.ctx.state !== "running") stop(); }, () => { if (S.running) stop(); });
const t = S.ctx.currentTime;
S.g.master.gain.cancelScheduledValues(t);
S.g.master.gain.setTargetAtTime(+$("vol").value, t, 2.5);
S.g.master.gain.setTargetAtTime(outVol(), t, 2.5);
S.tickId = setInterval(tick, 1000);
enterMode();
$("toggle").textContent = "Stopp";
@ -1121,7 +1129,7 @@ function updateInfo() {
$("preset").innerHTML = Object.keys(PRESETS).map(n => `<option>${n}</option>`).join("");
$("toggle").onclick = () => S.running ? stop() : start();
$("vol").oninput = e => { if (S.running) S.g.master.gain.setTargetAtTime(+e.target.value, S.ctx.currentTime, .1); };
$("vol").oninput = () => { if (S.running) S.g.master.gain.setTargetAtTime(outVol(), S.ctx.currentTime, .1); };
$("density").oninput = e => S.density = +e.target.value;
$("drift").oninput = e => S.drift = +e.target.value;
$("arc").oninput = e => S.arcDepth = +e.target.value;

View file

@ -350,6 +350,7 @@ function setVolume(val) {
const numInput = $('volume-num');
if (slider) slider.value = val;
if (numInput) numInput.value = val;
_ambientVolume(val);
}
const volSliderEl = document.getElementById('volume');
@ -1253,7 +1254,11 @@ function _ambientFrame() {
function _ambientLoad() {
const f = _ambientFrame();
if (!f) return;
if (!f.getAttribute('src')) f.src = f.dataset.src;
if (!f.getAttribute('src')) {
// Drift takes the page's volume before its first sound.
f.addEventListener('load', () => _ambientVolume());
f.src = f.dataset.src;
}
requestAnimationFrame(_ambientSize);
}
@ -1269,9 +1274,9 @@ function _ambientSize() {
window.addEventListener('resize', () => requestAnimationFrame(_ambientSize));
// A command for Drift ('start' | 'stop'): the app's notification, the sleep timer.
window.__dioraAmbientCmd = function (cmd) {
window.__dioraAmbientCmd = function (cmd, value) {
const f = _ambientFrame();
if (f && f.contentWindow && f.getAttribute('src')) f.contentWindow.postMessage({type: 'ambient', cmd}, '*');
if (f && f.contentWindow && f.getAttribute('src')) f.contentWindow.postMessage({type: 'ambient', cmd, value}, '*');
};
// The radio or a podcast starts: Ambient stops.
@ -1280,6 +1285,17 @@ window.__dioraAmbientStop = function () {
};
window.__dioraAmbientPlaying = () => _ambientPlaying;
// The page's volume (0–255, the bar's slider) is Drift's too; its own slider hides when
// embedded. Not in the Android app: there the volume keys set it, as for everything.
function _ambientVolume(val) {
if (window.DioraNative && DioraNative.ambientState) return;
if (val == null) {
const s = $('volume');
val = s ? +s.value : +(localStorage.getItem('diora_volume') || 102);
}
window.__dioraAmbientCmd && window.__dioraAmbientCmd('volume', val / 255);
}
window.addEventListener('message', e => {
const f = _ambientFrame();
const d = e.data;

View file

@ -2,7 +2,7 @@
* diora service worker — caches the app shell for offline use.
*/
const CACHE = 'diora-v76';
const CACHE = 'diora-v77';
const SHELL = [
'/static/css/app.css',
'/static/js/app.js',