- 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
platypush
backend
http
static
templates/plugins
music/mpd
message/event/music
plugins/music/mpd

View file

@ -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;

View file

@ -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 = $('<div></div>')
.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 = $('<div></div>')
.addClass('four').addClass('columns')
.addClass('track-artist').text(track.artist);
var $title = $('<div></div>')
.addClass('six').addClass('columns')
.addClass('track-title').text(track.title);
var $time = $('<div></div>')
.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 = $('<div></div>')
.addClass('playlist-track')
.addClass('row').addClass('music-item')
.data('file', track.file);
var $artist = $('<div></div>')
.addClass('four').addClass('columns')
.addClass('track-artist').text(track.artist);
var $title = $('<div></div>')
.addClass('six').addClass('columns')
.addClass('track-title').text(track.title);
var $time = $('<div></div>')
.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;

View file

@ -50,14 +50,25 @@
<div class="row">
<div class="eight columns offset-by-two slider-container" id="volume-ctrl-container">
<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>
</div>
</div>
</div>
<div class="row">
<div id="music-browser" class="three columns music-pane"></div>
<div id="playlist-content" class="nine columns music-pane"></div>
<div id="player-left-side" class="three columns music-pane">
<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>

View file

@ -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'])):

View file

@ -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)

View file

@ -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: