aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/music/audio_playback.py
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-04-29 14:47:32 +0200
committersalmonik <raisersoft@proton.me>2026-04-29 14:47:32 +0200
commit8a4352ebb5e15ff4be82a9b4e111c1535aab349e (patch)
tree2c4d5b4b65bdc8308b35c411cffa7f17b1bfeb4d /cogs/music/audio_playback.py
parentfix: fallback to plaintext if no json is provided for assistant.py (diff)
parentproper context for settings (diff)
downloadsenator-sperma-8a4352ebb5e15ff4be82a9b4e111c1535aab349e.tar.gz
senator-sperma-8a4352ebb5e15ff4be82a9b4e111c1535aab349e.zip
Merge branch 'dev'
Signed-off-by: salmonik <raisersoft@proton.me>
Diffstat (limited to 'cogs/music/audio_playback.py')
-rw-r--r--cogs/music/audio_playback.py42
1 files changed, 30 insertions, 12 deletions
diff --git a/cogs/music/audio_playback.py b/cogs/music/audio_playback.py
index 28c6118..1313d01 100644
--- a/cogs/music/audio_playback.py
+++ b/cogs/music/audio_playback.py
@@ -137,8 +137,16 @@ class AudioPlaybackManager:
137 return absolute_source, display_name, is_url 137 return absolute_source, display_name, is_url
138 138
139 @staticmethod 139 @staticmethod
140 async def execute_playback(ctx, source, display_name, is_url): 140 async def execute_playback(ctx, source, display_name, is_url, interrupt=True):
141 """Execute the actual audio playback.""" 141 """Execute the actual audio playback.
142
143 Args:
144 ctx: Discord context
145 source: Audio source path or URL
146 display_name: Name to display
147 is_url: Whether source is a URL
148 interrupt: If True, stop any currently playing audio. If False, skip if audio is playing.
149 """
142 target_channel = await AudioPlaybackManager.validate_voice_channel(ctx) 150 target_channel = await AudioPlaybackManager.validate_voice_channel(ctx)
143 if not target_channel: 151 if not target_channel:
144 return False 152 return False
@@ -169,9 +177,12 @@ class AudioPlaybackManager:
169 logger.info(f"Moving to voice channel: {target_channel.name}") 177 logger.info(f"Moving to voice channel: {target_channel.name}")
170 await vc.move_to(target_channel) 178 await vc.move_to(target_channel)
171 179
172 # Stop any currently playing audio 180 # Stop any currently playing audio (if interrupt is True)
173 if vc.is_playing() or vc.is_paused(): # type: ignore[union-attr] 181 if interrupt and (vc.is_playing() or vc.is_paused()): # type: ignore[union-attr]
174 vc.stop() # type: ignore[union-attr] 182 vc.stop() # type: ignore[union-attr]
183 elif not interrupt and vc.is_playing(): # type: ignore[union-attr]
184 logger.info("Audio already playing and interrupt=False, skipping playback")
185 return False
175 186
176 # Prepare audio source 187 # Prepare audio source
177 ( 188 (
@@ -181,13 +192,14 @@ class AudioPlaybackManager:
181 source, is_url, display_name 192 source, is_url, display_name
182 ) 193 )
183 194
184 # Track if playback failed 195 # Track playback state
185 playback_failed = {"error": None} 196 playback_state = {"error": None, "finished": False}
186 197
187 def after_playing(error): 198 def after_playing(error):
188 if error: 199 if error:
189 playback_failed["error"] = str(error) 200 playback_state["error"] = str(error)
190 logger.error(f"Playback error: {error}") 201 logger.error(f"Playback error: {error}")
202 playback_state["finished"] = True
191 203
192 # Play the audio 204 # Play the audio
193 vc.play(audio_source, after=after_playing) 205 vc.play(audio_source, after=after_playing)
@@ -197,14 +209,19 @@ class AudioPlaybackManager:
197 # Wait for FFmpeg to attempt connection - errors usually happen within 5 seconds 209 # Wait for FFmpeg to attempt connection - errors usually happen within 5 seconds
198 await asyncio.sleep(5) 210 await asyncio.sleep(5)
199 211
200 # Check if playback actually started 212 # Check if playback failed to start (not just finished quickly)
201 if playback_failed["error"]: 213 if playback_state["error"]:
202 logger.error(f"Playback failed: {playback_failed['error']}") 214 logger.error(f"Playback failed: {playback_state['error']}")
203 await ctx.followup.send( 215 await ctx.followup.send(
204 "eror: Timeout - nie udało się połączyć ze streamem", ephemeral=True 216 "eror: Timeout - nie udało się połączyć ze streamem", ephemeral=True
205 ) 217 )
206 return False 218 return False
207 219
220 # If playback finished within 5s, it was a short file - not an error
221 if playback_state["finished"]:
222 logger.info(f"Playback completed quickly: {final_display_name}")
223 return True
224
208 if not vc.is_playing(): 225 if not vc.is_playing():
209 logger.error(f"Stream failed - not playing after 5s: {source}") 226 logger.error(f"Stream failed - not playing after 5s: {source}")
210 await ctx.followup.send( 227 await ctx.followup.send(
@@ -232,13 +249,14 @@ class AudioPlaybackManager:
232 return prepared_source, display_name, is_url 249 return prepared_source, display_name, is_url
233 250
234 @staticmethod 251 @staticmethod
235 async def play(ctx, source, delete_after_play=False): 252 async def play(ctx, source, delete_after_play=False, interrupt=True):
236 """Play audio from a local file or URL. 253 """Play audio from a local file or URL.
237 254
238 Args: 255 Args:
239 ctx: Discord context 256 ctx: Discord context
240 source: Either a local file path or a URL to stream from 257 source: Either a local file path or a URL to stream from
241 delete_after_play: If True, delete the file after playing (only for local files) 258 delete_after_play: If True, delete the file after playing (only for local files)
259 interrupt: If True, stop any currently playing audio. If False, skip if audio is playing.
242 """ 260 """
243 # Validate and prepare source 261 # Validate and prepare source
244 ( 262 (
@@ -254,7 +272,7 @@ class AudioPlaybackManager:
254 272
255 # Execute playback 273 # Execute playback
256 success = await AudioPlaybackManager.execute_playback( 274 success = await AudioPlaybackManager.execute_playback(
257 ctx, prepared_source, display_name, is_url 275 ctx, prepared_source, display_name, is_url, interrupt
258 ) 276 )
259 277
260 # Cleanup if needed 278 # Cleanup if needed