From 1cab75757bb25fe8981a1a35131929c4fba75f34 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 5 Feb 2018 00:55:19 +0100 Subject: [PATCH] - Support for playlist update events handling on MPD - Support for playlist updates on the web interface - Support for selecting and playing tracks in the web interface --- .../backend/http/static/css/application.css | 59 ++++++- platypush/backend/http/static/js/music.mpd.js | 154 +++++++++++++----- .../http/templates/plugins/music.mpd.html | 17 +- platypush/backend/music/mpd/__init__.py | 14 +- platypush/message/event/music/__init__.py | 5 + platypush/plugins/music/mpd/__init__.py | 6 + 6 files changed, 205 insertions(+), 50 deletions(-) diff --git a/platypush/backend/http/static/css/application.css b/platypush/backend/http/static/css/application.css index 41d0c5d83a..54c8a02b2e 100644 --- a/platypush/backend/http/static/css/application.css +++ b/platypush/backend/http/static/css/application.css @@ -74,14 +74,35 @@ main { display: block; } -#playlist-content { +#player-left-side { + overflow-y: hidden; +} + +#player-right-side { margin-left: 0; width: 78%; } -.music-pane { - height: 360px; +#playlist-controls { + text-align: right; + margin-bottom: 7.5px; + padding-bottom: 7.5px; + border-bottom: 1px solid #ddd; + height: 3.8rem; +} + +#playlist-content { + height: 32.2rem; overflow-y: scroll; +} + +#music-browser { + padding-top: 0; + overflow-y: scroll; +} + +.music-pane { + height: 40rem; padding: 15px 15px 0 5px; background: #f8f8f8; } @@ -94,6 +115,38 @@ main { background-color: #f2f2f2; } + .playlist-track { + cursor: pointer; + } + + .playlist-track.active { + height: 4rem; + padding-top: 1.5rem; + font-size: 1.7rem; + border-radius: 10px; + animation: active-track 5s; + -moz-animation: active-track 5s infinite; + -webkit-animation: active-track 5s infinite; + } + + @keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + + @-moz-keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + + @-webkit-keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + .playlist-track > .track-time { text-align: right; color: #666; diff --git a/platypush/backend/http/static/js/music.mpd.js b/platypush/backend/http/static/js/music.mpd.js index 06c6f4e704..fc5e8a497c 100644 --- a/platypush/backend/http/static/js/music.mpd.js +++ b/platypush/backend/http/static/js/music.mpd.js @@ -1,9 +1,11 @@ $(document).ready(function() { - var seekInterval; - var curTrackElapsed = { - timestamp: null, - elapsed: null, - }; + var seekInterval, + trackLongPressTimeout, + curTrackUpdateHandler, + curTrackElapsed = { + timestamp: null, + elapsed: null, + }; var execute = function(request, onSuccess, onError, onComplete) { request['target'] = 'localhost'; @@ -38,6 +40,7 @@ $(document).ready(function() { var updateControls = function(status, track) { var $playbackControls = $('.playback-controls'); + var $playlistContent = $('#playlist-content'); var $curTrack = $('.track-info'); var $volumeCtrl = $('#volume-ctrl'); var $trackSeeker = $('#track-seeker'); @@ -137,16 +140,47 @@ $(document).ready(function() { if (track) { $curTrack.find('.artist').text(track.artist); $curTrack.find('.track').text(track.title); + + var updatePlayingTrack = function(track) { + return function() { + var $curTrack = $playlistContent.find('.playlist-track').filter( + function() { return $(this).data('pos') == track.pos }); + + if ($curTrack.length === 0) { + return; + } + + var offset = $curTrack.offset().top + - $playlistContent.offset().top + + $playlistContent.scrollTop() - 10; + + $playlistContent.find('.playlist-track').removeClass('active'); + $curTrack.addClass('active'); + $playlistContent.animate({ scrollTop: offset }, 500) + }; + }; + + if ($playlistContent.find('.playlist-track').length === 0) { + // Playlist viewer hasn't loaded yet + curTrackUpdateHandler = updatePlayingTrack(track); + } else { + updatePlayingTrack(track)(); + } } }; var onEvent = function(event) { - if ( - event.args.type === 'platypush.message.event.music.MusicStopEvent' || - event.args.type === 'platypush.message.event.music.MusicPlayEvent' || - event.args.type === 'platypush.message.event.music.MusicPauseEvent' || - event.args.type === 'platypush.message.event.music.NewPlayingTrackEvent') { - updateControls(status=event.args.status, track=event.args.track); + switch (event.args.type) { + case 'platypush.message.event.music.MusicStopEvent': + case 'platypush.message.event.music.MusicPlayEvent': + case 'platypush.message.event.music.MusicPauseEvent': + case 'platypush.message.event.music.NewPlayingTrackEvent': + updateControls(status=event.args.status, track=event.args.track); + break; + + case 'platypush.message.event.music.PlaylistChangeEvent': + updatePlaylist(tracks=event.args.changes); + break; } console.log(event); @@ -176,6 +210,71 @@ $(document).ready(function() { ); }; + var onTrackTouchDown = function(event) { + var $track = $(this); + trackLongPressTimeout = setTimeout(function() { + $track.addClass('selected'); + clearTimeout(trackLongPressTimeout); + trackLongPressTimeout = undefined; + }, 1000); + }; + + var onTrackTouchUp = function(event) { + var $track = $(this); + if (trackLongPressTimeout) { + execute({ + type: 'request', + action: 'music.mpd.playid', + args: { track_id: $track.data('track-id') } + }); + } + + clearTimeout(trackLongPressTimeout); + trackLongPressTimeout = undefined; + }; + + var updatePlaylist = function(tracks) { + var $playlistContent = $('#playlist-content'); + $playlistContent.find('.playlist-track').remove(); + + for (var track of tracks) { + var $element = $('
') + .addClass('playlist-track') + .addClass('row').addClass('music-item') + .data('track-id', parseInt(track.id)) + .data('pos', parseInt(track.pos)) + .data('file', track.file); + + var $artist = $('
') + .addClass('four').addClass('columns') + .addClass('track-artist').text(track.artist); + + var $title = $('
') + .addClass('six').addClass('columns') + .addClass('track-title').text(track.title); + + var $time = $('
') + .addClass('two').addClass('columns') + .addClass('track-time').text( + '' + parseInt(parseInt(track.time)/60) + + ':' + (parseInt(track.time)%60 < 10 ? '0' : '') + + parseInt(track.time)%60); + + $artist.appendTo($element); + $title.appendTo($element); + $time.appendTo($element); + + $element.on('mousedown touchstart', onTrackTouchDown); + $element.on('mouseup touchend', onTrackTouchUp); + $element.appendTo($playlistContent); + } + + if (curTrackUpdateHandler) { + curTrackUpdateHandler(); + curTrackUpdateHandler = undefined; + } + } + var initPlaylist = function() { execute( { @@ -184,35 +283,7 @@ $(document).ready(function() { }, onSuccess = function(response) { - var $playlistContent = $('#playlist-content'); - var tracks = response.response.output; - - for (var track of tracks) { - var $element = $('
') - .addClass('playlist-track') - .addClass('row').addClass('music-item') - .data('file', track.file); - - var $artist = $('
') - .addClass('four').addClass('columns') - .addClass('track-artist').text(track.artist); - - var $title = $('
') - .addClass('six').addClass('columns') - .addClass('track-title').text(track.title); - - var $time = $('
') - .addClass('two').addClass('columns') - .addClass('track-time').text( - '' + parseInt(parseInt(track.time)/60) + - ':' + (parseInt(track.time)%60 < 10 ? '0' : '') + - parseInt(track.time)%60); - - $artist.appendTo($element); - $title.appendTo($element); - $time.appendTo($element); - $element.appendTo($playlistContent); - } + updatePlaylist(response.response.output); } ); }; @@ -261,7 +332,8 @@ $(document).ready(function() { var initBindings = function() { window.registerEventListener(onEvent); - var $playbackControls = $('.playback-controls').find('button'); + var $playbackControls = $('.playback-controls, #playlist-controls').find('button'); + var $playlistContent = $('#playlist-content'); var $volumeCtrl = $('#volume-ctrl'); var $trackSeeker = $('#track-seeker'); var prevVolume; diff --git a/platypush/backend/http/templates/plugins/music.mpd.html b/platypush/backend/http/templates/plugins/music.mpd.html index fd380d7fd6..c5f6f46fc1 100644 --- a/platypush/backend/http/templates/plugins/music.mpd.html +++ b/platypush/backend/http/templates/plugins/music.mpd.html @@ -50,14 +50,25 @@
  - +  
