diff --git a/changelog.d/1474-radio-next-no-autoplay.md b/changelog.d/1474-radio-next-no-autoplay.md index 46c4b1610..6fab42138 100644 --- a/changelog.d/1474-radio-next-no-autoplay.md +++ b/changelog.d/1474-radio-next-no-autoplay.md @@ -1,4 +1,5 @@ -- Radio Next and Previous no longer start a broadcaster stream before Play is - pressed; while a stream plays, it follows the new station. Play reads Pause - while a stream connects, and a tuner release onto a station the directory - dropped no longer leaves a Resume for another station (gekh, #1474). +- Radio Next, Previous and the tuning dial no longer start a broadcaster stream + before Play is pressed; while a stream plays, it follows the new station. + Play reads Pause while a stream connects, and a tuner release onto a station + the directory removed or changed no longer leaves a Resume for another + station (gekh, #1474). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 2d04d4f7e..2a7e0d827 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -4702,10 +4702,12 @@ accepted snapshot, and feed telemetry before re-initialization; monotonic ownership tokens plus the session boundary keep late callbacks from a retired session inert. A tuner drag resolves previews against the single accepted snapshot captured at -pointer-down, even while a newer catalog is admitted. Release starts playback +pointer-down, even while a newer catalog is admitted. Like Previous/Next, +release starts playback only if a stream was active when the drag began, and only when the current same-ID record still exactly matches the frozen -presentation and stream metadata; removal or replacement reports the channel as -unavailable and never silently retargets the drag. A cold degraded fallback may +presentation and stream metadata; otherwise it just selects the station. Removal +or replacement reports the channel as unavailable and never silently retargets +the drag. A cold degraded fallback may still populate the directory and globe, but its null accepted generation cannot populate or begin the tuner. @@ -4884,7 +4886,7 @@ module. Voice Pause is a playback-only no-op while the layer is disabled; it never enables Radio and reports a fulfilled-false pause as failure. A newer-response or user-origin Stop still cancels stale handoff work, as do a user Pause, a user Previous/Next that only selects another station, and a tuner -release onto a station the directory no longer has. +release onto a station the directory removed or changed mid-drag. Only a newer user turn or session teardown aborts the complete active-tool set. A cancelled turn cannot publish a late Radio playback request. Dedicated Radio controls and generic layer-visibility commands both forward cancellation. diff --git a/scripts/qa-radio.mjs b/scripts/qa-radio.mjs index 06b59baf9..8e86de86d 100644 --- a/scripts/qa-radio.mjs +++ b/scripts/qa-radio.mjs @@ -3692,8 +3692,7 @@ async function main() { radio.stopPlayback(); Array.from({ length: gev.viewer.dataSources.length }, (_, index) => gev.viewer.dataSources.get(index)) .find((item) => item.name === 'Radio stations').clustering.enabled = true; - // Next only carries playback once a stream is active (HAL-722), so the - // fallback scenario starts from a playing station. + // Next plays only while a stream is active, so start from a playing station. radio.selectStation('00000000-0000-4000-8000-000000000001', { autoplay: true, focus: false }); }); await page.waitForFunction(() => window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().audioState === 'playing'); diff --git a/src/data/radio.test.mjs b/src/data/radio.test.mjs index 260facff9..de5717a11 100644 --- a/src/data/radio.test.mjs +++ b/src/data/radio.test.mjs @@ -902,12 +902,15 @@ test('failed exact tuner release cannot consume a stale playback fallback', asyn stations: stationRows, failingUrls: [stationRows[0].streamUrl], }, async ({ playUrls }) => { - assert.equal(radioLayer.selectStation(stationRows[0].id, { autoplay: false }), true); + // A playing stream, so the release plays. + assert.equal(radioLayer.selectStation(stationRows[1].id, { autoplay: true }), true); + await new Promise((resolve) => setTimeout(resolve)); assert.equal(radioLayer.cycleStation(1, { stationIds: stationRows.map(({ id }) => id), autoplay: false, fallback: true, }), true, 'a voice-style prepared cycle creates the stale-fallback precondition'); + playUrls.length = 0; assert.equal(radioLayer.beginTuning(), true); assert.equal(radioLayer.previewTuningStation(stationRows[0].id, { rotate: false }), true); assert.equal(radioLayer.commitTuningStation(stationRows[0].id, { origin: 'user' }).ok, true); diff --git a/src/data/radioNextState.test.mjs b/src/data/radioNextState.test.mjs index 264765c75..3f9b2c6f6 100644 --- a/src/data/radioNextState.test.mjs +++ b/src/data/radioNextState.test.mjs @@ -1,5 +1,4 @@ -// Radio Next/Previous that only select (autoplay: false), against the real layer -// and the panel that renders it. +// Radio selection, fallbacks and voice handoffs on the real layer and its published view. import assert from 'node:assert/strict'; import test from 'node:test'; import { radioLayer } from './radio.js'; @@ -247,3 +246,28 @@ test('while a stream connects the transport buttons offer Pause, because a click assert.equal(shown.button, 'PAUSE'); assert.equal(shown.cockpit, 'Ⅱ'); }); + +test('a dial release plays only if a stream was active when the drag began', async () => { + await withLayer([], async ({ playUrls }) => { + radioLayer.selectStation(ids[0], { autoplay: false }); + assert.equal(radioLayer.beginTuning(), true); + radioLayer.previewTuningStation(ids[1], { rotate: false }); + assert.equal(radioLayer.commitTuningStation(ids[1], { origin: 'user' }).ok, true); + await settle(); + let state = radioLayer.getUIState(); + assert.deepEqual(playUrls, []); + assert.equal(state.selected?.id, ids[1]); + assert.equal(state.audioState, 'stopped'); + assert.equal(state.tuningStatic, false); + + await radioLayer.togglePlayback({ origin: 'user' }); + assert.equal(radioLayer.beginTuning(), true); + radioLayer.previewTuningStation(ids[2], { rotate: false }); + radioLayer.commitTuningStation(ids[2], { origin: 'user' }); + await settle(); + state = radioLayer.getUIState(); + assert.deepEqual(playUrls, [stations[1].streamUrl, stations[2].streamUrl]); + assert.equal(state.audioState, 'playing'); + assert.equal(state.playingStationId, ids[2]); + }); +}); diff --git a/src/i18n/locales/ar.json b/src/i18n/locales/ar.json index ac27e1278..658167d34 100644 --- a/src/i18n/locales/ar.json +++ b/src/i18n/locales/ar.json @@ -562,7 +562,7 @@ "Radio volume": "مستوى صوت الراديو", "Radio off": "الراديو متوقف", "STATION SITE": "موقع المحطة", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "يتصل التطبيق مباشرةً بمحطة البث عند الضغط على «تشغيل» أو إنهاء الضبط أو اختيار محطة على الكرة الأرضية، فيظهر لها عنوان IP الخاص بك.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "يتصل التطبيق مباشرةً بمحطة البث عند الضغط على «تشغيل» أو اختيار محطة على الكرة الأرضية أو طلب التشغيل بالصوت، فيظهر لها عنوان IP الخاص بك.", "LOADING": "جارٍ التحميل", "DEGRADED": "متدهور", "STALE": "متقادم", diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index e17c3b165..c70d3378e 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -777,7 +777,7 @@ "Stop radio playback": "Radiowiedergabe stoppen", "Radio volume": "Radio-Lautstärke", "STATION SITE": "SENDER-WEBSITE", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "Beim Starten der Wiedergabe, nach dem Abstimmen oder bei der Wahl eines Senders auf dem Globus wird eine direkte Verbindung zum Sender hergestellt. Die IP-Adresse ist für diesen Sender sichtbar.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "Beim Starten der Wiedergabe, bei der Wahl eines Senders auf dem Globus oder auf Wunsch per Sprachsteuerung wird eine direkte Verbindung zum Sender hergestellt. Die IP-Adresse ist für diesen Sender sichtbar.", "Intelligence HUD (H)": "Lagebild-HUD (H)", "Layout": "Layout", "HUD layout": "HUD-Layout", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 6a733a147..d45b252d7 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -450,7 +450,7 @@ "Radio volume": "Volumen de Radio", "Radio off": "Radio desactivada", "STATION SITE": "SITIO DE LA EMISORA", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "El audio se conecta directamente con la emisora al pulsar ESCUCHAR, terminar de sintonizar o elegir una emisora en el globo. Tu IP es visible para esa emisora.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "El audio se conecta directamente con la emisora al pulsar ESCUCHAR, elegir una emisora en el globo o pedirlo por voz. Tu IP es visible para esa emisora.", "LOADING": "CARGANDO", "DEGRADED": "DEGRADADO", "STALE": "DESACTUALIZADO", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 39355732a..a869e77ac 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -2819,7 +2819,7 @@ "Radio volume": "Volume de la radio", "STATION SITE": "SITE DE LA STATION", "DIRECTORY: RADIO BROWSER": "ANNUAIRE : RADIO BROWSER", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "Le flux audio se connecte directement au diffuseur dès que vous lancez la lecture, terminez le réglage ou choisissez une station sur le globe. Votre IP est visible par ce diffuseur.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "Le flux audio se connecte directement au diffuseur dès que vous lancez la lecture, choisissez une station sur le globe ou la demandez par la voix. Votre IP est visible par ce diffuseur.", "Local RTL-SDR receiver": "Récepteur RTL-SDR local", "LOCAL RTL-SDR": "RTL-SDR LOCAL", "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · un seul tuner local partagé entre la FM et la couche ADS-B local.", diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 027e210b0..a376a8865 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -2819,7 +2819,7 @@ "Radio volume": "Volume radio", "STATION SITE": "SITO STAZIONE", "DIRECTORY: RADIO BROWSER": "ELENCO: RADIO BROWSER", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "L'audio si collega direttamente all'emittente dopo aver premuto play, terminato la sintonizzazione o scelto una stazione sul globo. Il tuo IP è visibile a quell'emittente.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "L'audio si collega direttamente all'emittente dopo aver premuto play, scelto una stazione sul globo o chiesto la riproduzione a voce. Il tuo IP è visibile a quell'emittente.", "Local RTL-SDR receiver": "Ricevitore RTL-SDR locale", "LOCAL RTL-SDR": "RTL-SDR LOCALE", "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · un sintonizzatore locale condiviso tra FM e il livello ADS-B locale.", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 7f6a53c2b..c00780d9e 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -2677,7 +2677,7 @@ "Radio volume": "ラジオの音量", "STATION SITE": "放送局サイト", "DIRECTORY: RADIO BROWSER": "ディレクトリ:Radio Browser", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "再生ボタンを押す、選局を終える、または地球儀で局を選ぶと、音声は放送局に直接接続されます。お使いのIPアドレスは放送局側から見えます。", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "再生ボタンを押す、地球儀で局を選ぶ、または音声コントロールで再生を指示すると、音声は放送局に直接接続されます。お使いのIPアドレスは放送局側から見えます。", "Local RTL-SDR receiver": "ローカルRTL-SDR受信機", "LOCAL RTL-SDR": "ローカルRTL-SDR", "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · FMとローカルADS-Bレイヤーで1台のローカルチューナーを共有します。", diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json index 8f75df8a1..77d33514a 100644 --- a/src/i18n/locales/pt.json +++ b/src/i18n/locales/pt.json @@ -2821,7 +2821,7 @@ "Radio volume": "Volume do rádio", "STATION SITE": "SITE DA ESTAÇÃO", "DIRECTORY: RADIO BROWSER": "DIRETÓRIO: RADIO BROWSER", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "O áudio conecta direto à emissora quando você aperta \"Ouvir\", termina de sintonizar ou escolhe uma estação no globo. Seu IP fica visível para essa emissora.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "O áudio conecta direto à emissora quando você aperta \"Ouvir\", escolhe uma estação no globo ou pede por voz. Seu IP fica visível para essa emissora.", "Local RTL-SDR receiver": "Receptor RTL-SDR local", "LOCAL RTL-SDR": "RTL-SDR LOCAL", "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · um sintonizador local compartilhado entre o FM e a camada ADS-B local.", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index a397e6575..2f42ed522 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -727,7 +727,7 @@ "STATION TAG": "ТЕГ СТАНЦИИ", "DRAG TO TUNE": "ТЯНИТЕ ДЛЯ НАСТРОЙКИ", "TAB PREVIEWS · ENTER / SPACE SELECTS": "TAB — ПРОСМОТР · ENTER / SPACE — ВЫБОР", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "Звук идёт напрямую от вещателя после нажатия «Пуск», завершения настройки или выбора станции на глобусе. Вещателю виден ваш IP-адрес.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "Звук идёт напрямую от вещателя после нажатия «Пуск», выбора станции на глобусе или голосовой команды. Вещателю виден ваш IP-адрес.", "Internet radio and local SDR": "Интернет-радио и локальный SDR", "Filter stations by station tag": "Фильтровать станции по тегу", "CONTEXT": "КОНТЕКСТ", diff --git a/src/i18n/locales/uk.json b/src/i18n/locales/uk.json index daeb655e7..981846c4f 100644 --- a/src/i18n/locales/uk.json +++ b/src/i18n/locales/uk.json @@ -2889,7 +2889,7 @@ "Radio volume": "Гучність радіо", "STATION SITE": "САЙТ СТАНЦІЇ", "DIRECTORY: RADIO BROWSER": "КАТАЛОГ: RADIO BROWSER", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "Звук надходить безпосередньо від мовника після натискання «Відтворити», завершення налаштування або вибору станції на глобусі. Мовник бачить вашу IP-адресу.", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "Звук надходить безпосередньо від мовника після натискання «Відтворити», вибору станції на глобусі або голосової команди. Мовник бачить вашу IP-адресу.", "Local RTL-SDR receiver": "Локальний приймач RTL-SDR", "LOCAL RTL-SDR": "ЛОКАЛЬНИЙ RTL-SDR", "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · один локальний тюнер для FM і шару «Локальний ADS-B».", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 14139e4e5..a91e4a359 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -2575,7 +2575,7 @@ "Radio volume": "电台音量", "STATION SITE": "电台网站", "DIRECTORY: RADIO BROWSER": "目录:RADIO BROWSER", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "点击播放、完成调谐或在地球上选择电台后,音频将直接连接到广播方。广播方可以看到你的 IP。", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "点击播放、在地球上选择电台或通过语音要求播放后,音频将直接连接到广播方。广播方可以看到你的 IP。", "Local RTL-SDR receiver": "本地 RTL-SDR 接收机", "LOCAL RTL-SDR": "本地 RTL-SDR", "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · 一个本地调谐器由 FM 与本地 ADS-B 图层共享。", diff --git a/src/i18n/locales/zh-Hant.json b/src/i18n/locales/zh-Hant.json index c1e7bbb38..8ae31a1c8 100644 --- a/src/i18n/locales/zh-Hant.json +++ b/src/i18n/locales/zh-Hant.json @@ -2563,7 +2563,7 @@ "Radio volume": "電臺音量", "STATION SITE": "電臺網站", "DIRECTORY: RADIO BROWSER": "目錄:RADIO BROWSER", - "Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.": "點擊播放、完成調諧或在地球上選擇電臺後,音頻將直接連接到廣播方。廣播方可以看到你的 IP。", + "Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.": "點擊播放、在地球上選擇電臺或通過語音要求播放後,音頻將直接連接到廣播方。廣播方可以看到你的 IP。", "Local RTL-SDR receiver": "本地 RTL-SDR 接收機", "LOCAL RTL-SDR": "本地 RTL-SDR", "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · 一個本地調諧器由 FM 與本地 ADS-B 圖層共享。", diff --git a/src/layers/radio/selection.js b/src/layers/radio/selection.js index 9359cb3e4..b09a8c19a 100644 --- a/src/layers/radio/selection.js +++ b/src/layers/radio/selection.js @@ -140,7 +140,7 @@ export function createSelection({ if (focus) parts.navigation.focusStation(station); parts.presentation.emitState(); if (autoplay) void parts.playback.playSelectedRadio({ origin, attemptId }); - // A user step to another station overrides a playback voice prepared. + // A user step to another station cancels a playback voice prepared. else if (origin === 'user' && changed) parts.presentation.emitPlaybackControl('select', origin); return true; diff --git a/src/layers/radio/state.js b/src/layers/radio/state.js index e1436f1bb..a420c165e 100644 --- a/src/layers/radio/state.js +++ b/src/layers/radio/state.js @@ -78,6 +78,8 @@ export function createState({ services }) { layerState._tuningStartStationId = null; + layerState._tuningResumesPlayback = false; + layerState._tuningResolutionSnapshot = EMPTY_ACCEPTED_CATALOG_SNAPSHOT; layerState._tuningStationById = new Map(); diff --git a/src/layers/radio/tuning.js b/src/layers/radio/tuning.js index ed344d2c0..d9903b1a2 100644 --- a/src/layers/radio/tuning.js +++ b/src/layers/radio/tuning.js @@ -66,6 +66,12 @@ export function createTuning({ state: layerState, services, parts, source }) { layerState._cancelledTuningPresentationStation = null; layerState._tuningStartStationId = layerState._selectedId; layerState._tuningPreviewId = layerState._selectedId; + // Like Previous/Next, the release plays only if a stream was active. + layerState._tuningResumesPlayback = [ + 'loading', + 'buffering', + 'playing', + ].includes(layerState._audioState); parts.tuningNoise.installTuningNoise(); parts.playback.pauseRadioPlayback({ origin: 'user' }); parts.tuningNoise.syncTuningNoiseGain(); @@ -179,9 +185,8 @@ export function createTuning({ state: layerState, services, parts, source }) { layerState._selectionGeneration += 1; parts.tuningNoise.clearRadioTuningNoise({ emit: false }); layerState._tuningUnavailableStationId = stationId; - // The drag paused the previous stream; with no selection left, Resume - // could only start the nearest station, so release it. Stop publishes - // the state, and as the user's gesture it cancels a voice handoff. + // The drag paused the stream; with no selection, Resume would start the + // nearest station. Stop also publishes the state and cancels a voice handoff. parts.playback.stopRadioPlayback({ origin }); return Object.freeze({ ok: false, @@ -190,9 +195,11 @@ export function createTuning({ state: layerState, services, parts, source }) { generation, }); } + const autoplay = layerState._tuningResumesPlayback; + layerState._tuningResumesPlayback = false; layerState._tuningActive = false; - layerState._tuningStatic = true; - layerState._tuningAwaitingStationId = frozenStation.id; + layerState._tuningStatic = autoplay; + layerState._tuningAwaitingStationId = autoplay ? frozenStation.id : null; layerState._tuningPreviewId = null; layerState._tuningStartStationId = null; layerState._tuningUnavailableStationId = null; @@ -210,7 +217,7 @@ export function createTuning({ state: layerState, services, parts, source }) { parts.tuningNoise.syncTuningNoiseGain(); parts.presentation.emitState(); const selected = parts.selection.selectRadioStation(frozenStation.id, { - autoplay: true, + autoplay, focus: false, origin, cameraNavigation, diff --git a/src/testSupport/radioLayerHarness.mjs b/src/testSupport/radioLayerHarness.mjs index 30b9e2a86..45493357c 100644 --- a/src/testSupport/radioLayerHarness.mjs +++ b/src/testSupport/radioLayerHarness.mjs @@ -1,7 +1,5 @@ -/** Shared Radio layer test stand-ins: a station row, fake Audio, directory fetch and viewer. */ import { radioLayer } from '../data/radio.js'; -/** One accepted Radio Browser directory row. */ export function radioStationRow(overrides = {}) { return { id: '00000000-0000-4000-8000-000000000001', diff --git a/src/ui/views/panels/RadioPanel.vue b/src/ui/views/panels/RadioPanel.vue index 01e4114eb..67a8f7543 100644 --- a/src/ui/views/panels/RadioPanel.vue +++ b/src/ui/views/panels/RadioPanel.vue @@ -67,7 +67,7 @@ const radioWords = computed(() => ({ {{ t('STATION SITE') }} {{ t('DIRECTORY: RADIO BROWSER') }} -

{{ t('Audio connects directly to the broadcaster when you press play, finish tuning or choose a station on the globe. Your IP is visible to that broadcaster.') }}

+

{{ t('Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.') }}