From 40efb3f9c7ccd88e63694e7f56cb976e7aae6606 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 5 Feb 2018 09:45:35 +0100 Subject: [PATCH] Support for selecting and playing tracks and playlists in web interface --- .../backend/http/static/css/application.css | 28 +- platypush/backend/http/static/js/music.mpd.js | 253 +++++++++++++++--- .../http/templates/plugins/music.mpd.html | 10 +- platypush/plugins/music/mpd/__init__.py | 8 +- 4 files changed, 251 insertions(+), 48 deletions(-) diff --git a/platypush/backend/http/static/css/application.css b/platypush/backend/http/static/css/application.css index 54c8a02b..69d551f2 100644 --- a/platypush/backend/http/static/css/application.css +++ b/platypush/backend/http/static/css/application.css @@ -49,6 +49,11 @@ main { border-radius: 0 0 7.5px 7.5px; } +button[disabled] { + color: #bbb; + border: 1px solid; +} + .playback-controls { text-align: center; border-bottom: 1px solid #e8eaf0; @@ -83,22 +88,24 @@ main { width: 78%; } -#playlist-controls { - text-align: right; +#playlist-controls, #browser-controls { margin-bottom: 7.5px; padding-bottom: 7.5px; border-bottom: 1px solid #ddd; height: 3.8rem; } -#playlist-content { +#playlist-controls { + text-align: right; +} + +#playlist-content, #music-browser { height: 32.2rem; overflow-y: scroll; } #music-browser { padding-top: 0; - overflow-y: scroll; } .music-pane { @@ -109,16 +116,21 @@ main { .music-item { padding: 5px; + cursor: pointer; } + .music-item.selected { + background-color: #c8ffd0 !important; + } + + .music-item:hover { + background-color: #daf8e2 !important; + } + .music-item:nth-child(odd) { background-color: #f2f2f2; } - .playlist-track { - cursor: pointer; - } - .playlist-track.active { height: 4rem; padding-top: 1.5rem; diff --git a/platypush/backend/http/static/js/music.mpd.js b/platypush/backend/http/static/js/music.mpd.js index fc5e8a49..c2b1f363 100644 --- a/platypush/backend/http/static/js/music.mpd.js +++ b/platypush/backend/http/static/js/music.mpd.js @@ -1,6 +1,7 @@ $(document).ready(function() { var seekInterval, - trackLongPressTimeout, + longPressTimeout, + curPath = [], curTrackUpdateHandler, curTrackElapsed = { timestamp: null, @@ -212,16 +213,16 @@ $(document).ready(function() { var onTrackTouchDown = function(event) { var $track = $(this); - trackLongPressTimeout = setTimeout(function() { + longPressTimeout = setTimeout(function() { $track.addClass('selected'); - clearTimeout(trackLongPressTimeout); - trackLongPressTimeout = undefined; + clearTimeout(longPressTimeout); + longPressTimeout = undefined; }, 1000); }; var onTrackTouchUp = function(event) { var $track = $(this); - if (trackLongPressTimeout) { + if (longPressTimeout) { execute({ type: 'request', action: 'music.mpd.playid', @@ -229,8 +230,8 @@ $(document).ready(function() { }); } - clearTimeout(trackLongPressTimeout); - trackLongPressTimeout = undefined; + clearTimeout(longPressTimeout); + longPressTimeout = undefined; }; var updatePlaylist = function(tracks) { @@ -288,6 +289,192 @@ $(document).ready(function() { ); }; + var onPlaylistTouchDown = function(event) { + var $playlist = $(this); + $playlist.addClass('selected'); + + longPressTimeout = setTimeout(function() { + clearTimeout(longPressTimeout); + longPressTimeout = undefined; + }, 1000); + }; + + var onPlaylistTouchUp = function(event) { + var $playlist = $(this); + if (longPressTimeout) { + execute( + { + type: 'request', + action: 'music.mpd.clear' + }, + + onSuccess = function() { + execute( + { + type: 'request', + action: 'music.mpd.load', + args: { playlist: $playlist.data('name') } + } + ); + } + ); + } + + clearTimeout(longPressTimeout); + longPressTimeout = undefined; + }; + + var onFileTouchDown = function(event) { + var $file = $(this); + $file.addClass('selected'); + + longPressTimeout = setTimeout(function() { + clearTimeout(longPressTimeout); + longPressTimeout = undefined; + }, 1000); + }; + + var onFileTouchUp = function(event) { + var $file = $(this); + if (longPressTimeout) { + execute( + { + type: 'request', + action: 'music.mpd.playlistinfo' + }, + + onSuccess = function(response) { + var pos = 0; + if (response.response.output.length > 0) { + pos = parseInt(response.response.output.slice(-1)[0].pos) + 1; + } + + execute( + { + type: 'request', + action: 'music.mpd.add', + args: { resource: $file.data('file') } + }, + + onSuccess = function() { + execute( + { + type: 'request', + action: 'music.mpd.play_pos', + args: { pos: pos } + } + ); + } + ) + } + ); + + $file.removeClass('selected'); + } + + clearTimeout(longPressTimeout); + longPressTimeout = undefined; + }; + + var onDirectorySelect = function(event) { + var $directory = $(this); + execute( + { + type: 'request', + action: 'music.mpd.lsinfo', + args: { uri: $directory.data('name') } + }, + + onSuccess = function(response) { + curPath = $directory.data('name').split('/') + .filter(function(term) { return term.length > 0 }); + + updateBrowser(response.response.output); + } + ); + }; + + var updateBrowser = function(items) { + var $browserContent = $('#music-browser'); + var $addButton = $('#browser-controls').find('button[data-action="add"]'); + var directories = []; + var playlists = []; + var files = []; + + $browserContent.find('.music-item').remove(); + + for (var item of items) { + if ('directory' in item) { + directories.push(item.directory); + } else if ('playlist' in item) { + playlists.push(item.playlist); + } else if ('file' in item) { + files.push(item); + } + } + + if (curPath.length > 0) { + var $parentElement = $('
') + .addClass('browser-directory').addClass('music-item') + .addClass('browser-item').addClass('row').data('name', curPath.slice(0, -1).join('/')) + .html('   ..'); + + $parentElement.on('click touch', onDirectorySelect); + $parentElement.appendTo($browserContent); + $addButton.removeAttr('disabled'); + } else { + $addButton.attr('disabled', 'disabled'); + } + + for (var directory of directories.sort()) { + var $element = $('
') + .addClass('browser-directory').addClass('music-item') + .addClass('browser-item').addClass('row').data('name', directory) + .html('   ' + directory.split('/').slice(-1)[0]); + + $element.on('click touch', onDirectorySelect); + $element.appendTo($browserContent); + } + + for (var playlist of playlists.sort()) { + var $element = $('
') + .addClass('browser-playlist').addClass('music-item') + .addClass('browser-item').addClass('row').data('name', playlist) + .html('   ' + playlist); + + $element.on('mousedown touchstart', onPlaylistTouchDown); + $element.on('mouseup touchend', onPlaylistTouchUp); + $element.appendTo($browserContent); + } + + files = files.sort(function(a, b) { + if (a.artist === b.artist) { + if (a.album === b.album) { + return parseInt(a.track) < parseInt(b.track) ? -1 : 1; + } else { + return a.album.localeCompare(b.album); + } + } else { + return a.artist.localeCompare(b.artist); + } + }); + + for (var file of files) { + var $element = $('
') + .addClass('browser-file').addClass('music-item') + .addClass('browser-item').addClass('row') + .html('   ' + file.title); + + for (var prop of Object.keys(file)) { + $element.data(prop, file[prop]); + } + + $element.on('mousedown touchstart', onFileTouchDown); + $element.on('mouseup touchend', onFileTouchUp); + $element.appendTo($browserContent); + } + }; + var initBrowser = function() { execute( { @@ -296,36 +483,7 @@ $(document).ready(function() { }, onSuccess = function(response) { - var $browserContent = $('#music-browser'); - var items = response.response.output; - var directories = []; - var playlists = []; - - for (var item of items) { - if ('directory' in item) { - directories.push(item.directory); - } else if ('playlist' in item) { - playlists.push(item.playlist); - } - } - - for (var directory of directories.sort()) { - var $element = $('
') - .addClass('browser-directory').addClass('music-item') - .addClass('browser-item').addClass('row') - .html('   ' + directory); - - $element.appendTo($browserContent); - } - - for (var playlist of playlists.sort()) { - var $element = $('
') - .addClass('browser-playlist').addClass('music-item') - .addClass('browser-item').addClass('row') - .html('   ' + playlist); - - $element.appendTo($browserContent); - } + updateBrowser(response.response.output); } ); }; @@ -334,6 +492,7 @@ $(document).ready(function() { window.registerEventListener(onEvent); var $playbackControls = $('.playback-controls, #playlist-controls').find('button'); var $playlistContent = $('#playlist-content'); + var $browserAddBtn = $('#browser-controls').find('button[data-action="add"]'); var $volumeCtrl = $('#volume-ctrl'); var $trackSeeker = $('#track-seeker'); var prevVolume; @@ -408,6 +567,26 @@ $(document).ready(function() { } ); }); + + $browserAddBtn.on('click touch', function(event) { + var $browserContent = $('#music-browser'); + var $items = $browserContent.find('.music-item').slice(1, -1); + var $selectedItems = $browserContent.find('.music-item.selected'); + + if ($selectedItems.length == 0) { + $selectedItems = $items; + } + + for (var item of $selectedItems) { + execute( + { + type: 'request', + action: 'music.mpd.add', + args: { resource: $(item).data('file') } + }, + ); + } + }); }; var init = function() { diff --git a/platypush/backend/http/templates/plugins/music.mpd.html b/platypush/backend/http/templates/plugins/music.mpd.html index c5f6f46f..1553c08a 100644 --- a/platypush/backend/http/templates/plugins/music.mpd.html +++ b/platypush/backend/http/templates/plugins/music.mpd.html @@ -58,8 +58,16 @@
-
+
+
+ +
+
+
+
diff --git a/platypush/plugins/music/mpd/__init__.py b/platypush/plugins/music/mpd/__init__.py index db0e16e9..90afbd0e 100644 --- a/platypush/plugins/music/mpd/__init__.py +++ b/platypush/plugins/music/mpd/__init__.py @@ -28,6 +28,9 @@ class MusicMpdPlugin(MusicPlugin): self.add(resource) return self._exec('play') + def play_pos(self, pos): + return self._exec('play', pos) + def pause(self): status = self.status().output['state'] if status == 'play': return self._exec('pause') @@ -111,8 +114,9 @@ class MusicMpdPlugin(MusicPlugin): return Response(output=sorted(self.client.listplaylists(), key=lambda p: p['playlist'])) - def lsinfo(self): - return Response(output=self.client.lsinfo()) + def lsinfo(self, uri=None): + output = self.client.lsinfo(uri) if uri else self.client.lsinfo() + return Response(output=output) def plchanges(self, version): return Response(output=self.client.plchanges(version))