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;
|
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;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var seekInterval;
|
var seekInterval,
|
||||||
var curTrackElapsed = {
|
trackLongPressTimeout,
|
||||||
timestamp: null,
|
curTrackUpdateHandler,
|
||||||
elapsed: null,
|
curTrackElapsed = {
|
||||||
};
|
timestamp: null,
|
||||||
|
elapsed: null,
|
||||||
|
};
|
||||||
|
|
||||||
var execute = function(request, onSuccess, onError, onComplete) {
|
var execute = function(request, onSuccess, onError, onComplete) {
|
||||||
request['target'] = 'localhost';
|
request['target'] = 'localhost';
|
||||||
|
@ -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,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() {
|
var initPlaylist = function() {
|
||||||
execute(
|
execute(
|
||||||
{
|
{
|
||||||
|
@ -184,35 +283,7 @@ $(document).ready(function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess = function(response) {
|
onSuccess = function(response) {
|
||||||
var $playlistContent = $('#playlist-content');
|
updatePlaylist(response.response.output);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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>
|
<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>
|
<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>
|
||||||
|
|
||||||
|
|
|
@ -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'])):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue