Ambient: Ton auf Android über den normalen Medienweg; dioras Lautstärke gilt für Drift; sw v77
- 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:
parent
3ff998f9cf
commit
16a1e23fcc
3 changed files with 32 additions and 8 deletions
|
|
@ -16,6 +16,7 @@
|
||||||
overflow-x:hidden; overflow-y:auto; }
|
overflow-x:hidden; overflow-y:auto; }
|
||||||
.embed header { display:none; }
|
.embed header { display:none; }
|
||||||
.embed #share { 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 */
|
.embed .state { padding-right:58px; } /* oben rechts liegt in diora ein Knopf */
|
||||||
.noise .m-music, .noise .meters { display:none !important; }
|
.noise .m-music, .noise .meters { display:none !important; }
|
||||||
html:not(.noise) .m-noise { display:none; }
|
html:not(.noise) .m-noise { display:none; }
|
||||||
|
|
@ -83,7 +84,7 @@
|
||||||
<label class="m-music">Tuning
|
<label class="m-music">Tuning
|
||||||
<select id="tuning"><option value="just">Rein</option><option value="equal">Gleichstufig</option></select></label>
|
<select id="tuning"><option value="just">Rein</option><option value="equal">Gleichstufig</option></select></label>
|
||||||
<div class="sep"></div>
|
<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="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-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>
|
<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,
|
ring: null, wp: 0, written: 0, grainBuf: null,
|
||||||
};
|
};
|
||||||
let rafId = 0; // Zeichenschleife (siehe wake/draw)
|
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;
|
const speed = () => 0.35 + S.drift * 2.2;
|
||||||
|
|
||||||
// Spannungsbogen: 0 = ruhig, 1 = Höhepunkt, über mehrere Minuten
|
// Spannungsbogen: 0 = ruhig, 1 = Höhepunkt, über mehrere Minuten
|
||||||
|
|
@ -216,7 +219,8 @@ function satCurve(k) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGraph() {
|
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 = {};
|
const g = S.g = {};
|
||||||
|
|
||||||
g.voiceBus = ctx.createGain();
|
g.voiceBus = ctx.createGain();
|
||||||
|
|
@ -1030,6 +1034,10 @@ addEventListener("message", e => {
|
||||||
if (d.cmd === "start" && !S.running) start();
|
if (d.cmd === "start" && !S.running) start();
|
||||||
else if (d.cmd === "stop" && S.running) stop();
|
else if (d.cmd === "stop" && S.running) stop();
|
||||||
else if (d.cmd === "mode" && MODES[d.mode]) setMode(d.mode);
|
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() {
|
function startMusic() {
|
||||||
|
|
@ -1053,7 +1061,7 @@ function start() {
|
||||||
S.ctx.resume().then(() => { if (S.running && S.ctx.state !== "running") stop(); }, () => { if (S.running) stop(); });
|
S.ctx.resume().then(() => { if (S.running && S.ctx.state !== "running") stop(); }, () => { if (S.running) stop(); });
|
||||||
const t = S.ctx.currentTime;
|
const t = S.ctx.currentTime;
|
||||||
S.g.master.gain.cancelScheduledValues(t);
|
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);
|
S.tickId = setInterval(tick, 1000);
|
||||||
enterMode();
|
enterMode();
|
||||||
$("toggle").textContent = "Stopp";
|
$("toggle").textContent = "Stopp";
|
||||||
|
|
@ -1121,7 +1129,7 @@ function updateInfo() {
|
||||||
|
|
||||||
$("preset").innerHTML = Object.keys(PRESETS).map(n => `<option>${n}</option>`).join("");
|
$("preset").innerHTML = Object.keys(PRESETS).map(n => `<option>${n}</option>`).join("");
|
||||||
$("toggle").onclick = () => S.running ? stop() : start();
|
$("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;
|
$("density").oninput = e => S.density = +e.target.value;
|
||||||
$("drift").oninput = e => S.drift = +e.target.value;
|
$("drift").oninput = e => S.drift = +e.target.value;
|
||||||
$("arc").oninput = e => S.arcDepth = +e.target.value;
|
$("arc").oninput = e => S.arcDepth = +e.target.value;
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,7 @@ function setVolume(val) {
|
||||||
const numInput = $('volume-num');
|
const numInput = $('volume-num');
|
||||||
if (slider) slider.value = val;
|
if (slider) slider.value = val;
|
||||||
if (numInput) numInput.value = val;
|
if (numInput) numInput.value = val;
|
||||||
|
_ambientVolume(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
const volSliderEl = document.getElementById('volume');
|
const volSliderEl = document.getElementById('volume');
|
||||||
|
|
@ -1253,7 +1254,11 @@ function _ambientFrame() {
|
||||||
function _ambientLoad() {
|
function _ambientLoad() {
|
||||||
const f = _ambientFrame();
|
const f = _ambientFrame();
|
||||||
if (!f) return;
|
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);
|
requestAnimationFrame(_ambientSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1269,9 +1274,9 @@ function _ambientSize() {
|
||||||
window.addEventListener('resize', () => requestAnimationFrame(_ambientSize));
|
window.addEventListener('resize', () => requestAnimationFrame(_ambientSize));
|
||||||
|
|
||||||
// A command for Drift ('start' | 'stop'): the app's notification, the sleep timer.
|
// 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();
|
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.
|
// The radio or a podcast starts: Ambient stops.
|
||||||
|
|
@ -1280,6 +1285,17 @@ window.__dioraAmbientStop = function () {
|
||||||
};
|
};
|
||||||
window.__dioraAmbientPlaying = () => _ambientPlaying;
|
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 => {
|
window.addEventListener('message', e => {
|
||||||
const f = _ambientFrame();
|
const f = _ambientFrame();
|
||||||
const d = e.data;
|
const d = e.data;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* diora service worker — caches the app shell for offline use.
|
* diora service worker — caches the app shell for offline use.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE = 'diora-v76';
|
const CACHE = 'diora-v77';
|
||||||
const SHELL = [
|
const SHELL = [
|
||||||
'/static/css/app.css',
|
'/static/css/app.css',
|
||||||
'/static/js/app.js',
|
'/static/js/app.js',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue