mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
feat(radio): add tooltips + aria-labels on every player button
Radio audio-player buttons (skip-prev, play/pause FAB, skip-next, mute, volume slider) had zero matTooltip or aria-label. The UX audit flagged this: "Skip-prev / Skip-next — what do they skip? In radio, is the next 'track' the next station? Next in favourites? Random? Without a tooltip the buttons trade silently." Same critique applied to mute and play/pause for screen-reader users. Add hover tooltips and screen-reader labels on all 5 controls. Skip prev/next say "Previous station" / "Next station" so the behaviour is explicit (they walk the active filtered list). Play/Pause flips its label with state. Mute/unmute flips when isMuted() || volume === 0. Volume slider gets its own label so the slider thumb is announced correctly. i18n: 7 new AUDIO_PLAYER.* keys translated across all 17 non-English locales by per-locale agents; all 18 locales pass coverage and placeholder integrity checks. 52/52 ui-playback tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
86ec1685a5
commit
55215a5059
19 files changed
+221
-3
No files matched your search
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "فاتح",
|
||||
"SYSTEM_THEME": "النظام"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "تشغيل",
|
||||
"PAUSE": "إيقاف مؤقت",
|
||||
"PREVIOUS_STATION": "المحطة السابقة",
|
||||
"NEXT_STATION": "المحطة التالية",
|
||||
"MUTE": "كتم الصوت",
|
||||
"UNMUTE": "إلغاء كتم الصوت",
|
||||
"VOLUME": "مستوى الصوت"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "فاتح",
|
||||
"SYSTEM_THEME": "النظام"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "شغل",
|
||||
"PAUSE": "وقّف",
|
||||
"PREVIOUS_STATION": "المحطة السابقة",
|
||||
"NEXT_STATION": "المحطة التالية",
|
||||
"MUTE": "كتم الصوت",
|
||||
"UNMUTE": "رجّع الصوت",
|
||||
"VOLUME": "الصوت"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Светлая",
|
||||
"SYSTEM_THEME": "Сістэмная"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Прайграць",
|
||||
"PAUSE": "Паўза",
|
||||
"PREVIOUS_STATION": "Папярэдняя станцыя",
|
||||
"NEXT_STATION": "Наступная станцыя",
|
||||
"MUTE": "Адключыць гук",
|
||||
"UNMUTE": "Уключыць гук",
|
||||
"VOLUME": "Гучнасць"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Hell",
|
||||
"SYSTEM_THEME": "System"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Wiedergabe",
|
||||
"PAUSE": "Pause",
|
||||
"PREVIOUS_STATION": "Vorheriger Sender",
|
||||
"NEXT_STATION": "Nächster Sender",
|
||||
"MUTE": "Stummschalten",
|
||||
"UNMUTE": "Stummschaltung aufheben",
|
||||
"VOLUME": "Lautstärke"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Ανοιχτό",
|
||||
"SYSTEM_THEME": "Σύστημα"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Αναπαραγωγή",
|
||||
"PAUSE": "Παύση",
|
||||
"PREVIOUS_STATION": "Προηγούμενος σταθμός",
|
||||
"NEXT_STATION": "Επόμενος σταθμός",
|
||||
"MUTE": "Σίγαση",
|
||||
"UNMUTE": "Κατάργηση σίγασης",
|
||||
"VOLUME": "Ένταση"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Light",
|
||||
"SYSTEM_THEME": "System"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Play",
|
||||
"PAUSE": "Pause",
|
||||
"PREVIOUS_STATION": "Previous station",
|
||||
"NEXT_STATION": "Next station",
|
||||
"MUTE": "Mute",
|
||||
"UNMUTE": "Unmute",
|
||||
"VOLUME": "Volume"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Claro",
|
||||
"SYSTEM_THEME": "Sistema"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Reproducir",
|
||||
"PAUSE": "Pausar",
|
||||
"PREVIOUS_STATION": "Emisora anterior",
|
||||
"NEXT_STATION": "Emisora siguiente",
|
||||
"MUTE": "Silenciar",
|
||||
"UNMUTE": "Activar sonido",
|
||||
"VOLUME": "Volumen"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Clair",
|
||||
"SYSTEM_THEME": "Système"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Lecture",
|
||||
"PAUSE": "Pause",
|
||||
"PREVIOUS_STATION": "Station précédente",
|
||||
"NEXT_STATION": "Station suivante",
|
||||
"MUTE": "Muet",
|
||||
"UNMUTE": "Activer le son",
|
||||
"VOLUME": "Volume"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Chiaro",
|
||||
"SYSTEM_THEME": "Sistema"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Riproduci",
|
||||
"PAUSE": "Pausa",
|
||||
"PREVIOUS_STATION": "Stazione precedente",
|
||||
"NEXT_STATION": "Stazione successiva",
|
||||
"MUTE": "Disattiva audio",
|
||||
"UNMUTE": "Attiva audio",
|
||||
"VOLUME": "Volume"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "ライト",
|
||||
"SYSTEM_THEME": "システム"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "再生",
|
||||
"PAUSE": "一時停止",
|
||||
"PREVIOUS_STATION": "前の局",
|
||||
"NEXT_STATION": "次の局",
|
||||
"MUTE": "ミュート",
|
||||
"UNMUTE": "ミュート解除",
|
||||
"VOLUME": "音量"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "밝게",
|
||||
"SYSTEM_THEME": "시스템"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "재생",
|
||||
"PAUSE": "일시정지",
|
||||
"PREVIOUS_STATION": "이전 방송국",
|
||||
"NEXT_STATION": "다음 방송국",
|
||||
"MUTE": "음소거",
|
||||
"UNMUTE": "음소거 해제",
|
||||
"VOLUME": "볼륨"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Licht",
|
||||
"SYSTEM_THEME": "Systeem"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Afspelen",
|
||||
"PAUSE": "Pauzeren",
|
||||
"PREVIOUS_STATION": "Vorige zender",
|
||||
"NEXT_STATION": "Volgende zender",
|
||||
"MUTE": "Dempen",
|
||||
"UNMUTE": "Dempen opheffen",
|
||||
"VOLUME": "Volume"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Jasny",
|
||||
"SYSTEM_THEME": "Systemowy"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Odtwórz",
|
||||
"PAUSE": "Wstrzymaj",
|
||||
"PREVIOUS_STATION": "Poprzednia stacja",
|
||||
"NEXT_STATION": "Następna stacja",
|
||||
"MUTE": "Wycisz",
|
||||
"UNMUTE": "Wyłącz wyciszenie",
|
||||
"VOLUME": "Głośność"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Claro",
|
||||
"SYSTEM_THEME": "Sistema"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Reproduzir",
|
||||
"PAUSE": "Pausar",
|
||||
"PREVIOUS_STATION": "Estação anterior",
|
||||
"NEXT_STATION": "Próxima estação",
|
||||
"MUTE": "Silenciar",
|
||||
"UNMUTE": "Ativar som",
|
||||
"VOLUME": "Volume"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Светлая",
|
||||
"SYSTEM_THEME": "Системная"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Воспроизвести",
|
||||
"PAUSE": "Пауза",
|
||||
"PREVIOUS_STATION": "Предыдущая станция",
|
||||
"NEXT_STATION": "Следующая станция",
|
||||
"MUTE": "Отключить звук",
|
||||
"UNMUTE": "Включить звук",
|
||||
"VOLUME": "Громкость"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "Контейнер потока, вероятно, не поддерживается встроенным браузерным плеером",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "Açık",
|
||||
"SYSTEM_THEME": "Sistem"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "Oynat",
|
||||
"PAUSE": "Duraklat",
|
||||
"PREVIOUS_STATION": "Önceki istasyon",
|
||||
"NEXT_STATION": "Sonraki istasyon",
|
||||
"MUTE": "Sesi kapat",
|
||||
"UNMUTE": "Sesi aç",
|
||||
"VOLUME": "Ses seviyesi"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "浅色",
|
||||
"SYSTEM_THEME": "跟随系统"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "播放",
|
||||
"PAUSE": "暂停",
|
||||
"PREVIOUS_STATION": "上一个电台",
|
||||
"NEXT_STATION": "下一个电台",
|
||||
"MUTE": "静音",
|
||||
"UNMUTE": "取消静音",
|
||||
"VOLUME": "音量"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -379,6 +379,15 @@
|
||||
"LIGHT_THEME": "淺色",
|
||||
"SYSTEM_THEME": "跟隨系統"
|
||||
},
|
||||
"AUDIO_PLAYER": {
|
||||
"PLAY": "播放",
|
||||
"PAUSE": "暫停",
|
||||
"PREVIOUS_STATION": "上一個電台",
|
||||
"NEXT_STATION": "下一個電台",
|
||||
"MUTE": "靜音",
|
||||
"UNMUTE": "取消靜音",
|
||||
"VOLUME": "音量"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
|
||||
@@ -16,7 +16,9 @@ import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSliderModule } from '@angular/material/slider';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { ChannelActions } from '@iptvnator/m3u-state';
|
||||
|
||||
@Component({
|
||||
@@ -67,6 +69,12 @@ import { ChannelActions } from '@iptvnator/m3u-state';
|
||||
<button
|
||||
mat-icon-button
|
||||
class="skip-btn"
|
||||
[matTooltip]="
|
||||
'AUDIO_PLAYER.PREVIOUS_STATION' | translate
|
||||
"
|
||||
[attr.aria-label]="
|
||||
'AUDIO_PLAYER.PREVIOUS_STATION' | translate
|
||||
"
|
||||
(click)="switchChannel('previous')"
|
||||
>
|
||||
<mat-icon>skip_previous</mat-icon>
|
||||
@@ -75,6 +83,18 @@ import { ChannelActions } from '@iptvnator/m3u-state';
|
||||
<button
|
||||
class="play-btn"
|
||||
mat-fab
|
||||
[matTooltip]="
|
||||
(playState() === 'play'
|
||||
? 'AUDIO_PLAYER.PAUSE'
|
||||
: 'AUDIO_PLAYER.PLAY'
|
||||
) | translate
|
||||
"
|
||||
[attr.aria-label]="
|
||||
(playState() === 'play'
|
||||
? 'AUDIO_PLAYER.PAUSE'
|
||||
: 'AUDIO_PLAYER.PLAY'
|
||||
) | translate
|
||||
"
|
||||
(click)="playState() === 'play' ? stop() : play()"
|
||||
>
|
||||
<mat-icon>{{
|
||||
@@ -85,6 +105,10 @@ import { ChannelActions } from '@iptvnator/m3u-state';
|
||||
<button
|
||||
mat-icon-button
|
||||
class="skip-btn"
|
||||
[matTooltip]="'AUDIO_PLAYER.NEXT_STATION' | translate"
|
||||
[attr.aria-label]="
|
||||
'AUDIO_PLAYER.NEXT_STATION' | translate
|
||||
"
|
||||
(click)="switchChannel('next')"
|
||||
>
|
||||
<mat-icon>skip_next</mat-icon>
|
||||
@@ -92,13 +116,38 @@ import { ChannelActions } from '@iptvnator/m3u-state';
|
||||
</div>
|
||||
|
||||
<div class="volume-row">
|
||||
<button mat-icon-button class="vol-icon" (click)="mute()">
|
||||
<button
|
||||
mat-icon-button
|
||||
class="vol-icon"
|
||||
[matTooltip]="
|
||||
(isMuted() || volume() === 0
|
||||
? 'AUDIO_PLAYER.UNMUTE'
|
||||
: 'AUDIO_PLAYER.MUTE'
|
||||
) | translate
|
||||
"
|
||||
[attr.aria-label]="
|
||||
(isMuted() || volume() === 0
|
||||
? 'AUDIO_PLAYER.UNMUTE'
|
||||
: 'AUDIO_PLAYER.MUTE'
|
||||
) | translate
|
||||
"
|
||||
(click)="mute()"
|
||||
>
|
||||
<mat-icon>{{ volumeIcon() }}</mat-icon>
|
||||
</button>
|
||||
<mat-slider class="vol-slider" min="0" max="1" step="0.05">
|
||||
<mat-slider
|
||||
class="vol-slider"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
[matTooltip]="'AUDIO_PLAYER.VOLUME' | translate"
|
||||
>
|
||||
<input
|
||||
matSliderThumb
|
||||
[ngModel]="volume()"
|
||||
[attr.aria-label]="
|
||||
'AUDIO_PLAYER.VOLUME' | translate
|
||||
"
|
||||
(ngModelChange)="setVolume($event)"
|
||||
/>
|
||||
</mat-slider>
|
||||
@@ -109,7 +158,14 @@ import { ChannelActions } from '@iptvnator/m3u-state';
|
||||
</div>
|
||||
`,
|
||||
styleUrls: ['./audio-player.component.scss'],
|
||||
imports: [MatSliderModule, MatIconModule, MatButtonModule, FormsModule],
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatSliderModule,
|
||||
MatTooltip,
|
||||
TranslatePipe,
|
||||
],
|
||||
})
|
||||
export class AudioPlayerComponent {
|
||||
readonly icon = input<string>('');
|
||||
|
||||
Reference in new issue
Block a user