Tkinter and waiting - python

I am making a mp3 player using python 3 and Tkinter, using pygame.mixer for the MP3s. When you input a directory and press play it auto finds all the MP3s in the directory (using glob) and adds them to a list. I use a for loop to load then play the music, but when it plays it starts the next song immediately and automatically repeats till the end of the list where it plays only that song. I tried using time.sleep() which works but freezes the program. How can I wait till the song is over to start the next one and not freeze the program.
My current code (just the function to play the music):
def play():
print("playing")
global playing
playing = True
direct = directorye.get()
direct = direct.lower()
if direct.endswith(".mp3"):
mixer.music.load(directorye.get())
mixer.music.play()
else:
dire = r"C:\Users\agweb\Downloads\Music" #for the time being I just hard coded the directory
mp3s = glob.glob(dire + r"\*.MP3") # adds all MP3s in directory to list
#mp3s =+ glob.glob(directorye.get() + "*.MP3")
for i in mp3s:
print(i)
mixer.music.load(i)
mixer.music.play()
audio = MP3(i)
time.sleep(audio.info.length) #works but freezes program

There are several approaches possible.
Since tkinter is thread-safeish(*) on Python 3, probably the easiest way is to start a slightly modified version of your play function as a second thread.
In the global context, create a threading.Event. I would suggest the name stop_playing.
In the play function, only sleep for a little interval at a time, checking if stop_playing is set:
for i in mp3s:
mixer.music.load(i)
mixer.music.play()
audio = MP3(i)
playtime = 0
interval = 100 # ms
while playtime < audio.info.length:
time.sleep(interval)
playtime += interval
if stop_playing.is_set():
# TODO: stop the current playback.
# Then exit the thread.
return
The new callback for the "Play" button should create a thread for play() and start() it.
The callback for the "Stop" button should simply call stop_playing.set(). That will cause the playing thread to stop.
(* Provided it uses a tcl compiled with threading enabled.)

This solution work you can test it
from threading import Thread
import pygame
from itertools import cycle
def put_next_music_inside(loop_on_musics, stop_event):
for cur_music in loop_on_musics:
while pygame.mixer.music.peek(stop_event):
pass
pygame.event.get(stop_event)
pygame.mixer.music.load(cur_music)
pygame.mixer.music.play()
pygame.mixer.init()
pygame.display.init()
END_OF_MUSIC_EVENT = -100
pygame.mixer.music.set_endevent(END_OF_MUSIC_EVENT)
list_of_music = []#fill it with musics'path
first_music = list_of_music[0]
del list_of_music[0]
list_of_music.append(first_music)
loop_on_musics = cycle(list_of_music)
t = Thread(target=put_next_music_inside, args=(loop_on_musics, END_OF_MUSIC_EVENT))
t.start()
pygame.mixer.music.load(first_music)
pygame.mixer.music.play()

Related

I need a way to stop a function from running at the push of a button

I've been working on this for hours, and I've tried everything from break in an if statement to making an index variable. I can't seem to find a way to stop the sound/music from playing. My goal is to press 'Q' and stop the function and while loop to start another while loop.
import keyboard
import time
from playsound import playsound
#this handles the name that is displayed and makes it easier to input
def Song(type,opt):
time = 1
print(opt)
playsound(type)
print('''Enter Your Music Selection:
O) Overworld
F) Fighting
B) Boss-Strong Enemy
V) Victory
I) Inside-Taven-Shop
D) Dungion
L) Lose-Death
''')
while True:
while keyboard.is_pressed('O'):
Song('MusicStuff/test0.wav','Overworld Theme')
while keyboard.is_pressed('F'):
Song('MusicStuff/TrashLine.wav','Fighting Theme')
Playsound consists of a single function which only plays sound, and does nothing else. That is why you are not able to "stop" the function.
If you want more interactivity you have to use a different library. You can use pyaudio.
Check out this post.
stopping audio with playsound module

How to get out of the while loop in Pygame when after playing the music?

