123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- #include "TrackPlayer.h"
- #include <mutex> // for mutex, scoped_lock
- #include <string> // for string
- #include <type_traits> // for remove_extent_t
- #include <vector> // for vector, vector<>::value_type
- #include "BellLogger.h" // for AbstractLogger
- #include "BellUtils.h" // for BELL_SLEEP_MS
- #include "Logger.h" // for CSPOT_LOG
- #include "Packet.h" // for cspot
- #include "TrackQueue.h" // for CDNTrackStream, CDNTrackStream::TrackInfo
- #include "WrappedSemaphore.h" // for WrappedSemaphore
- #ifdef BELL_VORBIS_FLOAT
- #define VORBIS_SEEK(file, position) (ov_time_seek(file, (double)position / 1000))
- #define VORBIS_READ(file, buffer, bufferSize, section) (ov_read(file, buffer, bufferSize, 0, 2, 1, section))
- #else
- #define VORBIS_SEEK(file, position) (ov_time_seek(file, position))
- #define VORBIS_READ(file, buffer, bufferSize, section) \
- (ov_read(file, buffer, bufferSize, section))
- #endif
- namespace cspot {
- struct Context;
- struct TrackReference;
- } // namespace cspot
- using namespace cspot;
- static size_t vorbisReadCb(void* ptr, size_t size, size_t nmemb,
- TrackPlayer* self) {
- return self->_vorbisRead(ptr, size, nmemb);
- }
- static int vorbisCloseCb(TrackPlayer* self) {
- return self->_vorbisClose();
- }
- static int vorbisSeekCb(TrackPlayer* self, int64_t offset, int whence) {
- return self->_vorbisSeek(offset, whence);
- }
- static long vorbisTellCb(TrackPlayer* self) {
- return self->_vorbisTell();
- }
- TrackPlayer::TrackPlayer(std::shared_ptr<cspot::Context> ctx,
- std::shared_ptr<cspot::TrackQueue> trackQueue,
- EOFCallback eof, TrackLoadedCallback trackLoaded)
- : bell::Task("cspot_player", 48 * 1024, 5, 1) {
- this->ctx = ctx;
- this->eofCallback = eof;
- this->trackLoaded = trackLoaded;
- this->trackQueue = trackQueue;
- this->playbackSemaphore = std::make_unique<bell::WrappedSemaphore>(5);
- // Initialize vorbis callbacks
- vorbisFile = {};
- vorbisCallbacks = {
- (decltype(ov_callbacks::read_func))&vorbisReadCb,
- (decltype(ov_callbacks::seek_func))&vorbisSeekCb,
- (decltype(ov_callbacks::close_func))&vorbisCloseCb,
- (decltype(ov_callbacks::tell_func))&vorbisTellCb,
- };
- }
- TrackPlayer::~TrackPlayer() {
- isRunning = false;
- std::scoped_lock lock(runningMutex);
- }
- void TrackPlayer::start() {
- if (!isRunning) {
- isRunning = true;
- startTask();
- }
- }
- void TrackPlayer::stop() {
- isRunning = false;
- resetState();
- std::scoped_lock lock(runningMutex);
- }
- void TrackPlayer::resetState() {
- // Mark for reset
- this->pendingReset = true;
- this->currentSongPlaying = false;
- std::scoped_lock lock(dataOutMutex);
- CSPOT_LOG(info, "Resetting state");
- }
- void TrackPlayer::seekMs(size_t ms) {
- if (inFuture) {
- // We're in the middle of the next track, so we need to reset the player in order to seek
- resetState();
- }
- CSPOT_LOG(info, "Seeking...");
- this->pendingSeekPositionMs = ms;
- }
- void TrackPlayer::runTask() {
- std::scoped_lock lock(runningMutex);
- std::shared_ptr<QueuedTrack> track, newTrack = nullptr;
- int trackOffset = 0;
- bool eof = false;
- bool endOfQueueReached = false;
- while (isRunning) {
- // Ensure we even have any tracks to play
- if (!this->trackQueue->hasTracks() ||
- (endOfQueueReached && trackQueue->isFinished())) {
- this->trackQueue->playableSemaphore->twait(300);
- continue;
- }
- // Last track was interrupted, reset to default
- if (pendingReset) {
- track = nullptr;
- pendingReset = false;
- inFuture = false;
- }
- endOfQueueReached = false;
- // Wait 800ms. If next reset is requested in meantime, restart the queue.
- // Gets rid of excess actions during rapid queueing
- BELL_SLEEP_MS(50);
- if (pendingReset) {
- continue;
- }
- newTrack = trackQueue->consumeTrack(track, trackOffset);
- if (newTrack == nullptr) {
- if (trackOffset == -1) {
- // Reset required
- track = nullptr;
- }
- BELL_SLEEP_MS(100);
- continue;
- }
- track = newTrack;
- inFuture = trackOffset > 0;
- if (track->state != QueuedTrack::State::READY) {
- track->loadedSemaphore->twait(5000);
- if (track->state != QueuedTrack::State::READY) {
- CSPOT_LOG(error, "Track failed to load, skipping it");
- this->eofCallback();
- continue;
- }
- }
- CSPOT_LOG(info, "Got track ID=%s", track->identifier.c_str());
- currentSongPlaying = true;
- {
- std::scoped_lock lock(playbackMutex);
- currentTrackStream = track->getAudioFile();
- // Open the stream
- currentTrackStream->openStream();
- if (pendingReset || !currentSongPlaying) {
- continue;
- }
- if (trackOffset == 0 && pendingSeekPositionMs == 0) {
- this->trackLoaded(track);
- }
- int32_t r =
- ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks);
- if (pendingSeekPositionMs > 0) {
- track->requestedPosition = pendingSeekPositionMs;
- }
- if (track->requestedPosition > 0) {
- VORBIS_SEEK(&vorbisFile, track->requestedPosition);
- }
- eof = false;
- CSPOT_LOG(info, "Playing");
- while (!eof && currentSongPlaying) {
- // Execute seek if needed
- if (pendingSeekPositionMs > 0) {
- uint32_t seekPosition = pendingSeekPositionMs;
- // Reset the pending seek position
- pendingSeekPositionMs = 0;
- // Seek to the new position
- VORBIS_SEEK(&vorbisFile, seekPosition);
- }
- long ret = VORBIS_READ(&vorbisFile, (char*)&pcmBuffer[0],
- pcmBuffer.size(), ¤tSection);
- if (ret == 0) {
- CSPOT_LOG(info, "EOF");
- // and done :)
- eof = true;
- } else if (ret < 0) {
- CSPOT_LOG(error, "An error has occured in the stream %d", ret);
- currentSongPlaying = false;
- } else {
- if (this->dataCallback != nullptr) {
- auto toWrite = ret;
- while (!eof && currentSongPlaying && !pendingReset && toWrite > 0) {
- int written = 0;
- {
- std::scoped_lock dataOutLock(dataOutMutex);
- // If reset happened during playback, return
- if (!currentSongPlaying || pendingReset)
- break;
- written =
- dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, track->identifier);
- }
- if (written == 0) {
- BELL_SLEEP_MS(50);
- }
- toWrite -= written;
- }
- }
- }
- }
- ov_clear(&vorbisFile);
- CSPOT_LOG(info, "Playing done");
- // always move back to LOADING (ensure proper seeking after last track has been loaded)
- currentTrackStream = nullptr;
- }
- if (eof) {
- if (trackQueue->isFinished()) {
- endOfQueueReached = true;
- }
- this->eofCallback();
- }
- }
- }
- size_t TrackPlayer::_vorbisRead(void* ptr, size_t size, size_t nmemb) {
- if (this->currentTrackStream == nullptr) {
- return 0;
- }
- return this->currentTrackStream->readBytes((uint8_t*)ptr, nmemb * size);
- }
- size_t TrackPlayer::_vorbisClose() {
- return 0;
- }
- int TrackPlayer::_vorbisSeek(int64_t offset, int whence) {
- if (this->currentTrackStream == nullptr) {
- return 0;
- }
- switch (whence) {
- case 0:
- this->currentTrackStream->seek(offset); // Spotify header offset
- break;
- case 1:
- this->currentTrackStream->seek(this->currentTrackStream->getPosition() +
- offset);
- break;
- case 2:
- this->currentTrackStream->seek(this->currentTrackStream->getSize() +
- offset);
- break;
- }
- return 0;
- }
- long TrackPlayer::_vorbisTell() {
- if (this->currentTrackStream == nullptr) {
- return 0;
- }
- return this->currentTrackStream->getPosition();
- }
- void TrackPlayer::setDataCallback(DataCallback callback) {
- this->dataCallback = callback;
- }
|