Extract and display radio ICY metadata (#845)
* extract and display radio ICY metadata * fix crash when casting radio station
This commit is contained in:
@@ -449,6 +449,15 @@ func (a *App) SetupWindowsSMTC(hwnd uintptr) {
|
||||
}
|
||||
}()
|
||||
})
|
||||
a.PlaybackManager.OnRadioMetadataChange(func(radioName, title, artist string) {
|
||||
if title != "" {
|
||||
smtc.UpdateMetadata(title, artist)
|
||||
smtc.UpdatePosition(0, 0)
|
||||
} else {
|
||||
smtc.UpdateMetadata(radioName, "")
|
||||
smtc.UpdatePosition(0, 0)
|
||||
}
|
||||
})
|
||||
a.PlaybackManager.OnSeek(func() {
|
||||
playbackStatus := a.PlaybackManager.PlaybackStatus()
|
||||
smtc.UpdatePosition(int(playbackStatus.TimePos*1000), int(playbackStatus.Duration*1000))
|
||||
|
||||
+21
-6
@@ -69,15 +69,28 @@ func InitMPMediaHandler(playbackManager *PlaybackManager, artURLLookup func(trac
|
||||
mpMediaEventRecipient = mp
|
||||
C.register_os_remote_commands()
|
||||
|
||||
mp.playbackManager.OnSongChange(func(track mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||
mp.playbackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||
// Asynchronously because artwork fetching can take time
|
||||
var meta *mediaprovider.MediaItemMetadata
|
||||
if track != nil {
|
||||
m := track.Metadata()
|
||||
if item != nil {
|
||||
m := item.Metadata()
|
||||
meta = &m
|
||||
}
|
||||
go mp.updateMetadata(meta)
|
||||
})
|
||||
mp.playbackManager.OnRadioMetadataChange(func(radioName, title, artist string) {
|
||||
if title != "" {
|
||||
go mp.updateMetadata(&mediaprovider.MediaItemMetadata{
|
||||
Name: title,
|
||||
Artists: []string{artist},
|
||||
Album: radioName,
|
||||
})
|
||||
} else {
|
||||
go mp.updateMetadata(&mediaprovider.MediaItemMetadata{
|
||||
Name: radioName,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
mp.playbackManager.OnStopped(func() {
|
||||
C.set_os_playback_state_stopped()
|
||||
@@ -103,16 +116,18 @@ func InitMPMediaHandler(playbackManager *PlaybackManager, artURLLookup func(trac
|
||||
func (mp *MPMediaHandler) updateMetadata(meta *mediaprovider.MediaItemMetadata) {
|
||||
var title, artist, artURL string
|
||||
var duration int
|
||||
if meta != nil && meta.ID != "" {
|
||||
if meta != nil {
|
||||
title = meta.Name
|
||||
artist = strings.Join(meta.Artists, ", ")
|
||||
duration = int(meta.Duration.Seconds())
|
||||
}
|
||||
if meta != nil && meta.ID != "" {
|
||||
var err error
|
||||
if artURL, err = mp.artURLLookup(meta.CoverArtID); err != nil {
|
||||
if meta.CoverArtID != "" {
|
||||
log.Printf("error fetching art url: %s", err.Error())
|
||||
}
|
||||
}
|
||||
artist = strings.Join(meta.Artists, ", ")
|
||||
duration = int(meta.Duration.Seconds())
|
||||
}
|
||||
|
||||
cTitle := C.CString(title)
|
||||
|
||||
+27
-3
@@ -43,6 +43,11 @@ type MPRISHandler struct {
|
||||
pm *PlaybackManager
|
||||
s *server.Server
|
||||
evt *events.EventHandler
|
||||
|
||||
// current radio metadata
|
||||
radioStationName string
|
||||
radioIcyTitle string
|
||||
radioIcyArtist string
|
||||
}
|
||||
|
||||
func NewMPRISHandler(playerName string, pm *PlaybackManager) *MPRISHandler {
|
||||
@@ -66,6 +71,14 @@ func NewMPRISHandler(playerName string, pm *PlaybackManager) *MPRISHandler {
|
||||
m.evt.Player.OnTitle()
|
||||
}
|
||||
})
|
||||
pm.OnRadioMetadataChange(func(radioName, title, artist string) {
|
||||
m.radioStationName = radioName
|
||||
m.radioIcyTitle = title
|
||||
m.radioIcyArtist = artist
|
||||
if m.connErr == nil {
|
||||
m.evt.Player.OnTitle()
|
||||
}
|
||||
})
|
||||
pm.OnVolumeChange(func(vol int) {
|
||||
if m.connErr == nil {
|
||||
m.evt.Player.OnVolume()
|
||||
@@ -276,12 +289,23 @@ func (m *MPRISHandler) Metadata() (types.Metadata, error) {
|
||||
artURL = u
|
||||
}
|
||||
}
|
||||
|
||||
title := meta.Name
|
||||
artists := meta.Artists
|
||||
album := meta.Album
|
||||
// if playing a radio station, override title/artist with current Icy metadata if present
|
||||
if m.radioStationName == title && m.radioIcyTitle != "" {
|
||||
title = m.radioIcyTitle
|
||||
artists = []string{m.radioIcyArtist}
|
||||
album = m.radioStationName
|
||||
}
|
||||
|
||||
mprisMeta := types.Metadata{
|
||||
TrackId: dbus.ObjectPath(trackObjPath),
|
||||
Length: secondsToMicroseconds(status.Duration),
|
||||
Title: meta.Name,
|
||||
Album: meta.Album,
|
||||
Artist: meta.Artists,
|
||||
Title: title,
|
||||
Album: album,
|
||||
Artist: artists,
|
||||
DiscNumber: discNumber,
|
||||
TrackNumber: trackNumber,
|
||||
UserRating: float64(userRating) / 5,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
@@ -103,6 +104,8 @@ type playbackEngine struct {
|
||||
onStopped []func()
|
||||
onPlaying []func()
|
||||
onQueueChange []func()
|
||||
|
||||
onRadioMetadataChange []func(radioName, title, artist string)
|
||||
}
|
||||
|
||||
func NewPlaybackEngine(
|
||||
@@ -698,6 +701,22 @@ func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error {
|
||||
url = filepath
|
||||
}
|
||||
}
|
||||
if mpvP, ok := p.player.(*mpv.Player); ok && !isTrack {
|
||||
mpvP.ObserveIcyRadioTitle(func(icytitle string) {
|
||||
var title, artist string
|
||||
if s := strings.Split(icytitle, " - "); len(s) == 2 {
|
||||
title, artist = s[1], s[0]
|
||||
} else {
|
||||
title = icytitle
|
||||
}
|
||||
log.Println("Radio metadata changed: ", icytitle)
|
||||
for _, cb := range p.onRadioMetadataChange {
|
||||
cb(meta.Name, title, artist)
|
||||
}
|
||||
})
|
||||
} else if ok {
|
||||
mpvP.UnobserveIcyRadioTitle()
|
||||
}
|
||||
if url == "" {
|
||||
return errors.New("no stream URL")
|
||||
}
|
||||
|
||||
@@ -318,6 +318,11 @@ func (p *PlaybackManager) OnSongChange(cb func(nowPlaying mediaprovider.MediaIte
|
||||
p.engine.onSongChange = append(p.engine.onSongChange, cb)
|
||||
}
|
||||
|
||||
// Sets a callback that is notified whenever the Icy radio metadata changes.
|
||||
func (p *PlaybackManager) OnRadioMetadataChange(cb func(radioName, title, artist string)) {
|
||||
p.engine.onRadioMetadataChange = append(p.engine.onRadioMetadataChange, cb)
|
||||
}
|
||||
|
||||
// Registers a callback that is notified whenever the play time should be updated.
|
||||
func (p *PlaybackManager) OnPlayTimeUpdate(cb func(curTime float64, totalTime float64, seeked bool)) {
|
||||
p.engine.onPlayTimeUpdate = append(p.engine.onPlayTimeUpdate, cb)
|
||||
|
||||
@@ -72,6 +72,8 @@ type Player struct {
|
||||
peaksEnabled bool
|
||||
pauseFade bool
|
||||
|
||||
icyTitleCb func(string)
|
||||
|
||||
fileLoadedLock sync.Mutex
|
||||
fileLoadedSig *sync.Cond
|
||||
|
||||
@@ -130,6 +132,8 @@ func (p *Player) Init(maxCacheMB int) error {
|
||||
m.SetOptionString("audio-client-name", p.clientName)
|
||||
}
|
||||
|
||||
m.ObserveProperty(0, "metadata", mpv.FORMAT_NODE)
|
||||
|
||||
if err := m.Initialize(); err != nil {
|
||||
return fmt.Errorf("error initializing mpv: %s", err.Error())
|
||||
}
|
||||
@@ -442,6 +446,16 @@ func (p *Player) GetMediaInfo() (MediaInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (p *Player) ObserveIcyRadioTitle(cb func(string)) {
|
||||
p.icyTitleCb = cb
|
||||
p.mpv.ObserveProperty(1, "metadata/icy-title", mpv.FORMAT_STRING)
|
||||
}
|
||||
|
||||
func (p *Player) UnobserveIcyRadioTitle() {
|
||||
p.icyTitleCb = nil
|
||||
p.mpv.UnobserveProperty(1)
|
||||
}
|
||||
|
||||
func (p *Player) getInt64Property(propName string) (int64, error) {
|
||||
playpos, err := p.mpv.GetProperty(propName, mpv.FORMAT_INT64)
|
||||
if err != nil {
|
||||
@@ -548,6 +562,11 @@ func (p *Player) eventHandler(ctx context.Context) {
|
||||
p.status.Duration = 0
|
||||
p.status.TimePos = 0
|
||||
p.setState(player.Stopped)
|
||||
case mpv.EVENT_PROPERTY_CHANGE:
|
||||
if e.Reply_Userdata == 1 && p.icyTitleCb != nil {
|
||||
p.icyTitleCb(p.mpv.GetPropertyString("metadata/icy-title"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr
|
||||
bp.ExtendBaseWidget(bp)
|
||||
|
||||
pm.OnSongChange(bp.onSongChange)
|
||||
pm.OnRadioMetadataChange(bp.onRadioMetadataChange)
|
||||
pm.OnWaveformImgUpdate(bp.updateWaveformImg)
|
||||
pm.OnPlayTimeUpdate(func(cur, total float64, _ bool) {
|
||||
fyne.Do(func() {
|
||||
@@ -143,6 +144,16 @@ func (bp *BottomPanel) onSongChange(song mediaprovider.MediaItem, _ *mediaprovid
|
||||
})
|
||||
}
|
||||
|
||||
func (bp *BottomPanel) onRadioMetadataChange(radioName, title, artist string) {
|
||||
fyne.Do(func() {
|
||||
bp.NowPlaying.Update(&mediaprovider.Track{
|
||||
Title: title,
|
||||
ArtistNames: []string{artist},
|
||||
Album: radioName,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (bp *BottomPanel) updateWaveformImg(img *backend.WaveformImage) {
|
||||
fyne.Do(func() {
|
||||
bp.Controls.UpdateWaveformImg(img)
|
||||
|
||||
@@ -109,6 +109,15 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
|
||||
app.PlaybackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||
fyne.Do(func() { m.UpdateOnTrackChange(item) })
|
||||
})
|
||||
app.PlaybackManager.OnRadioMetadataChange(func(radioName, title, artist string) {
|
||||
fyne.Do(func() {
|
||||
if title != "" {
|
||||
m.Window.SetTitle(fmt.Sprintf("%s – %s · %s", title, artist, res.DisplayName))
|
||||
} else {
|
||||
m.Window.SetTitle(fmt.Sprintf("%s · %s", radioName, res.DisplayName))
|
||||
}
|
||||
})
|
||||
})
|
||||
app.PlaybackManager.OnQueueChange(func() {
|
||||
fyne.Do(func() { m.Sidebar.SetQueueTracks(app.PlaybackManager.GetPlayQueue()) })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user