From c772225cbd771d16216c2fc47ffc7512939322fc Mon Sep 17 00:00:00 2001 From: Innokentiy Sokolov Date: Sat, 30 Nov 2024 10:09:39 +0300 Subject: [PATCH] fix live to cache switch --- player/player.go | 94 +++++++++++------------------------------------- ytdlp/ytdlp.go | 20 +++++++---- 2 files changed, 34 insertions(+), 80 deletions(-) diff --git a/player/player.go b/player/player.go index 033d1ab..dff37d5 100644 --- a/player/player.go +++ b/player/player.go @@ -115,6 +115,7 @@ PLAYBACK_LOOP: options.EncodingLineLog = true streamPath := p.Song.StreamURL + cacheReady := make(chan bool) if !isCached { go func() { @@ -124,16 +125,21 @@ PLAYBACK_LOOP: return } - path, err := ytdlp.New().GetStream(p.Song.PublicLink) + output, path, err := ytdlp.New().GetStream(p.Song.PublicLink) if err != nil { fmt.Printf("Error caching: %v\n", err) return } - p.Song.StreamURL = path - isCached = true // Mark as cached - cacheReady <- true - fmt.Println("DOING SWITCH") + fmt.Println(output) + + if output.ExitCode == 0 { + time.Sleep(5 * time.Second) + p.Song.StreamURL = path + cacheReady <- true + } + + fmt.Println("Caching is done, switching to playback from cache") }() } @@ -220,6 +226,7 @@ PLAYBACK_LOOP: encoding.Stop() encoding.Cleanup() p.Song = nil + isCached = false continue PLAYBACK_LOOP } // finished @@ -227,11 +234,15 @@ PLAYBACK_LOOP: return nil case <-cacheReady: // Cache is ready fmt.Println("Switching to cached playback") - encoding.Stop() + _, position, err := p.getPlaybackDuration(encoding, streaming, p.Song) + if err != nil { + return err + } + isCached = true + //encoding.Stop() encoding.Cleanup() - //streamPath = filename + startAt = position continue PLAYBACK_LOOP - case signal := <-p.ActionSignals: switch signal { case ActionSkip: @@ -240,6 +251,7 @@ PLAYBACK_LOOP: encoding.Stop() encoding.Cleanup() p.Song = nil + isCached = false continue PLAYBACK_LOOP } p.ActionSignals <- ActionStop @@ -262,70 +274,6 @@ PLAYBACK_LOOP: } } } -func (s *Player) streamToFile(stream io.Reader) (string, error) { - cacheDir := "./cache" - if err := os.MkdirAll(cacheDir, os.ModePerm); err != nil { - return "", fmt.Errorf("failed to create cache directory: %v", err) - } - - tempFile, err := os.CreateTemp(cacheDir, "*.mp4") - if err != nil { - return "", fmt.Errorf("failed to create temp file: %v", err) - } - defer tempFile.Close() - - _, err = io.Copy(tempFile, stream) - if err != nil { - return "", fmt.Errorf("failed to write stream to temp file: %v", err) - } - - return tempFile.Name(), nil -} - -func (p *Player) streamToFileWithRetry(streamProvider func() (io.Reader, error), retries int, delay time.Duration) (string, error) { - cacheDir := "./cache" - if err := os.MkdirAll(cacheDir, os.ModePerm); err != nil { - return "", fmt.Errorf("failed to create cache directory: %v", err) - } - - var tempFilePath string - var err error - - for attempt := 1; attempt <= retries; attempt++ { - fmt.Printf("Attempt %d/%d to write stream to file...\n", attempt, retries) - - // Create a new stream for each attempt - stream, err := streamProvider() - if err != nil { - fmt.Printf("Error retrieving stream on attempt %d: %v\n", attempt, err) - time.Sleep(delay) - continue - } - - // Create a temporary file - tempFile, err := os.CreateTemp(cacheDir, "*.mp4") - if err != nil { - return "", fmt.Errorf("failed to create temp file: %v", err) - } - tempFilePath = tempFile.Name() - defer tempFile.Close() - - // Write the stream to the file - written, err := io.Copy(tempFile, stream) - if err == nil && written > 0 { - fmt.Printf("Successfully wrote %d bytes to %s\n", written, tempFilePath) - return tempFilePath, nil - } - - // Clean up and retry if writing failed - fmt.Printf("Error writing stream on attempt %d: %v\n", attempt, err) - os.Remove(tempFilePath) // Clean up the failed file - time.Sleep(delay) - delay *= 2 // Exponential backoff - } - - return "", fmt.Errorf("failed to write stream to file after %d attempts: %v", retries, err) -} func (p *Player) joinVoiceChannel(session *discordgo.Session, guildID, channelID string) (*discordgo.VoiceConnection, error) { var voiceConnection *discordgo.VoiceConnection @@ -375,7 +323,7 @@ func (p *Player) getPlaybackDuration(encoding *dca.EncodeSession, streaming *dca return 0, 0, fmt.Errorf("unknown source: %v", song.Source) } - playbackPos := encodingStartTime + streamingPos + streamingDelay.Abs() // the delay is wrong here, but I'm out of ideas how to fix the precision + playbackPos := encodingStartTime + streamingPos fmt.Printf("Playback stopped at:\t%s,\tTotal Song duration:\t%s\n", playbackPos, songDuration) fmt.Printf("Encoding started at:\t%s,\tStreaming delay:\t%s\n", encodingStartTime, streamingDelay) diff --git a/ytdlp/ytdlp.go b/ytdlp/ytdlp.go index 7598a09..6456856 100644 --- a/ytdlp/ytdlp.go +++ b/ytdlp/ytdlp.go @@ -68,30 +68,36 @@ func (y *YtdlpWrapper) GetMetaInfo(url string) (Meta, error) { return meta, nil } -func (y *YtdlpWrapper) GetStream(url string) (string, error) { +func (y *YtdlpWrapper) GetStream(url string) (*ytdlp.Result, string, error) { cacheDir := "./cache" if err := os.MkdirAll(cacheDir, 0755); err != nil { - return "", fmt.Errorf("failed to create cache directory: %w", err) + return nil, "", fmt.Errorf("failed to create cache directory: %w", err) } timestamp := time.Now().Format("20060102_150405") outputFile := filepath.Join(cacheDir, timestamp+".webm") - dl := ytdlp.New().Output(outputFile) + dl := ytdlp.New(). + FormatSort("res,ext:webm:webm"). + NoPart(). + NoPlaylist(). + NoOverwrites(). + Output(outputFile) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - if _, err := dl.Run(ctx, url); err != nil { + result, err := dl.Run(ctx, url) + if err != nil { _ = os.Remove(outputFile) - return "", fmt.Errorf("failed to download stream from URL %q: %w", url, err) + return nil, "", fmt.Errorf("failed to download stream from URL %q: %w", url, err) } absPath, err := filepath.Abs(outputFile) if err != nil { - return "", fmt.Errorf("failed to resolve absolute path for %q: %w", outputFile, err) + return nil, "", fmt.Errorf("failed to resolve absolute path for %q: %w", outputFile, err) } - return absPath, nil + return result, absPath, nil }