Move playlist handling from mp3player to app
All checks were successful
Check code formatting / Check-C-Format (push) Successful in 7s
Check code formatting / Check-Python-Flake8 (push) Successful in 8s
Check code formatting / Check-Bash-Shellcheck (push) Successful in 5s
Run unit tests on host / Run-Unit-Tests (push) Successful in 8s
Build RPi Pico firmware image / Build-Firmware (push) Successful in 3m28s
All checks were successful
Check code formatting / Check-C-Format (push) Successful in 7s
Check code formatting / Check-Python-Flake8 (push) Successful in 8s
Check code formatting / Check-Bash-Shellcheck (push) Successful in 5s
Run unit tests on host / Run-Unit-Tests (push) Successful in 8s
Build RPi Pico firmware image / Build-Firmware (push) Successful in 3m28s
This completes the move of the nfc-mp3-demo to the new architecture.
This commit is contained in:
@@ -21,9 +21,15 @@ class PlayerApp:
|
||||
self.player = deps.mp3player(self)
|
||||
self.nfc = deps.nfcreader(self)
|
||||
self.buttons = deps.buttons(self) if deps.buttons is not None else None
|
||||
self.mp3file = None
|
||||
self.volume_pos = 3
|
||||
self.player.set_volume(VOLUME_CURVE[self.volume_pos])
|
||||
|
||||
def __del__(self):
|
||||
if self.mp3file is not None:
|
||||
self.mp3file.close()
|
||||
self.mp3file = None
|
||||
|
||||
def onTagChange(self, new_tag):
|
||||
if new_tag is not None:
|
||||
self.timer_manager.cancel(self.onTagRemoveDelay)
|
||||
@@ -43,7 +49,7 @@ class PlayerApp:
|
||||
self.player.stop()
|
||||
return
|
||||
testfiles.sort()
|
||||
self.player.set_playlist(testfiles)
|
||||
self._set_playlist(testfiles)
|
||||
else:
|
||||
self.timer_manager.schedule(time.ticks_ms() + 5000, self.onTagRemoveDelay)
|
||||
|
||||
@@ -61,4 +67,27 @@ class PlayerApp:
|
||||
self.volume_pos = max(self.volume_pos - 1, 0)
|
||||
self.player.set_volume(VOLUME_CURVE[self.volume_pos])
|
||||
elif what == self.buttons.NEXT:
|
||||
self.player.play_next()
|
||||
self._play_next()
|
||||
|
||||
def onPlaybackDone(self):
|
||||
self.mp3file.close()
|
||||
self.mp3file = None
|
||||
self._play_next()
|
||||
|
||||
def _set_playlist(self, files: list[bytes]):
|
||||
self.playlist_pos = 0
|
||||
self.playlist = files
|
||||
self._play(self.playlist[self.playlist_pos])
|
||||
|
||||
def _play_next(self):
|
||||
if self.playlist_pos + 1 < len(self.playlist):
|
||||
self.playlist_pos += 1
|
||||
self._play(self.playlist[self.playlist_pos])
|
||||
|
||||
def _play(self, filename: bytes):
|
||||
if self.mp3file is not None:
|
||||
self.player.stop()
|
||||
self.mp3file.close()
|
||||
self.mp3file = None
|
||||
self.mp3file = open(filename, 'rb')
|
||||
self.player.play(self.mp3file)
|
||||
|
||||
@@ -88,15 +88,12 @@ def run():
|
||||
# Setup MP3 player
|
||||
with SDContext(mosi=Pin(3), miso=Pin(4), sck=Pin(2), ss=Pin(5), baudrate=15000000), \
|
||||
AudioContext(Pin(8), Pin(6)) as audioctx:
|
||||
player = MP3Player(audioctx)
|
||||
player.set_volume(32)
|
||||
asyncio.create_task(player.task())
|
||||
|
||||
# Setup NFC
|
||||
reader = MFRC522(spi_id=1, sck=10, miso=12, mosi=11, cs=13, rst=9, tocard_retries=20)
|
||||
|
||||
# Setup app
|
||||
deps = app.Dependencies(mp3player=lambda _: player,
|
||||
deps = app.Dependencies(mp3player=lambda the_app: MP3Player(audioctx, the_app),
|
||||
nfcreader=lambda the_app: Nfc(reader, the_app),
|
||||
buttons=lambda the_app: Buttons(the_app))
|
||||
the_app = app.PlayerApp(deps)
|
||||
|
||||
@@ -3,51 +3,41 @@
|
||||
|
||||
import asyncio
|
||||
from array import array
|
||||
from utils import TimerManager
|
||||
try:
|
||||
from typing import TYPE_CHECKING # type: ignore
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
import typing
|
||||
|
||||
class PlayerCallback(typing.Protocol):
|
||||
def onPlaybackDone(self) -> None: ...
|
||||
|
||||
|
||||
class MP3Player:
|
||||
def __init__(self, audiocore):
|
||||
def __init__(self, audiocore, cb: PlayerCallback):
|
||||
self.audiocore = audiocore
|
||||
self.commands = []
|
||||
self.command_event = asyncio.Event()
|
||||
self.playlist = []
|
||||
self.mp3task = None
|
||||
self.volume = 128
|
||||
self.cb = cb
|
||||
|
||||
def set_playlist(self, mp3files):
|
||||
def play(self, stream):
|
||||
"""
|
||||
Set a new playlist and start playing from the first entry.
|
||||
For convenience a single file name can also be passed.
|
||||
Play from byte stream.
|
||||
"""
|
||||
if type(mp3files) is bytes:
|
||||
self.playlist = [mp3files]
|
||||
else:
|
||||
self.playlist = mp3files
|
||||
self._send_command('newplaylist')
|
||||
|
||||
def play_next(self):
|
||||
"""
|
||||
Skip to the next track in the playlist. Reaching the end of the playlist stops playback.
|
||||
"""
|
||||
self._send_command('next')
|
||||
|
||||
def play_prev(self):
|
||||
"""
|
||||
Skip to the previous track in the playlist.
|
||||
"""
|
||||
self._send_command('prev')
|
||||
if self.mp3task is not None:
|
||||
self.mp3task.cancel()
|
||||
self.mp3task = None
|
||||
self.mp3task = asyncio.create_task(self._play_task(stream))
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop playback, remembering the current position in the playlist (but not inside a track).
|
||||
Stop playback
|
||||
"""
|
||||
self._send_command('stop')
|
||||
|
||||
def play(self):
|
||||
"""
|
||||
Start playback.
|
||||
"""
|
||||
self._send_command('play')
|
||||
if self.mp3task is not None:
|
||||
self.mp3task.cancel()
|
||||
self.mp3task = None
|
||||
|
||||
def set_volume(self, volume: int):
|
||||
"""
|
||||
@@ -59,67 +49,25 @@ class MP3Player:
|
||||
def get_volume(self) -> int:
|
||||
return self.volume
|
||||
|
||||
def _send_command(self, command: str):
|
||||
self.commands.append(command)
|
||||
self.command_event.set()
|
||||
|
||||
async def _play_task(self, mp3path):
|
||||
async def _play_task(self, stream):
|
||||
known_underruns = 0
|
||||
send_done = False
|
||||
data = array('b', range(512))
|
||||
try:
|
||||
print(b'Playing ' + mp3path)
|
||||
with open(mp3path, 'rb') as mp3file:
|
||||
while True:
|
||||
bytes_read = mp3file.readinto(data)
|
||||
if bytes_read == 0:
|
||||
# End of file
|
||||
break
|
||||
_, _, underruns = await self.audiocore.async_put(data[:bytes_read])
|
||||
if underruns > known_underruns:
|
||||
print(f"{underruns:x}")
|
||||
known_underruns = underruns
|
||||
# Intentionally do not use _send_command, we don't want to set command_event yet
|
||||
self.commands.append('done')
|
||||
while True:
|
||||
bytes_read = stream.readinto(data)
|
||||
if bytes_read == 0:
|
||||
# End of file
|
||||
break
|
||||
_, _, underruns = await self.audiocore.async_put(data[:bytes_read])
|
||||
if underruns > known_underruns:
|
||||
print(f"{underruns:x}")
|
||||
known_underruns = underruns
|
||||
# Call onPlaybackDone after flush
|
||||
send_done = True
|
||||
finally:
|
||||
self.audiocore.flush()
|
||||
self.command_event.set()
|
||||
|
||||
def _play(self, mp3path):
|
||||
if self.mp3task is not None:
|
||||
self.mp3task.cancel()
|
||||
self.mp3task = None
|
||||
if mp3path is not None:
|
||||
self.mp3task = asyncio.create_task(self._play_task(mp3path))
|
||||
|
||||
async def task(self):
|
||||
playlist_pos = 0
|
||||
while True:
|
||||
await self.command_event.wait()
|
||||
self.command_event.clear()
|
||||
change_play = False
|
||||
while len(self.commands) > 0:
|
||||
command = self.commands.pop()
|
||||
if command == 'next' or command == 'done':
|
||||
if playlist_pos + 1 < len(self.playlist):
|
||||
playlist_pos += 1
|
||||
change_play = True
|
||||
else:
|
||||
# reaching the end of the playlist stops playback
|
||||
self._play(None)
|
||||
elif command == 'prev':
|
||||
if playlist_pos > 0:
|
||||
playlist_pos -= 1
|
||||
change_play = True
|
||||
elif command == 'stop':
|
||||
self._play(None)
|
||||
elif command == 'play':
|
||||
if self.mp3task is None:
|
||||
change_play = True
|
||||
elif command == 'newplaylist':
|
||||
if len(self.playlist) > 0:
|
||||
playlist_pos = 0
|
||||
change_play = True
|
||||
else:
|
||||
self._play(None)
|
||||
if change_play:
|
||||
self._play(self.playlist[playlist_pos])
|
||||
if send_done:
|
||||
# Only call onPlaybackDone if exit due to end of stream
|
||||
# Use timer with time 0 to call callback "immediately" but from a different task
|
||||
TimerManager().schedule(0, self.cb.onPlaybackDone)
|
||||
|
||||
Reference in New Issue
Block a user