简体   繁体   中英

How to play music continuously in Pyglet (using tkinter at the same time)?

I'm trying to put some music in my game by making a playlist and when a music ends the next one starts... and after hours searching for a solution i still can't find one... I know how to play one music and how to play the next one by using next_source , but i don't know how to make it do it automatically at the end of a music. And that's why i'm here. I found many websites/forums that tell you to use on_eos but i just can't make it work.

player = pyglet.media.Player()
music1 = pyglet.media.load('somemusic.wav')
music2 = pyglet.media.load('someothermusic.wav')
player.queue(music1)
player.queue(music2)

#the part i'm struggling with (I've tried that)

player.on_eos = pyglet.media.Player.on_eos
@player.event
def on_eos():
    #some function
    print("on player eos")

player.push_handlers(on_eos)

#

player.play()
player = pyglet.media.Player()
music1 = 'somemusic.wav'
music2 = 'someothermusic.wav'
music = [music1, music2]

@player.event
def on_eos():
    print("[event] on_eos: end of file")

@player.event
def on_player_eos():
    print("[event] on_player_eos: end of queue")

media = []
for filename in music:
    print('load:', filename)
    item = pyglet.media.load(filename)
    media.append(item)

def create_queue():
    for item in media:
        print('queue:', item)
        player.queue(item)

def play():
    create_queue()
    player.play()

def update(event):
    root.update()

def on_close():
    clock.unschedule(update)
    root.destroy()  #edit:useless --> It even creates problems!
    pyglet.app.exit()  #this line does the work without the previous one

is_paused = False

root.protocol("WM_DELETE_WINDOW", on_close)

clock.schedule(update)

And that give me the error: TypeError: queue() missing 1 required positional argument: 'source' #edit: no more(solved), the problem was somewhere else.

The play button:

Button(StartPage, text="Play", command=play).pack()

Note: If you had keys binded to functions (using tkinter) like i did and they do not work anymore, use window.focus_force() it will solve your problems.

It run it correctly you would have to use pyglet.app.run() at the end of code. It will run event loop which will run code which play next music. It will also run code assigned to on_eos , etc.

import pyglet

files = [
    'somemusic1.wav',
    'somemusic2.wav',
    'somemusic3.wav',
]
    
player = pyglet.media.Player()

@player.event
def on_eos():
    print("[event] on_eos: end of file")

@player.event
def on_player_eos():
    print("[event] on_player_eos: end of queue")

musics = []

for filename in files:
    item = pyglet.media.load(filename) 
    musics.append( item )
    print('load:', filename)
    
    player.queue(item)
    print('queue:', item)

player.play()

pyglet.app.run()  # need it to run `event loop` 

If you want to run it with tkinter then it can be problem because pyglet runs event loop pyglet.app.run() which blocks code and tkinter has to run own event loop root.mainloop() which also blocks code. One of this loop would have to run in separated thread - something similar to answer in Playing music with Pyglet and Tkinter in Python

I tried use thread for this code but I played only first music when I press button in tkinter . So I resigned.

I tried also build own loop in piglet (see and add root.update() instead of root.mainloop() but it would need something more to work correctly. So I resigned.

while True:
    pyglet.clock.tick()

    for window in pyglet.app.windows:
        window.switch_to()
        window.dispatch_events()
        window.dispatch_event('on_draw')
        window.flip()
    root.update()

As for me the best solution is to use PyQt . It is used to create GUI and it has many wigdets and many classes for other task.

I used some example code with QMediaPlayer and QMediaPlaylist to run all music one by one after clicking button. It has also function to change volume but I didn't test it.

It can load not only sound but also video which it can display in QVideoWidget . And it can load files directly from internet (URLs).

from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QPushButton
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer, QMediaPlaylist
import sys

files = [
    'somemusic1.wav',
    'somemusic2.wav',
    'somemusic3.wav',    
]
    
class VideoPlayer:

    def __init__(self):
        #self.video = QVideoWidget()
        #self.video.resize(300, 300)
        #self.video.move(0, 0)
        
        self.playlist = QMediaPlaylist()
        
        for item in files:
            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(item)))
        
        self.playlist.setCurrentIndex(1)
        
        self.player = QMediaPlayer()
        #self.player.setVideoOutput(self.video)      
        self.player.setPlaylist(self.playlist)

    def callback(self):
        self.player.setPosition(0) # to start at the beginning of the video every time
        #self.video.show()
        self.player.play()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    v = VideoPlayer()
    b = QPushButton('start')
    b.clicked.connect(v.callback)
    b.show()
    sys.exit(app.exec_())

EDIT:

I created working code with Pyglet and tkinter using pyglet.clock.schedule() to runs root.update() periodically.

import pyglet
from pyglet import clock
import tkinter as tk

files = [
    'somemusic1.wav',
    'somemusic2.wav',
    'somemusic3.wav',    
]
    
player = pyglet.media.Player()

@player.event
def on_eos():
    print("[event] on_eos: end of file")

@player.event
def on_player_eos():
    print("[event] on_player_eos: end of queue")

media = []

# load files
for filename in files:
    print('load:', filename)
    item = pyglet.media.load(filename) 
    media.append( item )


def create_queue():
    # create queue
    for item in media:
        print('queue:', item)
        player.queue(item)

# --- tkitner ---

def play():
    create_queue()
    player.play()

def update(event): # `schedule` sends `event` but `root.update` doesn't get any arguments
    root.update() 

def on_close():
    clock.unschedule(update)
    root.destroy()
    pyglet.app.exit()
            
is_paused = False

root = tk.Tk()

root.protocol("WM_DELETE_WINDOW", on_close)

tk.Button(root, text="Play", command=play).pack()
tk.Button(root, text="Exit", command=on_close).pack()

clock.schedule(update)

pyglet.app.run()  # need it to run `event loop` 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM