Support Pulseaudio sink-inputs
This commit is contained in:
parent
a9c6cdcd0f
commit
bbde2299fe
@ -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()
|
||||
}
|
||||
|
@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
|
1
app.go
1
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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
112
inputs/pulseaudio/input.go
Normal file
112
inputs/pulseaudio/input.go
Normal file
@ -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
|
||||
}
|
1
main.go
1
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"
|
||||
|
@ -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")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user