A lot of music updates
This commit is contained in:
parent
d28b0ba316
commit
57693f25c9
@ -532,7 +532,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
|
||||
sql::command(this->sql, query,
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":id", pid},
|
||||
variable{":chId", 0},
|
||||
variable{":chId", update.channel_id},
|
||||
variable{":type", permission::SQL_PERM_PLAYLIST},
|
||||
|
||||
variable{":permId", permission_data->name},
|
||||
|
@ -1175,3 +1175,44 @@ void VirtualServer::ensureValidDefaultGroups() {
|
||||
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = admin_channel_group->groupId();
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &channel, const std::shared_ptr<ConnectedClient> &client, const std::string &message) {
|
||||
assert(channel);
|
||||
assert(client);
|
||||
|
||||
auto client_id = client->getClientId();
|
||||
auto channel_id = channel->channelId();
|
||||
auto now = chrono::system_clock::now();
|
||||
|
||||
bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>();
|
||||
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>();
|
||||
for(const auto& client : this->getClients()) {
|
||||
if(client->connectionState() != ConnectionState::CONNECTED)
|
||||
continue;
|
||||
|
||||
auto type = client->getType();
|
||||
if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC)
|
||||
continue;
|
||||
|
||||
auto own_channel = client->currentChannel == channel;
|
||||
if(conversation_private && !own_channel)
|
||||
continue;
|
||||
|
||||
if(type != ClientType::CLIENT_TEAMSPEAK || own_channel) {
|
||||
if(!own_channel && &*client != client) {
|
||||
if(flag_password)
|
||||
continue; /* TODO: Send notification about new message. The client then could request messages via message history */
|
||||
|
||||
if(!client->calculate_and_get_join_state(channel))
|
||||
continue;
|
||||
}
|
||||
client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, client, client_id, channel_id, now, message);
|
||||
}
|
||||
}
|
||||
|
||||
if(!conversation_private) {
|
||||
auto conversations = this->conversation_manager();
|
||||
auto conversation = conversations->get_or_create(channel->channelId());
|
||||
conversation->register_message(client->getClientDatabaseId(), client->getUid(), this->getDisplayName(), now, message);
|
||||
}
|
||||
}
|
@ -270,6 +270,8 @@ namespace ts {
|
||||
std::unique_lock<std::shared_mutex>& /* tree lock */
|
||||
);
|
||||
|
||||
void send_text_message(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */);
|
||||
|
||||
inline int voice_encryption_mode() { return this->_voice_encryption_mode; }
|
||||
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; }
|
||||
protected:
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <MusicPlayer.h>
|
||||
#include <misc/net.h>
|
||||
#include <cstdint>
|
||||
#include <src/music/PlayablePlaylist.h>
|
||||
#include "music/Song.h"
|
||||
#include "../channel/ClientChannelView.h"
|
||||
#include "DataClient.h"
|
||||
@ -294,6 +295,10 @@ namespace ts {
|
||||
}
|
||||
return disconnect_lock;
|
||||
}
|
||||
|
||||
inline bool playlist_subscribed(const std::shared_ptr<ts::music::Playlist>& playlist) const {
|
||||
return this->_subscribed_playlist.lock() == playlist;
|
||||
}
|
||||
protected:
|
||||
std::weak_ptr<ConnectedClient> _this;
|
||||
sockaddr_storage remote_address;
|
||||
@ -362,6 +367,7 @@ namespace ts {
|
||||
|
||||
std::weak_ptr<MusicClient> selectedBot;
|
||||
std::weak_ptr<MusicClient> subscribed_bot;
|
||||
std::weak_ptr<ts::music::Playlist> _subscribed_playlist{};
|
||||
|
||||
virtual void tick(const std::chrono::system_clock::time_point &time);
|
||||
//Locked by everything who has something todo with command handling
|
||||
@ -532,6 +538,7 @@ namespace ts {
|
||||
command_result handleCommandPlaylistList(Command&);
|
||||
command_result handleCommandPlaylistCreate(Command&);
|
||||
command_result handleCommandPlaylistDelete(Command&);
|
||||
command_result handleCommandPlaylistSetSubscription(Command&);
|
||||
|
||||
command_result handleCommandPlaylistPermList(Command&);
|
||||
command_result handleCommandPlaylistAddPerm(Command&);
|
||||
@ -546,6 +553,7 @@ namespace ts {
|
||||
command_result handleCommandPlaylistEdit(Command&);
|
||||
|
||||
command_result handleCommandPlaylistSongList(Command&);
|
||||
command_result handleCommandPlaylistSongSetCurrent(Command&);
|
||||
command_result handleCommandPlaylistSongAdd(Command&);
|
||||
command_result handleCommandPlaylistSongReorder(Command&);
|
||||
command_result handleCommandPlaylistSongRemove(Command&);
|
||||
|
@ -225,6 +225,8 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
|
||||
else if (command == "playlistlist") return this->handleCommandPlaylistList(cmd);
|
||||
else if (command == "playlistcreate") return this->handleCommandPlaylistCreate(cmd);
|
||||
else if (command == "playlistdelete") return this->handleCommandPlaylistDelete(cmd);
|
||||
else if (command == "playlistsetsubscription") return this->handleCommandPlaylistSetSubscription(cmd);
|
||||
|
||||
else if (command == "playlistpermlist") return this->handleCommandPlaylistPermList(cmd);
|
||||
else if (command == "playlistaddperm") return this->handleCommandPlaylistAddPerm(cmd);
|
||||
else if (command == "playlistdelperm") return this->handleCommandPlaylistDelPerm(cmd);
|
||||
@ -235,6 +237,7 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
|
||||
else if (command == "playlistedit") return this->handleCommandPlaylistEdit(cmd);
|
||||
|
||||
else if (command == "playlistsonglist") return this->handleCommandPlaylistSongList(cmd);
|
||||
else if (command == "playlistsongsetcurrent") return this->handleCommandPlaylistSongSetCurrent(cmd);
|
||||
else if (command == "playlistsongadd") return this->handleCommandPlaylistSongAdd(cmd);
|
||||
else if (command == "playlistsongreorder" || command == "playlistsongmove") return this->handleCommandPlaylistSongReorder(cmd);
|
||||
else if (command == "playlistsongremove") return this->handleCommandPlaylistSongRemove(cmd);
|
||||
@ -557,40 +560,7 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) {
|
||||
return command_result{permission::b_client_channel_textmessage_send}; /* You're not allowed to send messages :) */
|
||||
}
|
||||
|
||||
auto message = cmd["msg"].string();
|
||||
auto _this = this->_this.lock();
|
||||
auto client_id = this->getClientId();
|
||||
|
||||
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>();
|
||||
for(const auto& client : this->server->getClients()) {
|
||||
if(client->connectionState() != ConnectionState::CONNECTED)
|
||||
continue;
|
||||
|
||||
auto type = client->getType();
|
||||
if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC)
|
||||
continue;
|
||||
|
||||
auto own_channel = client->currentChannel == this->currentChannel;
|
||||
if(conversation_private && !own_channel)
|
||||
continue;
|
||||
|
||||
if(type != ClientType::CLIENT_TEAMSPEAK || own_channel) {
|
||||
if(!own_channel && &*client != this) {
|
||||
if(flag_password)
|
||||
continue; /* TODO: Send notification about new message. The client then could request messages via message history */
|
||||
|
||||
if(!client->calculate_and_get_join_state(channel))
|
||||
continue;
|
||||
}
|
||||
client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, _this, client_id, channel_id, timestamp, message);
|
||||
}
|
||||
}
|
||||
|
||||
if(!conversation_private) {
|
||||
auto conversations = this->server->conversation_manager();
|
||||
auto conversation = conversations->get_or_create(channel->channelId());
|
||||
conversation->register_message(this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), timestamp, cmd["msg"].string());
|
||||
}
|
||||
this->server->send_text_message(channel, this->ref(), cmd["msg"].string());
|
||||
} else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_server_textmessage_send, 1);
|
||||
|
||||
|
@ -798,6 +798,23 @@ command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd)
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandPlaylistSongSetCurrent(ts::Command &cmd) {
|
||||
CMD_REF_SERVER(ref_server);
|
||||
CMD_RESET_IDLE;
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
||||
|
||||
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
|
||||
if(!playlist) return command_result{error::playlist_invalid_id};
|
||||
|
||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_move_power, permission::i_playlist_song_move_power); perr)
|
||||
return command_result{perr};
|
||||
|
||||
if(!playlist->set_current_song(cmd["song_id"]))
|
||||
return command_result{error::playlist_invalid_song_id};
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) {
|
||||
CMD_REF_SERVER(ref_server);
|
||||
CMD_RESET_IDLE;
|
||||
@ -823,10 +840,6 @@ command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) {
|
||||
auto song = playlist->add_song(_this.lock(), cmd["url"], cmd["invoker"], cmd["previous"]);
|
||||
if(!song) return command_result{error::vs_critical};
|
||||
|
||||
Command notify(this->notify_response_command("notifyplaylistsongadd"));
|
||||
notify["song_id"] = song->id;
|
||||
this->sendCommand(notify);
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@ -1051,6 +1064,33 @@ command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandPlaylistSetSubscription(ts::Command &cmd) {
|
||||
CMD_REF_SERVER(ref_server);
|
||||
CMD_RESET_IDLE;
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
||||
|
||||
if(!config::music::enabled) return command_result{error::music_disabled};
|
||||
|
||||
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
|
||||
if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id};
|
||||
|
||||
{
|
||||
auto old_playlist = this->_subscribed_playlist.lock();
|
||||
if(old_playlist)
|
||||
old_playlist->remove_subscriber(_this.lock());
|
||||
}
|
||||
|
||||
if(playlist) {
|
||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr)
|
||||
return command_result{perr};
|
||||
|
||||
playlist->add_subscriber(_this.lock());
|
||||
this->_subscribed_playlist = playlist;
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -159,8 +159,7 @@ void MusicClient::initialize_bot() {
|
||||
}
|
||||
|
||||
void MusicClient::broadcast_text_message(const std::string &message) {
|
||||
for(const auto& cl : this->server->getClientsByChannel<VoiceClient>(this->currentChannel))
|
||||
cl->sendChannelMessage(_this.lock(), message);
|
||||
this->server->send_text_message(this->currentChannel, this->ref(), message);
|
||||
}
|
||||
|
||||
//https://en.wikipedia.org/wiki/Decibel
|
||||
@ -230,8 +229,10 @@ void MusicClient::handle_event_song_replay_failed() {
|
||||
void MusicClient::replay_next_song() {
|
||||
auto playlist = this->playlist();
|
||||
if(playlist) {
|
||||
auto song = playlist->pop_next();
|
||||
if(song)
|
||||
bool await_load;
|
||||
if(auto song = playlist->current_song(await_load); song)
|
||||
this->replay_song(song);
|
||||
else if(!await_load)
|
||||
playlist->next();
|
||||
}
|
||||
}
|
@ -75,6 +75,7 @@ namespace ts {
|
||||
void player_pause();
|
||||
|
||||
void forwardSong();
|
||||
void resetSong();
|
||||
void rewindSong();
|
||||
|
||||
std::shared_ptr<music::MusicPlayer> current_player();
|
||||
|
@ -150,8 +150,8 @@ void MusicClient::tick(const std::chrono::system_clock::time_point &time) {
|
||||
else if(!this->current_song()) {
|
||||
auto playlist = this->playlist();
|
||||
if(playlist) { /* may bot just got initialized */
|
||||
auto song = playlist->pop_next();
|
||||
if(song) {
|
||||
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)
|
||||
@ -161,9 +161,11 @@ void MusicClient::tick(const std::chrono::system_clock::time_point &time) {
|
||||
else
|
||||
player->play();
|
||||
});
|
||||
} else if(!await_load) {
|
||||
this->replay_next_song();
|
||||
}
|
||||
}
|
||||
if(!playlist || !this->current_song()){
|
||||
if(!playlist || !this->current_song()) {
|
||||
this->changePlayerState(ReplayState::SLEEPING);
|
||||
}
|
||||
}
|
||||
@ -224,14 +226,46 @@ void MusicClient::player_pause() {
|
||||
|
||||
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<bool>()) {
|
||||
playlist->properties()[property::PLAYLIST_FLAG_FINISHED] = false;
|
||||
this->handle_event_song_ended();
|
||||
}
|
||||
if(song == this->current_song())
|
||||
this->player_reset(true);
|
||||
}
|
||||
|
||||
void MusicClient::rewindSong() {
|
||||
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);
|
||||
logError(this->getServerId(), "MusicClient::rewindSong hasn't been implemented yet!");
|
||||
}
|
||||
|
||||
void MusicClient::musicEventHandler(const std::weak_ptr<ts::music::PlayableSong>& weak_player, ::music::MusicEvent event) {
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include "src/VirtualServer.h"
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include <log/LogUtils.h>
|
||||
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
|
||||
#include "MusicPlayer.h"
|
||||
|
||||
using namespace ts;
|
||||
@ -350,10 +349,16 @@ void Playlist::destroy_tree() {
|
||||
}
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<PlaylistEntryInfo>> Playlist::list_songs() {
|
||||
std::deque<std::shared_ptr<PlaylistEntryInfo> > Playlist::list_songs() {
|
||||
unique_lock list_lock(this->playlist_lock);
|
||||
return this->_list_songs(list_lock);
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<PlaylistEntryInfo>> Playlist::_list_songs(const std::unique_lock<std::shared_mutex>& lock) {
|
||||
assert(lock.owns_lock());
|
||||
|
||||
deque<shared_ptr<PlaylistEntryInfo>> result;
|
||||
|
||||
unique_lock list_lock(this->playlist_lock);
|
||||
auto head = this->playlist_head;
|
||||
while(head) {
|
||||
result.push_back(head->entry);
|
||||
@ -402,13 +407,19 @@ std::shared_ptr<PlaylistEntryInfo> Playlist::add_song(ClientDbId client, const s
|
||||
}
|
||||
auto order_entry = this->playlist_find(list_lock, entry->previous_song_id);
|
||||
this->playlist_insert(list_lock, list_entry, order_entry);
|
||||
|
||||
if(order_entry ? order_entry->entry->id : 0 != order || list_entry->modified) {
|
||||
list_entry->modified = false;
|
||||
entry->previous_song_id = list_entry->previous_song ? list_entry->previous_song->entry->id : 0;
|
||||
list_lock.unlock();
|
||||
|
||||
this->sql_apply_changes(entry);
|
||||
} else {
|
||||
list_lock.unlock();
|
||||
}
|
||||
|
||||
this->enqueue_load(entry);
|
||||
this->notify_song_add(entry);
|
||||
this->properties()[property::PLAYLIST_FLAG_FINISHED] = false;
|
||||
return entry;
|
||||
}
|
||||
@ -422,6 +433,7 @@ bool Playlist::delete_song(ts::SongId id) {
|
||||
list_lock.unlock();
|
||||
|
||||
this->sql_remove(song->entry);
|
||||
this->notify_song_remove(song->entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -431,15 +443,18 @@ bool Playlist::reorder_song(ts::SongId song_id, ts::SongId order_id) {
|
||||
auto order = this->playlist_find(list_lock, order_id);
|
||||
if(!song) return false;
|
||||
if(!order && order_id != 0) return false;
|
||||
if(song->previous_song == order) return true;
|
||||
|
||||
if(!this->playlist_reorder(list_lock, song, order)) return false;
|
||||
list_lock.unlock();
|
||||
|
||||
this->sql_flush_all_changes();
|
||||
this->notify_song_reorder(song->entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Playlist::enqueue_load(const std::shared_ptr<ts::music::PlaylistEntryInfo> &entry) {
|
||||
assert(entry);
|
||||
if(entry->load_future) return; /* song has been already loaded or parsed */
|
||||
|
||||
entry->load_start = system_clock::now();
|
||||
@ -525,6 +540,7 @@ void Playlist::execute_async_load(const std::shared_ptr<ts::music::PlaylistEntry
|
||||
auto provider = ::music::manager::resolveProvider(entry->url_loader, entry->url);
|
||||
if(!provider) {
|
||||
entry->load_future->executionFailed("failed to load info provider");
|
||||
this->notify_song_loaded(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -542,11 +558,15 @@ void Playlist::execute_async_load(const std::shared_ptr<ts::music::PlaylistEntry
|
||||
auto casted = static_pointer_cast<UrlPlaylistInfo>(result);
|
||||
} else if(result->type == UrlType::TYPE_STREAM || result->type == UrlType::TYPE_VIDEO) {
|
||||
auto casted = static_pointer_cast<UrlSongInfo>(result);
|
||||
root["length"] = chrono::ceil<chrono::milliseconds>(casted->length).count();
|
||||
root["title"] = casted->title;
|
||||
root["description"] = casted->description;
|
||||
for(const auto& meta : casted->metadata) {
|
||||
root["metadata"][meta.first] = meta.second;
|
||||
if(casted->thumbnail) {
|
||||
if(auto thump = dynamic_pointer_cast<::music::ThumbnailUrl>(casted->thumbnail); thump)
|
||||
root["thumbnail"] = thump->url();
|
||||
}
|
||||
for(const auto& meta : casted->metadata)
|
||||
root["metadata"][meta.first] = meta.second;
|
||||
}
|
||||
|
||||
entry->metadata = root.toStyledString();
|
||||
@ -580,11 +600,109 @@ void Playlist::execute_async_load(const std::shared_ptr<ts::music::PlaylistEntry
|
||||
entry->load_future->executionSucceed(result);
|
||||
entry->loaded = true;
|
||||
|
||||
if(result->type != UrlType::TYPE_PLAYLIST) /* we've already deleted this entry if this is a playlist entry*/
|
||||
/* we've already deleted this entry if this is a playlist entry */
|
||||
if(result->type != UrlType::TYPE_PLAYLIST) {
|
||||
this->sql_apply_changes(entry);
|
||||
this->notify_song_loaded(entry);
|
||||
}
|
||||
} else {
|
||||
entry->load_future->executionFailed("failed to load info: " + info_future.errorMegssage());
|
||||
this->notify_song_loaded(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::add_subscriber(const std::shared_ptr<server::ConnectedClient> &client) {
|
||||
std::lock_guard slock{this->subscriber_lock};
|
||||
this->subscribers.push_back(client);
|
||||
}
|
||||
|
||||
void Playlist::remove_subscriber(const std::shared_ptr<server::ConnectedClient> &tclient) {
|
||||
std::lock_guard slock{this->subscriber_lock};
|
||||
this->subscribers.erase(std::remove_if(this->subscribers.begin(), this->subscribers.end(), [&](const std::weak_ptr<server::ConnectedClient>& wclient) {
|
||||
server::ConnectedLockedClient client{wclient.lock()};
|
||||
if(!client || client == tclient) return true;
|
||||
|
||||
return false;
|
||||
}), this->subscribers.end());
|
||||
}
|
||||
|
||||
inline bool write_song(const std::shared_ptr<PlaylistEntryInfo> &entry, command_builder& builder, size_t index) {
|
||||
if(!entry) {
|
||||
builder.put_unchecked(index, "song_id", "0");
|
||||
return true;
|
||||
}
|
||||
|
||||
builder.put_unchecked(index, "song_id", entry->id);
|
||||
builder.put_unchecked(index, "song_previous_song_id", entry->previous_song_id);
|
||||
|
||||
builder.put_unchecked(index, "song_url", entry->url);
|
||||
builder.put_unchecked(index, "song_url_loader", entry->url_loader);
|
||||
builder.put_unchecked(index, "song_loaded", entry->loaded);
|
||||
|
||||
builder.put_unchecked(index, "song_invoker", entry->invoker);
|
||||
builder.put_unchecked(index, "song_metadata", entry->metadata);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Playlist::broadcast_notify(const ts::command_builder &command) {
|
||||
std::lock_guard slock{this->subscriber_lock};
|
||||
this->subscribers.erase(std::remove_if(this->subscribers.begin(), this->subscribers.end(), [&](const std::weak_ptr<server::ConnectedClient>& wclient) {
|
||||
server::ConnectedLockedClient client{wclient.lock()};
|
||||
if(!client) return true;
|
||||
|
||||
client->sendCommand(command);
|
||||
return false;
|
||||
}), this->subscribers.end());
|
||||
}
|
||||
|
||||
bool Playlist::notify_song_add(const std::shared_ptr<PlaylistEntryInfo> &entry) {
|
||||
command_builder result{"notifyplaylistsongadd"};
|
||||
|
||||
result.put_unchecked(0, "playlist_id", this->playlist_id());
|
||||
write_song(entry, result, 0);
|
||||
|
||||
this->broadcast_notify(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Playlist::notify_song_remove(const std::shared_ptr<PlaylistEntryInfo> &entry) {
|
||||
command_builder result{"notifyplaylistsongremove"};
|
||||
|
||||
result.put_unchecked(0, "playlist_id", this->playlist_id());
|
||||
result.put_unchecked(0, "song_id", entry->id);
|
||||
|
||||
this->broadcast_notify(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Playlist::notify_song_reorder(const std::shared_ptr<PlaylistEntryInfo> &entry) {
|
||||
command_builder result{"notifyplaylistsongreorder"};
|
||||
|
||||
result.put_unchecked(0, "playlist_id", this->playlist_id());
|
||||
result.put_unchecked(0, "song_id", entry->id);
|
||||
result.put_unchecked(0, "song_previous_song_id", entry->previous_song_id);
|
||||
|
||||
this->broadcast_notify(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Playlist::notify_song_loaded(const std::shared_ptr<PlaylistEntryInfo> &entry) {
|
||||
command_builder result{"notifyplaylistsongloaded"};
|
||||
|
||||
result.put_unchecked(0, "playlist_id", this->playlist_id());
|
||||
result.put_unchecked(0, "song_id", entry->id);
|
||||
|
||||
if(entry->load_future && entry->load_future->failed()) {
|
||||
result.put_unchecked(0, "success", "0");
|
||||
result.put_unchecked(0, "load_error_msg", entry->load_future->errorMegssage());
|
||||
} else {
|
||||
result.put_unchecked(0, "success", "1");
|
||||
result.put_unchecked(0, "song_metadata", entry->metadata);
|
||||
}
|
||||
|
||||
this->broadcast_notify(result);
|
||||
return true;
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
#include <shared_mutex>
|
||||
#include <atomic>
|
||||
#include <MusicPlayer.h>
|
||||
#include <query/command3.h>
|
||||
#include "MusicBotManager.h"
|
||||
#include "PlaylistPermissions.h"
|
||||
|
||||
@ -56,7 +57,7 @@ namespace ts {
|
||||
assert(this->entry);
|
||||
|
||||
this->previous_song = song;
|
||||
this->entry->previous_song_id = 0;
|
||||
this->entry->previous_song_id = song ? song->entry->id : 0;
|
||||
this->modified = true;
|
||||
}
|
||||
};
|
||||
@ -75,7 +76,7 @@ namespace ts {
|
||||
Playlist(const std::shared_ptr<MusicBotManager>& /* manager */, const std::shared_ptr<Properties>& /* properties */, std::shared_ptr<permission::v2::PermissionManager> /* permissions */);
|
||||
virtual ~Playlist();
|
||||
|
||||
void load_songs();
|
||||
virtual void load_songs();
|
||||
inline bool loaded() { return this->_songs_loaded; };
|
||||
|
||||
virtual std::deque<std::shared_ptr<PlaylistEntryInfo>> list_songs();
|
||||
@ -103,6 +104,11 @@ namespace ts {
|
||||
|
||||
template <typename T = Playlist, typename std::enable_if<std::is_base_of<Playlist, T>::value, int>::type = 0>
|
||||
inline std::shared_ptr<Playlist> ref() { return std::dynamic_pointer_cast<T>(this->_self.lock()); }
|
||||
|
||||
|
||||
void add_subscriber(const std::shared_ptr<server::ConnectedClient>&);
|
||||
void remove_subscriber(const std::shared_ptr<server::ConnectedClient>&);
|
||||
bool is_subscriber(const std::shared_ptr<server::ConnectedClient>&);
|
||||
protected:
|
||||
virtual void set_self_ref(const std::shared_ptr<Playlist>& /* playlist */);
|
||||
bool is_playlist_owner(ClientDbId database_id) const override { return this->properties()[property::PLAYLIST_OWNER_DBID].as_save<ClientDbId>() == database_id; }
|
||||
@ -117,8 +123,13 @@ namespace ts {
|
||||
std::shared_ptr<server::VirtualServer> get_server();
|
||||
ServerId get_server_id();
|
||||
|
||||
std::shared_mutex playlist_lock;
|
||||
std::shared_ptr<PlaylistEntry> playlist_head;
|
||||
std::shared_mutex playlist_lock{};
|
||||
std::shared_ptr<PlaylistEntry> playlist_head{};
|
||||
|
||||
std::mutex subscriber_lock{};
|
||||
std::deque<std::weak_ptr<server::ConnectedClient>> subscribers{};
|
||||
|
||||
virtual std::deque<std::shared_ptr<PlaylistEntryInfo>> _list_songs(const std::unique_lock<std::shared_mutex>& /* playlist lock */);
|
||||
|
||||
/* playlist functions are threadsave */
|
||||
std::shared_ptr<PlaylistEntry> playlist_find(const std::unique_lock<std::shared_mutex>& /* playlist lock */, SongId /* song */);
|
||||
@ -141,6 +152,12 @@ namespace ts {
|
||||
bool sql_apply_changes(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
|
||||
bool sql_flush_all_changes();
|
||||
|
||||
void broadcast_notify(const command_builder& /* command */);
|
||||
bool notify_song_add(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
|
||||
bool notify_song_remove(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
|
||||
bool notify_song_reorder(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
|
||||
bool notify_song_loaded(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
|
||||
|
||||
void enqueue_load(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
|
||||
void execute_async_load(const std::shared_ptr<ts::music::PlaylistEntryInfo> &/* entry */);
|
||||
};
|
||||
|
@ -14,51 +14,73 @@ PlayablePlaylist::PlayablePlaylist(const std::shared_ptr<MusicBotManager> &handl
|
||||
|
||||
PlayablePlaylist::~PlayablePlaylist() {}
|
||||
|
||||
void PlayablePlaylist::load_songs() {
|
||||
Playlist::load_songs();
|
||||
|
||||
unique_lock playlist_lock(this->playlist_lock);
|
||||
auto song_id = this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as_save<SongId>();
|
||||
auto current_song = this->playlist_find(playlist_lock, song_id);
|
||||
if(!current_song && song_id != 0) {
|
||||
logWarning(this->get_server_id(), "[Playlist] Failed to reinitialize current song index for playlist {}. Song {} is missing.", this->playlist_id(), song_id);
|
||||
this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = 0;
|
||||
}
|
||||
if(current_song) {
|
||||
this->current_loading_entry = current_song->entry;
|
||||
this->enqueue_load(current_song->entry);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayablePlaylist::set_self_ref(const std::shared_ptr<Playlist> &ptr) {
|
||||
Playlist::set_self_ref(ptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<ts::music::PlayableSong> PlayablePlaylist::pop_next() {
|
||||
void PlayablePlaylist::previous() {
|
||||
unique_lock lock(this->currently_playing_lock);
|
||||
|
||||
auto entry = this->loading_entry.lock();
|
||||
if(!entry) {
|
||||
lock.unlock();
|
||||
entry = this->pop_next_entry();
|
||||
lock.lock();
|
||||
this->loading_entry = entry;
|
||||
auto entry = this->playlist_previous_entry();
|
||||
if(entry) this->enqueue_load(entry);
|
||||
this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = entry ? entry->id : 0;
|
||||
this->current_loading_entry = entry;
|
||||
}
|
||||
|
||||
void PlayablePlaylist::next() {
|
||||
unique_lock lock(this->currently_playing_lock);
|
||||
|
||||
auto entry = this->playlist_next_entry();
|
||||
if(entry) this->enqueue_load(entry);
|
||||
this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = entry ? entry->id : 0;
|
||||
this->current_loading_entry = entry;
|
||||
}
|
||||
|
||||
std::shared_ptr<ts::music::PlayableSong> PlayablePlaylist::current_song(bool& await_load) {
|
||||
await_load = false;
|
||||
|
||||
unique_lock lock(this->currently_playing_lock);
|
||||
auto entry = this->current_loading_entry.lock();
|
||||
|
||||
auto id = entry ? entry->id : 0;
|
||||
if(this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as<SongId>() != id) /* should not happen */
|
||||
this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = id;
|
||||
|
||||
auto id = entry ? entry->id : 0;
|
||||
if(this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as<SongId>() != id)
|
||||
this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = id;
|
||||
}
|
||||
if(!entry) return nullptr;
|
||||
|
||||
if(!entry->load_future) {
|
||||
this->loading_entry.reset();
|
||||
return nullptr; /* skip this entry */
|
||||
}
|
||||
if(!entry->load_future) return nullptr; /* skip this entry */
|
||||
|
||||
if(entry->load_future->state() == threads::FutureState::WORKING) {
|
||||
if(entry->load_start + seconds(30) < system_clock::now()) {
|
||||
this->loading_entry.reset();
|
||||
logError(this->get_server_id(), "[PlayList] Failed to load song info. Error: {}. Timeout. Removing song", entry->load_future->errorMegssage());
|
||||
this->delete_song(entry->id); /* acquired playlist */
|
||||
this->delete_song(entry->id); /* delete song because we cant load it */
|
||||
|
||||
return PlayableSong::create({
|
||||
entry->id,
|
||||
entry->url,
|
||||
entry->invoker
|
||||
}, MusicClient::failedLoader("Failed to parse song info. Info loader timeouted!"));
|
||||
}, MusicClient::failedLoader("Song failed to load"));
|
||||
}
|
||||
|
||||
await_load = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
this->loading_entry.reset(); /* reset the loading entry because we're returning it when succeeded, else we skip*/
|
||||
if(!entry->load_future->succeeded() || !entry->load_future->getValue(nullptr)) {
|
||||
//TODO log error within channel?
|
||||
logError(this->get_server_id(), "[PlayList] Failed to load song info. Error: {}. Removing song", entry->load_future->errorMegssage());
|
||||
this->delete_song(entry->id); /* acquired playlist */
|
||||
|
||||
/* return promise */
|
||||
@ -66,7 +88,7 @@ std::shared_ptr<ts::music::PlayableSong> PlayablePlaylist::pop_next() {
|
||||
entry->id,
|
||||
entry->url,
|
||||
entry->invoker
|
||||
}, MusicClient::failedLoader("Failed to parse song info: " + entry->load_future->errorMegssage()));
|
||||
}, MusicClient::failedLoader(entry->load_future->errorMegssage()));
|
||||
}
|
||||
|
||||
auto result = entry->load_future->getValue(nullptr);
|
||||
@ -85,19 +107,19 @@ std::shared_ptr<ts::music::PlayableSong> PlayablePlaylist::pop_next() {
|
||||
}, MusicClient::providerLoader(entry->url_loader));
|
||||
}
|
||||
|
||||
std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::pop_next_entry() {
|
||||
if(!this->playlist_head) return nullptr; /* fuzzy check if we're not maybe empty */
|
||||
|
||||
std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
|
||||
if(!this->playlist_head) return nullptr; /* fuzzy check if we're not empty */
|
||||
auto replay_mode = this->properties()[property::PLAYLIST_REPLAY_MODE].as<ReplayMode::value>();
|
||||
auto songs = this->list_songs();
|
||||
if(songs.empty()) return nullptr; /* we've nothing to play */
|
||||
|
||||
unique_lock playlist_lock(this->playlist_lock);
|
||||
auto current_song = this->playlist_find(playlist_lock, this->currently_playing());
|
||||
|
||||
if(replay_mode == ReplayMode::SINGLE_LOOPED) {
|
||||
if(current_song) return current_song->entry;
|
||||
return songs.front();
|
||||
if(this->playlist_head) return this->playlist_head->entry;
|
||||
|
||||
this->properties()[property::PLAYLIST_FLAG_FINISHED] = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<PlaylistEntryInfo> result;
|
||||
@ -112,13 +134,17 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::pop_next_entry() {
|
||||
result = nullptr;
|
||||
}
|
||||
} else if(replay_mode == ReplayMode::LINEAR_LOOPED || !this->properties()[property::PLAYLIST_FLAG_FINISHED]) {
|
||||
result = songs.front();
|
||||
result = this->playlist_head ? this->playlist_head->entry : nullptr;
|
||||
}
|
||||
} else if(replay_mode == ReplayMode::SHUFFLE) {
|
||||
auto songs = this->_list_songs(playlist_lock);
|
||||
|
||||
//TODO may add a already played list?
|
||||
if(songs.size() == 1) {
|
||||
if(songs.size() == 0) {
|
||||
this->properties()[property::PLAYLIST_FLAG_FINISHED] = true;
|
||||
result = nullptr;
|
||||
} else if(songs.size() == 1) {
|
||||
result = songs.front();
|
||||
} else {
|
||||
size_t index;
|
||||
while(songs[index = (rand() % songs.size())] == (!current_song ? nullptr : current_song->entry)) { }
|
||||
@ -127,18 +153,48 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::pop_next_entry() {
|
||||
}
|
||||
}
|
||||
playlist_lock.unlock();
|
||||
|
||||
if(current_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as<bool>())
|
||||
if(current_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as<bool>()) {
|
||||
this->delete_song(current_song->entry->id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_previous_entry() {
|
||||
if(!this->playlist_head) return nullptr; /* fuzzy check if we're not empty */
|
||||
auto replay_mode = this->properties()[property::PLAYLIST_REPLAY_MODE].as<ReplayMode::value>();
|
||||
|
||||
unique_lock playlist_lock(this->playlist_lock);
|
||||
this->properties()[property::PLAYLIST_FLAG_FINISHED] = false;
|
||||
|
||||
auto current_song = this->playlist_find(playlist_lock, this->currently_playing());
|
||||
if(current_song) {
|
||||
if(replay_mode == ReplayMode::LINEAR || replay_mode == ReplayMode::LINEAR_LOOPED) {
|
||||
if(current_song->previous_song) return current_song->previous_song->entry;
|
||||
if(!this->playlist_head) return nullptr;
|
||||
|
||||
if(replay_mode == ReplayMode::LINEAR)
|
||||
return this->playlist_head->entry;
|
||||
|
||||
auto tail = this->playlist_head;
|
||||
while(tail->next_song) tail = tail->next_song;
|
||||
return tail->entry;
|
||||
} else if(replay_mode == ReplayMode::SINGLE_LOOPED) {
|
||||
return current_song ? current_song->entry : this->playlist_head ? this->playlist_head->entry : nullptr;
|
||||
} else {
|
||||
return current_song ? current_song->entry : nullptr; /* not possible to rewind shuffle mode */
|
||||
}
|
||||
} else {
|
||||
return this->playlist_head ? this->playlist_head->entry : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayablePlaylist::set_current_song(SongId song_id) {
|
||||
{
|
||||
unique_lock playlist_lock(this->playlist_lock);
|
||||
auto current_song = this->playlist_find(playlist_lock, song_id);
|
||||
this->loading_entry = current_song->entry;
|
||||
if(!current_song && song_id != 0) return false;
|
||||
|
||||
this->current_loading_entry = current_song->entry;
|
||||
if(current_song) {
|
||||
this->enqueue_load(current_song->entry);
|
||||
auto id = current_song ? current_song->entry->id : 0;
|
||||
@ -148,7 +204,7 @@ bool PlayablePlaylist::set_current_song(SongId song_id) {
|
||||
}
|
||||
{
|
||||
auto bot = this->current_bot();
|
||||
bot->forwardSong(); /* skip to the song */
|
||||
bot->resetSong(); /* reset the song and use the song which is supposed to be the "current" */
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -27,22 +27,27 @@ namespace ts {
|
||||
PlayablePlaylist(const std::shared_ptr<MusicBotManager>& /* manager */, const std::shared_ptr<Properties>& /* properties */, const std::shared_ptr<permission::v2::PermissionManager>& /* permissions */);
|
||||
virtual ~PlayablePlaylist();
|
||||
|
||||
void load_songs() override;
|
||||
|
||||
inline ReplayMode::value replay_mode() { return this->properties()[property::PLAYLIST_REPLAY_MODE].as<ReplayMode::value>(); }
|
||||
inline void set_replay_mode(ReplayMode::value mode) { this->properties()[property::PLAYLIST_REPLAY_MODE] = mode; }
|
||||
|
||||
inline SongId currently_playing() { return this->properties()[property::PLAYLIST_CURRENT_SONG_ID]; }
|
||||
bool set_current_song(SongId /* song */);
|
||||
|
||||
std::shared_ptr<music::PlayableSong> pop_next();
|
||||
void previous();
|
||||
void next();
|
||||
std::shared_ptr<music::PlayableSong> current_song(bool& await_load);
|
||||
inline std::shared_ptr<server::MusicClient> current_bot() { return this->_current_bot.lock(); }
|
||||
protected:
|
||||
std::mutex currently_playing_lock;
|
||||
std::weak_ptr<PlaylistEntryInfo> loading_entry;
|
||||
std::weak_ptr<PlaylistEntryInfo> current_loading_entry;
|
||||
std::weak_ptr<server::MusicClient> _current_bot;
|
||||
|
||||
void set_self_ref(const std::shared_ptr<Playlist> &ptr) override;
|
||||
|
||||
std::shared_ptr<PlaylistEntryInfo> pop_next_entry();
|
||||
std::shared_ptr<PlaylistEntryInfo> playlist_previous_entry();
|
||||
std::shared_ptr<PlaylistEntryInfo> playlist_next_entry();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -25,5 +25,5 @@ permission::PermissionType PlaylistPermissions::client_has_permissions(
|
||||
return permission::v2::permission_granted(
|
||||
this->permission_manager()->permission_value_flagged(needed_permission),
|
||||
this->calculate_client_specific_permissions(granted_permission, client),
|
||||
flags & do_no_require_granted) ? permission::ok : needed_permission;
|
||||
(flags & do_no_require_granted) == 0) ? permission::ok : granted_permission;
|
||||
}
|
2
shared
2
shared
@ -1 +1 @@
|
||||
Subproject commit 04666982d937a1f14fe7fff22aa735afe7563525
|
||||
Subproject commit 0f920b58bccc90e825dc57ebba133e6834639cf5
|
Loading…
Reference in New Issue
Block a user