Toby Chui 1 неделя назад
Родитель
Сommit
a536cc47eb
1 измененных файлов с 27 добавлено и 3 удалено
  1. 27 3
      src/web/Arozcast/index.html

+ 27 - 3
src/web/Arozcast/index.html

@@ -471,6 +471,15 @@ function arozcastApp() {
             const url = wsUrl.toString();
             const self = this;
 
+            // Detach stale handlers from the old socket before replacing it.
+            // This prevents a dying connection from firing onmessage if it
+            // receives a relayed frame during the server-side cleanup window.
+            if (this.ws) {
+                this.ws.onopen    = null;
+                this.ws.onclose   = null;
+                this.ws.onmessage = null;
+            }
+
             this.ws = new WebSocket(url);
             this.ws.onopen = () => {
                 // Start periodic status broadcast
@@ -515,40 +524,55 @@ function arozcastApp() {
 
         // ── Message handler ───────────────────────────────────────────
         _handleMessage(msg) {
-            // Any message from a peer means at least one sender is active
-            this._touchPeer();
-
+            // _touchPeer() is called ONLY for sender-originated topics.
+            // The receiver emits 'status.update' and 'media.ended' itself; receiving
+            // those (e.g. via a brief double-connection during WS reconnect) must
+            // not be mistaken for a connected sender.
             switch(msg.topic) {
                 case 'media.load':
+                    this._touchPeer();
                     this._loadMedia(msg.payload);
                     break;
                 case 'media.play':
+                    this._touchPeer();
                     this._play();
                     break;
                 case 'media.pause':
+                    this._touchPeer();
                     this._pause();
                     break;
                 case 'media.seek':
+                    this._touchPeer();
                     this._seek(msg.payload.time);
                     break;
                 case 'media.seekrel': {
+                    this._touchPeer();
                     const el = this.mediaType === 'audio' ? this._audio : this._video;
                     const t = Math.max(0, Math.min((el.duration || 0), el.currentTime + (msg.payload.delta || 0)));
                     this._seek(t);
                     break;
                 }
                 case 'media.volume':
+                    this._touchPeer();
                     this._setVolume(msg.payload.volume, msg.payload.muted);
                     break;
                 case 'media.repeat':
+                    this._touchPeer();
                     this._setRepeat(msg.payload.mode || 'none');
                     break;
                 case 'media.stop':
+                    this._touchPeer();
                     this._stop();
                     break;
                 case 'peer.hello':
+                    this._touchPeer();
                     this.peerCount = 1;
                     break;
+                case 'peer.heartbeat':
+                    this._touchPeer();
+                    break;
+                // 'status.update' and 'media.ended' are sent BY this receiver —
+                // ignore silently if received (loopback via duplicate connection).
             }
         },