Implemented music search from web panel

This commit is contained in:
Fabio Manganiello 2018-04-12 13:04:56 +02:00
parent f3d725c890
commit decadee00a
6 changed files with 607 additions and 136 deletions

View file

@ -20,6 +20,43 @@ header {
font-weight: bold; font-weight: bold;
} }
.modal {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 999;
background-color: rgba(10,10,10,0.85);
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.modal-container {
margin: 5% auto auto auto;
width: 70%;
background: white;
border-radius: 10px;
}
.modal-header {
border-bottom: 1px solid #ccc;
margin: 0.5rem auto;
padding: 0.5rem;
text-align: center;
background-color: #f0f0f0;
border-radius: 10px 10px 0 0;
text-transform: uppercase;
font-weight: 400px;
letter-spacing: .1rem;
line-height: 38px;
}
.modal-body {
padding: 2.5rem 2rem 1.5rem 2rem;
}
#date-time { #date-time {
text-align: right; text-align: right;
padding-right: 30px; padding-right: 30px;
@ -56,138 +93,10 @@ button[disabled] {
border: 1px solid; border: 1px solid;
} }
.playback-controls {
text-align: center;
border-bottom: 1px solid #e8eaf0;
padding-bottom: 12px;
}
.playback-controls * > button.enabled {
color: #59df3e;
}
.button[disabled] { .button[disabled] {
background: rgba(240,240,240,1) background: rgba(240,240,240,1)
} }
.track-info {
text-align: center;
margin: -20px -20px 0 -20px;
padding: 10px 20px;
}
.track-info > .artist {
font-weight: bold;
display: block;
}
#player-left-side {
overflow-y: hidden;
}
#player-right-side {
margin-left: 0;
width: 78%;
}
#playlist-controls, #browser-controls {
margin-bottom: 7.5px;
padding-bottom: 7.5px;
border-bottom: 1px solid #ddd;
height: 3.8rem;
}
#playlist-controls {
text-align: right;
}
#playlist-content, #music-browser {
height: 27.2rem;
overflow-y: scroll;
}
#music-browser {
padding-top: 0;
}
#browser-filter {
width: 95%;
margin-bottom: 1.5rem;
}
#playlist-filter-container {
height: 5rem;
}
#playlist-filter {
width: 100%;
margin-bottom: 1.5rem;
}
.music-pane {
height: 40rem;
padding: 15px 15px 0 5px;
background: #f8f8f8;
}
.music-item {
padding: 5px;
cursor: pointer;
}
.music-item.selected {
background-color: #c8ffd0 !important;
}
.music-item:hover {
background-color: #daf8e2 !important;
}
.music-item:nth-child(odd) {
background-color: #f2f2f2;
}
.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;
}
#track-seeker-container {
margin-bottom: 10px;
}
#volume-ctrl-container {
margin-top: 15px;
}
.slider { .slider {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
@ -226,3 +135,12 @@ button[disabled] {
cursor: pointer; cursor: pointer;
} }
.btn-primary {
background-color: #d8ffe0 !important;
border: 1px solid #98efb0 !important;
}
.right-side {
text-align: right !important;
}

View file

@ -0,0 +1,173 @@
#player-left-side {
overflow-y: hidden;
}
#player-right-side {
margin-left: 0;
width: 78%;
}
.playback-controls {
text-align: center;
border-bottom: 1px solid #e8eaf0;
padding-bottom: 12px;
}
.playback-controls * > button.enabled {
color: #59df3e;
}
.track-info {
text-align: center;
margin: -20px -20px 0 -20px;
padding: 10px 20px;
}
.track-info > .artist {
font-weight: bold;
display: block;
}
#playlist-controls, #browser-controls {
margin-bottom: 7.5px;
padding-bottom: 7.5px;
border-bottom: 1px solid #ddd;
height: 3.8rem;
}
#playlist-controls {
text-align: right;
}
#playlist-content, #music-browser {
height: 27.2rem;
overflow-y: scroll;
}
#music-browser {
padding-top: 0;
}
#browser-filter {
width: 95%;
margin-bottom: 1.5rem;
}
#playlist-filter-container {
height: 5rem;
}
#playlist-filter {
width: 100%;
margin-bottom: 1.5rem;
}
.music-pane {
height: 40rem;
padding: 15px 15px 0 5px;
background: #f8f8f8;
}
.music-item {
padding: 5px;
cursor: pointer;
}
.music-item.selected {
background-color: #c8ffd0 !important;
}
.music-item:hover {
background-color: #daf8e2 !important;
}
.music-item:nth-child(odd) {
background-color: #f2f2f2;
}
.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;
}
#track-seeker-container {
margin-bottom: 10px;
}
#volume-ctrl-container {
margin-top: 15px;
}
#music-search-form {
margin-bottom: 1rem;
}
#music-search-form * > input[type=text] {
width: 100%;
}
#music-search-form > .row {
padding: 0.5rem;
}
.music-form-bottom {
text-align: right;
margin-top: 2rem;
border-top: 1px solid #ddd;
}
.music-form-bottom input {
margin-top: 2rem;
}
#music-search-results-form {
display: none;
margin-top: -2rem;
}
#music-search-results-container {
display: none;
max-height: 50rem;
margin-top: -1.4rem;
overflow-y: auto;
overflow-x: hidden;
}
#music-search-results-head {
padding: 0.5rem 1rem;
background-color: #eaeaea;
border-radius: 5px 5px 0 0;
border-bottom: 1px solid #bbb;
letter-spacing: .1rem;
line-height: 3.5rem;
}