I have the code down below actually what I want that when one the music get finished it should get out of the while loop automatically. But it isn't getting out of that and if I remove that while loop the song is not getting played.
from pygame import mixer
def mplayer(name):
''' for playing music '''
mixer.init()
mixer.music.load(name)
mixer.music.set_volume(0.7)
mixer.music.play()
mplayer('welcome.mp3')
while True:
continue
Is there any way that once the music is finished than it should get out the loop?
Use mixer.music.get_busy() to test test if any sound is being mixed.
from pygame import mixer
def mplayer(name):
''' for playing music '''
mixer.init()
mixer.music.load(name)
mixer.music.set_volume(0.7)
mixer.music.play()
mplayer('welcome.mp3')
while mixer.music.get_busy():
# you may have to handle the events here
# pygame.event.pump()
# [...]
pass
Note, you may need to handle the events in the loop that is waiting for the music to finish. See pygame.event.get() respectively pygame.event.pump():
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system.
PyGame has 2 different modules for playing sound and music, the pygame.mixer module and the pygame.mixer.music module. This module contains classes for loading Sound objects and controlling playback. The difference is explained in the documentation:
The difference between the music playback and regular Sound playback is that the music is streamed, and never actually loaded all at once. The mixer system only supports a single music stream at once.
If pygame.mixer.music doesn't work for you try pygame.mixer.music:
from pygame import mixer
def mplayer(name):
''' for playing music '''
mixer.init()
my_sound = mixer.Sound(name)
my_sound.set_volume(0.7)
my_sound.play(0)
mplayer('welcome.mp3')
while mixer.get_busy():
# you may have to handle the events here
# pygame.event.pump()
# [...]
pass
Here is the solution of my own problem:↓
from pygame import mixer
def mplayer(name):
''' for playing music '''
mixer.init()
mixer.music.load(name)
mixer.music.set_volume(0.7)
mixer.music.play()
mplayer('welcome.mp3')
while mixer.music.get_busy(): #just changed this line
pass

how to use pyglet to play sound in a mayavi animation?

I want to play sound using pyglet in a mayavi animation loop, but I found that pyglet worked not well with 'yield', which has to use in a mayavi animation. The situation is that, it just can't start a new loop when the sound played and animation done once, here are some of my codes, any ideas?
The pyglet can play sound in a for-loop, but can't use yield.
#mlab.animate(delay=delays)
def animate():
f = mlab.gcf()
while True:
for i in range(frames_num):
# update sound
sound = 'shiping/shiping_%d.wav'%i
sound_adjust = pyglet.resource.media(sound, streaming=False)
sound_adjust.play()
# update scene
print('Update scene >>', time.time())
function_to_update_scene()
# with out 'yield' it works well
yield
animate()
Any other modules suggest can also be accepted. The matter is that I need to update sound quickly within 20ms.
I at last solved this by using winsound module. Using
winsound.PlaySound(sound, winsound.SND_FILENAME | winsound.SND_ASYNC)
to replace
sound_adjust = pyglet.resource.media(sound, streaming=False)
sound_adjust.play()
to play defined sound asynchronously. Off course you have to import winsound at the very beginning.

tkSnack: how to play a second sound when first one is ended

I would love my script to play a second sound when the first one is ended. From tkSnack documentation:
play ( )
Plays the sound. All options are ignored if play() is used to resume a paused play options.
If a play() command is issued while another one is in progress,
the latter one is queued up and starts to play as soon as possible.
So, I thought that calling the play method on the sound object while a song was being played would result in the second song to be started when this one's finished.
Unfortunately, this doesn't seem to happen. In fact, as I call the read method on the sound object, in order to load the audio file, the player stops to play.
How should I load the audio?
I've also tried to use two different sound object, but this results in an overlapping playing.
So far, the only solution I've found is to use the blocking option for the play method. And add it in a loop:
for song in songList:
SoundObject.read(song)
SoundObject.play(blocking=True)
This is however far from a good solution, since it blocks the whole application, even if I start it in a daemon thread.
Here is a sample code:
import Tkinter, tkSnack, time
win = Tkinter.Tk()
tkSnack.initializeSnack(win)
snd = tkSnack.Sound()
def play1():
snd.read('/home/francesco/Musica/Andrea Bocelli - Ave Maria.mp3')
snd.play()
def play2():
snd.read('/home/francesco/Musica/Andrea Bocelli - La voce del silenzio.mp3')
snd.play()
b1 = Tkinter.Button(win, text="play1", command = play1)
b2 = Tkinter.Button(win, text="play2", command = play2)
b1.pack()
b2.pack()
win.mainloop()
I would love that clicking play2 it will "append" the soundtrack to the current one and start it as soon as the first one is ended.

