3

I tried something new to fit the code. Now when I press ctrl + s the audio really stops but when my personal assistant plays music again in the same run, the music does not stop as for the first time:

import glob
import os
import keyboard

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"

import pygame
songs = glob.glob("C:\\Users\zivsi\Music\\*.mp3")
import random

song = random.choice(songs)

with open("song_choice.txt", "r") as file:
    read = file.read()

if (read == song):
    print("switch song")
    song = random.choice(songs)

song_name = song.replace("C:\\Users\zivsi\Music\\", "").replace(".mp3", "")
print("song: ", song_name)

pygame.mixer.init()

music = pygame.mixer.music.load(song)
pygame.mixer.music.play()

while (pygame.mixer_music.get_busy()):
    pygame.time.Clock().tick(10)
    keyboard.add_hotkey("ctrl + s", lambda: pygame.mixer_music.stop())

# if i will press ctrl + s, the music will stop but it's working only once`

Zivv
  • 33
  • 6
  • 2
    You can't use `break` in a different scope, no. `lambda`s are *functions*, so a different scope. Set a flag instead. – Martijn Pieters Jan 07 '20 at 12:24
  • how to write flag in my code? – Zivv Jan 07 '20 at 12:26
  • Any value you can alter from a function that your `while` loop can test for will do. Like `flags[0] = True`, with `flags` being a global list set to `[False]` (with possibly more values). Ugly, but it would work. Or an attribute on a global instance. Etc. – Martijn Pieters Jan 07 '20 at 12:29
  • You don't want to have a function like keyboard.addhotkey inside your loop, as shown here. It would be done outside the loop. – RufusVS Jan 09 '20 at 15:53

1 Answers1

4

You can't use break in a different scope, it has to be part of the loop suite itself (so nested inside a while or for statement). A lambda is a function, which is a different scope.

Because you want to break out of the loop asynchronously, you'll need some other way to communicate from the keyboard callback handler back to the main loop. In general computer engineering terms, you need some kind of synchronization primitive to synchronize between different things that are happening at the same time, concurrently.

That is to say, you want to set some kind of flag, one that you can check in your while loop, so while pygame.mixer_music.get_busy() and not keyboard_ctrl_s_flag:. You can do this with any global value, and that'd probably be safe:

keyboard_ctrl_s_flag = False  

def control_s_handler():
    """Called when the keyboard combination CTRL-S is pressed"""
    global keyboard_ctrl_s_flag
    keyboard_ctrl_s_flag = True

keyboard.add_hotkey("ctrl + s", control_s_handler)

then you can use that in your loop:

# reset the flag to false, in case it was already set to true before
keyboard_ctrl_s_flag = False  
while pygame.mixer_music.get_busy() and not keyboard_ctrl_s_flag:
    pygame.time.Clock().tick(10)

I say this probably is safe, because I'm not quite sure how PyGame is running its event handling loop. Given that you also used pygame.time.Clock().tick(10) in your loop makes me think that the event loop probably waits for your Python code to return control before doing other things.

The next time that the user then uses CTRL-S, the control_s_handler() callback is called, keyboard_ctrl_s_flag is set to True, and when control returns to the while loop (because pygame.time.Clock().tick(10) returned control to your Python code) the while loop exits even if pygame.mixer_music.get_busy() is still true.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343