@@ -21,6 +21,8 @@ public class NetCordAudioPlayerService(
2121 public event Func < Task > ? NotInVoiceChannelCallback ;
2222 private Action < Func < Task > > OnDisconnectAsync { get ; set ; } = _ => { } ;
2323 private StringMenuInteractionContext Context { get ; set ; } = null ! ;
24+
25+ private int _retryCount = 0 ;
2426
2527 public async Task Play < T > ( T ctx , Action < Func < Task > > onDisconnectAsync )
2628 {
@@ -80,39 +82,9 @@ async Task HandleVoiceStream()
8082 }
8183
8284 var selectedValue = currentTrack . Context . SelectedValues [ 0 ] ;
83- string sourceUrl ;
85+ var sourceUrl = await GetSourceUrl ( selectedValue , radioSourceService , youTubeService , currentTrack ) ;
8486
85- if ( Guid . TryParse ( selectedValue , out var radioId ) )
86- {
87- sourceUrl = ( await radioSourceService . GetRadioSourceByIdAsync ( radioId ) ) . SourceUrl ;
88- }
89- else
90- {
91- sourceUrl = await youTubeService . GetAudioStreamUrlAsync ( selectedValue ,
92- playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
93-
94- var song = new SongDtoBase
95- {
96- Url = selectedValue ,
97- Title = await youTubeService . GetVideoTitleAsync ( selectedValue ,
98- playerState . SkipCts ? . Token ?? CancellationToken . None ) ,
99- UserId = currentTrack . Context . User . Id
100- } ;
101- ffmpegProcessService . OnProcessStart += ( ) => HandleOnProcessStartAsync ( song ) ;
102- }
103-
104- var ffmpeg =
105- await ffmpegProcessService . CreateStreamAsync ( sourceUrl ,
106- playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
107-
108-
109- ffmpegProcessService . OnExitProcess += HandleOnProcessExitAsync ;
110-
111- await ffmpeg . StandardOutput . BaseStream . CopyToAsync ( stream ,
112- playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
113-
114- // Flush 'stream' to make sure all the data has been sent and to indicate to Discord that we have finished sending
115- await stream . FlushAsync ( playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
87+ await StartFfmpegStream ( sourceUrl , stream ) ;
11688 }
11789
11890 async Task StartVoiceClientAsync ( )
@@ -146,9 +118,54 @@ await client.UpdateVoiceStateAsync(
146118 playerState . StopCts ? . Token ?? CancellationToken . None ) ;
147119 await playerState . CurrentVoiceClient . CloseAsync (
148120 cancellationToken : playerState . StopCts ? . Token ?? CancellationToken . None ) ;
121+
122+ _retryCount = 0 ;
149123 }
150124 }
151125
126+ private async Task StartFfmpegStream ( string sourceUrl , OpusEncodeStream stream )
127+ {
128+ var ffmpeg =
129+ await ffmpegProcessService . CreateStreamAsync ( sourceUrl ,
130+ playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
131+
132+ ffmpegProcessService . ErrorDataReceived += HandleFfmpegErrorAsync ;
133+ ffmpegProcessService . OnExitProcess += HandleOnProcessExitAsync ;
134+
135+ await ffmpeg . StandardOutput . BaseStream . CopyToAsync ( stream ,
136+ playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
137+
138+ // Flush 'stream' to make sure all the data has been sent and to indicate to Discord that we have finished sending
139+ await stream . FlushAsync ( playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
140+ }
141+
142+ private async Task < string > GetSourceUrl ( string selectedValue , IRadioSourceService radioSourceService ,
143+ IStreamService youTubeService , PlayRequest < StringMenuInteractionContext > currentTrack )
144+ {
145+ string sourceUrl ;
146+
147+ if ( Guid . TryParse ( selectedValue , out var radioId ) )
148+ {
149+ sourceUrl = ( await radioSourceService . GetRadioSourceByIdAsync ( radioId ) ) . SourceUrl ;
150+ }
151+ else
152+ {
153+ sourceUrl = await youTubeService . GetAudioStreamUrlAsync ( selectedValue ,
154+ playerState . SkipCts ? . Token ?? CancellationToken . None ) ;
155+
156+ var song = new SongDtoBase
157+ {
158+ Url = selectedValue ,
159+ Title = await youTubeService . GetVideoTitleAsync ( selectedValue ,
160+ playerState . SkipCts ? . Token ?? CancellationToken . None ) ,
161+ UserId = currentTrack . Context . User . Id
162+ } ;
163+ ffmpegProcessService . OnProcessStart += ( ) => HandleOnProcessStartAsync ( song ) ;
164+ }
165+
166+ return sourceUrl ;
167+ }
168+
152169 private async Task HandleOnProcessStartAsync ( SongDtoBase song )
153170 {
154171 using var scope = serviceProvider . CreateScope ( ) ;
@@ -174,4 +191,18 @@ private async ValueTask HandleOnVoiceClientDisconnectedAsync(DisconnectEventArgs
174191 {
175192 await ( DisconnectedVoiceClientEvent ? . Invoke ( ) ?? Task . CompletedTask ) ;
176193 }
194+
195+ private async Task HandleFfmpegErrorAsync ( )
196+ {
197+ // Retry to get new stream URL and play again
198+ logger . LogWarning ( "Ffmpeg error received, retrying to play the stream" ) ;
199+ _retryCount ++ ;
200+ if ( _retryCount > 3 )
201+ {
202+ logger . LogError ( "Ffmpeg error received, maximum retry count reached, stopping playback" ) ;
203+ await ( DisconnectedVoiceClientEvent ? . Invoke ( ) ?? Task . CompletedTask ) ;
204+ return ;
205+ }
206+ await HandleMusicPlayingAsync ( ) ;
207+ }
177208}
0 commit comments