#include "MusicClient.h" #include #include "src/client/voice/VoiceClient.h" #include #include #include #include #include "../../InstanceHandler.h" using namespace std; using namespace std::chrono; using namespace ts; using namespace ts::server; #define EVENT_HANDLER_NAME "ts_music" void MusicClient::player_reset(bool trigger_change) { unique_lock song_lock(this->current_song_lock); auto song = std::move(this->_current_song); song_lock.unlock(); if(this->playback.task > 0) music::MusicBotManager::tick_music.cancelExecute(this->playback.task); if(song) { auto player = song->get_player(); if(player) { player->unregisterEventHandler(EVENT_HANDLER_NAME); threads::Thread(THREAD_EXECUTE_LATER | THREAD_SAVE_OPERATIONS, [player]{ player->stop(); //May hangs a littlebit up }).name("song stopper").execute().detach(); } if(trigger_change) { this->schedule_stop_broadcast(); this->changePlayerState(ReplayState::SLEEPING); this->_current_song = nullptr; this->notifySongChange(nullptr); } } } //Important: Handle the event first and the set sleeping because sleeping could cause a dry event #define REPLAY_ERROR(this, message) \ do { \ this->player_reset(false); \ this->handle_event_song_replay_failed(); \ if(!this->current_song()) \ this->changePlayerState(ReplayState::SLEEPING);\ this->broadcast_text_message(message); \ return; \ } while(0) void MusicClient::replay_song(const shared_ptr &entry, const std::function&)>& loaded_callback) { if(!entry) { this->notifySongChange(nullptr); this->changePlayerState(ReplayState::SLEEPING); return; } unique_lock song_lock(this->current_song_lock); auto loader = entry->get_loader(this->getServer(), true); if(!loader) REPLAY_ERROR(this, "An error occurred while trying to replay the next song! (loader is empty)"); this->changePlayerState(ReplayState::LOADING); this->_current_song = entry; song_lock.unlock(); weak_ptr weak_self = dynamic_pointer_cast(this->ref()); loader->waitAndGetLater([weak_self, loader, entry, loaded_callback](std::shared_ptr loaded_data){ auto self = weak_self.lock(); if(!self) return; auto player = loaded_data ? loaded_data->player : nullptr; //If song available this should be executed within a second thread ts::music::MusicBotManager::tick_music.execute([weak_self, self, loader, entry, player, loaded_callback] { if(self->current_song() != entry) { debugMessage(self->getServerId(), "Music loaded but not anymore required! Dropping entry"); return; } if(loader->succeeded() && player) { if(!player->initialize(2)) //TODO Channel count dynamic REPLAY_ERROR(self, "An error occurred while trying to replay the next song! (Failed to initialize player. (" + player->error() + "))"); weak_ptr weak_song = entry; player->registerEventHandler(EVENT_HANDLER_NAME, [weak_song, weak_self](::music::MusicEvent event) { auto locked = weak_self.lock(); if(locked) { locked->musicEventHandler(weak_song, event); } }); self->changePlayerState(ReplayState::PAUSED); if(self->properties()[property::CLIENT_FLAG_NOTIFY_SONG_CHANGE].as()) { string invoker = "unknown"; { auto info_list = serverInstance->databaseHelper()->queryDatabaseInfo(self->getServer(), {entry->getInvoker()}); if(!info_list.empty()) { auto info = info_list.front(); invoker = "[URL=client://0/" + info->client_unique_id + "~WolverinDEV]" + info->client_nickname + "[/URL]"; } } auto info = entry->song_loaded_data(); if(info) { auto message = strvar::transform(config::messages::music::song_announcement, strvar::StringValue{"title", info->title}, strvar::StringValue{"description", info->description}, strvar::StringValue{"url", entry->getUrl()}, strvar::StringValue{"invoker", invoker} ); self->broadcast_text_message(message); } else { auto message = strvar::transform(config::messages::music::song_announcement, strvar::StringValue{"title", "unknown title"}, strvar::StringValue{"description", "unknown"}, strvar::StringValue{"url", entry->getUrl()}, strvar::StringValue{"invoker", invoker} ); self->broadcast_text_message(message); } } if(loaded_callback) loaded_callback(player); else self->playMusic(); } else { if(loader->state() == threads::FutureState::WORKING) { REPLAY_ERROR(self, "An error occurred while trying to replay the next song! (Loader load timeout!)"); } else { REPLAY_ERROR(self, "Got an error while trying to load next song: " + loader->errorMegssage()); } } }); }, nullptr, system_clock::now() + seconds(30)); this->notifySongChange(entry); } void MusicClient::tick_server(const std::chrono::system_clock::time_point &time) { ConnectedClient::tick_server(time); if(this->_player_state == ReplayState::SLEEPING) this->handle_event_song_dry(); else if(!this->current_song()) { auto playlist = this->playlist(); if(playlist) { /* may bot just got initialized */ bool await_load; if(auto song = playlist->current_song(await_load); song) { auto player_state = this->_player_state; this->replay_song(song, [player_state](const shared_ptr<::music::MusicPlayer>& player){ if(player_state == ReplayState::STOPPED) player->stop(); else if(player_state == ReplayState::PAUSED) player->pause(); else player->play(); }); } else if(!await_load) { this->replay_next_song(); } } if(!playlist || !this->current_song()) { this->changePlayerState(ReplayState::SLEEPING); } } } std::shared_ptr MusicClient::current_song() { unique_lock song_lock(this->current_song_lock, defer_lock_t{}); if(!song_lock.try_lock_for(milliseconds(5))) return nullptr; /* failed to acquire lock */ return this->_current_song; } std::shared_ptr<::music::MusicPlayer> MusicClient::current_player() { auto song = this->current_song(); return song ? song->get_player() : nullptr; } void MusicClient::playMusic() { auto self = dynamic_pointer_cast(this->ref()); ts::music::MusicBotManager::tick_music.execute([self]{ auto playlist = self->playlist(); if(playlist->properties()[property::PLAYLIST_FLAG_FINISHED].as()) { playlist->properties()[property::PLAYLIST_FLAG_FINISHED] = false; debugMessage(self->getServerId(), "{} Received play, but playlist had finished. Restarting playlist.", CLIENT_STR_LOG_PREFIX_(self)); } auto player = self->current_player(); if(player) player->play(); else debugMessage(self->getServerId(), "Tried to start music without a music player!"); }); } void MusicClient::stopMusic() { auto self = dynamic_pointer_cast(this->ref()); ts::music::MusicBotManager::tick_music.execute([self]{ auto player = self->current_player(); if(player) player->stop(); else debugMessage(self->getServerId(), "Tried to stop music without a music player!"); }); } void MusicClient::player_pause() { auto self = dynamic_pointer_cast(this->ref()); ts::music::MusicBotManager::tick_music.execute([self]{ auto player = self->current_player(); if(player) player->pause(); else debugMessage(self->getServerId(), "Tried to pause music without a music player!"); }); } void MusicClient::forwardSong() { auto song = this->current_song(); auto playlist = this->playlist(); this->handle_event_song_ended(); /* explicitly wanted a "next" song so start over again */ if(playlist->properties()[property::PLAYLIST_FLAG_FINISHED].as()) { playlist->properties()[property::PLAYLIST_FLAG_FINISHED] = false; this->handle_event_song_ended(); } if(song == this->current_song()) this->player_reset(true); } void MusicClient::resetSong() { auto song = this->current_song(); auto playlist = this->playlist(); if(playlist) { bool await_load; if(auto song = playlist->current_song(await_load); song) { this->replay_song(song); return; } } this->player_reset(true); } void MusicClient::rewindSong() { auto song = this->current_song(); auto playlist = this->playlist(); if(playlist) { playlist->previous(); bool await_load; if(auto song = playlist->current_song(await_load); song) { this->replay_song(song); return; } } this->player_reset(true); } void MusicClient::musicEventHandler(const std::weak_ptr& weak_player, ::music::MusicEvent event) { unique_lock song_lock(this->current_song_lock); auto player = weak_player.lock(); if(!player || player != this->_current_song) return; logTrace(this->server ? this->server->getServerId() : 0, "[{}] Got event " + to_string(event), CLIENT_STR_LOG_PREFIX); if(event == ::music::EVENT_PLAY){ debugMessage(this->getServerId(), "[MusicBot] Got play event from music player. Starting bot!"); this->next_music_tick = system_clock::time_point(); this->schedule_music_tick(song_lock, this->_current_song, this->next_music_tick); song_lock.unlock(); this->changePlayerState(ReplayState::PLAYING); } else if(event == ::music::EVENT_PAUSE || event == ::music::EVENT_STOP){ debugMessage(this->getServerId(), "[MusicBot] Got stop or pause event from player. Stopping bot!"); music::MusicBotManager::tick_music.cancelExecute(this->playback.task); this->playback.task = 0; song_lock.unlock(); this->changePlayerState(event == ::music::EVENT_PAUSE ? ReplayState::PAUSED : ReplayState::STOPPED); this->schedule_stop_broadcast(); } else if(event == ::music::EVENT_END) { debugMessage(this->getServerId(), "[MusicBot] Got end event from music player. Playing next song!"); song_lock.unlock(); auto song = this->current_song(); this->handle_event_song_ended(); if(song == this->current_song()) this->player_reset(true); } else if(event == ::music::EVENT_ABORT) { debugMessage(this->getServerId(), "[MusicBot] Got abort event from music player. Playing next song!"); song_lock.unlock(); this->broadcast_text_message("Song replay aborted due to an unrecoverable error. Replaying next song."); auto song = this->current_song(); this->handle_event_song_ended(); if(song == this->current_song()) this->player_reset(true); } else if(event == ::music::EVENT_INFO_UPDATE) { } } void MusicClient::volume_modifier(float vol) { this->playback.volume_modifier = vol; this->properties()[property::CLIENT_PLAYER_VOLUME] = this->playback.volume_modifier; this->server->notifyClientPropertyUpdates(this->ref(), std::deque{property::CLIENT_PLAYER_VOLUME}); } void MusicClient::schedule_music_tick(const unique_lock& song_lock, const std::shared_ptr &song, const std::chrono::system_clock::time_point& timepoint, bool ignore_lock) { if(!ignore_lock) { assert(song_lock.owns_lock() && song_lock.mutex() == &this->current_song_lock); } if(this->_current_song != song) return; //abort scheduling weak_ptr weak_self = dynamic_pointer_cast(this->ref()); weak_ptr weak_song = song; this->playback.task = music::MusicBotManager::tick_music.executeLater([weak_self, weak_song] { shared_ptr self = weak_self.lock(); auto song = weak_song.lock(); if(!self || !song) return; /* we're outdated */ unique_lock song_tick_lock(self->current_song_tick_lock, try_to_lock_t{}); if(!song_tick_lock.owns_lock()) { if(self->current_song_lock_fail++ > 10) { logError(self->getServerId(), "[{}][Music] Failed to invoke next tick over 10 times. Ticking lock is still acquired. Breaking reschedule loop.", CLIENT_STR_LOG_PREFIX_(self)); return; } unique_lock song_lock(self->current_song_lock, try_to_lock_t{}); /* if(!song_lock) { logError(self->getServerId(), "[{}][Music] Failed to invoke next tick. Ticking and song lock is still acquired. Breaking loop", CLIENT_STR_LOG_PREFIX_(self)); } else { */ logError(self->getServerId(), "[{}][Music] Failed to invoke next tick. Ticking lock is still acquired. Rescheduling in 20ms.", CLIENT_STR_LOG_PREFIX_(self)); self->schedule_music_tick(song_lock, song, system_clock::now() + milliseconds(20), true); //} return; //Current tick is still going on } self->current_song_lock_fail = 0; self->execute_music_tick(song); }, timepoint); } void MusicClient::schedule_stop_broadcast() { weak_ptr weak_self = dynamic_pointer_cast(this->ref()); this->playback.task = music::MusicBotManager::tick_music.executeLater([weak_self] { auto self = weak_self.lock(); if(!self) return; /* we're outdated */ unique_lock song_tick_lock(self->current_song_tick_lock, defer_lock_t{}); if(!song_tick_lock.try_lock_for(milliseconds(5))) { logError(self->getServerId(), "[{}][Music] Failed to schedule stop. Ticking lock is still acquired.", CLIENT_STR_LOG_PREFIX_(self)); return; //Current tick is still going on } self->broadcast_music_stop(); }, system_clock::now()); } void MusicClient::broadcast_music_stop() { if(this->voice.last_frame_silence) return; size_t buffer_size = 750; size_t voice_header_length = 5; char voice_buffer[buffer_size]; this->voice.last_frame_silence = true; le2be16(this->voice.packet_id++, voice_buffer, 0); le2be16(this->getClientId(), voice_buffer, 2); voice_buffer[4] = 5; //Voice type opus music SpeakingClient::VoicePacketFlags flags{}; for(const auto& cl : this->server->getClientsByChannel(this->currentChannel)) { if(cl->shouldReceiveVoice(this->ref())) { cl->send_voice_packet(pipes::buffer_view{voice_buffer, voice_header_length}, flags); } } } void MusicClient::execute_music_tick(const shared_ptr& song) { unique_lock song_lock(this->current_song_lock); if(this->_current_song != song) return; /* old music */ auto player = song->get_player(); if(!player || player->state() != ::music::STATE_PLAYING) return; song_lock.unlock(); system_clock::time_point next_schedule; { if(this->next_music_tick.time_since_epoch().count() == 0) this->next_music_tick = system_clock::now(); else if(this->next_music_tick + milliseconds(10) < system_clock::now()) { logWarning(this->getServerId(), "[{}] Resend task contained a delay of over 10ms! ({}ms)", CLIENT_STR_LOG_PREFIX, duration_cast(system_clock::now() - this->next_music_tick).count()); } if(this->next_music_tick + milliseconds(20) < system_clock::now()) { this->next_music_tick = system_clock::now(); } else this->next_music_tick += milliseconds(20); next_schedule = this->next_music_tick; } auto timestamp_begin = system_clock::now(); size_t buffer_size = 750; size_t voice_header_length = 5; char voice_buffer[buffer_size]; auto next_segment = player->popNextSegment(); if(next_segment) { this->apply_volume(next_segment); ssize_t length = 0; if(next_segment->segmentLength == player->preferredSampleCount()) length = opus_encode(this->voice.encoder, next_segment->segments, next_segment->segmentLength, reinterpret_cast(&voice_buffer[voice_header_length]), buffer_size - voice_header_length); if(length <= 0) { logError(this->getServerId(), "[{}] opus_encode(...) returns invalid code: {}", CLIENT_STR_LOG_PREFIX, length); } else { le2be16(this->voice.packet_id++, voice_buffer, 0); le2be16(this->getClientId(), voice_buffer, 2); voice_buffer[4] = 5; //Voice type opus music SpeakingClient::VoicePacketFlags flags{}; /* test if we've to recover from silence */ if(this->voice.last_frame_silence) { this->voice.frame_header = 5; this->voice.last_frame_silence = false; } if(this->voice.frame_header > 0) { this->voice.frame_header--; flags.head = true; } auto buffer = pipes::buffer_view{voice_buffer, voice_header_length + length}; for(const auto& cl : this->server->getClientsByChannel(this->currentChannel)) if(cl->shouldReceiveVoice(this->ref())) cl->send_voice_packet(buffer, flags); } } else { this->broadcast_music_stop(); } auto timestamp_begin_subscriber = system_clock::now(); { auto now = system_clock::now(); std::deque> subs; { lock_guard sub_lock(this->subscriber_lock); for(auto& sub : this->subscribers) { auto& time_point = std::get<1>(sub); if(time_point > now) continue; if(time_point.time_since_epoch().count() == 0) time_point = now; time_point = time_point + seconds(1); /* TODO allow this interval to be configurable */ auto client = std::get<0>(sub).lock(); if(!client) continue; subs.push_back(std::move(client)); } } auto self = dynamic_pointer_cast(this->ref()); for(const auto& sub : subs) sub->notifyMusicPlayerStatusUpdate(self); } auto timestamp_begin_schedule = system_clock::now(); song_lock.lock(); if(player->state() != ::music::STATE_PLAYING) return; /* status had been changed */ this->schedule_music_tick(song_lock, song, next_schedule); auto timestamp_end = system_clock::now(); if(timestamp_end - timestamp_begin > milliseconds(5)) { logWarning(this->getServerId(), "[{}] Ticking used more than 5ms to proceed ({}ms). Details: encode={},subscriber={},schedule={}.", CLIENT_STR_LOG_PREFIX, duration_cast(timestamp_end - timestamp_begin).count(), duration_cast(timestamp_begin_subscriber - timestamp_begin).count(), duration_cast(timestamp_begin_schedule - timestamp_begin_subscriber).count(), duration_cast(timestamp_end - timestamp_begin_schedule).count() ); } }