Fix #628: Add setting to transcode stream
This commit is contained in:
+8
-2
@@ -142,7 +142,10 @@ type ThemeConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TranscodingConfig struct {
|
type TranscodingConfig struct {
|
||||||
ForceRawFile bool
|
ForceRawFile bool
|
||||||
|
RequestTranscode bool
|
||||||
|
Codec string
|
||||||
|
MaxBitRateKBPS int
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeakMeterConfig struct {
|
type PeakMeterConfig struct {
|
||||||
@@ -260,7 +263,10 @@ func DefaultConfig(appVersionTag string) *Config {
|
|||||||
PreventClipping: true,
|
PreventClipping: true,
|
||||||
},
|
},
|
||||||
Transcoding: TranscodingConfig{
|
Transcoding: TranscodingConfig{
|
||||||
ForceRawFile: false,
|
ForceRawFile: false,
|
||||||
|
RequestTranscode: false,
|
||||||
|
Codec: "opus",
|
||||||
|
MaxBitRateKBPS: 160,
|
||||||
},
|
},
|
||||||
Theme: ThemeConfig{
|
Theme: ThemeConfig{
|
||||||
Appearance: "Dark",
|
Appearance: "Dark",
|
||||||
|
|||||||
@@ -332,12 +332,19 @@ func (j *jellyfinMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteP
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetStreamURL(trackID string, forceRaw bool) (string, error) {
|
func (j *jellyfinMediaProvider) GetStreamURL(trackID string, transcode *mediaprovider.TranscodeSettings, forceRaw bool) (string, error) {
|
||||||
return j.client.GetStreamURL(trackID)
|
var jfTranscode *jellyfin.TranscodeOptions
|
||||||
|
if transcode != nil {
|
||||||
|
jfTranscode = &jellyfin.TranscodeOptions{
|
||||||
|
AudioCodec: transcode.Codec,
|
||||||
|
AudioBitRate: uint32(transcode.BitRateKBPS * 1000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j.client.GetStreamURL(trackID, jfTranscode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) DownloadTrack(trackID string) (io.Reader, error) {
|
func (j *jellyfinMediaProvider) DownloadTrack(trackID string) (io.Reader, error) {
|
||||||
url, err := j.client.GetStreamURL(trackID)
|
url, err := j.client.GetStreamURL(trackID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,11 @@ type LoginResponse struct {
|
|||||||
IsAuthError bool
|
IsAuthError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TranscodeSettings struct {
|
||||||
|
Codec string
|
||||||
|
BitRateKBPS int
|
||||||
|
}
|
||||||
|
|
||||||
type Server interface {
|
type Server interface {
|
||||||
Login(username, password string) LoginResponse
|
Login(username, password string) LoginResponse
|
||||||
MediaProvider() MediaProvider
|
MediaProvider() MediaProvider
|
||||||
@@ -234,7 +239,7 @@ type MediaProvider interface {
|
|||||||
|
|
||||||
GetFavorites() (Favorites, error)
|
GetFavorites() (Favorites, error)
|
||||||
|
|
||||||
GetStreamURL(trackID string, forceRaw bool) (string, error)
|
GetStreamURL(trackID string, transcodeSettings *TranscodeSettings, forceRaw bool) (string, error)
|
||||||
|
|
||||||
GetTopTracks(artist Artist, count int) ([]*Track, error)
|
GetTopTracks(artist Artist, count int) ([]*Track, error)
|
||||||
|
|
||||||
|
|||||||
@@ -234,9 +234,12 @@ func (s *subsonicMediaProvider) GetSimilarTracks(artistID string, count int) ([]
|
|||||||
return sharedutil.MapSlice(tr, toTrack), nil
|
return sharedutil.MapSlice(tr, toTrack), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *subsonicMediaProvider) GetStreamURL(trackID string, forceRaw bool) (string, error) {
|
func (s *subsonicMediaProvider) GetStreamURL(trackID string, transcode *mediaprovider.TranscodeSettings, forceRaw bool) (string, error) {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
if forceRaw {
|
if transcode != nil {
|
||||||
|
m["format"] = transcode.Codec
|
||||||
|
m["maxBitRate"] = strconv.Itoa(transcode.BitRateKBPS)
|
||||||
|
} else if forceRaw {
|
||||||
m["format"] = "raw"
|
m["format"] = "raw"
|
||||||
}
|
}
|
||||||
u, err := s.client.GetStreamURL(trackID, m)
|
u, err := s.client.GetStreamURL(trackID, m)
|
||||||
|
|||||||
@@ -652,7 +652,14 @@ func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error {
|
|||||||
item := p.playQueue[idx]
|
item := p.playQueue[idx]
|
||||||
meta = item.Metadata()
|
meta = item.Metadata()
|
||||||
if tr, ok := item.(*mediaprovider.Track); ok {
|
if tr, ok := item.(*mediaprovider.Track); ok {
|
||||||
url, err = p.sm.Server.GetStreamURL(tr.ID, p.transcodeCfg.ForceRawFile)
|
var ts *mediaprovider.TranscodeSettings
|
||||||
|
if p.transcodeCfg.RequestTranscode {
|
||||||
|
ts = &mediaprovider.TranscodeSettings{
|
||||||
|
Codec: p.transcodeCfg.Codec,
|
||||||
|
BitRateKBPS: p.transcodeCfg.MaxBitRateKBPS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url, err = p.sm.Server.GetStreamURL(tr.ID, ts, p.transcodeCfg.ForceRawFile)
|
||||||
} else {
|
} else {
|
||||||
url = item.(*mediaprovider.RadioStation).StreamURL
|
url = item.(*mediaprovider.RadioStation).StreamURL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c
|
||||||
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64
|
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64
|
||||||
github.com/dweymouth/fyne-tooltip v0.3.0
|
github.com/dweymouth/fyne-tooltip v0.3.0
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645
|
github.com/dweymouth/go-jellyfin v0.0.0-20250531151636-29591764f0a0
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ github.com/dweymouth/fyne-tooltip v0.3.0 h1:NKCyTkh9NtvnTsiHtTOtaJzRDOFYP8AckQ2t
|
|||||||
github.com/dweymouth/fyne-tooltip v0.3.0/go.mod h1:m04ShLW/Tp6LXrNieTumApvNgo7YSB+wi+jZTN+kDBU=
|
github.com/dweymouth/fyne-tooltip v0.3.0/go.mod h1:m04ShLW/Tp6LXrNieTumApvNgo7YSB+wi+jZTN+kDBU=
|
||||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250406173843-f5cb002423a5 h1:/gxlRSmndtF8QVxV3oFjlozm2clkMlLBlfiKFtehH+M=
|
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250406173843-f5cb002423a5 h1:/gxlRSmndtF8QVxV3oFjlozm2clkMlLBlfiKFtehH+M=
|
||||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250406173843-f5cb002423a5/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
|
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250406173843-f5cb002423a5/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs=
|
github.com/dweymouth/go-jellyfin v0.0.0-20250531151636-29591764f0a0 h1:9t1CR83uzn5Va4Nycwncmuw/NEAN64O4lf82VBPzdfE=
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI=
|
github.com/dweymouth/go-jellyfin v0.0.0-20250531151636-29591764f0a0/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
|
|||||||
@@ -229,6 +229,7 @@
|
|||||||
"Track number": "Track number",
|
"Track number": "Track number",
|
||||||
"Track peak": "Track peak",
|
"Track peak": "Track peak",
|
||||||
"tracks": "tracks",
|
"tracks": "tracks",
|
||||||
|
"Transcode to": "Transcode to",
|
||||||
"UI Scaling": "UI Scaling",
|
"UI Scaling": "UI Scaling",
|
||||||
"Unable to play artist radio": "Unable to play artist radio",
|
"Unable to play artist radio": "Unable to play artist radio",
|
||||||
"Unable to play random tracks": "Unable to play random tracks",
|
"Unable to play random tracks": "Unable to play random tracks",
|
||||||
|
|||||||
@@ -302,7 +302,35 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer bool) *container.TabItem {
|
func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer bool) *container.TabItem {
|
||||||
disableTranscode := widget.NewCheckWithData(lang.L("Disable server transcoding"), binding.BindBool(&s.config.Transcoding.ForceRawFile))
|
transcodeCodec := widget.NewSelectWithData([]string{"opus", "mp3"}, binding.BindString(&s.config.Transcoding.Codec))
|
||||||
|
transcodeBitRate := widget.NewSelectWithData([]string{"96", "128", "160", "192", "256", "320"},
|
||||||
|
binding.IntToString(binding.BindInt(&s.config.Transcoding.MaxBitRateKBPS)))
|
||||||
|
if !s.config.Transcoding.RequestTranscode {
|
||||||
|
transcodeCodec.Disable()
|
||||||
|
transcodeBitRate.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
var transcode *widget.Check
|
||||||
|
disableTranscode := widget.NewCheck(lang.L("Disable server transcoding"), func(b bool) {
|
||||||
|
s.config.Transcoding.ForceRawFile = b
|
||||||
|
if b {
|
||||||
|
transcode.SetChecked(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
disableTranscode.Checked = s.config.Transcoding.ForceRawFile
|
||||||
|
transcode = widget.NewCheck(lang.L("Transcode to"), func(b bool) {
|
||||||
|
s.config.Transcoding.RequestTranscode = b
|
||||||
|
if b {
|
||||||
|
disableTranscode.SetChecked(false)
|
||||||
|
transcodeCodec.Enable()
|
||||||
|
transcodeBitRate.Enable()
|
||||||
|
} else {
|
||||||
|
transcodeCodec.Disable()
|
||||||
|
transcodeBitRate.Disable()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
transcode.Checked = s.config.Transcoding.RequestTranscode
|
||||||
|
|
||||||
deviceList := make([]string, len(s.audioDevices))
|
deviceList := make([]string, len(s.audioDevices))
|
||||||
var selIndex int
|
var selIndex int
|
||||||
for i, dev := range s.audioDevices {
|
for i, dev := range s.audioDevices {
|
||||||
@@ -392,14 +420,16 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
return container.NewTabItem(lang.L("Playback"), container.NewVBox(
|
return container.NewTabItem(lang.L("Playback"), container.NewVBox(
|
||||||
disableTranscode,
|
|
||||||
container.New(&layout.CustomPaddedLayout{TopPadding: 5},
|
container.New(&layout.CustomPaddedLayout{TopPadding: 5},
|
||||||
container.New(layout.NewFormLayout(),
|
container.New(layout.NewFormLayout(),
|
||||||
widget.NewLabel(lang.L("Audio device")), container.NewBorder(nil, nil, nil, util.NewHSpace(70), deviceSelect),
|
widget.NewLabel(lang.L("Audio device")), container.NewBorder(nil, nil, nil, util.NewHSpace(70), deviceSelect),
|
||||||
layout.NewSpacer(), audioExclusive,
|
layout.NewSpacer(), audioExclusive,
|
||||||
)),
|
)),
|
||||||
s.newSectionSeparator(),
|
s.newSectionSeparator(),
|
||||||
|
disableTranscode,
|
||||||
|
container.NewHBox(transcode, transcodeCodec, transcodeBitRate),
|
||||||
|
s.newSectionSeparator(),
|
||||||
widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}),
|
widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}),
|
||||||
container.New(layout.NewFormLayout(),
|
container.New(layout.NewFormLayout(),
|
||||||
widget.NewLabel(lang.L("ReplayGain mode")), container.NewGridWithColumns(2, replayGainSelect),
|
widget.NewLabel(lang.L("ReplayGain mode")), container.NewGridWithColumns(2, replayGainSelect),
|
||||||
|
|||||||
Reference in New Issue
Block a user