forked from DesignSparkRS/RadioGlobe
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstreaming.py
117 lines (95 loc) · 3.49 KB
/
streaming.py
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
#! /usr/bin/python3
# Thanks to Peter Milne!
import time
import subprocess
import os
import signal
from pathlib import Path
import json
import re
import requests
from requests.exceptions import Timeout
import concurrent.futures
import logging
mixer_name = None
def set_volume(percent: int) -> int:
logging.info("Setting volume: %u", percent)
global mixer_name
if not mixer_name:
get_control = subprocess.run(['amixer', 'scontrols'], stdout=subprocess.PIPE)
control_match = re.match(r"Simple mixer control \'(.*)\'", str(get_control.stdout, encoding="utf-8").rstrip())
if control_match:
mixer_name = control_match.group(1)
if percent > 100:
percent = 100
elif percent < 0:
percent = 0
subprocess.run(['amixer', 'set', mixer_name, ('{}%').format(percent)])
# Return the percent volume, so that the caller doesn't have to handle capping to 0-100
return percent
def check_url(url) -> str:
"""Returns only good urls, or None"""
try:
response = requests.get(url, timeout=0.1)
except Timeout as e:
print(f'URL Timeout, {url}, {e}')
except Exception as e:
print(f'URL error, {url}, {e}')
else:
if response.status_code == requests.codes.ok:
return url
return None
def launch(audio, url) -> 'pid':
"""Play url returning the vlc pid"""
logging.info("Launching audio: %s, %s", audio, url)
radio = subprocess.Popen(['cvlc', '--aout', audio, url])
return radio.pid
class Streamer ():
"""A streaming audio player using vlc's command line"""
def __init__(self, audio, url):
logging.info("Starting Streamer: %s, %s", audio, url)
self.audio = audio
self.url = url
self.radio_pid = None
def play(self):
with concurrent.futures.ProcessPoolExecutor() as executor:
try:
# Play streamer in a separate process
ex = executor.submit(launch, self.audio, self.url)
logging.info("Pool Executor: %s, %s", self.audio, self.url)
except Exception as e:
logging.info("Pool Executor error: %s", e)
else:
# Get the vlc process pid so it can be stopped (killed!)
self.radio_pid = ex.result()
logging.info("Pool Executor PID: %s", self.radio_pid)
def stop(self):
"""Kill the vlc process. It's a bit brutal but it works
even for streams that send vlc into a race condition,
which is probably a bug in vlc"""
try:
os.kill(self.radio_pid, signal.SIGKILL)
logging.info("Killing Streamer PID: %s", self.radio_pid)
except Exception as e:
logging.info("Kill Streamer error: %s", e)
if __name__ == "__main__":
stations_file = 'stations.json'
audio = 'alsa' # or pulse
clip_duration = 10
with Path(stations_file).open(mode='r') as f:
stations = json.load(f)
# Get list of urls
url_list = [url['url'].strip() for k, v in stations.items() for url in v['urls']]
urls = list(set(url_list)) # De-duped list
print(f'{len(urls)} URLs')
while True:
for url in urls:
i = urls.index(url)
if not check_url(url):
print(f'Bad URL, {i}, {url}')
else:
print(f'Playing URL, {i}, {url}')
streamer = Streamer(audio, url)
streamer.play()
time.sleep(clip_duration)
streamer.stop()