1. TanaRadio
  2. 374 日記 | 物理ボタンでプレ..
374 日記 | 物理ボタンでプレイリスト再生を制御
2026-07-04 25:57

374 日記 | 物理ボタンでプレイリスト再生を制御

spotify apple_podcasts

物理ボタンでプレイリスト再生の制御ができるようになりました(フェーズ5D完了)。

*以下のtanaradio_5d.pyのコードは,修正済みのものです(この収録後も1回修正しました)。

フェーズ5D 詳細マニュアル

既存3ボタンでRSSプレイリストを操作する

0. 今回の目標

今回やることは、これです。

フェーズ5Cで playlist.m3u を作る
↓
mpvで playlist.m3u を再生する
↓
GPIOの3ボタンでmpvを操作する

3つのボタンの役割は、今回は次のようにします。

再生/一時停止ボタン:再生・一時停止
戻るボタン:前のエピソードへ
次へボタン:次のエピソードへ

mpvへの命令は次の3つです。

再生/一時停止:cycle pause
戻る:playlist-prev
次へ:playlist-next

構想メモでは、戻るボタンに「1回押し=先頭へ、2回押し=前へ、長押し=10秒戻る」という発展案があります。
ただし、5Dではまだ入れません。ここで欲張ると、ボタン判定の沼に入ります。沼は楽しいですが、今日はまだ岸辺でお茶を飲みます。

ステップ1:作業フォルダへ移動する

Raspberry Piでターミナルを開きます。

cd ~/tanaradio5

確認します。

pwd

次のような表示になればOKです。

/home/ユーザー名/tanaradio5

ステップ2:5Cまでのファイルを確認する
ls

少なくとも、次のようなファイルがあるはずです。

feeds.txt
make_playlist_5c.py
playlist.m3u

スクリプト名は、たなさんが5Cで作った名前に合わせてください。
以下では、5Cのスクリプト名を make_playlist_5c.py として進めます。

ステップ3:5Cのプレイリストをもう一度作る

まず、5Cのスクリプトを実行して、最新の playlist.m3u を作ります。

./make_playlist_5c.py

または、実行権限を付けていない場合は、

python3 make_playlist_5c.py

うまくいけば、たとえば次のような表示になります。

playlist.m3u を作成しました。
音声URL数: 12 件
完了しました。

確認します。

ls -l playlist.m3u

playlist.m3u が存在し、サイズが0でなければOKです。

中身も少し見ます。

head playlist.m3u

次のように、音声URLが入っていればOKです。

#EXTM3U
# エピソードタイトル
https://...

ステップ4:mpvだけで再生できるか確認する

ボタン操作に入る前に、まず普通に再生します。

mpv playlist.m3u

音が出ればOKです。

この状態では、キーボードで操作できます。

スペースキー:一時停止/再開
Enter:次へ
q:終了

確認できたら、q を押してmpvを終了します。

ここで音が出ない場合は、5Dには進まず、音声出力側を先に確認してください。

ステップ5:GPIOライブラリを確認する

今回はPythonからGPIOボタンを読むために gpiozero を使います。

まず確認します。

python3 -c "import gpiozero; print('gpiozero OK')"

次のように出ればOKです。

gpiozero OK

もしエラーが出たら、次を実行します。

sudo apt update
sudo apt install -y python3-gpiozero python3-lgpio

インストール後、もう一度確認します。

python3 -c "import gpiozero; print('gpiozero OK')"

ステップ6:GPIO番号を確認する

今回のコードでは、フェーズ4で使ってきた3つのGPIOを次のように使います。

GPIO17:再生/一時停止
GPIO27:戻る、つまり前のエピソードへ
GPIO22:次へ、つまり次のエピソードへ

配線はこれまで通り、基本形は次です。構想メモでも、ボタン入力には保険として抵抗を残す方針になっています。

GPIO ─ 1kΩ程度の抵抗 ─ ボタン ─ GND

つまり、ボタンを押すとGPIOがGNDにつながる形です。
Python側では pull_up=True にします。

ステップ7:5D用スクリプトを作る

新しいファイルを作ります。

nano tanaradio_5d.py

次のコードをそのまま貼り付けます。

#!/usr/bin/env python3
from gpiozero import Button
from signal import pause
import json
import os
import signal
import socket
import subprocess
import sys
import time
from pathlib import Path

# ===== 設定ここから =====
BASE_DIR = Path(__file__).resolve().parent
PLAYLIST_FILE = BASE_DIR / "playlist.m3u"
SOCKET_PATH = "/tmp/tanaradio-mpv.sock"

# フェーズ4で使った既存3ボタンのGPIO番号
PIN_PLAY_PAUSE = 17   # 再生/一時停止
PIN_BACK = 27         # 戻る:前のエピソードへ
PIN_NEXT = 22         # 次へ:次のエピソードへ
# ===== 設定ここまで =====

mpv_process = None


def check_playlist():
    if not PLAYLIST_FILE.exists():
        print(f"{PLAYLIST_FILE} が見つかりません。")
        print("先にフェーズ5Cのスクリプトを実行して playlist.m3u を作ってください。")
        sys.exit(1)

    text = PLAYLIST_FILE.read_text(encoding="utf-8", errors="ignore").strip()
    if not text:
        print(f"{PLAYLIST_FILE} は空です。")
        sys.exit(1)


def start_mpv():
    global mpv_process

    if os.path.exists(SOCKET_PATH):
        os.remove(SOCKET_PATH)

    print("mpvを起動します。")
    print(f"プレイリスト: {PLAYLIST_FILE}")

    mpv_process = subprocess.Popen(
        [
            "mpv",
            "--no-video",
            f"--input-ipc-server={SOCKET_PATH}",
            str(PLAYLIST_FILE),
        ],
        cwd=str(BASE_DIR),
    )

    # mpvのIPCソケットができるまで少し待つ
    for _ in range(50):
        if os.path.exists(SOCKET_PATH):
            print("mpv操作用ソケットを確認しました。")
            return
        time.sleep(0.1)

    print("mpv操作用ソケットが見つかりません。")
    print("mpvが起動できていない可能性があります。")


def send_mpv_command(command):
    payload = json.dumps({"command": command}).encode("utf-8") + b"\n"

    try:
        with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
            client.settimeout(1.0)
            client.connect(SOCKET_PATH)
            client.sendall(payload)

            # mpvからの返事を少し読む。
            # これをしないと、mpv側に Broken pipe が出ることがある。
            try:
                client.recv(4096)
            except socket.timeout:
                pass

    except FileNotFoundError:
        print("mpvのソケットが見つかりません。mpvが終了している可能性があります。")
    except ConnectionRefusedError:
        print("mpvに接続できませんでした。")
    except Exception as e:
        print("mpvへの命令送信でエラーが出ました。")
        print(e)


def play_pause():
    print("ボタン: 再生/一時停止")
    send_mpv_command(["cycle", "pause"])


def back_episode():
    print("ボタン: 戻る:前のエピソードへ")
    send_mpv_command(["playlist-prev"])


def next_episode():
    print("ボタン: 次へ")
    send_mpv_command(["playlist-next"])


def setup_buttons():
    print("GPIOボタンを準備します。")
    print(f"再生/一時停止: GPIO{PIN_PLAY_PAUSE}")
    print(f"戻る: GPIO{PIN_BACK}")
    print(f"次へ: GPIO{PIN_NEXT}")

    play_button = Button(PIN_PLAY_PAUSE, pull_up=True, bounce_time=0.08)
    back_button = Button(PIN_BACK, pull_up=True, bounce_time=0.08)
    next_button = Button(PIN_NEXT, pull_up=True, bounce_time=0.08)

    play_button.when_pressed = play_pause
    back_button.when_pressed = back_episode
    next_button.when_pressed = next_episode

    # main側で保持するために返す
    return play_button, back_button, next_button


