-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathmpradio.py
More file actions
executable file
·210 lines (184 loc) · 8.02 KB
/
mpradio.py
File metadata and controls
executable file
·210 lines (184 loc) · 8.02 KB
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/usr/bin/env python3
import signal
import os
import threading
import time
from encoder import Encoder
from configuration import config # must be imported before all other modules (dependency)
from bluetooth_remote import BtRemote
from bluetooth_player import BtPlayer
from bluetooth_daemon import get_connected_device
from bluetooth_player_lite import BtPlayerLite
from fm_output import FmOutput
from analog_output import AnalogOutput
from storage_player import StoragePlayer
from control_pipe import ControlPipe
from media import MediaControl, MediaInfo
import platform
from subprocess import call, Popen, PIPE
import json
class Mpradio:
control_pipe = None
bt_remote = None
gpio_remote = None
remote_event = None
remote_msg = None
media_control_methods = None
media_info_methods = None
remotes_termination = None
player = None
encoder = None
output = None
def __init__(self):
super().__init__()
signal.signal(signal.SIGUSR1, self.handler)
signal.signal(signal.SIGINT, self.termination_handler)
self.remote_msg = dict()
self.remotes_termination = threading.Event()
self.remote_event = threading.Event() # Event for signaling control thread(s) events to main thread
self.reply_event = threading.Event()
self.control_pipe = ControlPipe(self.remote_event, self.remote_msg)
self.bt_remote = BtRemote(self.remote_event, self.remote_msg)
# TODO: maybe refractor into player_methods and always eval()?
self.media_control_methods = [f for f in dir(MediaControl)
if not f.startswith('_') and callable(getattr(MediaControl, f))]
self.media_info_methods = [f for f in dir(MediaInfo)
if not f.startswith('_') and callable(getattr(MediaInfo, f))]
self.player = StoragePlayer()
self.encoder = Encoder()
if config.get_settings()["PIRATERADIO"]["output"] == "fm":
self.output = FmOutput()
else:
self.output = AnalogOutput()
def handler(self, signum, frame):
print("received signal", signum)
def termination_handler(self, signum, frame):
print("stopping threads and clean termination...")
self.remotes_termination.set()
self.player.stop()
self.encoder.stop()
self.output.stop()
quit(0)
def run(self):
self.encoder.run()
self.output.run()
self.player.set_out_stream(self.output.input_stream)
self.player.run()
self.bt_remote.run()
self.control_pipe.listen()
if platform.machine() != "x86_64":
from gpio_remote import GpioRemote
self.gpio_remote = GpioRemote(self.remote_event, self.remote_msg)
self.gpio_remote.run()
threading.Thread(target=self.check_remotes).start()
'''
# play stream
while True:
self.player.ready.wait()
data = self.player.output_stream.read()
if data is not None:
self.output.ready.wait()
self.output.input_stream.write(data)
t = 0.005
wait_time = ((len(data)/4)/44.1) * 0.001
# print("just read", len(data), "bytes. sleeping for", wait_time, "-", t)
if wait_time >= t:
wait_time -= t
time.sleep(wait_time)
# print("advancing playhead...")
'''
def check_remotes(self):
while not self.remotes_termination.is_set():
time.sleep(0.02)
if self.remote_event.is_set():
self.remote_event.clear()
try:
cmd = self.remote_msg["command"]
except KeyError:
continue
if cmd[0] in self.media_control_methods:
exec("self.player."+cmd[0]+"()")
elif cmd[0] in self.media_info_methods:
result = eval("self.player."+cmd[0]+"()")
if self.remote_msg["source"] == "bluetooth":
self.bt_remote.reply(result)
elif cmd[0] == "bluetooth":
if cmd[1] == "attach":
mac = get_connected_device()
if self.player.__class__.__name__ == "BtPlayerLite" or mac is None:
continue
tmp = BtPlayerLite(mac)
self.player.stop()
self.player = tmp
self.player.set_out_stream(self.output.input_stream)
self.player.run()
print("bluetooth attached")
elif cmd[1] == "detach":
if self.player.__class__.__name__ != "BtPlayerLite":
continue
self.player.stop()
self.player = StoragePlayer()
self.player.set_out_stream(self.output.input_stream)
self.player.run()
# self.player.ready.wait()
print("bluetooth detached")
elif cmd[0] == "system":
if cmd[1] == "poweroff":
self.player.pause()
call(["sudo", "poweroff"])
elif cmd[1] == "reboot":
self.player.pause()
call(["sudo", "reboot"])
elif cmd[1] == "wifi-switch" and cmd[2] == "status":
if self.remote_msg["source"] == "bluetooth":
result = Popen(cmd[1:], stdout=PIPE).stdout.read().decode()
self.bt_remote.reply(result)
else:
call(["sudo"] + cmd[1:])
elif cmd[0] == "play":
if self.player.__class__.__name__ != "StoragePlayer":
continue
what = json.loads(self.remote_msg["data"])
self.player.play_on_demand(what)
elif cmd[0] == "playlist":
try:
with open(config.get_playlist_file()) as file: # TODO: implement in player
pl = str(json.load(file))
self.bt_remote.reply(pl)
except FileNotFoundError:
pass
elif cmd[0] == "library":
try:
with open(config.get_library_file()) as file:
lib = str(json.load(file))
self.bt_remote.reply(lib)
except FileNotFoundError:
pass
elif cmd[0] == "config":
if cmd[1] == "get":
self.bt_remote.reply(config.to_json())
elif cmd[1] == "set":
cfg = self.remote_msg["data"]
self.apply_configuration(cfg)
elif cmd[1] == "reload": # TODO: remove. this is for testing purposes only
self.reload_configuration()
else:
print("unknown command received:", cmd)
self.remote_msg.clear() # clean for next usage
# Remote checker termination
self.control_pipe.stop()
self.bt_remote.stop()
if self.gpio_remote is not None:
self.gpio_remote.stop()
def apply_configuration(self, cfg):
config.load_json(cfg)
self.reload_configuration()
def reload_configuration(self):
self.player.pause() # player must be paused/silenced to avoid audio feed loop on fm transmission
self.encoder.reload() # encoded must be reloaded to avoid broken pipe
self.output.check_reload() # don't restart output if not needed
self.player.resume()
if __name__ == "__main__":
print('mpradio main PID is:', os.getpid())
mpradio = Mpradio()
mpradio.run()