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"` } 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, ) } 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 playlistFileFeedToPlayback(feed *playlistFileFeed) playback.PlaylistFeed { if feed == nil { return playback.PlaylistFeed{} } return playback.PlaylistFeed{Domain: feed.Domain, UUID: feed.UUID} }