Added the ability for remote clients to request playback of a previously

snapshotted queue. Also fixed a bug in TrackList where the wrong type of
wrapped ITrack was getting returned, potentially leading to
read-after-free bugs.
This commit is contained in:
casey langen 2018-01-23 16:46:57 -08:00
parent 2b8d40126a
commit 0b3e1b2f19
4 changed files with 67 additions and 41 deletions

View File

@ -150,9 +150,7 @@ TrackPtr TrackList::Get(size_t index) const {
}
ITrack* TrackList::GetTrack(size_t index) const {
auto track = this->Get(index).get();
track->Retain();
return track;
return this->Get(index)->GetSdkValue();
}
int64_t TrackList::GetId(size_t index) const {

View File

@ -179,6 +179,7 @@ namespace request {
static const std::string query_albums = "query_albums";
static const std::string query_tracks_by_category = "query_tracks_by_category";
static const std::string play_all_tracks = "play_all_tracks";
static const std::string play_snapshot_tracks = "play_snapshot_tracks";
static const std::string play_tracks = "play_tracks";
static const std::string play_tracks_by_category = "play_tracks_by_category";
static const std::string query_play_queue_tracks = "query_play_queue_tracks";

View File

@ -410,6 +410,10 @@ void WebSocketServer::HandleRequest(connection_hdl connection, json& request) {
this->RespondWithPlayAllTracks(connection, request);
return;
}
else if (name == request::play_snapshot_tracks) {
this->RespondWithPlaySnapshotTracks(connection, request);
return;
}
else if (name == request::play_tracks) {
this->RespondWithPlayTracks(connection, request);
return;
@ -702,6 +706,7 @@ void WebSocketServer::RespondWithQueryTracksByExternalIds(connection_hdl connect
track = trackList->GetTrack(i);
externalId = GetMetadataString(track, track::ExternalId);
tracks[externalId] = this->ReadTrackMetadata(track);
track->Release();
}
trackList->Release();
@ -752,53 +757,48 @@ void WebSocketServer::RespondWithPlayQueueTracks(connection_hdl connection, json
});
}
else {
bool idsOnly = request[message::options].value(key::ids_only, false);
static auto releaseDeleter = [](ITrack* track) { track->Release(); };
static auto nullDeleter = [](ITrack* track) { };
std::vector<std::shared_ptr<ITrack>> tracks;
/* edit the playlist so it can be changed while we're getting the tracks
out of it. only applicable for the "live" type. */
ITrackListEditor* editor = nullptr;
if (type == value::live) {
editor = context.playback->EditPlaylist();
}
int trackCount = (int)context.playback->Count();
int to = trackCount;
if (offset >= 0 && limit >= 0) {
to = std::min(trackCount, offset + limit);
}
ITrack* track;
std::function<void(ITrack*)> deleter;
if (editor) { deleter = releaseDeleter; }
else { deleter = nullDeleter; }
for (int i = offset; i < to; i++) {
track = editor ? context.playback->GetTrack(i) : playQueueSnapshot->GetTrack(i);
tracks.push_back(std::shared_ptr<ITrack>(track, deleter));
}
if (editor) {
editor->Release();
}
/* now add the tracks to the output. they will be Release()'d automatically
as soon as this scope ends. */
json data = json::array();
bool idsOnly = request[message::options].value(key::ids_only, false);
if (type == value::live) {
/* edit the playlist so it can be changed while we're getting the tracks
out of it. only applicable for the "live" type. */
ITrackListEditor* editor = context.playback->EditPlaylist();
int to = (int)context.playback->Count();
for (auto track : tracks) {
if (idsOnly) {
data.push_back(GetMetadataString(track.get(), key::external_id));
if (offset >= 0 && limit >= 0) {
to = std::min(to, offset + limit);
}
else {
data.push_back(this->ReadTrackMetadata(track.get()));
for (int i = offset; i < to; i++) {
ITrack* track = context.playback->GetTrack(i);
if (idsOnly) { data.push_back(GetMetadataString(track, key::external_id)); }
else { data.push_back(this->ReadTrackMetadata(track)); }
track->Release();
}
editor->Release();
}
else if (type == value::snapshot) {
if (this->playQueueSnapshot) {
int to = (int)this->playQueueSnapshot->Count();
if (offset >= 0 && limit >= 0) {
to = std::min(to, offset + limit);
}
for (int i = offset; i < to; i++) {
ITrack* track = playQueueSnapshot->GetTrack(i);
if (idsOnly) { data.push_back(GetMetadataString(track, key::external_id)); }
else { data.push_back(this->ReadTrackMetadata(track)); }
track->Release();
}
}
}
@ -1064,6 +1064,32 @@ void WebSocketServer::RespondWithPlayAllTracks(connection_hdl connection, json&
RespondWithSuccess(connection, request);
}
void WebSocketServer::RespondWithPlaySnapshotTracks(connection_hdl connection, json& request) {
if (this->playQueueSnapshot) {
size_t index = 0;
double time = 0.0;
if (request.find(message::options) != request.end()) {
index = request[message::options].value(key::index, 0);
time = request[message::options].value(key::time, 0.0);
}
context.playback->Play(this->playQueueSnapshot, index);
if (time > 0.0) {
context.playback->SetPosition(time);
}
}
else {
context.playback->Stop();
auto editor = context.playback->EditPlaylist();
editor->Clear();
editor->Release();
}
RespondWithSuccess(connection, request);
}
void WebSocketServer::RespondWithPlayTracksByCategory(connection_hdl connection, json& request) {
int limit, offset;
ITrackList* tracks = this->QueryTracksByCategory(request, limit, offset);

View File

@ -146,6 +146,7 @@ class WebSocketServer {
void RespondWithListCategories(connection_hdl connection, json& request);
void RespondWithQueryCategory(connection_hdl connection, json& request);
void RespondWithPlayAllTracks(connection_hdl connection, json& request);
void RespondWithPlaySnapshotTracks(connection_hdl connection, json& request);
void RespondWithPlayTracksByCategory(connection_hdl connection, json& request);
void RespondWithEnvironment(connection_hdl connection, json& request);
void RespondWithCurrentTime(connection_hdl connection, json& request);