Playing music with Pyglet and Tkinter in Python

I wanted to create a simple gui with a play and stop button to play an mp3 file in python. I created a very simple gui using Tkinter that consists of 2 buttons (stop and play).
I created a function that does the following:
def playsound () :
sound = pyglet.media.load('music.mp3')
sound.play()
pyglet.app.run()
I added that function as a command to the button play. I also made a different function to stop music:
def stopsound ():
pyglet.app.exit
I added this function as a command to the second button. But the problem is that when I hit play, python and the gui freeze. I can try to close the window but it does not close, and the stop button is not responsive. I understand that this is because the pyglet.app.run() is executing till the song is over but how exactly do I prevent this? I want the gui to stop the music when I click on the button. Any ideas on where I can find a solution to this?
You are mixing two UI libraries together - that is not intrinsically bad, but there are some problems. Notably, both of them need a main loop of their own to process their events. TKinter uses it to communicate with the desktop and user-generated events, and in this case, pyglet uses it to play your music.
Each of these loops prevents a normal "top down" program flow, as we are used to when we learn non-GUI programming, and the program should proceed basically with callbacks from the main loops. In this case, in the middle of a Tkinter callback, you put the pyglet mainloop (calling pyglet.app.run) in motion, and the control never returns to the Tkinter library.
Sometimes loops of different libraries can coexist on the same process, with no conflicts -- but of course you will be either running one of them or the other. If so, it may be possible to run each library's mainloop in a different Python thread.
If they can not exist together, you will have to deal with each library in a different process.
So, one way to make the music player to start in another thread could be:
from threading import Thread
def real_playsound () :
sound = pyglet.media.load('music.mp3')
sound.play()
pyglet.app.run()
def playsound():
global player_thread
player_thread = Thread(target=real_playsound)
player_thread.start()
If Tkinter and pyglet can coexist, that should be enough to get your music to start.
To be able to control it, however, you will need to implement a couple more things. My suggestion is to have a callback on the pyglet thread that is called by pyglet every second or so -- this callback checks the state of some global variables, and based on them chooses to stop the music, change the file being played, and so on.
I would do something like:
import pyglet
from pyglet.gl import *
class main (pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 600, fullscreen = False)
self.button_texture = pyglet.image.load('button.png')
self.button = pyglet.sprite.Sprite(self.button_texture)
self.sound = pyglet.media.load('music.mp3')
self.sound.play()
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_press(self, x, y, button, modifiers):
if x > self.button.x and x < (self.button.x + self.button_texture.width):
if y > self.button.y and y < (self.button.y + self.button_texture.height):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == 65307: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.button.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
This solution is the easiest one:
import pyglet
foo=pyglet.media.load("/data/Me/Music/Goo Goo Dolls/[1998] Dizzy Up The Girl/11 - Iris.mp3")
foo.play()
def exiter(dt):
pyglet.app.exit()
print "Song length is: %f" % foo.duration
# foo.duration is the song length
pyglet.clock.schedule_once(exiter, foo.duration)
pyglet.app.run()
source: http://ubuntuforums.org/showthread.php?t=1651906
There is a media player implementation in the pyglet documentation:
http://www.pyglet.org/doc/programming_guide/playing_sounds_and_music.html
The script you should look at is media_player.py
Hopefully this will get you started

Categories

Resources