Raspberry Piでインターネットラジオを作る #4
キーボード操作版ができました
勉強しながらチマチマ作っているRaspberry Piのインターネットラジオですが、なんとか希望通りの動きを実現できるようになってきました。
操作はまだキーボードからですが、これをGPIO経由でボタンに割り振るのは簡単そうです。実際、少し試しましたが、一応問題なく動いているようです。
仕様
・プレイリストを読み込み、インターネットラジオを再生
・nで次の局、pで前の局などコンソールで操作します
・再生中の局名(あれば曲名も)表示
・局や曲が替わると自動で表示が切り替わる
・起動は手動
・ボリューム操作は無し(アンプに任せる)
一番苦労したのは、GPIOあるいはコマンドの入力を待ちつつ曲名表示の変化に対応する方法です。
変化のイベントは、前回書いたmpc idleでうまく取れるのですが、アイドリング中はコマンドの受付けも止まってしまうため、次の局への移動などができません。
これではラジオの体をなしていないので、mpc idle用の別スレッドを作り、そっちでアイドリングさせておくようにしました。このあたりの理解が一番難しかったです。
本屋で立ち読みしたこの本、役に立ちました
(買えって・・・!?)
Linuxによる並行プログラミング入門
プログラム
初のLinux+Pythonによるプログラムなので、おかしなところもあると思いますが、こんな感じになっています。
ステータス取得用
まず、threadingを使った、ステータス取得用のスレッドを作るクラス。再生中のフラグ、self.runnningを立てておき、再生中はひたすらイベントを待っている・・・はず。
局名や曲名変更などのイベントが発生すると、ステータス表示のshowStatus()を呼びます。
class getCurrent(threading.Thread): def __init__(self): self.running = True threading.Thread.__init__(self) def isRun(self, running): self.runnning = running def run(self): while(self.runnning): idle = p.sendCmd('idle player') idle = idle.communicate() if idle !='': p.showStatus()
ここからメイン部分
self.stPositionは、再生局の位置ですが、チェック用+後で使うかな? と思って置いている程度で、あまり重要ではありません。
class Player(): def __init__(self): Player.update = getCurrent() self.stPosition = 3 #局の位置(スタート地点) self.setPlayer()
mpdのイニシャライズ的なやつ
repeatはonにしておかないと、最終位置の局→1番目の局、またはその逆への移動ができません。
def setPlayer(self): self.sendCmd('clear') self.sendCmd('repeat on') self.sendCmd('volume 75') self.loadPlaylist()
プレイリストの読み込み
現在、下の三行はいりません。stcountはプレイリストからの局数カウントですが未使用。(想定局数と読み込み局数が一致するかのチェックなどに・・・)
def loadPlaylist(self): self.sendCmd('load radio.m3u') stcount = self.sendCmd('playlist') stcount = stcount.stdout.readlines() stcount = len(stcount)
再生
__init__で設定したstPositionの位置から再生。
再生中フラグを立て、ステータス取得用のスレッドスタート
def startRadio(self): self.sendCmd('play ' + str(self.stPosition)) self.update.isRun(True) self.update.start()
コントロール用
self.sendCmd('play ' + str(position))はテスト中のおまけ
def ctrlRadio(self, position, control=''): if control == 'next': self.sendCmd('next') elif control == 'prev': self.sendCmd('prev') elif control == 'stop': self.update.isRun(False) self.sendCmd('stop') else: print('play ' + str(position)) self.sendCmd('play ' + str(position)) def showStatus(self): proc = self.sendCmd('status')
ステータスの取得と表示
mpc statusの戻りから、局名部分と曲名部分、それと局番号(プレイリストの並び順の番号)を切り出しています。
局によっては、曲名が無い場合があるので、line[0]、line[1]のように決め打ちしてしまうとエラーが出ます。なので単にあるだけループで表示させています。
ここはもう少しちゃんとしたいところです。
def showStatus(self): proc = self.sendCmd('status') try: lines = proc.stdout.readlines() #StationName + Track_Title line = lines[0].split('/ ') line = line[0][:-1].split(': ') #Station_Number s = lines[1].find('#')+1 e = lines[1].find('/') stationNum = lines[1][s:e] except: line = ['No_Station','No_title'] stationNum = 'None' print('----------- Now On Play --------------') print('Station# ' + str(stationNum)) for status in line: print (status) print('--------------------------------------')
コマンド送信(汎用)
def sendCmd(self, cmd): proc = subprocess.Popen('mpc ' + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return proc
全体
# -*- coding: utf-8 -*- # キーボード操作バージョン import subprocess import threading #import time #ここでは未使用 #import RPi.GPIO as GPIO #ここでは未使用 class getCurrent(threading.Thread): def __init__(self): self.running = True threading.Thread.__init__(self) def isRun(self, running): self.runnning = running def run(self): while(self.runnning): idle = p.sendCmd('idle player') idle = idle.communicate() if idle !='': p.showStatus() class Player(): def __init__(self): Player.update = getCurrent() self.stPosition = 3 #局の位置(スタート地点) self.setPlayer() def setPlayer(self): self.sendCmd('clear') self.sendCmd('repeat on') self.sendCmd('volume 75') self.loadPlaylist() def loadPlaylist(self): self.sendCmd('load radio.m3u') stcount = self.sendCmd('playlist') stcount = stcount.stdout.readlines() stcount = len(stcount) def startRadio(self): self.sendCmd('play ' + str(self.stPosition)) self.showStatus() self.update.isRun(True) self.update.start() def ctrlRadio(self, position, control=''): if control == 'next': self.sendCmd('next') elif control == 'prev': self.sendCmd('prev') elif control == 'stop': self.update.isRun(False) self.sendCmd('stop') else: print('play ' + str(position)) self.sendCmd('play ' + str(position)) def showStatus(self): proc = self.sendCmd('status') try: lines = proc.stdout.readlines() #StationName + Track_Title line = lines[0].split('/ ') line = line[0][:-1].split(': ') #Station_Number s = lines[1].find('#')+1 e = lines[1].find('/') stationNum = lines[1][s:e] except: line = ['No_Station','No_title'] stationNum = 'None' print('----------- Now On Play --------------') print('Station# ' + str(stationNum)) for status in line: print (status) print('--------------------------------------') def sendCmd(self, cmd): proc = subprocess.Popen('mpc ' + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return proc p = Player() p.startRadio() #テスト用 cmdkey = '' while True: cmdkey == '' cmdkey = raw_input('next:n prev:p r:reload status quit:q \n>> ') if cmdkey == 'n': p.ctrlRadio(int(p.stPosition), 'next') if cmdkey == 'p': p.ctrlRadio(int(p.stPosition), 'prev') if cmdkey == 'r': p.showStatus() if cmdkey == 'q': p.ctrlRadio(int(p.stPosition), 'stop') break; if cmdkey == '1': p.ctrlRadio(1, 'play')
てな感じです。
おかしなところ、改善点があったらぜひ教えていただきたいところです。
動かすとわかりますが、まず局名部分がURLで表示されます。次に局名を取得したところでまたイベントが発生し、局名に表示が切り替わります。この時に曲名が未取得だと、曲名取得時にもう一度イベントが発生します。
全部揃ってからの表示にしようかとも思いましたが、そんなに気にならないのでそのままにしてあります。
次回はLCDへの表示にチャレンジ!!
難しそうで、挫折しそうな感じですが・・・