LabCodeHub öffentliche Ansicht
Anmelden
Küpper / ehc-tvwall öffentlich
Branch: main
ehc-tvwall / cmd / tvwall / main.go
Verlauf Rohdaten
R Rüdiger Küpper fix: v1.0.0
14518d05 vor 11 Tagen
cmd/tvwall/main.go 130 Zeilen · 3.4 KB · Go
1
// Kommando tvwall: Media-Player und Webinterface fuer die TV-Wall.
2
//
3
// Der Dienst liest regelmaessig den Medienordner ein, haelt daraus eine
4
// Playliste aktuell, laesst sie von mpv im Vollbild in Endlosschleife
5
// abspielen und stellt zum Hochladen und Sortieren ein Webinterface bereit.
6
package main
7
8
import (
9
	"context"
10
	"errors"
11
	"flag"
12
	"fmt"
13
	"log"
14
	"net/http"
15
	"os"
16
	"os/signal"
17
	"syscall"
18
	"time"
19
20
	"ehc/tvwall/internal/config"
21
	"ehc/tvwall/internal/player"
22
	"ehc/tvwall/internal/playlist"
23
	"ehc/tvwall/internal/web"
24
)
25
26
// version wird beim Bauen ueber -ldflags gesetzt.
27
var version = "dev"
28
29
func main() {
30
	var (
31
		configPath = flag.String("config", "/etc/tvwall/tvwall.conf", "Pfad zur Konfigurationsdatei")
32
		addr       = flag.String("addr", "", "Adresse des Webservers (ueberschreibt WEB_PORT)")
33
		stateDir   = flag.String("state-dir", "", "Datenverzeichnis (ueberschreibt STATE_DIR)")
34
		mpvSocket  = flag.String("mpv-socket", "", "Pfad des mpv-IPC-Sockets (ueberschreibt MPV_SOCKET)")
35
		noPlayer   = flag.Bool("no-player", false, "nur das Webinterface starten, kein mpv")
36
		showVer    = flag.Bool("version", false, "Version ausgeben und beenden")
37
	)
38
	flag.Parse()
39
40
	if *showVer {
41
		fmt.Printf("tvwall %s\n", version)
42
		return
43
	}
44
45
	log.SetFlags(log.Ldate | log.Ltime)
46
	log.Printf("tvwall %s startet", version)
47
48
	cfg, err := config.Load(*configPath)
49
	if err != nil {
50
		log.Fatalf("konfiguration %s: %v", *configPath, err)
51
	}
52
	if *stateDir != "" {
53
		cfg.StateDir = *stateDir
54
	}
55
	if *mpvSocket != "" {
56
		cfg.SocketPath = *mpvSocket
57
	}
58
	for _, dir := range []string{cfg.StateDir, cfg.MediaDir(), cfg.ThumbDir()} {
59
		if err := os.MkdirAll(dir, 0o775); err != nil {
60
			log.Fatalf("verzeichnis %s anlegen: %v", dir, err)
61
		}
62
	}
63
	log.Printf("medienordner: %s", cfg.MediaDir())
64
65
	settings := config.NewStore(cfg)
66
	pl := playlist.New(cfg)
67
	if _, err := pl.Scan(); err != nil {
68
		log.Printf("erster durchlauf des medienordners: %v", err)
69
	}
70
	log.Printf("playliste: %d eintraege", len(pl.Entries()))
71
72
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
73
	defer stop()
74
75
	pp := player.New(cfg, pl, settings)
76
	if *noPlayer {
77
		log.Printf("player deaktiviert (-no-player)")
78
	} else {
79
		go pp.Run(ctx)
80
	}
81
82
	go watchMedia(ctx, pl, time.Duration(cfg.ScanInterval)*time.Second)
83
84
	listen := *addr
85
	if listen == "" {
86
		listen = fmt.Sprintf(":%d", cfg.WebPort)
87
	}
88
	srv := web.New(cfg, pl, pp, settings)
89
90
	errCh := make(chan error, 1)
91
	go func() {
92
		if err := srv.ListenAndServe(listen); err != nil && !errors.Is(err, http.ErrServerClosed) {
93
			errCh <- err
94
		}
95
	}()
96
97
	select {
98
	case <-ctx.Done():
99
		log.Printf("beende …")
100
		// Dem Player einen Moment geben, mpv sauber zu beenden.
101
		time.Sleep(300 * time.Millisecond)
102
	case err := <-errCh:
103
		log.Fatalf("webserver: %v", err)
104
	}
105
}
106
107
// watchMedia liest den Medienordner regelmaessig ein. So landen auch Dateien
108
// in der Playliste, die per USB-Stick, SSH oder Netzwerkfreigabe ankommen.
109
func watchMedia(ctx context.Context, pl *playlist.Manager, every time.Duration) {
110
	if every < time.Second {
111
		every = 10 * time.Second
112
	}
113
	ticker := time.NewTicker(every)
114
	defer ticker.Stop()
115
	for {
116
		select {
117
		case <-ctx.Done():
118
			return
119
		case <-ticker.C:
120
			changed, err := pl.Scan()
121
			if err != nil {
122
				log.Printf("medienordner einlesen: %v", err)
123
				continue
124
			}
125
			if changed {
126
				log.Printf("medienordner geaendert: %d eintraege", len(pl.Entries()))
127
			}
128
		}
129
	}
130
}