def cleanup(signum=None, frame=None):
    global mpv_process

    print("\n終了します。")

    if mpv_process is not None and mpv_process.poll() is None:
        mpv_process.terminate()
        try:
            mpv_process.wait(timeout=5)
        except subprocess.TimeoutExpired:
            mpv_process.kill()

    if os.path.exists(SOCKET_PATH):
        try:
            os.remove(SOCKET_PATH)
        except OSError:
            pass

    sys.exit(0)


def main():
    signal.signal(signal.SIGINT, cleanup)
    signal.signal(signal.SIGTERM, cleanup)

    check_playlist()
    start_mpv()

    # 重要:
    # setup_buttons() の戻り値を変数に入れて保持する。
    # これをしないと、環境によってはボタンが反応しなくなることがある。
    buttons = setup_buttons()

    print("準備完了です。")
    print("3つの物理ボタンで操作できます。終了は Ctrl + C です。")

    pause()


if __name__ == "__main__":
    main()

保存します。

Ctrl + O
Enter
Ctrl + X

ステップ8:実行権限を付ける
chmod +x tanaradio_5d.py

ステップ9:5Dを実行する
./tanaradio_5d.py

うまくいくと、次のような表示になります。

mpvを起動します。
プレイリスト: /home/ユーザー名/tanaradio5/playlist.m3u
mpv操作用ソケットを確認しました。
GPIOボタンを準備します。
再生/一時停止: GPIO17
戻る: GPIO27
次へ: GPIO22
準備完了です。
3つの物理ボタンで操作できます。終了は Ctrl + C です。

この状態で、音声が流れ始めます。

ステップ10:3つのボタンを試す

10-1. 再生/一時停止ボタン

GPIO17につながっているボタンを押します。

ターミナルに次のように出ればOKです。

ボタン: 再生/一時停止

音声が一時停止します。
もう一度押すと再開します。

10-2. 次へボタン

GPIO22につながっているボタンを押します。

ボタン: 次へ

次のエピソードへ進めばOKです。

10-3. 戻るボタン

GPIO27につながっているボタンを押します。

ボタン: 戻る:前のエピソードへ

前のエピソードへ戻ればOKです。

ただし、プレイリストの最初のエピソードを再生しているときは、それ以上前へ戻れないので、動きがわかりにくい場合があります。
その場合は、先に「次へ」を1〜2回押してから「戻る」を試してください。

ステップ11:終了する

終了するときは、ターミナルで次を押します。

Ctrl + C

次のように出ればOKです。

終了します。

mpvも一緒に終了します。

ステップ12:今回の成功条件

フェーズ5Dの成功条件は、次の4つです。

1. フェーズ5Cの playlist.m3u が作れている
2. tanaradio_5d.py を実行すると mpv が起動する
3. 再生/一時停止ボタンで pause / resume ができる
4. 戻る・次へボタンで playlist-prev / playlist-next ができる

ここまでできれば、5Dは成功です。

これはかなり大きいです。
5Cまでは「RSSから音声を集める仕組み」でしたが、5Dでついに「物理ボタンで声の流れを操る装置」になります。ラジオ感が、ぐっと前に出てきます。

#声日記 #TanaRadioPi

サマリー

TanaRadio Pi製作シリーズのフェーズ5Dでは、既存の3つの物理ボタンでRSSプレイリストを操作することを目指しました。まず作業環境を整え、過去3日間のエピソードを含むプレイリストを作成し、音声再生を確認。次にGPIOライブラリとボタン配線をチェックし、ChatGPTが生成したスクリプトを導入しましたが、当初ボタンが機能しませんでした。ChatGPTの助けを借りてコードの一部を修正した結果、再生・一時停止、戻る、次へといった全てのボタン機能が正常に動作することを確認し、フェーズ5Dは無事成功しました。これにより、物理ボタンで音声の流れを操る「ラジオ感」が実現し、次フェーズ5EではRSSのグループ化「棚」の導入が予定されています。

