2019-05-30 02:07:28 +02:00
|
|
|
Vue.component('music-mpd', {
|
|
|
|
template: '#tmpl-music-mpd',
|
|
|
|
props: ['config'],
|
|
|
|
data: function() {
|
|
|
|
return {
|
|
|
|
track: {},
|
|
|
|
status: {},
|
|
|
|
timer: null,
|
2019-06-02 00:54:49 +02:00
|
|
|
playlist: [],
|
|
|
|
playlistFilter: '',
|
|
|
|
browserFilter: '',
|
2019-05-30 02:07:28 +02:00
|
|
|
browserPath: [],
|
|
|
|
browserItems: [],
|
2019-06-02 00:54:49 +02:00
|
|
|
|
|
|
|
selectionMode: {
|
|
|
|
playlist: false,
|
|
|
|
browser: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
selectedPlaylistItems: {},
|
|
|
|
selectedBrowserItems: {},
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
syncTime: {
|
|
|
|
timestamp: null,
|
|
|
|
elapsed: null,
|
|
|
|
},
|
2019-06-02 00:54:49 +02:00
|
|
|
|
|
|
|
newTrackLock: false,
|
2019-05-30 02:07:28 +02:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
computed: {
|
|
|
|
playlistDropdownItems: function() {
|
|
|
|
var self = this;
|
2019-06-03 23:37:19 +02:00
|
|
|
var items = [];
|
2019-06-02 00:54:49 +02:00
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
if (Object.keys(this.selectedPlaylistItems).length === 1) {
|
|
|
|
items.push({
|
2019-06-02 00:54:49 +02:00
|
|
|
text: 'Play',
|
|
|
|
icon: 'play',
|
|
|
|
click: async function() {
|
|
|
|
await self.playpos();
|
2019-06-03 23:37:19 +02:00
|
|
|
self.selectedPlaylistItems = {};
|
2019-06-02 00:54:49 +02:00
|
|
|
},
|
2019-06-03 23:37:19 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
items.push(
|
2019-06-02 00:54:49 +02:00
|
|
|
{
|
|
|
|
text: 'Add to playlist',
|
|
|
|
icon: 'list',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text: 'Move',
|
|
|
|
icon: 'retweet',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text: 'Remove from queue',
|
|
|
|
icon: 'trash',
|
|
|
|
click: async function() {
|
|
|
|
await self.del();
|
2019-06-03 23:37:19 +02:00
|
|
|
self.selectedPlaylistItems = {};
|
2019-06-02 00:54:49 +02:00
|
|
|
},
|
|
|
|
},
|
2019-06-03 23:37:19 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (Object.keys(this.selectedPlaylistItems).length === 1) {
|
|
|
|
items.push({
|
2019-06-02 00:54:49 +02:00
|
|
|
text: 'View track info',
|
|
|
|
icon: 'info',
|
2019-06-03 23:37:19 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return items;
|
|
|
|
},
|
|
|
|
|
|
|
|
browserDropdownItems: function() {
|
|
|
|
var self = this;
|
|
|
|
var items = [];
|
|
|
|
|
|
|
|
if (Object.keys(this.selectedBrowserItems).length === 1 &&
|
|
|
|
Object.values(this.selectedBrowserItems)[0].type === 'directory') {
|
|
|
|
items.push({
|
|
|
|
text: 'Open',
|
|
|
|
icon: 'folder',
|
|
|
|
click: async function() {
|
|
|
|
await self.cd();
|
|
|
|
self.selectedBrowserItems = {};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(this.selectedBrowserItems).length === 1) {
|
|
|
|
items.push(
|
|
|
|
{
|
|
|
|
text: 'Add and play',
|
|
|
|
icon: 'play',
|
|
|
|
click: async function() {
|
|
|
|
const item = Object.values(self.selectedBrowserItems)[0];
|
|
|
|
var promise;
|
|
|
|
|
|
|
|
switch (item.type) {
|
|
|
|
case 'playlist':
|
|
|
|
promise = self.load(item.name);
|
|
|
|
break;
|
|
|
|
case 'file':
|
|
|
|
promise = self.add(item.name, position=0);
|
|
|
|
break;
|
|
|
|
case 'directory':
|
|
|
|
promise = self.add(item.name);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.warning('Unable to handle type: ' + item.type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
await self.playpos(0);
|
|
|
|
self.selectedBrowserItems = {};
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text: 'Replace and play',
|
|
|
|
icon: 'play',
|
|
|
|
click: async function() {
|
|
|
|
await self.clear();
|
|
|
|
|
|
|
|
const item = Object.values(self.selectedBrowserItems)[0];
|
|
|
|
var promise;
|
|
|
|
|
|
|
|
switch (item.type) {
|
|
|
|
case 'playlist':
|
|
|
|
promise = self.load(item.name);
|
|
|
|
break;
|
|
|
|
case 'file':
|
|
|
|
promise = self.add(item.name, position=0);
|
|
|
|
break;
|
|
|
|
case 'directory':
|
|
|
|
promise = self.add(item.name);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.warning('Unable to handle type: ' + item.type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
await self.playpos(0);
|
|
|
|
self.selectedBrowserItems = {};
|
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
items.push(
|
|
|
|
{
|
|
|
|
text: 'Add to queue',
|
|
|
|
icon: 'plus',
|
|
|
|
click: async function() {
|
|
|
|
const items = Object.values(self.selectedBrowserItems);
|
|
|
|
const promises = items.map(item => item.type === 'playlist' ? self.load(item.name) : self.add(item.name));
|
|
|
|
|
|
|
|
await Promise.all(promises);
|
|
|
|
self.selectedBrowserItems = {};
|
|
|
|
},
|
2019-06-02 00:54:49 +02:00
|
|
|
},
|
2019-06-03 23:37:19 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (Object.keys(this.selectedBrowserItems).length === 1
|
|
|
|
&& Object.values(this.selectedBrowserItems)[0].type === 'playlist') {
|
|
|
|
items.push({
|
|
|
|
text: 'Edit',
|
|
|
|
icon: 'pen',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.values(this.selectedBrowserItems).filter(item => item.type === 'playlist').length === Object.values(this.selectedBrowserItems).length) {
|
|
|
|
items.push({
|
|
|
|
text: 'Remove',
|
|
|
|
icon: 'trash',
|
|
|
|
click: async function() {
|
|
|
|
const items = Object.values(self.selectedBrowserItems);
|
|
|
|
await self.rm(playlists);
|
|
|
|
self.selectedBrowserItems = {};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(this.selectedBrowserItems).length === 1
|
|
|
|
&& Object.values(this.selectedBrowserItems)[0].type === 'file') {
|
|
|
|
items.push({
|
|
|
|
text: 'View info',
|
|
|
|
icon: 'info',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return items;
|
2019-06-02 00:54:49 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
// Hack-ish workaround to get the browser and playlist panels to keep their height
|
|
|
|
// in sync with the nav and control bars, as both those elements are fixed.
|
|
|
|
adjustLayout: function() {
|
|
|
|
const adjust = (self) => {
|
|
|
|
const nav = document.querySelector('nav');
|
|
|
|
const panels = document.querySelectorAll('.music-mpd-container .panels .panel');
|
|
|
|
const controls = document.querySelector('.music-mpd-container .controls');
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
const panelHeight = window.innerHeight - nav.clientHeight - controls.clientHeight - 5;
|
|
|
|
if (panelHeight >= 0) {
|
|
|
|
for (const panel of panels) {
|
|
|
|
if (panelHeight != parseFloat(panel.style.height)) {
|
|
|
|
panel.style.height = panelHeight + 'px';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
adjust(this)();
|
|
|
|
setInterval(adjust(this), 2000);
|
|
|
|
},
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
_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({
|
2019-06-03 23:37:19 +02:00
|
|
|
id: 'directory:' + item.directory,
|
2019-05-30 02:07:28 +02:00
|
|
|
type: 'directory',
|
|
|
|
name: item.directory,
|
|
|
|
});
|
|
|
|
} else if (item.playlist) {
|
|
|
|
this.browserItems.push({
|
2019-06-03 23:37:19 +02:00
|
|
|
id: 'playlist:' + item.playlist,
|
2019-05-30 02:07:28 +02:00
|
|
|
type: 'playlist',
|
|
|
|
name: item.playlist,
|
|
|
|
'last-modified': item['last-modified'],
|
|
|
|
});
|
2019-06-03 23:37:19 +02:00
|
|
|
} else if (item.file) {
|
|
|
|
this.browserItems.push({
|
|
|
|
id: item.file,
|
|
|
|
type: 'file',
|
|
|
|
name: item.file,
|
|
|
|
...item,
|
|
|
|
});
|
2019-05-30 02:07:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
consume: async function() {
|
|
|
|
await request('music.mpd.consume');
|
|
|
|
let status = await request('music.mpd.status');
|
|
|
|
this._parseStatus(status);
|
|
|
|
},
|
|
|
|
|
|
|
|
single: async function() {
|
|
|
|
await request('music.mpd.single');
|
|
|
|
let status = await request('music.mpd.status');
|
|
|
|
this._parseStatus(status);
|
|
|
|
},
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
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 });
|
|
|
|
},
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
playpos: async function(pos) {
|
|
|
|
if (pos == null) {
|
|
|
|
if (!Object.keys(this.selectedPlaylistItems).length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pos = Object.keys(this.selectedPlaylistItems)[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = await request('music.mpd.play_pos', {pos: pos});
|
|
|
|
this._parseStatus(status);
|
|
|
|
|
|
|
|
let track = await request('music.mpd.currentsong');
|
|
|
|
this._parseTrack(track);
|
|
|
|
},
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
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});
|
|
|
|
},
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
clear: async function() {
|
|
|
|
if (!confirm('Are you sure that you want to clear the playlist?')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await request('music.mpd.clear');
|
|
|
|
this.stopTimer();
|
|
|
|
this.track = {};
|
|
|
|
this.playlist = [];
|
|
|
|
|
|
|
|
let status = await request('music.mpd.status');
|
|
|
|
this._parseStatus(status);
|
|
|
|
},
|
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
add: async function(resource, position=null) {
|
|
|
|
var args = {resource: resource};
|
|
|
|
if (position != null) {
|
|
|
|
args.position = position;
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = await request('music.mpd.add', args);
|
|
|
|
this._parseStatus(status);
|
|
|
|
|
|
|
|
let playlist = await request('music.mpd.playlistinfo');
|
|
|
|
this._parsePlaylist(playlist);
|
|
|
|
},
|
|
|
|
|
|
|
|
load: async function(item) {
|
|
|
|
let status = await request('music.mpd.load', {playlist:item});
|
|
|
|
this._parseStatus(status);
|
|
|
|
|
|
|
|
let playlist = await request('music.mpd.playlistinfo');
|
|
|
|
this._parsePlaylist(playlist);
|
|
|
|
},
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
del: async function() {
|
|
|
|
const positions = Object.keys(this.selectedPlaylistItems);
|
|
|
|
if (!positions.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = await request('music.mpd.delete', {'positions': positions});
|
|
|
|
this._parseStatus(status);
|
|
|
|
|
|
|
|
for (const pos in positions) {
|
|
|
|
Vue.delete(this.selectedPlaylistItems, pos);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
rm: async function(items) {
|
|
|
|
if (!items) {
|
|
|
|
items = Object.values(this.selectedBrowserItems);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!items.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = await request('music.mpd.rm', {resource: items.map(_ => _.name)});
|
|
|
|
this._parseStatus(status);
|
|
|
|
|
|
|
|
items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
|
|
|
this._parseBrowserItems(items);
|
|
|
|
},
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
swap: async function() {
|
|
|
|
if (Object.keys(this.selectedPlaylistItems).length !== 2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const positions = Object.keys(this.selectedPlaylistItems).sort();
|
|
|
|
await request('music.mpd.move', {from_pos: positions[1], to_pos: positions[0]});
|
|
|
|
|
|
|
|
let status = await request('music.mpd.move', {from_pos: positions[0]+1, to_pos: positions[1]});
|
|
|
|
this._parseStatus(status);
|
|
|
|
|
|
|
|
const playlist = await request('music.mpd.playlistinfo');
|
|
|
|
this._parsePlaylist(playlist);
|
|
|
|
},
|
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
cd: async function() {
|
|
|
|
const item = Object.values(this.selectedBrowserItems)[0];
|
|
|
|
|
|
|
|
if (item.name === '..') {
|
|
|
|
if (this.browserPath.length) {
|
|
|
|
this.browserPath.pop();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.browserPath = item.name.split('/');
|
|
|
|
}
|
|
|
|
|
|
|
|
const items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
|
|
|
this._parseBrowserItems(items);
|
|
|
|
},
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
onNewPlayingTrack: async function(event) {
|
|
|
|
var previousTrack = {
|
|
|
|
file: this.track.file,
|
|
|
|
artist: this.track.artist,
|
|
|
|
title: this.track.title,
|
|
|
|
};
|
|
|
|
|
|
|
|
this.status.state = 'play';
|
2019-06-02 00:54:49 +02:00
|
|
|
Vue.set(this.status, 'elapsed', 0);
|
2019-05-30 02:07:28 +02:00
|
|
|
this.track = {};
|
2019-06-02 00:54:49 +02:00
|
|
|
this._parseTrack(event.track);
|
2019-05-30 02:07:28 +02:00
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
let status = event.status ? event.status : await request('music.mpd.status');
|
2019-05-30 02:07:28 +02:00
|
|
|
this._parseStatus(status);
|
|
|
|
this.startTimer();
|
|
|
|
|
|
|
|
if (this.track.file != previousTrack.file
|
|
|
|
|| this.track.artist != previousTrack.artist
|
|
|
|
|| this.track.title != previousTrack.title) {
|
|
|
|
this.showNewTrackNotification();
|
2019-06-02 00:54:49 +02:00
|
|
|
|
|
|
|
const self = this;
|
|
|
|
setTimeout(function() {
|
|
|
|
self.scrollToActiveTrack();
|
|
|
|
}, 100);
|
2019-05-30 02:07:28 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
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';
|
2019-06-02 00:54:49 +02:00
|
|
|
Vue.set(this.status, 'elapsed', 0);
|
2019-05-30 02:07:28 +02:00
|
|
|
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);
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
Vue.set(this.syncTime, 'timestamp', new Date());
|
|
|
|
Vue.set(this.syncTime, 'elapsed', this.status.elapsed);
|
2019-05-30 02:07:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
onSeekChange: function(event) {
|
|
|
|
if (event.position != null)
|
2019-06-02 00:54:49 +02:00
|
|
|
Vue.set(this.status, 'elapsed', parseFloat(event.position));
|
2019-05-30 02:07:28 +02:00
|
|
|
if (event.status)
|
|
|
|
this._parseStatus(event.status);
|
|
|
|
if (event.track)
|
|
|
|
this._parseTrack(event.track);
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
Vue.set(this.syncTime, 'timestamp', new Date());
|
|
|
|
Vue.set(this.syncTime, 'elapsed', this.status.elapsed);
|
2019-05-30 02:07:28 +02:00
|
|
|
},
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
onPlaylistChange: async function(event) {
|
|
|
|
if (event.changes) {
|
|
|
|
this.playlist = event.changes;
|
|
|
|
} else {
|
|
|
|
const playlist = await request('music.mpd.playlistinfo');
|
|
|
|
this._parsePlaylist(playlist);
|
|
|
|
}
|
2019-05-30 02:07:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
onConsumeChange: function(event) {
|
|
|
|
this.status.consume = event.state;
|
|
|
|
},
|
|
|
|
|
|
|
|
onSingleChange: function(event) {
|
|
|
|
this.status.single = event.state;
|
|
|
|
},
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
startTimer: function() {
|
|
|
|
if (this.timer != null) {
|
|
|
|
this.stopTimer();
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
Vue.set(this.syncTime, 'timestamp', new Date());
|
|
|
|
Vue.set(this.syncTime, 'elapsed', this.status.elapsed);
|
2019-05-30 02:07:28 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
Vue.set(this.status, 'elapsed', this.syncTime.elapsed +
|
|
|
|
((new Date()).getTime()/1000) - (this.syncTime.timestamp.getTime()/1000));
|
|
|
|
},
|
|
|
|
|
|
|
|
matchesPlaylistFilter: function(track) {
|
|
|
|
if (this.playlistFilter.length === 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return [track.artist || '', track.title || '', track.album || '']
|
|
|
|
.join(' ').toLocaleLowerCase().indexOf(
|
|
|
|
this.playlistFilter.split(' ').filter(_ => _.length > 0).map(_ => _.toLocaleLowerCase()).join(' ')
|
|
|
|
) >= 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
matchesBrowserFilter: function(item) {
|
|
|
|
if (this.browserFilter.length === 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return item.name.toLocaleLowerCase().indexOf(
|
|
|
|
this.browserFilter.toLocaleLowerCase().split(' ').filter(_ => _.length > 0).join(' ')) >= 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
onPlaylistItemClick: function(track) {
|
|
|
|
if (this.selectionMode.playlist) {
|
|
|
|
if (track.pos in this.selectedPlaylistItems) {
|
|
|
|
Vue.delete(this.selectedPlaylistItems, track.pos);
|
|
|
|
} else {
|
|
|
|
Vue.set(this.selectedPlaylistItems, track.pos, track);
|
|
|
|
}
|
|
|
|
} else if (track.pos in this.selectedPlaylistItems) {
|
|
|
|
Vue.delete(this.selectedPlaylistItems, track.pos);
|
|
|
|
} else {
|
|
|
|
this.selectedPlaylistItems = {};
|
|
|
|
Vue.set(this.selectedPlaylistItems, track.pos, track);
|
|
|
|
openDropdown(this.$refs.playlistDropdown.$el);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
onBrowserItemClick: function(item) {
|
|
|
|
if (item.type === 'directory' && item.name === '..') {
|
|
|
|
this.selectedBrowserItems = {};
|
|
|
|
this.selectedBrowserItems[item.id] = item;
|
|
|
|
this.cd();
|
|
|
|
this.selectedBrowserItems = {};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.selectionMode.browser) {
|
|
|
|
if (item.id in this.selectedBrowserItems) {
|
|
|
|
Vue.delete(this.selectedBrowserItems, item.id);
|
|
|
|
} else {
|
|
|
|
Vue.set(this.selectedBrowserItems, item.id, item);
|
|
|
|
}
|
|
|
|
} else if (item.id in this.selectedBrowserItems) {
|
|
|
|
Vue.delete(this.selectedBrowserItems, item.id);
|
|
|
|
} else {
|
|
|
|
this.selectedBrowserItems = {};
|
|
|
|
Vue.set(this.selectedBrowserItems, item.id, item);
|
|
|
|
openDropdown(this.$refs.browserDropdown.$el);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
togglePlaylistSelectionMode: function() {
|
2019-06-03 23:37:19 +02:00
|
|
|
if (this.selectionMode.playlist && Object.keys(this.selectedPlaylistItems).length) {
|
|
|
|
openDropdown(this.$refs.playlistDropdown.$el);
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
this.selectionMode.playlist = !this.selectionMode.playlist;
|
2019-06-03 23:37:19 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
playlistSelectAll: function() {
|
|
|
|
this.selectedPlaylistItems = {};
|
|
|
|
this.selectionMode.playlist = true;
|
|
|
|
|
|
|
|
for (var track of this.playlist) {
|
|
|
|
this.selectedPlaylistItems[track.pos] = track;
|
2019-06-02 00:54:49 +02:00
|
|
|
}
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
openDropdown(this.$refs.playlistDropdown.$el);
|
2019-06-02 00:54:49 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
toggleBrowserSelectionMode: function() {
|
2019-06-03 23:37:19 +02:00
|
|
|
if (this.selectionMode.browser && Object.keys(this.selectedBrowserItems).length) {
|
|
|
|
openDropdown(this.$refs.browserDropdown.$el);
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
this.selectionMode.browser = !this.selectionMode.browser;
|
2019-06-03 23:37:19 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
browserSelectAll: function() {
|
|
|
|
this.selectedBrowserItems = {};
|
|
|
|
this.selectionMode.browser = true;
|
|
|
|
|
|
|
|
for (var item of this.browserItems) {
|
|
|
|
if (item.type !== 'directory' && item.name !== '..') {
|
|
|
|
this.selectedBrowserItems[item.id] = item;
|
|
|
|
}
|
2019-06-02 00:54:49 +02:00
|
|
|
}
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
openDropdown(this.$refs.browserDropdown.$el);
|
2019-06-02 00:54:49 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
scrollToActiveTrack: function() {
|
|
|
|
if (this.$refs.activePlaylistTrack && this.$refs.activePlaylistTrack.length) {
|
|
|
|
this.$refs.activePlaylistTrack[0].$el.scrollIntoView({behavior: 'smooth'});
|
|
|
|
}
|
2019-05-30 02:07:28 +02:00
|
|
|
},
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
addToPlaylistPrompt: async function() {
|
|
|
|
var resource = prompt('Path or URI of the resource to add');
|
|
|
|
if (!resource.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.add(resource);
|
|
|
|
},
|
|
|
|
|
|
|
|
savePlaylistPrompt: async function() {
|
|
|
|
var name = prompt('Playlist name');
|
|
|
|
if (!name.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = await request('music.mpd.save', {name: name});
|
|
|
|
this._parseStatus(status);
|
|
|
|
|
|
|
|
let items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
|
|
|
this._parseBrowserItems(items);
|
|
|
|
},
|
2019-05-30 02:07:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
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');
|
2019-06-03 23:37:19 +02:00
|
|
|
registerEventHandler(this.onConsumeChange, 'platypush.message.event.music.PlaybackConsumeModeChangeEvent');
|
|
|
|
registerEventHandler(this.onSingleChange, 'platypush.message.event.music.PlaybackSingleModeChangeEvent');
|
2019-05-30 02:07:28 +02:00
|
|
|
},
|
2019-06-02 00:54:49 +02:00
|
|
|
|
|
|
|
mounted: function() {
|
2019-06-03 23:37:19 +02:00
|
|
|
this.adjustLayout();
|
2019-06-02 00:54:49 +02:00
|
|
|
this.scrollToActiveTrack();
|
|
|
|
},
|
2019-05-30 02:07:28 +02:00
|
|
|
});
|
|
|
|
|