Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tkinter embedding: mpv.terminate() hangs when observe_property has been added to time-pos #114

Open
dfaker opened this issue May 29, 2020 · 6 comments
Labels
repro Reproduce to fix windows

Comments

@dfaker
Copy link
Contributor

dfaker commented May 29, 2020

Reproducible on windows 10 with python-mpv 0.4.6 and mpv-dev-x86_64-20200426-git-640db1e, the last print statement in this snippet is never reached:

import tkinter as tk
import mpv

root=tk.Tk()

player = mpv.MPV(wid=str(int(root.winfo_id())))
player.play('out.mp4')

def handlePropertyChange(name,value):
  print('property change',name,value)
  root.title(str(value))

player.observe_property('time-pos', handlePropertyChange)
tk.mainloop()

print('calling player.terminate.')
player.terminate()
print('terminate player.returned.')

The same behaviour isn't noted if osd-width or duration is observed, additionally placing a player.unobserve_property('time-pos', handlePropertyChange) directly before player.terminate() has no effect, however removing it ahead of time,presumably giving enough time for the event queue to empty does seem to work:

import tkinter as tk
import mpv

root=tk.Tk()

player = mpv.MPV(wid=str(int(root.winfo_id())))

player.play('out.mp4')

def handlePropertyChange(name,value):
  print('property change',name,value)
  root.title(str(value))
  if value is not None and value > 5:
    player.unobserve_property('time-pos', handlePropertyChange)

player.observe_property('time-pos', handlePropertyChange)

tk.mainloop()

print('calling player.terminate.')
player.terminate()
print('terminate player.returned.')

Does the event loop stop working when the frame with the wid passed to mpv is destroyed?

@dfaker
Copy link
Contributor Author

dfaker commented May 30, 2020

Inspecting the output of _event_generator in this scenario shows that that final event generated is a 22 property change, event 1 for shutdown never makes it through the pipe and no NONE 0 event is sent either, the grim_reaper will therefore I think wait forever blocking exit.

@dfaker
Copy link
Contributor Author

dfaker commented Jun 1, 2020

If it is seemingly an issue that the shutdown command is never sent at least giving an option to pass daemonic_reaper to terminate as terminate(daemonic_reaper=True) would seem to make sense so that the reaper thread doesn't block exit when we know we don't care about it.

@jaseg jaseg added windows repro Reproduce to fix labels Jul 13, 2020
@jaseg
Copy link
Owner

jaseg commented Jul 19, 2020

There were some issues around multithreading and terminate(). I re-structured the code somewhat and now things should generally work much more reliably than before. I tried your first example and it seemed to work as expected after replacint terminate() with quit(). Here's my test code:

import tkinter as tk
import mpv

root=tk.Tk()

player = mpv.MPV(wid=str(int(root.winfo_id())), vo='x11')
player.play('test.webm')

def handlePropertyChange(name,value):
  print('property change',name,value)
  root.title(str(value))

player.observe_property('time-pos', handlePropertyChange)

tk.mainloop()

print('calling player.terminate.')
player.quit()
print('terminate player.returned.')

When using terminate() and closing the TK window while mpv is still playing, it seems either mpv or the tk mainloop try to do some teardown work on the X11 window that was already done by the other and some x11 lib barfs with an error message:

calling player.terminate.
X Error of failed request:  BadWindow (invalid Window parameter)
  Major opcode of failed request:  2 (X_ChangeWindowAttributes)
  Resource id in failed request:  0x1200002
  Serial number of failed request:  144
  Current serial number in output stream:  149

I'm not sure where that is coming from. If you can, just calling quit() and exiting the program should already shut down everything cleanly enough and should be a decent workaround.

@dfaker
Copy link
Contributor Author

dfaker commented Jul 20, 2020

Thanks! I'll pull these and report back.

@m44soroush
Copy link

I had the same issue in linux (Ubuntu 20.04) and pyqt5.
Terminating mpv player from another thread solved my problem.

class TerminateMPV(QRunnable): 
    def __init__(self,player): 
        super().__init__()
        self.player: mpv.MPV = player 
     
     def run(self) -> None: 
          self.player.terminate() 

Main thread (which mpv player is defined and started in):

...
def on_stop():
    self.player.stop()
    self.threadpool = QThreadPool()
    terminate_mpv_thread =  TerminateMPV(self.player)
    self.threadpool.start(terminate_mpv_thread)
...

@jaseg
Copy link
Owner

jaseg commented Aug 16, 2022

@m44soroush I'm glad that your workaround functions, but it would be interesting to see why this happened. Do you think you could get a stack trace of the hanging code? Python should spit out a stack trace if you just Ctrl+C, and you can get a stack trace of mpv's threads by running python inside gdb via gdb --args python3 the_script.py, then Ctrl+C'ing the hanging program, then running thread apply all bt in the gdb shell that opens. Alternatively, if you could post a hanging testcase I could reproduce the issue myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
repro Reproduce to fix windows
Projects
None yet
Development

No branches or pull requests

3 participants