From da8f92a60754147ec7b424790b51e47a20c6d50a 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 cc60035a..b0142715 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -528,5 +528,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 82282163..43c906fe 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -528,5 +528,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 97e58fe5..476b9764 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -528,5 +528,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 914d8994..50aec41b 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -528,5 +528,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 41bd2e50..4e62c245 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -528,5 +528,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 d616eb3e..244c7308 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -528,5 +528,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 199c66af..1c7c2712 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -528,5 +528,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 ee06ac61..fa633a44 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -511,5 +511,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 6e808a02..5b9d170c 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -528,5 +528,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 8c767b55..82b133cb 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -511,5 +511,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 d47510e9..751bd8c1 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -528,5 +528,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 32f3e4f8..359db9c5 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -545,5 +545,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 56861a3a..56b78ecb 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -528,5 +528,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 69c139f0..adbef9c9 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -545,5 +545,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 15fe4c8c..fad2858a 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -495,5 +495,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 eeb305d7..b30f89cf 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -545,5 +545,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 dc1079c4..5bdba072 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -511,5 +511,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 4b4316f3..9791e9c0 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -511,5 +511,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..ad85361d --- /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 4284f097..9baf02d9 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..218c2a53 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); + 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)); + }); });