ts-video-player 0.1.7 → 0.1.9

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.
@@ -16,7 +16,7 @@ import {
16
16
  detectPipAvailability2,
17
17
  enterPiP2,
18
18
  exitPiP2
19
- } from "./chunk-rhykj0qy.js";
19
+ } from "./chunk-pvq6e7gz.js";
20
20
 
21
21
  // src/core/media-session.ts
22
22
  function isMediaSessionSupported2() {
@@ -1026,8 +1026,9 @@ class VimeoProvider2 extends BaseProvider2 {
1026
1026
  return;
1027
1027
  }
1028
1028
  if (this.player) {
1029
- await this.player.destroy();
1030
- this.player = null;
1029
+ await this.player.loadVideo(parseInt(parsed.videoId, 10));
1030
+ await this.announceLoaded();
1031
+ return;
1031
1032
  }
1032
1033
  const playerOptions = {
1033
1034
  id: parseInt(parsed.videoId, 10),
@@ -1051,6 +1052,11 @@ class VimeoProvider2 extends BaseProvider2 {
1051
1052
  this.iframe.style.cssText = "position:absolute;inset:0;width:100%;height:100%;border:0";
1052
1053
  }
1053
1054
  this.attachVimeoEvents();
1055
+ await this.announceLoaded();
1056
+ }
1057
+ async announceLoaded() {
1058
+ if (!this.player)
1059
+ return;
1054
1060
  this.events.emit("loadedmetadata");
1055
1061
  this.events.emit("canplay");
1056
1062
  this._duration = await this.player.getDuration();
@@ -2042,6 +2048,630 @@ function embedCover(state, dismissed) {
2042
2048
  return { show: true, image: youtubeId ? `https://i.ytimg.com/vi/${youtubeId}/hqdefault.jpg` : "" };
2043
2049
  }
2044
2050
 
2051
+ // src/core/i18n.ts
2052
+ var englishStrings2 = {
2053
+ play: "Play",
2054
+ pause: "Pause",
2055
+ replay: "Replay",
2056
+ mute: "Mute",
2057
+ unmute: "Unmute",
2058
+ volume: "Volume",
2059
+ seekBackward: "Seek backward {seconds} seconds",
2060
+ seekForward: "Seek forward {seconds} seconds",
2061
+ seekTo: "Seek to {time}",
2062
+ duration: "Duration",
2063
+ currentTime: "Current time",
2064
+ played: "Played",
2065
+ buffered: "Buffered",
2066
+ enterFullscreen: "Enter fullscreen",
2067
+ exitFullscreen: "Exit fullscreen",
2068
+ enterPiP: "Enter picture-in-picture",
2069
+ exitPiP: "Exit picture-in-picture",
2070
+ enableCaptions: "Enable captions",
2071
+ disableCaptions: "Disable captions",
2072
+ captions: "Captions",
2073
+ captionsOff: "Off",
2074
+ settings: "Settings",
2075
+ quality: "Quality",
2076
+ auto: "Auto",
2077
+ speed: "Speed",
2078
+ normal: "Normal",
2079
+ loop: "Loop",
2080
+ loopEnabled: "Loop enabled",
2081
+ loopDisabled: "Loop disabled",
2082
+ audioBoost: "Audio boost",
2083
+ audioTrack: "Audio track",
2084
+ live: "LIVE",
2085
+ liveEdge: "At live edge",
2086
+ seekToLive: "Seek to live edge",
2087
+ behindLive: "{seconds}s behind",
2088
+ networkError: "A network error occurred",
2089
+ mediaError: "The media could not be loaded",
2090
+ unknownError: "An unknown error occurred",
2091
+ loading: "Loading",
2092
+ buffering: "Buffering",
2093
+ chapters: "Chapters",
2094
+ airPlay: "AirPlay",
2095
+ googleCast: "Cast",
2096
+ casting: "Casting to {device}",
2097
+ download: "Download",
2098
+ share: "Share",
2099
+ close: "Close",
2100
+ menu: "Menu",
2101
+ back: "Back",
2102
+ seek: "Seek",
2103
+ playbackRate: "Playback speed {rate}x",
2104
+ airPlayPlay: "Play on AirPlay",
2105
+ remotePlayback: "Play on another device",
2106
+ videoPlayer: "Video player",
2107
+ videoRole: "video player",
2108
+ playTitle: "Play {title}",
2109
+ audio: "Audio",
2110
+ defaultTrack: "Default",
2111
+ playing: "Playing",
2112
+ paused: "Paused",
2113
+ muted: "Muted",
2114
+ volumeLevel: "Volume {percent}%",
2115
+ ended: "Playback ended",
2116
+ enteredFullscreen: "Entered fullscreen",
2117
+ exitedFullscreen: "Exited fullscreen",
2118
+ errorMessage: "Error: {message}",
2119
+ timeOf: "{current} of {duration}",
2120
+ hourOne: "{n} hour",
2121
+ hourOther: "{n} hours",
2122
+ minuteOne: "{n} minute",
2123
+ minuteOther: "{n} minutes",
2124
+ secondOne: "{n} second",
2125
+ secondOther: "{n} seconds"
2126
+ };
2127
+ var spanishStrings2 = {
2128
+ play: "Reproducir",
2129
+ pause: "Pausar",
2130
+ replay: "Repetir",
2131
+ mute: "Silenciar",
2132
+ unmute: "Activar sonido",
2133
+ volume: "Volumen",
2134
+ seekBackward: "Retroceder {seconds} segundos",
2135
+ seekForward: "Avanzar {seconds} segundos",
2136
+ seekTo: "Ir a {time}",
2137
+ duration: "Duración",
2138
+ currentTime: "Tiempo actual",
2139
+ played: "Reproducido",
2140
+ buffered: "Almacenado",
2141
+ enterFullscreen: "Pantalla completa",
2142
+ exitFullscreen: "Salir de pantalla completa",
2143
+ enterPiP: "Imagen en imagen",
2144
+ exitPiP: "Salir de imagen en imagen",
2145
+ enableCaptions: "Activar subtítulos",
2146
+ disableCaptions: "Desactivar subtítulos",
2147
+ captions: "Subtítulos",
2148
+ captionsOff: "Desactivados",
2149
+ settings: "Ajustes",
2150
+ quality: "Calidad",
2151
+ auto: "Automático",
2152
+ speed: "Velocidad",
2153
+ normal: "Normal",
2154
+ loop: "Bucle",
2155
+ loopEnabled: "Bucle activado",
2156
+ loopDisabled: "Bucle desactivado",
2157
+ audioBoost: "Aumento de audio",
2158
+ audioTrack: "Pista de audio",
2159
+ live: "EN VIVO",
2160
+ liveEdge: "En vivo",
2161
+ seekToLive: "Ir al directo",
2162
+ behindLive: "{seconds}s de retraso",
2163
+ networkError: "Error de red",
2164
+ mediaError: "No se pudo cargar el medio",
2165
+ unknownError: "Error desconocido",
2166
+ loading: "Cargando",
2167
+ buffering: "Almacenando",
2168
+ chapters: "Capítulos",
2169
+ airPlay: "AirPlay",
2170
+ googleCast: "Enviar",
2171
+ casting: "Enviando a {device}",
2172
+ download: "Descargar",
2173
+ share: "Compartir",
2174
+ close: "Cerrar",
2175
+ menu: "Menú",
2176
+ back: "Volver",
2177
+ seek: "Buscar",
2178
+ playbackRate: "Velocidad de reproducción {rate}x",
2179
+ airPlayPlay: "Reproducir con AirPlay",
2180
+ remotePlayback: "Reproducir en otro dispositivo",
2181
+ videoPlayer: "Reproductor de vídeo",
2182
+ videoRole: "reproductor de vídeo",
2183
+ playTitle: "Reproducir {title}",
2184
+ audio: "Audio",
2185
+ defaultTrack: "Predeterminado",
2186
+ playing: "Reproduciendo",
2187
+ paused: "En pausa",
2188
+ muted: "Silenciado",
2189
+ volumeLevel: "Volumen {percent}%",
2190
+ ended: "Reproducción finalizada",
2191
+ enteredFullscreen: "Pantalla completa activada",
2192
+ exitedFullscreen: "Pantalla completa desactivada",
2193
+ errorMessage: "Error: {message}",
2194
+ timeOf: "{current} de {duration}",
2195
+ hourOne: "{n} hora",
2196
+ hourOther: "{n} horas",
2197
+ minuteOne: "{n} minuto",
2198
+ minuteOther: "{n} minutos",
2199
+ secondOne: "{n} segundo",
2200
+ secondOther: "{n} segundos"
2201
+ };
2202
+ var frenchStrings2 = {
2203
+ play: "Lecture",
2204
+ pause: "Pause",
2205
+ replay: "Relire",
2206
+ mute: "Muet",
2207
+ unmute: "Son activé",
2208
+ volume: "Volume",
2209
+ seekBackward: "Reculer de {seconds} secondes",
2210
+ seekForward: "Avancer de {seconds} secondes",
2211
+ seekTo: "Aller à {time}",
2212
+ duration: "Durée",
2213
+ currentTime: "Temps actuel",
2214
+ played: "Joué",
2215
+ buffered: "Mis en mémoire tampon",
2216
+ enterFullscreen: "Plein écran",
2217
+ exitFullscreen: "Quitter le plein écran",
2218
+ enterPiP: "Image dans l'image",
2219
+ exitPiP: "Quitter l'image dans l'image",
2220
+ enableCaptions: "Activer les sous-titres",
2221
+ disableCaptions: "Désactiver les sous-titres",
2222
+ captions: "Sous-titres",
2223
+ captionsOff: "Désactivés",
2224
+ settings: "Paramètres",
2225
+ quality: "Qualité",
2226
+ auto: "Auto",
2227
+ speed: "Vitesse",
2228
+ normal: "Normal",
2229
+ loop: "Boucle",
2230
+ loopEnabled: "Boucle activée",
2231
+ loopDisabled: "Boucle désactivée",
2232
+ audioBoost: "Amplification audio",
2233
+ audioTrack: "Piste audio",
2234
+ live: "EN DIRECT",
2235
+ liveEdge: "En direct",
2236
+ seekToLive: "Aller au direct",
2237
+ behindLive: "{seconds}s de retard",
2238
+ networkError: "Erreur réseau",
2239
+ mediaError: "Le média n'a pas pu être chargé",
2240
+ unknownError: "Erreur inconnue",
2241
+ loading: "Chargement",
2242
+ buffering: "Mise en mémoire tampon",
2243
+ chapters: "Chapitres",
2244
+ airPlay: "AirPlay",
2245
+ googleCast: "Caster",
2246
+ casting: "Diffusion sur {device}",
2247
+ download: "Télécharger",
2248
+ share: "Partager",
2249
+ close: "Fermer",
2250
+ menu: "Menu",
2251
+ back: "Retour",
2252
+ seek: "Rechercher",
2253
+ playbackRate: "Vitesse de lecture {rate}x",
2254
+ airPlayPlay: "Lire avec AirPlay",
2255
+ remotePlayback: "Lire sur un autre appareil",
2256
+ videoPlayer: "Lecteur vidéo",
2257
+ videoRole: "lecteur vidéo",
2258
+ playTitle: "Lire {title}",
2259
+ audio: "Audio",
2260
+ defaultTrack: "Par défaut",
2261
+ playing: "Lecture",
2262
+ paused: "En pause",
2263
+ muted: "Son coupé",
2264
+ volumeLevel: "Volume {percent} %",
2265
+ ended: "Lecture terminée",
2266
+ enteredFullscreen: "Plein écran activé",
2267
+ exitedFullscreen: "Plein écran désactivé",
2268
+ errorMessage: "Erreur : {message}",
2269
+ timeOf: "{current} sur {duration}",
2270
+ hourOne: "{n} heure",
2271
+ hourOther: "{n} heures",
2272
+ minuteOne: "{n} minute",
2273
+ minuteOther: "{n} minutes",
2274
+ secondOne: "{n} seconde",
2275
+ secondOther: "{n} secondes"
2276
+ };
2277
+ var germanStrings2 = {
2278
+ play: "Abspielen",
2279
+ pause: "Pause",
2280
+ replay: "Wiederholen",
2281
+ mute: "Stumm",
2282
+ unmute: "Ton an",
2283
+ volume: "Lautstärke",
2284
+ seekBackward: "{seconds} Sekunden zurück",
2285
+ seekForward: "{seconds} Sekunden vor",
2286
+ seekTo: "Zu {time} springen",
2287
+ duration: "Dauer",
2288
+ currentTime: "Aktuelle Zeit",
2289
+ played: "Abgespielt",
2290
+ buffered: "Gepuffert",
2291
+ enterFullscreen: "Vollbild",
2292
+ exitFullscreen: "Vollbild beenden",
2293
+ enterPiP: "Bild-in-Bild",
2294
+ exitPiP: "Bild-in-Bild beenden",
2295
+ enableCaptions: "Untertitel aktivieren",
2296
+ disableCaptions: "Untertitel deaktivieren",
2297
+ captions: "Untertitel",
2298
+ captionsOff: "Aus",
2299
+ settings: "Einstellungen",
2300
+ quality: "Qualität",
2301
+ auto: "Auto",
2302
+ speed: "Geschwindigkeit",
2303
+ normal: "Normal",
2304
+ loop: "Schleife",
2305
+ loopEnabled: "Schleife aktiviert",
2306
+ loopDisabled: "Schleife deaktiviert",
2307
+ audioBoost: "Audio-Verstärkung",
2308
+ audioTrack: "Audiospur",
2309
+ live: "LIVE",
2310
+ liveEdge: "Am Live-Punkt",
2311
+ seekToLive: "Zum Live-Punkt",
2312
+ behindLive: "{seconds}s hinter Live",
2313
+ networkError: "Netzwerkfehler",
2314
+ mediaError: "Medium konnte nicht geladen werden",
2315
+ unknownError: "Unbekannter Fehler",
2316
+ loading: "Wird geladen",
2317
+ buffering: "Puffern",
2318
+ chapters: "Kapitel",
2319
+ airPlay: "AirPlay",
2320
+ googleCast: "Streamen",
2321
+ casting: "Streame zu {device}",
2322
+ download: "Herunterladen",
2323
+ share: "Teilen",
2324
+ close: "Schließen",
2325
+ menu: "Menü",
2326
+ back: "Zurück",
2327
+ seek: "Spulen",
2328
+ playbackRate: "Wiedergabegeschwindigkeit {rate}x",
2329
+ airPlayPlay: "Über AirPlay abspielen",
2330
+ remotePlayback: "Auf einem anderen Gerät abspielen",
2331
+ videoPlayer: "Videoplayer",
2332
+ videoRole: "Videoplayer",
2333
+ playTitle: "{title} abspielen",
2334
+ audio: "Audio",
2335
+ defaultTrack: "Standard",
2336
+ playing: "Wird abgespielt",
2337
+ paused: "Pausiert",
2338
+ muted: "Stumm",
2339
+ volumeLevel: "Lautstärke {percent} %",
2340
+ ended: "Wiedergabe beendet",
2341
+ enteredFullscreen: "Vollbild an",
2342
+ exitedFullscreen: "Vollbild aus",
2343
+ errorMessage: "Fehler: {message}",
2344
+ timeOf: "{current} von {duration}",
2345
+ hourOne: "{n} Stunde",
2346
+ hourOther: "{n} Stunden",
2347
+ minuteOne: "{n} Minute",
2348
+ minuteOther: "{n} Minuten",
2349
+ secondOne: "{n} Sekunde",
2350
+ secondOther: "{n} Sekunden"
2351
+ };
2352
+ var japaneseStrings2 = {
2353
+ play: "再生",
2354
+ pause: "一時停止",
2355
+ replay: "リプレイ",
2356
+ mute: "ミュート",
2357
+ unmute: "ミュート解除",
2358
+ volume: "音量",
2359
+ seekBackward: "{seconds}秒戻る",
2360
+ seekForward: "{seconds}秒進む",
2361
+ seekTo: "{time}にシーク",
2362
+ duration: "長さ",
2363
+ currentTime: "現在の時間",
2364
+ played: "再生済み",
2365
+ buffered: "バッファ済み",
2366
+ enterFullscreen: "フルスクリーン",
2367
+ exitFullscreen: "フルスクリーン終了",
2368
+ enterPiP: "ピクチャーインピクチャー",
2369
+ exitPiP: "ピクチャーインピクチャー終了",
2370
+ enableCaptions: "字幕を有効化",
2371
+ disableCaptions: "字幕を無効化",
2372
+ captions: "字幕",
2373
+ captionsOff: "オフ",
2374
+ settings: "設定",
2375
+ quality: "画質",
2376
+ auto: "自動",
2377
+ speed: "再生速度",
2378
+ normal: "通常",
2379
+ loop: "ループ",
2380
+ loopEnabled: "ループ有効",
2381
+ loopDisabled: "ループ無効",
2382
+ audioBoost: "オーディオブースト",
2383
+ audioTrack: "オーディオトラック",
2384
+ live: "ライブ",
2385
+ liveEdge: "ライブ中",
2386
+ seekToLive: "ライブに移動",
2387
+ behindLive: "{seconds}秒遅れ",
2388
+ networkError: "ネットワークエラー",
2389
+ mediaError: "メディアを読み込めません",
2390
+ unknownError: "不明なエラー",
2391
+ loading: "読み込み中",
2392
+ buffering: "バッファリング",
2393
+ chapters: "チャプター",
2394
+ airPlay: "AirPlay",
2395
+ googleCast: "キャスト",
2396
+ casting: "{device}にキャスト中",
2397
+ download: "ダウンロード",
2398
+ share: "共有",
2399
+ close: "閉じる",
2400
+ menu: "メニュー",
2401
+ back: "戻る",
2402
+ seek: "シーク",
2403
+ playbackRate: "再生速度 {rate}x",
2404
+ airPlayPlay: "AirPlayで再生",
2405
+ remotePlayback: "別のデバイスで再生",
2406
+ videoPlayer: "動画プレーヤー",
2407
+ videoRole: "動画プレーヤー",
2408
+ playTitle: "{title}を再生",
2409
+ audio: "音声",
2410
+ defaultTrack: "デフォルト",
2411
+ playing: "再生中",
2412
+ paused: "一時停止中",
2413
+ muted: "ミュート",
2414
+ volumeLevel: "音量 {percent}%",
2415
+ ended: "再生終了",
2416
+ enteredFullscreen: "全画面表示",
2417
+ exitedFullscreen: "全画面表示を終了",
2418
+ errorMessage: "エラー: {message}",
2419
+ timeOf: "{current} / {duration}",
2420
+ hourOne: "{n}時間",
2421
+ hourOther: "{n}時間",
2422
+ minuteOne: "{n}分",
2423
+ minuteOther: "{n}分",
2424
+ secondOne: "{n}秒",
2425
+ secondOther: "{n}秒"
2426
+ };
2427
+ var chineseStrings2 = {
2428
+ play: "播放",
2429
+ pause: "暂停",
2430
+ replay: "重播",
2431
+ mute: "静音",
2432
+ unmute: "取消静音",
2433
+ volume: "音量",
2434
+ seekBackward: "后退{seconds}秒",
2435
+ seekForward: "前进{seconds}秒",
2436
+ seekTo: "跳转到{time}",
2437
+ duration: "时长",
2438
+ currentTime: "当前时间",
2439
+ played: "已播放",
2440
+ buffered: "已缓冲",
2441
+ enterFullscreen: "全屏",
2442
+ exitFullscreen: "退出全屏",
2443
+ enterPiP: "画中画",
2444
+ exitPiP: "退出画中画",
2445
+ enableCaptions: "开启字幕",
2446
+ disableCaptions: "关闭字幕",
2447
+ captions: "字幕",
2448
+ captionsOff: "关闭",
2449
+ settings: "设置",
2450
+ quality: "画质",
2451
+ auto: "自动",
2452
+ speed: "播放速度",
2453
+ normal: "正常",
2454
+ loop: "循环",
2455
+ loopEnabled: "循环已开启",
2456
+ loopDisabled: "循环已关闭",
2457
+ audioBoost: "音频增强",
2458
+ audioTrack: "音轨",
2459
+ live: "直播",
2460
+ liveEdge: "直播中",
2461
+ seekToLive: "跳转到直播",
2462
+ behindLive: "落后{seconds}秒",
2463
+ networkError: "网络错误",
2464
+ mediaError: "无法加载媒体",
2465
+ unknownError: "未知错误",
2466
+ loading: "加载中",
2467
+ buffering: "缓冲中",
2468
+ chapters: "章节",
2469
+ airPlay: "AirPlay",
2470
+ googleCast: "投射",
2471
+ casting: "正在投射到{device}",
2472
+ download: "下载",
2473
+ share: "分享",
2474
+ close: "关闭",
2475
+ menu: "菜单",
2476
+ back: "返回",
2477
+ seek: "跳转",
2478
+ playbackRate: "播放速度 {rate}x",
2479
+ airPlayPlay: "通过 AirPlay 播放",
2480
+ remotePlayback: "在其他设备上播放",
2481
+ videoPlayer: "视频播放器",
2482
+ videoRole: "视频播放器",
2483
+ playTitle: "播放{title}",
2484
+ audio: "音频",
2485
+ defaultTrack: "默认",
2486
+ playing: "正在播放",
2487
+ paused: "已暂停",
2488
+ muted: "已静音",
2489
+ volumeLevel: "音量 {percent}%",
2490
+ ended: "播放结束",
2491
+ enteredFullscreen: "已进入全屏",
2492
+ exitedFullscreen: "已退出全屏",
2493
+ errorMessage: "错误:{message}",
2494
+ timeOf: "{current} / {duration}",
2495
+ hourOne: "{n}小时",
2496
+ hourOther: "{n}小时",
2497
+ minuteOne: "{n}分钟",
2498
+ minuteOther: "{n}分钟",
2499
+ secondOne: "{n}秒",
2500
+ secondOther: "{n}秒"
2501
+ };
2502
+ var defaultTranslations2 = {
2503
+ en: englishStrings2,
2504
+ es: spanishStrings2,
2505
+ fr: frenchStrings2,
2506
+ de: germanStrings2,
2507
+ ja: japaneseStrings2,
2508
+ zh: chineseStrings2
2509
+ };
2510
+
2511
+ class I18n2 {
2512
+ language;
2513
+ fallbackLanguage;
2514
+ translations;
2515
+ interpolationPattern;
2516
+ constructor(config = {}) {
2517
+ this.translations = { ...defaultTranslations2 };
2518
+ this.language = config.language || this.detectLanguage() || "en";
2519
+ this.fallbackLanguage = config.fallbackLanguage || "en";
2520
+ if (config.translations) {
2521
+ for (const [lang, strings] of Object.entries(config.translations)) {
2522
+ if (strings) {
2523
+ this.translations[lang] = { ...this.translations[lang], ...strings };
2524
+ }
2525
+ }
2526
+ }
2527
+ this.interpolationPattern = config.interpolationPattern || /\{(\w+)\}/g;
2528
+ }
2529
+ detectLanguage() {
2530
+ if (typeof navigator === "undefined")
2531
+ return null;
2532
+ const lang = navigator.language || navigator.userLanguage;
2533
+ if (!lang)
2534
+ return null;
2535
+ if (this.translations[lang]) {
2536
+ return lang;
2537
+ }
2538
+ const baseLang = lang.split("-")[0];
2539
+ if (this.translations[baseLang]) {
2540
+ return baseLang;
2541
+ }
2542
+ return null;
2543
+ }
2544
+ getLanguage() {
2545
+ return this.language;
2546
+ }
2547
+ setLanguage(language) {
2548
+ this.language = language;
2549
+ }
2550
+ t(key, params) {
2551
+ let str = this.translations[this.language]?.[key];
2552
+ if (!str) {
2553
+ str = this.translations[this.fallbackLanguage]?.[key];
2554
+ }
2555
+ if (!str) {
2556
+ return key;
2557
+ }
2558
+ if (params) {
2559
+ str = str.replace(this.interpolationPattern, (match, paramKey) => {
2560
+ const value = params[paramKey];
2561
+ return value !== undefined ? String(value) : match;
2562
+ });
2563
+ }
2564
+ return str;
2565
+ }
2566
+ addTranslations(language, strings) {
2567
+ this.translations[language] = {
2568
+ ...this.translations[language],
2569
+ ...strings
2570
+ };
2571
+ }
2572
+ getTranslations(language) {
2573
+ return this.translations[language];
2574
+ }
2575
+ getAvailableLanguages() {
2576
+ return Object.keys(this.translations);
2577
+ }
2578
+ hasLanguage(language) {
2579
+ return language in this.translations;
2580
+ }
2581
+ }
2582
+ var globalI18n = null;
2583
+ function createI18n2(config) {
2584
+ return new I18n2(config);
2585
+ }
2586
+ function getI18n2(config) {
2587
+ if (!globalI18n) {
2588
+ globalI18n = createI18n2(config);
2589
+ }
2590
+ return globalI18n;
2591
+ }
2592
+ function setI18n2(i18n) {
2593
+ globalI18n = i18n;
2594
+ }
2595
+ function t2(key, params) {
2596
+ return getI18n2().t(key, params);
2597
+ }
2598
+
2599
+ // src/elements/utils.ts
2600
+ function languageOf(el) {
2601
+ let node = el;
2602
+ while (node) {
2603
+ const tagged = node.closest?.("[lang]")?.getAttribute("lang");
2604
+ if (tagged)
2605
+ return tagged.toLowerCase();
2606
+ const root = node.getRootNode?.();
2607
+ node = root && root.host ? root.host : null;
2608
+ }
2609
+ if (typeof navigator !== "undefined" && navigator.language)
2610
+ return navigator.language.toLowerCase();
2611
+ return "en";
2612
+ }
2613
+ function label(el, key, params) {
2614
+ const lang = languageOf(el);
2615
+ const i18n = getI18n2();
2616
+ const text = i18n.getTranslations(lang)?.[key] || i18n.getTranslations(lang.split("-")[0])?.[key] || englishStrings2[key] || key;
2617
+ return params ? text.replace(/\{(\w+)\}/g, (match, name) => params[name] !== undefined ? String(params[name]) : match) : text;
2618
+ }
2619
+ function formatTime2(seconds, guide = 0) {
2620
+ if (!isFinite(seconds) || isNaN(seconds))
2621
+ return "0:00";
2622
+ const s = Math.floor(seconds);
2623
+ const h = Math.floor(s / 3600);
2624
+ const m = Math.floor(s % 3600 / 60);
2625
+ const sec = s % 60;
2626
+ const showHours = h > 0 || guide >= 3600;
2627
+ if (showHours) {
2628
+ return `${h}:${m.toString().padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
2629
+ }
2630
+ return `${m}:${sec.toString().padStart(2, "0")}`;
2631
+ }
2632
+ function formatTimePhrase2(seconds, el) {
2633
+ const say = (n, one, other) => label(el, n === 1 ? one : other, { n });
2634
+ if (!isFinite(seconds) || isNaN(seconds))
2635
+ return say(0, "secondOne", "secondOther");
2636
+ const s = Math.floor(seconds);
2637
+ const h = Math.floor(s / 3600);
2638
+ const m = Math.floor(s % 3600 / 60);
2639
+ const sec = s % 60;
2640
+ const parts = [];
2641
+ if (h > 0)
2642
+ parts.push(say(h, "hourOne", "hourOther"));
2643
+ if (m > 0)
2644
+ parts.push(say(m, "minuteOne", "minuteOther"));
2645
+ if (sec > 0 || parts.length === 0)
2646
+ parts.push(say(sec, "secondOne", "secondOther"));
2647
+ return parts.join(", ");
2648
+ }
2649
+ function toISODuration2(seconds) {
2650
+ if (!isFinite(seconds) || isNaN(seconds))
2651
+ return "PT0S";
2652
+ const s = Math.floor(seconds);
2653
+ const h = Math.floor(s / 3600);
2654
+ const m = Math.floor(s % 3600 / 60);
2655
+ const sec = s % 60;
2656
+ let result = "PT";
2657
+ if (h > 0)
2658
+ result += `${h}H`;
2659
+ if (m > 0)
2660
+ result += `${m}M`;
2661
+ if (sec > 0 || result === "PT")
2662
+ result += `${sec}S`;
2663
+ return result;
2664
+ }
2665
+ function resolvePlayer2(el) {
2666
+ const forId = el.getAttribute("for");
2667
+ if (forId) {
2668
+ const target = document.getElementById(forId);
2669
+ return target?.player ?? null;
2670
+ }
2671
+ const host = el.closest("video-player");
2672
+ return host?.player ?? null;
2673
+ }
2674
+
2045
2675
  // src/player.ts
2046
2676
  class Player2 {
2047
2677
  _el;
@@ -2098,8 +2728,8 @@ class Player2 {
2098
2728
  this._el.classList.add("ts-video-player");
2099
2729
  this._el.setAttribute("tabindex", "0");
2100
2730
  this._el.setAttribute("role", "region");
2101
- this._el.setAttribute("aria-roledescription", "video player");
2102
- this._el.setAttribute("aria-label", this._options.title || "Video player");
2731
+ this._el.setAttribute("aria-roledescription", label(this._el, "videoRole"));
2732
+ this._el.setAttribute("aria-label", this._options.title || label(this._el, "videoPlayer"));
2103
2733
  this._el.setAttribute("aria-keyshortcuts", "Space K M F I C ArrowLeft ArrowRight J L ArrowUp ArrowDown Home End 0 1 2 3 4 5 6 7 8 9");
2104
2734
  const mediaContainer = document.createElement("div");
2105
2735
  mediaContainer.className = "ts-video-player__container";
@@ -2123,7 +2753,7 @@ class Player2 {
2123
2753
  const cover = document.createElement("button");
2124
2754
  cover.type = "button";
2125
2755
  cover.className = "ts-video-player__cover";
2126
- cover.setAttribute("aria-label", `Play ${this._options.title || "video"}`);
2756
+ cover.setAttribute("aria-label", this._options.title ? label(this._el, "playTitle", { title: this._options.title }) : label(this._el, "play"));
2127
2757
  cover.hidden = true;
2128
2758
  cover.style.cssText = "position:absolute;inset:0;z-index:2;display:none;align-items:center;justify-content:center;width:100%;height:100%;margin:0;padding:0;border:0;border-radius:inherit;background:#000 center/cover no-repeat;cursor:pointer;-webkit-tap-highlight-color:transparent";
2129
2759
  const button = document.createElement("span");
@@ -2218,6 +2848,8 @@ class Player2 {
2218
2848
  toggleCaptions: () => this.toggleCaptions(),
2219
2849
  seekBackward: () => this.seekBy(-5),
2220
2850
  seekForward: () => this.seekBy(5),
2851
+ seekBackwardLong: () => this.seekBy(-10),
2852
+ seekForwardLong: () => this.seekBy(10),
2221
2853
  volumeUp: () => this.setVolume(Math.min(1, this.state.volume + 0.1)),
2222
2854
  volumeDown: () => this.setVolume(Math.max(0, this.state.volume - 0.1)),
2223
2855
  speedUp: () => this.setPlaybackRate(Math.min(4, this.state.playbackRate + 0.25)),
@@ -2348,7 +2980,7 @@ class Player2 {
2348
2980
  return;
2349
2981
  }
2350
2982
  this._coverDismissed = false;
2351
- this._playRequested = false;
2983
+ this._playRequested = !!this._options.autoplay;
2352
2984
  this._store.batch({
2353
2985
  sources,
2354
2986
  src: sources[0],
@@ -2408,7 +3040,7 @@ class Player2 {
2408
3040
  });
2409
3041
  if (this._playRequested) {
2410
3042
  this._playRequested = false;
2411
- this._provider.play().catch(() => {});
3043
+ this.startRequestedPlay(this._provider);
2412
3044
  }
2413
3045
  return;
2414
3046
  } catch (error) {
@@ -2455,12 +3087,12 @@ class Player2 {
2455
3087
  provider.on("play", () => {
2456
3088
  this._store.batch({ paused: false, ended: false });
2457
3089
  this._events.emit("play");
2458
- this.announce("Playing");
3090
+ this.announce(label(this._el, "playing"));
2459
3091
  });
2460
3092
  provider.on("pause", () => {
2461
- this._store.set("paused", true);
3093
+ this._store.batch({ paused: true, playing: false, playbackState: "paused" });
2462
3094
  this._events.emit("pause");
2463
- this.announce("Paused");
3095
+ this.announce(label(this._el, "paused"));
2464
3096
  });
2465
3097
  provider.on("playing", () => {
2466
3098
  this._store.batch({ playing: true, started: true, waiting: false, playbackState: "playing" });
@@ -2490,9 +3122,9 @@ class Player2 {
2490
3122
  this._store.batch({ volume, muted });
2491
3123
  this._events.emit("volumechange", volume, muted);
2492
3124
  if (muted) {
2493
- this.announce("Muted");
3125
+ this.announce(label(this._el, "muted"));
2494
3126
  } else {
2495
- this.announce(`Volume ${Math.round(volume * 100)}%`);
3127
+ this.announce(label(this._el, "volumeLevel", { percent: Math.round(volume * 100) }));
2496
3128
  }
2497
3129
  });
2498
3130
  provider.on("ratechange", (rate) => {
@@ -2509,7 +3141,7 @@ class Player2 {
2509
3141
  provider.on("ended", () => {
2510
3142
  this._store.batch({ ended: true, playing: false, playbackState: "ended" });
2511
3143
  this._events.emit("ended");
2512
- this.announce("Playback ended");
3144
+ this.announce(label(this._el, "ended"));
2513
3145
  if (this.state.loop) {
2514
3146
  this.seekTo(0);
2515
3147
  this.play();
@@ -2518,7 +3150,7 @@ class Player2 {
2518
3150
  provider.on("error", (error) => {
2519
3151
  this._store.batch({ loadingState: "error", error });
2520
3152
  this._events.emit("error", error);
2521
- this.announce(`Error: ${error.message}`);
3153
+ this.announce(label(this._el, "errorMessage", { message: error.message }));
2522
3154
  });
2523
3155
  provider.on("statechange", (state) => {
2524
3156
  this._store.batch(state);
@@ -2531,7 +3163,7 @@ class Player2 {
2531
3163
  provider.on("fullscreenchange", (fullscreen) => {
2532
3164
  this._store.set("fullscreen", fullscreen);
2533
3165
  this._events.emit("fullscreenchange", fullscreen);
2534
- this.announce(fullscreen ? "Entered fullscreen" : "Exited fullscreen");
3166
+ this.announce(label(this._el, fullscreen ? "enteredFullscreen" : "exitedFullscreen"));
2535
3167
  });
2536
3168
  provider.on("pipchange", (pip) => {
2537
3169
  this._store.set("pictureInPicture", pip);
@@ -2547,6 +3179,14 @@ class Player2 {
2547
3179
  this._events.emit("availabilitychange", feature, availability);
2548
3180
  });
2549
3181
  }
3182
+ async startRequestedPlay(provider) {
3183
+ await provider.play().catch(() => {});
3184
+ if (this._provider !== provider || this.state.error?.code !== 1 || this.state.muted)
3185
+ return;
3186
+ this._store.set("error", null);
3187
+ this.setMuted(true);
3188
+ await provider.play().catch(() => {});
3189
+ }
2550
3190
  async play() {
2551
3191
  if (!this._provider) {
2552
3192
  this._playRequested = true;
@@ -2719,7 +3359,7 @@ class VideoPlayerElement2 extends HTMLElementBase {
2719
3359
  this._disconnectId++;
2720
3360
  if (this._player)
2721
3361
  return;
2722
- const shadow = this.shadowRoot ?? this.attachShadow({ mode: "open" });
3362
+ const shadow = this.shadowRoot ?? this.attachShadow({ mode: "open", delegatesFocus: true });
2723
3363
  this._container = document.createElement("div");
2724
3364
  this._container.setAttribute("part", "container");
2725
3365
  this._container.style.cssText = "position:relative;width:100%;height:100%;";
@@ -2871,62 +3511,6 @@ class VideoPlayerElement2 extends HTMLElementBase {
2871
3511
  }
2872
3512
  }
2873
3513
  }
2874
- // src/elements/utils.ts
2875
- function formatTime2(seconds, guide = 0) {
2876
- if (!isFinite(seconds) || isNaN(seconds))
2877
- return "0:00";
2878
- const s = Math.floor(seconds);
2879
- const h = Math.floor(s / 3600);
2880
- const m = Math.floor(s % 3600 / 60);
2881
- const sec = s % 60;
2882
- const showHours = h > 0 || guide >= 3600;
2883
- if (showHours) {
2884
- return `${h}:${m.toString().padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
2885
- }
2886
- return `${m}:${sec.toString().padStart(2, "0")}`;
2887
- }
2888
- function formatTimePhrase2(seconds) {
2889
- if (!isFinite(seconds) || isNaN(seconds))
2890
- return "0 seconds";
2891
- const s = Math.floor(seconds);
2892
- const h = Math.floor(s / 3600);
2893
- const m = Math.floor(s % 3600 / 60);
2894
- const sec = s % 60;
2895
- const parts = [];
2896
- if (h > 0)
2897
- parts.push(`${h} ${h === 1 ? "hour" : "hours"}`);
2898
- if (m > 0)
2899
- parts.push(`${m} ${m === 1 ? "minute" : "minutes"}`);
2900
- if (sec > 0 || parts.length === 0)
2901
- parts.push(`${sec} ${sec === 1 ? "second" : "seconds"}`);
2902
- return parts.join(", ");
2903
- }
2904
- function toISODuration2(seconds) {
2905
- if (!isFinite(seconds) || isNaN(seconds))
2906
- return "PT0S";
2907
- const s = Math.floor(seconds);
2908
- const h = Math.floor(s / 3600);
2909
- const m = Math.floor(s % 3600 / 60);
2910
- const sec = s % 60;
2911
- let result = "PT";
2912
- if (h > 0)
2913
- result += `${h}H`;
2914
- if (m > 0)
2915
- result += `${m}M`;
2916
- if (sec > 0 || result === "PT")
2917
- result += `${sec}S`;
2918
- return result;
2919
- }
2920
- function resolvePlayer2(el) {
2921
- const forId = el.getAttribute("for");
2922
- if (forId) {
2923
- const target = document.getElementById(forId);
2924
- return target?.player ?? null;
2925
- }
2926
- const host = el.closest("video-player");
2927
- return host?.player ?? null;
2928
- }
2929
-
2930
3514
  // src/elements/video-skin.ts
2931
3515
  class VideoSkin2 extends HTMLElementBase {
2932
3516
  _cleanups = [];
@@ -3061,8 +3645,10 @@ class MediaPlayButton2 extends HTMLElementBase {
3061
3645
  background: transparent; border: none; color: inherit; cursor: pointer;
3062
3646
  }
3063
3647
  button:hover { opacity: 0.8; }
3648
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
3649
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
3064
3650
  </style>
3065
- <button part="button" type="button" aria-label="Play">
3651
+ <button part="button" type="button" aria-label="${label(this, "play")}">
3066
3652
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3067
3653
  <path d="${PLAY_ICON}"/>
3068
3654
  </svg>
@@ -3088,7 +3674,7 @@ class MediaPlayButton2 extends HTMLElementBase {
3088
3674
  btn.addEventListener("click", this._onClick);
3089
3675
  const unsub = player.subscribe("paused", (state) => {
3090
3676
  const paused = state.paused;
3091
- btn.setAttribute("aria-label", paused ? "Play" : "Pause");
3677
+ btn.setAttribute("aria-label", label(this, paused ? "play" : "pause"));
3092
3678
  btn.setAttribute("aria-pressed", String(!paused));
3093
3679
  const path = this.shadowRoot.querySelector("path");
3094
3680
  path.setAttribute("d", paused ? PLAY_ICON : PAUSE_ICON);
@@ -3122,8 +3708,10 @@ class MediaMuteButton2 extends HTMLElementBase {
3122
3708
  background: transparent; border: none; color: inherit; cursor: pointer;
3123
3709
  }
3124
3710
  button:hover { opacity: 0.8; }
3711
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
3712
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
3125
3713
  </style>
3126
- <button part="button" type="button" aria-label="Mute">
3714
+ <button part="button" type="button" aria-label="${label(this, "mute")}">
3127
3715
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3128
3716
  <path d="${ICON_HIGH}"/>
3129
3717
  </svg>
@@ -3144,7 +3732,7 @@ class MediaMuteButton2 extends HTMLElementBase {
3144
3732
  btn.addEventListener("click", () => player.toggleMute());
3145
3733
  const unsub = player.subscribe((state) => {
3146
3734
  const icon = getVolumeIcon(state.muted, state.volume);
3147
- btn.setAttribute("aria-label", state.muted ? "Unmute" : "Mute");
3735
+ btn.setAttribute("aria-label", label(this, state.muted ? "unmute" : "mute"));
3148
3736
  const path = this.shadowRoot.querySelector("path");
3149
3737
  path.setAttribute("d", icon);
3150
3738
  });
@@ -3171,8 +3759,10 @@ class MediaFullscreenButton2 extends HTMLElementBase {
3171
3759
  }
3172
3760
  button:hover { opacity: 0.8; }
3173
3761
  button:disabled { opacity: 0.4; cursor: not-allowed; }
3762
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
3763
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
3174
3764
  </style>
3175
- <button part="button" type="button" aria-label="Enter fullscreen">
3765
+ <button part="button" type="button" aria-label="${label(this, "enterFullscreen")}">
3176
3766
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3177
3767
  <path d="${ENTER_FS}"/>
3178
3768
  </svg>
@@ -3200,7 +3790,7 @@ class MediaFullscreenButton2 extends HTMLElementBase {
3200
3790
  }
3201
3791
  btn.disabled = avail !== "available";
3202
3792
  const fs = state.fullscreen;
3203
- btn.setAttribute("aria-label", fs ? "Exit fullscreen" : "Enter fullscreen");
3793
+ btn.setAttribute("aria-label", label(this, fs ? "exitFullscreen" : "enterFullscreen"));
3204
3794
  const path = this.shadowRoot.querySelector("path");
3205
3795
  path.setAttribute("d", fs ? EXIT_FS : ENTER_FS);
3206
3796
  });
@@ -3226,8 +3816,10 @@ class MediaPipButton2 extends HTMLElementBase {
3226
3816
  }
3227
3817
  button:hover { opacity: 0.8; }
3228
3818
  button:disabled { opacity: 0.4; cursor: not-allowed; }
3819
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
3820
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
3229
3821
  </style>
3230
- <button part="button" type="button" aria-label="Picture in picture">
3822
+ <button part="button" type="button" aria-label="${label(this, "enterPiP")}">
3231
3823
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3232
3824
  <path d="${PIP_ICON}"/>
3233
3825
  </svg>
@@ -3254,6 +3846,7 @@ class MediaPipButton2 extends HTMLElementBase {
3254
3846
  this.removeAttribute("hidden");
3255
3847
  }
3256
3848
  btn.disabled = avail !== "available";
3849
+ btn.setAttribute("aria-label", label(this, state.pictureInPicture ? "exitPiP" : "enterPiP"));
3257
3850
  });
3258
3851
  this._cleanup = unsub;
3259
3852
  }
@@ -3284,7 +3877,7 @@ class MediaTimeDisplay2 extends HTMLElementBase {
3284
3877
  time { color: inherit; }
3285
3878
  .separator { margin: 0 2px; }
3286
3879
  </style>
3287
- <time part="time" datetime="PT0S" aria-valuetext="0 seconds">0:00</time>
3880
+ <time part="time" datetime="PT0S" aria-valuetext="${formatTimePhrase2(0, this)}">0:00</time>
3288
3881
  `;
3289
3882
  }
3290
3883
  this.addEventListener("click", this.handleClick);
@@ -3330,7 +3923,7 @@ class MediaTimeDisplay2 extends HTMLElementBase {
3330
3923
  timeEl.textContent = formatTime2(seconds, state.duration);
3331
3924
  }
3332
3925
  timeEl.setAttribute("datetime", toISODuration2(seconds));
3333
- timeEl.setAttribute("aria-valuetext", formatTimePhrase2(seconds));
3926
+ timeEl.setAttribute("aria-valuetext", formatTimePhrase2(seconds, this));
3334
3927
  }
3335
3928
  }
3336
3929
  // src/elements/media-time-group.ts
@@ -3485,10 +4078,12 @@ class MediaProgressBar2 extends HTMLElementBase {
3485
4078
  border-top-color: rgba(0,0,0,0.85);
3486
4079
  }
3487
4080
  .container:hover .tooltip { opacity: 1; }
4081
+ /* A finger needs more than the 20px a mouse does. */
4082
+ @media (pointer: coarse) { .container { height: 36px; } .thumb { width: 18px; height: 18px; } }
3488
4083
  </style>
3489
4084
  <div class="container" part="container" role="slider"
3490
4085
  aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
3491
- aria-label="Seek" tabindex="0">
4086
+ aria-label="${label(this, "seek")}" tabindex="0">
3492
4087
  <div class="tooltip" part="tooltip">0:00</div>
3493
4088
  <div class="track" part="track">
3494
4089
  <div class="waveform" part="waveform" aria-hidden="true"></div>
@@ -3594,7 +4189,7 @@ class MediaProgressBar2 extends HTMLElementBase {
3594
4189
  fill.style.width = `${percent}%`;
3595
4190
  thumb.style.left = `${percent}%`;
3596
4191
  container.setAttribute("aria-valuenow", String(Math.round(percent)));
3597
- container.setAttribute("aria-valuetext", `${formatTimePhrase2(state.currentTime)} of ${formatTimePhrase2(duration)}`);
4192
+ container.setAttribute("aria-valuetext", label(this, "timeOf", { current: formatTimePhrase2(state.currentTime, this), duration: formatTimePhrase2(duration, this) }));
3598
4193
  const bufferedPct = (state.bufferedAmount || 0) * 100;
3599
4194
  buffered.style.width = `${bufferedPct}%`;
3600
4195
  });
@@ -3633,10 +4228,12 @@ class MediaVolumeSlider2 extends HTMLElementBase {
3633
4228
  transition: transform 0.1s;
3634
4229
  }
3635
4230
  .container:hover .thumb { transform: translate(-50%, -50%) scale(1.2); }
4231
+ /* A finger needs more than the 20px a mouse does. */
4232
+ @media (pointer: coarse) { .container { height: 36px; } .thumb { width: 18px; height: 18px; } }
3636
4233
  </style>
3637
4234
  <div class="container" part="container" role="slider"
3638
4235
  aria-valuemin="0" aria-valuemax="100" aria-valuenow="100"
3639
- aria-label="Volume" tabindex="0">
4236
+ aria-label="${label(this, "volume")}" tabindex="0">
3640
4237
  <div class="track" part="track">
3641
4238
  <div class="fill" part="fill"></div>
3642
4239
  <div class="thumb" part="thumb"></div>
@@ -3829,9 +4426,11 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3829
4426
  .item[aria-checked="true"]::before { content: '\\2713'; }
3830
4427
  .item[aria-checked="false"]::before { content: ''; min-width: 12px; display: inline-block; }
3831
4428
  .back { font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.2); }
4429
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
4430
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
3832
4431
  </style>
3833
4432
  <button class="trigger" part="trigger" type="button"
3834
- aria-label="Settings" aria-haspopup="true" aria-expanded="false">
4433
+ aria-label="${label(this, "settings")}" aria-haspopup="true" aria-expanded="false">
3835
4434
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3836
4435
  <path d="${GEAR_ICON}"/>
3837
4436
  </svg>
@@ -3943,35 +4542,35 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3943
4542
  }
3944
4543
  renderMain(menu, player) {
3945
4544
  const state = player.state;
3946
- this.addItem(menu, `Speed: ${state.playbackRate}x`, () => {
4545
+ this.addItem(menu, `${label(this, "speed")}: ${state.playbackRate}x`, () => {
3947
4546
  this._currentPanel = "speed";
3948
4547
  this.renderPanel();
3949
4548
  });
3950
4549
  if (state.qualities.length > 0) {
3951
4550
  const current = state.qualities.find((q) => q.selected);
3952
- const label = state.autoQuality ? "Auto" : current ? `${current.height}p` : "Auto";
3953
- this.addItem(menu, `Quality: ${label}`, () => {
4551
+ const label2 = state.autoQuality ? label(this, "auto") : current ? `${current.height}p` : label(this, "auto");
4552
+ this.addItem(menu, `${label(this, "quality")}: ${label2}`, () => {
3954
4553
  this._currentPanel = "quality";
3955
4554
  this.renderPanel();
3956
4555
  });
3957
4556
  }
3958
4557
  if (state.textTracks.length > 0) {
3959
4558
  const showing = state.textTracks.find((t) => t.mode === "showing");
3960
- this.addItem(menu, `Captions: ${showing?.label || "Off"}`, () => {
4559
+ this.addItem(menu, `${label(this, "captions")}: ${showing?.label || label(this, "captionsOff")}`, () => {
3961
4560
  this._currentPanel = "captions";
3962
4561
  this.renderPanel();
3963
4562
  });
3964
4563
  }
3965
4564
  if (state.audioTracks.length > 1) {
3966
4565
  const selected = state.audioTracks.find((track) => track.selected);
3967
- this.addItem(menu, `Audio: ${selected?.label || "Default"}`, () => {
4566
+ this.addItem(menu, `${label(this, "audio")}: ${selected?.label || label(this, "defaultTrack")}`, () => {
3968
4567
  this._currentPanel = "audio";
3969
4568
  this.renderPanel();
3970
4569
  });
3971
4570
  }
3972
4571
  }
3973
4572
  renderSpeed(menu, player) {
3974
- this.addItem(menu, "Back", () => {
4573
+ this.addItem(menu, label(this, "back"), () => {
3975
4574
  this._currentPanel = "main";
3976
4575
  this.renderPanel();
3977
4576
  }, false, true);
@@ -3984,11 +4583,11 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3984
4583
  }
3985
4584
  }
3986
4585
  renderQuality(menu, player) {
3987
- this.addItem(menu, "Back", () => {
4586
+ this.addItem(menu, label(this, "back"), () => {
3988
4587
  this._currentPanel = "main";
3989
4588
  this.renderPanel();
3990
4589
  }, false, true);
3991
- this.addItem(menu, "Auto", () => {
4590
+ this.addItem(menu, label(this, "auto"), () => {
3992
4591
  player.setQuality("auto");
3993
4592
  this.close();
3994
4593
  }, player.state.autoQuality);
@@ -4000,12 +4599,12 @@ class MediaSettingsMenu2 extends HTMLElementBase {
4000
4599
  }
4001
4600
  }
4002
4601
  renderCaptions(menu, player) {
4003
- this.addItem(menu, "Back", () => {
4602
+ this.addItem(menu, label(this, "back"), () => {
4004
4603
  this._currentPanel = "main";
4005
4604
  this.renderPanel();
4006
4605
  }, false, true);
4007
4606
  const anyShowing = player.state.textTracks.some((t) => t.mode === "showing");
4008
- this.addItem(menu, "Off", () => {
4607
+ this.addItem(menu, label(this, "captionsOff"), () => {
4009
4608
  for (const t of player.state.textTracks) {
4010
4609
  if (t.mode === "showing")
4011
4610
  player.setTextTrack(t.id, "disabled");
@@ -4022,12 +4621,12 @@ class MediaSettingsMenu2 extends HTMLElementBase {
4022
4621
  }
4023
4622
  }
4024
4623
  renderAudio(menu, player) {
4025
- this.addItem(menu, "Back", () => {
4624
+ this.addItem(menu, label(this, "back"), () => {
4026
4625
  this._currentPanel = "main";
4027
4626
  this.renderPanel();
4028
4627
  }, false, true);
4029
4628
  for (const track of player.state.audioTracks) {
4030
- this.addItem(menu, track.label || track.language || "Audio track", () => {
4629
+ this.addItem(menu, track.label || track.language || label(this, "audioTrack"), () => {
4031
4630
  player.setAudioTrack(track.id);
4032
4631
  this.close();
4033
4632
  }, track.selected);
@@ -4058,6 +4657,8 @@ class MediaSeekButton2 extends HTMLElementBase {
4058
4657
  button:hover { opacity: .8; }
4059
4658
  button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
4060
4659
  button:disabled { cursor: default; opacity: .45; }
4660
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
4661
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
4061
4662
  </style>
4062
4663
  <button part="button" type="button"></button>
4063
4664
  `;
@@ -4079,9 +4680,8 @@ class MediaSeekButton2 extends HTMLElementBase {
4079
4680
  return;
4080
4681
  const seconds = Number.parseFloat(this.getAttribute("seconds") ?? "10");
4081
4682
  const offset = Number.isFinite(seconds) && seconds !== 0 ? seconds : 10;
4082
- const label = offset > 0 ? `Forward ${offset} seconds` : `Back ${Math.abs(offset)} seconds`;
4083
4683
  button.textContent = offset > 0 ? `+${offset}` : String(offset);
4084
- button.setAttribute("aria-label", label);
4684
+ button.setAttribute("aria-label", label(this, offset > 0 ? "seekForward" : "seekBackward", { seconds: Math.abs(offset) }));
4085
4685
  this.onClick = () => player.seekBy(offset);
4086
4686
  button.addEventListener("click", this.onClick);
4087
4687
  this.cleanup = player.subscribe((state) => {
@@ -4104,8 +4704,10 @@ class MediaPlaybackRateButton2 extends HTMLElementBase {
4104
4704
  button { min-width: 42px; height: 40px; padding: 0 8px; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; font-variant-numeric: tabular-nums; }
4105
4705
  button:hover { opacity: .8; }
4106
4706
  button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
4707
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
4708
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
4107
4709
  </style>
4108
- <button part="button" type="button" aria-label="Playback speed"></button>
4710
+ <button part="button" type="button" aria-label="${label(this, "speed")}"></button>
4109
4711
  `;
4110
4712
  }
4111
4713
  queueMicrotask(() => this.attach());
@@ -4135,7 +4737,7 @@ class MediaPlaybackRateButton2 extends HTMLElementBase {
4135
4737
  button.addEventListener("click", this.onClick);
4136
4738
  this.cleanup = player.subscribe("playbackRate", (state) => {
4137
4739
  button.textContent = `${state.playbackRate}x`;
4138
- button.setAttribute("aria-label", `Playback speed ${state.playbackRate}x`);
4740
+ button.setAttribute("aria-label", label(this, "playbackRate", { rate: state.playbackRate }));
4139
4741
  });
4140
4742
  }
4141
4743
  }
@@ -4154,8 +4756,10 @@ class MediaAirplayButton2 extends HTMLElementBase {
4154
4756
  button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }
4155
4757
  button:hover { opacity: 0.8; }
4156
4758
  :host([connected]) button { color: var(--media-airplay-active-color, #38bdf8); }
4759
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
4760
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
4157
4761
  </style>
4158
- <button part="button" type="button" aria-label="Play on AirPlay">
4762
+ <button part="button" type="button" aria-label="${label(this, "airPlayPlay")}">
4159
4763
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${AIRPLAY_ICON}"/></svg>
4160
4764
  </button>
4161
4765
  `;
@@ -4213,8 +4817,10 @@ class MediaLiveButton2 extends HTMLElementBase {
4213
4817
  button { display: inline-flex; align-items: center; gap: 6px; min-height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; }
4214
4818
  i { width: 8px; height: 8px; border-radius: 9999px; background: #ef4444; }
4215
4819
  :host(:not([at-live-edge])) button { opacity: 0.72; }
4820
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
4821
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
4216
4822
  </style>
4217
- <button part="button" type="button" aria-label="Go to live edge"><i aria-hidden="true"></i><span>LIVE</span></button>
4823
+ <button part="button" type="button" aria-label="${label(this, "seekToLive")}"><i aria-hidden="true"></i><span>${label(this, "live")}</span></button>
4218
4824
  `;
4219
4825
  }
4220
4826
  queueMicrotask(() => this.attach());
@@ -4309,8 +4915,10 @@ class MediaRemotePlaybackButton2 extends HTMLElementBase {
4309
4915
  button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }
4310
4916
  button:hover { opacity: 0.8; }
4311
4917
  :host([connected]) button { color: var(--media-remote-active-color, #38bdf8); }
4918
+ /* 44px, the smallest a finger hits reliably, on a touch screen. */
4919
+ @media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
4312
4920
  </style>
4313
- <button part="button" type="button" aria-label="Play on another device">
4921
+ <button part="button" type="button" aria-label="${label(this, "remotePlayback")}">
4314
4922
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${REMOTE_ICON}"/></svg>
4315
4923
  </button>
4316
4924
  `;
@@ -4413,4 +5021,4 @@ function registerElements2() {
4413
5021
  }
4414
5022
  }
4415
5023
 
4416
- export { isMediaSessionSupported2, createMediaSession2, updateMetadata2, isOrientationLockSupported2, lockOrientation2, unlockOrientation2, BaseProvider2, isHTML5Source2, HTML5Provider2, html5Loader2, extractYouTubeId2, isYouTubeSource2, YouTubeProvider2, youtubeLoader2, extractVimeoId2, extractVimeoHash2, isVimeoSource2, VimeoProvider2, vimeoLoader2, isHLSSource2, isNativeHLSSupported2, HLSProvider2, hlsLoader2, isDASHSource2, DASHProvider2, dashLoader2, defaultLoaders2, findLoader2, detectMediaType2, getPreconnectHints2, Player2, createPlayer2, getPlayer2, VideoPlayerElement2, formatTime2, formatTimePhrase2, toISODuration2, resolvePlayer2, VideoSkin2, MediaPlayButton2, MediaMuteButton2, MediaFullscreenButton2, MediaPipButton2, MediaTimeDisplay2, MediaTimeGroup2, MediaTimeSeparator2, MediaPreviewTime2, MediaProgressBar2, MediaVolumeSlider2, computePosition2, applyPosition2, MediaSettingsMenu2, MediaSeekButton2, MediaPlaybackRateButton2, MediaAirplayButton2, MediaLiveButton2, MediaErrorDisplay2, MediaRemotePlaybackButton2, registerElements2 };
5024
+ export { isMediaSessionSupported2, createMediaSession2, updateMetadata2, isOrientationLockSupported2, lockOrientation2, unlockOrientation2, BaseProvider2, isHTML5Source2, HTML5Provider2, html5Loader2, extractYouTubeId2, isYouTubeSource2, YouTubeProvider2, youtubeLoader2, extractVimeoId2, extractVimeoHash2, isVimeoSource2, VimeoProvider2, vimeoLoader2, isHLSSource2, isNativeHLSSupported2, HLSProvider2, hlsLoader2, isDASHSource2, DASHProvider2, dashLoader2, defaultLoaders2, findLoader2, detectMediaType2, getPreconnectHints2, englishStrings2, spanishStrings2, frenchStrings2, germanStrings2, japaneseStrings2, chineseStrings2, defaultTranslations2, I18n2, createI18n2, getI18n2, setI18n2, t2, formatTime2, formatTimePhrase2, toISODuration2, resolvePlayer2, Player2, createPlayer2, getPlayer2, VideoPlayerElement2, VideoSkin2, MediaPlayButton2, MediaMuteButton2, MediaFullscreenButton2, MediaPipButton2, MediaTimeDisplay2, MediaTimeGroup2, MediaTimeSeparator2, MediaPreviewTime2, MediaProgressBar2, MediaVolumeSlider2, computePosition2, applyPosition2, MediaSettingsMenu2, MediaSeekButton2, MediaPlaybackRateButton2, MediaAirplayButton2, MediaLiveButton2, MediaErrorDisplay2, MediaRemotePlaybackButton2, registerElements2 };