PythonでMP3再生(just-playbackから非同期の話へ)
(初心者の試行錯誤の記録です。すぐ役に立つような整理された内容ではありません)
の続きから。
just-playback使ってみる
just-playback 0.1.8(2024年3月11日最新バージョン)
まずは pip install just_playback
のなかでやった、playsound3では、テストコード
from playsound3 import playsound
playsound("test.mp3")
が無事に鳴ったのですが、まず、これと同じことが、just_playbackでも、同じくらい簡単にできるかどうか。
from just_playback import Playback
playback = Playback()
playback.load_file("test.mp3")
playback.play()
あれ、鳴りません。ちょっと嫌な予感がして、次のように書き加えてみると
import time
from just_playback import Playback
playback = Playback()
playback.load_file("test.mp3")
playback.play()
time.sleep(5)
・・・鳴りました。
そもそもtime.sleepの記述が気持ち悪いせいで、pygameをやめてplaysound3にしようとしていたのに、just_playbackにしたらまたsleepが必要?なんか嫌です。
Copilot先生に恨み言を言っちゃいます。
なんか、sleep以上に重くなりそうな悪寒(?)。
このCopilot先生の説明で気になることが。「再生が非同期で行われるため、スクリプトが終了してしまう」ために打ち切られてしまう、ということなら、tkinterのGUIから動かしている場合はmainloopはまわっていてスクリプトは終了していないのだから、sleepなしでも鳴るってことじゃない?
そう思って(実際には誤解)、CustomTkinterの復習もかねて試してみました。
CustomTkinterと組み合わせてみる
import customtkinter as ctk
from just_playback import Playback
class App(ctk.CTk):
def __init__(self, title):
#main window
super().__init__()
self.title(title)
ctk.CTkButton(self, text="再生", command=self.playmp3).pack(padx=50, pady=10)
#mainloop
self.mainloop()
def playmp3(self):
print("playmp3")
playback = Playback()
playback.load_file("test.mp3")
playback.play()
App("MyMP3Player")
でもこれは実際には鳴りません。
import time
import customtkinter as ctk
from just_playback import Playback
class App(ctk.CTk):
def __init__(self, title):
#main window
super().__init__()
self.title(title)
ctk.CTkButton(self, text="再生", command=self.playmp3).pack(padx=50, pady=10)
#mainloop
self.mainloop()
def playmp3(self):
print("playmp3")
playback = Playback()
playback.load_file("test.mp3")
playback.play()
time.sleep(3)
App("MyMP3Player")
こう書いたら、鳴った。結局sleepが必要なのか?
ただ、この書き方ではGUIが3秒間「固まって」しまうのです。
Copilot先生が教えてくれたsleepを使わない方法に書き換えてみます。
#import time
import customtkinter as ctk
from just_playback import Playback
class App(ctk.CTk):
def __init__(self, title):
#main window
super().__init__()
self.title(title)
ctk.CTkButton(self, text="再生", command=self.playmp3).pack(padx=50, pady=10)
#mainloop
self.mainloop()
def playmp3(self):
print("playmp3")
playback = Playback()
playback.load_file("test.mp3")
playback.play()
#time.sleep(3)
while playback.active:
pass
App("MyMP3Player")
秒数指定しなくていいのは良いですが、これでもやはり再生中はGUIが固まります。
そもそもTkinterって・・
そもそもTkinterって非同期の仕組みを使っているわけではないのかな?と思い、Copilotに聞いてみます。
質問の書き方が悪かった。意図が間違って伝わったみたいですが貴重な情報が得られました。
・Tkinterはシングルスレッドで動作する。
・非同期処理の二通りの定跡がある。
・・threadingモジュールを使ったマルチスレッド処理
・・asincioモジュールを使った非同期IO
どっちがいいの?
音声再生は、どちらかというとおそらくI/Oバウンドタスクの方に該当するのだろうから(?)、asyncioを使うのがいいのかな?
asyncio
非同期処理についての基礎知識が無いと本当にどうにもならないので、ネット上の分かりやすい解説を探していたら、サプー先生のこの動画がよさそう。今回の進捗はこの動画を眺めるところまでです。
要点メモ
・import asyncio
・非同期の関数定義 async defでコルーチンという関数になる
・呼び出しは asyncio.run(コルーチンの関数:普通の関数は扱えない)
・awaitを含む関数はasync defでコルーチンにすること
◎都度コルーチンの終了を「待つ」場合
await をつけてコルーチン呼び・・そこで処理を待つ
◎並行処理の場合(そのコルーチンの終了を「待たない」場合)
task1 = asyncio.create_task(コルーチン呼び)
task2 = asyncio.create_task(コルーチン呼び)
await task1
await task2
・・並行処理になる
関数の返り値は task1.result()で取れる。
◎gather
results = await asyncio.gather(taskとか、コルーチンとか並べる)
◎run_in_executor
awaitの後ろには普通の関数は指定できないが、
loop=asyncio.get_running_loop()
loop.run_in_executor(Executor(None), 普通関数,関数に渡す引数)
◎タイムアウト
await asyncio.wait_for(コルーチン、timeout=3)
try except でasyncio.TimeoutErrorを拾う。
新しい概念がいっぱい出てきました・・お腹いっぱい。