0

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()
Thewhyap
  • 49
  • 10
  • if you have problem to use `on_eos` then show your code and then we can try to fix it. – furas May 01 '21 at 11:49
  • Actually i don't understand how events work in pyglet so i just don't know how to make it work but i've tried several things like: @player.event def on_eos(): #some function print("on player eos") player.push_handlers(on_eos) but that's not working... i can't make on_eos being dispatch – Thewhyap May 01 '21 at 13:19
  • I don't understand - based on documentation as default it should play all music in queue one-by-one and it doesn't need any `on_eos`. When I tested it then it plays all files in queue wihtout any `on_eos`. Tested on Linux Mint, Python 3.8, pyglet 1.5.16. – furas May 01 '21 at 19:59
  • when I use `@player.event` then I see text `"on player eos"` but I don't have to use `player.next_source` to play next music. – furas May 01 '21 at 20:05
  • if it plays only one sound then maybe for some reason there is only file in queue. – furas May 01 '21 at 20:06
  • last idea - did you use `pyglet.app.run()` at the end of code? If you don't run it then it doesn't run event loop which process events and runs next music on queue. – furas May 01 '21 at 20:11
  • To begin, thank you for your responses, but you are saying that worked for you? Oh... Why not me :( And then, yes I saw that on documentation too, so I thought it will work without on_eos but it just don't, the first file is played, and if I try to play only the other one it does too so the issue isn't in the files I'm using(and i tried with others to be sure)... And the worst is that i can play the second one after the first one when using "next_source"... So I really don't see where's the thing that go wrong. And to finish I'm on windows 10, latest version of python and pyglet. – Thewhyap May 01 '21 at 20:20
  • howwww i didn't saw your last reply, effectively not i'm so dumb but i'm also working on tkinter with this project i thought this would break everything no? – Thewhyap May 01 '21 at 20:20
  • effectively i tried to put pyglet.app.run(), music works well now but my code makes my computer crash and it don't show the window... – Thewhyap May 01 '21 at 20:25
  • `tkiner` need own event loop `root.mainloop()` to work correctly and `pyglet` need own event loop `pyglet.app.run()` to work correctly but both blocks code so it can make problem to work both at the same time. One of them would have to run in separated thread but usually GUIs doesn't like to run in two threads at the same time. It may need to create own event loop which runs `root.update()` and something similar from `pyglet` – furas May 01 '21 at 20:26
  • Hooo... that's effectively a problem... seems like i m gonna give up with that idea for now, by any chance do you know an other way to put music like that for your app (and where you can change the volume too)? – Thewhyap May 01 '21 at 20:33
  • And thanks a lot, really i was struggling so much on a thing so dumb... – Thewhyap May 01 '21 at 20:34
  • in this [answer](https://stackoverflow.com/a/10285724/1832058) to question `Playing music with Pyglet and Tkinter in Python` you can see how to use `threading` to run `Pyglet` in separated threading. As for other modules - I know only `PyGame` but it may also need to run own `event loop` to work. – furas May 01 '21 at 20:43
  • I think you could use `PyQt` instead of both `tkinter` and `Pyglet`. `PyQt` is used to create GUI but it has many widgets and classes. It seems it has QMedia which can load audio in different format (and it can load it even directly from internet - URL). It can change `volume` and it can have `playlist`. I found [example code](https://doc.qt.io/qt-5/qmediaplayer.html#details) for `Qt/C++` but it should be similar in `PyQt/Python` - – furas May 01 '21 at 21:08

2 Answers2

1
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.

Thewhyap
  • 49
  • 10
  • this is place for answer/solutions - not for new problem. If you have new problem then you should write it on new page. – furas May 02 '21 at 23:09
  • always put full error message (starting at word "Traceback") in question (not comment) as text (not screenshot, not link to external portal). There are other useful information. – furas May 02 '21 at 23:10
  • you forgot `pyglet.app.run()` at the end. – furas May 02 '21 at 23:13
0

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` 
furas
  • 134,197
  • 12
  • 106
  • 148
  • Okay so your 'edit' works but... not really... Until I press the button play everything's almost perfect... But then when i press it, I have this error: TypeError: queue() missing 1 required positional argument: 'source' – Thewhyap May 02 '21 at 21:34
  • Then i have 2 more problems: first one(I think I can solve it easily): When I quit my app i get this error:_tkinter.TclError: can't invoke "update" command: application has been destroyed (but the app is already shutted down so no big deal)... But the second one is more problematic: I did 2 functions and linked them with F3 and Escape, but when I press F3 now nothing happens(it was a function setting fullscreen on True or False) and when I use Escape I now exit my app and I have this: Exception ignored on calling ctypes callback function:Process finished with exit code -1073741819 (0xC0000005) – Thewhyap May 02 '21 at 21:46
  • my code works for me without problem. Do you have problem with my original code or with your modifications? Did you use `clock.unschedule(update)` before `destroy`? – furas May 02 '21 at 21:49
  • When i quit my app i also have this: invalid command name "2603924919552updatetime" while executing "2603924919552updatetime" ("after" script) invalid command name "2603930046592update_dialogue" while executing "2603930046592update_dialogue" ("after" script) >>>updatetime and update_dialogue are two functions i made and I use them with root.after() – Thewhyap May 02 '21 at 21:49
  • if you run some `after` then you have to stop it before exit `tkitner` - if you don't stop it then it try to run code which doesn't exist after destroying `tkitner`. Inside `after` you could use i.e`. if running: root.after(...)` to repeate it only when `running` is `True`. This way you can set `running = False` to stop `after`. – furas May 02 '21 at 21:50
  • or you can use task ID which you can get from `after` - `task_ID = root.after(...)` and you can use it to stop it - `root.after_cancel(task_ID)` - and rember to use `global task_ID` inside function to assign it to global/external variable `task_ID`. – furas May 02 '21 at 21:56
  • as for keys - they may work in different way if they are binded to different widgets. Sometimes it may need also to `focus()` this widget - and then system will send keys to this widget instead of sending to other widget. – furas May 02 '21 at 21:58
  • better create new question on new page and you will have more space for code, errors and description. And new people will see it so you may chance to get solution also form other people. – furas May 02 '21 at 22:00
  • Yes I used clock.unschedule(update) before destroy (I pasted everything) i just deleted the #, changed files by another name but np and i deleted your first "clock.schedule(update)" the one you put before everything (because not working with it of course) – Thewhyap May 02 '21 at 22:01
  • I put this first `clock.schedule(update)` by accident :)- now I removed it from code. – furas May 02 '21 at 22:02
  • For the "after" issue yes i had this idea in mind that's why I said "I think I can solve it" but it's for the rest i have no clue... for the clock.schedule(update) by accident i knew that was a mistake lol that's why that is the only thing i changed but the rest nothing... Maybe the rest of my app is making your code not working – Thewhyap May 02 '21 at 22:09
  • And I will not create an other page because i'm giving up for now, I wanted to try your code and everything, and try to see if I can, make it work but if that doesn't work it's not a problem... The only thing I want to know is why i can't make my play button work, and you are telling me it's working for you? I changed nothing in your code so I don't know why... – Thewhyap May 02 '21 at 22:14
  • the only idea: code on your computer runs slower or faster then mine and it may need little time to unschedule `update` - so it may need ie. `time.sleep(0.1)`. Or maybe it need only to use `pyglet.app.exit()` before `root.destroy()` - maybe it will stop scheduler (and `update`) before `root.destroy()` – furas May 02 '21 at 22:32
  • My app has more than 1000 lines of code (without spaces) so maybe that's why it's slower too... next time i will use PyQt like you told me and I will think at music at the begining of my project! I've tried 'pydub' and it was an even bigger mess than with pyglet... – Thewhyap May 02 '21 at 22:46
  • `PyQt` seems the most poweful GUI - it uses `Qt` which is used by LG in SmartTV, `Mercedes-Benz` in (smart)cars - long time age `Qt` was part `Nokia`. See its page [Qt](https://www.qt.io/). And `Qt` has also [Qt Creator](https://www.qt.io/product/development-tools) which can be use to create layout, save it in XML and load in Python. – furas May 02 '21 at 23:01
  • And if you know how to write it in `PyQt` then you can try to rewrite it in `Qt/C++` to make it faster, and to hide source code. – furas May 02 '21 at 23:08
  • Okay i've searched deeper into my problem and i found it (it was an other part of my code) so your code is working great (except for the little problems I mentioned after with linked functions but I will find a way to fix them) furas you are amazing. – Thewhyap May 03 '21 at 17:01