Compare commits
3 Commits
e38f103b82
...
7cedd74706
Author | SHA1 | Date | |
---|---|---|---|
7cedd74706 | |||
207a4562e6 | |||
eb2eeee3c7 |
2
app.go
2
app.go
@ -73,7 +73,7 @@ func (app *App) loadCustomSources() error {
|
|||||||
if newss, ok := sources.LoadableSources[csrc.Source]; !ok {
|
if newss, ok := sources.LoadableSources[csrc.Source]; !ok {
|
||||||
return fmt.Errorf("Unable to load source #%d: %q: no such source registered", id, csrc.Source)
|
return fmt.Errorf("Unable to load source #%d: %q: no such source registered", id, csrc.Source)
|
||||||
} else {
|
} else {
|
||||||
src, err := newss(csrc.KV)
|
src, err := newss.LoadSource(csrc.KV)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to load source #%d (%s): %w", id, csrc.Source, err)
|
return fmt.Errorf("Unable to load source #%d (%s): %w", id, csrc.Source, err)
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CustomSource struct {
|
type CustomSource struct {
|
||||||
Source string `json:"src"`
|
Source string `json:"src"`
|
||||||
KV map[string]string `json:"kv"`
|
KV map[string]interface{} `json:"kv"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package sources
|
package sources
|
||||||
|
|
||||||
import ()
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
LoadableSources = map[string]LoadaleSource{}
|
LoadableSources = map[string]LoadaleSource{}
|
||||||
@ -19,4 +21,17 @@ type PlayingSource interface {
|
|||||||
CurrentlyPlaying() string
|
CurrentlyPlaying() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoadaleSource func(map[string]string) (SoundSource, error)
|
type LoadaleSource struct {
|
||||||
|
LoadSource func(map[string]interface{}) (SoundSource, error)
|
||||||
|
Description string
|
||||||
|
SourceDefinition interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unmarshal(in map[string]interface{}, out interface{}) error {
|
||||||
|
jin, err := json.Marshal(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(jin, out)
|
||||||
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package mpv
|
package mpv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DexterLB/mpvipc"
|
"github.com/DexterLB/mpvipc"
|
||||||
@ -17,31 +17,23 @@ import (
|
|||||||
type MPVSource struct {
|
type MPVSource struct {
|
||||||
process *exec.Cmd
|
process *exec.Cmd
|
||||||
ipcSocketDir string
|
ipcSocketDir string
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Options []string
|
Options []string `json:"opts"`
|
||||||
File string
|
File string `json:"file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sources.LoadableSources["mpv"] = NewMPVSource
|
sources.LoadableSources["mpv"] = sources.LoadaleSource{
|
||||||
|
LoadSource: NewMPVSource,
|
||||||
|
Description: "Play any file, stream or URL through mpv",
|
||||||
|
SourceDefinition: &MPVSource{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMPVSource(kv map[string]string) (sources.SoundSource, error) {
|
func NewMPVSource(kv map[string]interface{}) (sources.SoundSource, error) {
|
||||||
var s MPVSource
|
var s MPVSource
|
||||||
|
err := sources.Unmarshal(kv, &s)
|
||||||
if name, ok := kv["name"]; ok {
|
return &s, err
|
||||||
s.Name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts, ok := kv["opts"]; ok {
|
|
||||||
s.Options = strings.Split(opts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
if file, ok := kv["file"]; ok {
|
|
||||||
s.File = file
|
|
||||||
}
|
|
||||||
|
|
||||||
return &s, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MPVSource) GetName() string {
|
func (s *MPVSource) GetName() string {
|
||||||
@ -75,13 +67,23 @@ func (s *MPVSource) Enable() (err error) {
|
|||||||
|
|
||||||
s.process = exec.Command("mpv", opts...)
|
s.process = exec.Command("mpv", opts...)
|
||||||
if err = s.process.Start(); err != nil {
|
if err = s.process.Start(); err != nil {
|
||||||
|
log.Println("Unable to launch mpv:", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := s.process.Wait()
|
err := s.process.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.process.Process.Kill()
|
var exiterr *exec.ExitError
|
||||||
|
if errors.As(err, &exiterr) {
|
||||||
|
if exiterr.ExitCode() > 0 {
|
||||||
|
log.Printf("mpv exited with error code = %d", exiterr.ExitCode())
|
||||||
|
} else {
|
||||||
|
log.Print("mpv exited successfully")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.process.Process.Kill()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ipcSocketDir != "" {
|
if s.ipcSocketDir != "" {
|
||||||
@ -106,6 +108,7 @@ func (s *MPVSource) Enable() (err error) {
|
|||||||
err = conn.Open()
|
err = conn.Open()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("Unable to connect to mpv socket:", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
@ -120,15 +123,21 @@ func (s *MPVSource) Enable() (err error) {
|
|||||||
|
|
||||||
err = conn.Set("pause", false)
|
err = conn.Set("pause", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("Unable to unpause:", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var pfc interface{}
|
var pfc interface{}
|
||||||
|
pfc, err = conn.Get("core-idle")
|
||||||
|
|
||||||
for err == nil && pfc.(bool) {
|
for err == nil && pfc.(bool) {
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
pfc, err = conn.Get("core-idle")
|
pfc, err = conn.Get("core-idle")
|
||||||
}
|
}
|
||||||
err = nil
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to retrieve core-idle status:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
s.FadeIn(conn, 3, 50)
|
s.FadeIn(conn, 3, 50)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../hathoris.scss'
|
import '../hathoris.scss';
|
||||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||||
|
|
||||||
import { sources } from '$lib/stores/sources';
|
import { activeSources, sources } from '$lib/stores/sources';
|
||||||
sources.refresh();
|
sources.refresh();
|
||||||
setInterval(sources.refresh, 5000);
|
setInterval(sources.refresh, 5000);
|
||||||
|
|
||||||
import { inputs } from '$lib/stores/inputs';
|
import { activeInputs, inputs } from '$lib/stores/inputs';
|
||||||
inputs.refresh();
|
inputs.refresh();
|
||||||
setInterval(inputs.refresh, 4500);
|
setInterval(inputs.refresh, 4500);
|
||||||
|
|
||||||
|
import SourceSelection from '$lib/components/SourceSelection.svelte';
|
||||||
|
|
||||||
const version = fetch('api/version', {headers: {'Accept': 'application/json'}}).then((res) => res.json())
|
const version = fetch('api/version', {headers: {'Accept': 'application/json'}}).then((res) => res.json())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -18,7 +20,50 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="flex-fill d-flex flex-column">
|
<div class="flex-fill d-flex flex-column">
|
||||||
<div class="container-fluid flex-fill d-flex flex-column justify-content-center">
|
<div class="container-fluid flex-fill d-flex flex-column justify-content-start">
|
||||||
|
<div class="my-3">
|
||||||
|
<SourceSelection />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $activeSources.length === 0 && $activeInputs.length === 0}
|
||||||
|
<div class="text-muted text-center mt-1 mb-1">
|
||||||
|
Aucune source active pour l'instant.
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<marquee>
|
||||||
|
{#each $activeSources as source}
|
||||||
|
<div class="d-inline-block me-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
{#if source.currentTitle}
|
||||||
|
<strong>{source.currentTitle}</strong> <span class="text-muted">@ {source.name}</span>
|
||||||
|
{:else}
|
||||||
|
{source.name} activée
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{#each $activeInputs as input}
|
||||||
|
<div class="d-inline-block me-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
{#if input.streams.length}
|
||||||
|
{#each Object.keys(input.streams) as idstream}
|
||||||
|
{@const title = input.streams[idstream]}
|
||||||
|
<strong>{title}</strong>
|
||||||
|
{/each}
|
||||||
|
<span class="text-muted">@ {input.name}</span>
|
||||||
|
{:else}
|
||||||
|
{input.name} activée
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</marquee>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import Applications from '$lib/components/Applications.svelte';
|
import Applications from '$lib/components/Applications.svelte';
|
||||||
import Inputs from '$lib/components/Inputs.svelte';
|
import Inputs from '$lib/components/Inputs.svelte';
|
||||||
import Mixer from '$lib/components/Mixer.svelte';
|
import Mixer from '$lib/components/Mixer.svelte';
|
||||||
import SourceSelection from '$lib/components/SourceSelection.svelte';
|
|
||||||
import { activeSources } from '$lib/stores/sources';
|
import { activeSources } from '$lib/stores/sources';
|
||||||
import { activeInputs } from '$lib/stores/inputs';
|
import { activeInputs } from '$lib/stores/inputs';
|
||||||
|
|
||||||
@ -10,50 +9,7 @@
|
|||||||
let showInactiveInputs = false;
|
let showInactiveInputs = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="my-3">
|
|
||||||
<SourceSelection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#if $activeSources.length === 0 && $activeInputs.length === 0}
|
|
||||||
<div class="text-muted text-center mt-1 mb-1">
|
|
||||||
Aucune source active pour l'instant.
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<marquee>
|
|
||||||
{#each $activeSources as source}
|
|
||||||
<div class="d-inline-block me-3">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
{#if source.currentTitle}
|
|
||||||
<strong>{source.currentTitle}</strong> <span class="text-muted">@ {source.name}</span>
|
|
||||||
{:else}
|
|
||||||
{source.name} activée
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{#each $activeInputs as input}
|
|
||||||
<div class="d-inline-block me-3">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
{#if input.streams.length}
|
|
||||||
{#each Object.keys(input.streams) as idstream}
|
|
||||||
{@const title = input.streams[idstream]}
|
|
||||||
<strong>{title}</strong>
|
|
||||||
{/each}
|
|
||||||
<span class="text-muted">@ {input.name}</span>
|
|
||||||
{:else}
|
|
||||||
{input.name} activée
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</marquee>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<div class="card my-3">
|
<div class="card my-3">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user