discstation 0.1.42 → 0.1.43
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/README.md +7 -2
- package/arduino/firmware/discstation.bin +0 -0
- package/arduino/firmware/firmware.json +1 -1
- package/package.json +1 -1
- package/src/discstation.py +24 -2
- package/src/static/app.js +13 -13
- package/src/static/index.html +2 -2
package/README.md
CHANGED
|
@@ -175,7 +175,11 @@ self-signed cert. On startup the host prints the LAN URL to open
|
|
|
175
175
|
— and picking one starts it directly, no separate confirm step. Also has
|
|
176
176
|
EJECT/CLOSE toggle, a live disc-status readout, and full playback
|
|
177
177
|
transport. Greys out and auto-collapses the instant a physical remote is
|
|
178
|
-
detected, so the two never fight for control
|
|
178
|
+
detected, so the two never fight for control — including mid-burn/rip/play:
|
|
179
|
+
plugging the remote in hands it control without interrupting whatever's
|
|
180
|
+
running, and its screen picks up the current status instead of starting
|
|
181
|
+
over; unplugging it hands control back to the on-screen remote within a
|
|
182
|
+
second or two.
|
|
179
183
|
|
|
180
184
|
## Mobile App
|
|
181
185
|
|
|
@@ -184,7 +188,8 @@ has the same on-screen remote as the web UI, rendered inline in the main
|
|
|
184
188
|
screen rather than a popup — same "REMOTE" tag toggle, opens automatically
|
|
185
189
|
with no ESP32 attached, mode buttons filtered to what the disc actually
|
|
186
190
|
supports, and picking one starts it directly. Same auto-collapse behavior
|
|
187
|
-
when a physical ESP32 shows up
|
|
191
|
+
when a physical ESP32 shows up — mid-operation and without interruption, same
|
|
192
|
+
as the web UI — and the same underlying `/remote/button` protocol.
|
|
188
193
|
|
|
189
194
|
## Disc Support
|
|
190
195
|
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.1.
|
|
1
|
+
{"version":"0.1.43","size":1260592,"md5":"575faf0891e354ecd451d8454228a18e"}
|
package/package.json
CHANGED
package/src/discstation.py
CHANGED
|
@@ -530,6 +530,14 @@ class _WebHandler(http.server.BaseHTTPRequestHandler):
|
|
|
530
530
|
return
|
|
531
531
|
if _active_ser is not None and _active_ser.hw is None:
|
|
532
532
|
_active_ser.virtual.push_line(cmd)
|
|
533
|
+
# One-click burn: chain START right behind SELECT:BURN* in the SAME
|
|
534
|
+
# request, atomically - station_loop's SELECT handler clears any
|
|
535
|
+
# stale buffered input the instant it reads the SELECT line, and a
|
|
536
|
+
# START sent as a second, separate HTTP call can land in that exact
|
|
537
|
+
# gap and get wiped (intermittent, timing-dependent "burn never
|
|
538
|
+
# starts" bug). Queuing both lines from one call removes the gap.
|
|
539
|
+
if cmd.upper().startswith("SELECT:BURN"):
|
|
540
|
+
_active_ser.virtual.push_line("START")
|
|
533
541
|
self._respond(200, 'OK')
|
|
534
542
|
else:
|
|
535
543
|
self._respond(409, 'A hardware remote is attached')
|
|
@@ -645,7 +653,7 @@ class _WebHandler(http.server.BaseHTTPRequestHandler):
|
|
|
645
653
|
def _serve_sw(self):
|
|
646
654
|
sw = '''self.addEventListener('install', e => {
|
|
647
655
|
self.skipWaiting();
|
|
648
|
-
caches.open('discstation-
|
|
656
|
+
caches.open('discstation-v20').then(c => c.addAll(['/','/static/style.css?v=20','/static/app.js?v=20']));
|
|
649
657
|
});
|
|
650
658
|
self.addEventListener('activate', e => e.waitUntil(clients.claim()));
|
|
651
659
|
self.addEventListener('fetch', e => {
|
|
@@ -654,7 +662,7 @@ self.addEventListener('fetch', e => {
|
|
|
654
662
|
if (path === '/' || path.startsWith('/static/')) {
|
|
655
663
|
e.respondWith(fetch(e.request).then(r => {
|
|
656
664
|
const copy = r.clone();
|
|
657
|
-
caches.open('discstation-
|
|
665
|
+
caches.open('discstation-v20').then(c => c.put(e.request, copy));
|
|
658
666
|
return r;
|
|
659
667
|
}).catch(() => caches.match(e.request)));
|
|
660
668
|
} else {
|
|
@@ -845,6 +853,20 @@ def wait_for_web_url(ser):
|
|
|
845
853
|
url = f"{protocol}://{ip}:{_web_port}"
|
|
846
854
|
safe_send(ser, f"IP:{url}")
|
|
847
855
|
print(f"URL displayed: {url}")
|
|
856
|
+
# The one-click remote buttons queue START right behind SELECT:BURN* (see
|
|
857
|
+
# _handle_remote_button) - when files were uploaded BEFORE the mode was
|
|
858
|
+
# picked (the normal one-click order), that START can already be sitting
|
|
859
|
+
# in the buffer by the time this runs. Check the queue before touching the
|
|
860
|
+
# serial line at all: the loop below reads and discards one line per pass
|
|
861
|
+
# looking for CANCEL/HOME, and would otherwise eat that START and throw it
|
|
862
|
+
# away, leaving the burn waiting forever for a START that already came.
|
|
863
|
+
try:
|
|
864
|
+
got = _burn_url_queue.get_nowait()
|
|
865
|
+
except Empty:
|
|
866
|
+
got = None
|
|
867
|
+
if got:
|
|
868
|
+
safe_send(ser, "STATUS:Got URL, starting...")
|
|
869
|
+
return got
|
|
848
870
|
while True:
|
|
849
871
|
line = read_serial_line(ser, timeout=0.5)
|
|
850
872
|
if line and line.strip().upper() in ("CANCEL", "HOME"):
|
package/src/static/app.js
CHANGED
|
@@ -51,11 +51,16 @@
|
|
|
51
51
|
function setProgress(phase, percent, active = true) {
|
|
52
52
|
const panel = $("global-progress");
|
|
53
53
|
const fill = $("progress-fill");
|
|
54
|
-
|
|
54
|
+
// Clamp here, the one place this renders - a browser's own upload
|
|
55
|
+
// "progress" event isn't guaranteed to stop exactly at loaded===total
|
|
56
|
+
// (seen reading past 100%), and this is the single choke point every
|
|
57
|
+
// caller (upload, burn/rip progress, SSE) goes through either way.
|
|
58
|
+
const shown = percent >= 0 ? Math.min(100, Math.max(0, Math.round(percent))) : -1;
|
|
59
|
+
panel.hidden = !active && shown < 0;
|
|
55
60
|
$("progress-phase").textContent = (phase || "READY").toUpperCase();
|
|
56
|
-
$("progress-value").textContent =
|
|
57
|
-
fill.style.width =
|
|
58
|
-
fill.classList.toggle("indeterminate",
|
|
61
|
+
$("progress-value").textContent = shown >= 0 ? `${shown}%` : "...";
|
|
62
|
+
fill.style.width = shown >= 0 ? `${shown}%` : "34%";
|
|
63
|
+
fill.classList.toggle("indeterminate", shown < 0);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
async function pollStatus() {
|
|
@@ -154,16 +159,11 @@
|
|
|
154
159
|
const button = event.target.closest("[data-cmd]");
|
|
155
160
|
if (!button || button.disabled) return;
|
|
156
161
|
const cmd = button.dataset.cmd;
|
|
162
|
+
// One-click burn: the server queues START right behind SELECT:BURN* in the
|
|
163
|
+
// same request now (was two separate POSTs from here, which raced against
|
|
164
|
+
// the server clearing stale input the instant it read SELECT and could
|
|
165
|
+
// silently drop the START - see _handle_remote_button in discstation.py).
|
|
157
166
|
await sendRemoteCmd(cmd);
|
|
158
|
-
// On real hardware START is a long-press of the encoder on the burn-ready
|
|
159
|
-
// review screen, not its own button - a one-click "BURN AUDIO"/"BURN
|
|
160
|
-
// DATA"/"BURN" here is the whole point of the web remote, so chase the
|
|
161
|
-
// mode select straight through to starting the burn instead of leaving
|
|
162
|
-
// the user stuck with nothing left to press. (The backend queues this
|
|
163
|
-
// harmlessly if files/URL aren't in yet - it's only consumed once the
|
|
164
|
-
// burn's actually at its own "waiting for start" step, and gets flushed
|
|
165
|
-
// if a different mode gets selected first so it can't fire the wrong burn.)
|
|
166
|
-
if (cmd.startsWith("SELECT:BURN")) await sendRemoteCmd("START");
|
|
167
167
|
});
|
|
168
168
|
let volumeTimer;
|
|
169
169
|
$("remote-volume").addEventListener("input", (event) => {
|
package/src/static/index.html
CHANGED
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
</footer>
|
|
145
145
|
</div>
|
|
146
146
|
<div id="install-slot"></div>
|
|
147
|
-
<script>if ("serviceWorker" in navigator) navigator.serviceWorker.register("/sw.js?v=
|
|
148
|
-
<script src="/static/app.js?v=
|
|
147
|
+
<script>if ("serviceWorker" in navigator) navigator.serviceWorker.register("/sw.js?v=20").catch(() => {});</script>
|
|
148
|
+
<script src="/static/app.js?v=20" defer></script>
|
|
149
149
|
</body>
|
|
150
150
|
</html>
|