feat: complete `uninstall.sh` logic to make everything synced. feat: complete basic play logic to handle background, music, lyric play.
117 lines
3.7 KiB
C++
117 lines
3.7 KiB
C++
// musicPlayer.cpp
|
|
//
|
|
// miniaudio-backed playback of mp3 / wav / flac files. See musicPlayer.h.
|
|
//
|
|
// MINIAUDIO_IMPLEMENTATION is defined in this one translation unit so the
|
|
// large miniaudio implementation is compiled exactly once per binary.
|
|
|
|
#define MINIAUDIO_IMPLEMENTATION
|
|
#include "miniaudio.h"
|
|
|
|
#include "musicPlayer.h"
|
|
|
|
#include <atomic>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
// When built with ALSA support, silence ALSA's own error printing: with no
|
|
// sound card present it dumps a lot of "cannot find card '0'" chatter to
|
|
// stderr, which would corrupt the TUI during playback.
|
|
#ifdef HMS_HAS_ALSA
|
|
#include <alsa/asoundlib.h>
|
|
#endif
|
|
|
|
namespace mp {
|
|
|
|
namespace {
|
|
|
|
#ifdef HMS_HAS_ALSA
|
|
void alsaErrorHandler(const char*, int, const char*, int, const char*, ...) {
|
|
// swallow ALSA's verbose configuration/device errors
|
|
}
|
|
#endif
|
|
|
|
struct PlaybackUser {
|
|
ma_decoder* decoder;
|
|
const std::atomic<bool>* stop;
|
|
std::atomic<bool> ended{false};
|
|
};
|
|
|
|
void dataCallback(ma_device* device, void* out, const void* /*in*/,
|
|
ma_uint32 frameCount) {
|
|
auto* user = static_cast<PlaybackUser*>(device->pUserData);
|
|
ma_decoder* decoder = user->decoder;
|
|
const size_t bytesPerFrame =
|
|
ma_get_bytes_per_frame(decoder->outputFormat, decoder->outputChannels);
|
|
|
|
if (user->stop && user->stop->load()) {
|
|
// Stop requested: feed silence and let the main loop tear the device
|
|
// down.
|
|
memset(out, 0, static_cast<size_t>(frameCount) * bytesPerFrame);
|
|
user->ended.store(true);
|
|
return;
|
|
}
|
|
|
|
ma_uint64 framesRead = 0;
|
|
const ma_result result =
|
|
ma_decoder_read_pcm_frames(decoder, out, frameCount, &framesRead);
|
|
|
|
if (framesRead > 0 && framesRead < frameCount) {
|
|
// Underrun / near EOF: zero-pad the rest of the buffer.
|
|
memset(static_cast<char*>(out) + framesRead * bytesPerFrame, 0,
|
|
static_cast<size_t>(frameCount - framesRead) * bytesPerFrame);
|
|
} else if (framesRead == 0) {
|
|
memset(out, 0, static_cast<size_t>(frameCount) * bytesPerFrame);
|
|
}
|
|
|
|
// A short read means the file is exhausted (MA_AT_END); any other
|
|
// non-success result is a decode error. Both end the playback loop.
|
|
if (framesRead < frameCount || (result != MA_SUCCESS && result != MA_AT_END)) {
|
|
user->ended.store(true);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int play(const std::string& path, Timeline* timeline, std::atomic<bool>* stop) {
|
|
if (path.empty()) return 1;
|
|
|
|
// Audio is the show's clock: anchor the timeline the moment playback is
|
|
// about to begin (a no-op if the caller already anchored it).
|
|
if (timeline && !timeline->started()) timeline->start();
|
|
|
|
ma_decoder_config decoderConfig = ma_decoder_config_init_default();
|
|
ma_decoder decoder;
|
|
if (ma_decoder_init_file(path.c_str(), &decoderConfig, &decoder) != MA_SUCCESS) {
|
|
return 2; // cannot open / no decoder for the format
|
|
}
|
|
|
|
#ifdef HMS_HAS_ALSA
|
|
snd_lib_error_set_handler(alsaErrorHandler);
|
|
#endif
|
|
|
|
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
|
|
deviceConfig.playback.format = decoder.outputFormat;
|
|
deviceConfig.playback.channels = decoder.outputChannels;
|
|
deviceConfig.sampleRate = decoder.outputSampleRate;
|
|
deviceConfig.dataCallback = dataCallback;
|
|
PlaybackUser user{&decoder, stop};
|
|
deviceConfig.pUserData = &user;
|
|
|
|
ma_device device;
|
|
if (ma_device_init(nullptr, &deviceConfig, &device) != MA_SUCCESS) {
|
|
ma_decoder_uninit(&decoder);
|
|
return 3; // no audio backend / device available
|
|
}
|
|
|
|
ma_device_start(&device);
|
|
while (!user.ended.load() && !(stop && stop->load())) {
|
|
ma_sleep(50);
|
|
}
|
|
ma_device_uninit(&device);
|
|
ma_decoder_uninit(&decoder);
|
|
return 0;
|
|
}
|
|
|
|
} // namespace mp
|