ts-video-player 0.1.6 → 0.1.8

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.
@@ -625,6 +625,7 @@ class YouTubeProvider2 extends BaseProvider2 {
625
625
  endTime;
626
626
  timeUpdateInterval = null;
627
627
  lastState = -1;
628
+ pendingPlay = false;
628
629
  get mediaElement() {
629
630
  return this.iframe;
630
631
  }
@@ -702,6 +703,7 @@ class YouTubeProvider2 extends BaseProvider2 {
702
703
  this.player?.destroy();
703
704
  this.player = null;
704
705
  this.playerReady = false;
706
+ this.pendingPlay = false;
705
707
  this.iframe = null;
706
708
  }
707
709
  onReady() {
@@ -714,8 +716,12 @@ class YouTubeProvider2 extends BaseProvider2 {
714
716
  this.events.emit("loadedmetadata");
715
717
  this.events.emit("canplay");
716
718
  if (this.pendingVideoId) {
717
- this.loadVideo(this.pendingVideoId, this.startTime, this.endTime);
719
+ const videoId = this.pendingVideoId;
718
720
  this.pendingVideoId = null;
721
+ this.loadVideo(videoId, this.startTime, this.endTime);
722
+ } else if (this.pendingPlay) {
723
+ this.pendingPlay = false;
724
+ this.player?.playVideo();
719
725
  }
720
726
  }
721
727
  onStateChange(event) {
@@ -727,6 +733,8 @@ class YouTubeProvider2 extends BaseProvider2 {
727
733
  this.events.emit("loadstart");
728
734
  break;
729
735
  case PlayerState.PLAYING:
736
+ if (this.getDuration() > 0)
737
+ this.events.emit("durationchange", this.getDuration());
730
738
  this.events.emit("play");
731
739
  this.events.emit("playing");
732
740
  this.events.emit("statechange", { paused: false, playing: true, waiting: false });
@@ -787,7 +795,8 @@ class YouTubeProvider2 extends BaseProvider2 {
787
795
  loadVideo(videoId, startTime, endTime) {
788
796
  if (!this.player)
789
797
  return;
790
- if (this.options.autoplay) {
798
+ if (this.options.autoplay || this.pendingPlay) {
799
+ this.pendingPlay = false;
791
800
  this.player.loadVideoById({
792
801
  videoId,
793
802
  startSeconds: startTime,
@@ -802,13 +811,21 @@ class YouTubeProvider2 extends BaseProvider2 {
802
811
  }
803
812
  }
804
813
  async play() {
805
- this.player?.playVideo();
814
+ if (!this.player || !this.playerReady || this.pendingVideoId) {
815
+ this.pendingPlay = true;
816
+ return;
817
+ }
818
+ this.player.playVideo();
806
819
  }
807
820
  pause() {
808
- this.player?.pauseVideo();
821
+ this.pendingPlay = false;
822
+ if (this.playerReady)
823
+ this.player?.pauseVideo();
809
824
  }
810
825
  stop() {
811
- this.player?.stopVideo();
826
+ this.pendingPlay = false;
827
+ if (this.playerReady)
828
+ this.player?.stopVideo();
812
829
  }
813
830
  seekTo(time) {
814
831
  this.player?.seekTo(time, true);
@@ -1009,8 +1026,9 @@ class VimeoProvider2 extends BaseProvider2 {
1009
1026
  return;
1010
1027
  }
1011
1028
  if (this.player) {
1012
- await this.player.destroy();
1013
- this.player = null;
1029
+ await this.player.loadVideo(parseInt(parsed.videoId, 10));
1030
+ await this.announceLoaded();
1031
+ return;
1014
1032
  }
1015
1033
  const playerOptions = {
1016
1034
  id: parseInt(parsed.videoId, 10),
@@ -1034,6 +1052,11 @@ class VimeoProvider2 extends BaseProvider2 {
1034
1052
  this.iframe.style.cssText = "position:absolute;inset:0;width:100%;height:100%;border:0";
1035
1053
  }
1036
1054
  this.attachVimeoEvents();
1055
+ await this.announceLoaded();
1056
+ }
1057
+ async announceLoaded() {
1058
+ if (!this.player)
1059
+ return;
1037
1060
  this.events.emit("loadedmetadata");
1038
1061
  this.events.emit("canplay");
1039
1062
  this._duration = await this.player.getDuration();
@@ -2025,6 +2048,630 @@ function embedCover(state, dismissed) {
2025
2048
  return { show: true, image: youtubeId ? `https://i.ytimg.com/vi/${youtubeId}/hqdefault.jpg` : "" };
2026
2049
  }
2027
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
+
2028
2675
  // src/player.ts
2029
2676
  class Player2 {
2030
2677
  _el;
@@ -2040,6 +2687,7 @@ class Player2 {
2040
2687
  _cover = null;
2041
2688
  _coverDismissed = false;
2042
2689
  _coverTimer = null;
2690
+ _playRequested = false;
2043
2691
  constructor(container, options = {}) {
2044
2692
  if (typeof container === "string") {
2045
2693
  const el = document.querySelector(container);
@@ -2080,8 +2728,8 @@ class Player2 {
2080
2728
  this._el.classList.add("ts-video-player");
2081
2729
  this._el.setAttribute("tabindex", "0");
2082
2730
  this._el.setAttribute("role", "region");
2083
- this._el.setAttribute("aria-roledescription", "video player");
2084
- 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"));
2085
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");
2086
2734
  const mediaContainer = document.createElement("div");
2087
2735
  mediaContainer.className = "ts-video-player__container";
@@ -2105,27 +2753,43 @@ class Player2 {
2105
2753
  const cover = document.createElement("button");
2106
2754
  cover.type = "button";
2107
2755
  cover.className = "ts-video-player__cover";
2108
- 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"));
2109
2757
  cover.hidden = true;
2110
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";
2111
2759
  const button = document.createElement("span");
2112
2760
  button.setAttribute("aria-hidden", "true");
2113
2761
  button.style.cssText = "display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:50%;background:rgba(255,255,255,0.92);box-shadow:0 4px 18px rgba(0,0,0,0.35)";
2114
2762
  const triangle = document.createElement("span");
2763
+ triangle.className = "ts-video-player__cover-play";
2115
2764
  triangle.style.cssText = "width:22px;height:24px;margin-left:5px;background:#0f172a;clip-path:polygon(0 0,100% 50%,0 100%)";
2116
2765
  button.appendChild(triangle);
2117
2766
  cover.appendChild(button);
2118
2767
  cover.addEventListener("click", (event) => {
2119
2768
  event.stopPropagation();
2769
+ if (!cover.dataset.loading) {
2770
+ cover.dataset.loading = "";
2771
+ triangle.style.display = "none";
2772
+ const ring = document.createElement("span");
2773
+ ring.className = "ts-video-player__cover-spinner";
2774
+ ring.style.cssText = "width:26px;height:26px;border-radius:50%;border:3px solid rgba(15,23,42,0.2);border-top-color:#0f172a";
2775
+ button.appendChild(ring);
2776
+ ring.animate?.([{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }], { duration: 800, iterations: Infinity });
2777
+ }
2120
2778
  this.play().catch(() => {});
2121
2779
  if (this._coverTimer)
2122
2780
  clearTimeout(this._coverTimer);
2123
- this._coverTimer = setTimeout(() => {
2124
- if (this.state.started)
2125
- return;
2126
- this._coverDismissed = true;
2127
- this.updateCover(this.state);
2128
- }, 2000);
2781
+ let waits = 0;
2782
+ const settle = () => {
2783
+ this._coverTimer = setTimeout(() => {
2784
+ if (this.state.started)
2785
+ return;
2786
+ if (!this.state.canPlay && ++waits < 10)
2787
+ return settle();
2788
+ this._coverDismissed = true;
2789
+ this.updateCover(this.state);
2790
+ }, 2000);
2791
+ };
2792
+ settle();
2129
2793
  });
2130
2794
  this._el.appendChild(cover);
2131
2795
  this._cover = cover;
@@ -2145,6 +2809,13 @@ class Player2 {
2145
2809
  if (cover.style.backgroundImage !== background)
2146
2810
  cover.style.backgroundImage = background;
2147
2811
  }
2812
+ if (!show && cover.dataset.loading !== undefined) {
2813
+ delete cover.dataset.loading;
2814
+ cover.querySelector(".ts-video-player__cover-spinner")?.remove();
2815
+ const triangle = cover.querySelector(".ts-video-player__cover-play");
2816
+ if (triangle)
2817
+ triangle.style.display = "";
2818
+ }
2148
2819
  cover.hidden = !show;
2149
2820
  cover.style.display = show ? "flex" : "none";
2150
2821
  }
@@ -2307,6 +2978,7 @@ class Player2 {
2307
2978
  return;
2308
2979
  }
2309
2980
  this._coverDismissed = false;
2981
+ this._playRequested = !!this._options.autoplay;
2310
2982
  this._store.batch({
2311
2983
  sources,
2312
2984
  src: sources[0],
@@ -2364,6 +3036,10 @@ class Player2 {
2364
3036
  mediaType: loader.mediaType(candidate.src),
2365
3037
  providerType: loader.type
2366
3038
  });
3039
+ if (this._playRequested) {
3040
+ this._playRequested = false;
3041
+ this.startRequestedPlay(this._provider);
3042
+ }
2367
3043
  return;
2368
3044
  } catch (error) {
2369
3045
  lastError = error;
@@ -2409,12 +3085,12 @@ class Player2 {
2409
3085
  provider.on("play", () => {
2410
3086
  this._store.batch({ paused: false, ended: false });
2411
3087
  this._events.emit("play");
2412
- this.announce("Playing");
3088
+ this.announce(label(this._el, "playing"));
2413
3089
  });
2414
3090
  provider.on("pause", () => {
2415
3091
  this._store.set("paused", true);
2416
3092
  this._events.emit("pause");
2417
- this.announce("Paused");
3093
+ this.announce(label(this._el, "paused"));
2418
3094
  });
2419
3095
  provider.on("playing", () => {
2420
3096
  this._store.batch({ playing: true, started: true, waiting: false, playbackState: "playing" });
@@ -2444,9 +3120,9 @@ class Player2 {
2444
3120
  this._store.batch({ volume, muted });
2445
3121
  this._events.emit("volumechange", volume, muted);
2446
3122
  if (muted) {
2447
- this.announce("Muted");
3123
+ this.announce(label(this._el, "muted"));
2448
3124
  } else {
2449
- this.announce(`Volume ${Math.round(volume * 100)}%`);
3125
+ this.announce(label(this._el, "volumeLevel", { percent: Math.round(volume * 100) }));
2450
3126
  }
2451
3127
  });
2452
3128
  provider.on("ratechange", (rate) => {
@@ -2463,7 +3139,7 @@ class Player2 {
2463
3139
  provider.on("ended", () => {
2464
3140
  this._store.batch({ ended: true, playing: false, playbackState: "ended" });
2465
3141
  this._events.emit("ended");
2466
- this.announce("Playback ended");
3142
+ this.announce(label(this._el, "ended"));
2467
3143
  if (this.state.loop) {
2468
3144
  this.seekTo(0);
2469
3145
  this.play();
@@ -2472,7 +3148,7 @@ class Player2 {
2472
3148
  provider.on("error", (error) => {
2473
3149
  this._store.batch({ loadingState: "error", error });
2474
3150
  this._events.emit("error", error);
2475
- this.announce(`Error: ${error.message}`);
3151
+ this.announce(label(this._el, "errorMessage", { message: error.message }));
2476
3152
  });
2477
3153
  provider.on("statechange", (state) => {
2478
3154
  this._store.batch(state);
@@ -2485,7 +3161,7 @@ class Player2 {
2485
3161
  provider.on("fullscreenchange", (fullscreen) => {
2486
3162
  this._store.set("fullscreen", fullscreen);
2487
3163
  this._events.emit("fullscreenchange", fullscreen);
2488
- this.announce(fullscreen ? "Entered fullscreen" : "Exited fullscreen");
3164
+ this.announce(label(this._el, fullscreen ? "enteredFullscreen" : "exitedFullscreen"));
2489
3165
  });
2490
3166
  provider.on("pipchange", (pip) => {
2491
3167
  this._store.set("pictureInPicture", pip);
@@ -2501,12 +3177,23 @@ class Player2 {
2501
3177
  this._events.emit("availabilitychange", feature, availability);
2502
3178
  });
2503
3179
  }
3180
+ async startRequestedPlay(provider) {
3181
+ await provider.play().catch(() => {});
3182
+ if (this._provider !== provider || this.state.error?.code !== 1 || this.state.muted)
3183
+ return;
3184
+ this._store.set("error", null);
3185
+ this.setMuted(true);
3186
+ await provider.play().catch(() => {});
3187
+ }
2504
3188
  async play() {
2505
- if (!this._provider)
3189
+ if (!this._provider) {
3190
+ this._playRequested = true;
2506
3191
  return;
3192
+ }
2507
3193
  await this._provider.play();
2508
3194
  }
2509
3195
  pause() {
3196
+ this._playRequested = false;
2510
3197
  this._provider?.pause();
2511
3198
  }
2512
3199
  togglePlay() {
@@ -2822,62 +3509,6 @@ class VideoPlayerElement2 extends HTMLElementBase {
2822
3509
  }
2823
3510
  }
2824
3511
  }
2825
- // src/elements/utils.ts
2826
- function formatTime2(seconds, guide = 0) {
2827
- if (!isFinite(seconds) || isNaN(seconds))
2828
- return "0:00";
2829
- const s = Math.floor(seconds);
2830
- const h = Math.floor(s / 3600);
2831
- const m = Math.floor(s % 3600 / 60);
2832
- const sec = s % 60;
2833
- const showHours = h > 0 || guide >= 3600;
2834
- if (showHours) {
2835
- return `${h}:${m.toString().padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
2836
- }
2837
- return `${m}:${sec.toString().padStart(2, "0")}`;
2838
- }
2839
- function formatTimePhrase2(seconds) {
2840
- if (!isFinite(seconds) || isNaN(seconds))
2841
- return "0 seconds";
2842
- const s = Math.floor(seconds);
2843
- const h = Math.floor(s / 3600);
2844
- const m = Math.floor(s % 3600 / 60);
2845
- const sec = s % 60;
2846
- const parts = [];
2847
- if (h > 0)
2848
- parts.push(`${h} ${h === 1 ? "hour" : "hours"}`);
2849
- if (m > 0)
2850
- parts.push(`${m} ${m === 1 ? "minute" : "minutes"}`);
2851
- if (sec > 0 || parts.length === 0)
2852
- parts.push(`${sec} ${sec === 1 ? "second" : "seconds"}`);
2853
- return parts.join(", ");
2854
- }
2855
- function toISODuration2(seconds) {
2856
- if (!isFinite(seconds) || isNaN(seconds))
2857
- return "PT0S";
2858
- const s = Math.floor(seconds);
2859
- const h = Math.floor(s / 3600);
2860
- const m = Math.floor(s % 3600 / 60);
2861
- const sec = s % 60;
2862
- let result = "PT";
2863
- if (h > 0)
2864
- result += `${h}H`;
2865
- if (m > 0)
2866
- result += `${m}M`;
2867
- if (sec > 0 || result === "PT")
2868
- result += `${sec}S`;
2869
- return result;
2870
- }
2871
- function resolvePlayer2(el) {
2872
- const forId = el.getAttribute("for");
2873
- if (forId) {
2874
- const target = document.getElementById(forId);
2875
- return target?.player ?? null;
2876
- }
2877
- const host = el.closest("video-player");
2878
- return host?.player ?? null;
2879
- }
2880
-
2881
3512
  // src/elements/video-skin.ts
2882
3513
  class VideoSkin2 extends HTMLElementBase {
2883
3514
  _cleanups = [];
@@ -2896,6 +3527,7 @@ class VideoSkin2 extends HTMLElementBase {
2896
3527
  color: white;
2897
3528
  font-family: system-ui, -apple-system, sans-serif;
2898
3529
  font-size: 14px;
3530
+ container-type: inline-size;
2899
3531
  }
2900
3532
 
2901
3533
  /* Click-to-play overlay */
@@ -2923,6 +3555,25 @@ class VideoSkin2 extends HTMLElementBase {
2923
3555
  visibility: hidden;
2924
3556
  }
2925
3557
 
3558
+ /* The time and the progress bar. One row with the buttons on a
3559
+ wide player; a phone-width one gives the progress bar a row of
3560
+ its own, where it would otherwise be squeezed to a dot between
3561
+ the buttons. */
3562
+ .center { display: contents; }
3563
+ @container (max-width: 520px) {
3564
+ .controls { flex-wrap: wrap; row-gap: 0; padding: 4px 10px 6px; }
3565
+ .center { display: flex; order: -1; flex: 1 0 100%; align-items: center; gap: 10px; min-width: 0; font-size: 12px; }
3566
+ /* Speed is in the settings menu too; one button fewer keeps the
3567
+ rest on one row. */
3568
+ ::slotted(media-playback-rate-button) { display: none; }
3569
+ }
3570
+
3571
+ /* A touch screen sets the volume with its own buttons, and iOS
3572
+ ignores a page's volume altogether: no slider there. */
3573
+ @media (pointer: coarse) {
3574
+ ::slotted(media-volume-slider) { display: none; }
3575
+ }
3576
+
2926
3577
  /* Spacer pushes right-aligned controls */
2927
3578
  .spacer {
2928
3579
  flex: 1;
@@ -2938,7 +3589,7 @@ class VideoSkin2 extends HTMLElementBase {
2938
3589
  </div>
2939
3590
  <div class="controls" part="controls">
2940
3591
  <slot name="left"></slot>
2941
- <slot name="center"></slot>
3592
+ <div class="center" part="center"><slot name="center"></slot></div>
2942
3593
  <div class="spacer"></div>
2943
3594
  <slot name="right"></slot>
2944
3595
  <slot></slot>
@@ -2993,7 +3644,7 @@ class MediaPlayButton2 extends HTMLElementBase {
2993
3644
  }
2994
3645
  button:hover { opacity: 0.8; }
2995
3646
  </style>
2996
- <button part="button" type="button" aria-label="Play">
3647
+ <button part="button" type="button" aria-label="${label(this, "play")}">
2997
3648
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
2998
3649
  <path d="${PLAY_ICON}"/>
2999
3650
  </svg>
@@ -3019,7 +3670,7 @@ class MediaPlayButton2 extends HTMLElementBase {
3019
3670
  btn.addEventListener("click", this._onClick);
3020
3671
  const unsub = player.subscribe("paused", (state) => {
3021
3672
  const paused = state.paused;
3022
- btn.setAttribute("aria-label", paused ? "Play" : "Pause");
3673
+ btn.setAttribute("aria-label", label(this, paused ? "play" : "pause"));
3023
3674
  btn.setAttribute("aria-pressed", String(!paused));
3024
3675
  const path = this.shadowRoot.querySelector("path");
3025
3676
  path.setAttribute("d", paused ? PLAY_ICON : PAUSE_ICON);
@@ -3054,7 +3705,7 @@ class MediaMuteButton2 extends HTMLElementBase {
3054
3705
  }
3055
3706
  button:hover { opacity: 0.8; }
3056
3707
  </style>
3057
- <button part="button" type="button" aria-label="Mute">
3708
+ <button part="button" type="button" aria-label="${label(this, "mute")}">
3058
3709
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3059
3710
  <path d="${ICON_HIGH}"/>
3060
3711
  </svg>
@@ -3075,7 +3726,7 @@ class MediaMuteButton2 extends HTMLElementBase {
3075
3726
  btn.addEventListener("click", () => player.toggleMute());
3076
3727
  const unsub = player.subscribe((state) => {
3077
3728
  const icon = getVolumeIcon(state.muted, state.volume);
3078
- btn.setAttribute("aria-label", state.muted ? "Unmute" : "Mute");
3729
+ btn.setAttribute("aria-label", label(this, state.muted ? "unmute" : "mute"));
3079
3730
  const path = this.shadowRoot.querySelector("path");
3080
3731
  path.setAttribute("d", icon);
3081
3732
  });
@@ -3103,7 +3754,7 @@ class MediaFullscreenButton2 extends HTMLElementBase {
3103
3754
  button:hover { opacity: 0.8; }
3104
3755
  button:disabled { opacity: 0.4; cursor: not-allowed; }
3105
3756
  </style>
3106
- <button part="button" type="button" aria-label="Enter fullscreen">
3757
+ <button part="button" type="button" aria-label="${label(this, "enterFullscreen")}">
3107
3758
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3108
3759
  <path d="${ENTER_FS}"/>
3109
3760
  </svg>
@@ -3131,7 +3782,7 @@ class MediaFullscreenButton2 extends HTMLElementBase {
3131
3782
  }
3132
3783
  btn.disabled = avail !== "available";
3133
3784
  const fs = state.fullscreen;
3134
- btn.setAttribute("aria-label", fs ? "Exit fullscreen" : "Enter fullscreen");
3785
+ btn.setAttribute("aria-label", label(this, fs ? "exitFullscreen" : "enterFullscreen"));
3135
3786
  const path = this.shadowRoot.querySelector("path");
3136
3787
  path.setAttribute("d", fs ? EXIT_FS : ENTER_FS);
3137
3788
  });
@@ -3158,7 +3809,7 @@ class MediaPipButton2 extends HTMLElementBase {
3158
3809
  button:hover { opacity: 0.8; }
3159
3810
  button:disabled { opacity: 0.4; cursor: not-allowed; }
3160
3811
  </style>
3161
- <button part="button" type="button" aria-label="Picture in picture">
3812
+ <button part="button" type="button" aria-label="${label(this, "enterPiP")}">
3162
3813
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3163
3814
  <path d="${PIP_ICON}"/>
3164
3815
  </svg>
@@ -3185,6 +3836,7 @@ class MediaPipButton2 extends HTMLElementBase {
3185
3836
  this.removeAttribute("hidden");
3186
3837
  }
3187
3838
  btn.disabled = avail !== "available";
3839
+ btn.setAttribute("aria-label", label(this, state.pictureInPicture ? "exitPiP" : "enterPiP"));
3188
3840
  });
3189
3841
  this._cleanup = unsub;
3190
3842
  }
@@ -3215,7 +3867,7 @@ class MediaTimeDisplay2 extends HTMLElementBase {
3215
3867
  time { color: inherit; }
3216
3868
  .separator { margin: 0 2px; }
3217
3869
  </style>
3218
- <time part="time" datetime="PT0S" aria-valuetext="0 seconds">0:00</time>
3870
+ <time part="time" datetime="PT0S" aria-valuetext="${formatTimePhrase2(0, this)}">0:00</time>
3219
3871
  `;
3220
3872
  }
3221
3873
  this.addEventListener("click", this.handleClick);
@@ -3261,7 +3913,7 @@ class MediaTimeDisplay2 extends HTMLElementBase {
3261
3913
  timeEl.textContent = formatTime2(seconds, state.duration);
3262
3914
  }
3263
3915
  timeEl.setAttribute("datetime", toISODuration2(seconds));
3264
- timeEl.setAttribute("aria-valuetext", formatTimePhrase2(seconds));
3916
+ timeEl.setAttribute("aria-valuetext", formatTimePhrase2(seconds, this));
3265
3917
  }
3266
3918
  }
3267
3919
  // src/elements/media-time-group.ts
@@ -3419,7 +4071,7 @@ class MediaProgressBar2 extends HTMLElementBase {
3419
4071
  </style>
3420
4072
  <div class="container" part="container" role="slider"
3421
4073
  aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
3422
- aria-label="Seek" tabindex="0">
4074
+ aria-label="${label(this, "seek")}" tabindex="0">
3423
4075
  <div class="tooltip" part="tooltip">0:00</div>
3424
4076
  <div class="track" part="track">
3425
4077
  <div class="waveform" part="waveform" aria-hidden="true"></div>
@@ -3525,7 +4177,7 @@ class MediaProgressBar2 extends HTMLElementBase {
3525
4177
  fill.style.width = `${percent}%`;
3526
4178
  thumb.style.left = `${percent}%`;
3527
4179
  container.setAttribute("aria-valuenow", String(Math.round(percent)));
3528
- container.setAttribute("aria-valuetext", `${formatTimePhrase2(state.currentTime)} of ${formatTimePhrase2(duration)}`);
4180
+ container.setAttribute("aria-valuetext", label(this, "timeOf", { current: formatTimePhrase2(state.currentTime, this), duration: formatTimePhrase2(duration, this) }));
3529
4181
  const bufferedPct = (state.bufferedAmount || 0) * 100;
3530
4182
  buffered.style.width = `${bufferedPct}%`;
3531
4183
  });
@@ -3567,7 +4219,7 @@ class MediaVolumeSlider2 extends HTMLElementBase {
3567
4219
  </style>
3568
4220
  <div class="container" part="container" role="slider"
3569
4221
  aria-valuemin="0" aria-valuemax="100" aria-valuenow="100"
3570
- aria-label="Volume" tabindex="0">
4222
+ aria-label="${label(this, "volume")}" tabindex="0">
3571
4223
  <div class="track" part="track">
3572
4224
  <div class="fill" part="fill"></div>
3573
4225
  <div class="thumb" part="thumb"></div>
@@ -3762,7 +4414,7 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3762
4414
  .back { font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.2); }
3763
4415
  </style>
3764
4416
  <button class="trigger" part="trigger" type="button"
3765
- aria-label="Settings" aria-haspopup="true" aria-expanded="false">
4417
+ aria-label="${label(this, "settings")}" aria-haspopup="true" aria-expanded="false">
3766
4418
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
3767
4419
  <path d="${GEAR_ICON}"/>
3768
4420
  </svg>
@@ -3874,35 +4526,35 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3874
4526
  }
3875
4527
  renderMain(menu, player) {
3876
4528
  const state = player.state;
3877
- this.addItem(menu, `Speed: ${state.playbackRate}x`, () => {
4529
+ this.addItem(menu, `${label(this, "speed")}: ${state.playbackRate}x`, () => {
3878
4530
  this._currentPanel = "speed";
3879
4531
  this.renderPanel();
3880
4532
  });
3881
4533
  if (state.qualities.length > 0) {
3882
4534
  const current = state.qualities.find((q) => q.selected);
3883
- const label = state.autoQuality ? "Auto" : current ? `${current.height}p` : "Auto";
3884
- this.addItem(menu, `Quality: ${label}`, () => {
4535
+ const label2 = state.autoQuality ? label(this, "auto") : current ? `${current.height}p` : label(this, "auto");
4536
+ this.addItem(menu, `${label(this, "quality")}: ${label2}`, () => {
3885
4537
  this._currentPanel = "quality";
3886
4538
  this.renderPanel();
3887
4539
  });
3888
4540
  }
3889
4541
  if (state.textTracks.length > 0) {
3890
4542
  const showing = state.textTracks.find((t) => t.mode === "showing");
3891
- this.addItem(menu, `Captions: ${showing?.label || "Off"}`, () => {
4543
+ this.addItem(menu, `${label(this, "captions")}: ${showing?.label || label(this, "captionsOff")}`, () => {
3892
4544
  this._currentPanel = "captions";
3893
4545
  this.renderPanel();
3894
4546
  });
3895
4547
  }
3896
4548
  if (state.audioTracks.length > 1) {
3897
4549
  const selected = state.audioTracks.find((track) => track.selected);
3898
- this.addItem(menu, `Audio: ${selected?.label || "Default"}`, () => {
4550
+ this.addItem(menu, `${label(this, "audio")}: ${selected?.label || label(this, "defaultTrack")}`, () => {
3899
4551
  this._currentPanel = "audio";
3900
4552
  this.renderPanel();
3901
4553
  });
3902
4554
  }
3903
4555
  }
3904
4556
  renderSpeed(menu, player) {
3905
- this.addItem(menu, "Back", () => {
4557
+ this.addItem(menu, label(this, "back"), () => {
3906
4558
  this._currentPanel = "main";
3907
4559
  this.renderPanel();
3908
4560
  }, false, true);
@@ -3915,11 +4567,11 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3915
4567
  }
3916
4568
  }
3917
4569
  renderQuality(menu, player) {
3918
- this.addItem(menu, "Back", () => {
4570
+ this.addItem(menu, label(this, "back"), () => {
3919
4571
  this._currentPanel = "main";
3920
4572
  this.renderPanel();
3921
4573
  }, false, true);
3922
- this.addItem(menu, "Auto", () => {
4574
+ this.addItem(menu, label(this, "auto"), () => {
3923
4575
  player.setQuality("auto");
3924
4576
  this.close();
3925
4577
  }, player.state.autoQuality);
@@ -3931,12 +4583,12 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3931
4583
  }
3932
4584
  }
3933
4585
  renderCaptions(menu, player) {
3934
- this.addItem(menu, "Back", () => {
4586
+ this.addItem(menu, label(this, "back"), () => {
3935
4587
  this._currentPanel = "main";
3936
4588
  this.renderPanel();
3937
4589
  }, false, true);
3938
4590
  const anyShowing = player.state.textTracks.some((t) => t.mode === "showing");
3939
- this.addItem(menu, "Off", () => {
4591
+ this.addItem(menu, label(this, "captionsOff"), () => {
3940
4592
  for (const t of player.state.textTracks) {
3941
4593
  if (t.mode === "showing")
3942
4594
  player.setTextTrack(t.id, "disabled");
@@ -3953,12 +4605,12 @@ class MediaSettingsMenu2 extends HTMLElementBase {
3953
4605
  }
3954
4606
  }
3955
4607
  renderAudio(menu, player) {
3956
- this.addItem(menu, "Back", () => {
4608
+ this.addItem(menu, label(this, "back"), () => {
3957
4609
  this._currentPanel = "main";
3958
4610
  this.renderPanel();
3959
4611
  }, false, true);
3960
4612
  for (const track of player.state.audioTracks) {
3961
- this.addItem(menu, track.label || track.language || "Audio track", () => {
4613
+ this.addItem(menu, track.label || track.language || label(this, "audioTrack"), () => {
3962
4614
  player.setAudioTrack(track.id);
3963
4615
  this.close();
3964
4616
  }, track.selected);
@@ -4010,9 +4662,8 @@ class MediaSeekButton2 extends HTMLElementBase {
4010
4662
  return;
4011
4663
  const seconds = Number.parseFloat(this.getAttribute("seconds") ?? "10");
4012
4664
  const offset = Number.isFinite(seconds) && seconds !== 0 ? seconds : 10;
4013
- const label = offset > 0 ? `Forward ${offset} seconds` : `Back ${Math.abs(offset)} seconds`;
4014
4665
  button.textContent = offset > 0 ? `+${offset}` : String(offset);
4015
- button.setAttribute("aria-label", label);
4666
+ button.setAttribute("aria-label", label(this, offset > 0 ? "seekForward" : "seekBackward", { seconds: Math.abs(offset) }));
4016
4667
  this.onClick = () => player.seekBy(offset);
4017
4668
  button.addEventListener("click", this.onClick);
4018
4669
  this.cleanup = player.subscribe((state) => {
@@ -4036,7 +4687,7 @@ class MediaPlaybackRateButton2 extends HTMLElementBase {
4036
4687
  button:hover { opacity: .8; }
4037
4688
  button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
4038
4689
  </style>
4039
- <button part="button" type="button" aria-label="Playback speed"></button>
4690
+ <button part="button" type="button" aria-label="${label(this, "speed")}"></button>
4040
4691
  `;
4041
4692
  }
4042
4693
  queueMicrotask(() => this.attach());
@@ -4066,7 +4717,7 @@ class MediaPlaybackRateButton2 extends HTMLElementBase {
4066
4717
  button.addEventListener("click", this.onClick);
4067
4718
  this.cleanup = player.subscribe("playbackRate", (state) => {
4068
4719
  button.textContent = `${state.playbackRate}x`;
4069
- button.setAttribute("aria-label", `Playback speed ${state.playbackRate}x`);
4720
+ button.setAttribute("aria-label", label(this, "playbackRate", { rate: state.playbackRate }));
4070
4721
  });
4071
4722
  }
4072
4723
  }
@@ -4086,7 +4737,7 @@ class MediaAirplayButton2 extends HTMLElementBase {
4086
4737
  button:hover { opacity: 0.8; }
4087
4738
  :host([connected]) button { color: var(--media-airplay-active-color, #38bdf8); }
4088
4739
  </style>
4089
- <button part="button" type="button" aria-label="Play on AirPlay">
4740
+ <button part="button" type="button" aria-label="${label(this, "airPlayPlay")}">
4090
4741
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${AIRPLAY_ICON}"/></svg>
4091
4742
  </button>
4092
4743
  `;
@@ -4145,7 +4796,7 @@ class MediaLiveButton2 extends HTMLElementBase {
4145
4796
  i { width: 8px; height: 8px; border-radius: 9999px; background: #ef4444; }
4146
4797
  :host(:not([at-live-edge])) button { opacity: 0.72; }
4147
4798
  </style>
4148
- <button part="button" type="button" aria-label="Go to live edge"><i aria-hidden="true"></i><span>LIVE</span></button>
4799
+ <button part="button" type="button" aria-label="${label(this, "seekToLive")}"><i aria-hidden="true"></i><span>${label(this, "live")}</span></button>
4149
4800
  `;
4150
4801
  }
4151
4802
  queueMicrotask(() => this.attach());
@@ -4241,7 +4892,7 @@ class MediaRemotePlaybackButton2 extends HTMLElementBase {
4241
4892
  button:hover { opacity: 0.8; }
4242
4893
  :host([connected]) button { color: var(--media-remote-active-color, #38bdf8); }
4243
4894
  </style>
4244
- <button part="button" type="button" aria-label="Play on another device">
4895
+ <button part="button" type="button" aria-label="${label(this, "remotePlayback")}">
4245
4896
  <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${REMOTE_ICON}"/></svg>
4246
4897
  </button>
4247
4898
  `;
@@ -4344,4 +4995,4 @@ function registerElements2() {
4344
4995
  }
4345
4996
  }
4346
4997
 
4347
- 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 };
4998
+ 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 };