mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 07:22:23 -04:00
refactor sound code
this addresses a bug where mplayer was sometimes not being restarted when opening a new deck, and should remove the need for atexit or hacks in the GUI code to make sure mplayer is closed
This commit is contained in:
parent
ece9b2eccd
commit
d56bddea2d
1 changed files with 46 additions and 50 deletions
|
@ -8,7 +8,7 @@ Sound support
|
||||||
"""
|
"""
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
|
|
||||||
import re, sys, threading, time, subprocess, os, signal, atexit, errno
|
import re, sys, threading, time, subprocess, os, signal, errno
|
||||||
from anki.hooks import addHook, runHook
|
from anki.hooks import addHook, runHook
|
||||||
|
|
||||||
# Shared utils
|
# Shared utils
|
||||||
|
@ -123,6 +123,7 @@ mplayerQueue = []
|
||||||
mplayerManager = None
|
mplayerManager = None
|
||||||
mplayerReader = None
|
mplayerReader = None
|
||||||
mplayerCond = threading.Condition()
|
mplayerCond = threading.Condition()
|
||||||
|
mplayerClear = False
|
||||||
|
|
||||||
class MplayerReader(threading.Thread):
|
class MplayerReader(threading.Thread):
|
||||||
"Read any debugging info to prevent mplayer from blocking."
|
"Read any debugging info to prevent mplayer from blocking."
|
||||||
|
@ -140,33 +141,50 @@ class MplayerReader(threading.Thread):
|
||||||
class MplayerMonitor(threading.Thread):
|
class MplayerMonitor(threading.Thread):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
global mplayerClear
|
||||||
self.mplayer = None
|
self.mplayer = None
|
||||||
|
self.deadPlayers = []
|
||||||
while 1:
|
while 1:
|
||||||
mplayerCond.acquire()
|
mplayerCond.acquire()
|
||||||
while not mplayerQueue:
|
|
||||||
if not mplayerCond:
|
|
||||||
return
|
|
||||||
mplayerCond.wait()
|
mplayerCond.wait()
|
||||||
|
# clearing playing file if mplayer is running
|
||||||
|
if mplayerClear and self.mplayer:
|
||||||
|
self.mplayer.stdin.write("stop\n")
|
||||||
|
if mplayerQueue:
|
||||||
|
# ensure started
|
||||||
if not self.mplayer:
|
if not self.mplayer:
|
||||||
self.startProcess()
|
self.startProcess()
|
||||||
if self.mplayer != -1 and self.mplayer.poll() is not None:
|
# loop through files to play
|
||||||
self.mplayer.wait()
|
|
||||||
self.startProcess()
|
|
||||||
nextClears = False
|
|
||||||
while mplayerQueue:
|
while mplayerQueue:
|
||||||
item = mplayerQueue.pop(0)
|
item = mplayerQueue.pop(0)
|
||||||
if item is None:
|
if mplayerClear:
|
||||||
nextClears = True
|
mplayerClear = False
|
||||||
continue
|
|
||||||
if nextClears:
|
|
||||||
nextClears = False
|
|
||||||
extra = ""
|
extra = ""
|
||||||
else:
|
else:
|
||||||
extra = " 1"
|
extra = " 1"
|
||||||
cmd = 'loadfile "%s"%s\n' % (item, extra)
|
cmd = 'loadfile "%s"%s\n' % (item, extra)
|
||||||
self.mplayer.stdin.write(cmd)
|
self.mplayer.stdin.write(cmd)
|
||||||
|
# wait() on finished processes. we don't want to block on the
|
||||||
|
# wait, so we keep trying each time we're reactivated
|
||||||
|
def clean(pl):
|
||||||
|
if pl.poll() is not None:
|
||||||
|
pl.wait()
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
self.deadPlayers = [pl for pl in self.deadPlayers if clean(pl)]
|
||||||
mplayerCond.release()
|
mplayerCond.release()
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
if not self.mplayer:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.mplayer.stdin.write("quit\n")
|
||||||
|
self.deadPlayers.append(self.mplayer)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.mplayer = None
|
||||||
|
|
||||||
def startProcess(self):
|
def startProcess(self):
|
||||||
try:
|
try:
|
||||||
cmd = mplayerCmd + ["-slave", "-idle"]
|
cmd = mplayerCmd + ["-slave", "-idle"]
|
||||||
|
@ -187,8 +205,10 @@ def queueMplayer(path):
|
||||||
runHook("soundQueued")
|
runHook("soundQueued")
|
||||||
|
|
||||||
def clearMplayerQueue():
|
def clearMplayerQueue():
|
||||||
|
global mplayerClear
|
||||||
mplayerCond.acquire()
|
mplayerCond.acquire()
|
||||||
mplayerQueue.append(None)
|
mplayerClear = True
|
||||||
|
mplayerCond.notifyAll()
|
||||||
mplayerCond.release()
|
mplayerCond.release()
|
||||||
|
|
||||||
def ensureMplayerThreads():
|
def ensureMplayerThreads():
|
||||||
|
@ -200,37 +220,13 @@ def ensureMplayerThreads():
|
||||||
mplayerReader = MplayerReader()
|
mplayerReader = MplayerReader()
|
||||||
mplayerReader.daemon = True
|
mplayerReader.daemon = True
|
||||||
mplayerReader.start()
|
mplayerReader.start()
|
||||||
atexit.register(stopMplayer)
|
|
||||||
|
|
||||||
def stopMplayer(restart=False):
|
def stopMplayer():
|
||||||
if not mplayerManager:
|
if not mplayerManager:
|
||||||
return
|
return
|
||||||
mplayerCond.acquire()
|
mplayerManager.kill()
|
||||||
if mplayerManager.mplayer:
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
mplayerManager.mplayer.stdin.write("quit\n")
|
|
||||||
break
|
|
||||||
except OSError, e:
|
|
||||||
if e.errno != errno.EINTR:
|
|
||||||
# osx throws interrupt errors regularly, but we want to
|
|
||||||
# ignore other errors on shutdown
|
|
||||||
break
|
|
||||||
except IOError:
|
|
||||||
# already closed
|
|
||||||
break
|
|
||||||
except ValueError:
|
|
||||||
# already closed
|
|
||||||
break
|
|
||||||
if not restart:
|
|
||||||
mplayerManager.mplayer = -1
|
|
||||||
mplayerCond.notifyAll()
|
|
||||||
mplayerCond.release()
|
|
||||||
|
|
||||||
def stopMplayerOnce():
|
addHook("deckClosed", stopMplayer)
|
||||||
stopMplayer(restart=True)
|
|
||||||
|
|
||||||
addHook("deckClosed", stopMplayerOnce)
|
|
||||||
|
|
||||||
# PyAudio recording
|
# PyAudio recording
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
Loading…
Reference in a new issue