forked from platypush/platypush
- 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:
parent
9cf9135eae
commit
1cab75757b
6 changed files with 205 additions and 50 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
<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%">
|
||||
<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>
|
||||
|
||||
|
|
|
@ -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'])):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
Loading…
Reference in a new issue