-
-
+
+
+
+
+
+
+ +
+
+
+
diff --git a/platypush/backend/music/mpd/__init__.py b/platypush/backend/music/mpd/__init__.py index 1da0b5ab79..6da2edb5f4 100644 --- a/platypush/backend/music/mpd/__init__.py +++ b/platypush/backend/music/mpd/__init__.py @@ -4,8 +4,8 @@ import time from platypush.backend import Backend from platypush.context import get_plugin -from platypush.message.event.music import \ - MusicPlayEvent, MusicPauseEvent, MusicStopEvent, NewPlayingTrackEvent +from platypush.message.event.music import MusicPlayEvent, MusicPauseEvent, \ + MusicStopEvent, NewPlayingTrackEvent, PlaylistChangeEvent class MusicMpdBackend(Backend): @@ -27,11 +27,13 @@ class MusicMpdBackend(Backend): plugin = get_plugin('music.mpd') last_state = None last_track = None + last_playlist = None while not self.should_stop(): status = plugin.status().output - state = status['state'].lower() track = plugin.currentsong().output + state = status['state'].lower() + playlist = status['playlist'] if state != last_state: if state == 'stop': @@ -41,6 +43,12 @@ class MusicMpdBackend(Backend): elif state == 'play': self.bus.post(MusicPlayEvent(status=status, track=track)) + if playlist != last_playlist: + if last_playlist: + changes = plugin.plchanges(last_playlist).output + self.bus.post(PlaylistChangeEvent(changes=changes)) + last_playlist = playlist + if 'title' in track and ('artist' not in track or not track['artist'] or re.search('^tunein:', track['file'])): diff --git a/platypush/message/event/music/__init__.py b/platypush/message/event/music/__init__.py index 0f95746fd2..7c88a852c9 100644 --- a/platypush/message/event/music/__init__.py +++ b/platypush/message/event/music/__init__.py @@ -23,6 +23,11 @@ class MusicPauseEvent(MusicEvent): super().__init__(*args, **kwargs) +class PlaylistChangeEvent(MusicEvent): + def __init__(self, changes, status=None, track=None, *args, **kwargs): + super().__init__(changes=changes, status=status, track=track, *args, **kwargs) + + class NewPlayingTrackEvent(MusicEvent): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/platypush/plugins/music/mpd/__init__.py b/platypush/plugins/music/mpd/__init__.py index ed04c2d862..db0e16e9e5 100644 --- a/platypush/plugins/music/mpd/__init__.py +++ b/platypush/plugins/music/mpd/__init__.py @@ -41,6 +41,9 @@ class MusicMpdPlugin(MusicPlugin): if status == 'play': return self._exec('stop') else: return self._exec('play') + def playid(self, track_id): + return self._exec('playid', track_id) + def next(self): return self._exec('next') @@ -111,6 +114,9 @@ class MusicMpdPlugin(MusicPlugin): def lsinfo(self): return Response(output=self.client.lsinfo()) + def plchanges(self, version): + return Response(output=self.client.plchanges(version)) + # vim:sw=4:ts=4:et: