Imported changes to web-remote from AME

This commit is contained in:
booploops 2021-12-02 20:21:03 -08:00
parent 45623f257b
commit ab6c24f9be
11 changed files with 1265 additions and 595 deletions

View file

@ -48,7 +48,6 @@ function CreateWindow() {
app.on('ready', () => {
if (app.isQuiting) { app.quit(); return; }
require('vue-devtools').install()
// Apple Header tomfoolery.
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
if(details.url.match(/^https:\/\/store-\d{3}\.blobstore\.apple\.com/) || details.url.startsWith("https://store-037.blobstore.apple.com")){

View file

@ -180,7 +180,7 @@
</div>
<div class="bg-artwork"></div>
</div>
<script src="https://js-cdn.music.apple.com/musickit/v3/amp/musickit.js"></script>
<script src="https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js"></script>
<script src="index.js?v=1"></script>
</body>

View file

@ -1,4 +1,4 @@
const {app, BrowserWindow} = require("electron")
const {app, BrowserWindow, ipcMain} = require("electron")
const {join, resolve} = require("path")
const CiderWin = {
@ -34,6 +34,8 @@ const CiderWin = {
win.on("closed", () => {
win = null
})
},
SetupHandlers() {
}
}

View file

@ -8,6 +8,7 @@ const path = require('path');
const port = process.argv[2] || 9000;
const express = require('express');
const router = express.Router();
const getPort = require('get-port');
const {
ipcMain,
app,
@ -32,7 +33,7 @@ const wsapi = {
return v.toString(16);
});
},
InitWebSockets() {
async InitWebSockets () {
ipcMain.on('wsapi-updatePlaybackState', (event, arg) => {
wsapi.updatePlaybackState(arg);
})
@ -49,12 +50,20 @@ const wsapi = {
wsapi.returnSearchLibrary(JSON.parse(arg));
});
ipcMain.on('wsapi-returnDynamic', (event, arg, type) => {
wsapi.returnDynamic(JSON.parse(arg), type);
});
ipcMain.on('wsapi-returnMusicKitApi', (event, arg, method) => {
wsapi.returnMusicKitApi(JSON.parse(arg), method);
});
ipcMain.on('wsapi-returnLyrics', (event, arg) => {
wsapi.returnLyrics(JSON.parse(arg));
});
var safeport = await getPort({port : 26369});
wss = new WebSocketServer({
port: 26369,
port: safeport,
perMessageDeflate: {
zlibDeflateOptions: {
// See zlib defaults.
@ -75,10 +84,11 @@ const wsapi = {
// should not be compressed if context takeover is disabled.
}
})
console.log(`WebSocketServer started on port: ${safeport}`);
const defaultResponse = new wsapi.standardResponse(0, {}, "OK");
console.log(`WebSocketServer started on port: ${this.port}`);
wss.on('connection', function connection(ws) {
ws.id = wsapi.createId();
console.log(`Client ${ws.id} connected`)
@ -127,6 +137,13 @@ const wsapi = {
case "shuffle":
app.win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
break;
case "set-shuffle":
if(data.shuffle == true) {
app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
}else{
app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
}
break;
case "repeat":
app.win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
break;
@ -167,10 +184,9 @@ const wsapi = {
response.message = "Previous";
break;
case "musickit-api":
app.win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)})`);
break;
case "musickit-library-api":
break;
case "set-autoplay":
app.win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
@ -200,7 +216,7 @@ const wsapi = {
app.win.hide()
break;
case "play-mediaitem":
app.win.webContents.executeJavaScript(`wsapi.playTrackById(${data.id})`);
app.win.webContents.executeJavaScript(`wsapi.playTrackById(${data.id}, \`${data.kind}\`)`);
response.message = "Playing track";
break;
case "get-status":
@ -249,6 +265,18 @@ const wsapi = {
client.send(JSON.stringify(response));
});
},
returnMusicKitApi(results, method) {
const response = new wsapi.standardResponse(0, results, "OK", `musickitapi.${method}`);
wsapi.clients.forEach(function each(client) {
client.send(JSON.stringify(response));
});
},
returnDynamic(results, type) {
const response = new wsapi.standardResponse(0, results, "OK", type);
wsapi.clients.forEach(function each(client) {
client.send(JSON.stringify(response));
});
},
returnLyrics(results) {
const response = new wsapi.standardResponse(0, results, "OK", "lyrics");
wsapi.clients.forEach(function each(client) {
@ -274,7 +302,8 @@ const wsapi = {
});
},
webRemotePort: 8090,
InitWebServer() {
async InitWebServer() {
const webRemotePort = await getPort({port : wsapi.webRemotePort});
// Web Remote
// express server that will serve static files in the "../web-remote" folder
const webapp = express();
@ -283,8 +312,8 @@ const wsapi = {
webapp.get('/', function (req, res) {
res.sendFile(path.join(webRemotePath, 'index.html'));
});
webapp.listen(wsapi.webRemotePort, function () {
console.log(`Web Remote listening on port ${wsapi.webRemotePort}`);
webapp.listen(webRemotePort, function () {
console.log(`Web Remote listening on port ${webRemotePort}`);
});
}
}

View file

@ -16,6 +16,14 @@ const wsapi = {
setAutoplay(value) {
MusicKit.getInstance().autoplayEnabled = value
},
returnDynamic(data, type) {
ipcRenderer.send('wsapi-returnDynamic', JSON.stringify(data), type)
},
musickitApi(method, id, params) {
MusicKit.getInstance().api[method](id, params).then((results)=>{
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
})
},
getPlaybackState () {
ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
},
@ -38,8 +46,8 @@ const wsapi = {
love() {
},
playTrackById(id) {
MusicKit.getInstance().setQueue({ song: id }).then(function (queue) {
playTrackById(id, kind = "song") {
MusicKit.getInstance().setQueue({ [kind]: id }).then(function (queue) {
MusicKit.getInstance().play()
})
},

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 28 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-11,-10)">
<path d="M39,12.24C39,11.004 37.996,10 36.76,10L13.24,10C12.004,10 11,11.004 11,12.24L11,71.76C11,72.996 12.004,74 13.24,74L36.76,74C37.996,74 39,72.996 39,71.76L39,12.24Z" style="fill:rgb(108,108,108);fill-opacity:0.43;"/>
<g transform="matrix(0.714286,0,0,1,7.14286,0)">
<rect x="18" y="41" width="14" height="2" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
</g>
<g transform="matrix(0.714286,0,0,1,7.14286,-5)">
<rect x="18" y="41" width="14" height="2" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
</g>
<g transform="matrix(0.714286,0,0,1,7.14286,5)">
<rect x="18" y="41" width="14" height="2" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
</g>
<g transform="matrix(0.571429,0,0,0.6,10.7143,10.4)">
<path d="M25,26L32,36L18,36L25,26Z" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
</g>
<g transform="matrix(0.571429,0,0,-0.6,10.7143,73.6)">
<path d="M25,26L32,36L18,36L25,26Z" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" fill="white"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M471.1 96C405 96 353.3 137.3 320 174.6 286.7 137.3 235 96 168.9 96 75.8 96 0 167.8 0 256s75.8 160 168.9 160c66.1 0 117.8-41.3 151.1-78.6 33.3 37.3 85 78.6 151.1 78.6 93.1 0 168.9-71.8 168.9-160S564.2 96 471.1 96zM168.9 320c-40.2 0-72.9-28.7-72.9-64s32.7-64 72.9-64c38.2 0 73.4 36.1 94 64-20.4 27.6-55.9 64-94 64zm302.2 0c-38.2 0-73.4-36.1-94-64 20.4-27.6 55.9-64 94-64 40.2 0 72.9 28.7 72.9 64s-32.7 64-72.9 64z"/></svg>

After

Width:  |  Height:  |  Size: 684 B

View file

@ -15,7 +15,39 @@
<body oncontextmenu="return false;">
<div id="app" :style="{'--artwork': getAlbumArtUrl()}">
<!-- App view when connected -->
<template v-if="connectedState == 1">
<!-- Streamer Overlay -->
<template></template>
<!-- Mini Player -->
<template v-if="screen == 'miniplayer'">
<div class="miniplayer-main">
<div class="media-artwork--miniplayer" :class="artworkPlaying()"
:style="{'--artwork': getAlbumArtUrl()}">
</div>
<div class="miniplayer-draggable">
</div>
<div class="miniplayer-controls">
<button class="md-btn playback-button--small repeat" @click="repeat()"
v-if="player.currentMediaItem.repeatMode == 0"></button>
<button class="md-btn playback-button--small repeat active" @click="repeat()"
v-else-if="player.currentMediaItem.repeatMode == 2"></button>
<button class="md-btn playback-button--small repeat repeatOne" @click="repeat()"
v-else-if="player.currentMediaItem.repeatMode == 1"></button>
<button class="md-btn playback-button previous" @click="previous()"></button>
<button class="md-btn playback-button pause" @click="pause()"
v-if="player.currentMediaItem.status"></button>
<button class="md-btn playback-button play" @click="play()" v-else></button>
<button class="md-btn playback-button next" @click="next()"></button>
<button class="md-btn playback-button--small shuffle" @click="shuffle()"
v-if="player.currentMediaItem.shuffleMode == 0"></button>
<button class="md-btn playback-button--small shuffle active" @click="shuffle()"
v-else></button>
</div>
</div>
</template>
<!-- Player -->
<transition name="wpfade">
<div class="md-container md-container_panel player-panel" v-if="screen == 'player'">
<div class="player_top">
@ -76,11 +108,11 @@
<div class="player_bottom" v-if="player.lowerPanelState == 'controls'">
<div class="md-footer">
<div class="row player-track-info">
<div class="col nopadding">
<div class="player-song-title">
<div class="col nopadding text-overflow-elipsis">
<div class="player-song-title text-overflow-elipsis">
{{ player.currentMediaItem.name }}
</div>
<div class="player-song-artist" @click="searchArtist()">
<div class="player-song-artist text-overflow-elipsis" @click="searchArtist()">
{{ player.currentMediaItem.artistName }}
</div>
</div>
@ -143,11 +175,12 @@
<button class="md-btn playback-button--small lyrics"
v-if="checkOrientation() == 'landscape'" @click="showLyricsInline()"></button>
<button class="md-btn playback-button--small queue" @click="showQueue()"></button>
<button class="md-btn playback-button--small search" @click="screen = 'search'"></button>
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
</div>
</div>
</div>
</transition>
<!-- Search -->
<transition name="wpfade">
<div class="md-container md-container_panel search-panel" v-if="screen == 'search'">
<div class="search-header">
@ -204,7 +237,10 @@
</div>
</transition>
<transition name="wpfade">
<div class="md-body search-body" style="overflow-y:auto;" v-if="search.state == 2">
<div class="md-body search-body"
ref="searchBody"
@scroll="searchScroll"
style="overflow-y:auto;" v-if="search.state == 2">
<template v-if="canShowSearchTab('songs')">
<div class="list-entry-header">Songs</div>
@ -222,6 +258,7 @@
</div>
<div class="list-entry-artist">
{{ song.artistName }}
<span class="lossless-badge" v-if="song.audioTraits.includes('lossless')">Lossless</span>
</div>
</div>
</div>
@ -250,7 +287,8 @@
<template v-if="canShowSearchTab('albums')">
<div class="list-entry-header">Albums</div>
<div class="list-entry" v-for="album in search.results.albums">
<div class="list-entry" v-for="album in search.results.albums"
@click="showAlbum(album.id)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="album.artwork"
@ -263,6 +301,7 @@
</div>
<div class="list-entry-artist">
{{ album.artistName }}
<span class="lossless-badge" v-if="album.audioTraits.includes('lossless')">Lossless</span>
</div>
</div>
</div>
@ -290,7 +329,10 @@
<template v-if="canShowSearchTab('artists')">
<div class="list-entry-header">Artists</div>
<div class="list-entry" v-for="artist in search.results.artists">
<div class="list-entry"
@click="showArtist(artist.id)"
v-for="artist in search.results.artists"
>
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image artist" v-if="artist.artwork"
@ -330,8 +372,10 @@
</div>
</transition>
</div>
<footer-player></footer-player>
</div>
</transition>
<!-- Track Select Actions -->
<transition name="wpfade">
<div class="md-container md-container_panel context-menu" style="overflow-y:auto;"
v-if="search.trackSelect">
@ -409,6 +453,7 @@
</div>
</div>
</transition>
<!-- Song Actions -->
<transition name="wpfade">
<div class="md-container md-container_panel context-menu" v-if="player.songActions">
<div class="md-header">
@ -461,31 +506,84 @@
</div>
</div>
</transition>
<!-- Artist Page -->
<transition name="wpfade">
<div class="md-container md-container_panel" v-if="screen == 'queue'">
<div class="md-container md-container_panel" v-if="screen == 'artist-page'" v-if="artistPage.data['name']">
<div class="md-header">
<div class="row">
<div class="col-auto">
<button class="back-button" @click="screen = 'player'"></button>
<button class="back-button" @click="showSearch(true)"></button>
</div>
<div class="col" style="display: flex;align-items: center;">
<div class="col">
Queue
<div class="col flex-center">
{{ artistPage.data["name"] }}
</div>
</div>
<div class="col-auto">
</div>
<div class="album-body-container" :style="getMediaPalette(artistPage.data)">
<div class="artist-header" v-if="artistPage.data['artwork']"
:style="getMediaPalette(artistPage.data)">
<div class="artist-header-portrait"
:style="{'--artwork': getAlbumArtUrlList(artistPage.data['artwork']['url'], 600)}"></div>
<h2>{{ artistPage.data["name"] }}</h2>
</div>
<div class="md-body artist-body">
<h2>Songs</h2>
<div class="song-scroller-horizontal">
<button v-for="song in artistPage.data['songs']" class="song-placeholder"
@click="trackSelect(song)">
{{ song.name }}
</button>
</div>
<h2>Albums</h2>
<div class="mediaitem-scroller-horizontal">
<button v-for="album in artistPage.data['albums']" class="album-placeholder"
@click="showAlbum(album.id)">
{{ album.name }}
</button>
</div>
<h2>Playlists</h2>
<div class="mediaitem-scroller-horizontal">
<button v-for="playlist in artistPage.data['playlists']" class="album-placeholder">
{{ playlist.name }}
</button>
</div>
</div>
</div>
<footer-player></footer-player>
</div>
</transition>
<!-- Queue -->
<transition name="wpfade">
<div class="md-container md-container_panel" v-if="screen == 'queue'">
<div class="md-header">
<div class="list-entry" @click="screen = 'player'">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" :style="{'--artwork': getAlbumArtUrl()}">
</div>
</div>
<div class="col flex-center">
<div class="list-entry-name">
{{ player.currentMediaItem.name }}
</div>
<div class="list-entry-artist">
{{ player.currentMediaItem.artistName }}
</div>
</div>
</div>
</div>
</div>
<div class="md-header" style="text-align: right;padding: 5px 16px;">
<button
class="md-btn playback-button--small"
class="md-btn playback-button--small autoplay"
v-if="!player.currentMediaItem.autoplayEnabled"
@click="setAutoplay(true)"
>♾️</button>
></button>
<button
class="md-btn playback-button--small active"
class="md-btn playback-button--small autoplay activeColor"
v-else
@click="setAutoplay(false)"
>♾️</button>
</div>
</div>
></button>
</div>
<div class="md-body queue-body" v-if="!player.queue['_queueItems']">
Empty
@ -496,14 +594,14 @@
handle=".handle"
filter=".passed"
@change="queueMove">
<template v-for="(song, position) in queue.temp">
<template
v-for="(song, position) in queue.temp"
v-if="position > player.queue['_position']"
>
<div class="list-entry" :class="getQueuePositionClass(position)">
<div class="row" style="width:100%;">
<div class="col-auto">
<div class="handle">
⬆️
<br>
⬇️
</div>
</div>
<div class="col-auto flex-center">
@ -528,14 +626,21 @@
</draggable>
</div>
<div class="md-footer">
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'"
@click="showLyrics()"></button>
<button class="md-btn playback-button--small lyrics"
v-if="checkOrientation() == 'landscape'"
@click="screen = 'player';showLyricsInline()"></button>
<button class="md-btn playback-button--small queue active" @click="screen = 'player'"></button>
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
</div>
</div>
</transition>
<!-- Lyrics -->
<transition name="wpfade">
<div class="md-container md-container_panel" v-if="screen == 'lyrics'">
<div class="md-header">
<div class="list-entry">
<div class="list-entry" @click="screen = 'player'">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" :style="{'--artwork': getAlbumArtUrl()}">
@ -578,11 +683,114 @@
<div class="md-footer">
<button class="md-btn playback-button--small lyrics active" @click="screen = 'player'"></button>
<button class="md-btn playback-button--small queue" @click="showQueue()"></button>
<button class="md-btn playback-button--small search" @click="screen = 'search'"></button>
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
</div>
</div>
</transition>
<!-- Album Page -->
<transition name="wpfade">
<div class="md-container md-container_panel md-container_album"
v-if="screen == 'album-page' && albumPage.data['name']"
>
<div class="md-header">
<div class="row">
<div class="col-auto">
<button class="back-button" @click="showSearch(true)"></button>
</div>
</div>
</div>
<div class="album-body-container">
<div class="md-header">
<div class="albumpage-artwork"
:style="{'--artwork': getAlbumArtUrlList(albumPage.data['artwork']['url'], 300)}">
</div>
<div class="albumpage-album-name">
{{ albumPage.data["name"] }}
</div>
<div class="albumpage-artist-name" @click="showArtist(albumPage.data['artists'][0]['id'])">
{{ albumPage.data["artistName"] }}
</div>
<div class="albumpage-misc-info">
{{ albumPage.data.genreNames[0] }} ∙ {{ new Date(albumPage.data.releaseDate).getFullYear()
}}
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<button class="wr-btn"
@click="playAlbum(albumPage.data.id, false)"
style="width:100%;">Play
</button>
</div>
<div class="col">
<button class="wr-btn" style="width:100%;"
@click="playAlbum(albumPage.data.id, true)"
>Shuffle
</button>
</div>
</div>
<div class="albumpage-album-notes" v-if="albumPage.data['editorialNotes']">
<div class="notes-preview" v-html="albumPage.data['editorialNotes']['standard']">
</div>
<button @click="albumPage.editorsNotes = true" class="notes-more">More</button>
</div>
</div>
<div class="md-body artist-body">
<div class="list-entry-header">Tracks</div>
<div class="list-entry" v-for="song in albumPage.data['tracks']"
@click="trackSelect(song)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="song.artwork"
:style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
</div>
</div>
<div class="col flex-center">
<div class="list-entry-name">
{{ song.name }}
</div>
<div class="list-entry-artist">
{{ song.artistName }}
</div>
</div>
</div>
</div>
<div class="md-footer">
<div>{{ albumPage.data['tracks'].length }} Tracks</div>
<div>
{{ albumPage.data['copyright'] }}
</div>
</div>
</div>
</div>
<footer-player></footer-player>
</div>
</transition>
<!-- Album Page - Editorial Notes -->
<transition name="wpfade">
<div class="md-container md-container_panel context-menu" v-if="albumPage.editorsNotes"
style="padding-top: 42px;">
<div class="md-header"
:style="getMediaPalette(albumPage.data)"
style="font-size: 18px;background:var(--bgColor);color:var(--textColor1);text-align: center;border-radius: 10px 10px 0 0;border-top: 1px solid #ffffff1f;"
>
{{ albumPage.data["name"] }}
</div>
<div class="md-body album-page-fullnotes-body"
:style="getMediaPalette(albumPage.data)"
style="background:var(--bgColor);color:var(--textColor1);"
v-html="albumPage.data['editorialNotes']['standard']">
</div>
<div class="md-footer"
:style="getMediaPalette(albumPage.data)"
style="background:var(--bgColor);color:var(--textColor1);"
>
<button class="context-menu-item" @click="albumPage.editorsNotes = false">Close</button>
</div>
</div>
</transition>
</template>
<!-- Loading -->
<transition name="wpfade">
<div class="md-container md-container_panel connection-error-panel" v-if="connectedState != 1">
<div class="md-header">
@ -605,6 +813,7 @@
</div>
</div>
</transition>
<!-- Template -->
<transition name="wpfade">
<div class="md-container md-container_panel" v-if="false">
<div class="md-header">
@ -619,6 +828,31 @@
</div>
</transition>
</div>
<script type="text/x-template" id="footer-player">
<div class="footer-player" v-show="$parent.player.currentMediaItem['name']">
<div class="row" style="width:100%;margin:0px;">
<div class="col-auto flex-center" style="padding:0 6px;" @click="$parent.screen = 'player'">
<div class="list-entry-image" :style="{'--artwork': $parent.getAlbumArtUrl()}">
</div>
</div>
<div class="col flex-center text-overflow-elipsis" @click="$parent.screen = 'player'">
<div class="list-entry-name text-overflow-elipsis">
{{ $parent.player.currentMediaItem.name }}
</div>
<div class="list-entry-artist text-overflow-elipsis">
{{ $parent.player.currentMediaItem.artistName }}
</div>
</div>
<div class="col-auto">
<button class="md-btn playback-button pause" @click="$parent.pause()"
v-if="$parent.player.currentMediaItem.status"></button>
<button class="md-btn playback-button play" @click="$parent.play()" v-else></button>
</div>
</div>
</div>
</script>
<script src="index.js?v=1"></script>
</body>

View file

@ -1,5 +1,9 @@
var socket;
Vue.component('footer-player', {
template: '#footer-player'
});
// vue instance
var app = new Vue({
el: '#app',
@ -22,6 +26,14 @@ var app = new Vue({
queue: {
temp: []
},
artistPage: {
data: {},
editorsNotes: false
},
albumPage: {
data: {},
editorsNotes: false
},
search: {
query: "",
results: [],
@ -31,12 +43,29 @@ var app = new Vue({
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";
},
@ -171,10 +200,11 @@ var app = new Vue({
getArtworkColor(hex) {
return `#${hex}`
},
playMediaItemById(id) {
playMediaItemById(id, kind = "song") {
socket.send(JSON.stringify({
action: "play-mediaitem",
id: id
id: id,
kind: kind
}))
this.screen = "player";
},
@ -309,6 +339,38 @@ var app = new Vue({
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"
@ -338,6 +400,31 @@ var app = new Vue({
}))
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",
@ -373,6 +460,19 @@ var app = new Vue({
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;
@ -384,7 +484,11 @@ var app = new Vue({
console.log(e);
console.log('connected');
app.connectedState = 1;
self.screen = "player"
if(getParameterByName("mode")) {
self.setMode(getParameterByName("mode"))
}else{
self.setMode("default")
}
self.clearSelectedTrack()
}
@ -404,20 +508,25 @@ var app = new Vue({
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()
if (self.screen == "queue") {
setTimeout(() => {
document.querySelector(".playing").scrollIntoView({
behavior: "smooth",
block: "start"
})
}, 200)
}
break;
case "lyrics":
self.player.lyrics = response.data;
@ -455,6 +564,16 @@ var app = new Vue({
},
});
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

View file

@ -1,20 +0,0 @@
{
"name": "AME",
"short_name": "AME",
"description": "AME",
"icons": [
{
"src": "images/icon.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/icon.png",
"sizes": "512x512",
"type": "image/png"
}
],
"display": "fullscreen",
"start_url": "/web-remote/index.html",
"orientation": "portrait"
}

View file

@ -2,6 +2,7 @@
:root {
--appleEase: cubic-bezier(0.42, 0, 0.58, 1);
--mediaItemShadow: inset 0px 0px 0px 1px rgb(200 200 200 / 16%), 0 8px 40px rgb(0 0 0 / 0.55);
--keyColor: #fa586a;
--keyColor-rgb: 250, 88, 106;
--keyColor-rollover: #ff8a9c;
@ -85,6 +86,18 @@ body {
filter: blur(32px) saturate(180%);
}
body.streamer-overlay {
background: transparent;
}
body.streamer-overlay #app {
background: transparent;
}
body.streamer-overlay #app:before {
display: none;
}
body {
background: #111;
font-family: "Segoe UI Variable Display", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
@ -124,6 +137,24 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
font-family: inherit;
}
.wr-btn {
font-family: inherit;
appearance: none;
border:0px;
border-radius: 6px;
padding: 8px;
font-weight: 600;
background: rgb(80 80 80 / 70%);
color: white;
}
.footer-player {
padding: 6px;
display:flex;
border-top: 1px solid rgb(200 200 200 / 15%);
background: rgb(200 200 200 / 10%);
}
.player-duration-time {
opacity: 0.5;
}
@ -151,7 +182,7 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
background-position: center;
background-repeat: no-repeat;
border-radius: 8px;
box-shadow: inset 0px 0px 0px 1px rgb(200 200 200 / 16%), 0 8px 40px rgb(0 0 0 / 0.55);
box-shadow: var(--mediaItemShadow);
transition: transform .10s var(--appleEase);
}
@ -160,6 +191,14 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
transform: scale(0.85);
}
.lossless-badge {
background: rgb(150 150 150);
border-radius: 6px;
padding: 0px 6px;
color: rgb(30 30 30);
font-weight: 600;
}
.playback-slider {
width: 90%;
}
@ -218,6 +257,10 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
background-color: rgb(200 200 200 / 10%);
}
.playback-button--small.activeColor {
background-color: var(--keyColor);
}
.playback-button--small.search {
background-image: url("./assets/search.svg");
}
@ -230,6 +273,10 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
background-image: url("./assets/quote-right.svg");
}
.playback-button--small.autoplay {
background-image: url("./assets/infinity.svg");
}
.playback-button--small.shuffle {
background-image: url("./assets/shuffle.svg");
}
@ -392,8 +439,8 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 8px;
box-shadow: inset 0px 0px 0px 1px rgb(200 200 200 / 16%), 0 8px 40px rgb(0 0 0 / 0.55);
border-radius: 4px;
box-shadow: var(--mediaItemShadow);
}
.list-entry-image.artist {
@ -424,10 +471,48 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
.list-entry .handle {
height: 100%;
width: 28px;
background:var(--keyColor);
display: flex;
justify-content: center;
align-items: center;
background-size: contain;
background-repeat: no-repeat;
background-image: url('./assets/Grabber.svg');
cursor: grab;
}
.list-entry .handle:active {
cursor: grabbing;
}
.song-scroller-horizontal {
display:flex;
overflow-y: scroll;
overflow-x:hidden;
overflow-y: overlay;
width: 100%;
height: 200px;
/*! flex-flow: row; */
flex-direction: row;
flex-wrap: wrap;
}
.song-placeholder {
height: 60px;
width: 50%;
flex: 0 0 auto;
}
.mediaitem-scroller-horizontal {
display:flex;
overflow-y: hidden;
overflow-x:scroll;
overflow-x: overlay;
width: 100%;
}
.album-placeholder {
height: 180px;
width: 180px;
flex: 0 0 auto;
}
.md-container {
@ -485,6 +570,8 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
position: relative;
width: 100%;
height: 100%;
overflow-y: scroll;
overflow-y: overlay;
}
.queue-body {
@ -492,6 +579,130 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
height: 100%;
}
.text-overflow-elipsis {
min-width: 0px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.album-body-container {
position: relative;
width: 100%;
height: 100%;
overflow-y: scroll;
overflow-y: overlay;
overflow-x: hidden;
}
.albumpage-artwork {
--artwork: url("");
width: 30vh;
height: 30vh;
margin: 0 auto;
border-radius: 6px;
background:black;
margin-bottom: 18px;
box-shadow: var(--mediaItemShadow);
background-image: var(--artwork);
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.albumpage-misc-info {
text-align: center;
font-size: 13px;
font-weight: 600;
opacity: 0.50;
}
.albumpage-album-notes {
margin: 0 auto;
margin-top: 16px;
height: 60px;
overflow: hidden;
position: relative;
max-width: 300px;
}
.albumpage-album-notes>.notes-preview {
height: 60px;
overflow: hidden;
-webkit-mask-image: -webkit-gradient(linear, left 95%, left bottom, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0)));
}
.albumpage-album-notes>.notes-more {
position: absolute;
bottom:0px;
right:0px;
appearance: none;
background: transparent;
color: var(--keyColor);
font-weight: bold;
font-family: inherit;
text-transform: uppercase;
height: 100%;
width: 100%;
padding: 0px;
border:0px;
display:flex;
justify-content: flex-end;
align-items: flex-end;
}
.album-page-fullnotes-body {
padding: 22px;
font-size: 17px;
overflow-y: scroll;
overflow-y: overlay;
}
.albumpage-album-name {
margin: 0 auto;
font-size: 17px;
width: 100%;
text-align: center;
font-weight: 600;
}
.albumpage-artist-name {
margin: 0 auto;
width: 100%;
text-align: center;
font-size: 18px;
color: var(--keyColor);
}
.albumpage-artist-name:hover {
text-decoration: underline;
cursor: pointer;
}
.artist-header {
height: 400px;
width: 100%;
margin: 12px auto;
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
flex-direction: column;
background: rgb(0 0 0 / 40%);
}
.artist-header .artist-header-portrait {
height: 200px;
width: 200px;
background: var(--artwork);
background-size: contain;
background-repeat: no-repeat;
border-radius: 100%;
box-shadow: var(--mediaItemShadow);
}
.search-body {
position: absolute;
width: 100%;
@ -499,6 +710,12 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
padding-top: 220px;
}
.artist-body {
position: absolute;
width: 100%;
height: 100%;
}
.search-tab {
background: rgb(20 20 20 / 0.85);
border-radius: 50px;
@ -691,6 +908,48 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
height: 100%;
}
.miniplayer-main {
width: 100%;
height: 100%;
position: relative;
}
.miniplayer-main .miniplayer-controls {
position: absolute;
bottom: 0px;
left: 0px;
background:rgb(0 0 0 / 50%);
height: 80px;
z-index: 2;
width:100%;
display:flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(16px) saturate(180%);
}
.miniplayer-main .miniplayer-draggable {
position: absolute;
top: 0;
left: 0;
height: calc(100% - 80px);
width: 100%;
}
.miniplayer-main .miniplayer-controls .md-btn {
width: 40px;
background-size: 16px;
}
.media-artwork--miniplayer {
width: 100%;
height: 100%;
background: var(--artwork);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
/* Small Screen */
@media only screen and (max-height: 668px) {
#app {
@ -706,6 +965,23 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
max-width: 100%;
}
.album-body-container {
display: flex;
flex-wrap: wrap;
}
.album-body-container>.md-header {
min-width: 300px;
flex: 0 0 auto;
/*! max-width: 300px; */
margin: 0 auto;
}
.artist-body {
position: relative;
/*! flex: 0 0 auto; */
}
.player-panel {
display: flex;
flex-direction: row;