music.mpd plugin vue.js refactoring - WIP
This commit is contained in:
parent
19162a3b8d
commit
7a74b83c76
18 changed files with 724 additions and 128 deletions
|
@ -26,6 +26,7 @@ $widths: (
|
||||||
float: left;
|
float: left;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: ((100%/12)*$i);
|
width: ((100%/12)*$i);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@if $i < 12 {
|
@if $i < 12 {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 25em;
|
width: 25em;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
background: $notification-bg;
|
background: $notification-bg;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
//// Common defaults
|
//// Common defaults
|
||||||
$default-bg: #f4f5f6 !default;
|
$default-bg: white !default;
|
||||||
|
$default-bg-2: #f4f5f6 !default;
|
||||||
|
$default-bg-3: #f1f3f2 !default;
|
||||||
|
$default-bg-4: #edf0ee !default;
|
||||||
$default-fg: black !default;
|
$default-fg: black !default;
|
||||||
$default-fg-2: #333333 !default;
|
$default-fg-2: #333333 !default;
|
||||||
|
$default-font-size: 1.5rem !default;
|
||||||
|
|
||||||
$default-font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif !default;
|
$default-font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif !default;
|
||||||
$default-border: 1px solid #e1e4e8 !default;
|
$default-border: 1px solid #e1e4e8 !default;
|
||||||
|
@ -9,6 +13,7 @@ $default-border-2: 1px solid #dddddd !default;
|
||||||
$default-bottom: $default-border !default;
|
$default-bottom: $default-border !default;
|
||||||
$default-link-fg: #5f7869 !default;
|
$default-link-fg: #5f7869 !default;
|
||||||
|
|
||||||
|
$font-size: $default-font-size !default;
|
||||||
$selected-bg: #c8ffd0 !default;
|
$selected-bg: #c8ffd0 !default;
|
||||||
$hover-bg: #def6ea !default;
|
$hover-bg: #def6ea !default;
|
||||||
$header-bg: $default_bg !default;
|
$header-bg: $default_bg !default;
|
||||||
|
@ -26,12 +31,12 @@ $fade-in-transition-duration: $fade-transition-duration !default;
|
||||||
$fade-out-transition-duration: $fade-transition-duration !default;
|
$fade-out-transition-duration: $fade-transition-duration !default;
|
||||||
|
|
||||||
//// Notifications
|
//// Notifications
|
||||||
$notification-bg: rgba(185, 255, 193, 0.85) !default;
|
$notification-bg: rgba(185, 255, 193, 0.9) !default;
|
||||||
$notification-hover-bg: rgba(160,245,178,0.9) !default;
|
$notification-hover-bg: rgba(160,245,178,0.95) !default;
|
||||||
$notification-warning-bg: rgba(228, 255, 78, 0.85) !default;
|
$notification-warning-bg: rgba(228, 255, 78, 0.9) !default;
|
||||||
$notification-warning-hover-bg: rgba(218, 245, 68, 0.9) !default;
|
$notification-warning-hover-bg: rgba(218, 245, 68, 0.95) !default;
|
||||||
$notification-error-bg: rgba(255, 100, 100, 0.85) !default;
|
$notification-error-bg: rgba(255, 100, 100, 0.9) !default;
|
||||||
$notification-error-hover-bg: rgba(245, 90, 90, 0.9) !default;
|
$notification-error-hover-bg: rgba(245, 90, 90, 0.95) !default;
|
||||||
$notification-border: 1px solid rgba(109, 205, 134, 0.62) !default;
|
$notification-border: 1px solid rgba(109, 205, 134, 0.62) !default;
|
||||||
$notification-warning-border: 1px solid rgba(205, 205, 109, 0.62) !default;
|
$notification-warning-border: 1px solid rgba(205, 205, 109, 0.62) !default;
|
||||||
$notification-error-border: 1px solid rgba(205, 109, 109, 0.62) !default;
|
$notification-error-border: 1px solid rgba(205, 109, 109, 0.62) !default;
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
header {
|
|
||||||
.row {
|
|
||||||
width: 100%;
|
|
||||||
background: $header-bg;
|
|
||||||
padding: 1rem 2.5rem;
|
|
||||||
// margin: 0 1rem 3.5rem -1rem;
|
|
||||||
border-bottom: $header-bottom;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
font-size: 25px;
|
|
||||||
|
|
||||||
.logo-1 {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-time {
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 3rem;
|
|
||||||
|
|
||||||
.date {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 25px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,27 +7,34 @@
|
||||||
@import 'common/modal';
|
@import 'common/modal';
|
||||||
@import 'common/notifications';
|
@import 'common/notifications';
|
||||||
|
|
||||||
@import 'header';
|
|
||||||
@import 'nav';
|
@import 'nav';
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
font-family: $default-font-family;
|
font-family: $default-font-family;
|
||||||
|
font-size: $default-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar {
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar-track {
|
||||||
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar-thumb {
|
||||||
|
background-color: darkgrey;
|
||||||
|
outline: 1px solid slategrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: 6rem auto;
|
padding: 4.9rem 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $default-link-fg;
|
color: $default-link-fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-container {
|
|
||||||
border: $default-border-2;
|
|
||||||
border-radius: 1rem;
|
|
||||||
margin: 1.5rem;
|
|
||||||
box-shadow: 8px 8px 6px -1px rgba(187,187,187,0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ nav {
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
opacity: 0.9;
|
opacity: 0.95;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -23,9 +23,9 @@ nav {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: .1rem;
|
letter-spacing: .1rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-radius: 2rem;
|
|
||||||
background: $hover-bg;
|
background: $hover-bg;
|
||||||
letter-spacing: .4rem;
|
letter-spacing: .4rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 3.8rem;
|
line-height: 3.8rem;
|
||||||
letter-spacing: .1rem;
|
letter-spacing: .1rem;
|
||||||
|
border-bottom: $default-border-2;
|
||||||
|
border-radius: 0 0 1em 1em;
|
||||||
|
|
||||||
%panel {
|
%panel {
|
||||||
margin: 1.5rem auto;
|
margin: 1.5rem auto;
|
||||||
|
@ -28,7 +30,7 @@
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
padding: .75rem;
|
padding: .75rem;
|
||||||
background: $default-bg;
|
background: $default-bg-2;
|
||||||
border-bottom: $default-border-2;
|
border-bottom: $default-border-2;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
@import 'common/vars';
|
||||||
|
@import 'common/layout';
|
||||||
|
@import 'webpanel/plugins/music.mpd/vars';
|
||||||
|
|
||||||
|
// background-image: linear-gradient(to right bottom, rgb(123, 84, 30), rgb(0, 0, 0)), linear-gradient(transparent, rgb(0, 0, 0) 70%);
|
||||||
|
|
||||||
|
.music-mpd-container {
|
||||||
|
line-height: 3rem;
|
||||||
|
letter-spacing: .03rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
* > .item {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 1rem;
|
||||||
|
padding: .5rem;
|
||||||
|
|
||||||
|
&:nth-child(odd) { background: rgba(255, 255, 255, 0.0); }
|
||||||
|
&:nth-child(even) { background: $default-bg-3; }
|
||||||
|
|
||||||
|
.artist {
|
||||||
|
font-size: $artist-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .duration {
|
||||||
|
color: $duration-color;
|
||||||
|
font-size: $duration-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panels {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.browser, .playlist {
|
||||||
|
height: 100vh - 16rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser {
|
||||||
|
background: $browser-panel-bg;
|
||||||
|
border-right: $default-border-2;
|
||||||
|
padding: .3rem 1rem 6rem 1rem;
|
||||||
|
font-size: $browser-font-size;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist {
|
||||||
|
padding: .5rem 1rem 6rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
@extend .vertical-center;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 6rem;
|
||||||
|
bottom: 0;
|
||||||
|
border-top: $default-border-2;
|
||||||
|
padding: 1rem;
|
||||||
|
background: $control-panel-bg;
|
||||||
|
box-shadow: $control-panel-shadow;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.track-container {
|
||||||
|
@extend .vertical-center;
|
||||||
|
line-height: 2.6rem;
|
||||||
|
|
||||||
|
.track-info {
|
||||||
|
.artist {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-controls {
|
||||||
|
.row {
|
||||||
|
@extend .vertical-center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
* > button {
|
||||||
|
border: 0;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
|
||||||
|
&.enabled {
|
||||||
|
color: $button-enabled-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.fa {
|
||||||
|
color: $button-hover-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-play, .fa-pause {
|
||||||
|
color: $button-hover-color;
|
||||||
|
font-size: $font-size * 2;
|
||||||
|
margin-top: .3rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $play-button-hover-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-right {
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
button {
|
||||||
|
&:not(last-child) {
|
||||||
|
padding: 0 .7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-container {
|
||||||
|
button {
|
||||||
|
padding: 0 .3rem 0 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .seek-slider {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .volume-slider {
|
||||||
|
width: 75%;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .elapsed-time,
|
||||||
|
* > .total-time {
|
||||||
|
font-size: $control-time-font-size;
|
||||||
|
color: $control-time-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .elapsed-time {
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .total-time {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .item:hover {
|
||||||
|
background: $hover-bg !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
$button-enabled-color: #59df3e;
|
||||||
|
$button-hover-color: $button-enabled-color;
|
||||||
|
$play-button-hover-color: #64ef4a;
|
||||||
|
|
||||||
|
$artist-font-size: $font-size * 0.9333;
|
||||||
|
|
||||||
|
$duration-color: #666;
|
||||||
|
$duration-font-size: $font-size * 0.86666;
|
||||||
|
|
||||||
|
$control-panel-bg: rgba(245,245,245,0.95);
|
||||||
|
$control-panel-shadow: 0 -2.5px 4px 0 #c0c0c0;
|
||||||
|
$control-time-color: #666;
|
||||||
|
$control-time-font-size: $font-size * 0.666666;
|
||||||
|
|
||||||
|
$browser-panel-bg: rgba(248,250,250,0.95);
|
||||||
|
$browser-font-size: $font-size * 0.8666;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
Vue.component('music-mpd-browser-item', {
|
||||||
|
template: '#tmpl-music-mpd-browser-item',
|
||||||
|
props: ['type','name'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
331
platypush/backend/http/static/js/plugins/music.mpd/index.js
Normal file
331
platypush/backend/http/static/js/plugins/music.mpd/index.js
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
Vue.component('music-mpd', {
|
||||||
|
template: '#tmpl-music-mpd',
|
||||||
|
props: ['config'],
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
track: {},
|
||||||
|
status: {},
|
||||||
|
playlist: [],
|
||||||
|
timer: null,
|
||||||
|
browserPath: [],
|
||||||
|
browserItems: [],
|
||||||
|
syncTime: {
|
||||||
|
timestamp: null,
|
||||||
|
elapsed: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
refresh: async function() {
|
||||||
|
const getStatus = request('music.mpd.status');
|
||||||
|
const getTrack = request('music.mpd.currentsong');
|
||||||
|
const getPlaylist = request('music.mpd.playlistinfo');
|
||||||
|
const getBrowserItems = request('music.mpd.lsinfo');
|
||||||
|
|
||||||
|
let [status, track, playlist, browserItems] = await Promise.all([getStatus, getTrack, getPlaylist, getBrowserItems]);
|
||||||
|
|
||||||
|
this._parseStatus(status);
|
||||||
|
this._parseTrack(track);
|
||||||
|
this._parsePlaylist(playlist);
|
||||||
|
this._parseBrowserItems(browserItems);
|
||||||
|
|
||||||
|
if (this.status.state === 'play') {
|
||||||
|
this.startTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_parseStatus: async function(status) {
|
||||||
|
if (!status || status.length === 0) {
|
||||||
|
status = await request('music.mpd.status');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [attr, value] of Object.entries(status)) {
|
||||||
|
if (['consume','random','repeat','single','bitrate'].indexOf(attr) >= 0) {
|
||||||
|
Vue.set(this.status, attr, !!parseInt(value));
|
||||||
|
} else if (['nextsong','nextsongid','playlist','playlistlength',
|
||||||
|
'volume','xfade','song','songid'].indexOf(attr) >= 0) {
|
||||||
|
Vue.set(this.status, attr, parseInt(value));
|
||||||
|
} else if (['elapsed'].indexOf(attr) >= 0) {
|
||||||
|
Vue.set(this.status, attr, parseFloat(value));
|
||||||
|
} else {
|
||||||
|
Vue.set(this.status, attr, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_parseTrack: async function(track) {
|
||||||
|
if (!track || track.length === 0) {
|
||||||
|
track = await request('music.mpd.currentsong');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [attr, value] of Object.entries(track)) {
|
||||||
|
if (['id','pos','time'].indexOf(attr) >= 0) {
|
||||||
|
Vue.set(this.track, attr, parseInt(value));
|
||||||
|
} else {
|
||||||
|
Vue.set(this.track, attr, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_parsePlaylist: function(playlist) {
|
||||||
|
if (!playlist || playlist.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.playlist = [];
|
||||||
|
|
||||||
|
for (var track of playlist) {
|
||||||
|
for (const [attr, value] of Object.entries(track)) {
|
||||||
|
if (['time','pos','id'].indexOf(attr) >= 0) {
|
||||||
|
track[attr] = parseInt(value);
|
||||||
|
} else {
|
||||||
|
track[attr] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.playlist.push(track);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_parseBrowserItems: function(browserItems) {
|
||||||
|
if (!browserItems || browserItems.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.browserItems = [];
|
||||||
|
|
||||||
|
for (var item of browserItems) {
|
||||||
|
if (item.directory) {
|
||||||
|
this.browserItems.push({
|
||||||
|
type: 'directory',
|
||||||
|
name: item.directory,
|
||||||
|
});
|
||||||
|
} else if (item.playlist) {
|
||||||
|
this.browserItems.push({
|
||||||
|
type: 'playlist',
|
||||||
|
name: item.playlist,
|
||||||
|
'last-modified': item['last-modified'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
convertTime: function(time) {
|
||||||
|
time = parseFloat(time); // Normalize strings
|
||||||
|
var t = {};
|
||||||
|
t.h = '' + parseInt(time/3600);
|
||||||
|
t.m = '' + parseInt(time/60 - t.h*60);
|
||||||
|
t.s = '' + parseInt(time - t.m*60);
|
||||||
|
|
||||||
|
for (var attr of ['m','s']) {
|
||||||
|
if (parseInt(t[attr]) < 10) {
|
||||||
|
t[attr] = '0' + t[attr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = [];
|
||||||
|
if (parseInt(t.h)) {
|
||||||
|
ret.push(t.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.push(t.m, t.s);
|
||||||
|
return ret.join(':');
|
||||||
|
},
|
||||||
|
|
||||||
|
previous: async function() {
|
||||||
|
await request('music.mpd.previous');
|
||||||
|
let track = await request('music.mpd.currentsong');
|
||||||
|
this.onNewPlayingTrack({track: track});
|
||||||
|
},
|
||||||
|
|
||||||
|
repeat: async function() {
|
||||||
|
await request('music.mpd.repeat');
|
||||||
|
let status = await request('music.mpd.status');
|
||||||
|
this._parseStatus(status);
|
||||||
|
},
|
||||||
|
|
||||||
|
playPause: async function() {
|
||||||
|
await request('music.mpd.pause');
|
||||||
|
let status = await request('music.mpd.status');
|
||||||
|
const method = status.state === 'play' ? this.onMusicPlay : this.onMusicPause;
|
||||||
|
method({ status: status });
|
||||||
|
},
|
||||||
|
|
||||||
|
stop: async function() {
|
||||||
|
await request('music.mpd.stop');
|
||||||
|
this.onMusicStop({});
|
||||||
|
},
|
||||||
|
|
||||||
|
random: async function() {
|
||||||
|
await request('music.mpd.random');
|
||||||
|
let status = await request('music.mpd.status');
|
||||||
|
this._parseStatus(status);
|
||||||
|
},
|
||||||
|
|
||||||
|
next: async function() {
|
||||||
|
await request('music.mpd.next');
|
||||||
|
let track = await request('music.mpd.currentsong');
|
||||||
|
this.onNewPlayingTrack({track: track});
|
||||||
|
},
|
||||||
|
|
||||||
|
seek: async function(event) {
|
||||||
|
var value;
|
||||||
|
|
||||||
|
if (event.target) {
|
||||||
|
value = parseFloat(event.target.value);
|
||||||
|
} else if (event.value) {
|
||||||
|
value = parseFloat(event.value);
|
||||||
|
} else {
|
||||||
|
value = parseFloat(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await request('music.mpd.seekcur', {value: value});
|
||||||
|
this.onSeekChange({status: status});
|
||||||
|
},
|
||||||
|
|
||||||
|
volume: async function(event) {
|
||||||
|
var value;
|
||||||
|
|
||||||
|
if (event.target) {
|
||||||
|
value = parseFloat(event.target.value);
|
||||||
|
} else if (event.value) {
|
||||||
|
value = parseFloat(event.value);
|
||||||
|
} else {
|
||||||
|
value = parseFloat(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await request('music.mpd.setvol', {vol: value});
|
||||||
|
this.onVolumeChange({status: status});
|
||||||
|
},
|
||||||
|
|
||||||
|
onNewPlayingTrack: async function(event) {
|
||||||
|
var previousTrack = {
|
||||||
|
file: this.track.file,
|
||||||
|
artist: this.track.artist,
|
||||||
|
title: this.track.title,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.status.state = 'play';
|
||||||
|
this.status.elapsed = 0;
|
||||||
|
this.track = {};
|
||||||
|
|
||||||
|
let status = await request('music.mpd.status');
|
||||||
|
this._parseStatus(status);
|
||||||
|
this._parseTrack(event.track);
|
||||||
|
this.startTimer();
|
||||||
|
|
||||||
|
if (this.track.file != previousTrack.file
|
||||||
|
|| this.track.artist != previousTrack.artist
|
||||||
|
|| this.track.title != previousTrack.title) {
|
||||||
|
this.showNewTrackNotification();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showNewTrackNotification: function() {
|
||||||
|
createNotification({
|
||||||
|
html: '<b>' + (this.track.artist || '[No Artist]') + '</b><br>' +
|
||||||
|
(this.track.title || '[No Title]'),
|
||||||
|
image: {
|
||||||
|
icon: 'play',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onMusicStop: function(event) {
|
||||||
|
this.status.state = 'stop';
|
||||||
|
this._parseStatus(event.status);
|
||||||
|
this._parseTrack(event.track);
|
||||||
|
this.stopTimer();
|
||||||
|
},
|
||||||
|
|
||||||
|
onMusicPlay: function(event) {
|
||||||
|
this.status.state = 'play';
|
||||||
|
this._parseStatus(event.status);
|
||||||
|
this._parseTrack(event.track);
|
||||||
|
this.startTimer();
|
||||||
|
},
|
||||||
|
|
||||||
|
onMusicPause: function(event) {
|
||||||
|
this.status.state = 'pause';
|
||||||
|
this._parseStatus(event.status);
|
||||||
|
this._parseTrack(event.track);
|
||||||
|
|
||||||
|
this.syncTime.timestamp = new Date();
|
||||||
|
this.syncTime.elapsed = this.status.elapsed;
|
||||||
|
},
|
||||||
|
|
||||||
|
onSeekChange: function(event) {
|
||||||
|
if (event.position != null)
|
||||||
|
this.status.elapsed = parseFloat(event.position);
|
||||||
|
if (event.status)
|
||||||
|
this._parseStatus(event.status);
|
||||||
|
if (event.track)
|
||||||
|
this._parseTrack(event.track);
|
||||||
|
|
||||||
|
this.syncTime.timestamp = new Date();
|
||||||
|
this.syncTime.elapsed = this.status.elapsed;
|
||||||
|
},
|
||||||
|
|
||||||
|
onPlaylistChange: function(event) {
|
||||||
|
console.log(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
onVolumeChange: function(event) {
|
||||||
|
if (event.volume != null)
|
||||||
|
this.status.volume = parseFloat(event.volume);
|
||||||
|
if (event.status)
|
||||||
|
this._parseStatus(event.status);
|
||||||
|
if (event.track)
|
||||||
|
this._parseTrack(event.track);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRepeatChange: function(event) {
|
||||||
|
this.status.repeat = event.state;
|
||||||
|
},
|
||||||
|
|
||||||
|
onRandomChange: function(event) {
|
||||||
|
this.status.random = event.state;
|
||||||
|
},
|
||||||
|
|
||||||
|
startTimer: function() {
|
||||||
|
if (this.timer != null) {
|
||||||
|
this.stopTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.syncTime.timestamp = new Date();
|
||||||
|
this.syncTime.elapsed = this.status.elapsed;
|
||||||
|
this.timer = setInterval(this.timerFunc, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopTimer: function() {
|
||||||
|
if (this.timer == null) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
timerFunc: function() {
|
||||||
|
if (this.status.state !== 'play' || this.status.elapsed == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status.elapsed = this.syncTime.elapsed +
|
||||||
|
((new Date()).getTime()/1000) - (this.syncTime.timestamp.getTime()/1000);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
this.refresh();
|
||||||
|
registerEventHandler(this.onNewPlayingTrack, 'platypush.message.event.music.NewPlayingTrackEvent');
|
||||||
|
registerEventHandler(this.onMusicStop, 'platypush.message.event.music.MusicStopEvent');
|
||||||
|
registerEventHandler(this.onMusicPlay, 'platypush.message.event.music.MusicPlayEvent');
|
||||||
|
registerEventHandler(this.onMusicPause, 'platypush.message.event.music.MusicPauseEvent');
|
||||||
|
registerEventHandler(this.onSeekChange, 'platypush.message.event.music.SeekChangeEvent');
|
||||||
|
registerEventHandler(this.onPlaylistChange, 'platypush.message.event.music.PlaylistChangeEvent');
|
||||||
|
registerEventHandler(this.onVolumeChange, 'platypush.message.event.music.VolumeChangeEvent');
|
||||||
|
registerEventHandler(this.onRepeatChange, 'platypush.message.event.music.PlaybackRepeatModeChangeEvent');
|
||||||
|
registerEventHandler(this.onRandomChange, 'platypush.message.event.music.PlaybackRandomModeChangeEvent');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<script type="text/x-template" id="tmpl-app-header">
|
|
||||||
<header class="s-hidden m-hidden">
|
|
||||||
<div class="row">
|
|
||||||
<div class="logo col-9">
|
|
||||||
<span class="logo-1">Platypush</span>
|
|
||||||
<span class="logo-2">Web Panel</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="date-time col-3">
|
|
||||||
<div class="date" v-text="now.toDateString().substring(0,10)"></div>
|
|
||||||
<div class="time" v-text="now.toTimeString().substring(0,8)"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<app-header></app-header>
|
|
||||||
|
|
|
@ -40,25 +40,31 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% include 'elements.html' %}
|
{% include 'elements.html' %}
|
||||||
|
|
||||||
|
{% for plugin, conf in templates.items() %}
|
||||||
|
{% with configuration=templates[plugin] %}
|
||||||
|
{% include conf['_template_file'] %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for script in scripts.values() %}
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename=script['_script_file']) }}"></script>
|
||||||
|
{% endfor %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
{# include 'header.html' #}
|
|
||||||
|
|
||||||
{% with plugins=templates.keys() %}
|
{% with plugins=templates.keys() %}
|
||||||
{% include 'nav.html' %}
|
{% include 'nav.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="plugins-container">
|
<div class="plugins-container">
|
||||||
{% for plugin, conf in templates.items() %}
|
<plugin v-for="(conf, plugin) in {{ utils.to_json(templates) }}"
|
||||||
{% with configuration=templates[plugin], utils=utils %}
|
:tag="plugin.replace('.', '-')"
|
||||||
{% include conf['_template_file'] %}
|
:key="plugin"
|
||||||
<plugin tag="{{ utils.plugin_name_to_tag(plugin) }}"
|
:config="conf"
|
||||||
:config="{{ conf }}" :class="{hidden: '{{ plugin }}' != selectedPlugin}"/>
|
:class="{hidden: plugin != selectedPlugin}"/>
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
@ -67,10 +73,6 @@
|
||||||
|
|
||||||
{% include 'plugins/template.html' %}
|
{% include 'plugins/template.html' %}
|
||||||
|
|
||||||
{% for script in scripts.values() %}
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename=script['_script_file']) }}"></script>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,9 @@
|
||||||
{{ plugin }}
|
{{ plugin }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<span class="decorator" v-if="'{{ plugin }}' == selectedPlugin"></span>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="#{{ plugin }}">Test tab 2</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#{{ plugin }}">Test tab 3</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<div class="date-time pull-right">
|
<div class="date-time pull-right">
|
||||||
<!--<div class="date" v-text="now.toDateString().substring(0,10)"></div>-->
|
|
||||||
<div class="time" v-text="now.toTimeString().substring(0,8)"></div>
|
<div class="time" v-text="now.toTimeString().substring(0,8)"></div>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/music.mpd/browser.js') }}"></script>
|
||||||
|
|
||||||
|
<script type="text/x-template" id="tmpl-music-mpd-browser-item">
|
||||||
|
<div class="row item">
|
||||||
|
<div class="col-1 icon">
|
||||||
|
<i class="fa fa-folder" v-if="type == 'directory'"></i>
|
||||||
|
<i class="fa fa-list" v-else-if="type == 'playlist'"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-11 name" v-text="name"></div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
{% include 'plugins/music.mpd/browser.html' %}
|
||||||
|
|
||||||
|
<script type="text/x-template" id="tmpl-music-mpd">
|
||||||
|
<div class="row music-mpd-container">
|
||||||
|
<div class="row panels">
|
||||||
|
<div class="col-no-margin-l-3 s-hidden m-hidden browser">
|
||||||
|
<music-mpd-browser-item
|
||||||
|
v-for="item in browserItems"
|
||||||
|
:key="item.type + '-' + item.name"
|
||||||
|
:type="item.type"
|
||||||
|
:name="item.name">
|
||||||
|
</music-mpd-browser-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-no-margin-s-12 col-no-margin-m-12 col-no-margin-l-9 playlist">
|
||||||
|
<div class="row track item"
|
||||||
|
v-for="track in playlist">
|
||||||
|
<div class="col-5 artist" v-text="track.artist"></div>
|
||||||
|
<div class="col-5 title" v-text="track.title"></div>
|
||||||
|
<div class="col-2 pull-right duration" v-text="convertTime(track.time)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row controls">
|
||||||
|
<div class="col-3 track-container">
|
||||||
|
<div class="track-info" v-if="status.state == 'play' || status.state == 'pause'">
|
||||||
|
<div class="row artist" v-text="track.artist"></div>
|
||||||
|
<div class="row title" v-text="track.title"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 playback-controls">
|
||||||
|
<div class="row">
|
||||||
|
<button @click="previous">
|
||||||
|
<i class="fa fa-step-backward"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="playPause">
|
||||||
|
<i class="fa fa-pause" v-if="status.state == 'play'"></i>
|
||||||
|
<i class="fa fa-play" v-else></i>
|
||||||
|
</button>
|
||||||
|
<button @click="stop" v-if="status.state != 'stop'">
|
||||||
|
<i class="fa fa-stop"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="next">
|
||||||
|
<i class="fa fa-step-forward"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<span class="elapsed-time" v-text="status.elapsed && status.state != 'stop' ? convertTime(status.elapsed) : '-:--'"></span>
|
||||||
|
<input type="range"
|
||||||
|
class="slider seek-slider"
|
||||||
|
v-model="status.elapsed"
|
||||||
|
min="0"
|
||||||
|
:max="track.time"
|
||||||
|
:disabled="!track.time || status.state == 'stop'"
|
||||||
|
@input="seek">
|
||||||
|
<span class="total-time" v-text="track.time && status.state != 'stop' ? convertTime(track.time) : '-:--'"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-3 pull-right">
|
||||||
|
<div class="row">
|
||||||
|
<button @click="random" :class="{enabled: status.random}">
|
||||||
|
<i class="fa fa-random"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="repeat" :class="{enabled: status.repeat}">
|
||||||
|
<i class="fa fa-repeat"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row volume-container">
|
||||||
|
<button disabled>
|
||||||
|
<i class="fa fa-volume-up"></i>
|
||||||
|
</button>
|
||||||
|
<input type="range"
|
||||||
|
class="slider volume-slider"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
v-model="status.volume"
|
||||||
|
@input="volume">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
|
@ -185,11 +185,11 @@ class MusicMopidyBackend(Backend):
|
||||||
new_status = self._get_tracklist_status()
|
new_status = self._get_tracklist_status()
|
||||||
if new_status['random'] != self._latest_status.get('random'):
|
if new_status['random'] != self._latest_status.get('random'):
|
||||||
self.bus.post(PlaybackRandomModeChangeEvent(state=new_status['random']))
|
self.bus.post(PlaybackRandomModeChangeEvent(state=new_status['random']))
|
||||||
if new_status['repeat'] != self._latest_status('repeat'):
|
if new_status['repeat'] != self._latest_status['repeat']:
|
||||||
self.bus.post(PlaybackRepeatModeChangeEvent(state=new_status['repeat']))
|
self.bus.post(PlaybackRepeatModeChangeEvent(state=new_status['repeat']))
|
||||||
if new_status['single'] != self._latest_status('single'):
|
if new_status['single'] != self._latest_status['single']:
|
||||||
self.bus.post(PlaybackSingleModeChangeEvent(state=new_status['single']))
|
self.bus.post(PlaybackSingleModeChangeEvent(state=new_status['single']))
|
||||||
if new_status['consume'] != self._latest_status('consume'):
|
if new_status['consume'] != self._latest_status['consume']:
|
||||||
self.bus.post(PlaybackConsumeModeChangeEvent(state=new_status['consume']))
|
self.bus.post(PlaybackConsumeModeChangeEvent(state=new_status['consume']))
|
||||||
|
|
||||||
self._latest_status = new_status
|
self._latest_status = new_status
|
||||||
|
|
|
@ -37,37 +37,50 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
self.port = port
|
self.port = port
|
||||||
self.client = None
|
self.client = None
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self, n_tries=2):
|
||||||
if not self.client:
|
with self._client_lock:
|
||||||
self.client = mpd.MPDClient(use_unicode=True)
|
if self.client:
|
||||||
self.client.connect(self.host, self.port)
|
return
|
||||||
return self.client
|
|
||||||
|
error = None
|
||||||
|
while n_tries > 0:
|
||||||
|
try:
|
||||||
|
n_tries -= 1
|
||||||
|
self.client = mpd.MPDClient(use_unicode=True)
|
||||||
|
self.client.connect(self.host, self.port)
|
||||||
|
return self.client
|
||||||
|
except Exception as e:
|
||||||
|
error = e
|
||||||
|
self.logger.warning('Connection exception: {}{}'.
|
||||||
|
format(str(e), (': Retrying' if n_tries > 0 else '')))
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
self.client = None
|
||||||
|
raise error
|
||||||
|
|
||||||
def _exec(self, method, *args, **kwargs):
|
def _exec(self, method, *args, **kwargs):
|
||||||
n_tries = int(kwargs.pop('n_tries')) if 'n_tries' in kwargs else 1
|
error = None
|
||||||
|
n_tries = int(kwargs.pop('n_tries')) if 'n_tries' in kwargs else 2
|
||||||
return_status = kwargs.pop('return_status') \
|
return_status = kwargs.pop('return_status') \
|
||||||
if 'return_status' in kwargs else True
|
if 'return_status' in kwargs else True
|
||||||
|
|
||||||
try:
|
while n_tries > 0:
|
||||||
self._connect()
|
try:
|
||||||
response = None
|
self._connect()
|
||||||
with self._client_lock:
|
n_tries -= 1
|
||||||
response = getattr(self.client, method)(*args, **kwargs)
|
with self._client_lock:
|
||||||
|
response = getattr(self.client, method)(*args, **kwargs)
|
||||||
|
|
||||||
if return_status:
|
if return_status:
|
||||||
return self.status().output
|
return self.status().output
|
||||||
return response
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning('Exception while executing MPD method {}: {}'.
|
error = str(e)
|
||||||
format(method, str(e)))
|
self.logger.warning('Exception while executing MPD method {}: {}'.
|
||||||
self.client = None
|
format(method, error))
|
||||||
|
self.client = None
|
||||||
|
|
||||||
if n_tries > 0:
|
return None, error
|
||||||
kwargs['return_status'] = return_status
|
|
||||||
kwargs['n_tries'] = n_tries-1
|
|
||||||
return self._exec(method, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
return (None, str(e))
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def play(self, resource=None):
|
def play(self, resource=None):
|
||||||
|
@ -346,14 +359,21 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
n_tries = 2
|
||||||
self._connect()
|
error = None
|
||||||
return self.client.status()
|
|
||||||
except Exception as e:
|
while n_tries > 0:
|
||||||
self.logger.warning('Exception while getting MPD status: {}'.
|
try:
|
||||||
format(str(e)))
|
n_tries -= 1
|
||||||
self.client = None
|
self._connect()
|
||||||
return (None, str(e))
|
return self.client.status()
|
||||||
|
except Exception as e:
|
||||||
|
error = e
|
||||||
|
self.logger.warning('Exception while getting MPD status: {}'.
|
||||||
|
format(str(e)))
|
||||||
|
self.client = None
|
||||||
|
|
||||||
|
return None, error
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def currentsong(self):
|
def currentsong(self):
|
||||||
|
|
Loading…
Reference in a new issue