aboutsummaryrefslogtreecommitdiff
path: root/pydora/mpg123.py
blob: 796a8abd57bde667efccaf006740b07167512bc2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import select

from .utils import iterate_forever, SilentPopen


class Player(object):
    """Remote control for an mpg123 process

    Starts and owns a handle to an mpg123 process then feeds commands to it to
    play pandora audio
    """

    def __init__(self, callbacks, control_channel):
        self._control_channel = control_channel
        self._control_fd = control_channel.fileno()
        self._callbacks = callbacks
        self._process = None
        self._ensure_started()

    def __del__(self):
        self._process.kill()

    def _ensure_started(self):
        """Ensure mpg123 is started
        """
        if self._process and self._process.poll() is None:
            return

        self._process = SilentPopen(
            ["mpg123", "-q", "-R", "--ignore-mime"])

        # Only output play status in the player stdout
        self._send_cmd("silence")

    def _send_cmd(self, cmd):
        """Write command to remote mpg123 process
        """
        self._process.stdin.write("{}\n".format(cmd).encode("utf-8"))
        self._process.stdin.flush()

    def stop(self):
        """Stop the currently playing song
        """
        self._send_cmd("stop")

    def pause(self):
        """Pause the player
        """
        self._send_cmd("pause")

    def _player_stopped(self, value):
        """Determine if player has stopped
        """
        return value.startswith(b"@P") and value.decode("utf-8")[3] == "0"

    def play(self, song):
        """Play a new song from a Pandora model

        Returns once the stream starts but does not shut down the remote mpg123
        process. Calls the input callback when the user has input.
        """
        self._callbacks.play(song)
        self._send_cmd("load {}".format(song.audio_url))

        while True:
            try:
                self._callbacks.pre_poll()
                self._ensure_started()

                readers, _, _ = select.select(
                    [self._control_channel, self._process.stdout], [], [], 1)

                for handle in readers:
                    value = handle.readline().strip()

                    if handle.fileno() == self._control_fd:
                        self._callbacks.input(value, song)
                    else:
                        if self._player_stopped(value):
                            return
            finally:
                self._callbacks.post_poll()

    def end_station(self):
        """Stop playing the station
        """
        raise StopIteration

    def play_station(self, station):
        """Play the station until something ends it

        This function will run forever until termintated by calling
        end_station.
        """
        for song in iterate_forever(station.get_playlist):
            try:
                self.play(song)
            except StopIteration:
                self.stop()
                return