見出し画像

PythonでMP3再生(非同期手法の試行錯誤3)

 しばらく実践しないうちに「非同期手法」が頭からすっかり抜けてしまったので「リハビリ」です。
 一か月ほど前にこんなの書いていたけど

見返すと、余計なこと書き過ぎていて肝心な点がよくわからない(反省)。
 今後、すぐ思い出せるように、今回は要点を簡潔にまとめなおしたいと思います。


MP3再生の基本(GUI無し)

今回は非同期の動作を確認する目的で、できるだけ簡潔なコードにします。テストデータは例によって「甘茶の音楽工房」様の「残業戦士」

です。

import io
import time

import pygame
from pydub import AudioSegment


class MyPlayer:
    def __init__(self):
        super().__init__()
        self.audio = None  # pydub AudioSegment
        self.mixer = pygame.mixer
        self.mixer.init()

    # ファイル読み込み
    def set_mp3file(self, filename):
        self.audio = AudioSegment.from_mp3(filename)

    def play(self):
        self.audio_buffer = io.BytesIO()
        self.audio.export(self.audio_buffer, format="wav")
        self.audio_buffer.seek(0)
        self.mixer.music.load(self.audio_buffer)
        self.mixer.music.play()
        while self.mixer.music.get_busy():
            time.sleep(0.2)
        self.stop()

    def stop(self):
        self.mixer.music.stop()


# テストコード
if __name__ == "__main__":
    myplayer = MyPlayer()
    myplayer.set_mp3file("zangyousenshi.mp3")
    print("一回目演奏開始")
    myplayer.play()
    print("一回目演奏終了")

    print("二回目演奏開始")
    myplayer.play()
    print("二回目演奏終了")

これを実行すると、曲が2回再生されます。これが今回の出発点。

GUI(CustomTkinter)と組み合わせる

非同期を考えないで書いてみると

playボタンとstopボタンがあるだけのプレイヤーのGUIで

ソースコードは

import io
import time

import customtkinter as ctk
import pygame
from pydub import AudioSegment


class MyPlayer:
    def __init__(self):
        super().__init__()
        self.audio = None  # pydub AudioSegment
        self.mixer = pygame.mixer
        self.mixer.init()

    # ファイル読み込み
    def set_mp3file(self, filename):
        self.audio = AudioSegment.from_mp3(filename)

    def play(self):
        self.audio_buffer = io.BytesIO()
        self.audio.export(self.audio_buffer, format="wav")
        self.audio_buffer.seek(0)
        self.mixer.music.load(self.audio_buffer)
        self.mixer.music.play()
        while self.mixer.music.get_busy():
            time.sleep(0.2)
        self.stop()

    def stop(self):
        self.mixer.music.stop()


# テストコード
if __name__ == "__main__":

    class App(ctk.CTk):
        def __init__(self, title):
            super().__init__()
            self.title(title)

            self.player = MyPlayer()
            self.player.set_mp3file("zangyousenshi.mp3")

            # widget
            # 再生ボタン
            ctk.CTkButton(self, text="再生", command=self.player.play).pack(
                padx=50, pady=10
            )

            # 停止ボタン
            ctk.CTkButton(self, text="停止", command=self.player.stop).pack(
                padx=50, pady=10
            )

            self.protocol("WM_DELETE_WINDOW", self.on_closing)
            self.mainloop()

        def on_closing(self):
            # 閉じる前にやること
            self.player.stop()
            # 閉じる
            self.destroy()

    App("MyMP3Player20241116")

「再生」ボタンをクリックすると音楽は流れるので、できたつもりになって満足していると、実は停止ボタンが効かない・・

GUI固まった

曲の演奏が終了するまで、GUIが固まっていて、受け付けないわけです。

playの処理をplay_audioメソッドに移す

    def play_audio(self):
        self.audio_buffer = io.BytesIO()
        self.audio.export(self.audio_buffer, format="wav")
        self.audio_buffer.seek(0)
        self.mixer.music.load(self.audio_buffer)
        self.mixer.music.play()
        while self.mixer.music.get_busy():
            time.sleep(0.2)
        self.stop()

    def play(self):
        self.play_audio()

非同期を導入する(threading)

from threading import Thread

を追加しておいて、

    def play_audio(self):
        self.audio_buffer = io.BytesIO()
        self.audio.export(self.audio_buffer, format="wav")
        self.audio_buffer.seek(0)
        self.mixer.music.load(self.audio_buffer)
        self.mixer.music.play()
        while self.mixer.music.get_busy():
            time.sleep(0.2)
        self.stop()

    def play(self):
        # self.play_audio()
        thread = Thread(target=self.play_audio)
        thread.start()

こう書くとGUIが固まらなくなってGood。

現時点の最適解

 今後どうなるかわかりませんが、今の時点ではこれが最適解と思っております。コード書いておきます。

import io
import time
from threading import Thread

import customtkinter as ctk
import pygame
from pydub import AudioSegment


class MyPlayer:
    def __init__(self):
        super().__init__()
        self.audio = None  # pydub AudioSegment
        self.mixer = pygame.mixer
        self.mixer.init()

    # ファイル読み込み
    def set_mp3file(self, filename):
        self.audio = AudioSegment.from_mp3(filename)

    def play_audio(self):
        self.audio_buffer = io.BytesIO()
        self.audio.export(self.audio_buffer, format="wav")
        self.audio_buffer.seek(0)
        self.mixer.music.load(self.audio_buffer)
        self.mixer.music.play()
        while self.mixer.music.get_busy():
            time.sleep(0.2)
        self.stop()

    def play(self):
        # self.play_audio()
        thread = Thread(target=self.play_audio)
        thread.start()

    def stop(self):
        self.mixer.music.stop()


# テストコード
if __name__ == "__main__":

    class App(ctk.CTk):
        def __init__(self, title):
            super().__init__()
            self.title(title)

            self.player = MyPlayer()
            self.player.set_mp3file("zangyousenshi.mp3")

            # widget
            # 再生ボタン
            ctk.CTkButton(self, text="再生", command=self.player.play).pack(
                padx=50, pady=10
            )

            # 停止ボタン
            ctk.CTkButton(self, text="停止", command=self.player.stop).pack(
                padx=50, pady=10
            )

            self.protocol("WM_DELETE_WINDOW", self.on_closing)
            self.mainloop()

        def on_closing(self):
            # 閉じる前にやること
            self.player.stop()
            # 閉じる
            self.destroy()

    App("MyMP3Player20241116")

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