A lot of music updates

This commit is contained in:
WolverinDEV 2020-02-02 14:58:46 +01:00
parent d28b0ba316
commit 57693f25c9
15 changed files with 392 additions and 99 deletions

View File

@ -532,7 +532,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
sql::command(this->sql, query, sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0}, variable{":serverId", server ? server->getServerId() : 0},
variable{":id", pid}, variable{":id", pid},
variable{":chId", 0}, variable{":chId", update.channel_id},
variable{":type", permission::SQL_PERM_PLAYLIST}, variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":permId", permission_data->name}, variable{":permId", permission_data->name},

View File

@ -1174,4 +1174,45 @@ void VirtualServer::ensureValidDefaultGroups() {
logError(this->serverId, "Using {} ({}) instead!", admin_channel_group->groupId(), admin_channel_group->name()); logError(this->serverId, "Using {} ({}) instead!", admin_channel_group->groupId(), admin_channel_group->name());
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = admin_channel_group->groupId(); 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);
}
} }

View File

@ -270,6 +270,8 @@ namespace ts {
std::unique_lock<std::shared_mutex>& /* tree lock */ 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 int voice_encryption_mode() { return this->_voice_encryption_mode; }
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; } inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; }
protected: protected:

View File

@ -3,6 +3,7 @@
#include <MusicPlayer.h> #include <MusicPlayer.h>
#include <misc/net.h> #include <misc/net.h>
#include <cstdint> #include <cstdint>
#include <src/music/PlayablePlaylist.h>
#include "music/Song.h" #include "music/Song.h"
#include "../channel/ClientChannelView.h" #include "../channel/ClientChannelView.h"
#include "DataClient.h" #include "DataClient.h"
@ -294,6 +295,10 @@ namespace ts {
} }
return disconnect_lock; return disconnect_lock;
} }
inline bool playlist_subscribed(const std::shared_ptr<ts::music::Playlist>& playlist) const {
return this->_subscribed_playlist.lock() == playlist;
}
protected: protected:
std::weak_ptr<ConnectedClient> _this; std::weak_ptr<ConnectedClient> _this;
sockaddr_storage remote_address; sockaddr_storage remote_address;
@ -362,6 +367,7 @@ namespace ts {
std::weak_ptr<MusicClient> selectedBot; std::weak_ptr<MusicClient> selectedBot;
std::weak_ptr<MusicClient> subscribed_bot; 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); virtual void tick(const std::chrono::system_clock::time_point &time);
//Locked by everything who has something todo with command handling //Locked by everything who has something todo with command handling
@ -532,6 +538,7 @@ namespace ts {
command_result handleCommandPlaylistList(Command&); command_result handleCommandPlaylistList(Command&);
command_result handleCommandPlaylistCreate(Command&); command_result handleCommandPlaylistCreate(Command&);
command_result handleCommandPlaylistDelete(Command&); command_result handleCommandPlaylistDelete(Command&);
command_result handleCommandPlaylistSetSubscription(Command&);
command_result handleCommandPlaylistPermList(Command&); command_result handleCommandPlaylistPermList(Command&);
command_result handleCommandPlaylistAddPerm(Command&); command_result handleCommandPlaylistAddPerm(Command&);
@ -546,6 +553,7 @@ namespace ts {
command_result handleCommandPlaylistEdit(Command&); command_result handleCommandPlaylistEdit(Command&);
command_result handleCommandPlaylistSongList(Command&); command_result handleCommandPlaylistSongList(Command&);
command_result handleCommandPlaylistSongSetCurrent(Command&);
command_result handleCommandPlaylistSongAdd(Command&); command_result handleCommandPlaylistSongAdd(Command&);
command_result handleCommandPlaylistSongReorder(Command&); command_result handleCommandPlaylistSongReorder(Command&);
command_result handleCommandPlaylistSongRemove(Command&); command_result handleCommandPlaylistSongRemove(Command&);

View File

@ -225,6 +225,8 @@ command_result ConnectedClient::handleCommand(Command &cmd) {
else if (command == "playlistlist") return this->handleCommandPlaylistList(cmd); else if (command == "playlistlist") return this->handleCommandPlaylistList(cmd);
else if (command == "playlistcreate") return this->handleCommandPlaylistCreate(cmd); else if (command == "playlistcreate") return this->handleCommandPlaylistCreate(cmd);
else if (command == "playlistdelete") return this->handleCommandPlaylistDelete(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 == "playlistpermlist") return this->handleCommandPlaylistPermList(cmd);
else if (command == "playlistaddperm") return this->handleCommandPlaylistAddPerm(cmd); else if (command == "playlistaddperm") return this->handleCommandPlaylistAddPerm(cmd);
else if (command == "playlistdelperm") return this->handleCommandPlaylistDelPerm(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 == "playlistedit") return this->handleCommandPlaylistEdit(cmd);
else if (command == "playlistsonglist") return this->handleCommandPlaylistSongList(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 == "playlistsongadd") return this->handleCommandPlaylistSongAdd(cmd);
else if (command == "playlistsongreorder" || command == "playlistsongmove") return this->handleCommandPlaylistSongReorder(cmd); else if (command == "playlistsongreorder" || command == "playlistsongmove") return this->handleCommandPlaylistSongReorder(cmd);
else if (command == "playlistsongremove") return this->handleCommandPlaylistSongRemove(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 :) */ return command_result{permission::b_client_channel_textmessage_send}; /* You're not allowed to send messages :) */
} }
auto message = cmd["msg"].string(); this->server->send_text_message(channel, this->ref(), 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());
}
} else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) { } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_server_textmessage_send, 1); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_server_textmessage_send, 1);

