Files
go-mxl-player/cmd/mxl-player/playlist_file.go
T
2026-09-02 00:03:47 +03:00

157 lines
4.0 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"os"
"time"
"mxl-player/internal/playback"
)
type playlistFile struct {
Entries []playlistFileEntry `json:"entries"`
Loop bool `json:"loop"`
OnFailure string `json:"on_failure"`
Retry *playlistFileRetry `json:"retry"`
}
type playlistFileRetry struct {
MaxAttempts *int `json:"max_attempts"`
InitialDelay string `json:"initial_delay"`
MaxDelay string `json:"max_delay"`
}
type playlistFileEntry struct {
Name string `json:"name"`
Video *playlistFileFeed `json:"video"`
Audio *playlistFileFeed `json:"audio"`
Sync bool `json:"sync"`
Duration string `json:"duration"`
}
type playlistFileFeed struct {
Domain string `json:"domain"`
UUID string `json:"uuid"`
}
func loadPlaylistFile(path string) (playback.Playlist, error) {
file, err := os.Open(path)
if err != nil {
return playback.Playlist{}, fmt.Errorf("open playlist %q: %w", path, err)
}
defer file.Close()
playlist, err := decodePlaylistFile(file)
if err != nil {
return playback.Playlist{}, fmt.Errorf("decode playlist %q: %w", path, err)
}
return playlist, nil
}
func decodePlaylistFile(reader io.Reader) (playback.Playlist, error) {
decoder := json.NewDecoder(reader)
decoder.DisallowUnknownFields()
var file playlistFile
if err := decoder.Decode(&file); err != nil {
return playback.Playlist{}, fmt.Errorf("decode JSON: %w", err)
}
var extra any
if err := decoder.Decode(&extra); err != io.EOF {
if err == nil {
return playback.Playlist{}, fmt.Errorf("decode JSON: multiple root values")
}
return playback.Playlist{}, fmt.Errorf("decode trailing JSON: %w", err)
}
playlist := playback.Playlist{
Entries: make([]playback.PlaylistEntry, len(file.Entries)),
Loop: file.Loop,
}
switch file.OnFailure {
case "", "wait":
playlist.OnFailure = playback.PlaylistFailureWait
case "next":
playlist.OnFailure = playback.PlaylistFailureNext
default:
return playback.Playlist{}, fmt.Errorf(
"on_failure %q: %w", file.OnFailure, playback.ErrPlaylistFailurePolicy,
)
}
if file.Retry != nil {
retry, err := decodePlaylistRetry(*file.Retry)
if err != nil {
return playback.Playlist{}, err
}
playlist.Retry = &retry
}
for index, entry := range file.Entries {
duration := time.Duration(0)
if entry.Duration != "" {
parsed, err := time.ParseDuration(entry.Duration)
if err != nil {
return playback.Playlist{}, fmt.Errorf(
"playlist entry %d duration %q: %w",
index,
entry.Duration,
err,
)
}
duration = parsed
}
playlist.Entries[index] = playback.PlaylistEntry{
Name: entry.Name,
Video: playlistFileFeedToPlayback(entry.Video),
Audio: playlistFileFeedToPlayback(entry.Audio),
SyncRequested: entry.Sync,
Duration: duration,
}
}
if err := playlist.Validate(); err != nil {
return playback.Playlist{}, fmt.Errorf("validate playlist: %w", err)
}
return playlist, nil
}
func decodePlaylistRetry(file playlistFileRetry) (playback.RetryPolicy, error) {
retry := playback.RetryPolicy{
InitialDelay: initialRetryDelay,
MaxDelay: maxRetryDelay,
}
if file.MaxAttempts != nil {
retry.MaxAttempts = *file.MaxAttempts
}
var err error
if file.InitialDelay != "" {
retry.InitialDelay, err = time.ParseDuration(file.InitialDelay)
if err != nil {
return playback.RetryPolicy{}, fmt.Errorf(
"retry initial_delay %q: %w", file.InitialDelay, err,
)
}
}
if file.MaxDelay != "" {
retry.MaxDelay, err = time.ParseDuration(file.MaxDelay)
if err != nil {
return playback.RetryPolicy{}, fmt.Errorf(
"retry max_delay %q: %w", file.MaxDelay, err,
)
}
}
if err := retry.Validate(); err != nil {
return playback.RetryPolicy{}, fmt.Errorf("validate playlist retry: %w", err)
}
return retry, nil
}
func playlistFileFeedToPlayback(feed *playlistFileFeed) playback.PlaylistFeed {
if feed == nil {
return playback.PlaylistFeed{}
}
return playback.PlaylistFeed{Domain: feed.Domain, UUID: feed.UUID}
}