- 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
This commit is contained in:
Fabio Manganiello 2018-02-05 00:55:19 +01:00
parent 9cf9135eae
commit 1cab75757b
6 changed files with 205 additions and 50 deletions

View file

@ -74,14 +74,35 @@ main {
display: block; display: block;
} }
#playlist-content { #player-left-side {
overflow-y: hidden;
}
#player-right-side {
margin-left: 0; margin-left: 0;
width: 78%; width: 78%;
} }
.music-pane { #playlist-controls {
height: 360px; 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; overflow-y: scroll;
}
#music-browser {
padding-top: 0;
overflow-y: scroll;
}
.music-pane {
height: 40rem;
padding: 15px 15px 0 5px; padding: 15px 15px 0 5px;
background: #f8f8f8; background: #f8f8f8;
} }
@ -94,6 +115,38 @@ main {
background-color: #f2f2f2; 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 { .playlist-track > .track-time {
text-align: right; text-align: right;
color: #666; color: #666;

View file

@ -1,6 +1,8 @@
$(document).ready(function() { $(document).ready(function() {
var seekInterval; var seekInterval,
var curTrackElapsed = { trackLongPressTimeout,
curTrackUpdateHandler,
curTrackElapsed = {
timestamp: null, timestamp: null,
elapsed: null, elapsed: null,
}; };
@ -38,6 +40,7 @@ $(document).ready(function() {
var updateControls = function(status, track) { var updateControls = function(status, track) {
var $playbackControls = $('.playback-controls'); var $playbackControls = $('.playback-controls');
var $playlistContent = $('#playlist-content');
var $curTrack = $('.track-info'); var $curTrack = $('.track-info');
var $volumeCtrl = $('#volume-ctrl'); var $volumeCtrl = $('#volume-ctrl');
var $trackSeeker = $('#track-seeker'); var $trackSeeker = $('#track-seeker');
@ -137,16 +140,47 @@ $(document).ready(function() {
if (track) { if (track) {
$curTrack.find('.artist').text(track.artist); $curTrack.find('.artist').text(track.artist);
$curTrack.find('.track').text(track.title); $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) { var onEvent = function(event) {
if ( switch (event.args.type) {
event.args.type === 'platypush.message.event.music.MusicStopEvent' || case 'platypush.message.event.music.MusicStopEvent':
event.args.type === 'platypush.message.event.music.MusicPlayEvent' || case 'platypush.message.event.music.MusicPlayEvent':
event.args.type === 'platypush.message.event.music.MusicPauseEvent' || case 'platypush.message.event.music.MusicPauseEvent':
event.args.type === 'platypush.message.event.music.NewPlayingTrackEvent') { case 'platypush.message.event.music.NewPlayingTrackEvent':
updateControls(status=event.args.status, track=event.args.track); 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); console.log(event);
@ -176,21 +210,39 @@ $(document).ready(function() {
); );
}; };
var initPlaylist = function() { var onTrackTouchDown = function(event) {
execute( var $track = $(this);
{ trackLongPressTimeout = setTimeout(function() {
type: 'request', $track.addClass('selected');
action: 'music.mpd.playlistinfo', clearTimeout(trackLongPressTimeout);
}, trackLongPressTimeout = undefined;
}, 1000);
};
onSuccess = function(response) { 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'); var $playlistContent = $('#playlist-content');
var tracks = response.response.output; $playlistContent.find('.playlist-track').remove();
for (var track of tracks) { for (var track of tracks) {
var $element = $('<div></div>') var $element = $('<div></div>')
.addClass('playlist-track') .addClass('playlist-track')
.addClass('row').addClass('music-item') .addClass('row').addClass('music-item')
.data('track-id', parseInt(track.id))
.data('pos', parseInt(track.pos))
.data('file', track.file); .data('file', track.file);
var $artist = $('<div></div>') var $artist = $('<div></div>')
@ -211,8 +263,27 @@ $(document).ready(function() {
$artist.appendTo($element); $artist.appendTo($element);
$title.appendTo($element); $title.appendTo($element);
$time.appendTo($element); $time.appendTo($element);
$element.on('mousedown touchstart', onTrackTouchDown);
$element.on('mouseup touchend', onTrackTouchUp);
$element.appendTo($playlistContent); $element.appendTo($playlistContent);
} }
if (curTrackUpdateHandler) {
curTrackUpdateHandler();
curTrackUpdateHandler = undefined;
}
}
var initPlaylist = function() {
execute(
{
type: 'request',
action: 'music.mpd.playlistinfo',
},
onSuccess = function(response) {
updatePlaylist(response.response.output);
} }
); );
}; };
@ -261,7 +332,8 @@ $(document).ready(function() {
var initBindings = function() { var initBindings = function() {
window.registerEventListener(onEvent); 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 $volumeCtrl = $('#volume-ctrl');
var $trackSeeker = $('#track-seeker'); var $trackSeeker = $('#track-seeker');
var prevVolume; var prevVolume;

View file

@ -50,14 +50,25 @@
<div class="row"> <div class="row">
<div class="eight columns offset-by-two slider-container" id="volume-ctrl-container"> <div class="eight columns offset-by-two slider-container" id="volume-ctrl-container">
<i class="fa fa-volume-down"></i> &nbsp; <i class="fa fa-volume-down"></i> &nbsp;
<input type="range" min="0" max="100" id="volume-ctrl" class="slider" style="width:90%"> <input type="range" min="0" max="100" id="volume-ctrl" class="slider" style="width:80%">
&nbsp; <i class="fa fa-volume-up"></i> &nbsp; <i class="fa fa-volume-up"></i>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div id="music-browser" class="three columns music-pane"></div> <div id="player-left-side" class="three columns music-pane">
<div id="playlist-content" class="nine columns music-pane"></div> <div id="music-browser" class="music-pane"></div>
</div>
<div id="player-right-side" class="nine columns music-pane">
<div class="row">
<div id="playlist-controls">
<button data-action="clear">
<i class="fa fa-eraser"></i>
</button>
</div>
<div id="playlist-content"></div>
</div>
</div>
</div> </div>

View file

@ -4,8 +4,8 @@ import time
from platypush.backend import Backend from platypush.backend import Backend
from platypush.context import get_plugin from platypush.context import get_plugin
from platypush.message.event.music import \ from platypush.message.event.music import MusicPlayEvent, MusicPauseEvent, \
MusicPlayEvent, MusicPauseEvent, MusicStopEvent, NewPlayingTrackEvent MusicStopEvent, NewPlayingTrackEvent, PlaylistChangeEvent
class MusicMpdBackend(Backend): class MusicMpdBackend(Backend):
@ -27,11 +27,13 @@ class MusicMpdBackend(Backend):
plugin = get_plugin('music.mpd') plugin = get_plugin('music.mpd')
last_state = None last_state = None
last_track = None last_track = None
last_playlist = None
while not self.should_stop(): while not self.should_stop():
status = plugin.status().output status = plugin.status().output
state = status['state'].lower()
track = plugin.currentsong().output track = plugin.currentsong().output
state = status['state'].lower()
playlist = status['playlist']
if state != last_state: if state != last_state:
if state == 'stop': if state == 'stop':
@ -41,6 +43,12 @@ class MusicMpdBackend(Backend):
elif state == 'play': elif state == 'play':
self.bus.post(MusicPlayEvent(status=status, track=track)) 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 if 'title' in track and ('artist' not in track
or not track['artist'] or not track['artist']
or re.search('^tunein:', track['file'])): or re.search('^tunein:', track['file'])):

View file

@ -23,6 +23,11 @@ class MusicPauseEvent(MusicEvent):
super().__init__(*args, **kwargs) 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): class NewPlayingTrackEvent(MusicEvent):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View file

@ -41,6 +41,9 @@ class MusicMpdPlugin(MusicPlugin):
if status == 'play': return self._exec('stop') if status == 'play': return self._exec('stop')
else: return self._exec('play') else: return self._exec('play')
def playid(self, track_id):
return self._exec('playid', track_id)
def next(self): def next(self):
return self._exec('next') return self._exec('next')
@ -111,6 +114,9 @@ class MusicMpdPlugin(MusicPlugin):
def lsinfo(self): def lsinfo(self):
return Response(output=self.client.lsinfo()) return Response(output=self.client.lsinfo())
def plchanges(self, version):
return Response(output=self.client.plchanges(version))
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: