parent
b68ada396a
commit
00dabc1d3c
31 changed files with 188 additions and 74 deletions
|
@ -41,6 +41,7 @@ type User struct {
|
||||||
DefaultHomePage string `json:"default_home_page"`
|
DefaultHomePage string `json:"default_home_page"`
|
||||||
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView bool `json:"mark_read_on_view"`
|
MarkReadOnView bool `json:"mark_read_on_view"`
|
||||||
|
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) String() string {
|
func (u User) String() string {
|
||||||
|
@ -58,28 +59,29 @@ type UserCreationRequest struct {
|
||||||
|
|
||||||
// UserModificationRequest represents the request to update a user.
|
// UserModificationRequest represents the request to update a user.
|
||||||
type UserModificationRequest struct {
|
type UserModificationRequest struct {
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
IsAdmin *bool `json:"is_admin"`
|
IsAdmin *bool `json:"is_admin"`
|
||||||
Theme *string `json:"theme"`
|
Theme *string `json:"theme"`
|
||||||
Language *string `json:"language"`
|
Language *string `json:"language"`
|
||||||
Timezone *string `json:"timezone"`
|
Timezone *string `json:"timezone"`
|
||||||
EntryDirection *string `json:"entry_sorting_direction"`
|
EntryDirection *string `json:"entry_sorting_direction"`
|
||||||
EntryOrder *string `json:"entry_sorting_order"`
|
EntryOrder *string `json:"entry_sorting_order"`
|
||||||
Stylesheet *string `json:"stylesheet"`
|
Stylesheet *string `json:"stylesheet"`
|
||||||
GoogleID *string `json:"google_id"`
|
GoogleID *string `json:"google_id"`
|
||||||
OpenIDConnectID *string `json:"openid_connect_id"`
|
OpenIDConnectID *string `json:"openid_connect_id"`
|
||||||
EntriesPerPage *int `json:"entries_per_page"`
|
EntriesPerPage *int `json:"entries_per_page"`
|
||||||
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
||||||
ShowReadingTime *bool `json:"show_reading_time"`
|
ShowReadingTime *bool `json:"show_reading_time"`
|
||||||
EntrySwipe *bool `json:"entry_swipe"`
|
EntrySwipe *bool `json:"entry_swipe"`
|
||||||
GestureNav *string `json:"gesture_nav"`
|
GestureNav *string `json:"gesture_nav"`
|
||||||
DisplayMode *string `json:"display_mode"`
|
DisplayMode *string `json:"display_mode"`
|
||||||
DefaultReadingSpeed *int `json:"default_reading_speed"`
|
DefaultReadingSpeed *int `json:"default_reading_speed"`
|
||||||
CJKReadingSpeed *int `json:"cjk_reading_speed"`
|
CJKReadingSpeed *int `json:"cjk_reading_speed"`
|
||||||
DefaultHomePage *string `json:"default_home_page"`
|
DefaultHomePage *string `json:"default_home_page"`
|
||||||
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView *bool `json:"mark_read_on_view"`
|
MarkReadOnView *bool `json:"mark_read_on_view"`
|
||||||
|
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users represents a list of users.
|
// Users represents a list of users.
|
||||||
|
|
|
@ -871,4 +871,9 @@ var migrations = []func(tx *sql.Tx) error{
|
||||||
_, err = tx.Exec(sql)
|
_, err = tx.Exec(sql)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
func(tx *sql.Tx) (err error) {
|
||||||
|
sql := `ALTER TABLE users ADD COLUMN media_playback_rate numeric default 1;`
|
||||||
|
_, err = tx.Exec(sql)
|
||||||
|
return err
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Dieses Abonnement kann nicht gelesen werden: %v.",
|
"error.unable_to_parse_feed": "Dieses Abonnement kann nicht gelesen werden: %v.",
|
||||||
"error.feed_not_found": "Dieses Abonnement existiert nicht oder gehört nicht zu diesem Benutzer.",
|
"error.feed_not_found": "Dieses Abonnement existiert nicht oder gehört nicht zu diesem Benutzer.",
|
||||||
"error.unable_to_detect_rssbridge": "Abonnement kann nicht durch RSS-Bridge erkannt werden: %v.",
|
"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."
|
"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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "Ταχύτητα αναπαραγωγής του ήχου/βίντεο",
|
||||||
|
"error.settings_media_playback_rate_range": "Η ταχύτητα αναπαραγωγής είναι εκτός εύρους"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Impossible d'analyser ce flux : %v.",
|
"error.unable_to_parse_feed": "Impossible d'analyser ce flux : %v.",
|
||||||
"error.feed_not_found": "Impossible de trouver ce flux.",
|
"error.feed_not_found": "Impossible de trouver ce flux.",
|
||||||
"error.unable_to_detect_rssbridge": "Impossible de détecter un flux RSS en utilisant RSS-Bridge: %v.",
|
"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."
|
"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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "ऑडियो/वीडियो की प्लेबैक गति",
|
||||||
|
"error.settings_media_playback_rate_range": "प्लेबैक गति सीमा से बाहर है"
|
||||||
}
|
}
|
||||||
|
|
|
@ -507,5 +507,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -507,5 +507,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "オーディオ/ビデオの再生速度",
|
||||||
|
"error.settings_media_playback_rate_range": "再生速度が範囲外"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,5 +541,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,5 +541,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "Скорость воспроизведения аудио/видео",
|
||||||
|
"error.settings_media_playback_rate_range": "Скорость воспроизведения выходит за пределы диапазона"
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,5 +524,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "Ses/video oynatma hızı",
|
||||||
|
"error.settings_media_playback_rate_range": "Oynatma hızı aralık dışında"
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,5 +541,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "Швидкість відтворення аудіо/відео",
|
||||||
|
"error.settings_media_playback_rate_range": "Швидкість відтворення виходить за межі діапазону"
|
||||||
}
|
}
|
||||||
|
|
|
@ -507,5 +507,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "音频/视频的播放速度",
|
||||||
|
"error.settings_media_playback_rate_range": "播放速度超出范围"
|
||||||
}
|
}
|
||||||
|
|
|
@ -507,5 +507,7 @@
|
||||||
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
|
||||||
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
|
||||||
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
|
||||||
"error.feed_format_not_detected": "Unable to detect feed format: %v."
|
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
|
||||||
|
"form.prefs.label.media_playback_rate": "音訊/視訊的播放速度",
|
||||||
|
"error.settings_media_playback_rate_range": "播放速度超出範圍"
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,3 +26,11 @@ func OptionalInt64(value int64) *int64 {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptionalFloat populates an optional float64 field.
|
||||||
|
func OptionalFloat(value float64) *float64 {
|
||||||
|
if value > 0 {
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ type User struct {
|
||||||
DefaultHomePage string `json:"default_home_page"`
|
DefaultHomePage string `json:"default_home_page"`
|
||||||
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView bool `json:"mark_read_on_view"`
|
MarkReadOnView bool `json:"mark_read_on_view"`
|
||||||
|
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserCreationRequest represents the request to create a user.
|
// UserCreationRequest represents the request to create a user.
|
||||||
|
@ -48,28 +49,29 @@ type UserCreationRequest struct {
|
||||||
|
|
||||||
// UserModificationRequest represents the request to update a user.
|
// UserModificationRequest represents the request to update a user.
|
||||||
type UserModificationRequest struct {
|
type UserModificationRequest struct {
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
Theme *string `json:"theme"`
|
Theme *string `json:"theme"`
|
||||||
Language *string `json:"language"`
|
Language *string `json:"language"`
|
||||||
Timezone *string `json:"timezone"`
|
Timezone *string `json:"timezone"`
|
||||||
EntryDirection *string `json:"entry_sorting_direction"`
|
EntryDirection *string `json:"entry_sorting_direction"`
|
||||||
EntryOrder *string `json:"entry_sorting_order"`
|
EntryOrder *string `json:"entry_sorting_order"`
|
||||||
Stylesheet *string `json:"stylesheet"`
|
Stylesheet *string `json:"stylesheet"`
|
||||||
GoogleID *string `json:"google_id"`
|
GoogleID *string `json:"google_id"`
|
||||||
OpenIDConnectID *string `json:"openid_connect_id"`
|
OpenIDConnectID *string `json:"openid_connect_id"`
|
||||||
EntriesPerPage *int `json:"entries_per_page"`
|
EntriesPerPage *int `json:"entries_per_page"`
|
||||||
IsAdmin *bool `json:"is_admin"`
|
IsAdmin *bool `json:"is_admin"`
|
||||||
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
||||||
ShowReadingTime *bool `json:"show_reading_time"`
|
ShowReadingTime *bool `json:"show_reading_time"`
|
||||||
EntrySwipe *bool `json:"entry_swipe"`
|
EntrySwipe *bool `json:"entry_swipe"`
|
||||||
GestureNav *string `json:"gesture_nav"`
|
GestureNav *string `json:"gesture_nav"`
|
||||||
DisplayMode *string `json:"display_mode"`
|
DisplayMode *string `json:"display_mode"`
|
||||||
DefaultReadingSpeed *int `json:"default_reading_speed"`
|
DefaultReadingSpeed *int `json:"default_reading_speed"`
|
||||||
CJKReadingSpeed *int `json:"cjk_reading_speed"`
|
CJKReadingSpeed *int `json:"cjk_reading_speed"`
|
||||||
DefaultHomePage *string `json:"default_home_page"`
|
DefaultHomePage *string `json:"default_home_page"`
|
||||||
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView *bool `json:"mark_read_on_view"`
|
MarkReadOnView *bool `json:"mark_read_on_view"`
|
||||||
|
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch updates the User object with the modification request.
|
// Patch updates the User object with the modification request.
|
||||||
|
@ -161,6 +163,10 @@ func (u *UserModificationRequest) Patch(user *User) {
|
||||||
if u.MarkReadOnView != nil {
|
if u.MarkReadOnView != nil {
|
||||||
user.MarkReadOnView = *u.MarkReadOnView
|
user.MarkReadOnView = *u.MarkReadOnView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.MediaPlaybackRate != nil {
|
||||||
|
user.MediaPlaybackRate = *u.MediaPlaybackRate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseTimezone converts last login date to the given timezone.
|
// UseTimezone converts last login date to the given timezone.
|
||||||
|
|
|
@ -91,7 +91,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
||||||
cjk_reading_speed,
|
cjk_reading_speed,
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view
|
mark_read_on_view,
|
||||||
|
media_playback_rate
|
||||||
`
|
`
|
||||||
|
|
||||||
tx, err := s.db.Begin()
|
tx, err := s.db.Begin()
|
||||||
|
@ -130,6 +131,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
||||||
&user.DefaultHomePage,
|
&user.DefaultHomePage,
|
||||||
&user.CategoriesSortingOrder,
|
&user.CategoriesSortingOrder,
|
||||||
&user.MarkReadOnView,
|
&user.MarkReadOnView,
|
||||||
|
&user.MediaPlaybackRate,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
@ -186,9 +188,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
cjk_reading_speed=$19,
|
cjk_reading_speed=$19,
|
||||||
default_home_page=$20,
|
default_home_page=$20,
|
||||||
categories_sorting_order=$21,
|
categories_sorting_order=$21,
|
||||||
mark_read_on_view=$22
|
mark_read_on_view=$22,
|
||||||
|
media_playback_rate=$23
|
||||||
WHERE
|
WHERE
|
||||||
id=$23
|
id=$24
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err = s.db.Exec(
|
_, err = s.db.Exec(
|
||||||
|
@ -215,6 +218,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.DefaultHomePage,
|
user.DefaultHomePage,
|
||||||
user.CategoriesSortingOrder,
|
user.CategoriesSortingOrder,
|
||||||
user.MarkReadOnView,
|
user.MarkReadOnView,
|
||||||
|
user.MediaPlaybackRate,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -243,9 +247,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
cjk_reading_speed=$18,
|
cjk_reading_speed=$18,
|
||||||
default_home_page=$19,
|
default_home_page=$19,
|
||||||
categories_sorting_order=$20,
|
categories_sorting_order=$20,
|
||||||
mark_read_on_view=$21
|
mark_read_on_view=$21,
|
||||||
|
media_playback_rate=$22
|
||||||
WHERE
|
WHERE
|
||||||
id=$22
|
id=$23
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err := s.db.Exec(
|
_, err := s.db.Exec(
|
||||||
|
@ -271,6 +276,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.DefaultHomePage,
|
user.DefaultHomePage,
|
||||||
user.CategoriesSortingOrder,
|
user.CategoriesSortingOrder,
|
||||||
user.MarkReadOnView,
|
user.MarkReadOnView,
|
||||||
|
user.MediaPlaybackRate,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -318,7 +324,8 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
||||||
cjk_reading_speed,
|
cjk_reading_speed,
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view
|
mark_read_on_view,
|
||||||
|
media_playback_rate
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -353,7 +360,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
||||||
cjk_reading_speed,
|
cjk_reading_speed,
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view
|
mark_read_on_view,
|
||||||
|
media_playback_rate
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -388,7 +396,8 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
|
||||||
cjk_reading_speed,
|
cjk_reading_speed,
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view
|
mark_read_on_view,
|
||||||
|
media_playback_rate
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -430,7 +439,8 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
||||||
u.cjk_reading_speed,
|
u.cjk_reading_speed,
|
||||||
u.default_home_page,
|
u.default_home_page,
|
||||||
u.categories_sorting_order,
|
u.categories_sorting_order,
|
||||||
u.mark_read_on_view
|
u.mark_read_on_view,
|
||||||
|
media_playback_rate
|
||||||
FROM
|
FROM
|
||||||
users u
|
users u
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
@ -467,6 +477,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
||||||
&user.DefaultHomePage,
|
&user.DefaultHomePage,
|
||||||
&user.CategoriesSortingOrder,
|
&user.CategoriesSortingOrder,
|
||||||
&user.MarkReadOnView,
|
&user.MarkReadOnView,
|
||||||
|
&user.MediaPlaybackRate,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
@ -574,7 +585,8 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
cjk_reading_speed,
|
cjk_reading_speed,
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view
|
mark_read_on_view,
|
||||||
|
media_playback_rate
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
ORDER BY username ASC
|
ORDER BY username ASC
|
||||||
|
@ -612,6 +624,7 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
&user.DefaultHomePage,
|
&user.DefaultHomePage,
|
||||||
&user.CategoriesSortingOrder,
|
&user.CategoriesSortingOrder,
|
||||||
&user.MarkReadOnView,
|
&user.MarkReadOnView,
|
||||||
|
&user.MediaPlaybackRate,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -172,6 +172,7 @@
|
||||||
<div class="enclosure-audio" >
|
<div class="enclosure-audio" >
|
||||||
<audio controls preload="metadata"
|
<audio controls preload="metadata"
|
||||||
data-last-position="{{ .MediaProgression }}"
|
data-last-position="{{ .MediaProgression }}"
|
||||||
|
{{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }}
|
||||||
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
||||||
>
|
>
|
||||||
{{ if (and $.user (mustBeProxyfied "audio")) }}
|
{{ if (and $.user (mustBeProxyfied "audio")) }}
|
||||||
|
@ -185,6 +186,7 @@
|
||||||
<div class="enclosure-video">
|
<div class="enclosure-video">
|
||||||
<video controls preload="metadata"
|
<video controls preload="metadata"
|
||||||
data-last-position="{{ .MediaProgression }}"
|
data-last-position="{{ .MediaProgression }}"
|
||||||
|
{{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }}
|
||||||
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
||||||
>
|
>
|
||||||
{{ if (and $.user (mustBeProxyfied "video")) }}
|
{{ if (and $.user (mustBeProxyfied "video")) }}
|
||||||
|
@ -214,6 +216,7 @@
|
||||||
<div class="enclosure-audio">
|
<div class="enclosure-audio">
|
||||||
<audio controls preload="metadata"
|
<audio controls preload="metadata"
|
||||||
data-last-position="{{ .MediaProgression }}"
|
data-last-position="{{ .MediaProgression }}"
|
||||||
|
{{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }}
|
||||||
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
||||||
>
|
>
|
||||||
{{ if (and $.user (mustBeProxyfied "audio")) }}
|
{{ if (and $.user (mustBeProxyfied "audio")) }}
|
||||||
|
@ -227,6 +230,7 @@
|
||||||
<div class="enclosure-video">
|
<div class="enclosure-video">
|
||||||
<video controls preload="metadata"
|
<video controls preload="metadata"
|
||||||
data-last-position="{{ .MediaProgression }}"
|
data-last-position="{{ .MediaProgression }}"
|
||||||
|
{{ if $.user.MediaPlaybackRate }}data-playback-rate="{{ $.user.MediaPlaybackRate }}"{{ end }}
|
||||||
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
data-save-url="{{ route "saveEnclosureProgression" "enclosureID" .ID }}"
|
||||||
>
|
>
|
||||||
{{ if (and $.user (mustBeProxyfied "video")) }}
|
{{ if (and $.user (mustBeProxyfied "video")) }}
|
||||||
|
|
|
@ -108,6 +108,9 @@
|
||||||
<label for="form-default-reading-speed">{{ t "form.prefs.label.default_reading_speed" }}</label>
|
<label for="form-default-reading-speed">{{ t "form.prefs.label.default_reading_speed" }}</label>
|
||||||
<input type="number" name="default_reading_speed" id="form-default-reading-speed" value="{{ .form.DefaultReadingSpeed }}" min="1">
|
<input type="number" name="default_reading_speed" id="form-default-reading-speed" value="{{ .form.DefaultReadingSpeed }}" min="1">
|
||||||
|
|
||||||
|
<label for="form-media-playback-rate">{{ t "form.prefs.label.media_playback_rate" }}</label>
|
||||||
|
<input type="number" name="media_playback_rate" id="form-media-playback-rate" value="{{ .form.MediaPlaybackRate }}" min="0.25" max="4" step="any" />
|
||||||
|
|
||||||
<label><input type="checkbox" name="show_reading_time" value="1" {{ if .form.ShowReadingTime }}checked{{ end }}> {{ t "form.prefs.label.show_reading_time" }}</label>
|
<label><input type="checkbox" name="show_reading_time" value="1" {{ if .form.ShowReadingTime }}checked{{ end }}> {{ t "form.prefs.label.show_reading_time" }}</label>
|
||||||
|
|
||||||
<label><input type="checkbox" name="mark_read_on_view" value="1" {{ if .form.MarkReadOnView }}checked{{ end }}> {{ t "form.prefs.label.mark_read_on_view" }}</label>
|
<label><input type="checkbox" name="mark_read_on_view" value="1" {{ if .form.MarkReadOnView }}checked{{ end }}> {{ t "form.prefs.label.mark_read_on_view" }}</label>
|
||||||
|
|
|
@ -33,6 +33,7 @@ type SettingsForm struct {
|
||||||
DefaultHomePage string
|
DefaultHomePage string
|
||||||
CategoriesSortingOrder string
|
CategoriesSortingOrder string
|
||||||
MarkReadOnView bool
|
MarkReadOnView bool
|
||||||
|
MediaPlaybackRate float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge updates the fields of the given user.
|
// Merge updates the fields of the given user.
|
||||||
|
@ -55,6 +56,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
||||||
user.DefaultHomePage = s.DefaultHomePage
|
user.DefaultHomePage = s.DefaultHomePage
|
||||||
user.CategoriesSortingOrder = s.CategoriesSortingOrder
|
user.CategoriesSortingOrder = s.CategoriesSortingOrder
|
||||||
user.MarkReadOnView = s.MarkReadOnView
|
user.MarkReadOnView = s.MarkReadOnView
|
||||||
|
user.MediaPlaybackRate = s.MediaPlaybackRate
|
||||||
|
|
||||||
if s.Password != "" {
|
if s.Password != "" {
|
||||||
user.Password = s.Password
|
user.Password = s.Password
|
||||||
|
@ -84,6 +86,10 @@ func (s *SettingsForm) Validate() *locale.LocalizedError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.MediaPlaybackRate < 0.25 || s.MediaPlaybackRate > 4 {
|
||||||
|
return locale.NewLocalizedError("error.settings_media_playback_rate_range")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +107,10 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cjkReadingSpeed = 0
|
cjkReadingSpeed = 0
|
||||||
}
|
}
|
||||||
|
mediaPlaybackRate, err := strconv.ParseFloat(r.FormValue("media_playback_rate"), 64)
|
||||||
|
if err != nil {
|
||||||
|
mediaPlaybackRate = 1
|
||||||
|
}
|
||||||
return &SettingsForm{
|
return &SettingsForm{
|
||||||
Username: r.FormValue("username"),
|
Username: r.FormValue("username"),
|
||||||
Password: r.FormValue("password"),
|
Password: r.FormValue("password"),
|
||||||
|
@ -122,5 +132,6 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
||||||
DefaultHomePage: r.FormValue("default_home_page"),
|
DefaultHomePage: r.FormValue("default_home_page"),
|
||||||
CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
|
CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
|
||||||
MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
|
MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
|
||||||
|
MediaPlaybackRate: mediaPlaybackRate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ func TestValid(t *testing.T) {
|
||||||
DefaultReadingSpeed: 35,
|
DefaultReadingSpeed: 35,
|
||||||
CJKReadingSpeed: 25,
|
CJKReadingSpeed: 25,
|
||||||
DefaultHomePage: "unread",
|
DefaultHomePage: "unread",
|
||||||
|
MediaPlaybackRate: 1.25,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := settings.Validate()
|
err := settings.Validate()
|
||||||
|
@ -45,6 +46,7 @@ func TestConfirmationEmpty(t *testing.T) {
|
||||||
DefaultReadingSpeed: 35,
|
DefaultReadingSpeed: 35,
|
||||||
CJKReadingSpeed: 25,
|
CJKReadingSpeed: 25,
|
||||||
DefaultHomePage: "unread",
|
DefaultHomePage: "unread",
|
||||||
|
MediaPlaybackRate: 1.25,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := settings.Validate()
|
err := settings.Validate()
|
||||||
|
@ -72,6 +74,7 @@ func TestConfirmationIncorrect(t *testing.T) {
|
||||||
DefaultReadingSpeed: 35,
|
DefaultReadingSpeed: 35,
|
||||||
CJKReadingSpeed: 25,
|
CJKReadingSpeed: 25,
|
||||||
DefaultHomePage: "unread",
|
DefaultHomePage: "unread",
|
||||||
|
MediaPlaybackRate: 1.25,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := settings.Validate()
|
err := settings.Validate()
|
||||||
|
|
|
@ -41,6 +41,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
|
||||||
DefaultHomePage: user.DefaultHomePage,
|
DefaultHomePage: user.DefaultHomePage,
|
||||||
CategoriesSortingOrder: user.CategoriesSortingOrder,
|
CategoriesSortingOrder: user.CategoriesSortingOrder,
|
||||||
MarkReadOnView: user.MarkReadOnView,
|
MarkReadOnView: user.MarkReadOnView,
|
||||||
|
MediaPlaybackRate: user.MediaPlaybackRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
timezones, err := h.store.Timezones()
|
timezones, err := h.store.Timezones()
|
||||||
|
|
|
@ -62,6 +62,7 @@ func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
DefaultReadingSpeed: model.OptionalInt(settingsForm.DefaultReadingSpeed),
|
DefaultReadingSpeed: model.OptionalInt(settingsForm.DefaultReadingSpeed),
|
||||||
CJKReadingSpeed: model.OptionalInt(settingsForm.CJKReadingSpeed),
|
CJKReadingSpeed: model.OptionalInt(settingsForm.CJKReadingSpeed),
|
||||||
DefaultHomePage: model.OptionalString(settingsForm.DefaultHomePage),
|
DefaultHomePage: model.OptionalString(settingsForm.DefaultHomePage),
|
||||||
|
MediaPlaybackRate: model.OptionalFloat(settingsForm.MediaPlaybackRate),
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationErr := validator.ValidateUserModification(h.store, loggedUser.ID, userModificationRequest); validationErr != nil {
|
if validationErr := validator.ValidateUserModification(h.store, loggedUser.ID, userModificationRequest); validationErr != nil {
|
||||||
|
|
12
internal/ui/static/js/bootstrap.js
vendored
12
internal/ui/static/js/bootstrap.js
vendored
|
@ -152,11 +152,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save and resume media position
|
// Save and resume media position
|
||||||
const elements = document.querySelectorAll("audio[data-last-position],video[data-last-position]");
|
const lastPositionElements = document.querySelectorAll("audio[data-last-position],video[data-last-position]");
|
||||||
elements.forEach((element) => {
|
lastPositionElements.forEach((element) => {
|
||||||
if (element.dataset.lastPosition) {
|
if (element.dataset.lastPosition) {
|
||||||
element.currentTime = element.dataset.lastPosition;
|
element.currentTime = element.dataset.lastPosition;
|
||||||
}
|
}
|
||||||
element.ontimeupdate = () => handlePlayerProgressionSave(element);
|
element.ontimeupdate = () => handlePlayerProgressionSave(element);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set media playback rate
|
||||||
|
const playbackRateElements = document.querySelectorAll("audio[data-playback-rate],video[data-playback-rate]");
|
||||||
|
playbackRateElements.forEach((element) => {
|
||||||
|
if (element.dataset.playbackRate) {
|
||||||
|
element.playbackRate = element.dataset.playbackRate;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -102,6 +102,12 @@ func ValidateUserModification(store *storage.Storage, userID int64, changes *mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if changes.MediaPlaybackRate != nil {
|
||||||
|
if err := validateMediaPlaybackRate(*changes.MediaPlaybackRate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,3 +188,10 @@ func validateDefaultHomePage(defaultHomePage string) *locale.LocalizedError {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateMediaPlaybackRate(mediaPlaybackRate float64) *locale.LocalizedError {
|
||||||
|
if mediaPlaybackRate < 0.25 || mediaPlaybackRate > 4 {
|
||||||
|
return locale.NewLocalizedError("error.settings_media_playback_rate_range")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue