Compare commits

...

3 Commits

Author SHA1 Message Date
7cedd74706 Refactor SourceSelection, display it in layout
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-09 09:04:52 +02:00
207a4562e6 Improve LoadableSource implementation 2024-10-09 09:02:32 +02:00
eb2eeee3c7 mpv: Add error logs 2024-10-08 19:35:52 +02:00
6 changed files with 100 additions and 75 deletions

2
app.go
View File

@ -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)
} }

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
} }

View File

@ -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>

View File

@ -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">