forked from platypush/platypush
Support for selecting and playing tracks and playlists in web interface
This commit is contained in:
parent
1cab75757b
commit
40efb3f9c7
4 changed files with 251 additions and 48 deletions
|
@ -49,6 +49,11 @@ main {
|
||||||
border-radius: 0 0 7.5px 7.5px;
|
border-radius: 0 0 7.5px 7.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button[disabled] {
|
||||||
|
color: #bbb;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
.playback-controls {
|
.playback-controls {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid #e8eaf0;
|
border-bottom: 1px solid #e8eaf0;
|
||||||
|
@ -83,22 +88,24 @@ main {
|
||||||
width: 78%;
|
width: 78%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#playlist-controls {
|
#playlist-controls, #browser-controls {
|
||||||
text-align: right;
|
|
||||||
margin-bottom: 7.5px;
|
margin-bottom: 7.5px;
|
||||||
padding-bottom: 7.5px;
|
padding-bottom: 7.5px;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
height: 3.8rem;
|
height: 3.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#playlist-content {
|
#playlist-controls {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#playlist-content, #music-browser {
|
||||||
height: 32.2rem;
|
height: 32.2rem;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
#music-browser {
|
#music-browser {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-pane {
|
.music-pane {
|
||||||
|
@ -109,16 +116,21 @@ main {
|
||||||
|
|
||||||
.music-item {
|
.music-item {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-item.selected {
|
||||||
|
background-color: #c8ffd0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-item:hover {
|
||||||
|
background-color: #daf8e2 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-item:nth-child(odd) {
|
.music-item:nth-child(odd) {
|
||||||
background-color: #f2f2f2;
|
background-color: #f2f2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-track {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playlist-track.active {
|
.playlist-track.active {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
padding-top: 1.5rem;
|
padding-top: 1.5rem;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var seekInterval,
|
var seekInterval,
|
||||||
trackLongPressTimeout,
|
longPressTimeout,
|
||||||
|
curPath = [],
|
||||||
curTrackUpdateHandler,
|
curTrackUpdateHandler,
|
||||||
curTrackElapsed = {
|
curTrackElapsed = {
|
||||||
timestamp: null,
|
timestamp: null,
|
||||||
|
@ -212,16 +213,16 @@ $(document).ready(function() {
|
||||||
|
|
||||||
var onTrackTouchDown = function(event) {
|
var onTrackTouchDown = function(event) {
|
||||||
var $track = $(this);
|
var $track = $(this);
|
||||||
trackLongPressTimeout = setTimeout(function() {
|
longPressTimeout = setTimeout(function() {
|
||||||
$track.addClass('selected');
|
$track.addClass('selected');
|
||||||
clearTimeout(trackLongPressTimeout);
|
clearTimeout(longPressTimeout);
|
||||||
trackLongPressTimeout = undefined;
|
longPressTimeout = undefined;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
var onTrackTouchUp = function(event) {
|
var onTrackTouchUp = function(event) {
|
||||||
var $track = $(this);
|
var $track = $(this);
|
||||||
if (trackLongPressTimeout) {
|
if (longPressTimeout) {
|
||||||
execute({
|
execute({
|
||||||
type: 'request',
|
type: 'request',
|
||||||
action: 'music.mpd.playid',
|
action: 'music.mpd.playid',
|
||||||
|
@ -229,8 +230,8 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimeout(trackLongPressTimeout);
|
clearTimeout(longPressTimeout);
|
||||||
trackLongPressTimeout = undefined;
|
longPressTimeout = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
var updatePlaylist = function(tracks) {
|
var updatePlaylist = function(tracks) {
|
||||||
|
@ -288,6 +289,192 @@ $(document).ready(function() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var onPlaylistTouchDown = function(event) {
|
||||||
|
var $playlist = $(this);
|
||||||
|
$playlist.addClass('selected');
|
||||||
|
|
||||||
|
longPressTimeout = setTimeout(function() {
|
||||||
|
clearTimeout(longPressTimeout);
|
||||||
|
longPressTimeout = undefined;
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
var onPlaylistTouchUp = function(event) {
|
||||||
|
var $playlist = $(this);
|
||||||
|
if (longPressTimeout) {
|
||||||
|
execute(
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.mpd.clear'
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess = function() {
|
||||||
|
execute(
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.mpd.load',
|
||||||
|
args: { playlist: $playlist.data('name') }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(longPressTimeout);
|
||||||
|
longPressTimeout = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
var onFileTouchDown = function(event) {
|
||||||
|
var $file = $(this);
|
||||||
|
$file.addClass('selected');
|
||||||
|
|
||||||
|
longPressTimeout = setTimeout(function() {
|
||||||
|
clearTimeout(longPressTimeout);
|
||||||
|
longPressTimeout = undefined;
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
var onFileTouchUp = function(event) {
|
||||||
|
var $file = $(this);
|
||||||
|
if (longPressTimeout) {
|
||||||
|
execute(
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.mpd.playlistinfo'
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess = function(response) {
|
||||||
|
var pos = 0;
|
||||||
|
if (response.response.output.length > 0) {
|
||||||
|
pos = parseInt(response.response.output.slice(-1)[0].pos) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.mpd.add',
|
||||||
|
args: { resource: $file.data('file') }
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess = function() {
|
||||||
|
execute(
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.mpd.play_pos',
|
||||||
|
args: { pos: pos }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$file.removeClass('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(longPressTimeout);
|
||||||
|
longPressTimeout = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
var onDirectorySelect = function(event) {
|
||||||
|
var $directory = $(this);
|
||||||
|
execute(
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.mpd.lsinfo',
|
||||||
|
args: { uri: $directory.data('name') }
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess = function(response) {
|
||||||
|
curPath = $directory.data('name').split('/')
|
||||||
|
.filter(function(term) { return term.length > 0 });
|
||||||
|
|
||||||
|
updateBrowser(response.response.output);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateBrowser = function(items) {
|
||||||
|
var $browserContent = $('#music-browser');
|
||||||
|
var $addButton = $('#browser-controls').find('button[data-action="add"]');
|
||||||
|
var directories = [];
|
||||||
|
var playlists = [];
|
||||||
|
var files = [];
|
||||||
|
|
||||||
|
$browserContent.find('.music-item').remove();
|
||||||
|
|
||||||
|
for (var item of items) {
|
||||||
|
if ('directory' in item) {
|
||||||
|
directories.push(item.directory);
|
||||||
|
} else if ('playlist' in item) {
|
||||||
|
playlists.push(item.playlist);
|
||||||
|
} else if ('file' in item) {
|
||||||
|
files.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curPath.length > 0) {
|
||||||
|
var $parentElement = $('<div></div>')
|
||||||
|
.addClass('browser-directory').addClass('music-item')
|
||||||
|
.addClass('browser-item').addClass('row').data('name', curPath.slice(0, -1).join('/'))
|
||||||
|
.html('<i class="fa fa-folder-open-o"></i> ..');
|
||||||
|
|
||||||
|
$parentElement.on('click touch', onDirectorySelect);
|
||||||
|
$parentElement.appendTo($browserContent);
|
||||||
|
$addButton.removeAttr('disabled');
|
||||||
|
} else {
|
||||||
|
$addButton.attr('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var directory of directories.sort()) {
|
||||||
|
var $element = $('<div></div>')
|
||||||
|
.addClass('browser-directory').addClass('music-item')
|
||||||
|
.addClass('browser-item').addClass('row').data('name', directory)
|
||||||
|
.html('<i class="fa fa-folder-open-o"></i> ' + directory.split('/').slice(-1)[0]);
|
||||||
|
|
||||||
|
$element.on('click touch', onDirectorySelect);
|
||||||
|
$element.appendTo($browserContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var playlist of playlists.sort()) {
|
||||||
|
var $element = $('<div></div>')
|
||||||
|
.addClass('browser-playlist').addClass('music-item')
|
||||||
|
.addClass('browser-item').addClass('row').data('name', playlist)
|
||||||
|
.html('<i class="fa fa-list"></i> ' + playlist);
|
||||||
|
|
||||||
|
$element.on('mousedown touchstart', onPlaylistTouchDown);
|
||||||
|
$element.on('mouseup touchend', onPlaylistTouchUp);
|
||||||
|
$element.appendTo($browserContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
files = files.sort(function(a, b) {
|
||||||
|
if (a.artist === b.artist) {
|
||||||
|
if (a.album === b.album) {
|
||||||
|
return parseInt(a.track) < parseInt(b.track) ? -1 : 1;
|
||||||
|
} else {
|
||||||
|
return a.album.localeCompare(b.album);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return a.artist.localeCompare(b.artist);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var file of files) {
|
||||||
|
var $element = $('<div></div>')
|
||||||
|
.addClass('browser-file').addClass('music-item')
|
||||||
|
.addClass('browser-item').addClass('row')
|
||||||
|
.html('<i class="fa fa-music"></i> ' + file.title);
|
||||||
|
|
||||||
|
for (var prop of Object.keys(file)) {
|
||||||
|
$element.data(prop, file[prop]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$element.on('mousedown touchstart', onFileTouchDown);
|
||||||
|
$element.on('mouseup touchend', onFileTouchUp);
|
||||||
|
$element.appendTo($browserContent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var initBrowser = function() {
|
var initBrowser = function() {
|
||||||
execute(
|
execute(
|
||||||
{
|
{
|
||||||
|
@ -296,36 +483,7 @@ $(document).ready(function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess = function(response) {
|
onSuccess = function(response) {
|
||||||
var $browserContent = $('#music-browser');
|
updateBrowser(response.response.output);
|
||||||
var items = response.response.output;
|
|
||||||
var directories = [];
|
|
||||||
var playlists = [];
|
|
||||||
|
|
||||||
for (var item of items) {
|
|
||||||
if ('directory' in item) {
|
|
||||||
directories.push(item.directory);
|
|
||||||
} else if ('playlist' in item) {
|
|
||||||
playlists.push(item.playlist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var directory of directories.sort()) {
|
|
||||||
var $element = $('<div></div>')
|
|
||||||
.addClass('browser-directory').addClass('music-item')
|
|
||||||
.addClass('browser-item').addClass('row')
|
|
||||||
.html('<i class="fa fa-folder-open-o"></i> ' + directory);
|
|
||||||
|
|
||||||
$element.appendTo($browserContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var playlist of playlists.sort()) {
|
|
||||||
var $element = $('<div></div>')
|
|
||||||
.addClass('browser-playlist').addClass('music-item')
|
|
||||||
.addClass('browser-item').addClass('row')
|
|
||||||
.html('<i class="fa fa-music"></i> ' + playlist);
|
|
||||||
|
|
||||||
$element.appendTo($browserContent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -334,6 +492,7 @@ $(document).ready(function() {
|
||||||
window.registerEventListener(onEvent);
|
window.registerEventListener(onEvent);
|
||||||
var $playbackControls = $('.playback-controls, #playlist-controls').find('button');
|
var $playbackControls = $('.playback-controls, #playlist-controls').find('button');
|
||||||
var $playlistContent = $('#playlist-content');
|
var $playlistContent = $('#playlist-content');
|
||||||
|
var $browserAddBtn = $('#browser-controls').find('button[data-action="add"]');
|
||||||
var $volumeCtrl = $('#volume-ctrl');
|
var $volumeCtrl = $('#volume-ctrl');
|
||||||
var $trackSeeker = $('#track-seeker');
|
var $trackSeeker = $('#track-seeker');
|
||||||
var prevVolume;
|
var prevVolume;
|
||||||
|
@ -408,6 +567,26 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$browserAddBtn.on('click touch', function(event) {
|
||||||
|
var $browserContent = $('#music-browser');
|
||||||
|
var $items = $browserContent.find('.music-item').slice(1, -1);
|
||||||
|
var $selectedItems = $browserContent.find('.music-item.selected');
|
||||||
|
|
||||||
|
if ($selectedItems.length == 0) {
|
||||||
|
$selectedItems = $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var item of $selectedItems) {
|
||||||
|
execute(
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.mpd.add',
|
||||||
|
args: { resource: $(item).data('file') }
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var init = function() {
|
var init = function() {
|
||||||
|
|
|
@ -58,8 +58,16 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="player-left-side" class="three columns music-pane">
|
<div id="player-left-side" class="three columns music-pane">
|
||||||
|
<div class="row">
|
||||||
|
<div id="browser-controls">
|
||||||
|
<button data-action="add">
|
||||||
|
<i class="fa fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="music-browser" class="music-pane"></div>
|
<div id="music-browser" class="music-pane"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="player-right-side" class="nine columns music-pane">
|
<div id="player-right-side" class="nine columns music-pane">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="playlist-controls">
|
<div id="playlist-controls">
|
||||||
|
|
|
@ -28,6 +28,9 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
self.add(resource)
|
self.add(resource)
|
||||||
return self._exec('play')
|
return self._exec('play')
|
||||||
|
|
||||||
|
def play_pos(self, pos):
|
||||||
|
return self._exec('play', pos)
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
status = self.status().output['state']
|
status = self.status().output['state']
|
||||||
if status == 'play': return self._exec('pause')
|
if status == 'play': return self._exec('pause')
|
||||||
|
@ -111,8 +114,9 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return Response(output=sorted(self.client.listplaylists(),
|
return Response(output=sorted(self.client.listplaylists(),
|
||||||
key=lambda p: p['playlist']))
|
key=lambda p: p['playlist']))
|
||||||
|
|
||||||
def lsinfo(self):
|
def lsinfo(self, uri=None):
|
||||||
return Response(output=self.client.lsinfo())
|
output = self.client.lsinfo(uri) if uri else self.client.lsinfo()
|
||||||
|
return Response(output=output)
|
||||||
|
|
||||||
def plchanges(self, version):
|
def plchanges(self, version):
|
||||||
return Response(output=self.client.plchanges(version))
|
return Response(output=self.client.plchanges(version))
|
||||||
|
|
Loading…
Reference in a new issue