見出し画像

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")

評価

今回の書き換えで、予想以上の性能改善効果があったわけです。比較すると「改善後」じゃなきゃ絶対ダメ、「改善前」のパフォーマンスでは許容できない、もう戻れない、と感じました。


いいなと思ったら応援しよう!