From 93bc9ce24d698d7adfe76cf67328c88996abd4eb Mon Sep 17 00:00:00 2001 From: Ztec Date: Wed, 10 Apr 2024 17:14:38 +0200 Subject: [PATCH] add seek and speed controls to media player When listening to podcast, it is usual to want to speed up the playback. https://github.com/miniflux/v2/pull/2521 was addressing the need globally, this PR allow to address it for just the current open enclosure media. (no save) Some Browser already include this control directly, but firefox does not (directly anyway). Also, it is often useful to be able to skip chunk of a podcast, to skip commercials for example, or get back a bit because we couldn't hear the last part. I added rudimentary seek controls with the usual +/-10 and 30 seconds chuck size. This is pretty handy when podcast are very long and using the seek bar is way too tricky to just skip 30s. As always, I'm French and could only provide English and French translation for the few text I added in the locale/translations files. Any help is welcome. Tested mostly on Firefox (121.0) and quickly on Vivaldi(6.5.3206.53), chrome based. Fixes: #1845 #1846 --- internal/locale/translations/de_DE.json | 11 +++- internal/locale/translations/el_EL.json | 11 +++- internal/locale/translations/en_US.json | 11 +++- internal/locale/translations/es_ES.json | 11 +++- internal/locale/translations/fi_FI.json | 11 +++- internal/locale/translations/fr_FR.json | 11 +++- internal/locale/translations/hi_IN.json | 11 +++- internal/locale/translations/id_ID.json | 11 +++- internal/locale/translations/it_IT.json | 11 +++- internal/locale/translations/ja_JP.json | 11 +++- internal/locale/translations/nl_NL.json | 11 +++- internal/locale/translations/pl_PL.json | 11 +++- internal/locale/translations/pt_BR.json | 11 +++- internal/locale/translations/ru_RU.json | 11 +++- internal/locale/translations/tr_TR.json | 11 +++- internal/locale/translations/uk_UA.json | 11 +++- internal/locale/translations/zh_CN.json | 11 +++- internal/locale/translations/zh_TW.json | 11 +++- .../common/enclosure_media_controls.html | 18 +++++++ internal/template/templates/views/entry.html | 8 +++ internal/ui/static/css/common.css | 33 ++++++++++++ internal/ui/static/js/app.js | 50 +++++++++++++++++-- internal/ui/static/js/bootstrap.js | 14 ++++++ 23 files changed, 298 insertions(+), 23 deletions(-) create mode 100644 internal/template/templates/common/enclosure_media_controls.html diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index c49a27f6..b8cceb46 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Abonnement kann nicht durch RSS-Bridge erkannt werden: %v.", "error.feed_format_not_detected": "Das Format des Abonnements kann nicht erkannt werden: %v.", "form.prefs.label.media_playback_rate": "Wiedergabegeschwindigkeit von Audio/Video", - "error.settings_media_playback_rate_range": "Die Wiedergabegeschwindigkeit liegt außerhalb des Bereichs" + "error.settings_media_playback_rate_range": "Die Wiedergabegeschwindigkeit liegt außerhalb des Bereichs", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json index 94d74149..42758fb7 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Ταχύτητα αναπαραγωγής του ήχου/βίντεο", - "error.settings_media_playback_rate_range": "Η ταχύτητα αναπαραγωγής είναι εκτός εύρους" + "error.settings_media_playback_rate_range": "Η ταχύτητα αναπαραγωγής είναι εκτός εύρους", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json index 77b73778..61e14f8e 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Playback speed of the audio/video", - "error.settings_media_playback_rate_range": "Playback speed is out of range" + "error.settings_media_playback_rate_range": "Playback speed is out of range", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json index d4669b39..cdb4b9df 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Velocidad de reproducción del audio/vídeo", - "error.settings_media_playback_rate_range": "La velocidad de reproducción está fuera de rango" + "error.settings_media_playback_rate_range": "La velocidad de reproducción está fuera de rango", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json index 6b2f2fca..922b6356 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Äänen/videon toistonopeus", - "error.settings_media_playback_rate_range": "Toistonopeus on alueen ulkopuolella" + "error.settings_media_playback_rate_range": "Toistonopeus on alueen ulkopuolella", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json index fb2982fa..c533a15c 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Impossible de détecter un flux RSS en utilisant RSS-Bridge: %v.", "error.feed_format_not_detected": "Impossible de détecter le format du flux : %v.", "form.prefs.label.media_playback_rate": "Vitesse de lecture de l'audio/vidéo", - "error.settings_media_playback_rate_range": "La vitesse de lecture est hors limites" + "error.settings_media_playback_rate_range": "La vitesse de lecture est hors limites", + "enclosure_media_controls.seek" : "Avancer/Reculer :", + "enclosure_media_controls.seek.title" : "Avancer/Reculer de %s seconds", + "enclosure_media_controls.speed" : "Vitesse :", + "enclosure_media_controls.speed.faster" : "Accélérer", + "enclosure_media_controls.speed.faster.title" : "Accélérer de %sx", + "enclosure_media_controls.speed.slower" : "Ralentir", + "enclosure_media_controls.speed.slower.title" : "Ralentir de %sx", + "enclosure_media_controls.speed.reset" : "Réinitialiser", + "enclosure_media_controls.speed.reset.title" : "Réinitialiser la vitesse de lecture à 1x" } diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json index ef7c0c04..b857b1c9 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "ऑडियो/वीडियो की प्लेबैक गति", - "error.settings_media_playback_rate_range": "प्लेबैक गति सीमा से बाहर है" + "error.settings_media_playback_rate_range": "प्लेबैक गति सीमा से बाहर है", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json index 3f1e3cd3..a0ac7880 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -512,5 +512,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Kecepatan pemutaran audio/video", - "error.settings_media_playback_rate_range": "Kecepatan pemutaran di luar jangkauan" + "error.settings_media_playback_rate_range": "Kecepatan pemutaran di luar jangkauan", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json index e9385c7c..e3b12225 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Velocità di riproduzione dell'audio/video", - "error.settings_media_playback_rate_range": "La velocità di riproduzione non rientra nell'intervallo" + "error.settings_media_playback_rate_range": "La velocità di riproduzione non rientra nell'intervallo", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json index efd2d37d..55f6de83 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -512,5 +512,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "オーディオ/ビデオの再生速度", - "error.settings_media_playback_rate_range": "再生速度が範囲外" + "error.settings_media_playback_rate_range": "再生速度が範囲外", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json index d9141819..c9b235e2 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Afspeelsnelheid van de audio/video", - "error.settings_media_playback_rate_range": "Afspeelsnelheid is buiten bereik" + "error.settings_media_playback_rate_range": "Afspeelsnelheid is buiten bereik", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json index 3b39301d..78c49876 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -546,5 +546,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Prędkość odtwarzania audio/wideo", - "error.settings_media_playback_rate_range": "Prędkość odtwarzania jest poza zakresem" + "error.settings_media_playback_rate_range": "Prędkość odtwarzania jest poza zakresem", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json index 7c639f5b..dd9dca9c 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -529,5 +529,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Velocidade de reprodução do áudio/vídeo", - "error.settings_media_playback_rate_range": "A velocidade de reprodução está fora do intervalo" + "error.settings_media_playback_rate_range": "A velocidade de reprodução está fora do intervalo", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json index 6327c09f..636ef54e 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -546,5 +546,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Скорость воспроизведения аудио/видео", - "error.settings_media_playback_rate_range": "Скорость воспроизведения выходит за пределы диапазона" + "error.settings_media_playback_rate_range": "Скорость воспроизведения выходит за пределы диапазона", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json index 4fc999a2..87a67025 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -496,5 +496,14 @@ "time_elapsed.years": ["%d yıl önce", "%d yıl önce"], "time_elapsed.yesterday": "dün", "tooltip.keyboard_shortcuts": "Klavye Kısayolu: %s", - "tooltip.logged_user": "%s olarak giriş yapıldı" + "tooltip.logged_user": "%s olarak giriş yapıldı", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json index 832a1471..221a4ca9 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -546,5 +546,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "Швидкість відтворення аудіо/відео", - "error.settings_media_playback_rate_range": "Швидкість відтворення виходить за межі діапазону" + "error.settings_media_playback_rate_range": "Швидкість відтворення виходить за межі діапазону", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index b32a270a..d1797c24 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -512,5 +512,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "音频/视频的播放速度", - "error.settings_media_playback_rate_range": "播放速度超出范围" + "error.settings_media_playback_rate_range": "播放速度超出范围", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json index 39504b73..35be9e2b 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -512,5 +512,14 @@ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.", "error.feed_format_not_detected": "Unable to detect feed format: %v.", "form.prefs.label.media_playback_rate": "音訊/視訊的播放速度", - "error.settings_media_playback_rate_range": "播放速度超出範圍" + "error.settings_media_playback_rate_range": "播放速度超出範圍", + "enclosure_media_controls.seek" : "Seek:", + "enclosure_media_controls.seek.title" : "Seek %s seconds", + "enclosure_media_controls.speed" : "Speed:", + "enclosure_media_controls.speed.faster" : "Faster", + "enclosure_media_controls.speed.faster.title" : "Faster by %sx", + "enclosure_media_controls.speed.slower" : "Slower", + "enclosure_media_controls.speed.slower.title" : "Slower by %sx", + "enclosure_media_controls.speed.reset" : "Reset", + "enclosure_media_controls.speed.reset.title" : "Reset speed to 1x" } diff --git a/internal/template/templates/common/enclosure_media_controls.html b/internal/template/templates/common/enclosure_media_controls.html new file mode 100644 index 00000000..208b3933 --- /dev/null +++ b/internal/template/templates/common/enclosure_media_controls.html @@ -0,0 +1,18 @@ +{{ define "enclosure_media_controls" }} +
+
+
{{ t "enclosure_media_controls.seek" }}
+ + + + +
+
+ +
{{ t "enclosure_media_controls.speed" }} (x.xxx)
+ + + +
+
+{{ end }} diff --git a/internal/template/templates/views/entry.html b/internal/template/templates/views/entry.html index 48f3c5fe..85618954 100644 --- a/internal/template/templates/views/entry.html +++ b/internal/template/templates/views/entry.html @@ -174,6 +174,7 @@ data-last-position="{{ .MediaProgression }}" {{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }} data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}" + data-enclosure-id="{{.ID}}" > {{ if (and $.user (mustBeProxyfied "audio")) }} @@ -181,6 +182,7 @@ {{ end }} + {{ template "enclosure_media_controls" . }} {{ else if hasPrefix .MimeType "video/" }}
@@ -188,6 +190,7 @@ data-last-position="{{ .MediaProgression }}" {{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }} data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}" + data-enclosure-id="{{.ID}}" > {{ if (and $.user (mustBeProxyfied "video")) }} @@ -195,6 +198,7 @@ {{ end }} + {{ template "enclosure_media_controls" . }}
{{ end }} {{ end }} @@ -218,6 +222,7 @@ data-last-position="{{ .MediaProgression }}" {{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }} data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}" + data-enclosure-id="{{.ID}}" > {{ if (and $.user (mustBeProxyfied "audio")) }} @@ -225,6 +230,7 @@ {{ end }} + {{ template "enclosure_media_controls" . }} {{ else if hasPrefix .MimeType "video/" }}
@@ -232,6 +238,7 @@ data-last-position="{{ .MediaProgression }}" {{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }} data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}" + data-enclosure-id="{{.ID}}" > {{ if (and $.user (mustBeProxyfied "video")) }} @@ -239,6 +246,7 @@ {{ end }} + {{ template "enclosure_media_controls" . }}
{{ else if hasPrefix .MimeType "image/" }}
diff --git a/internal/ui/static/css/common.css b/internal/ui/static/css/common.css index 1a3c3ee8..a27aadae 100644 --- a/internal/ui/static/css/common.css +++ b/internal/ui/static/css/common.css @@ -1215,6 +1215,39 @@ audio, video { width: 100%; } +.media-controls{ + font-size: .9em; + display: flex; + flex-wrap: wrap; +} + +.media-controls .media-control-label{ + line-height: 1em; +} + +.media-controls>div{ + display: flex; + flex-wrap: nowrap; + justify-content:center; + min-width: 50%; + align-items: center; +} + +.media-controls>div>*{ + padding-left:12px; +} + +.media-controls>div>*:first-child{ + padding-left:0; +} + +.media-controls span.speed-indicator{ + /*monospace to ensure constant width even when value change. JS ensure the value is always on 4 characters (in most cases) + This reduce ui flickering due to element moving around a bit + */ + font-family: monospace; +} + .integration-form summary { font-weight: 700; } diff --git a/internal/ui/static/js/app.js b/internal/ui/static/js/app.js index 02911194..03f2880e 100644 --- a/internal/ui/static/js/app.js +++ b/internal/ui/static/js/app.js @@ -446,8 +446,8 @@ function goToPage(page, fallbackSelf) { } /** - * - * @param {(number|event)} offset - many items to jump for focus. + * + * @param {(number|event)} offset - many items to jump for focus. */ function goToPrevious(offset) { if (offset instanceof KeyboardEvent) { @@ -461,8 +461,8 @@ function goToPrevious(offset) { } /** - * - * @param {(number|event)} offset - How many items to jump for focus. + * + * @param {(number|event)} offset - How many items to jump for focus. */ function goToNext(offset) { if (offset instanceof KeyboardEvent) { @@ -521,7 +521,7 @@ function goToListItem(offset) { items[i].classList.remove("current-item"); // By default adjust selection by offset - let itemOffset = (i + offset + items.length) % items.length; + let itemOffset = (i + offset + items.length) % items.length; // Allow jumping to top or bottom if (offset == TOP) { itemOffset = 0; @@ -742,3 +742,43 @@ function getCsrfToken() { return ""; } + +/** + * Handle all clicks on media player controls button on enclosures. + * Will change the current speed and position of the player accordingly. + * Will not save anything, all is done client-side, however, changing the position + * will trigger the handlePlayerProgressionSave and save the new position backends side. + * @param {Element} button + */ +function handleMediaControl(button) { + const action = button.dataset.enclosureAction; + const value = parseFloat(button.dataset.actionValue); + const targetEnclosureId = button.dataset.enclosureId; + const enclosures = document.querySelectorAll(`audio[data-enclosure-id="${targetEnclosureId}"],video[data-enclosure-id="${targetEnclosureId}"]`); + const speedIndicator = document.querySelectorAll(`span.speed-indicator[data-enclosure-id="${targetEnclosureId}"]`); + enclosures.forEach((enclosure) => { + switch (action) { + case "seek": + enclosure.currentTime = enclosure.currentTime + value > 0 ? enclosure.currentTime + value : 0; + break; + case "speed": + // I set a floor speed of 0.25 to avoid too slow speed where it gives the impression it stopped. + // 0.25 was chosen because it will allow to get back to 1x in two "faster" click, and lower value with same property would be 0. + enclosure.playbackRate = Math.max(0.25, enclosure.playbackRate + value); + speedIndicator.forEach((speedI) => { + // Two digit precision to ensure we always have the same number of characters (4) to avoid controls moving when clicking buttons because of more or less characters. + // The trick only work on rate less than 10, but it feels an acceptable tread of considering the feature + speedI.innerText = `${enclosure.playbackRate.toFixed(2)}x`; + }); + break; + case "speed-reset": + enclosure.playbackRate = value ; + speedIndicator.forEach((speedI) => { + // Two digit precision to ensure we always have the same number of characters (4) to avoid controls moving when clicking buttons because of more or less characters. + // The trick only work on rate less than 10, but it feels an acceptable tread of considering the feature + speedI.innerText = `${enclosure.playbackRate.toFixed(2)}x`; + }); + break; + } + }); +} diff --git a/internal/ui/static/js/bootstrap.js b/internal/ui/static/js/bootstrap.js index 44d6e716..d78a9d41 100644 --- a/internal/ui/static/js/bootstrap.js +++ b/internal/ui/static/js/bootstrap.js @@ -167,6 +167,20 @@ document.addEventListener("DOMContentLoaded", () => { playbackRateElements.forEach((element) => { if (element.dataset.playbackRate) { element.playbackRate = element.dataset.playbackRate; + if (element.dataset.enclosureId){ + // In order to display properly the speed we need to do it on bootstrap. + // Could not do it backend side because I didn't know how to do it because of the template inclusion and + // the way the initial playback speed is handled. See enclosure_media_controls.html if you want to try to fix this + document.querySelectorAll(`span.speed-indicator[data-enclosure-id="${element.dataset.enclosureId}"]`).forEach((speedI)=>{ + speedI.innerText = `${element.dataset.playbackRate}x`; + }); + } } }); + + // Set enclosure media controls handlers + const mediaControlsElements = document.querySelectorAll("button[data-enclosure-action]"); + mediaControlsElements.forEach((element) => { + element.addEventListener("click", () => handleMediaControl(element)); + }); });