619 lines
No EOL
20 KiB
JavaScript
619 lines
No EOL
20 KiB
JavaScript
var socket;
|
|
|
|
Vue.component('footer-player', {
|
|
template: '#footer-player'
|
|
});
|
|
|
|
// vue instance
|
|
var app = new Vue({
|
|
el: '#app',
|
|
data: {
|
|
screen: "player",
|
|
player: {
|
|
currentMediaItem: {},
|
|
songActions: false,
|
|
lyrics: {},
|
|
lyricsMediaItem: {},
|
|
lyricsDebug: {
|
|
current: 0,
|
|
start: 0,
|
|
end: 0
|
|
},
|
|
queue: {},
|
|
lowerPanelState: "controls",
|
|
userInteraction: false
|
|
},
|
|
queue: {
|
|
temp: []
|
|
},
|
|
artistPage: {
|
|
data: {},
|
|
editorsNotes: false
|
|
},
|
|
albumPage: {
|
|
data: {},
|
|
editorsNotes: false
|
|
},
|
|
search: {
|
|
query: "",
|
|
results: [],
|
|
state: 0,
|
|
tab: "all",
|
|
searchType: "applemusic",
|
|
trackSelect: false,
|
|
selected: {},
|
|
queue: {},
|
|
lastPage: "search",
|
|
lastY: 0
|
|
},
|
|
lastPage: "player",
|
|
connectedState: 0,
|
|
url: window.location.hostname,
|
|
mode: "default",
|
|
// url: "localhost",
|
|
},
|
|
methods: {
|
|
searchScroll(e) {
|
|
this.search.lastY = e.target.scrollTop;
|
|
},
|
|
musicKitAPI(method, id, params) {
|
|
socket.send(
|
|
JSON.stringify({
|
|
action: "musickit-api",
|
|
method: method,
|
|
id: id,
|
|
params: params
|
|
})
|
|
)
|
|
},
|
|
resetPlayerUI() {
|
|
this.player.lowerPanelState = "controls";
|
|
},
|
|
musicAppVariant() {
|
|
if (navigator.userAgent.match(/Android/i)) {
|
|
return "Apple Music";
|
|
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
|
return "Music";
|
|
} else {
|
|
if (navigator.userAgent.indexOf('Mac') > 0) {
|
|
return 'Music';
|
|
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
|
return 'Apple Music Electron';
|
|
} else {
|
|
return 'Apple Music Electron';
|
|
}
|
|
}
|
|
},
|
|
checkOrientation() {
|
|
// check orientation of device
|
|
if (window.innerHeight > window.innerWidth) {
|
|
return 'portrait'
|
|
} else {
|
|
return 'landscape';
|
|
}
|
|
},
|
|
checkPlatformMD() {
|
|
// check if platfom is desktop or mobile
|
|
if (navigator.userAgent.match(/Android/i)) {
|
|
return "mobile";
|
|
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
|
return "mobile";
|
|
} else {
|
|
if (navigator.userAgent.indexOf('Mac') > 0) {
|
|
return 'desktop';
|
|
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
|
return 'desktop';
|
|
} else {
|
|
return 'desktop';
|
|
}
|
|
}
|
|
},
|
|
checkPlatform() {
|
|
if (navigator.userAgent.match(/Android/i)) {
|
|
return "android";
|
|
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
|
return "ios";
|
|
} else {
|
|
if (navigator.userAgent.indexOf('Mac') > 0) {
|
|
return 'mac';
|
|
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
|
return 'win';
|
|
} else {
|
|
return 'linux';
|
|
}
|
|
}
|
|
},
|
|
artworkPlaying() {
|
|
if (this.player.currentMediaItem.status) {
|
|
return
|
|
} else {
|
|
return ["paused"]
|
|
}
|
|
},
|
|
setAutoplay(value) {
|
|
socket.send(JSON.stringify({
|
|
"action": "set-autoplay",
|
|
"autoplay": value
|
|
}));
|
|
this.getCurrentMediaItem()
|
|
if (value) {
|
|
setTimeout(() => {
|
|
this.getQueue()
|
|
}, 1000)
|
|
} else {
|
|
this.getQueue()
|
|
}
|
|
},
|
|
seekTo(time, adjust = true) {
|
|
if (adjust) {
|
|
time = parseInt(time / 1000)
|
|
}
|
|
socket.send(JSON.stringify({
|
|
action: "seek",
|
|
time: time
|
|
}));
|
|
},
|
|
setVolume(volume) {
|
|
socket.send(JSON.stringify({
|
|
action: "volume",
|
|
volume: volume
|
|
}));
|
|
},
|
|
getQueue() {
|
|
socket.send(JSON.stringify({
|
|
action: "get-queue"
|
|
}))
|
|
},
|
|
play() {
|
|
socket.send(JSON.stringify({
|
|
action: "play"
|
|
}))
|
|
},
|
|
pause() {
|
|
socket.send(JSON.stringify({
|
|
action: "pause"
|
|
}))
|
|
},
|
|
next() {
|
|
socket.send(JSON.stringify({
|
|
action: "next"
|
|
}))
|
|
},
|
|
previous() {
|
|
socket.send(JSON.stringify({
|
|
action: "previous"
|
|
}))
|
|
},
|
|
searchArtist() {
|
|
this.search.query = this.player.currentMediaItem.artistName;
|
|
this.screen = "search";
|
|
this.searchQuery();
|
|
},
|
|
trackSelect(song) {
|
|
this.search.selected = song;
|
|
this.search.trackSelect = true
|
|
},
|
|
clearSelectedTrack() {
|
|
this.search.selected = {}
|
|
this.search.trackSelect = false
|
|
},
|
|
getArtworkColor(hex) {
|
|
return `#${hex}`
|
|
},
|
|
playMediaItemById(id, kind = "song") {
|
|
socket.send(JSON.stringify({
|
|
action: "play-mediaitem",
|
|
id: id,
|
|
kind: kind
|
|
}))
|
|
this.screen = "player";
|
|
},
|
|
playNext(type, id) {
|
|
socket.send(JSON.stringify({
|
|
action: "play-next",
|
|
type: type,
|
|
id: id
|
|
}))
|
|
},
|
|
playLater(type, id) {
|
|
socket.send(JSON.stringify({
|
|
action: "play-later",
|
|
type: type,
|
|
id: id
|
|
}))
|
|
},
|
|
searchQuery() {
|
|
if (this.search.query.length == 0) {
|
|
this.search.state = 0;
|
|
return;
|
|
}
|
|
this.search.state = 1;
|
|
var actionType = "search"
|
|
if (this.search.searchType == "library") {
|
|
actionType = "library-search"
|
|
}
|
|
socket.send(JSON.stringify({
|
|
"action": actionType,
|
|
"term": this.search.query,
|
|
"limit": 20
|
|
}))
|
|
},
|
|
quickSearch() {
|
|
var search = prompt("Search for a song", "")
|
|
if (search == null || search == "") {
|
|
return
|
|
}
|
|
|
|
socket.send(JSON.stringify({
|
|
action: "quick-play",
|
|
term: search
|
|
}))
|
|
},
|
|
parseTime(value) {
|
|
var minutes = Math.floor(value / 60000);
|
|
var seconds = ((value % 60000) / 1000).toFixed(0);
|
|
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
|
|
},
|
|
parseTimeDecimal(value) {
|
|
var minutes = Math.floor(value / 60000);
|
|
var seconds = ((value % 60000) / 1000).toFixed(0);
|
|
return minutes + "." + (seconds < 10 ? '0' : '') + seconds;
|
|
},
|
|
hmsToSecondsOnly(str) {
|
|
var p = str.split(':'),
|
|
s = 0,
|
|
m = 1;
|
|
|
|
while (p.length > 0) {
|
|
s += m * parseInt(p.pop(), 10);
|
|
m *= 60;
|
|
}
|
|
|
|
return s;
|
|
},
|
|
getCurrentTime() {
|
|
return parseFloat(this.hmsToSecondsOnly(this.parseTime(this.player.currentMediaItem.durationInMillis - this.player.currentMediaItem.remainingTime)));
|
|
},
|
|
percentage(partial, full) {
|
|
return (100 * partial) / full
|
|
},
|
|
getLyricBGStyle(start, end) {
|
|
var currentTime = this.getCurrentTime();
|
|
var duration = this.player.currentMediaItem.durationInMillis
|
|
var start2 = this.hmsToSecondsOnly(start)
|
|
var end2 = this.hmsToSecondsOnly(end)
|
|
var currentProgress = ((100 * (currentTime)) / (end2))
|
|
// check if currenttime is between start and end
|
|
this.player.lyricsDebug.start = start2
|
|
this.player.lyricsDebug.end = end2
|
|
this.player.lyricsDebug.current = currentTime
|
|
if (currentTime >= start2 && currentTime <= end2) {
|
|
return {
|
|
"--bgSpeed": `${(end2 - start2)}s`
|
|
}
|
|
} else {
|
|
return {}
|
|
}
|
|
},
|
|
getLyricClass(start, end) {
|
|
var currentTime = this.getCurrentTime();
|
|
// check if currenttime is between start and end
|
|
if (currentTime >= start && currentTime <= end) {
|
|
setTimeout(() => {
|
|
if (document.querySelector(".lyric-line.active")) {
|
|
document.querySelector(".lyric-line.active").scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center"
|
|
})
|
|
}
|
|
}, 200)
|
|
return "active"
|
|
} else {
|
|
return ""
|
|
}
|
|
},
|
|
getAlbumArtUrl(size = 600) {
|
|
if (this.player.currentMediaItem.artwork) {
|
|
return `url("${this.player.currentMediaItem.artwork.url.replace('{w}', size).replace('{h}', size)}")`;
|
|
} else {
|
|
return "";
|
|
}
|
|
},
|
|
getAlbumArtUrlList(url, size = 64) {
|
|
return `url("${url.replace('{w}', size).replace('{h}', size)}")`;
|
|
},
|
|
searchTabClass(tab) {
|
|
if (tab == this.search.tab) {
|
|
return "active";
|
|
}
|
|
},
|
|
searchTypeClass(type) {
|
|
if (type == this.search.searchType) {
|
|
return "active";
|
|
}
|
|
},
|
|
getQueuePositionClass(position) {
|
|
if (this.player.queue["_position"] == position) {
|
|
return ["playing", "passed"]
|
|
} else if (this.player.queue["_position"] > position) {
|
|
return ["passed"]
|
|
}
|
|
},
|
|
showSearch(reset = false) {
|
|
if(reset) {
|
|
this.search.lastPage = "search"
|
|
}
|
|
switch(this.search.lastPage) {
|
|
case "search":
|
|
this.screen = "search"
|
|
break;
|
|
case "album":
|
|
this.screen = "album-page"
|
|
break;
|
|
case "artist":
|
|
this.screen = "artist-page"
|
|
break;
|
|
case "playlist":
|
|
this.screen = "playlist-page"
|
|
break;
|
|
}
|
|
},
|
|
showArtistByName(name) {
|
|
this.musicKitAPI("search", name, {types: "artists"})
|
|
},
|
|
showAlbum(id) {
|
|
this.search.lastPage = "album"
|
|
this.screen = "album-page"
|
|
this.musicKitAPI("album", id, {})
|
|
},
|
|
showArtist(id) {
|
|
this.search.lastPage = "artist"
|
|
this.screen = "artist-page"
|
|
this.musicKitAPI("artist", id, {include: "songs,playlists,albums"})
|
|
},
|
|
showQueue() {
|
|
this.queue.temp = this.player["queue"]["_queueItems"]
|
|
this.screen = "queue"
|
|
this.getQueue()
|
|
},
|
|
queueMove(evt) {
|
|
console.log(evt)
|
|
console.log(`new: ${evt.moved.newIndex} old: ${evt.moved.oldIndex}`)
|
|
this.queue.temp.splice(evt.moved.newIndex, 0, this.queue.temp.splice(evt.moved.oldIndex, 1)[0])
|
|
socket.send(JSON.stringify({
|
|
action: "queue-move",
|
|
from: evt.moved.oldIndex,
|
|
to: evt.moved.newIndex
|
|
}))
|
|
this.getQueue()
|
|
return true
|
|
},
|
|
repeat() {
|
|
socket.send(JSON.stringify({
|
|
action: "repeat"
|
|
}))
|
|
this.getCurrentMediaItem()
|
|
},
|
|
shuffle() {
|
|
socket.send(JSON.stringify({
|
|
action: "shuffle"
|
|
}))
|
|
this.getCurrentMediaItem()
|
|
},
|
|
setShuffle(val) {
|
|
socket.send(JSON.stringify({
|
|
action: "set-shuffle",
|
|
shuffle: val
|
|
}))
|
|
this.getCurrentMediaItem()
|
|
},
|
|
getMediaPalette(data) {
|
|
var palette = {
|
|
'--bgColor': `#${data['artwork']['bgColor']}`,
|
|
'--textColor1': `#${data['artwork']['textColor1']}`,
|
|
'--textColor2': `#${data['artwork']['textColor2']}`,
|
|
'--textColor3': `#${data['artwork']['textColor3']}`,
|
|
'--textColor4': `#${data['artwork']['textColor4']}`
|
|
}
|
|
return palette
|
|
},
|
|
playAlbum(id, shuffle = false) {
|
|
if(shuffle) {
|
|
this.setShuffle(true)
|
|
}else{
|
|
this.setShuffle(false)
|
|
}
|
|
this.playMediaItemById(id, 'album');
|
|
},
|
|
getLyrics() {
|
|
socket.send(JSON.stringify({
|
|
action: "get-lyrics",
|
|
}))
|
|
},
|
|
showLyrics() {
|
|
this.getLyrics()
|
|
this.screen = "lyrics"
|
|
},
|
|
showLyricsInline() {
|
|
this.getLyrics()
|
|
this.player.lowerPanelState = "lyrics"
|
|
},
|
|
parseLyrics() {
|
|
var xml = this.stringToXml(this.player.lyricsMediaItem.ttml)
|
|
var json = xmlToJson(xml);
|
|
this.player.lyrics = json
|
|
},
|
|
stringToXml(st) {
|
|
// string to xml
|
|
var xml = (new DOMParser()).parseFromString(st, "text/xml");
|
|
return xml;
|
|
},
|
|
canShowSearchTab(tab) {
|
|
if (tab == this.search.tab || this.search.tab == "all") {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
getCurrentMediaItem() {
|
|
socket.send(JSON.stringify({
|
|
action: "get-currentmediaitem"
|
|
}))
|
|
},
|
|
setStreamerOverlay() {
|
|
document.body.classList.add("streamer-overlay")
|
|
},
|
|
setMode(mode) {
|
|
switch(mode) {
|
|
default:
|
|
this.screen = "player"
|
|
break;
|
|
case "miniplayer":
|
|
this.screen = "miniplayer"
|
|
break;
|
|
}
|
|
},
|
|
connect() {
|
|
let self = this;
|
|
this.connectedState = 0;
|
|
if (this.url === "") {
|
|
this.url = prompt("Host IP", "localhost")
|
|
}
|
|
socket = new WebSocket(`ws://${this.url}:26369`);
|
|
socket.onopen = (e) => {
|
|
console.log(e);
|
|
console.log('connected');
|
|
app.connectedState = 1;
|
|
if(getParameterByName("mode")) {
|
|
self.setMode(getParameterByName("mode"))
|
|
}else{
|
|
self.setMode("default")
|
|
}
|
|
self.clearSelectedTrack()
|
|
}
|
|
|
|
socket.onclose = (e) => {
|
|
console.log(e);
|
|
console.log('disconnected');
|
|
app.connectedState = 2;
|
|
}
|
|
|
|
socket.onerror = (e) => {
|
|
console.log(e);
|
|
console.log('error');
|
|
app.connectedState = 2;
|
|
}
|
|
|
|
socket.onmessage = (e) => {
|
|
const response = JSON.parse(e.data);
|
|
switch (response.type) {
|
|
default:
|
|
console.log(response);
|
|
break;
|
|
case "musickitapi.search":
|
|
self.showArtist(response.data["artists"][0]["id"]);
|
|
break;
|
|
case "musickitapi.album":
|
|
if(self.screen == "album-page") {
|
|
self.albumPage.data = response.data
|
|
}
|
|
break;
|
|
case "musickitapi.artist":
|
|
if(self.screen == "artist-page") {
|
|
self.artistPage.data = response.data
|
|
}
|
|
break;
|
|
case "queue":
|
|
self.player.queue = response.data;
|
|
self.queue.temp = response.data["_queueItems"];
|
|
self.$forceUpdate()
|
|
break;
|
|
case "lyrics":
|
|
self.player.lyrics = response.data;
|
|
self.$forceUpdate()
|
|
break;
|
|
case "searchResultsLibrary":
|
|
self.search.results = response.data;
|
|
self.search.state = 2;
|
|
break;
|
|
case "searchResults":
|
|
self.search.results = response.data;
|
|
self.search.state = 2;
|
|
break;
|
|
case "playbackStateUpdate":
|
|
if (!self.player.userInteraction) {
|
|
self.updatePlaybackState(response.data)
|
|
}
|
|
break;
|
|
}
|
|
// console.log(e.data);
|
|
}
|
|
},
|
|
updatePlaybackState(mediaitem) {
|
|
var lyricsDisplayed = this.screen == "lyrics" || this.player.lowerPanelState == "lyrics"
|
|
if (this.player.currentMediaItem["isrc"] != mediaitem["isrc"]) {
|
|
if (lyricsDisplayed) {
|
|
this.getLyrics()
|
|
}
|
|
if (this.screen == "queue") {
|
|
this.getQueue()
|
|
}
|
|
}
|
|
this.player.currentMediaItem = mediaitem
|
|
}
|
|
},
|
|
});
|
|
|
|
function getParameterByName(name, url) {
|
|
if (!url) url = window.location.href;
|
|
name = name.replace(/[\[\]]/g, '\\$&');
|
|
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
|
results = regex.exec(url);
|
|
if (!results) return null;
|
|
if (!results[2]) return '';
|
|
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
|
}
|
|
|
|
function xmlToJson(xml) {
|
|
|
|
// Create the return object
|
|
var obj = {};
|
|
|
|
if (xml.nodeType == 1) { // element
|
|
// do attributes
|
|
if (xml.attributes.length > 0) {
|
|
obj["@attributes"] = {};
|
|
for (var j = 0; j < xml.attributes.length; j++) {
|
|
var attribute = xml.attributes.item(j);
|
|
obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
|
|
}
|
|
}
|
|
} else if (xml.nodeType == 3) { // text
|
|
obj = xml.nodeValue;
|
|
}
|
|
|
|
// do children
|
|
if (xml.hasChildNodes()) {
|
|
for (var i = 0; i < xml.childNodes.length; i++) {
|
|
var item = xml.childNodes.item(i);
|
|
var nodeName = item.nodeName;
|
|
if (typeof (obj[nodeName]) == "undefined") {
|
|
obj[nodeName] = xmlToJson(item);
|
|
} else {
|
|
if (typeof (obj[nodeName].push) == "undefined") {
|
|
var old = obj[nodeName];
|
|
obj[nodeName] = [];
|
|
obj[nodeName].push(old);
|
|
}
|
|
obj[nodeName].push(xmlToJson(item));
|
|
}
|
|
}
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
window.onresize = function () {
|
|
app.resetPlayerUI()
|
|
}
|
|
|
|
app.connect() |