Skip to content

Commit

Permalink
Feat: Add resume for albums #1338
Browse files Browse the repository at this point in the history
  • Loading branch information
jcorporation committed Sep 1, 2024
1 parent 380557c commit 79e1818
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 3 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ An another notable feature is the new list view that supplements the table and g
- MYMPD_API_QUEUE_APPEND_PLAYLIST_RANGE: new
- MYMPD_API_QUEUE_INSERT_PLAYLIST_RANGE: new
- MYMPD_API_QUEUE_REPLACE_PLAYLIST_RANGE: new
- MYMPD_API_QUEUE_APPEND_ALBUM_RANGE: new
- MYMPD_API_QUEUE_INSERT_ALBUM_RANGE: new
- MYMPD_API_QUEUE_REPLACE_ALBUM_RANGE: new
- MYMPD_API_SETTINGS_GET: returns now available sticker types

### Changelog

- Feat: Resume for songs
- Feat: Resume for songs, playlists and albums #1338
- Feat: Rating for albums and playlists #1134
- Feat: User defined stickers #1091
- Feat: Add list view
Expand Down
54 changes: 54 additions & 0 deletions htdocs/js/album.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,57 @@ function addAlbumDisc(action, albumId, disc) {
logError('Invalid action: ' + action);
}
}

