2

现在的情况

我正在编写一个小型 Python 程序来播放终端中 8tracks 的播放列表。

它由三部分组成,一个client.py使用 stdlib 的cmd模块,一个api.py使用 python-requests 访问 API 的player.py模块和一个在从属模式下创建 mplayer 子进程并向其发送命令的模块。

问题

到目前为止,这是可行的,问题是除了轮询子进程的标准输出之外,我没有其他方法可以判断歌曲是否已在 mplayer 中播放完毕。这意味着我必须观看该过程,以便在歌曲播放完毕后请求并开始播放列表的下一首歌曲。

问题是等待子进程会阻塞cmd模块的主循环。不过,我也不能简单地在单独的线程或进程中运行它,因为我必须共享对子进程的 stdout 的引用,而像这样的引用不能在进程之间共享。

可能的方法

我为此想到了不同的解决方案。我可以放入player.py一个单独的进程并通过队列发送文本命令,但这会使事情变得过于复杂。我可以创建一个 Twisted 应用程序,但 Twisted 相当大,我不知道从哪里开始。另外,我不希望在我的项目中有这样的依赖。

第三种解决方案是使用 Gevent。问题是我如何让它与cmd模块一起工作。据我了解 Gevent,我将不得不在我“等待”某事的每个地方屈服。在这种情况下,这将是在 HTTP 请求期间、在等待期间cmd.cmdloop()以及在子进程轮询之间的暂停期间。但是如何让cmd模块产生?某种子类或猴子补丁?

4

2 回答 2

1

从您的代码的外观来看,您可以在与 mplayer 通信时使用 pexpect。Pexpect(或期望)非常适合来回 stdio 通信。

于 2013-04-09T13:44:28.790 回答
-1

看来我想通了。我之前使用线程的所有尝试都是使用multiprocessing.dummy模块,它包装了threading模块,但在参数传递时表现有点不同——你不能传递引用。

直接使用threading,好像可以。我每次加载新曲目时都会启动一个后台线程来做到这一点。曲目播放完毕后,我向SIGUSR1客户端发送信号,客户端通过加载和播放新歌曲来处理它。

播放器.py

import os
import signal
import threading
from pipes import quote

class MPlayer(object):

    def __init__(self):
        self.process = Process(['mplayer',
            '-slave', '-idle',
            '-really-quiet', '-msglevel', 'global=6:cplayer=4', '-msgmodule',
            '-input', 'nodefault-bindings',
            '-cache', '1024',
        ], bufsize=1)
        self.write_lock = threading.Lock()

    # (...)

    def load(self, path):

        with self.write_lock:
            self.p.write('loadfile {}\n'.format(quote(path)))

        def wait_for_finish(process):
            # HERE: poll process for track ending with process.read()
            os.kill(os.getpid(), signal.SIGUSR1)

        t = threading.Thread(target=wait_for_finish, args=(self.process,))
        t.daemon = True
        t.start()

客户端.py

import cmd
import signal
from player import MPlayer

class PlayCommand(cmd.Cmd, object):

    def __init__(self, *args, **kwargs):

        # (...)

        self.p = MPlayer()
        signal.signal(signal.SIGUSR1, self._song_end_handler)

    def _song_end_handler(self, signum, frame):
        print('SIGUSR1!!!!!!!!!!111!1')
        # HERE: Fetch new track URL
        self.p.load()

尽管如此,如果有人认为他/她使用协程或事件找到了更好的解决方案,请随时用您的解决方案来回答。

于 2013-04-09T19:43:58.683 回答