Reputation: 617
I'm doing a project which is making a music player using PyGame and Tkinter. So far I've built the interface and got the player to play two different songs, but I need a piece of advice on how to use PyGame mixer better. My code is:
from tkinter import *
import time, sys
from pygame import mixer
track_names = ['lady maria', 'cleric beast']
current_track = ''
def press(word):
global track_name
global current_track
word = button_text.get()
if word == 'PLAY':
update_button_text('PLAY')
for name in track_names:
window_2.delete(0, 'end')
current_track = name
window_2.configure(state='normal')
window_2.insert('end', name)
mixer.init()
mixer.music.set_volume(100)
mixer.music.load(f'C:/Users/user/Desktop/python/projects/etc/{name}.mp3')
mixer.music.play()
time.sleep(5)
if word == 'PAUSE':
update_button_text('PAUSE')
mixer.music.pause()
time.sleep(5)
if word == 'STOP':
mixer.music.stop()
time.sleep(5)
if word == 'NEXT':
pass
if word == 'PREVIOUS':
pass
def update_button_text(word):
if word == 'PLAY':
button_text.set('PAUSE')
elif word == 'PAUSE':
button_text.set('PLAY')
if __name__ == '__main__':
# create application window
app = Tk()
# title
app.title("Music Players")
# geometry
app.geometry('383x121')
# background color
app.configure(bg='orange')
equation = StringVar()
window_1 = Label(app, textvariable=equation)
window_1.grid(columnspan=4, ipadx=100, ipady=10)
equation.set('music player')
window_2 = Entry(app, width=30)
window_2.grid(columnspan=4, ipadx=100, ipady=10)
window_2.configure(state='disabled')
window_2.grid_columnconfigure((0, 1, 2), uniform="equal", weight=1)
# Create buttons
button_text = StringVar()
button_text.set("PLAY")
button1 = Button(app, textvariable=button_text, fg='yellow', bg='purple',
command=lambda: press(button_text), height=2, width=1)
button1.grid(row=2, column=0, sticky="NSEW")
button2 = Button(app, text='STOP', fg='yellow', bg='purple',
command=lambda: press('STOP'), height=2, width=1)
button2.grid(row=2, column=1, sticky="NSEW")
button3 = Button(app, text='NEXT', fg='yellow', bg='purple',
command=lambda: press('NEXT'), height=2, width=1)
button3.grid(row=2, column=2, sticky="NSEW")
button4 = Button(app, text='PREVIOUS', fg='yellow', bg='purple',
command=lambda: press('PREVIOUS'), height=2, width=1)
button4.grid(row=2, column=3, sticky="NSEW")
# start the GUI
app.mainloop()
So when I run the code and click PLAY, it looks like the player is stopped responding. Although it starts playing the music right away, it does not show the title. I have code that inserts the title of the song into Tkinter entry first, and then mixer function comes after but it seems like the mixer function always comes first. This is the part I'm having a problem (I think):
def press(word):
global track_name
global current_track
word = button_text.get()
if word == 'PLAY':
update_button_text('PLAY')
for name in track_names:
window_2.delete(0, 'end')
current_track = name
window_2.configure(state='normal')
window_2.insert('end', name)
mixer.init()
mixer.music.set_volume(100)
mixer.music.load(f'C:/Users/user/Desktop/python/projects/etc/{name}.mp3')
mixer.music.play()
time.sleep(5)
Why is this so? And how should I fix the code to make a better music player? Any advice on this will be greatly appreciated!!
Upvotes: 1
Views: 942
Reputation: 14926
The problem is your code is blocking the TKinter main event loop as it loops through the audio tracks. Event driven programs cannot have their events blocked, this is why you're seeing the window "lock up" - no re-painting, sizing, closing, etc. The window code that controls these things never gets the messages, and is never able to act on them.
Your application code must never block this loop for more than a split-second.
So what can you do... use a timer or a thread to do your application's work. Timers are easy to use, they simply add a function call-out to a queue in the main loop. After X milliseconds, that function is called.
For example:
window = tk.Tk()
[...]
window.after( 1000, myFunction, arg1 )
Will cause the main event loop to call your myFunction( arg1 )
after 1000 milliseconds. Then inside myFunction
, you can again queue a call to myFunction
forming a periodic loop.
The PyGame mixer functions already play the music in a separate thread. So in your case, there's no real need to do anything except poll to see if the music has finished. All the "PLAY" code needs to do is start playing the next sound-file, and return.
In the main loop we can create a new timer-function to check to see if the sound-file is still playing, or whether it needs to queue the next one:
def checkPlaying( main_window ):
global current_track
if ( mixer.music.get_busy() == False ):
# Playing has finished
print("Sound finished, playing next" )
track_index, track_name = current_track
current_track = ( track_index + 1, '' ) # Advance to next track
press( 'PLAY' ) # Start playing
# Queue the next call to this function
main_window.after( 250, checkPlaying, main_window ) # call again later
Since it re-queues a timer-call to itself, it needs that initial call in the main function:
# Start the Music Playing Check
app.after( 1000, checkPlaying, app )
The press()
function needs to be modified to remove the continuous loop, just playing the current sound-file:
mixer.init()
current_track = ( 0, '' ) # track-index and name
def press(word):
global track_names
global current_track
word = button_text.get()
if word == 'PLAY':
update_button_text('PLAY')
track_index, track_name = current_track
if ( track_index >= len( track_names ) ): # if out of music, re-start
track_index = 0
# Start Playing the current track
name = track_names[ track_index ]
current_track = ( track_index, name )
mixer.music.set_volume(100)
mixer.music.load(f'C:/Users/user/Desktop/python/projects/etc/{name}.mp3')
mixer.music.play()
# Update the GUI
window_2.delete(0, 'end')
window_2.configure(state='normal')
window_2.insert('end', name)
And that's it.
Reference Code:
from tkinter import *
import time, sys
from pygame import mixer
mixer.init()
track_names = [ 'car-horn2', 'car-horn', 'cash-register', 'dog-bark', 'duck-quack', 'rain-falling', 'single-ding', 'turkey-gobble' ]
current_track = ( 0, '' )
def press(word):
global track_names
global current_track
word = button_text.get()
if word == 'PLAY':
update_button_text('PLAY')
track_index, track_name = current_track
if ( track_index >= len( track_names ) ): # if out of music, re-start
track_index = 0
# Play the current track
name = track_names[ track_index ]
current_track = ( track_index, name )
mixer.music.set_volume(100)
#mixer.music.load(f'C:/Users/user/Desktop/python/projects/etc/{name}.mp3')
mixer.music.load(f'{name}.mp3')
mixer.music.play()
window_2.delete(0, 'end')
window_2.configure(state='normal')
window_2.insert('end', name)
if word == 'PAUSE':
update_button_text('PAUSE')
mixer.music.pause()
time.sleep(5)
if word == 'STOP':
mixer.music.stop()
time.sleep(5)
if word == 'NEXT':
pass
if word == 'PREVIOUS':
pass
def checkPlaying( main_window ):
global track_names
global current_track
result = False
if ( mixer.music.get_busy() == True ):
# Still playing
result = True
else:
# Playing has finished
# TODO: Change button states, whatever
print("Sound finished, playing next" )
track_index, track_name = current_track
current_track = ( track_index + 1, '' )
press( 'PLAY' ) # start next track
result = False
# Queue the next call to this function
main_window.after( 250, checkPlaying, main_window )
return result
def update_button_text(word):
if word == 'PLAY':
button_text.set('PAUSE')
elif word == 'PAUSE':
button_text.set('PLAY')
if __name__ == '__main__':
# create application window
app = Tk()
# title
app.title("Music Players")
# geometry
app.geometry('383x121')
# background color
app.configure(bg='orange')
equation = StringVar()
window_1 = Label(app, textvariable=equation)
window_1.grid(columnspan=4, ipadx=100, ipady=10)
equation.set('music player')
window_2 = Entry(app, width=30)
window_2.grid(columnspan=4, ipadx=100, ipady=10)
window_2.configure(state='disabled')
window_2.grid_columnconfigure((0, 1, 2), uniform="equal", weight=1)
# Create buttons
button_text = StringVar()
button_text.set("PLAY")
button1 = Button(app, textvariable=button_text, fg='yellow', bg='purple',
command=lambda: press(button_text), height=2, width=1)
button1.grid(row=2, column=0, sticky="NSEW")
button2 = Button(app, text='STOP', fg='yellow', bg='purple',
command=lambda: press('STOP'), height=2, width=1)
button2.grid(row=2, column=1, sticky="NSEW")
button3 = Button(app, text='NEXT', fg='yellow', bg='purple',
command=lambda: press('NEXT'), height=2, width=1)
button3.grid(row=2, column=2, sticky="NSEW")
button4 = Button(app, text='PREVIOUS', fg='yellow', bg='purple',
command=lambda: press('PREVIOUS'), height=2, width=1)
button4.grid(row=2, column=3, sticky="NSEW")
# Start the Music Playing Check
app.after( 1000, checkPlaying, app )
# start the GUI
app.mainloop()
Upvotes: 1