/**
* Resume album API implementation.
* Load the album from last played song and start playing.
* @param {string} albumId Album ID
* @param {number} pos Position of first song to resume
* @param {string} action Action
* @returns {void}
*/
function resumeAlbum(albumId, pos, action) {
pos++;
switch(action) {
case 'append':
case 'appendPlay':
sendAPI("MYMPD_API_QUEUE_APPEND_ALBUM_RANGE", {
'albumid': albumId,
'start': pos,
'end': -1,
'play': true
}, null, false);
break;
case 'insert':
sendAPI('MYMPD_API_QUEUE_INSERT_ALBUM_RANGE', {
'albumid': albumId,
'start': pos,
'end': -1,
'play': true,
'to': 0,
'whence': 0
}, null, false);
break;
case 'insertAfterCurrent':
case 'insertPlayAfterCurrent':
sendAPI('MYMPD_API_QUEUE_INSERT_ALBUM_RANGE', {
'albumid': albumId,
'start': pos,
'end': -1,
'play': true,
'to': 0,
'whence': 1
}, null, false);
break;
case 'replace':
case 'replacePlay':
sendAPI("MYMPD_API_QUEUE_REPLACE_ALBUM_RANGE", {
'albumid': albumId,
'start': pos,
'end': -1,
'play': true
}, null, false);
break;
// No default
}
}
33 changes: 31 additions & 2 deletions htdocs/js/apidoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ const APImethods = {
}
},
"MYMPD_API_QUEUE_INSERT_ALBUMS": {
"desc": "Adds the albums to distinct position in the queue.",
"desc": "Inserts the albums to distinct position in the queue.",
"params": {
"albumids": APIparams.albumids,
"to": APIparams.to,
Expand All @@ -511,7 +511,7 @@ const APImethods = {
}
},
"MYMPD_API_QUEUE_INSERT_ALBUM_DISC": {
"desc": "Adds one discs from an album to distinct position in the queue.",
"desc": "Inserts one discs from an album to distinct position in the queue.",
"params": {
"albumid": APIparams.albumid,
"disc": APIparams.disc,
Expand All @@ -520,6 +520,17 @@ const APImethods = {
"play": APIparams.play
}
},
"MYMPD_API_QUEUE_INSERT_ALBUM_RANGE": {
"desc": "Inserts a range of song from an album into the queue",
"params": {
"albumid": APIparams.albumid,
"start": APIparams.start,
"end": APIparams.end,
"to": APIparams.to,
"whence": APIparams.whence,
"play": APIparams.play
}
},
"MYMPD_API_QUEUE_APPEND_PLAYLISTS": {
"desc": "Appends the playlists to the queue.",
"params": {
Expand Down Expand Up @@ -581,6 +592,15 @@ const APImethods = {
"play": APIparams.play
}
},
"MYMPD_API_QUEUE_APPEND_ALBUM_RANGE": {
"desc": "Appends one disc of an album to the queue",
"params": {
"albumid": APIparams.albumid,
"start": APIparams.start,
"end": APIparams.end,
"play": APIparams.play
}
},
"MYMPD_API_QUEUE_REPLACE_PLAYLISTS": {
"desc": "Replaces the queue with the playlists.",
"params": {
Expand Down Expand Up @@ -642,6 +662,15 @@ const APImethods = {
"play": APIparams.play
}
},
"MYMPD_API_QUEUE_REPLACE_ALBUM_RANGE": {
"desc": "Replaces the queue with a range of song from an album",
"params": {
"albumid": APIparams.albumid,
"start": APIparams.start,
"end": APIparams.end,
"play": APIparams.play
}
},
"MYMPD_API_QUEUE_SHUFFLE": {
"desc": "Shuffles the queue.",
"params": {}
Expand Down
17 changes: 17 additions & 0 deletions htdocs/js/clickActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,3 +459,20 @@ function clickResumePlist(event) {
const action = event.target.getAttribute('data-action');
resumePlist(uri, pos, action);
}

/**
* Handler for resume album dropdown actions
* @param {Event} event Click event
* @returns {void}
*/
function clickResumeAlbum(event) {
event.preventDefault();
if (event.target.nodeName !== 'BUTTON') {
return;
}
const dataNode = event.target.closest('.btn-group');
const pos = getData(dataNode, 'pos');
const albumId = getDataId('viewDatabaseAlbumDetailCover', 'AlbumId');
const action = event.target.getAttribute('data-action');
resumeAlbum(albumId, pos, action);
}
1 change: 1 addition & 0 deletions htdocs/js/playlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ function isMPDplaylist(uri) {
* @returns {void}
*/
function resumePlist(plist, pos, action) {
pos++;
switch(action) {
case 'append':
case 'appendPlay':
Expand Down
22 changes: 22 additions & 0 deletions htdocs/js/viewBrowseDatabase.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,28 @@ function parseAlbumDetails(obj) {
infoEl.appendChild(mbField);
}

if (obj.result.lastPlayedSong.uri !== '' &&
obj.result.lastPlayedSong.pos < obj.result.totalEntities - 1)
{
const resumeBtn = pEl.resumeBtn.cloneNode(true);
resumeBtn.classList.add('ms-3', 'dropdown');
resumeBtn.classList.remove('dropup');
setData(resumeBtn, 'pos', obj.result.lastPlayedSong.pos);
new BSN.Dropdown(resumeBtn.firstElementChild);
resumeBtn.lastElementChild.firstElementChild.addEventListener('click', function(event) {
clickResumeAlbum(event);
}, false);
infoEl.appendChild(
elCreateNodes('div', {"class": ["col-xl-6"]}, [
elCreateTextTn('small', {}, 'Last played'),
elCreateNodes('p', {}, [
document.createTextNode(obj.result.lastPlayedSong.title),
resumeBtn
])
])
);
}

const rowTitle = tn(settingsWebuiFields.clickSong.validValues[settings.webuiSettings.clickSong]);
updateTable(obj, 'BrowseDatabaseAlbumDetail', function(row, data) {
setData(row, 'type', 'song');
Expand Down
3 changes: 3 additions & 0 deletions src/lib/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
X(MYMPD_API_QUEUE_APPEND_URI_RESUME) \
X(MYMPD_API_QUEUE_APPEND_ALBUMS) \
X(MYMPD_API_QUEUE_APPEND_ALBUM_DISC) \
X(MYMPD_API_QUEUE_APPEND_ALBUM_RANGE) \
X(MYMPD_API_QUEUE_CLEAR) \
X(MYMPD_API_QUEUE_CROP) \
X(MYMPD_API_QUEUE_CROP_OR_CLEAR) \
Expand All @@ -167,6 +168,7 @@
X(MYMPD_API_QUEUE_INSERT_URI_RESUME) \
X(MYMPD_API_QUEUE_INSERT_ALBUMS) \
X(MYMPD_API_QUEUE_INSERT_ALBUM_DISC) \
X(MYMPD_API_QUEUE_INSERT_ALBUM_RANGE) \
X(MYMPD_API_QUEUE_MOVE_POSITION) \
X(MYMPD_API_QUEUE_MOVE_RELATIVE) \
X(MYMPD_API_QUEUE_PRIO_SET) \
Expand All @@ -179,6 +181,7 @@
X(MYMPD_API_QUEUE_REPLACE_URI_RESUME) \
X(MYMPD_API_QUEUE_REPLACE_ALBUMS) \
X(MYMPD_API_QUEUE_REPLACE_ALBUM_DISC) \
X(MYMPD_API_QUEUE_REPLACE_ALBUM_RANGE) \
X(MYMPD_API_QUEUE_RM_RANGE) \
X(MYMPD_API_QUEUE_RM_IDS) \
X(MYMPD_API_QUEUE_SAVE) \
Expand Down
36 changes: 36 additions & 0 deletions src/mpd_client/search.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,42 @@ bool mpd_client_search_add_to_queue(struct t_partition_state *partition_state, c
return mympd_check_error_and_recover(partition_state, error, "mpd_search_add_db_songs");
}

/**
* Searches the mpd database for songs by expression and adds the result window to the queue
* @param partition_state pointer to partition specific states
* @param expression mpd search expression
* @param to position to insert the songs, UINT_MAX to append
* @param whence enum mpd_position_whence:
* 0 = MPD_POSITION_ABSOLUTE
* 1 = MPD_POSITION_AFTER_CURRENT
* 2 = MPD_POSITION_BEFORE_CURRENT
* @param sort tag to sort
* @param sortdesc false = ascending, true = descending
* @param start Start of the range (including)
* @param end End of the range (excluding), use UINT_MAX for open end
* @param error pointer to already allocated sds string for the error message
* or NULL to return no response
* @return true on success else false
*/
bool mpd_client_search_add_to_queue_window(struct t_partition_state *partition_state, const char *expression,
unsigned to, enum mpd_position_whence whence, const char *sort, bool sortdesc,
unsigned start, unsigned end, sds *error)
{
if (mpd_search_add_db_songs(partition_state->conn, false) == false ||
mpd_search_add_expression(partition_state->conn, expression) == false ||
mpd_client_add_search_sort_param(partition_state, sort, sortdesc, true) == false ||
mpd_search_add_window(partition_state->conn, start, end) == false ||
add_search_whence_param(partition_state, to, whence) == false)
{
mpd_search_cancel(partition_state->conn);
*error = sdscat(*error, "Error creating MPD search command");
return false;
}
mpd_search_commit(partition_state->conn);
mpd_response_finish(partition_state->conn);
return mympd_check_error_and_recover(partition_state, error, "mpd_search_add_db_songs");
}

/**
* Creates a mpd search expression to find all songs in an album
* @param tag_albumartist albumartist tag
Expand Down
3 changes: 3 additions & 0 deletions src/mpd_client/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ bool mpd_client_search_add_to_plist_window(struct t_partition_state *partition_s
const char *plist, unsigned to, const char *sort, bool sortdesc, unsigned start, unsigned end, sds *error);
bool mpd_client_search_add_to_queue(struct t_partition_state *partition_state, const char *expression,
unsigned to, enum mpd_position_whence whence, const char *sort, bool sortdesc, sds *error);
bool mpd_client_search_add_to_queue_window(struct t_partition_state *partition_state, const char *expression,
unsigned to, enum mpd_position_whence whence, const char *sort, bool sortdesc,
unsigned start, unsigned end, sds *error);

bool mpd_client_add_search_sort_param(struct t_partition_state *partition_state, const char *sort, bool sortdesc, bool check_version);
bool mpd_client_add_search_group_param(struct mpd_connection *conn, enum mpd_tag_type tag);
Expand Down
7 changes: 7 additions & 0 deletions src/mympd_api/browse.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ sds mympd_api_browse_album_detail(struct t_mympd_state *mympd_state, struct t_pa
time_t last_played_max = 0;
sds first_song_uri = sdsempty();
sds last_played_song_uri = sdsempty();
sds last_played_song_title = sdsempty();
unsigned last_played_song_pos = 0;
if (partition_state->config->albums.mode == ALBUM_MODE_SIMPLE) {
// reset album values for simple album mode
album_cache_set_total_time(mpd_album, 0);
Expand Down Expand Up @@ -119,6 +121,8 @@ sds mympd_api_browse_album_detail(struct t_mympd_state *mympd_state, struct t_pa
if (sticker.mympd[STICKER_LAST_PLAYED] > last_played_max) {
last_played_max = (time_t)sticker.mympd[STICKER_LAST_PLAYED];
last_played_song_uri = sds_replace(last_played_song_uri, mpd_song_get_uri(song));
last_played_song_title = sds_replace(last_played_song_title, mpd_song_get_tag(song, MPD_TAG_TITLE, 0));
last_played_song_pos = entities_returned - 1;
}
sticker_struct_clear(&sticker);
}
Expand Down Expand Up @@ -153,6 +157,8 @@ sds mympd_api_browse_album_detail(struct t_mympd_state *mympd_state, struct t_pa
buffer = print_album_tags(buffer, partition_state->mpd_state, &partition_state->mpd_state->tags_album, mpd_album);
buffer = sdscat(buffer, ",\"lastPlayedSong\":{");
buffer = tojson_time(buffer, "time", last_played_max, true);
buffer = tojson_uint(buffer, "pos", last_played_song_pos, true);
buffer = tojson_sds(buffer, "title", last_played_song_uri, true);
buffer = tojson_sds(buffer, "uri", last_played_song_uri, false);
buffer = sdscatlen(buffer, "}", 1);
if (partition_state->mpd_state->feat.stickers == true) {
Expand All @@ -166,6 +172,7 @@ sds mympd_api_browse_album_detail(struct t_mympd_state *mympd_state, struct t_pa
FREE_SDS(expression);
FREE_SDS(first_song_uri);
FREE_SDS(last_played_song_uri);
FREE_SDS(last_played_song_title);
return buffer;
}

Expand Down
31 changes: 31 additions & 0 deletions src/mympd_api/mympd_api_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,37 @@ void mympd_api_handler(struct t_mympd_state *mympd_state, struct t_partition_sta
}
break;
}
case MYMPD_API_QUEUE_APPEND_ALBUM_RANGE:
case MYMPD_API_QUEUE_REPLACE_ALBUM_RANGE: {
if (json_get_string(request->data, "$.params.albumid", 1, NAME_LEN_MAX, &sds_buf1, vcb_isalnum, &parse_error) == true &&
json_get_uint(request->data, "$.params.start", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf1, &parse_error) == true &&
json_get_int(request->data, "$.params.end", -1, MPD_PLAYLIST_LENGTH_MAX, &int_buf1, &parse_error) == true &&
json_get_bool(request->data, "$.params.play", &bool_buf1, &parse_error) == true)
{
rc = (request->cmd_id == MYMPD_API_QUEUE_APPEND_ALBUM_DISC
? mympd_api_queue_append_album_range(partition_state, &mympd_state->album_cache, sds_buf1, uint_buf1, int_buf1, &error)
: mympd_api_queue_replace_album_range(partition_state, &mympd_state->album_cache, sds_buf1, uint_buf1, int_buf1, &error)) &&
mpd_client_queue_check_start_play(partition_state, bool_buf1, &error);
response->data = jsonrpc_respond_with_message_or_error(response->data, request->cmd_id, request->id, rc,
JSONRPC_FACILITY_QUEUE, "Queue updated", error);
}
break;
}
case MYMPD_API_QUEUE_INSERT_ALBUM_RANGE: {
if (json_get_string(request->data, "$.params.albumid", 1, NAME_LEN_MAX, &sds_buf1, vcb_isalnum, &parse_error) == true &&
json_get_uint(request->data, "$.params.to", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf1, &parse_error) == true &&
json_get_uint(request->data, "$.params.whence", 0, 2, &uint_buf2, &parse_error) == true &&
json_get_uint(request->data, "$.params.start", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf3, &parse_error) == true &&
json_get_int(request->data, "$.params.end", -1, MPD_PLAYLIST_LENGTH_MAX, &int_buf1, &parse_error) == true &&
json_get_bool(request->data, "$.params.play", &bool_buf1, &parse_error) == true)
{
rc = mympd_api_queue_insert_album_range(partition_state, &mympd_state->album_cache, sds_buf1, uint_buf3, int_buf1, uint_buf1, uint_buf2, &error) &&
mpd_client_queue_check_start_play(partition_state, bool_buf1, &error);
response->data = jsonrpc_respond_with_message_or_error(response->data, request->cmd_id, request->id, rc,
JSONRPC_FACILITY_QUEUE, "Queue updated", error);
}
break;
}
case MYMPD_API_QUEUE_SAVE:
if (json_get_string(request->data, "$.params.plist", 1, FILENAME_LEN_MAX, &sds_buf1, vcb_isfilename, &parse_error) == true &&
json_get_string(request->data, "$.params.mode", 1, NAME_LEN_MAX, &sds_buf2, vcb_isalnum, &parse_error) == true)
Expand Down
Loading

0 comments on commit 79e1818

Please sign in to comment.