フェーズ5Dの目標設定
今日はTanaRadio Pi製作シリーズフェーズ5Dですね。
今日の目標は既存3ボタンでRSSプレイリストを操作するというものです。
3ボタンで1つのエピソードの再生をコントロールすることはもうできました。
それからプレイリストを作って、これは複数のRSSですね。
これを登録しておいて、過去3日間のエピソードを古い順に再生するプレイリストを作るということもできましたので、
今回はそのボタンでプレイリストの再生をコントロールするということができるのが目標です。
作業準備とプレイリスト作成
まず1番目は何をするかというと、まず作業フォルダへ移動するということで、
TanaRadio Pi、Raspberry Piのターミナルで作業用のフォルダに移動しました。
そうしましたら、そこにあるファイルをもう一度確認しますが、これまでの作業で作ったファイルがそこに並んでいます。
これも問題ないですね。
そうしましたらば、プレイリストを前回やりました5Cでやりましたプレイリストをもう1回作ると、
過去3日間ですから1日経ちますと中身が変わりますので、もう一度そのプレイリストを作成するPythonのプログラムを実行します。
これを実行しましたらば、過去3日分は4件ということで、4件のプレイリストができたようです。
では、このプレイリストの中身を見てみますかね。
headというコマンドを変えます。
そうしますと、NHKのラジオ、マイあさでやっている健康ライフとワールドリポートのエピソードがですね、
7月2日分2つと、7月3日分2つ。
7月4日分、4日ってのは今日で土曜日なんですが、これはないんですね。
マイあさが、このコーナーは土曜日やってないってことですかね。
これでいいわけですね。
では、これを再生してみましょう。音が出るかどうか。
音が出ましたので、OKですね。
GPIOライブラリとボタン配線の確認
そうしましたら、次はですね、GPIOライブラリを確認するということで、
GPIOボタンというのを使うために必要なプログラムでしょうか、ライブラリなのかな。
これをターミナルで確認するということです。
この確認するためのコマンドをコピー&ペーストでターミナルに貼り付けてエンターで実行しますと、OKと出ましたので大丈夫のようです。
次はですね、GPIOボタンを確認するということで、
GPIO番号、これはRaspberry PiにGPIOという入出力用のピンが40本立っているんですが、
それに番号が付いていて、その番号、どの番号のピンを使うかということを確認するということですね。
これは前に設定したもので、17番が再生、一時停止、27番が戻る、22番が次へというふうになっているということで、配線は変えていませんので、
どうなっているか、ボタンスイッチと線がちゃんとつながっているか確認しますが、
まず17番、これは再生、一時停止でちゃんとつながっています。
次が27番、これが戻る方ですね、戻る。
で、22番が進むということで、ちゃんとつながっていることを確認しました。
変えていませんので、大丈夫なはずなわけですが。
スクリプト作成と初期動作不良
では、いよいよこのボタンで再生状態をコントロールするためのスクリプトを作ります。
新しいファイルで、tana.radio_5d.pyというスクリプトの書いてあるファイルをnanoで作ります。
今、エディターが開きました。
そうしましたら、ChatGPTが書いてくれたコードをそのまま漏れなく貼り付けます。
これはかなり長いです。
一応最初から最後まで反転させましたので、コピーをしてペーストです。
で、保存ですね。
はい、できました。
次はこのファイルに実行権限を付けるということで、これも何度もやっているものですが、
はい、できましたのでこれを実行します。
はい、実行しますと音が聞こえるわけですよね。
はい、これでボタンがちゃんと動くかどうかを確認します。
はい、やってみましたが、物理ボタンでうまくコントロールできませんでした。
これはどうしてでしょうか。
おかしいですね。もう一度やってみましょうかね。
はい、やっぱりうまくいきません。
物理ボタンではうまくコントロールできませんでした。
よくあるエラーと対処という風にChatGPTが書いてくれてますのでそこを見ます。
ChatGPTによる問題解決とコード修正
ボタンを押しても何も起きない。これに相当しますね。
まずターミナルに次のような表示が出るか見ます。
ボタン、次へ。こういう表示が出ませんね。
線がちゃんと繋がっているかどうかもう一回しっかり差し込んで確認をした上でもう一回やってみます。
出ませんね。困ったな。
どうしましょうか。
これはもうどうしようもないのでチャッピーのお助けをするしかないですね。
どこでつまづいたかというと、ステップ10というところですね。
ステップ10でボタンを押しましたが反応しません。
チャッピーに行ってみました。
コード側の可能性が高いですという風に言ってますね。
なので、エディターですね。
エディターで開いてコードを書き換えてくださいと言ってますね。
一行だけですね。ちょっと書き換えたらどうかということです。
じゃあこれをやってみましょう。
まずはプログラムを開きました。
そうしましたら、setup_buttons()というのがないですね。どうしたんだろう。
書き換えなきゃいけない部分が下の方にあるということで探しましたら確かにありました。
setup_buttons()というのがあって、
これの前にbuttons = というのを付けないといけないということなんですね。
ですのでそれを今書き換えます。
コピー&ペーストで、コピー&ペーストする場所。
カーソルを持っていかないといけない。
ペーストできましたので、これで保存かな。
これを保存ですね。
保存しました。
物理ボタンによる再生制御の成功
そしたらまた実行ですね。
ボタンを押したらば、止まりましたね。
今一時停止のボタンを押したんですがもう一回押します。
次は戻るボタンを押します。
戻るボタンはこれが一番最初なので効かないですね。
次は進むボタンです。
進んだようです。
これをまた戻るボタンで戻します。
今一時停止ボタンで止まりました。
ということでボタン3つですね。
それぞれの機能をちゃんと果たすことが確認できましたね。
よかったです。
フェーズ5D完了と次フェーズ5Eの展望
これでうまくいったということですね。
問題解決しましたので元のマニュアルに戻りまして、
最後の成功条件の確認です。
4つありますが、1番目。
フェーズ5Cのplaylist.m3uが作れている。
くれました。
2番目。
TanaRadio_5d.pyを実行するとmpvが起動する。
ちょっと意味がわかりませんが、
要するに再生ができるということですね。
3番目。
再生一時停止ボタンでpause / resumeができました。
4番目。
戻る・次へボタンでplaylist-prev、playlist-nextができる。
戻ると進むが操作できるということですが、
これもできましたので、
全部できてフェーズ5Dは成功ということになります。
最後にチャッピーはこう書いています。
これはかなり大きいです。
5CまではRSSから音声を集める仕組みでしたが、
5Dでついに物理ボタンで声の流れを操る装置になります。
ラジオ感がぐっと前に出てきますということで。
確かにボタンでいろいろと操作できるというのは、
ラジオっぽくなってきたなという感じがしますね。
それで、次は5Eですけど、
5Eというのは何をするのかと言いますと、
いよいよ棚を導入します。
この棚というのは本棚の棚ですね。
一応、私はこのRSSの一つのまとまりですね。
これを棚というふうに、英語で言うとシェルフというふうに言うようにしました。
つまり本棚に本が並んでいるというイメージですね。
一つ一つのRSSが一つの本ということになるわけですが、
そのRSSがいくつも入っているものが棚に並んでいるということで、
その棚を選ぶということができるようにしたいんですね。
つまりフォローしているもののグループがいくつかあって、
それを選べるようにするということなんですが。
5Eでは、そのRSSの集合をいくつかのファイルに書いておいて、
そのファイル、棚のファイルですね。
これをターミナル上で切り替えて再生ができるようにするということのようです。
次にはそれをボタンで切り替えるということになるわけですが、
その一歩手前のステップが5Eということになるようですね。
次はそれをやりたいと思います。
それではまた。
25:57

コメント

スクロール