diff --git a/api/inputs.go b/api/inputs.go index 68f0f76..554a7f7 100644 --- a/api/inputs.go +++ b/api/inputs.go @@ -57,14 +57,18 @@ func declareInputsRoutes(cfg *config.Config, router *gin.RouterGroup) { c.JSON(http.StatusOK, src.CurrentlyPlaying()) }) - inputsRoutes.POST("/pause", func(c *gin.Context) { + + streamRoutes := inputsRoutes.Group("/stream/:stream") + streamRoutes.Use(StreamHandler) + + streamRoutes.POST("/pause", func(c *gin.Context) { input, ok := c.MustGet("input").(inputs.ControlableInput) if !ok { c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"}) return } - err := input.TogglePause() + err := input.TogglePause(c.MustGet("streamid").(string)) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to pause the input: %s", err.Error())}) return @@ -85,3 +89,20 @@ func InputHandler(c *gin.Context) { c.Next() } + +func StreamHandler(c *gin.Context) { + input := c.MustGet("input").(inputs.SoundInput) + + streams := input.CurrentlyPlaying() + + stream, ok := streams[c.Param("stream")] + if !ok { + c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("Stream not found: %s", c.Param("stream"))}) + return + } + + c.Set("streamid", c.Param("stream")) + c.Set("stream", stream) + + c.Next() +} diff --git a/api/sources.go b/api/sources.go index 9907bbe..0a28297 100644 --- a/api/sources.go +++ b/api/sources.go @@ -122,7 +122,7 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) { return } - c.JSON(http.StatusOK, s.TogglePause()) + c.JSON(http.StatusOK, s.TogglePause("default")) }) } diff --git a/app.go b/app.go index 7930d6e..b78923a 100644 --- a/app.go +++ b/app.go @@ -38,7 +38,6 @@ func NewApp(cfg *config.Config) *App { // Register routes ui.DeclareRoutes(router, cfg) - ui.DeclareNoJSRoutes(router, cfg) api.DeclareRoutes(router, cfg) router.GET("/api/version", func(c *gin.Context) { diff --git a/inputs/interfaces.go b/inputs/interfaces.go index 6b86973..13e57c6 100644 --- a/inputs/interfaces.go +++ b/inputs/interfaces.go @@ -7,9 +7,9 @@ var SoundInputs = map[string]SoundInput{} type SoundInput interface { GetName() string IsActive() bool - CurrentlyPlaying() *string + CurrentlyPlaying() map[string]string } type ControlableInput interface { - TogglePause() error + TogglePause(string) error } diff --git a/inputs/mpris/input.go b/inputs/mpris/input.go index cfbec1a..4fe65f2 100644 --- a/inputs/mpris/input.go +++ b/inputs/mpris/input.go @@ -103,7 +103,7 @@ func (i *MPRISInput) IsActive() bool { return err == nil } -func (i *MPRISInput) CurrentlyPlaying() *string { +func (i *MPRISInput) CurrentlyPlaying() map[string]string { p, err := i.getPlayer() if err != nil || p == nil { log.Println(err) @@ -129,10 +129,12 @@ func (i *MPRISInput) CurrentlyPlaying() *string { } ret := strings.Join(infos, " - ") - return &ret + return map[string]string{ + "default": ret, + } } -func (i *MPRISInput) TogglePause() error { +func (i *MPRISInput) TogglePause(id string) error { p, err := i.getPlayer() if err != nil { return err diff --git a/inputs/pulseaudio/input.go b/inputs/pulseaudio/input.go new file mode 100644 index 0000000..aad6926 --- /dev/null +++ b/inputs/pulseaudio/input.go @@ -0,0 +1,112 @@ +package pa + +import ( + "encoding/json" + "fmt" + "log" + "os/exec" + "strconv" + + "git.nemunai.re/nemunaire/hathoris/inputs" +) + +type PulseaudioInput struct { +} + +type PASink struct { + Index int `json:"index"` + Name string `json:"name"` + Driver string `json:"driver"` + SampleSpecification string `json:"sample_specifications"` + State string `json:"state"` +} + +type PAVolume struct { + Value uint `json:"value"` + ValuePercent string `json:"value_percent"` + DB string `json:"db"` +} + +type PASinkInput struct { + Index int64 `json:"index"` + Driver string `json:"driver"` + OwnerModule string `json:"owner_module"` + Client string `json:"client"` + Sink int `json:"sink"` + SampleSpecification string `json:"sample_specifications"` + ChannelMap string `json:"channel_map"` + Format string `json:"format"` + Corked bool `json:"corked"` + Mute bool `json:"mute"` + Volume map[string]PAVolume `json:"volume"` + Balance float64 `json:"balance"` + BufferLatencyUsec float64 `json:"buffer_latency_usec"` + SinkLatencyUsec float64 `json:"sink_latency_usec"` + ResampleMethod string `json:"resample_method"` + Properties map[string]string `json:"properties"` +} + +func init() { + cmd := exec.Command("pactl", "-f", "json", "list", "sinks", "short") + err := cmd.Run() + if err == nil { + inputs.SoundInputs["pulseaudio"] = &PulseaudioInput{} + } else { + log.Println("Unable to access pulseaudio:", err.Error()) + } +} + +func (s *PulseaudioInput) GetName() string { + return "pulseaudio" +} + +func (s *PulseaudioInput) IsActive() bool { + cmd := exec.Command("pactl", "-f", "json", "list", "sinks", "short") + stdoutStderr, err := cmd.CombinedOutput() + if err != nil { + return false + } + + var sinks []PASink + err = json.Unmarshal(stdoutStderr, &sinks) + if err != nil { + return false + } + + for _, sink := range sinks { + if sink.State != "SUSPENDED" { + return true + } + } + + return false +} + +func (s *PulseaudioInput) CurrentlyPlaying() map[string]string { + cmd := exec.Command("pactl", "-f", "json", "list", "sink-inputs") + stdoutStderr, err := cmd.CombinedOutput() + if err != nil { + log.Println("Unable to list sink-inputs:", err.Error()) + return nil + } + + var sinkinputs []PASinkInput + err = json.Unmarshal(stdoutStderr, &sinkinputs) + if err != nil { + log.Println("Unable to list sink-inputs:", err.Error()) + return nil + } + + ret := map[string]string{} + for _, input := range sinkinputs { + if v, ok := input.Properties["media.name"]; ok { + ret[strconv.FormatInt(input.Index, 10)] = v + } else if v, ok := input.Properties["device.description"]; ok { + ret[strconv.FormatInt(input.Index, 10)] = v + } else { + ret[strconv.FormatInt(input.Index, 10)] = fmt.Sprintf("#%d", input.Index) + } + } + + return ret +} diff --git a/main.go b/main.go index bbdcd23..6244a75 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "git.nemunai.re/nemunaire/hathoris/config" _ "git.nemunai.re/nemunaire/hathoris/inputs/mpris" + _ "git.nemunai.re/nemunaire/hathoris/inputs/pulseaudio" _ "git.nemunai.re/nemunaire/hathoris/sources/amp1_gpio" _ "git.nemunai.re/nemunaire/hathoris/sources/mpv" _ "git.nemunai.re/nemunaire/hathoris/sources/spdif" diff --git a/sources/mpv/source.go b/sources/mpv/source.go index 8392440..44cf6e7 100644 --- a/sources/mpv/source.go +++ b/sources/mpv/source.go @@ -180,7 +180,7 @@ func (s *MPVSource) CurrentlyPlaying() string { return "-" } -func (s *MPVSource) TogglePause() error { +func (s *MPVSource) TogglePause(id string) error { if s.ipcSocket == "" { return fmt.Errorf("Not supported") }