fix(mediacontrols): answer "no artwork" instead of saying nothing #80
@@ -286,12 +286,36 @@ func (h *MPRISHandler) UpdateMetadata(meta Metadata) {
|
|||||||
tid := h.trackID
|
tid := h.trackID
|
||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
|
|
||||||
|
m := metadataMap(meta, tid)
|
||||||
|
|
||||||
|
h.enqueue(func() {
|
||||||
|
h.props.SetMust(playerIf, "Metadata", m)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// metadataMap is the org.mpris.MediaPlayer2.Player Metadata dictionary
|
||||||
|
// for one track.
|
||||||
|
//
|
||||||
|
// Absent fields are omitted, with one deliberate exception: a track
|
||||||
|
// with **no cover art still publishes mpris:artUrl**, empty. Omitting
|
||||||
|
// it is what a missing value naturally reads as, and it is wrong here
|
||||||
|
// -- KDE's media controller keeps showing the last artwork it was
|
||||||
|
// given, so playing an untagged track under a Plasma taskbar preview
|
||||||
|
// displayed the cover of whichever earlier album happened to have one.
|
||||||
|
// An empty string is our answer to "what is the artwork" rather than
|
||||||
|
// no answer at all.
|
||||||
|
//
|
||||||
|
// A generated placeholder image would be the stronger fix and needs an
|
||||||
|
// app icon to point at; this is the version that costs nothing and can
|
||||||
|
// be checked with busctl.
|
||||||
|
func metadataMap(meta Metadata, trackID uint64) map[string]interface{} {
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
"mpris:trackid": dbus.ObjectPath(
|
"mpris:trackid": dbus.ObjectPath(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"/org/yellowjacket/Track/%d", tid,
|
"/org/yellowjacket/Track/%d", trackID,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
"mpris:artUrl": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
if meta.Title != "" {
|
if meta.Title != "" {
|
||||||
@@ -316,9 +340,7 @@ func (h *MPRISHandler) UpdateMetadata(meta Metadata) {
|
|||||||
) * usPerSec
|
) * usPerSec
|
||||||
}
|
}
|
||||||
|
|
||||||
h.enqueue(func() {
|
return m
|
||||||
h.props.SetMust(playerIf, "Metadata", m)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlaybackState pushes the playback state and position
|
// UpdatePlaybackState pushes the playback state and position
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
|
package mediacontrols
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// TestArtURLIsAlwaysAnswered pins the one field in the MPRIS metadata
|
||||||
|
// dictionary that must be published even when it is empty.
|
||||||
|
//
|
||||||
|
// Omitting a field is what an absent value naturally reads as, and it
|
||||||
|
// is wrong for artwork: KDE's media controller keeps showing the last
|
||||||
|
// image it was handed, so a track with no cover art displayed the
|
||||||
|
// cover of whichever earlier album had one — in the taskbar preview,
|
||||||
|
// where the user has no way to tell it is stale.
|
||||||
|
func TestArtURLIsAlwaysAnswered(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
meta Metadata
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "art present",
|
||||||
|
meta: Metadata{
|
||||||
|
Title: "Tideline",
|
||||||
|
ArtFilePath: "/covers/abc_lg.jpg",
|
||||||
|
},
|
||||||
|
want: "file:///covers/abc_lg.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no art at all",
|
||||||
|
meta: Metadata{Title: "Untagged"},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
m := metadataMap(tt.meta, 1)
|
||||||
|
|
||||||
|
got, ok := m["mpris:artUrl"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("mpris:artUrl absent: a missing key " +
|
||||||
|
"leaves the previous track's art on screen")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("mpris:artUrl = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEmptyFieldsStayAbsent guards the other half: artUrl is the
|
||||||
|
// exception, not a new rule for the whole dictionary. A title of ""
|
||||||
|
// published as "" would make every player show an empty line where it
|
||||||
|
// would otherwise fall back to the filename.
|
||||||
|
func TestEmptyFieldsStayAbsent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
m := metadataMap(Metadata{}, 7)
|
||||||
|
|
||||||
|
for _, key := range []string{
|
||||||
|
"xesam:title", "xesam:artist", "xesam:album", "mpris:length",
|
||||||
|
} {
|
||||||
|
if _, ok := m[key]; ok {
|
||||||
|
t.Errorf("%s should be omitted when it has no value", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user