PythonでMP3再生(停止・一時停止)
壁
「音声再生(playメソッド)」はPython始めた昨年12月以来、当方にとってずっと「壁」でした。年初に
で引用した
とか、
とかは、いずれも「time.sleep()をつけることで解決できるよ」とする説明です。たしかに自分も、time.sleep()のやり方で「再生」はできた。そのやり方を今まで踏襲してきた。でも、ずっともやもやが残っていました。
改めて見直して、もやもやの原因が分かりました。stackoverflow.com の回答見ると
「非効率だが簡単」な方法だ、としてtime.sleep() を使う手法は紹介されている。「一応鳴るからいいや」で、今まできてしまいましたが、「重い」上に「GUIにすれば再生終了まで『固まる』」わけで、まさにinefficientだった。
一時しのぎならいいですが、恒久的に使う手法じゃなかったということ。
壁をのり越えたかな?
前回までに「非同期」を使って「いい感じ」に書けました。重くならなくなった。ようやく「再生」の壁は越えられたかな、と思っています。
様々なメソッド
これまで、pygame, playsound3, just_playback と プレイヤー機能を持つmoduleをいろいろ試してきてはいましたが、いずれも実質、再生(play)のメソッドだけしか使ってきませんでした。それ以外の機能に手を出す余裕がなかった。
いまさらですが、あらためてモジュールの説明を見てみると、play以外にもたくさんメソッドがあるのに気づかされます。
たとえば、このところメインで使っているjust-playbackですと
Usageの記述
>>> from just_playback import Playback
>>> playback = Playback() # creates an object for managing playback of a single audio file
>>> playback.load_file('music/sample.mp3')
# or just pass the filename directly to the constructor
>>> playback.play() # plays loaded audio file from the beginning
>>> playback.pause() # pauses playback. No effect if playback is already paused
>>> playback.resume() # resumes playback. No effect if playback is playing
>>> playback.stop() # stops playback. No effect if playback is not active
>>> playback.seek(60) # positions playback at 1 minute from the start of the audio file. No effect
# if playback is not active
>>> playback.set_volume(0.5) # sets the playback volume to 50% of the audio file's original value
>>> playback.loop_at_end(True) # since 0.1.5. Causes playback to automatically restart when it completes.
>>> playback.active # True if playback is active i.e playing or paused
>>> playback.playing # True if playback is active and not paused
>>> playback.curr_pos # current absolute playback position in seconds from
# the start of the audio file (unlike pygame.mixer.get_pos).
>>> playback.paused # True if playback is paused.
>>> playback.duration # length of the audio file in seconds.
>>> playback.volume # current playback volume
>>> playback.loops_at_end # True if playback is set to restart when it completes.
(https://github.com/cheofusi/just_playback のUsageより)
とまあ、いろいろな機能があるじゃないですか。
再生:play の他にも 一時停止:pause、一時停止解除:resume、
停止:stop、頭出し:seek、音量:set_volume、
繰り返し再生:loop_at_end(bool)
と各種メソッドあり。
また、状態を取得できるプロパティとして
active:再生中又はpause中か?、playing:再生中か?、curr_pos:現在位置
paused:pause中か?、duration:オーディオファイルの長さ
volume:ボリューム playback.loops_at_end:繰り返し再生設定中か?
これだけあれば十分高機能なMp3プレイヤーが作れそう。
まず停止・一時停止から試してみる
ライブラリの実装
前回までに実現したアプリ機能は次のようなものです。
・再生ボタン一つ。クリックしたら(test.mp3ファイルの)曲を終わりまで再生。
・途中で右上のバツ印でウインドウを閉じれば、再生を終了して、アプリも終了する。(当初は、ウインドウを閉じても音の再生が止まらなかった。停止のメソッドを実装して、閉じる際にそれを実行するようにした)
・・というところまで。
ここから拡張してみます。停止のメソッドがあるから「停止ボタン」も用意して、アプリを終了しなくても再生を止められるようにしたらいいですし、pause とresumeメソッドを組み合わせて一時停止ボタンも作れるはず。
ライブラリのファイルplayer_lib.py 内のMyPlayerのクラスに、前回のstop の他、pause、resumeのメソッドを実装しました。
def stop(self):
self.playback.stop()
def pause(self):
self.playback.pause()
def resume(self):
self.playback.resume()
(いや、これだけだから「実装」とか大げさな話ではないな(笑))
GUI
まずアプリが使われる様子を思い描いて「見た目」から入る。
Pauseの状態と、それを解除する状態をボタンで切り替えられるようにする。昔のテープレコーダーの「一時停止」ボタンは、一度押すと押し込まれた状態で維持され、再度押すと押しこまれていない元の位置に戻るという動作で、直感的に理解できた。それに似たものを作ってみます。
class App(ctk.CTk):
def __init__(self, title):
(抜粋)
# 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
)
def on_play_click(self):
# Play時はポーズを外して再生
self.pause_on = True
self.on_pause_click() # ここで必ずポーズFalseになる
self.player.play()
def on_pause_click(self):
self.pause_on = not self.pause_on
if self.pause_on:
self.pause_button.configure(text="一時停止:停止中")
self.player.pause()
else:
self.pause_button.configure(text="一時停止:解除")
self.player.resume()
動作テスト
停止・一時停止については上の書き方で期待通りの動作をしています。
実行の様子を動画で・・
テスト用のmp3ファイルには、「甘茶の音楽工房」様の「残業戦士」
という曲を使わせていただいています。