View File

@ -798,6 +798,23 @@ command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd)
return command_result{error::ok}; 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) { command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) {
CMD_REF_SERVER(ref_server); CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE; 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"]); auto song = playlist->add_song(_this.lock(), cmd["url"], cmd["invoker"], cmd["previous"]);
if(!song) return command_result{error::vs_critical}; 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}; return command_result{error::ok};
} }
@ -1051,6 +1064,33 @@ command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command
return command_result{error::ok}; 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};
}

View File

@ -159,8 +159,7 @@ void MusicClient::initialize_bot() {
} }
void MusicClient::broadcast_text_message(const std::string &message) { void MusicClient::broadcast_text_message(const std::string &message) {
for(const auto& cl : this->server->getClientsByChannel<VoiceClient>(this->currentChannel)) this->server->send_text_message(this->currentChannel, this->ref(), message);
cl->sendChannelMessage(_this.lock(), message);
} }
//https://en.wikipedia.org/wiki/Decibel //https://en.wikipedia.org/wiki/Decibel
@ -230,8 +229,10 @@ void MusicClient::handle_event_song_replay_failed() {
void MusicClient::replay_next_song() { void MusicClient::replay_next_song() {
auto playlist = this->playlist(); auto playlist = this->playlist();
if(playlist) { if(playlist) {
auto song = playlist->pop_next(); bool await_load;
if(song) if(auto song = playlist->current_song(await_load); song)
this->replay_song(song); this->replay_song(song);
else if(!await_load)
playlist->next();
} }
} }

View File

@ -75,6 +75,7 @@ namespace ts {
void player_pause(); void player_pause();
void forwardSong(); void forwardSong();
void resetSong();
void rewindSong(); void rewindSong();
std::shared_ptr<music::MusicPlayer> current_player(); std::shared_ptr<music::MusicPlayer> current_player();

View File

@ -150,8 +150,8 @@ void MusicClient::tick(const std::chrono::system_clock::time_point &time) {
else if(!this->current_song()) { else if(!this->current_song()) {
auto playlist = this->playlist(); auto playlist = this->playlist();
if(playlist) { /* may bot just got initialized */ if(playlist) { /* may bot just got initialized */
auto song = playlist->pop_next(); bool await_load;
if(song) { if(auto song = playlist->current_song(await_load); song) {
auto player_state = this->_player_state; auto player_state = this->_player_state;
this->replay_song(song, [player_state](const shared_ptr<::music::MusicPlayer>& player){ this->replay_song(song, [player_state](const shared_ptr<::music::MusicPlayer>& player){
if(player_state == ReplayState::STOPPED) if(player_state == ReplayState::STOPPED)
@ -161,9 +161,11 @@ void MusicClient::tick(const std::chrono::system_clock::time_point &time) {
else else
player->play(); player->play();
}); });
} else if(!await_load) {
this->replay_next_song();
} }
} }
if(!playlist || !this->current_song()){ if(!playlist || !this->current_song()) {
this->changePlayerState(ReplayState::SLEEPING); this->changePlayerState(ReplayState::SLEEPING);
} }
} }
@ -224,14 +226,46 @@ void MusicClient::player_pause() {
void MusicClient::forwardSong() { void MusicClient::forwardSong() {
auto song = this->current_song(); auto song = this->current_song();
auto playlist = this->playlist();
this->handle_event_song_ended(); 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()) if(song == this->current_song())
this->player_reset(true); 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); 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) { void MusicClient::musicEventHandler(const std::weak_ptr<ts::music::PlayableSong>& weak_player, ::music::MusicEvent event) {

View File

@ -4,7 +4,6 @@
#include "src/VirtualServer.h" #include "src/VirtualServer.h"
#include "src/client/ConnectedClient.h" #include "src/client/ConnectedClient.h"
#include <log/LogUtils.h> #include <log/LogUtils.h>
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
#include "MusicPlayer.h" #include "MusicPlayer.h"
using namespace ts; 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; deque<shared_ptr<PlaylistEntryInfo>> result;
unique_lock list_lock(this->playlist_lock);
auto head = this->playlist_head; auto head = this->playlist_head;
while(head) { while(head) {
result.push_back(head->entry); 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); auto order_entry = this->playlist_find(list_lock, entry->previous_song_id);
this->playlist_insert(list_lock, list_entry, order_entry); this->playlist_insert(list_lock, list_entry, order_entry);
if(order_entry ? order_entry->entry->id : 0 != order || list_entry->modified) { if(order_entry ? order_entry->entry->id : 0 != order || list_entry->modified) {
list_entry->modified = false; list_entry->modified = false;
entry->previous_song_id = list_entry->previous_song ? list_entry->previous_song->entry->id : 0; entry->previous_song_id = list_entry->previous_song ? list_entry->previous_song->entry->id : 0;
list_lock.unlock();
this->sql_apply_changes(entry); this->sql_apply_changes(entry);
} else {
list_lock.unlock();
} }
this->enqueue_load(entry); this->enqueue_load(entry);
this->notify_song_add(entry);
this->properties()[property::PLAYLIST_FLAG_FINISHED] = false; this->properties()[property::PLAYLIST_FLAG_FINISHED] = false;
return entry; return entry;
} }
@ -422,6 +433,7 @@ bool Playlist::delete_song(ts::SongId id) {
list_lock.unlock(); list_lock.unlock();
this->sql_remove(song->entry); this->sql_remove(song->entry);
this->notify_song_remove(song->entry);
return true; 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); auto order = this->playlist_find(list_lock, order_id);
if(!song) return false; if(!song) return false;
if(!order && order_id != 0) 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; if(!this->playlist_reorder(list_lock, song, order)) return false;
list_lock.unlock(); list_lock.unlock();
this->sql_flush_all_changes(); this->sql_flush_all_changes();
this->notify_song_reorder(song->entry);
return true; return true;
} }
void Playlist::enqueue_load(const std::shared_ptr<ts::music::PlaylistEntryInfo> &entry) { 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 */ if(entry->load_future) return; /* song has been already loaded or parsed */
entry->load_start = system_clock::now(); 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); auto provider = ::music::manager::resolveProvider(entry->url_loader, entry->url);
if(!provider) { if(!provider) {
entry->load_future->executionFailed("failed to load info provider"); entry->load_future->executionFailed("failed to load info provider");
this->notify_song_loaded(entry);
return; return;
} }
@ -542,11 +558,15 @@ void Playlist::execute_async_load(const std::shared_ptr<ts::music::PlaylistEntry
auto casted = static_pointer_cast<UrlPlaylistInfo>(result); auto casted = static_pointer_cast<UrlPlaylistInfo>(result);
} else if(result->type == UrlType::TYPE_STREAM || result->type == UrlType::TYPE_VIDEO) { } else if(result->type == UrlType::TYPE_STREAM || result->type == UrlType::TYPE_VIDEO) {
auto casted = static_pointer_cast<UrlSongInfo>(result); auto casted = static_pointer_cast<UrlSongInfo>(result);
root["length"] = chrono::ceil<chrono::milliseconds>(casted->length).count();
root["title"] = casted->title; root["title"] = casted->title;
root["description"] = casted->description; root["description"] = casted->description;
for(const auto& meta : casted->metadata) { if(casted->thumbnail) {
root["metadata"][meta.first] = meta.second; 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(); 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->load_future->executionSucceed(result);
entry->loaded = true; 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->sql_apply_changes(entry);
this->notify_song_loaded(entry);
}
} else { } else {
entry->load_future->executionFailed("failed to load info: " + info_future.errorMegssage()); entry->load_future->executionFailed("failed to load info: " + info_future.errorMegssage());
this->notify_song_loaded(entry);
return; 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;
} }

View File

@ -5,6 +5,7 @@
#include <shared_mutex> #include <shared_mutex>
#include <atomic> #include <atomic>
#include <MusicPlayer.h> #include <MusicPlayer.h>
#include <query/command3.h>
#include "MusicBotManager.h" #include "MusicBotManager.h"
#include "PlaylistPermissions.h" #include "PlaylistPermissions.h"
@ -56,7 +57,7 @@ namespace ts {
assert(this->entry); assert(this->entry);
this->previous_song = song; this->previous_song = song;
this->entry->previous_song_id = 0; this->entry->previous_song_id = song ? song->entry->id : 0;
this->modified = true; 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 */); Playlist(const std::shared_ptr<MusicBotManager>& /* manager */, const std::shared_ptr<Properties>& /* properties */, std::shared_ptr<permission::v2::PermissionManager> /* permissions */);
virtual ~Playlist(); virtual ~Playlist();
void load_songs(); virtual void load_songs();
inline bool loaded() { return this->_songs_loaded; }; inline bool loaded() { return this->_songs_loaded; };
virtual std::deque<std::shared_ptr<PlaylistEntryInfo>> list_songs(); 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> 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()); } 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: protected:
virtual void set_self_ref(const std::shared_ptr<Playlist>& /* playlist */); 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; } 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(); std::shared_ptr<server::VirtualServer> get_server();
ServerId get_server_id(); ServerId get_server_id();
std::shared_mutex playlist_lock; std::shared_mutex playlist_lock{};
std::shared_ptr<PlaylistEntry> playlist_head; 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 */ /* playlist functions are threadsave */
std::shared_ptr<PlaylistEntry> playlist_find(const std::unique_lock<std::shared_mutex>& /* playlist lock */, SongId /* song */); 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_apply_changes(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
bool sql_flush_all_changes(); 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 enqueue_load(const std::shared_ptr<PlaylistEntryInfo>& /* entry */);
void execute_async_load(const std::shared_ptr<ts::music::PlaylistEntryInfo> &/* entry */); void execute_async_load(const std::shared_ptr<ts::music::PlaylistEntryInfo> &/* entry */);
}; };

