MP3Player module to manage mp3 playing

This commit is contained in:
2025-03-16 21:56:00 +01:00
parent 931571bd0a
commit 389fed4d3b
2 changed files with 127 additions and 27 deletions

120
software/src/mp3player.py Normal file
View File

@@ -0,0 +1,120 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
import asyncio
from array import array
class MP3Player:
def __init__(self, audiocore):
self.audiocore = audiocore
self.commands = []
self.command_event = asyncio.Event()
self.playlist = []
self.mp3task = None
def set_playlist(self, mp3files):
"""
Set a new playlist and start playing from the first entry.
For convenience a single file name can also be passed.
"""
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')
def stop(self):
"""
Stop playback, remembering the current position in the playlist (but not inside a track).
"""
self._send_command('stop')
def play(self):
"""
Start playback.
"""
self._send_command('play')
def set_volume(self, volume: int):
"""
Set volume (0..255).
"""
self.audiocore.set_volume(volume)
def _send_command(self, command: str):
self.commands.append(command)
self.command_event.set()
async def _play_task(self, mp3path):
known_underruns = 0
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')
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])

View File

@@ -1,5 +1,5 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
# Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
import aiorepl
import asyncio
@@ -13,6 +13,7 @@ from micropython import const
# Own modules
from audiocore import Audiocore
from mp3player import MP3Player
from rp2_neopixel import NeoPixel
from rp2_sd import SDCard
@@ -42,30 +43,6 @@ async def rainbow(np, period=10):
await asyncio.sleep_ms(20 - (now - before))
async def play_mp3(audiocore, mp3file):
known_underruns = 0
while True:
data = mp3file.read(4096)
if len(data) == 0:
# End of file
break
_, _, underruns = await audioctx.async_put(data)
if underruns > known_underruns:
print(f"{underruns:x}")
known_underruns = underruns
audioctx.flush()
print("Decoding ended")
async def play_mp3s(audiocore, mp3files):
audiocore.set_volume(64)
for name in mp3files:
print(b'Playing ' + name)
with open(name, "rb") as testfile:
await play_mp3(audiocore, testfile)
await asyncio.sleep_ms(1000)
# Set 8 mA drive strength and fast slew rate
machine.mem32[0x4001c004 + 6*4] = 0x67
machine.mem32[0x4001c004 + 7*4] = 0x67
@@ -122,12 +99,15 @@ asyncio.create_task(rainbow(np))
# Test audio
audioctx = Audiocore(Pin(8), Pin(6))
player = MP3Player(audioctx)
# high prio for proc 1
machine.mem32[0x40030000 + 0x00] = 0x10
testfiles = [b'/sd/' + name for name in os.listdir(b'/sd') if name.endswith(b'mp3')]
player.set_playlist(testfiles)
asyncio.create_task(player.task())
asyncio.create_task(play_mp3s(audioctx, testfiles))
asyncio.create_task(aiorepl.task())
asyncio.create_task(aiorepl.task({'player': player}))
asyncio.get_event_loop().run_forever()