diff --git a/extension/locales/de.json b/extension/locales/de.json index efdee88..22fdb35 100644 --- a/extension/locales/de.json +++ b/extension/locales/de.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Festlegen, wer die Wiedergabe in diesem Raum steuern darf", "LABEL_HOST_ONLY_TOGGLE": "Nur ich darf die Wiedergabe steuern", "NOTICE_HOST_CONTROLS": "Der Host steuert die Wiedergabe für alle.", + "NOTICE_COHOST_HINT": "Tipp: Gib einzelnen Teilnehmern unten in der Teilnehmerliste die Steuerung.", "BADGE_HOST": "Host", "BADGE_GUEST": "Gast", "BADGE_CONTROLLER": "Controller", diff --git a/extension/locales/en.json b/extension/locales/en.json index 416d6b4..5e7ffe2 100644 --- a/extension/locales/en.json +++ b/extension/locales/en.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Restrict who can control playback in this room", "LABEL_HOST_ONLY_TOGGLE": "Only I can control playback", "NOTICE_HOST_CONTROLS": "The host controls playback for everyone.", + "NOTICE_COHOST_HINT": "Tip: grant control to individual viewers in the participant list below.", "BADGE_HOST": "Host", "BADGE_GUEST": "Guest", "BADGE_CONTROLLER": "Controller", diff --git a/extension/locales/es.json b/extension/locales/es.json index 7e72723..95b5e0b 100644 --- a/extension/locales/es.json +++ b/extension/locales/es.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Restringir quién puede controlar la reproducción en esta sala", "LABEL_HOST_ONLY_TOGGLE": "Solo yo puedo controlar la reproducción", "NOTICE_HOST_CONTROLS": "El anfitrión controla la reproducción para todos.", + "NOTICE_COHOST_HINT": "Consejo: concede el control a usuarios concretos en la lista de participantes de abajo.", "BADGE_HOST": "Anfitrión", "BADGE_GUEST": "Invitado", "BADGE_CONTROLLER": "Controlador", diff --git a/extension/locales/fr.json b/extension/locales/fr.json index b550c2e..8c2dbba 100644 --- a/extension/locales/fr.json +++ b/extension/locales/fr.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Définir qui peut contrôler la lecture dans ce salon", "LABEL_HOST_ONLY_TOGGLE": "Moi seul peux contrôler la lecture", "NOTICE_HOST_CONTROLS": "L'hôte contrôle la lecture pour tout le monde.", + "NOTICE_COHOST_HINT": "Astuce : donnez le contrôle à des participants précis dans la liste ci-dessous.", "BADGE_HOST": "Hôte", "BADGE_GUEST": "Invité", "BADGE_CONTROLLER": "Contrôleur", diff --git a/extension/locales/it.json b/extension/locales/it.json index 21996c7..6734f1e 100644 --- a/extension/locales/it.json +++ b/extension/locales/it.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Limita chi può controllare la riproduzione in questa stanza", "LABEL_HOST_ONLY_TOGGLE": "Solo io posso controllare la riproduzione", "NOTICE_HOST_CONTROLS": "L'host controlla la riproduzione per tutti.", + "NOTICE_COHOST_HINT": "Suggerimento: concedi il controllo ai singoli partecipanti nell'elenco qui sotto.", "BADGE_HOST": "Host", "BADGE_GUEST": "Ospite", "BADGE_CONTROLLER": "Controller", diff --git a/extension/locales/ja.json b/extension/locales/ja.json index cc3ab9d..12c4bf7 100644 --- a/extension/locales/ja.json +++ b/extension/locales/ja.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "このルームで再生を操作できる人を制限します", "LABEL_HOST_ONLY_TOGGLE": "再生を操作できるのは自分だけ", "NOTICE_HOST_CONTROLS": "ホストが全員の再生を操作します。", + "NOTICE_COHOST_HINT": "ヒント: 下の参加者リストで個別の参加者に操作権限を付与できます。", "BADGE_HOST": "ホスト", "BADGE_GUEST": "ゲスト", "BADGE_CONTROLLER": "操作権あり", diff --git a/extension/locales/ko.json b/extension/locales/ko.json index ea573a5..8c648f0 100644 --- a/extension/locales/ko.json +++ b/extension/locales/ko.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "이 방에서 재생을 제어할 수 있는 사람 제한", "LABEL_HOST_ONLY_TOGGLE": "나만 재생을 제어할 수 있음", "NOTICE_HOST_CONTROLS": "호스트가 모두의 재생을 제어합니다.", + "NOTICE_COHOST_HINT": "팁: 아래 참가자 목록에서 개별 참가자에게 제어 권한을 부여하세요.", "BADGE_HOST": "호스트", "BADGE_GUEST": "게스트", "BADGE_CONTROLLER": "제어자", diff --git a/extension/locales/nl.json b/extension/locales/nl.json index 6a07ca2..c54da24 100644 --- a/extension/locales/nl.json +++ b/extension/locales/nl.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Bepaal wie de weergave in deze kamer mag bedienen", "LABEL_HOST_ONLY_TOGGLE": "Alleen ik mag de weergave bedienen", "NOTICE_HOST_CONTROLS": "De host bedient de weergave voor iedereen.", + "NOTICE_COHOST_HINT": "Tip: geef afzonderlijke deelnemers de besturing in de lijst hieronder.", "BADGE_HOST": "Host", "BADGE_GUEST": "Gast", "BADGE_CONTROLLER": "Bediener", diff --git a/extension/locales/pl.json b/extension/locales/pl.json index 2cc04be..b061ea9 100644 --- a/extension/locales/pl.json +++ b/extension/locales/pl.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Określ, kto może sterować odtwarzaniem w tym pokoju", "LABEL_HOST_ONLY_TOGGLE": "Tylko ja mogę sterować odtwarzaniem", "NOTICE_HOST_CONTROLS": "Host steruje odtwarzaniem dla wszystkich.", + "NOTICE_COHOST_HINT": "Wskazówka: przyznaj sterowanie wybranym uczestnikom na liście poniżej.", "BADGE_HOST": "Host", "BADGE_GUEST": "Gość", "BADGE_CONTROLLER": "Sterujący", diff --git a/extension/locales/pt-BR.json b/extension/locales/pt-BR.json index 87ab3bc..2165844 100644 --- a/extension/locales/pt-BR.json +++ b/extension/locales/pt-BR.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Restringir quem pode controlar a reprodução nesta sala", "LABEL_HOST_ONLY_TOGGLE": "Somente eu posso controlar a reprodução", "NOTICE_HOST_CONTROLS": "O anfitrião controla a reprodução para todos.", + "NOTICE_COHOST_HINT": "Dica: conceda o controle a participantes específicos na lista abaixo.", "BADGE_HOST": "Anfitrião", "BADGE_GUEST": "Convidado", "BADGE_CONTROLLER": "Controlador", diff --git a/extension/locales/pt.json b/extension/locales/pt.json index 4b7fbe3..2b647f4 100644 --- a/extension/locales/pt.json +++ b/extension/locales/pt.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Restringir quem pode controlar a reprodução nesta sala", "LABEL_HOST_ONLY_TOGGLE": "Só eu posso controlar a reprodução", "NOTICE_HOST_CONTROLS": "O anfitrião controla a reprodução para todos.", + "NOTICE_COHOST_HINT": "Dica: conceda o controlo a participantes específicos na lista abaixo.", "BADGE_HOST": "Anfitrião", "BADGE_GUEST": "Convidado", "BADGE_CONTROLLER": "Controlador", diff --git a/extension/locales/ru.json b/extension/locales/ru.json index 06e1dfd..f3451b5 100644 --- a/extension/locales/ru.json +++ b/extension/locales/ru.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Ограничить, кто может управлять воспроизведением в этой комнате", "LABEL_HOST_ONLY_TOGGLE": "Только я могу управлять воспроизведением", "NOTICE_HOST_CONTROLS": "Ведущий управляет воспроизведением для всех.", + "NOTICE_COHOST_HINT": "Совет: предоставьте управление отдельным участникам в списке ниже.", "BADGE_HOST": "Ведущий", "BADGE_GUEST": "Гость", "BADGE_CONTROLLER": "Управляющий", diff --git a/extension/locales/tr.json b/extension/locales/tr.json index a6caddb..6f53a50 100644 --- a/extension/locales/tr.json +++ b/extension/locales/tr.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Bu odada oynatmayı kimin kontrol edebileceğini belirleyin", "LABEL_HOST_ONLY_TOGGLE": "Oynatmayı yalnızca ben kontrol edebilirim", "NOTICE_HOST_CONTROLS": "Oynatmayı herkes için sunucu kontrol eder.", + "NOTICE_COHOST_HINT": "İpucu: aşağıdaki katılımcı listesinde tek tek katılımcılara kontrol verin.", "BADGE_HOST": "Sunucu", "BADGE_GUEST": "Misafir", "BADGE_CONTROLLER": "Kontrolcü", diff --git a/extension/locales/uk.json b/extension/locales/uk.json index 6f4466f..9088b9b 100644 --- a/extension/locales/uk.json +++ b/extension/locales/uk.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "Обмежити, хто може керувати відтворенням у цій кімнаті", "LABEL_HOST_ONLY_TOGGLE": "Лише я можу керувати відтворенням", "NOTICE_HOST_CONTROLS": "Ведучий керує відтворенням для всіх.", + "NOTICE_COHOST_HINT": "Порада: надайте керування окремим учасникам у списку нижче.", "BADGE_HOST": "Ведучий", "BADGE_GUEST": "Гість", "BADGE_CONTROLLER": "Керуючий", diff --git a/extension/locales/zh.json b/extension/locales/zh.json index 26247fc..8aba7be 100644 --- a/extension/locales/zh.json +++ b/extension/locales/zh.json @@ -48,6 +48,7 @@ "LABEL_HOST_CONTROL_TOOLTIP": "限制谁可以在此房间中控制播放", "LABEL_HOST_ONLY_TOGGLE": "仅我可以控制播放", "NOTICE_HOST_CONTROLS": "由主持人为所有人控制播放。", + "NOTICE_COHOST_HINT": "提示:在下方的参与者列表中将控制权授予单个参与者。", "BADGE_HOST": "主持人", "BADGE_GUEST": "访客", "BADGE_CONTROLLER": "控制者", diff --git a/extension/popup.html b/extension/popup.html index 3d36a4c..c6a50ad 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -402,14 +402,18 @@
Manual Connect / Advanced
-
- -
- - + +
+
+ +
+ + +
+
-
+
@@ -455,10 +459,14 @@
- + +
diff --git a/extension/popup.js b/extension/popup.js index 6a73613..edaef4a 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -46,6 +46,7 @@ const elements = { hostControlToggleRow: document.getElementById('hostControlToggleRow'), hostControlToggle: document.getElementById('hostControlToggle'), hostControlGuestNote: document.getElementById('hostControlGuestNote'), + hostControlCohostHint: document.getElementById('hostControlCohostHint'), activeRoomId: document.getElementById('activeRoomId'), activeServer: document.getElementById('activeServer'), peerListSync: document.getElementById('peerListSync'), @@ -361,6 +362,11 @@ function updateHostControlUI(state) { if (elements.hostControlToggleRow) elements.hostControlToggleRow.style.display = amHost ? 'flex' : 'none'; if (elements.hostControlToggle) elements.hostControlToggle.checked = hostOnly; if (elements.hostControlGuestNote) elements.hostControlGuestNote.style.display = (!amController && hostOnly) ? 'block' : 'none'; + // Owner-only discoverability hint: co-hosting is granted down in the peer list, + // which isn't obvious from this card. Only show it when it's actionable — owner, + // relay supports co-host, and the room is locked to host-only (the buttons only + // render in that mode). + if (elements.hostControlCohostHint) elements.hostControlCohostHint.style.display = (amHost && !!s.coHostSupported && hostOnly) ? 'block' : 'none'; // Only a non-controller is locked out of the remote controls. hcmGuestLocked = (!amController && hostOnly); @@ -670,6 +676,11 @@ function updatePeerList(peers) { header.appendChild(nameSpan); + // Right-side badges + actions, kept in one group so they sit together + // on the right instead of being scattered by the header's space-between. + const rightGroup = document.createElement('div'); + rightGroup.style.cssText = 'display:flex; align-items:center; gap:6px; flex-shrink:0;'; + // Volume Icon (Top Right) if (p.volume !== undefined && p.volume !== null) { const volIcon = document.createElement('div'); @@ -683,7 +694,7 @@ function updatePeerList(peers) { const you = document.createElement('span'); you.style.cssText = 'font-size:10px; color:var(--accent); font-weight:bold;'; you.textContent = getMessage('LABEL_YOU') || 'YOU'; - header.appendChild(you); + rightGroup.appendChild(you); } // Host Control Mode: show "Solo" badge for peers watching on their own. @@ -693,13 +704,14 @@ function updatePeerList(peers) { const soloText = getMessage('BADGE_DESYNCED') || 'Solo'; solo.textContent = soloText; solo.title = getMessage('TOOLTIP_PEER_DESYNCED') || soloText; - header.appendChild(solo); + rightGroup.appendChild(solo); } - // Co-Host: role badges + the owner's promote/demote control. Shown when the - // feature is active and either the room is host-only or we're the owner - // (so a guest in a normal room sees no role noise). - const showRoles = !!hcmOwnerPeerId && (hcmRoomIsHostOnly || hcmAmOwner); + // Co-Host: role badges + the owner's promote/demote control. Only meaningful + // once the room is locked to host-only — in 'everyone' mode anyone can already + // control, so roles are moot and we show nothing (no badge/button noise). + // The promote/demote button additionally requires owner privileges (below). + const showRoles = !!hcmOwnerPeerId && hcmRoomIsHostOnly; if (showRoles) { const isOwner = pId === hcmOwnerPeerId; const isController = !isOwner && hcmControllers.includes(pId); @@ -707,20 +719,32 @@ function updatePeerList(peers) { const roleBadge = document.createElement('span'); roleBadge.style.cssText = 'font-size:10px; color:#fff; background:var(--accent); padding:2px 6px; border-radius:6px; font-weight:600;'; roleBadge.textContent = isOwner ? (getMessage('BADGE_HOST') || 'Host') : (getMessage('BADGE_CONTROLLER') || 'Controller'); - header.appendChild(roleBadge); + rightGroup.appendChild(roleBadge); } // Owner can promote/demote any non-owner peer. if (hcmAmOwner && hcmCoHostSupported && !isOwner) { const btn = document.createElement('button'); - btn.style.cssText = 'font-size:10px; padding:2px 8px; border-radius:6px; cursor:pointer; border:1px solid var(--accent); background:transparent; color:var(--accent); white-space:nowrap;'; - btn.textContent = isController ? (getMessage('BTN_REVOKE_CONTROL') || 'Revoke') : (getMessage('BTN_GIVE_CONTROL') || 'Give control'); + const revoke = isController; + btn.style.cssText = `font-size:10px; padding:3px 9px; border-radius:6px; cursor:pointer; white-space:nowrap; font-weight:600; transition:background .15s,color .15s; border:1px solid ${revoke ? 'var(--text-muted)' : 'var(--accent)'}; background:transparent; color:${revoke ? 'var(--text-muted)' : 'var(--accent)'};`; + btn.textContent = revoke ? (getMessage('BTN_REVOKE_CONTROL') || 'Revoke') : (getMessage('BTN_GIVE_CONTROL') || 'Give control'); + btn.title = revoke ? (getMessage('BTN_REVOKE_CONTROL') || 'Revoke') : (getMessage('BTN_GIVE_CONTROL') || 'Give control'); + btn.addEventListener('mouseenter', () => { + btn.style.background = revoke ? 'var(--text-muted)' : 'var(--accent)'; + btn.style.color = '#fff'; + }); + btn.addEventListener('mouseleave', () => { + btn.style.background = 'transparent'; + btn.style.color = revoke ? 'var(--text-muted)' : 'var(--accent)'; + }); btn.addEventListener('click', () => { chrome.runtime.sendMessage({ type: 'SET_PEER_ROLE', peerId: pId, controller: !isController }, () => {}); }); - header.appendChild(btn); + rightGroup.appendChild(btn); } } + if (rightGroup.childNodes.length) header.appendChild(rightGroup); + peerItem.appendChild(header); // Media Info @@ -1273,7 +1297,11 @@ if (elements.langSelector) { elements.serverUrl.addEventListener('change', () => { let url = elements.serverUrl.value.trim(); if (url && !url.includes('://')) { - url = 'ws://' + url; + // Default to secure wss:// — plain ws:// is only valid for a local relay + // (and the background rejects/upgrades ws:// for any remote host anyway). + const host = url.split('/')[0].split(':')[0].toLowerCase(); + const isLocal = host === 'localhost' || host === '127.0.0.1'; + url = (isLocal ? 'ws://' : 'wss://') + url; elements.serverUrl.value = url; chrome.storage.local.set({ serverUrl: url }); } @@ -1390,7 +1418,7 @@ elements.joinBtn.addEventListener('click', async () => { } if (useCustom && serverUrl) { try { - const urlToCheck = serverUrl.includes('://') ? serverUrl : 'ws://' + serverUrl; + const urlToCheck = serverUrl.includes('://') ? serverUrl : 'wss://' + serverUrl; new URL(urlToCheck); } catch (_e) { showError(getMessage('ERR_INVALID_SERVER_URL'));