126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
// Copyright (c) 2024 Joshua Rich <joshua.rich@gmail.com>
|
|
//
|
|
// This software is released under the MIT License.
|
|
// https://opensource.org/licenses/MIT
|
|
|
|
package media
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass"
|
|
mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt"
|
|
|
|
"github.com/joshuar/go-hass-agent/internal/linux"
|
|
"github.com/joshuar/go-hass-agent/internal/logging"
|
|
"github.com/joshuar/go-hass-agent/internal/preferences"
|
|
"github.com/joshuar/go-hass-agent/pkg/linux/dbusx"
|
|
)
|
|
|
|
const (
|
|
mediaStopIcon = "mdi:stop"
|
|
mediaPauseIcon = "mdi:pause"
|
|
mediaPlayIcon = "mdi:play"
|
|
mediaOffIcon = "mdi:music-note-off"
|
|
|
|
mprisDBusPath = "/org/mpris/MediaPlayer2"
|
|
mprisDBusNamespace = "org.mpris.MediaPlayer2.Player"
|
|
)
|
|
|
|
type mprisMonitor struct {
|
|
logger *slog.Logger
|
|
mediaStateEntity *mqtthass.SensorEntity
|
|
msgCh chan *mqttapi.Msg
|
|
mediaState string
|
|
}
|
|
|
|
func MPRISControl(ctx context.Context, device *mqtthass.Device, msgCh chan *mqttapi.Msg) (*mqtthass.SensorEntity, error) {
|
|
bus, ok := linux.CtxGetSessionBus(ctx)
|
|
if !ok {
|
|
return nil, linux.ErrNoSessionBus
|
|
}
|
|
|
|
mprisMonitor := &mprisMonitor{
|
|
logger: logging.FromContext(ctx).With(slog.String("controller", "mpris")),
|
|
msgCh: msgCh,
|
|
}
|
|
|
|
mprisMonitor.mediaStateEntity = mqtthass.NewSensorEntity().
|
|
WithDetails(
|
|
mqtthass.App(preferences.AppName),
|
|
mqtthass.Name("Media State"),
|
|
mqtthass.ID(device.Name+"_media_state"),
|
|
mqtthass.OriginInfo(preferences.MQTTOrigin()),
|
|
mqtthass.DeviceInfo(device),
|
|
mqtthass.Icon(mediaOffIcon),
|
|
).
|
|
WithState(
|
|
mqtthass.StateCallback(mprisMonitor.mprisStateCallback),
|
|
mqtthass.ValueTemplate("{{ value_json.value }}"),
|
|
)
|
|
|
|
triggerCh, err := dbusx.NewWatch(
|
|
dbusx.MatchPath(mprisDBusPath),
|
|
dbusx.MatchPropChanged(),
|
|
dbusx.MatchArgNameSpace(mprisDBusNamespace),
|
|
).Start(ctx, bus)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not watch D-Bus for MPRIS signals: %w", err)
|
|
}
|
|
|
|
// Watch for power profile changes.
|
|
go func() {
|
|
mprisMonitor.logger.Debug("Monitoring for MPRIS signals.")
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
mprisMonitor.logger.Debug("Stopped monitoring for MPRIS signals.")
|
|
|
|
return
|
|
case event := <-triggerCh:
|
|
changed, status, err := dbusx.HasPropertyChanged[string](event.Content, "PlaybackStatus")
|
|
if err != nil {
|
|
mprisMonitor.logger.Warn("Could not parse received D-Bus signal.", slog.Any("error", err))
|
|
} else {
|
|
if changed {
|
|
mprisMonitor.publishPlaybackState(status)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return mprisMonitor.mediaStateEntity, nil
|
|
}
|
|
|
|
func (m *mprisMonitor) mprisStateCallback(_ ...any) (json.RawMessage, error) {
|
|
return json.RawMessage(`{ "value": ` + m.mediaState + ` }`), nil
|
|
}
|
|
|
|
func (m *mprisMonitor) publishPlaybackState(state string) {
|
|
m.mediaState = state
|
|
|
|
switch m.mediaState {
|
|
case "Playing":
|
|
m.mediaStateEntity.Icon = mediaPlayIcon
|
|
case "Paused":
|
|
m.mediaStateEntity.Icon = mediaPauseIcon
|
|
case "Stopped":
|
|
m.mediaStateEntity.Icon = mediaStopIcon
|
|
default:
|
|
m.mediaStateEntity.Icon = mediaOffIcon
|
|
}
|
|
|
|
msg, err := m.mediaStateEntity.MarshalState()
|
|
if err != nil {
|
|
m.logger.Warn("Could not publish MPRIS state.", slog.Any("error", err))
|
|
|
|
return
|
|
}
|
|
m.msgCh <- msg
|
|
}
|