Improving backend
This commit is contained in:
parent
e2013351d1
commit
c67b43ab3c
@ -21,9 +21,12 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
ret := map[string]*SourceState{}
|
ret := map[string]*SourceState{}
|
||||||
|
|
||||||
for k, src := range sources.SoundSources {
|
for k, src := range sources.SoundSources {
|
||||||
|
active := src.IsActive()
|
||||||
|
|
||||||
ret[k] = &SourceState{
|
ret[k] = &SourceState{
|
||||||
Name: src.GetName(),
|
Name: src.GetName(),
|
||||||
Enabled: src.IsEnabled(),
|
Enabled: src.IsEnabled(),
|
||||||
|
Active: &active,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +50,22 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
sourcesRoutes.GET("/settings", func(c *gin.Context) {
|
sourcesRoutes.GET("/settings", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, c.MustGet("source"))
|
c.JSON(http.StatusOK, c.MustGet("source"))
|
||||||
})
|
})
|
||||||
|
sourcesRoutes.GET("/currently", func(c *gin.Context) {
|
||||||
|
src := c.MustGet("source").(sources.SoundSource)
|
||||||
|
|
||||||
|
if !src.IsActive() {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotAcceptable, gin.H{"errmsg": "Source not active"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := src.(sources.PlayingSource)
|
||||||
|
if !ok {
|
||||||
|
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, s.CurrentlyPlaying())
|
||||||
|
})
|
||||||
sourcesRoutes.POST("/enable", func(c *gin.Context) {
|
sourcesRoutes.POST("/enable", func(c *gin.Context) {
|
||||||
src := c.MustGet("source").(sources.SoundSource)
|
src := c.MustGet("source").(sources.SoundSource)
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -13,6 +15,12 @@ import (
|
|||||||
"git.nemunai.re/nemunaire/hathoris/config"
|
"git.nemunai.re/nemunaire/hathoris/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var cardId string = "0"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&cardId, "card-id", cardId, "ALSA card identifier for volume handling")
|
||||||
|
}
|
||||||
|
|
||||||
func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
||||||
router.GET("/mixer", func(c *gin.Context) {
|
router.GET("/mixer", func(c *gin.Context) {
|
||||||
cnt, err := parseAmixerContent()
|
cnt, err := parseAmixerContent()
|
||||||
@ -21,7 +29,13 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, cnt)
|
var ret []*CardControlState
|
||||||
|
|
||||||
|
for _, cc := range cnt {
|
||||||
|
ret = append(ret, cc.ToCardControlState())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ret)
|
||||||
})
|
})
|
||||||
|
|
||||||
mixerRoutes := router.Group("/mixer/:mixer")
|
mixerRoutes := router.Group("/mixer/:mixer")
|
||||||
@ -45,10 +59,12 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
|
|
||||||
var values []string
|
var values []string
|
||||||
for _, v := range valuesINT {
|
for _, v := range valuesINT {
|
||||||
if t, ok := v.(int64); ok {
|
if t, ok := v.(float64); ok {
|
||||||
values = append(values, strconv.FormatInt(t, 10))
|
if float64(int64(t)) == t {
|
||||||
} else if t, ok := v.(float64); ok {
|
values = append(values, strconv.FormatInt(int64(t), 10))
|
||||||
values = append(values, fmt.Sprintf("%f", t))
|
} else {
|
||||||
|
values = append(values, fmt.Sprintf("%f", t))
|
||||||
|
}
|
||||||
} else if t, ok := v.(bool); ok {
|
} else if t, ok := v.(bool); ok {
|
||||||
if t {
|
if t {
|
||||||
values = append(values, "on")
|
values = append(values, "on")
|
||||||
@ -110,7 +126,7 @@ func (cc *CardControl) parseAmixerField(key, value string) (err error) {
|
|||||||
case "iface":
|
case "iface":
|
||||||
cc.Interface = value
|
cc.Interface = value
|
||||||
case "name":
|
case "name":
|
||||||
cc.Name = value
|
cc.Name = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'")
|
||||||
case "type":
|
case "type":
|
||||||
cc.Type = value
|
cc.Type = value
|
||||||
case "access":
|
case "access":
|
||||||
@ -133,12 +149,17 @@ func (cc *CardControl) ToCardControlState() *CardControlState {
|
|||||||
NumID: cc.NumID,
|
NumID: cc.NumID,
|
||||||
Type: cc.Type,
|
Type: cc.Type,
|
||||||
Name: cc.Name,
|
Name: cc.Name,
|
||||||
|
RW: strings.HasPrefix(cc.Access, "rw"),
|
||||||
Items: cc.Items,
|
Items: cc.Items,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cc.DBScale.Min != 0 || cc.DBScale.Step != 0 {
|
||||||
|
ccs.DBScale = &cc.DBScale
|
||||||
|
}
|
||||||
|
|
||||||
// Convert values
|
// Convert values
|
||||||
for _, v := range cc.Values {
|
for _, v := range cc.Values {
|
||||||
if cc.Type == "INTEGER" {
|
if cc.Type == "INTEGER" || cc.Type == "ENUMERATED" {
|
||||||
if tmp, err := strconv.ParseFloat(v, 10); err == nil {
|
if tmp, err := strconv.ParseFloat(v, 10); err == nil {
|
||||||
ccs.Current = append(ccs.Current, tmp)
|
ccs.Current = append(ccs.Current, tmp)
|
||||||
}
|
}
|
||||||
@ -151,25 +172,8 @@ func (cc *CardControl) ToCardControlState() *CardControlState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cc.DBScale.Min != 0 {
|
ccs.Min = cc.Min
|
||||||
ccs.Min = cc.DBScale.Min
|
ccs.Max = cc.Max
|
||||||
ccs.Unit = "dB"
|
|
||||||
} else if cc.Min != 0 {
|
|
||||||
ccs.Min = float64(cc.Min)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cc.DBScale.Step != 0 {
|
|
||||||
ccs.Step = cc.DBScale.Step
|
|
||||||
ccs.Unit = "dB"
|
|
||||||
} else if cc.Step != 0 {
|
|
||||||
ccs.Step = float64(cc.Step)
|
|
||||||
} else {
|
|
||||||
ccs.Step = 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
if cc.Max != 0 {
|
|
||||||
ccs.Max = ccs.Min + ccs.Step*float64(cc.Max-cc.Min)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ccs
|
return ccs
|
||||||
}
|
}
|
||||||
@ -177,7 +181,7 @@ func (cc *CardControl) ToCardControlState() *CardControlState {
|
|||||||
type CardControldBScale struct {
|
type CardControldBScale struct {
|
||||||
Min float64
|
Min float64
|
||||||
Step float64
|
Step float64
|
||||||
Mute int64
|
Mute int64 `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *CardControldBScale) parseAmixerField(key, value string) (err error) {
|
func (cc *CardControldBScale) parseAmixerField(key, value string) (err error) {
|
||||||
@ -197,16 +201,16 @@ type CardControlState struct {
|
|||||||
NumID int64
|
NumID int64
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
Min float64
|
RW bool `json:"RW,omitempty"`
|
||||||
Max float64
|
Min int64
|
||||||
Step float64
|
Max int64
|
||||||
Unit string `json:"unit,omitempty"`
|
DBScale *CardControldBScale `json:",omitempty"`
|
||||||
Current []interface{} `json:"values,omitempty"`
|
Current []interface{} `json:"values,omitempty"`
|
||||||
Items []string `json:"items,omitempty"`
|
Items []string `json:"items,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAmixerContent() ([]*CardControl, error) {
|
func parseAmixerContent() ([]*CardControl, error) {
|
||||||
cmd := exec.Command("amixer", "-c1", "contents")
|
cmd := exec.Command("amixer", "-c", cardId, "-M", "contents")
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -274,11 +278,14 @@ func parseAmixerContent() ([]*CardControl, error) {
|
|||||||
|
|
||||||
func (cc *CardControl) CsetAmixer(values ...string) error {
|
func (cc *CardControl) CsetAmixer(values ...string) error {
|
||||||
opts := []string{
|
opts := []string{
|
||||||
"-c1",
|
"-c",
|
||||||
|
cardId,
|
||||||
|
"-M",
|
||||||
"cset",
|
"cset",
|
||||||
fmt.Sprintf("numid=%d", cc.NumID),
|
fmt.Sprintf("numid=%d", cc.NumID),
|
||||||
}
|
}
|
||||||
opts = append(opts, values...)
|
opts = append(opts, strings.Join(values, ","))
|
||||||
|
log.Println(opts)
|
||||||
cmd := exec.Command("amixer", opts...)
|
cmd := exec.Command("amixer", opts...)
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
|
6
go.mod
6
go.mod
@ -2,7 +2,10 @@ module git.nemunai.re/nemunaire/hathoris
|
|||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require github.com/gin-gonic/gin v1.9.1
|
require (
|
||||||
|
github.com/DexterLB/mpvipc v0.0.0-20230829142118-145d6eabdc37
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
@ -28,5 +31,6 @@ require (
|
|||||||
golang.org/x/sys v0.8.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
|
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
4
go.sum
4
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
github.com/DexterLB/mpvipc v0.0.0-20230829142118-145d6eabdc37 h1:/oQBAuySCcme0DLhicWkr7FaAT5nh1XbbbnCMR2WdPA=
|
||||||
|
github.com/DexterLB/mpvipc v0.0.0-20230829142118-145d6eabdc37/go.mod h1:nMVB54ifXmC1hpgfq7gTpotbv891pd2wAX/whuUj1q4=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
@ -80,6 +82,8 @@ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cn
|
|||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
|
||||||
|
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
1
main.go
1
main.go
@ -9,6 +9,7 @@ import (
|
|||||||
"git.nemunai.re/nemunaire/hathoris/config"
|
"git.nemunai.re/nemunaire/hathoris/config"
|
||||||
_ "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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -2,16 +2,19 @@ package amp1gpio
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.nemunai.re/nemunaire/hathoris/sources"
|
"git.nemunai.re/nemunaire/hathoris/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AMP1GPIOSource struct {
|
type AMP1GPIOSource struct {
|
||||||
Path string
|
process *exec.Cmd
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
const GPIODirectory = "/sys/class/gpio/gpio46/"
|
const GPIODirectory = "/sys/class/gpio/gpio46/"
|
||||||
@ -39,7 +42,7 @@ func (s *AMP1GPIOSource) read() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AMP1GPIOSource) IsActive() bool {
|
func (s *AMP1GPIOSource) IsActive() bool {
|
||||||
return s.IsEnabled()
|
return s.process != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AMP1GPIOSource) IsEnabled() bool {
|
func (s *AMP1GPIOSource) IsEnabled() bool {
|
||||||
@ -49,7 +52,7 @@ func (s *AMP1GPIOSource) IsEnabled() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes.Compare(b, []byte{'1'}) == 0
|
return bytes.Compare(b, []byte{'1', '\n'}) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AMP1GPIOSource) write(value string) error {
|
func (s *AMP1GPIOSource) write(value string) error {
|
||||||
@ -65,9 +68,33 @@ func (s *AMP1GPIOSource) write(value string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AMP1GPIOSource) Enable() error {
|
func (s *AMP1GPIOSource) Enable() error {
|
||||||
|
if s.process != nil {
|
||||||
|
return fmt.Errorf("Already running")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.process = exec.Command("aplay", "-f", "cd", "/dev/zero")
|
||||||
|
if err := s.process.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := s.process.Wait()
|
||||||
|
if err != nil {
|
||||||
|
s.process.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.process = nil
|
||||||
|
}()
|
||||||
|
|
||||||
return s.write("1")
|
return s.write("1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AMP1GPIOSource) Disable() error {
|
func (s *AMP1GPIOSource) Disable() error {
|
||||||
|
if s.process != nil {
|
||||||
|
if s.process.Process != nil {
|
||||||
|
s.process.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return s.write("0")
|
return s.write("0")
|
||||||
}
|
}
|
||||||
|
@ -11,3 +11,7 @@ type SoundSource interface {
|
|||||||
Enable() error
|
Enable() error
|
||||||
Disable() error
|
Disable() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlayingSource interface {
|
||||||
|
CurrentlyPlaying() string
|
||||||
|
}
|
||||||
|
@ -2,26 +2,41 @@ package mpv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/DexterLB/mpvipc"
|
||||||
|
|
||||||
"git.nemunai.re/nemunaire/hathoris/sources"
|
"git.nemunai.re/nemunaire/hathoris/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MPVSource struct {
|
type MPVSource struct {
|
||||||
process *exec.Cmd
|
process *exec.Cmd
|
||||||
Options []string
|
ipcSocket string
|
||||||
File string
|
Name string
|
||||||
|
Options []string
|
||||||
|
File string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sources.SoundSources["mpv"] = &MPVSource{
|
sources.SoundSources["mpv-1"] = &MPVSource{
|
||||||
Options: []string{"--no-video"},
|
Name: "Radio 1",
|
||||||
File: "https://mediaserv38.live-streams.nl:18030/stream",
|
ipcSocket: "/tmp/tmpmpv.radio-1",
|
||||||
|
Options: []string{"--no-video", "--no-terminal"},
|
||||||
|
File: "https://mediaserv38.live-streams.nl:18030/stream",
|
||||||
|
}
|
||||||
|
sources.SoundSources["mpv-2"] = &MPVSource{
|
||||||
|
Name: "Radio 2",
|
||||||
|
ipcSocket: "/tmp/tmpmpv.radio-2",
|
||||||
|
Options: []string{"--no-video", "--no-terminal"},
|
||||||
|
File: "https://mediaserv38.live-streams.nl:18040/live",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MPVSource) GetName() string {
|
func (s *MPVSource) GetName() string {
|
||||||
return "Radio 1"
|
return s.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MPVSource) IsActive() bool {
|
func (s *MPVSource) IsActive() bool {
|
||||||
@ -39,6 +54,9 @@ func (s *MPVSource) Enable() (err error) {
|
|||||||
|
|
||||||
var opts []string
|
var opts []string
|
||||||
opts = append(opts, s.Options...)
|
opts = append(opts, s.Options...)
|
||||||
|
if s.ipcSocket != "" {
|
||||||
|
opts = append(opts, "--input-ipc-server="+s.ipcSocket, "--pause")
|
||||||
|
}
|
||||||
opts = append(opts, s.File)
|
opts = append(opts, s.File)
|
||||||
|
|
||||||
s.process = exec.Command("mpv", opts...)
|
s.process = exec.Command("mpv", opts...)
|
||||||
@ -55,6 +73,39 @@ func (s *MPVSource) Enable() (err error) {
|
|||||||
s.process = nil
|
s.process = nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if s.ipcSocket != "" {
|
||||||
|
_, err = os.Stat(s.ipcSocket)
|
||||||
|
for i := 20; i >= 0 && err != nil; i-- {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
_, err = os.Stat(s.ipcSocket)
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
conn := mpvipc.NewConnection(s.ipcSocket)
|
||||||
|
err = conn.Open()
|
||||||
|
for i := 20; i >= 0 && err != nil; i-- {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
err = conn.Open()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, err = conn.Get("media-title")
|
||||||
|
for err != nil {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
_, err = conn.Get("media-title")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Set("ao-volume", 50)
|
||||||
|
|
||||||
|
err = conn.Set("pause", false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,3 +118,24 @@ func (s *MPVSource) Disable() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MPVSource) CurrentlyPlaying() string {
|
||||||
|
if s.ipcSocket != "" {
|
||||||
|
conn := mpvipc.NewConnection(s.ipcSocket)
|
||||||
|
err := conn.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to open mpv socket:", err.Error())
|
||||||
|
return "!"
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
title, err := conn.Get("media-title")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to retrieve title:", err.Error())
|
||||||
|
return "!"
|
||||||
|
}
|
||||||
|
return title.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
116
sources/spdif/source.go
Normal file
116
sources/spdif/source.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package spdif
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.nemunai.re/nemunaire/hathoris/sources"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SPDIFSource struct {
|
||||||
|
processRec *exec.Cmd
|
||||||
|
processPlay *exec.Cmd
|
||||||
|
DeviceIn string
|
||||||
|
DeviceOut string
|
||||||
|
Bitrate int64
|
||||||
|
Channels int64
|
||||||
|
Format string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if dirs, err := os.ReadDir("/sys/class/sound"); err == nil {
|
||||||
|
for _, dir := range dirs {
|
||||||
|
thisdir := path.Join("/sys/class/sound", dir.Name())
|
||||||
|
if s, err := os.Stat(thisdir); err == nil && s.IsDir() {
|
||||||
|
idfile := path.Join(thisdir, "id")
|
||||||
|
if fd, err := os.Open(idfile); err == nil {
|
||||||
|
if cnt, err := io.ReadAll(fd); err == nil && string(cnt) == "imxspdif\n" {
|
||||||
|
sources.SoundSources["imxspdif"] = &SPDIFSource{
|
||||||
|
DeviceIn: "imxspdif",
|
||||||
|
DeviceOut: "is31ap2121",
|
||||||
|
Bitrate: 48000,
|
||||||
|
Channels: 2,
|
||||||
|
Format: "S24_LE",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPDIFSource) GetName() string {
|
||||||
|
return "S/PDIF"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPDIFSource) IsActive() bool {
|
||||||
|
return s.processRec != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPDIFSource) IsEnabled() bool {
|
||||||
|
return s.processRec != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPDIFSource) Enable() error {
|
||||||
|
if s.processRec != nil {
|
||||||
|
return fmt.Errorf("Already running")
|
||||||
|
}
|
||||||
|
if s.processPlay != nil {
|
||||||
|
s.processPlay.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeR, pipeW, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.processPlay = exec.Command("aplay", "-c", fmt.Sprintf("%d", s.Channels), "-D", "hw:"+s.DeviceOut, "--period-size=512", "-B0", "--buffer-size=512")
|
||||||
|
s.processPlay.Stdin = pipeR
|
||||||
|
if err := s.processPlay.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := s.processPlay.Wait()
|
||||||
|
if err != nil {
|
||||||
|
s.processPlay.Process.Kill()
|
||||||
|
pipeR.Close()
|
||||||
|
pipeW.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.processPlay = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.processRec = exec.Command("arecord", "-t", "wav", "-f", s.Format, fmt.Sprintf("-r%d", s.Bitrate), fmt.Sprintf("-c%d", s.Channels), "-D", "hw:"+s.DeviceIn, "-B0", "--buffer-size=512")
|
||||||
|
s.processRec.Stdout = pipeW
|
||||||
|
if err := s.processRec.Start(); err != nil {
|
||||||
|
s.processPlay.Process.Kill()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := s.processRec.Wait()
|
||||||
|
if err != nil {
|
||||||
|
s.processRec.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.processRec = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPDIFSource) Disable() error {
|
||||||
|
if s.processRec != nil && s.processRec.Process != nil {
|
||||||
|
s.processRec.Process.Kill()
|
||||||
|
}
|
||||||
|
if s.processPlay != nil && s.processPlay.Process != nil {
|
||||||
|
s.processPlay.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user