View File

@ -14,51 +14,73 @@ PlayablePlaylist::PlayablePlaylist(const std::shared_ptr<MusicBotManager> &handl
PlayablePlaylist::~PlayablePlaylist() {} 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) { void PlayablePlaylist::set_self_ref(const std::shared_ptr<Playlist> &ptr) {
Playlist::set_self_ref(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); unique_lock lock(this->currently_playing_lock);
auto entry = this->loading_entry.lock(); auto entry = this->playlist_previous_entry();
if(!entry) { if(entry) this->enqueue_load(entry);
lock.unlock(); this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = entry ? entry->id : 0;
entry = this->pop_next_entry(); this->current_loading_entry = entry;
lock.lock(); }
this->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) return nullptr;
if(!entry->load_future) return nullptr; /* skip this entry */
if(!entry->load_future) {
this->loading_entry.reset();
return nullptr; /* skip this entry */
}
if(entry->load_future->state() == threads::FutureState::WORKING) { if(entry->load_future->state() == threads::FutureState::WORKING) {
if(entry->load_start + seconds(30) < system_clock::now()) { if(entry->load_start + seconds(30) < system_clock::now()) {
this->loading_entry.reset(); this->delete_song(entry->id); /* delete song because we cant load it */
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 */
return PlayableSong::create({ return PlayableSong::create({
entry->id, entry->id,
entry->url, entry->url,
entry->invoker entry->invoker
}, MusicClient::failedLoader("Failed to parse song info. Info loader timeouted!")); }, MusicClient::failedLoader("Song failed to load"));
} }
await_load = true;
return nullptr; 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)) { 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 */ this->delete_song(entry->id); /* acquired playlist */
/* return promise */ /* return promise */
@ -66,7 +88,7 @@ std::shared_ptr<ts::music::PlayableSong> PlayablePlaylist::pop_next() {
entry->id, entry->id,
entry->url, entry->url,
entry->invoker 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); 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)); }, MusicClient::providerLoader(entry->url_loader));
} }
std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::pop_next_entry() { std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
if(!this->playlist_head) return nullptr; /* fuzzy check if we're not maybe empty */ 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 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); unique_lock playlist_lock(this->playlist_lock);
auto current_song = this->playlist_find(playlist_lock, this->currently_playing()); auto current_song = this->playlist_find(playlist_lock, this->currently_playing());
if(replay_mode == ReplayMode::SINGLE_LOOPED) { if(replay_mode == ReplayMode::SINGLE_LOOPED) {
if(current_song) return current_song->entry; 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; std::shared_ptr<PlaylistEntryInfo> result;
@ -112,13 +134,17 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::pop_next_entry() {
result = nullptr; result = nullptr;
} }
} else if(replay_mode == ReplayMode::LINEAR_LOOPED || !this->properties()[property::PLAYLIST_FLAG_FINISHED]) { } 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) { } else if(replay_mode == ReplayMode::SHUFFLE) {
auto songs = this->_list_songs(playlist_lock);
//TODO may add a already played list? //TODO may add a already played list?
if(songs.size() == 1) { if(songs.size() == 0) {
this->properties()[property::PLAYLIST_FLAG_FINISHED] = true; this->properties()[property::PLAYLIST_FLAG_FINISHED] = true;
result = nullptr; result = nullptr;
} else if(songs.size() == 1) {
result = songs.front();
} else { } else {
size_t index; size_t index;
while(songs[index = (rand() % songs.size())] == (!current_song ? nullptr : current_song->entry)) { } 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(); 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); this->delete_song(current_song->entry->id);
}
return result; 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) { bool PlayablePlaylist::set_current_song(SongId song_id) {
{ {
unique_lock playlist_lock(this->playlist_lock); unique_lock playlist_lock(this->playlist_lock);
auto current_song = this->playlist_find(playlist_lock, song_id); 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) { if(current_song) {
this->enqueue_load(current_song->entry); this->enqueue_load(current_song->entry);
auto id = current_song ? current_song->entry->id : 0; 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(); 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; return true;

View File

@ -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 */); PlayablePlaylist(const std::shared_ptr<MusicBotManager>& /* manager */, const std::shared_ptr<Properties>& /* properties */, const std::shared_ptr<permission::v2::PermissionManager>& /* permissions */);
virtual ~PlayablePlaylist(); virtual ~PlayablePlaylist();
void load_songs() override;
inline ReplayMode::value replay_mode() { return this->properties()[property::PLAYLIST_REPLAY_MODE].as<ReplayMode::value>(); } 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 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]; } inline SongId currently_playing() { return this->properties()[property::PLAYLIST_CURRENT_SONG_ID]; }
bool set_current_song(SongId /* song */); 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(); } inline std::shared_ptr<server::MusicClient> current_bot() { return this->_current_bot.lock(); }
protected: protected:
std::mutex currently_playing_lock; 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; std::weak_ptr<server::MusicClient> _current_bot;
void set_self_ref(const std::shared_ptr<Playlist> &ptr) override; 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();
}; };
} }
} }

View File

@ -25,5 +25,5 @@ permission::PermissionType PlaylistPermissions::client_has_permissions(
return permission::v2::permission_granted( return permission::v2::permission_granted(
this->permission_manager()->permission_value_flagged(needed_permission), this->permission_manager()->permission_value_flagged(needed_permission),
this->calculate_client_specific_permissions(granted_permission, client), 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

@ -1 +1 @@
Subproject commit 04666982d937a1f14fe7fff22aa735afe7563525 Subproject commit 0f920b58bccc90e825dc57ebba133e6834639cf5