Radio
Któż nie lubi posłuchać radia, jedni słuchają dla muzyki, inni dla informacji, a jeszcze inni po prostu włączają radio jako tło. W dzisiejszych czasach nie jesteśmy już ograniczeni tylko do stacji radiowych, które odbiera nasz odbiornik fm, możemy słuchać audycji z całego świata. W niniejszym artykule pokażę jak zbudować swój własny odbiornik radiowy działający na mini-komputerze Raspberry Pi, sterowany przez przeglądarkę z dowolnego urządzenia w sieci wi-fi. Sercem systemu będzie aplikacja w języku Python.
Czego użyjemy
Na początek wystarczy Raspberry Pi z systemem Raspbian i podłączone do niego słuchawki, na końcu artykułu przedstawię możliwości rozbudowy naszego radia.
Kod źródłowy napiszemy w języku Python 3, do stworzenia prostego interfejsu webowego wykorzystamy framework Flask, zaś za odtwarzanie dźwięku odpowiadać będzie aplikacja Music Player Daemon (mpd) oraz prosty interfejs konsolowy o nazwie mpc. Lista stacji radiowych będzie pobierana z pliku YAML.
Instalacja zależności
Aby zainstalować wszystkie potrzebne narzędzia w systemie Ubuntu lub Raspbian należy wykonać polecenia:
sudo apt-get update sudo apt-get install mpd mpc python-pip pip install -U Flask oyaml
Kodujemy
Stwórzmy katalog w którym będzie się znajdował nasz projekt a w nim plik main.py
.
W pliku main.py
utwórzmy klasę i stwórzmy jej obiekt:
class Radio: def __init__(self): None radio = Radio()
Następnie dodajemy metodę odpowiedzialną za komunikację z daemonem mpd:
def mpcCommand(self, cmd): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) return p.stdout.read()
Możemy już w tym momencie spróbować odtworzyć pierwszy stream dodając w metodzie __init__() kod:
self.mpcCommand(["mpc", "add", "http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-einws"]) self.mpcCommand(["mpc", "play"])
Zaimportujemy też moduł subprocess:
import subprocess
Po wykonaniu aplikacji stream zacznie się odtwarzać, a aplikacja się zakończy. Aby zakończyć odtwarzanie musimy w konsoli systemowej wykonać polecenie mpc stop
.
Jeśli wykonamy polecenie mpc playlist
, zobaczymy że do naszej playlisty została dodana stacja „http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-einws”. W celu uproszczenia komunikacji naszego skryptu z mpd, przed każdym rozpoczęciem odtwarzania będziemy czyścić listę i dodawać wybraną przez nas stację. Implementacja sprowadza się do dodania linii self.mpcCommand(["mpc", "clear"])
przed innymi wywołaniami self.mpcCommand()
. Dodatkowo na końcu naszej metody __init__(self) możemy dodać:
while True: None
Spowoduje to, że aplikacja będzie uruchomiona dopóki nie przerwiemy jej działania, na przykład kombinacją klawiszy CTRL+C. Przerwanie pracy aplikacji nie spowoduje jednak przerwania odtwarzania strumienia przez demon mpd, dlatego musimy dodać metodę exitHandler()
oraz zadeklarować jej wywołanie po zakończeniu aplikacji.
Dodajemy moduł atexit:
import atexit
oraz metodę:
def exitHandler(self): self.mpcCommand(["mpc", "stop"]) self.mpcCommand(["mpc", "clear"])
Jak widać metoda zatrzymuje odtwarzanie i czyści playlistę. Aby python wykonał tą metodę po zakończeniu aplikacji, należy metodzie __init__() dodać linijkę:
atexit.register(self.exitHandler)
Linia ta musi być umieszczona przed pętlą while True
.
Kolejnym krokiem będzie wczytanie listy stacji z pliku YAML. Przykładowy plik znajduje się tutaj, natomiast jego wczytanie wygląda następująco:
stationsFile = open("stations.yml", "r") data = yaml.load(stationsFile) self.stations = [] for k in data: self.stations.append({ "name": k, "url": data[k] })
Powyższy kod wczytuje zawartość pliku, ładuje go do zmiennej data
a następnie tworzy listę słowników self.stations
. Aby kod zadziałał musimy dodać jeszcze jeden moduł:
import oyaml as yaml
Użyłem oyaml zamiast PyYaml, ponieważ PyYaml nie zawsze wczytuje listę stacji w takiej kolejności jaką ustaliliśmy w pliku .yml, oyaml rozwiązuje ten problem.
Następnie usuwamy pętlę while True
a zamiast niej wstawiamy kod:
self.initWebUi()
Jest to wywołanie metody, której definicja wygląda następująco:
def initWebUi(self): app = Flask(__name__, template_folder="template") @app.route("/", methods=["GET", "POST"]) def control_page(title="Radio control"): if request.method == "POST": if request.form["submit"] == "Play": self.currentStationUrl = str(request.form["station"]) self.mpcCommand(["mpc", "clear"]) self.mpcCommand(["mpc", "add", self.currentStationUrl]) self.mpcCommand(["mpc", "play"]) elif request.form["submit"] == "Stop": self.mpcCommand(["mpc", "stop"]) return render_template("/control.html", title=title, stations=self.stations, currentStationUrl=self.currentStationUrl) app.run(host="0.0.0.0", port=1234)
Dodajemy też moduły:
from flask import Flask from flask import render_template from flask import request
Metoda initWebUi() inicjuje framework flask i definuje prosty routing wewnątrz którego jest akcja control_page
wyświetlająca szablon z pliku template/control.html
oraz obsługująca komendy „Play” i „Stop”. Plik szablonu znajduje się tutaj.
Dodatkowo w metodzie __init__() musimy dodać:
self.currentStationUrl = None
W tym momencie możemy otworzyć w przeglądarce adres http://0.0.0.0:1234/ i zobaczymy prosty panel pozwalający na wybór stacji i rozpoczęcie lub zatrzymanie odtwarzania.
Pełny kod aplikacji umiesciłem się w repozytorium na GitHub https://github.com/jakubthedeveloper/PythonInternetRadio, znajduje się w nim kod z artykułu z kilkoma usprawnieniami, na przykład możliwość podania w argumentach wywołania skryptu adresu i portu na którym ma działać nasze webowe UI. W pliku README.md znajdziemy również informację jak uruchomić projekt na mini-komputerze Raspberry Pi.
Dodatkowo kod w repozytorium został zrefaktoryzowany, poszczególne funkcje zostały przeniesione do odpowiednich klas, dodałem też unit testy.
Rozwój projektu
W momencie gdy na słuchawkach podpiętych do Raspberry Pi słyszymy stację radiową wybraną w naszym webowym interfejsie mamy doskonały punkt wyjścia, żeby zbudować coś ciekawszego. Kilka pomysłów:
- Możemy użyć zewnętrznego głośnika, np. połączonego z Raspberry przez bluetooth
- Możemy użyć nakładki wzmacniacza audio dla Raspberry Pi i podłączyć dowolny głośnik
- Możemy użyć starego radioodbiornika, z którego wymontujemy całą elektronikę a w jej miejsce wstawimy Raspberry i moduł wzmacniacza podłączony do głośnika, który służył w wykorzystanym urządzeniu.
- Możemy do naszej konstrukcji wstawić powerbank i uczynić ją bardziej przenośną
- Możemy zamienić interfejs webowy na bluetooth i stworzyć interfejs mobilny na Androida lub iOS, w ten sposób naszym radiem będziemy mogli sterować nawet tam gdzie nie mamy dostępu do sieci Wi-fi
- Możemy stworzyć fizyczny interfejs dla naszego radia, zawierający takie elementy jak: przyciski do wywoływania zdefiniowanych stacji radiowych, wyświetlacz wyświetlający nazwę aktualnej stacji, kontrolę głośności (można to zrobić przez komendę `mpc volume [+-]<num>` lub sprzętowo), kontrolę tonów niskich/wysokich.
- Można zbudować old-schoolowo wyglądające urządzenie, na przykład używając obudowy i głośnika z radioodbiornika Unitra Kasprzak
- Można zbudować futurystycznie wyglądające urządzenie, na przykład budując panel przypominający kokpit statku kosmicznego
Zachęcam do eksperymentowania i podsyłania waszych realizacji.
Aktualizacja
Już po napisaniu powyższego artykułu dokonałem wielu modyfikacji, które znajdziecie w repozytorium projektu. Jedną z nich jest modyfikacja interfejsu webowego z wykorzystaniem Bootstrap i jQuery:
Przykład użycia
Moje radyjko zbudowane z wykorzystaniem rozwiązania przedstawionego w niniejszym artykule opiera się na następującej architekturze: