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())
|
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)
|
input, ok := c.MustGet("input").(inputs.ControlableInput)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"})
|
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := input.TogglePause()
|
err := input.TogglePause(c.MustGet("streamid").(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to pause the input: %s", err.Error())})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to pause the input: %s", err.Error())})
|
||||||
return
|
return
|
||||||
@ -85,3 +89,20 @@ func InputHandler(c *gin.Context) {
|
|||||||
|
|
||||||
c.Next()
|
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
|
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
|
// Register routes
|
||||||
ui.DeclareRoutes(router, cfg)
|
ui.DeclareRoutes(router, cfg)
|
||||||
ui.DeclareNoJSRoutes(router, cfg)
|
|
||||||
api.DeclareRoutes(router, cfg)
|
api.DeclareRoutes(router, cfg)
|
||||||
|
|
||||||
router.GET("/api/version", func(c *gin.Context) {
|
router.GET("/api/version", func(c *gin.Context) {
|
||||||
|
@ -7,9 +7,9 @@ var SoundInputs = map[string]SoundInput{}
|
|||||||
type SoundInput interface {
|
type SoundInput interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
IsActive() bool
|
IsActive() bool
|
||||||
CurrentlyPlaying() *string
|
CurrentlyPlaying() map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ControlableInput interface {
|
type ControlableInput interface {
|
||||||
TogglePause() error
|
TogglePause(string) error
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ func (i *MPRISInput) IsActive() bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *MPRISInput) CurrentlyPlaying() *string {
|
func (i *MPRISInput) CurrentlyPlaying() map[string]string {
|
||||||
p, err := i.getPlayer()
|
p, err := i.getPlayer()
|
||||||
if err != nil || p == nil {
|
if err != nil || p == nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -129,10 +129,12 @@ func (i *MPRISInput) CurrentlyPlaying() *string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret := strings.Join(infos, " - ")
|
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()
|
p, err := i.getPlayer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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/config"
|
||||||
_ "git.nemunai.re/nemunaire/hathoris/inputs/mpris"
|
_ "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/amp1_gpio"
|
||||||
_ "git.nemunai.re/nemunaire/hathoris/sources/mpv"
|
_ "git.nemunai.re/nemunaire/hathoris/sources/mpv"
|
||||||
_ "git.nemunai.re/nemunaire/hathoris/sources/spdif"
|
_ "git.nemunai.re/nemunaire/hathoris/sources/spdif"
|
||||||
|
@ -180,7 +180,7 @@ func (s *MPVSource) CurrentlyPlaying() string {
|
|||||||
return "-"
|
return "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MPVSource) TogglePause() error {
|
func (s *MPVSource) TogglePause(id string) error {
|
||||||
if s.ipcSocket == "" {
|
if s.ipcSocket == "" {
|
||||||
return fmt.Errorf("Not supported")
|
return fmt.Errorf("Not supported")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user