PythonでMP3再生(超軽くなった話)
明らかな差が出た
後半、ごちゃごちゃ経緯を書いていますけれど、今回のポイントは、
・(音楽再生中に「ボリューム変更」したとき)「GUIの更新の追随性」の点で明らかな違いが出た(とても軽くなった)。
という点です。
まずは動画で違い(効果)を見ていただけると一目瞭然だと思います。
変更前
重いよう・・・
変更後
めっちゃ軽い!
経緯
一週間前には
「非同期スゲー」という発想で、まるで「非同期」を「銀の弾丸」のように思っていたのです(^_^;)
ところが今に至って(非同期使っているとはいえ、書き方が)冗長に感じられて、Copilot先生に質問しました。
(syncをsincと書き間違えちゃった(^_^;))
Copilot先生は「正しい方向」をご存じのようです。なんだか悔しいなぁ(^_^;)
なるほど、必要ないところにloop.run_in_executorを使って独り相撲をとっていたわけか。
loop.run_in_executor を使う「秘密の呪文」を覚えたのでとりあえず唱えておけ、と思っていたのが、ここでは呪文不要であると判明して拍子抜け(笑)
まあ、こういう回り道も勉強だと思うことにします。
記述変更の内容
とりあえずtimer_lib.py の内容(TTimerクラス)とまとめてmytools_lib.pyという一つのファイルにしました。「プロパティ」多用しています。
import asyncio
from abc import ABC, abstractmethod
from threading import Thread
import customtkinter as ctk
from just_playback import Playback
class CustomPlayer(ABC):
@abstractmethod
def play(self):
pass
class MyPlayer(CustomPlayer):
def __init__(self):
super().__init__()
self.mp3file = None
self.playback = Playback()
def set_mp3file(self, filename):
self.mp3file = filename
self.playback.load_file(filename)
# 非同期の書き方
async def playasync(self):
if self.mp3file is not None:
self.playback.play()
while self.playback.active:
await asyncio.sleep(0.1)
# 抽象メソッドを実装
def play(self):
# 非同期関数を別スレッドで実行
if not self.playback.active:
Thread(target=lambda: asyncio.run(self.playasync())).start()
def stop(self):
self.playback.stop()
@property
def paused(self):
return self.playback.paused
@paused.setter
def paused(self, flag):
if flag is True:
self.playback.pause()
else:
self.playback.resume()
@property
def pos(self):
return self.playback.curr_pos
@pos.setter
def pos(self, seekpos):
self.playback.seek(seekpos)
@property
def duration(self):
return self.playback.duration
@property
def volume(self):
return self.playback.volume
@volume.setter
def volume(self, vol):
self.playback.set_volume(vol)
class TTimer(ctk.CTkFrame):
def __init__(self, parent, task_function, interval=1000, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.parent = parent
self.task_function = task_function
self.interval = interval
self.enabled = True # 初期状態を有効に設定
self.background_task()
def background_task(self):
if self.enabled:
self.task_function()
self.parent.after(
self.interval, self.background_task
) # 動的に間隔を変更可能
def set_interval(self, new_interval):
self.interval = new_interval
def set_enabled(self, enabled):
self.enabled = enabled
この書き換えに伴って、前回
のテストコードの記述内容も更新
import customtkinter as ctk
from mytools_lib import MyPlayer, TTimer
class App(ctk.CTk):
def __init__(self, title):
# main window
super().__init__()
self.title(title)
# self.pause_on = False
# player
self.player = MyPlayer()
# 再生する曲は当面固定
self.player.set_mp3file("test2.mp3")
# widget
# 再生ボタン
ctk.CTkButton(self, text="再生", command=self.on_play_click).pack(
padx=50, pady=10
)
# ポーズボタン
self.pause_button = ctk.CTkButton(
self, text="一時停止:解除", command=self.on_pause_click
)
self.pause_button.pack(padx=50, pady=10)
# 停止ボタン
ctk.CTkButton(self, text="停止", command=self.player.stop).pack(
padx=50, pady=10
)
# 全体長さ表示
self.label_duration = ctk.CTkLabel(self, text="曲の長さ")
self.label_duration.pack()
# 再生位置表示
self.label_currpos = ctk.CTkLabel(self, text="再生位置")
self.label_currpos.pack()
# プログレスバーWidget
self.progressbar = ctk.CTkProgressBar(self, width=200, height=8)
self.progressbar.pack(padx=50, pady=10)
# 5秒早送り
ctk.CTkButton(self, text="5秒送り", command=self.ffw).pack(
padx=50, pady=10
)
# 5秒巻き戻し
ctk.CTkButton(self, text="5秒戻し", command=self.rew).pack(
padx=50, pady=10
)
# VOL
self.label_volume = ctk.CTkLabel(self, text="VOL")
self.label_volume.pack()
# スライダーボリュームWidget
self.slider_volume = ctk.CTkSlider(
self,
width=200,
height=8,
from_=0,
to=1.0,
command=self.slider_event,
)
self.slider_volume.pack(padx=50, pady=10)
self.slider_volume.set(self.player.volume)
# 表示更新用
self.timer1 = TTimer(self, self.task_updatedisp, interval=1000)
# mainloop
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.mainloop()
def ffw(self):
self.player.pos = self.player.pos + 5.0
def rew(self):
self.player.pos = self.player.pos - 5.0
def on_play_click(self):
self.player.paused = False
self.player.play()
def slider_event(self, value):
self.player.volume = value
def on_pause_click(self):
self.player.paused = not self.player.paused
if self.player.paused:
self.pause_button.configure(text="一時停止:停止中")
else:
self.pause_button.configure(text="一時停止:解除")
def on_closing(self):
# 閉じる前にやること
self.player.stop()
# 閉じる
self.destroy()
def task_updatedisp(self):
duration = self.player.duration
self.label_duration.configure(text=f"長さ:{duration:.1f}")
curr_pos = self.player.pos
self.label_currpos.configure(text=f"位置:{curr_pos:.1f}")
self.progressbar.set(curr_pos / duration)
App("MyMP3Player")
評価
今回の書き換えで、予想以上の性能改善効果があったわけです。比較すると「改善後」じゃなきゃ絶対ダメ、「改善前」のパフォーマンスでは許容できない、もう戻れない、と感じました。