python can't break while loop when play audio with pygame-change - python

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`

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.

Related

A function is running despite me changing a variable that should stop it

I was hoping someone could help me with this issue. I'm hoping it's fairly simple to fix, but I have been trying for a while to figure this out. I have trimmed my larger code to this, as I believe the issue here is the crux of the problem.
I have a raspberry pi and an external button. This is on python3 on Linux. I am using GPIOZero for the button. The code below I believe is simple to understand, but basically, I want a function to loop at all times. When I press a button I want another function to run, but only if a variable is a certain number. I describe what I ultimately want to happen in a comment below, but my code is unfinished and simplified just for this problem.
I only want button.when_pressed to work when timer = 0. The problem is, once the code naturally gets into the button.when_pressed function, it never "lets go" of the function again. When I successfully redefine the variable to timer = 1, it still prints button is pressed when I press the button. I don't know why. To me, it seems like it should only work once timer = 0.
Any suggestions? I think I have a misunderstanding of global variables I will plan on researching. I'm not sure if that's the issue. I have also tried using break and continue to try to get it "back on its loop" but that hasn't worked either. Also I want to use button.when_pressed instead of btn.is_pressed, because I want the program to only do something when I'm holding the button down once, and not loop when I'm holding it down. In this case, I want button is pressed to print a single time. If I did btn.is_pressed it would print button is pressed every two seconds, which I dont want.
Thanks for any help. I'm happy to learn.
#!/usr/bin/python3
from gpiozero import Button
from time import sleep
import time
button = Button(4)
timer = 0
def press():
print("Button is pressed")
global timer
timer = 1
def loop():
global timer
timer = 1
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
If you want to disable the callback you had set to button.when_pressed, you need to do another assignment with button.when_pressed = None. This is listed in the documentation:
when_released:
[...] Set this property to None (the default) to disable the event.
It's not exactly clear what behavior you want from your current code. If you want the button to be active for 2 seconds, then be deactivated indefinitely, you can use:
button.when_pressed = press
sleep(2)
button.when_pressed = None
There's no need for a loop, since you don't want to repeat anything.
If you only want the button to be active for a single button press, that needs to happen within 2 seconds, you could instead call button.wait_for_press(2). I hesitate to write a full block of code for that though, as the docs don't specify how a timeout is signaled (it might be by return value, or via an exception). I don't have a Raspberry Pi so I can't test myself, but you could try it out and see what happens.
Treat your whole piece of code as one "black box", ask yourself, what is the input/output? button press or timer mode? (because I don't quite understand what does timer variable mean in your code)
Your code implies timer mode is the top level input to control the flow,
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
Is it expected?
If you allow user to press the button at any time, suggest you make button press to be your top level input, change the logic to keep when_pressed callback always on, set flag once triggered, and then check if the button has been pressed and still is_pressed in your while loop.
pressed = False
def play_video_1():
pass
def play_video_2():
pass
def press():
print("Button is pressed")
global pressed
pressed = True
button.when_pressed = press
while True:
if pressed and not_playing_video2:
if is_pressed:
play_video_1()
else:
pressed = False
play_video_2()
else:
play_video_2()

How to stop repeat on button press/hold - Python

I was hoping someone might have some insight on how to stop a script from continuing to repeat if a button is held (or in my case pressed longer than a second)?
Basically i've a button setup on the breadboard, and I have it coded to play an audio file when the button is pressed. This works, however if the button isn't very quickly tapped, then the audio will repeat itself until button is fully released. Also if the button is pressed and held, the audio file will just repeat indefinitely.
I've recorded a quick recording to demonstrate the issue if its helpful, here: https://streamable.com/esvoy6
I should also note that I am very new to python (coding in general actually), so its most likely something simple that I just haven't been able to find yet. I am using gpiozero for my library.
Any help or insight is greatly appreciated!
Here is what my code looks like right now:
from gpiozero import LED, Button
import vlc
import time
import sys
def sleep_minute(minutes):
sleep(minutes * 60)
# GPIO Pins of Green LED
greenLight = LED(17)
greenButton = Button(27)
# Green Button Pressed Definition
def green_btn_pressed():
print("Green Button Pressed")
greenButton.when_pressed = greenLight.on
greenButton.when_released = greenLight.on
# Executed Script
while True:
if greenButton.is_pressed:
green_btn_pressed()
time.sleep(.1)
print("Game Audio Start")
p = vlc.MediaPlayer("/home/pi/Desktop/10 Second Countdown.mp3")
p.play()
So from a brief look at it, it seems that 'time.sleep(.1)' is not doing what you are expecting. Ie. it is obviously interrupted by button presses. This is not abnormal behaviour as button presses on Ardiuno and raspPi (guessing here) would be processed as interrupts.
The script itself does not contain any prevention from double pressing or press and hold etc.
Have you put in any debug lines to see what is executing when you press the button?
I would start there and make adjustments based on what you are seeing.
I am not familiar with this gpiozero, so I can't give any insight about what it may be doing, but looking at the code and given the issue you are having, I would start with some debug lines in both functions to confirm what is happening.
Thinking about it for a minute though, could you not just change the check to 'if greenButton.is_released:'? As then you know the button has already been pressed, and the amount of time it is held in for becomes irrelevant. May also want to put in a check for if the file is already playing to stop it and start it again, or ignore and continue playing (if that is the desired behaviour).
Further suggestions:
For this section of code:
# Executed Script
while True:
if greenButton.is_pressed:
green_btn_pressed()
time.sleep(.1)
print("Game Audio Start")
p = vlc.MediaPlayer("/home/pi/Desktop/10 Second Countdown.mp3")
p.play()
You want to change this to something along these lines:
alreadyPlaying = 0
# Executed Script
while True:
if greenButton.is_pressed:
green_btn_pressed()
#Check if already playing file.
if alreadyPlaying == 1:
# Do check to see if file is still playing (google this, not sure off the top of head how to do this easiest).
# If file still playing do nothing,
#else set 'alreadyPlaying' back to '0'
break
#Check if already playing file.
if alreadyPlaying == 0:
time.sleep(.1)
print("Game Audio Start")
p = vlc.MediaPlayer("/home/pi/Desktop/10 Second Countdown.mp3")
p.play()
alreadyPlaying = 1
Hopefully you get the idea of what I am saying. Best of luck!
i think you have to write something like this in your loop:
import time, vlc
def Sound(sound):
vlc_instance = vlc.Instance()
player = vlc_instance.media_player_new()
media = vlc_instance.media_new(sound)
player.set_media(media)
player.play()
time.sleep(1.5)
duration = player.get_length() / 1000
time.sleep(duration)

Trying to create a mouse recorder, but it keeps looping endlessly?

I'm trying my hand at Pynput, and I'm starting off with creating a simple program to record the movements of a mouse, and then replay those movements once a button is clicked.
However, every time I click the mouse, it just starts to freak out and endlessly loop. I think it's going through the movements at a super high speed, but I eventually have to Alt-F4 the shell to stop it.
Any help would be appreciated.
import pynput
arr = []
from pynput import mouse
mou = pynput.mouse.Controller()
def on_move(x,y):
Pos = mou.position
arr.append(Pos)
def on_click(x, y, button, pressed):
listener.stop()
for i in arr:
mou.position = i
print("Done")
listener = mouse.Listener(on_move = on_move, on_click=on_click)
listener.start()
You have to be careful when using multiple threads (which is the case here, since mouse.Listener runs in its own thread). Apparently, as long as you are in the callback function, all events are still processed, even after you have called listener.stop(). So when replaying, for each mouse position you set, the on_move callback function is called, so that mouse position is added to your list again, which causes the endless loop.
In general, it's bad practice to implement too much functionality (in this case the "replaying") in a callback function. A better solution would be to use an event to signal another thread that the mouse button has been clicked. See the following example code. A few remarks:
I've added a few print statements to see what's happening.
I've added a small delay between the mouse positions to really see the playback. (NB: This also might make breaking out of the application a bit easier in case it hangs!)
I've changed a few variable names to make more sense. Calling an array "arr" is not a good idea. Try to use names that really describe the variable. In this case it is a list of positions, so I choose to call it positions.
I'm using return False to stop the mouse controller. The documentation states "Call pynput.mouse.Listener.stop from anywhere, raise StopException or return False from a callback to stop the listener.", but personally, I think returning False is the cleanest and safest solution.
import threading
import time
import pynput
positions = []
clicked = threading.Event()
controller = pynput.mouse.Controller()
def on_move(x, y):
print(f'on_move({x}, {y})')
positions.append((x, y))
def on_click(x, y, button, pressed):
print(f'on_move({x}, {y}, {button}, {pressed})')
# Tell the main thread that the mouse is clicked
clicked.set()
return False
listener = pynput.mouse.Listener(on_move=on_move, on_click=on_click)
listener.start()
try:
listener.wait()
# Wait for the signal from the listener thread
clicked.wait()
finally:
listener.stop()
print('*REPLAYING*')
for position in positions:
controller.position = position
time.sleep(0.01)
Note that when you run this in a Windows command prompt, the application might hang because you have pressed the mouse button and are then starting to send mouse positions. This causes a "drag" movement, which pauses the terminal. If this happens, you can just press Escape and the program will continue to run.
You got yourself an infinite loop. I think the listener you referred to in the on_click method might be null or undefined. Also according to some documentation I found you need to return false for the on_click method to stop listening
This is what I was looking at:
https://pythonhosted.org/pynput/mouse.html

How do I exit a while loop in python with an event?

I'm making this project for school that involves displaying data to a raspberry pi. The code I'm using refreshes (and needs to refresh) incredibly quickly, but I need a way for the user to stop the output, which I believe requires some kind of key event. The thing is, I'm new to Python and I can't figure out how to exit a while loop with turtle.onkey(). I found this code:
import turtle
def quit():
global more
more = False
turtle.onkey(quit, "Up")
turtle.listen()
more = True
while more:
print("something")
Which doesn't work. I've tested it. How do I make this work, or is there another way to get user input without interrupting the flow of the program?
while loop run on thread
check this code
import threading
def something():
while more:
print("something")
th = threading.Thread(something)
th.start()
Avoid infinite loops in a Python turtle graphics program:
more = True
while more:
print("something")
You can effectively block events from firing, including the one intended to stop the loop. Instead, use timer events to run your code and allow other events to fire:
from turtle import Screen
more = True
counter = 0
def stop():
global more
more = False
def start():
global more
more = True
screen.ontimer(do_something, 100)
def do_something():
global counter
print("something", counter)
counter += 1
if more:
screen.ontimer(do_something, 100)
screen = Screen()
screen.onkey(stop, "Up")
screen.onkey(start, "Down")
screen.listen()
start()
screen.mainloop()
I've added a counter to your program just so you can more easily see when the 'something' statements stop and I've added a restart on the down key so you can start them up again. Control should always reach mainloop() (or done() or exitonclick()) to give all the event handlers a chance to execute. Some infinite loops allow events to fire but they typically have calls into the turtle methods that allow it to have control some of the time but are still the wrong approach.
Chances are that you are trying to run your code in an interactive IPython shell. That does not work. The bare Python repl shell works, though.
Here I found a project that tries to bring turtle to IPython: https://github.com/Andrewkind/Turtle-Ipython. I did not test it, and I'm not exactly sure that this is a better solution than simply using the unsugared shell.
You could have you loop check a file like this:
def check_for_value_in_file():
with open('file.txt') as f:
value = f.read()
return value
while check_for_value_in_file() == 'the right value':
do_stuff()

How do I get a variable from a running python function

I am working on some code that will count mouse clicks on a beaglebone board in python.
My question is how to structure code such that I can access the total number of mouse clicks while the mouse click counter function is still running (indefinately), and at the same time not interrupt the mouse click counter function ( i dont want to miss a click!)?
Is there a way to access variables in a running function in python without interrupting it?
Depends on how you're wanting to access them. An infinite loop works well.
_clicks = 0
while True:
if _clicks != clicks:
_clicks = clicks
print(_clicks)
But you'll almost certainly have to put that in another thread.
from threading import Thread
def notifier():
global clicks # whatever variable you're accessing
while True:
if _clicks != clicks:
_clicks = clicks
print(_clicks)
t = Thread(target=notifier)
t.start()

Categories