音源再生ライブラリ書き換え(6)
前回、Copilot先生に「一時停止機能」を追加してもらったMyPlayerクラスです。これで、まあ、動いていたようなので(一時停止と、同じ位置からの再開ができていた)、コードの中身を自分なりに「解読」してみます。
import time
from threading import Thread
import numpy as np
import sounddevice as sd
from pydub import AudioSegment
class MyPlayer:
def __init__(self):
super().__init__()
self.filename = None
self.audio = None
self.audio_data = None # オーディオデータ
self.sr = None # サンプルレート
self.is_playing = False
self.is_paused = False
self.playstarttime = None
self.pause_time = 0
self.pause_sample = 0
# ファイル読み込み
def set_mp3file(self, filename):
self.filename = filename
self.audio = AudioSegment.from_mp3(filename)
self.audio_data = np.array(self.audio.get_array_of_samples())
if self.audio.channels == 2:
self.audio_data = self.audio_data.reshape((-1, 2))
self.sr = self.audio.frame_rate
def play_audio(self):
if self.is_paused:
start_sample = self.pause_sample
self.is_paused = False
else:
start_sample = 0
self.is_playing = True
sd.play(self.audio_data[start_sample:], samplerate=self.sr)
sd.wait()
self.is_playing = False
def play(self):
if not self.is_playing:
self.playstarttime = time.time() - self.pause_time
Thread(target=self.play_audio).start()
def pause(self):
if self.is_playing:
self.is_paused = True
self.pause_time = time.time() - self.playstarttime
self.pause_sample = int(self.pause_time * self.sr)
sd.stop()
def stop(self):
if self.is_playing or self.is_paused:
sd.stop()
self.is_playing = False
self.is_paused = False
self.pause_time = 0
self.playstarttime = None
@property
def duration(self):
return self.audio.duration_seconds
@property
def pos(self):
if self.is_playing:
return time.time() - self.playstarttime
elif self.is_paused:
return self.pause_time
else:
return None
class MyPlayer
init
インスタンス変数の宣言箇所
filename ・・・読み込んだmp3のファイル名
audio・・・pydubのAudioSegmentとして音声データを保持
audio_data・・・numpy配列として音声データを保持
sr・・・サンプルレート
is_playing ・・・再生中かどうか(bool) 初期値はfalse
is_paused ・・・一時停止中かどうか(bool) 初期値はfalse
playstarttime・・・再生開始時刻
pause_time・・・一時停止した位置(先頭からの秒数)
pause_sample・・・一時停止した位置(先頭からのフレーム数)
set_mp3file
mp3ファイルを読み込んで、filename、audio、audio_data、srに各情報をセットする。(特に今回、目新しい内容ではない。)
pause
新規のメソッド
def pause(self):
if self.is_playing:
self.is_paused = True
self.pause_time = time.time() - self.playstarttime
self.pause_sample = int(self.pause_time * self.sr)
sd.stop()
・if self.is_playing により、再生中だけ、機能する。
・・is_pausedをTrueにして、pause_timeを現在時刻と、開始時刻の差から現在の再生位置を求めてpause_timeに格納。そこまでのサンプリングデータ数を整数値でpause_sampleに格納して停止。
再生中 is_playing :True ⇒is_paused はTrueになって再生は停止
※ぱっと見、pause中にpauseが再実行されると変なことになる?いや、play_audio抜けるときにself.is_playing = False が実行されているので一時停止中はis_playingはFalseになっており、影響なしのようです。
play_audioとplay
pause導入前
def play_audio(self):
if self.filename is not None:
if not self.is_playing:
self.is_playing = True
# 音声の再生
sd.play(self.audio_data, samplerate=self.sr)
sd.wait()
self.is_playing = False
def play(self):
if not self.is_playing:
Thread(target=self.play_audio).start()
pause導入後
def play_audio(self):
if self.is_paused:
start_sample = self.pause_sample
self.is_paused = False
else:
start_sample = 0
self.is_playing = True
sd.play(self.audio_data[start_sample:], samplerate=self.sr)
sd.wait()
self.is_playing = False
def play(self):
if not self.is_playing:
self.playstarttime = time.time() - self.pause_time
Thread(target=self.play_audio).start()
解読
is_pausedではstart_sampleには停止位置のフレーム数が入る。そうでなければ0、つまり先頭から再生。
is_paused状態から再生開始する場合は、開始時点を現在時刻から今の位置までの再生時間を引いた時点に過去にさかのぼって設定して、あたかも、その時点から再生が続いているかのように処理。
pos
@property
def pos(self):
if self.is_playing:
return time.time() - self.playstarttime
elif self.is_paused:
return self.pause_time
else:
return None
再生中なら、開始からの時間、pause中ならpause_timeを返す。
stop
def stop(self):
if self.is_playing or self.is_paused:
sd.stop()
self.is_playing = False
self.is_paused = False
self.pause_time = 0
self.playstarttime = None
is_playing もしくは is_pausedの状態を解除。
ようやく理解できた(かな?)
昨晩は、全く頭に入ってこなかった部分ですが、一日経ってどうにか今は、動作の流れを把握できたように自分では思っています。
ここからいじるとしたら、時刻を求めるところでtimeモジュールの利用をやめて、前回やったsd.Stream().time を使う、ということくらいかな?