View file

@ -144,10 +144,32 @@ $(document).ready(function() {
}); });
}; };
var initModalOpenBindings = function() {
$('body').on('click touch', '[data-modal]', function(event) {
var $source = $(event.target);
var $modal = $($source.data('modal'));
$modal.fadeIn();
});
};
var initModalCloseBindings = function() {
$('body').on('click touch', '[data-dismiss-modal]', function(event) {
var $source = $(event.target);
var $modal = $($source.data('dismiss-modal'));
$modal.fadeOut();
});
};
var initModals = function() {
initModalOpenBindings();
initModalCloseBindings();
};
var init = function() { var init = function() {
initWebsocket(); initWebsocket();
initElements(); initElements();
initDateTime(); initDateTime();
initModals();
}; };
window.registerEventListener = registerEventListener; window.registerEventListener = registerEventListener;

View file

@ -6,7 +6,16 @@ $(document).ready(function() {
curTrackElapsed = { curTrackElapsed = {
timestamp: null, timestamp: null,
elapsed: null, elapsed: null,
}; },
$musicSearchForm = $('#music-search-form'),
$musicSearchResults = $('#music-search-results'),
$musicSearchResultsContainer = $('#music-search-results-container'),
$musicSearchResultsForm = $('#music-search-results-form'),
$musicResultsAddBtn = $('#music-results-add'),
$musicResultsPlayBtn = $('#music-results-play'),
$resetSearchBtn = $('#music-search-reset');
$doSearchBtns = $('.do-search-btns');
var execute = function(request, onSuccess, onError, onComplete) { var execute = function(request, onSuccess, onError, onComplete) {
request['target'] = 'localhost'; request['target'] = 'localhost';
@ -39,6 +48,24 @@ $(document).ready(function() {
}); });
}; };
var formatMinutes = function(time) {
if (typeof time === 'string') {
time = parseInt(time);
} else if (isNaN(time)) {
console.warn('Unexpected non-numeric value in formatMinutes');
console.log(time);
return undefined;
}
if (!time) {
return '-:--';
}
var minutes = parseInt(time/60);
var seconds = time%60;
return (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
};
var updateControls = function(status, track) { var updateControls = function(status, track) {
var $playbackControls = $('.playback-controls'); var $playbackControls = $('.playback-controls');
var $playlistContent = $('#playlist-content'); var $playlistContent = $('#playlist-content');
@ -80,7 +107,7 @@ $(document).ready(function() {
$curTrack.find('.track').hide(); $curTrack.find('.track').hide();
$curTrack.find('.no-track').show(); $curTrack.find('.no-track').show();
$trackSeeker.attr('disabled', true); $trackSeeker.prop('disabled', true);
$('.seek-time').text('-:--'); $('.seek-time').text('-:--');
break; break;
@ -91,7 +118,7 @@ $(document).ready(function() {
$curTrack.find('.track').show(); $curTrack.find('.track').show();
$curTrack.find('.no-track').hide(); $curTrack.find('.no-track').hide();
$trackSeeker.removeAttr('disabled'); $trackSeeker.prop('disabled', false);
$('#seek-time-elapsed').text(elapsed ? elapsed : '-:--'); $('#seek-time-elapsed').text(elapsed ? elapsed : '-:--');
$('#seek-time-length').text(length ? length : '-:--'); $('#seek-time-length').text(length ? length : '-:--');
break; break;
@ -103,7 +130,7 @@ $(document).ready(function() {
$curTrack.find('.track').show(); $curTrack.find('.track').show();
$curTrack.find('.no-track').hide(); $curTrack.find('.no-track').hide();
$trackSeeker.removeAttr('disabled'); $trackSeeker.prop('disabled', false);
$('#seek-time-elapsed').text(elapsed ? elapsed : '-:--'); $('#seek-time-elapsed').text(elapsed ? elapsed : '-:--');
$('#seek-time-length').text(length ? length : '-:--'); $('#seek-time-length').text(length ? length : '-:--');
@ -424,9 +451,9 @@ $(document).ready(function() {
$parentElement.on('click touch', onDirectorySelect); $parentElement.on('click touch', onDirectorySelect);
$parentElement.appendTo($browserContent); $parentElement.appendTo($browserContent);
$addButton.removeAttr('disabled'); $addButton.prop('disabled', false);
} else { } else {
$addButton.attr('disabled', 'disabled'); $addButton.prop('disabled', true);
} }
for (var directory of directories.sort()) { for (var directory of directories.sort()) {
@ -506,7 +533,7 @@ $(document).ready(function() {
$playbackControls.on('click', function(evt) { $playbackControls.on('click', function(evt) {
var action = $(this).data('action'); var action = $(this).data('action');
var $btn = $(this); var $btn = $(this);
$btn.attr('disabled', true); $btn.prop('disabled', true);
execute( execute(
{ {
@ -520,7 +547,7 @@ $(document).ready(function() {
onError=undefined, onError=undefined,
onComplete = function() { onComplete = function() {
$btn.removeAttr('disabled'); $btn.prop('disabled', false);
} }
); );
}); });
@ -618,10 +645,237 @@ $(document).ready(function() {
}); });
}; };
var initSearch = function() {
$musicSearchForm.on('submit', function(event) {
var searchData = $(this).serializeArray().reduce(function(obj, item) {
var value = item.value.trim();
if (value.length > 0) {
obj[item.name] = item.value;
}
return obj;
}, {});
var args = {};
var searchFilters = {};
if ('any' in searchData) {
args = {
type: 'any',
filter: searchData.any
};
searchFilters.any = searchData.any;
} else {
if ('albumartist' in searchData) {
args = {
type: 'albumartist',
filter: searchData.albumartist
};
searchFilters.albumartist = searchData.albumartist;
}
if ('album' in searchData) {
args = {
type: 'album',
filter: searchData.album
};
searchFilters.album = searchData.album;
}
if ('title' in searchData) {
args = {
type: 'title',
filter: searchData.title
};
searchFilters.title = searchData.title;
}
}
$(this).find('input').prop('disabled', true);
execute(
{
type: 'request',
action: 'music.mpd.search',
args: args
},
onSuccess = function(response) {
var results = response.response.output;
if (!results) {
return false;
}
if (Object.keys(searchFilters).length > 1) {
results = results.filter(function(item) {
return (
('title' in searchFilters && 'title' in item
? item.title.toLowerCase().indexOf(
searchFilters.title.toLowerCase()) >= 0 : true) &&
('album' in searchFilters && 'album' in item
? item.album.toLowerCase().indexOf(
searchFilters.album.toLowerCase()) >= 0 : true) &&
('albumartist' in searchFilters && 'artist' in item
? item.artist.toLowerCase().indexOf(
searchFilters.albumartist.toLowerCase()) >= 0 : true)
);
});
}
for (var item of results) {
var $item = $('<div></div>')
.addClass('row').addClass('music-item')
.addClass('music-search-item')
.data('file', item.file);
var $artist = $('<div></div>')
.addClass('three columns').addClass('artist')
.addClass('music-search-item-artist')
if ('artist' in item) {
$artist.text(item.artist);
} else {
$artist.html('&nbsp;');
}
var $title = $('<div></div>')
.addClass('four columns').addClass('title')
.addClass('music-search-item-title');
if ('title' in item) {
$title.text(item.title);
} else {
$title.html('&nbsp;');
}
var $album = $('<div></div>')
.addClass('four columns').addClass('album')
.addClass('music-search-item-album');
if ('album' in item) {
$album.text(item.album);
} else {
$album.html('&nbsp;');
}
var $time = $('<div></div>')
.addClass('one column').addClass('time')
.addClass('music-search-item-time')
.text('time' in item ? formatMinutes(item.time) : '-:--');
$artist.appendTo($item);
$title.appendTo($item);
$album.appendTo($item);
$time.appendTo($item);
$item.appendTo($musicSearchResults);
}
},
onError = function(xhr, status, error) {
console.error(error);
},
onComplete = function() {
$musicSearchForm.find('input').prop('disabled', false);
$musicSearchForm.hide();
$musicSearchResultsContainer.show();
$musicSearchResultsForm.show();
}
);
return false;
});
$resetSearchBtn.on('click', function(event) {
$musicSearchResultsForm.hide();
$musicSearchResultsContainer.hide();
$musicSearchResults.html('');
$musicSearchForm.show();
$musicResultsAddBtn.removeData('file');
$musicResultsAddBtn.prop('disabled', true);
$musicResultsPlayBtn.removeData('file');
$musicResultsPlayBtn.prop('disabled', true);
});
$musicSearchResults.on('click', '.music-search-item', function(event) {
var isCurrentlySelected = $(this).hasClass('selected');
$('.music-search-item').removeClass('selected');
if (isCurrentlySelected) {
$musicResultsAddBtn.removeData('file');
$musicResultsAddBtn.prop('disabled', true);
$musicResultsPlayBtn.removeData('file');
$musicResultsPlayBtn.prop('disabled', true);
$(this).removeClass('selected');
} else {
var file = $(this).data('file');
$musicResultsAddBtn.data('file', file);
$musicResultsAddBtn.prop('disabled', false);
$musicResultsPlayBtn.data('file', file);
$musicResultsPlayBtn.prop('disabled', false);
$(this).addClass('selected');
}
});
$musicSearchResultsForm.on('submit', function(event) {
return false;
});
$musicResultsAddBtn.on('click', function(event) {
var file = $(this).data('file');
if (!file) {
return false;
}
execute(
{
type: 'request',
action: 'music.mpd.add',
args: {
resource: file
}
},
onSuccess = function(response) {
initPlaylist();
}
);
});
$musicResultsPlayBtn.on('click', function(event) {
var file = $(this).data('file');
if (!file) {
return false;
}
execute(
{
type: 'request',
action: 'music.mpd.play',
args: {
resource: file
}
},
onSuccess = function(response) {
initPlaylist();
}
);
});
};
var init = function() { var init = function() {
initStatus(); initStatus();
initPlaylist(); initPlaylist();
initBrowser(); initBrowser();
initSearch();
initBindings(); initBindings();
}; };

View file

@ -1,4 +1,90 @@
<script type="text/javascript" src="{{ url_for('static', filename='js/music.mpd.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/music.mpd.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/music.mpd.css') }}"></script>
<div id="music-search-modal" class="modal">
<div class="modal-container">
<div class="modal-header">
Search For Music
</div>
<div class="modal-body">
<form id="music-search-form" action="#">
<div class="row form-row">
<div class="two columns">
<label for="music-search-any">Any</label>
</div>
<div class="ten columns">
<input type="text" name="any">
</div>
</div>
<div class="row form-row">
<div class="two columns">
<label for="music-search-artist">Artist</label>
</div>
<div class="ten columns">
<input type="text" name="albumartist">
</div>
</div>
<div class="row form-row">
<div class="two columns">
<label for="music-search-title">Title</label>
</div>
<div class="ten columns">
<input type="text" name="title">
</div>
</div>
<div class="row form-row">
<div class="two columns">
<label for="music-search-album">Album</label>
</div>
<div class="ten columns">
<input type="text" name="album">
</div>
</div>
<div class="row music-form-bottom">
<div class="six columns offset-by-six do-search-btns">
<input type="button" class="btn-default" data-dismiss-modal="#music-search-modal"
value="Close">
<input type="submit" class="btn-primary" value="Search">
</div>
</div>
</form>
<form class="row" id="music-search-results-form">
<div class="six columns">
<button class="btn-default" id="music-results-add" disabled="disabled">
<i class="fa fa-plus"></i>
</button>
<button class="btn-default" id="music-results-play" disabled="disabled">
<i class="fa fa-play"></i>
</button>
</div>
<div class="six columns right-side">
<input type="button" class="btn-default"
data-dismiss-modal="#music-search-modal" value="Close">
<input type="button" class="btn-primary"
id="music-search-reset" value="Reset">
</div>
</form>
<div id="music-search-results-container" class="row">
<div id="music-search-results-head" class="row">
<div class="three columns">Artist</div>
<div class="four columns">Title</div>
<div class="four columns">Album</div>
<div class="one column">Time</div>
</div>
<div id="music-search-results" class="row"></div>
</div>
</div>
</div>
</div>
<div class="row track-info"> <div class="row track-info">
<span class="no-track">No media is being played</span> <span class="no-track">No media is being played</span>
@ -63,6 +149,9 @@
<button data-action="add"> <button data-action="add">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
</button> </button>
<button data-action="search" data-modal="#music-search-modal">
<i class="fa fa-search"></i>
</button>
</div> </div>
<div id="browser-filter-container"> <div id="browser-filter-container">

View file

@ -121,6 +121,21 @@ class MusicMpdPlugin(MusicPlugin):
def plchanges(self, version): def plchanges(self, version):
return Response(output=self.client.plchanges(version)) return Response(output=self.client.plchanges(version))
def find(self, type, filter, *args, **kwargs):
return Response(
output=self.client.find(type, filter, *args, **kwargs))
def findadd(self, type, filter, *args, **kwargs):
return Response(
output=self.client.findadd(type, filter, *args, **kwargs))
def search(self, type, filter, *args, **kwargs):
return Response(
output=self.client.search(type, filter, *args, **kwargs))
def searchadd(self, type, filter, *args, **kwargs):
return Response(
output=self.client.searchadd(type, filter, *args, **kwargs))
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: