177 lines
3.6 KiB
Go
177 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"mxl-player/internal/playback"
|
|
)
|
|
|
|
const playlistReadinessInterval = 10 * time.Millisecond
|
|
|
|
var (
|
|
ErrPlayerPlaybackRequired = errors.New("player playback is required")
|
|
ErrPlayerSessionControllerRequired = errors.New("player session controller is required")
|
|
ErrPlayerStatusStoreRequired = errors.New("player status store is required")
|
|
)
|
|
|
|
type playerPlaylist struct {
|
|
Controller *playback.PlaylistController
|
|
Coordinator *playback.PlaylistReadinessCoordinator
|
|
Commands chan playback.PlaylistCommand
|
|
Readiness chan playback.PlaylistReadiness
|
|
}
|
|
|
|
func newPlayerPlaylist(
|
|
playlist playback.Playlist,
|
|
retry playback.RetryPolicy,
|
|
player *playerPlayback,
|
|
) (*playerPlaylist, error) {
|
|
if player == nil {
|
|
return nil, ErrPlayerPlaybackRequired
|
|
}
|
|
if player.Controller == nil {
|
|
return nil, ErrPlayerSessionControllerRequired
|
|
}
|
|
if player.Status == nil {
|
|
return nil, ErrPlayerStatusStoreRequired
|
|
}
|
|
|
|
commands := make(chan playback.PlaylistCommand, 32)
|
|
readiness := make(chan playback.PlaylistReadiness, 8)
|
|
controller, err := playback.NewPlaylistController(
|
|
playlist,
|
|
retry,
|
|
player.Commands,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coordinator, err := playback.NewPlaylistReadinessCoordinator(
|
|
controller,
|
|
player.Controller,
|
|
player.Status,
|
|
readiness,
|
|
playlistReadinessInterval,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &playerPlaylist{
|
|
Controller: controller,
|
|
Coordinator: coordinator,
|
|
Commands: commands,
|
|
Readiness: readiness,
|
|
}, nil
|
|
}
|
|
|
|
func (p *playerPlaylist) Run(ctx context.Context) error {
|
|
runCtx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
results := make(chan error, 2)
|
|
go func() {
|
|
results <- p.Controller.Run(runCtx, p.Commands, p.Readiness)
|
|
}()
|
|
go func() {
|
|
results <- p.Coordinator.Run(runCtx)
|
|
}()
|
|
|
|
first := <-results
|
|
cancel()
|
|
second := <-results
|
|
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
if err := playlistRuntimeError(first); err != nil {
|
|
return err
|
|
}
|
|
if err := playlistRuntimeError(second); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *playerPlaylist) Select(index int) bool {
|
|
return p.enqueue(playback.PlaylistCommand{
|
|
Kind: playback.PlaylistSelect,
|
|
Index: index,
|
|
})
|
|
}
|
|
|
|
func (p *playerPlaylist) Next() bool {
|
|
return p.enqueue(playback.PlaylistCommand{Kind: playback.PlaylistNext})
|
|
}
|
|
|
|
func (p *playerPlaylist) Previous() bool {
|
|
return p.enqueue(playback.PlaylistCommand{Kind: playback.PlaylistPrevious})
|
|
}
|
|
|
|
func (p *playerPlaylist) enqueue(command playback.PlaylistCommand) bool {
|
|
select {
|
|
case p.Commands <- command:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func playlistRuntimeError(err error) error {
|
|
if err == nil || errors.Is(err, context.Canceled) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func shouldAutoStartPlaylist(
|
|
args appArgs,
|
|
playlist playback.Playlist,
|
|
) bool {
|
|
return args.PlaylistPath != "" &&
|
|
args.VideoFlowId == "" &&
|
|
args.AudioFlowId == "" &&
|
|
len(playlist.Entries) > 0
|
|
}
|
|
|
|
func playlistEntryDisplayName(entry playback.PlaylistEntry, index int) string {
|
|
if entry.Name != "" {
|
|
return entry.Name
|
|
}
|
|
return fmt.Sprintf("Entry %d", index+1)
|
|
}
|
|
|
|
func playlistTimingProgress(
|
|
timing playback.PlaylistTimingState,
|
|
now time.Time,
|
|
) (float32, time.Duration) {
|
|
if timing.Duration <= 0 {
|
|
return 0, 0
|
|
}
|
|
if timing.Expired {
|
|
return 1, 0
|
|
}
|
|
if !timing.Started {
|
|
return 0, timing.Duration
|
|
}
|
|
|
|
remaining := timing.Deadline.Sub(now)
|
|
if remaining < 0 {
|
|
remaining = 0
|
|
}
|
|
if remaining > timing.Duration {
|
|
remaining = timing.Duration
|
|
}
|
|
fraction := 1 - float32(remaining)/float32(timing.Duration)
|
|
if fraction < 0 {
|
|
fraction = 0
|
|
}
|
|
if fraction > 1 {
|
|
fraction = 1
|
|
}
|
|
return fraction, remaining
|
|
}
|