Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
nemunaire | 85ecd89104 | |
nemunaire | 19fa419d89 | |
nemunaire | 677f93723b | |
nemunaire | f7760416b9 | |
nemunaire | 1d091be264 | |
nemunaire | a5e3452342 | |
nemunaire | e224b6a986 | |
nemunaire | 4891d5f7b7 |
|
@ -13,7 +13,7 @@ workspace:
|
|||
|
||||
steps:
|
||||
- name: build front
|
||||
image: node:20-alpine
|
||||
image: node:21
|
||||
commands:
|
||||
- mkdir deploy
|
||||
- cd ui
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:20-alpine as nodebuild
|
||||
FROM node:21 as nodebuild
|
||||
|
||||
WORKDIR /ui
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ type InputState struct {
|
|||
Name string `json:"name"`
|
||||
Active bool `json:"active"`
|
||||
Controlable bool `json:"controlable"`
|
||||
HasPlaylist bool `json:"hasplaylist"`
|
||||
Streams map[string]string `json:"streams,omitempty"`
|
||||
Mixable bool `json:"mixable"`
|
||||
Mixer map[string]*inputs.InputMixer `json:"mixer,omitempty"`
|
||||
|
@ -32,10 +33,16 @@ func declareInputsRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
mixer, _ = im.GetMixers()
|
||||
}
|
||||
|
||||
var hasPlaylist bool
|
||||
if p, withPlaylist := inp.(inputs.PlaylistInput); withPlaylist {
|
||||
hasPlaylist = p.HasPlaylist()
|
||||
}
|
||||
|
||||
ret[k] = &InputState{
|
||||
Name: inp.GetName(),
|
||||
Active: inp.IsActive(),
|
||||
Controlable: controlable,
|
||||
HasPlaylist: hasPlaylist,
|
||||
Streams: inp.CurrentlyPlaying(),
|
||||
Mixable: mixable,
|
||||
Mixer: mixer,
|
||||
|
@ -57,11 +64,16 @@ func declareInputsRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
if mixable {
|
||||
mixer, _ = im.GetMixers()
|
||||
}
|
||||
var hasPlaylist bool
|
||||
if p, withPlaylist := inp.(inputs.PlaylistInput); withPlaylist {
|
||||
hasPlaylist = p.HasPlaylist()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, &InputState{
|
||||
Name: inp.GetName(),
|
||||
Active: inp.IsActive(),
|
||||
Controlable: controlable,
|
||||
HasPlaylist: hasPlaylist,
|
||||
Streams: inp.CurrentlyPlaying(),
|
||||
Mixable: mixable,
|
||||
Mixer: mixer,
|
||||
|
@ -84,6 +96,7 @@ func declareInputsRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
streamRoutes := inputsRoutes.Group("/streams/:stream")
|
||||
streamRoutes.Use(StreamHandler)
|
||||
|
||||
// ControlableInput
|
||||
streamRoutes.POST("/pause", func(c *gin.Context) {
|
||||
input, ok := c.MustGet("input").(inputs.ControlableInput)
|
||||
if !ok {
|
||||
|
@ -99,6 +112,8 @@ func declareInputsRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
|
||||
// MixableInput
|
||||
streamRoutes.POST("/volume", func(c *gin.Context) {
|
||||
input, ok := c.MustGet("input").(inputs.MixableInput)
|
||||
if !ok {
|
||||
|
@ -121,6 +136,62 @@ func declareInputsRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
|
||||
// PlaylistInput
|
||||
streamRoutes.POST("/has_playlist", func(c *gin.Context) {
|
||||
input, ok := c.MustGet("input").(inputs.PlaylistInput)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, input.HasPlaylist())
|
||||
})
|
||||
streamRoutes.POST("/next_track", func(c *gin.Context) {
|
||||
input, ok := c.MustGet("input").(inputs.PlaylistInput)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"})
|
||||
return
|
||||
}
|
||||
|
||||
err := input.NextTrack()
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
streamRoutes.POST("/next_random_track", func(c *gin.Context) {
|
||||
input, ok := c.MustGet("input").(inputs.PlaylistInput)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"})
|
||||
return
|
||||
}
|
||||
|
||||
err := input.NextRandomTrack()
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
streamRoutes.POST("/prev_track", func(c *gin.Context) {
|
||||
input, ok := c.MustGet("input").(inputs.PlaylistInput)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"})
|
||||
return
|
||||
}
|
||||
|
||||
err := input.PreviousTrack()
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
}
|
||||
|
||||
func InputHandler(c *gin.Context) {
|
||||
|
|
|
@ -17,6 +17,7 @@ type SourceState struct {
|
|||
Enabled bool `json:"enabled"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
Controlable bool `json:"controlable,omitempty"`
|
||||
HasPlaylist bool `json:"hasplaylist,omitempty"`
|
||||
CurrentTitle string `json:"currentTitle,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -28,6 +29,11 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
active := src.IsActive()
|
||||
_, controlable := src.(inputs.ControlableInput)
|
||||
|
||||
var hasPlaylist bool
|
||||
if p, withPlaylist := src.(inputs.PlaylistInput); withPlaylist {
|
||||
hasPlaylist = p.HasPlaylist()
|
||||
}
|
||||
|
||||
var title string
|
||||
if s, ok := src.(sources.PlayingSource); ok && active {
|
||||
title = s.CurrentlyPlaying()
|
||||
|
@ -38,6 +44,7 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
Enabled: src.IsEnabled(),
|
||||
Active: &active,
|
||||
Controlable: controlable,
|
||||
HasPlaylist: hasPlaylist,
|
||||
CurrentTitle: title,
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +61,11 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
active := src.IsActive()
|
||||
_, controlable := src.(inputs.ControlableInput)
|
||||
|
||||
var hasPlaylist bool
|
||||
if p, withPlaylist := src.(inputs.PlaylistInput); withPlaylist {
|
||||
hasPlaylist = p.HasPlaylist()
|
||||
}
|
||||
|
||||
var title string
|
||||
if s, ok := src.(sources.PlayingSource); ok && active {
|
||||
title = s.CurrentlyPlaying()
|
||||
|
@ -64,6 +76,7 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
Enabled: src.IsEnabled(),
|
||||
Active: &active,
|
||||
Controlable: controlable,
|
||||
HasPlaylist: hasPlaylist,
|
||||
CurrentTitle: title,
|
||||
})
|
||||
})
|
||||
|
@ -123,6 +136,8 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
|
||||
// ControlableInput
|
||||
sourcesRoutes.POST("/pause", func(c *gin.Context) {
|
||||
src := c.MustGet("source").(sources.SoundSource)
|
||||
|
||||
|
@ -139,6 +154,90 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||
|
||||
c.JSON(http.StatusOK, s.TogglePause("default"))
|
||||
})
|
||||
|
||||
// PlaylistInput
|
||||
sourcesRoutes.POST("/has_playlist", 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.(inputs.PlaylistInput)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, s.HasPlaylist())
|
||||
})
|
||||
sourcesRoutes.POST("/next_track", 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.(inputs.PlaylistInput)
|
||||
if !ok || !s.HasPlaylist() {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support"})
|
||||
return
|
||||
}
|
||||
|
||||
err := s.NextTrack()
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
sourcesRoutes.POST("/next_random_track", 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.(inputs.PlaylistInput)
|
||||
if !ok || !s.HasPlaylist() {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support"})
|
||||
return
|
||||
}
|
||||
|
||||
err := s.NextRandomTrack()
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
sourcesRoutes.POST("/prev_track", 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.(inputs.PlaylistInput)
|
||||
if !ok || !s.HasPlaylist() {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support"})
|
||||
return
|
||||
}
|
||||
|
||||
err := s.PreviousTrack()
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
}
|
||||
|
||||
func SourceHandler(c *gin.Context) {
|
||||
|
|
|
@ -22,6 +22,13 @@ type ControlableInput interface {
|
|||
TogglePause(string) error
|
||||
}
|
||||
|
||||
type PlaylistInput interface {
|
||||
HasPlaylist() bool
|
||||
NextTrack() error
|
||||
NextRandomTrack() error
|
||||
PreviousTrack() error
|
||||
}
|
||||
|
||||
type MixableInput interface {
|
||||
GetMixers() (map[string]*InputMixer, error)
|
||||
SetMixer(string, *InputMixer) error
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/DexterLB/mpvipc"
|
||||
|
@ -13,25 +14,29 @@ import (
|
|||
)
|
||||
|
||||
type MPVSource struct {
|
||||
process *exec.Cmd
|
||||
ipcSocket string
|
||||
Name string
|
||||
Options []string
|
||||
File string
|
||||
process *exec.Cmd
|
||||
ipcSocketDir string
|
||||
Name string
|
||||
Options []string
|
||||
File string
|
||||
}
|
||||
|
||||
func init() {
|
||||
sources.SoundSources["mpv-1"] = &MPVSource{
|
||||
Name: "Radio 1",
|
||||
ipcSocket: "/tmp/tmpmpv.radio-1",
|
||||
Options: []string{"--no-video", "--no-terminal"},
|
||||
File: "https://mediaserv38.live-streams.nl:18030/stream",
|
||||
sources.SoundSources["mpv-nig"] = &MPVSource{
|
||||
Name: "Radio NIG",
|
||||
File: "http://stream.syntheticfm.com:8030/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",
|
||||
sources.SoundSources["mpv-synthfm"] = &MPVSource{
|
||||
Name: "Radio Synthetic FM",
|
||||
File: "http://stream.syntheticfm.com:8040/stream",
|
||||
}
|
||||
sources.SoundSources["mpv-nrw"] = &MPVSource{
|
||||
Name: "NewRetroWave",
|
||||
File: "https://youtube.com/channel/UCD-4g5w1h8xQpLaNS_ghU4g/videos",
|
||||
}
|
||||
sources.SoundSources["mpv-abgt"] = &MPVSource{
|
||||
Name: "ABGT",
|
||||
File: "https://youtube.com/playlist?list=PL6RLee9oArCArCAjnOtZ17dlVZQxaHG8G",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,15 +52,20 @@ func (s *MPVSource) IsEnabled() bool {
|
|||
return s.process != nil
|
||||
}
|
||||
|
||||
func (s *MPVSource) ipcSocket() string {
|
||||
return path.Join(s.ipcSocketDir, "mpv.socket")
|
||||
}
|
||||
|
||||
func (s *MPVSource) Enable() (err error) {
|
||||
if s.process != nil {
|
||||
return fmt.Errorf("Already running")
|
||||
}
|
||||
|
||||
var opts []string
|
||||
opts = append(opts, s.Options...)
|
||||
if s.ipcSocket != "" {
|
||||
opts = append(opts, "--input-ipc-server="+s.ipcSocket, "--pause")
|
||||
s.ipcSocketDir, err = os.MkdirTemp("", "hathoris")
|
||||
|
||||
opts := append([]string{"--no-video", "--no-terminal"}, s.Options...)
|
||||
if s.ipcSocketDir != "" {
|
||||
opts = append(opts, "--input-ipc-server="+s.ipcSocket(), "--pause")
|
||||
}
|
||||
opts = append(opts, s.File)
|
||||
|
||||
|
@ -70,18 +80,22 @@ func (s *MPVSource) Enable() (err error) {
|
|||
s.process.Process.Kill()
|
||||
}
|
||||
|
||||
if s.ipcSocketDir != "" {
|
||||
os.RemoveAll(s.ipcSocketDir)
|
||||
}
|
||||
|
||||
s.process = nil
|
||||
}()
|
||||
|
||||
if s.ipcSocket != "" {
|
||||
_, err = os.Stat(s.ipcSocket)
|
||||
if s.ipcSocketDir != "" {
|
||||
_, err = os.Stat(s.ipcSocket())
|
||||
for i := 20; i >= 0 && err != nil; i-- {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, err = os.Stat(s.ipcSocket)
|
||||
_, err = os.Stat(s.ipcSocket())
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
conn := mpvipc.NewConnection(s.ipcSocket)
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err = conn.Open()
|
||||
for i := 20; i >= 0 && err != nil; i-- {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
@ -145,11 +159,13 @@ func (s *MPVSource) FadeOut(conn *mpvipc.Connection, speed int) {
|
|||
func (s *MPVSource) Disable() error {
|
||||
if s.process != nil {
|
||||
if s.process.Process != nil {
|
||||
conn := mpvipc.NewConnection(s.ipcSocket)
|
||||
err := conn.Open()
|
||||
if err == nil {
|
||||
s.FadeOut(conn, 3)
|
||||
conn.Close()
|
||||
if s.ipcSocketDir != "" {
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err := conn.Open()
|
||||
if err == nil {
|
||||
s.FadeOut(conn, 3)
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
s.process.Process.Kill()
|
||||
|
@ -160,8 +176,8 @@ func (s *MPVSource) Disable() error {
|
|||
}
|
||||
|
||||
func (s *MPVSource) CurrentlyPlaying() string {
|
||||
if s.ipcSocket != "" {
|
||||
conn := mpvipc.NewConnection(s.ipcSocket)
|
||||
if s.ipcSocketDir != "" {
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
log.Println("Unable to open mpv socket:", err.Error())
|
||||
|
@ -181,11 +197,11 @@ func (s *MPVSource) CurrentlyPlaying() string {
|
|||
}
|
||||
|
||||
func (s *MPVSource) TogglePause(id string) error {
|
||||
if s.ipcSocket == "" {
|
||||
if s.ipcSocketDir == "" {
|
||||
return fmt.Errorf("Not supported")
|
||||
}
|
||||
|
||||
conn := mpvipc.NewConnection(s.ipcSocket)
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -212,3 +228,93 @@ func (s *MPVSource) TogglePause(id string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MPVSource) HasPlaylist() bool {
|
||||
if s.ipcSocketDir == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
plistCount, err := conn.Get("playlist-count")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return plistCount.(float64) > 1
|
||||
}
|
||||
|
||||
func (s *MPVSource) NextTrack() error {
|
||||
if s.ipcSocketDir == "" {
|
||||
return fmt.Errorf("Not supported")
|
||||
}
|
||||
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.Call("playlist-next", "weak")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MPVSource) NextRandomTrack() error {
|
||||
if s.ipcSocketDir == "" {
|
||||
return fmt.Errorf("Not supported")
|
||||
}
|
||||
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.Call("playlist-shuffle")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Call("playlist-next", "weak")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Call("playlist-unshuffle")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MPVSource) PreviousTrack() error {
|
||||
if s.ipcSocketDir == "" {
|
||||
return fmt.Errorf("Not supported")
|
||||
}
|
||||
|
||||
conn := mpvipc.NewConnection(s.ipcSocket())
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.Call("playlist-prev", "weak")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ func (s *SPDIFSource) IsActive() bool {
|
|||
}
|
||||
|
||||
func (s *SPDIFSource) IsEnabled() bool {
|
||||
return s.processRec != nil
|
||||
return s.processRec != nil || s.processPlay != nil
|
||||
}
|
||||
|
||||
func (s *SPDIFSource) Enable() error {
|
||||
|
@ -77,10 +77,11 @@ func (s *SPDIFSource) Enable() error {
|
|||
go func() {
|
||||
err := s.processPlay.Wait()
|
||||
if err != nil {
|
||||
s.processPlay.Process.Kill()
|
||||
pipeR.Close()
|
||||
pipeW.Close()
|
||||
if s.processPlay != nil && s.processPlay.Process != nil {
|
||||
s.processPlay.Process.Kill()
|
||||
}
|
||||
}
|
||||
pipeR.Close()
|
||||
|
||||
s.processPlay = nil
|
||||
}()
|
||||
|
@ -97,6 +98,7 @@ func (s *SPDIFSource) Enable() error {
|
|||
if err != nil {
|
||||
s.processRec.Process.Kill()
|
||||
}
|
||||
pipeW.Close()
|
||||
|
||||
s.processRec = nil
|
||||
}()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,21 +10,22 @@
|
|||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte": "^2.30.0",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^4.0.5",
|
||||
"vite": "^4.4.2"
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-static": "^2.0.3",
|
||||
"@sveltejs/adapter-static": "^3.0.0",
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootstrap-icons": "^1.11.1",
|
||||
"sass": "^1.69.5"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,14 +11,33 @@
|
|||
<span class="text-muted">{source.currentTitle}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if source.controlable}
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
on:click={() => source.playpause()}
|
||||
>
|
||||
<i class="bi bi-pause"></i>
|
||||
</button>
|
||||
{#if source.controlable || source.hasplaylist}
|
||||
<div class="d-flex gap-1">
|
||||
{#if source.hasplaylist}
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
on:click={() => source.prevtrack()}
|
||||
>
|
||||
<i class="bi bi-skip-backward-fill"></i>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
on:click={() => source.nexttrack()}
|
||||
on:dblclick={() => source.nextrandomtrack()}
|
||||
>
|
||||
<i class="bi bi-skip-forward-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if source.controlable}
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
on:click={() => source.playpause()}
|
||||
>
|
||||
<i class="bi bi-pause"></i>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
|
|
|
@ -6,10 +6,11 @@ export class Input {
|
|||
}
|
||||
}
|
||||
|
||||
update({ name, active, controlable, streams, mixable, mixer }) {
|
||||
update({ name, active, controlable, hasplaylist, streams, mixable, mixer }) {
|
||||
this.name = name;
|
||||
this.active = active;
|
||||
this.controlable = controlable;
|
||||
this.hasplaylist = hasplaylist;
|
||||
this.streams = streams;
|
||||
this.mixable = mixable;
|
||||
this.mixer = mixer;
|
||||
|
@ -30,6 +31,27 @@ export class Input {
|
|||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
async nexttrack(idstream) {
|
||||
const data = await fetch(`api/inputs/${this.id}/streams/${idstream}/next_track`, {headers: {'Accept': 'application/json'}, method: 'POST'});
|
||||
if (data.status != 200) {
|
||||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
async nextrandomtrack(idstream) {
|
||||
const data = await fetch(`api/inputs/${this.id}/streams/${idstream}/next_random_track`, {headers: {'Accept': 'application/json'}, method: 'POST'});
|
||||
if (data.status != 200) {
|
||||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
async prevtrack(idstream) {
|
||||
const data = await fetch(`api/inputs/${this.id}/streams/${idstream}/prev_track`, {headers: {'Accept': 'application/json'}, method: 'POST'});
|
||||
if (data.status != 200) {
|
||||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getInputs() {
|
||||
|
|
|
@ -6,11 +6,12 @@ export class Source {
|
|||
}
|
||||
}
|
||||
|
||||
update({ name, enabled, active, controlable, currentTitle }) {
|
||||
update({ name, enabled, active, controlable, hasplaylist, currentTitle }) {
|
||||
this.name = name;
|
||||
this.enabled = enabled;
|
||||
this.active = active;
|
||||
this.controlable = controlable;
|
||||
this.hasplaylist = hasplaylist;
|
||||
this.currentTitle = currentTitle;
|
||||
}
|
||||
|
||||
|
@ -37,6 +38,27 @@ export class Source {
|
|||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
async nexttrack() {
|
||||
const data = await fetch(`api/sources/${this.id}/next_track`, {headers: {'Accept': 'application/json'}, method: 'POST'});
|
||||
if (data.status != 200) {
|
||||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
async nextrandomtrack() {
|
||||
const data = await fetch(`api/sources/${this.id}/next_random_track`, {headers: {'Accept': 'application/json'}, method: 'POST'});
|
||||
if (data.status != 200) {
|
||||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
async prevtrack() {
|
||||
const data = await fetch(`api/sources/${this.id}/prev_track`, {headers: {'Accept': 'application/json'}, method: 'POST'});
|
||||
if (data.status != 200) {
|
||||
throw new Error((await res.json()).errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSources() {
|
||||
|
|
Loading…
Reference in New Issue