1#!/usr/bin/env python
2""" pygame.examples.playmus
3
4A simple music player.
5
6   Use pygame.mixer.music to play an audio file.
7
8A window is created to handle keyboard events for playback commands.
9
10
11Keyboard Controls
12-----------------
13
14space - play/pause toggle
15r     - rewind
16f     - fade out
17q     - stop
18
19"""
20import sys
21
22import pygame as pg
23import pygame.freetype
24
25
26class Window(object):
27    """The application's Pygame window
28
29    A Window instance manages the creation of and drawing to a
30    window. It is a singleton class. Only one instance can exist.
31
32    """
33
34    instance = None
35
36    def __new__(cls, *args, **kwds):
37        """Return an open Pygame window"""
38
39        if Window.instance is not None:
40            return Window.instance
41        self = object.__new__(cls)
42        pg.display.init()
43        self.screen = pg.display.set_mode((600, 400))
44        Window.instance = self
45        return self
46
47    def __init__(self, title):
48        pg.display.set_caption(title)
49        self.text_color = (254, 231, 21, 255)
50        self.background_color = (16, 24, 32, 255)
51        self.screen.fill(self.background_color)
52        pg.display.flip()
53
54        pygame.freetype.init()
55        self.font = pygame.freetype.Font(None, 20)
56        self.font.origin = True
57        self.ascender = int(self.font.get_sized_ascender() * 1.5)
58        self.descender = int(self.font.get_sized_descender() * 1.5)
59        self.line_height = self.ascender - self.descender
60
61        self.write_lines(
62            "\nPress 'q' or 'ESCAPE' or close this window to quit\n"
63            "Press 'SPACE' to play / pause\n"
64            "Press 'r' to rewind to the beginning (restart)\n"
65            "Press 'f' to fade music out over 5 seconds\n\n"
66            "Window will quit automatically when music ends\n",
67            0,
68        )
69
70    def __enter__(self):
71        return self
72
73    def __exit__(self, exc_type, exc_val, exc_tb):
74        self.close()
75        return False
76
77    def close(self):
78        pg.display.quit()
79        Window.instance = None
80
81    def write_lines(self, text, line=0):
82        w, h = self.screen.get_size()
83        line_height = self.line_height
84        nlines = h // line_height
85        if line < 0:
86            line = nlines + line
87        for i, text_line in enumerate(text.split("\n"), line):
88            y = i * line_height + self.ascender
89            # Clear the line first.
90            self.screen.fill(
91                self.background_color, (0, i * line_height, w, line_height)
92            )
93            # Write new text.
94            self.font.render_to(self.screen, (15, y), text_line, self.text_color)
95        pg.display.flip()
96
97
98def show_usage_message():
99    print("Usage: python playmus.py <file>")
100    print("       python -m pygame.examples.playmus <file>")
101
102
103def main(file_path):
104    """Play an audio file with pg.mixer.music"""
105
106    with Window(file_path) as win:
107        win.write_lines("Loading ...", -1)
108        pg.mixer.init(frequency=44100)
109        try:
110            paused = False
111            pg.mixer.music.load(file_path)
112
113            # Make sure the event loop ticks over at least every 0.5 seconds.
114            pg.time.set_timer(pg.USEREVENT, 500)
115
116            pg.mixer.music.play()
117            win.write_lines("Playing ...\n", -1)
118
119            while pg.mixer.music.get_busy() or paused:
120                e = pg.event.wait()
121                if e.type == pg.KEYDOWN:
122                    key = e.key
123                    if key == pg.K_SPACE:
124                        if paused:
125                            pg.mixer.music.unpause()
126                            paused = False
127                            win.write_lines("Playing ...\n", -1)
128                        else:
129                            pg.mixer.music.pause()
130                            paused = True
131                            win.write_lines("Paused ...\n", -1)
132                    elif key == pg.K_r:
133                        if file_path[-3:].lower() in ("ogg", "mp3", "mod"):
134                            status = "Rewound."
135                            pg.mixer.music.rewind()
136                        else:
137                            status = "Restarted."
138                            pg.mixer.music.play()
139                        if paused:
140                            pg.mixer.music.pause()
141                            win.write_lines(status, -1)
142                    elif key == pg.K_f:
143                        win.write_lines("Fading out ...\n", -1)
144                        pg.mixer.music.fadeout(5000)
145                        # when finished get_busy() will return False.
146                    elif key in [pg.K_q, pg.K_ESCAPE]:
147                        paused = False
148                        pg.mixer.music.stop()
149                        # get_busy() will now return False.
150                elif e.type == pg.QUIT:
151                    paused = False
152                    pg.mixer.music.stop()
153                    # get_busy() will now return False.
154            pg.time.set_timer(pg.USEREVENT, 0)
155        finally:
156            pg.mixer.quit()
157    pg.quit()
158
159
160if __name__ == "__main__":
161    # Check the only command line argument, a file path
162    if len(sys.argv) != 2:
163        show_usage_message()
164    else:
165        main(sys.argv[1])
166