Merge branch 'upcoming' of https://github.com/CiderApp/Cider into upcoming
This commit is contained in:
commit
a51dc79a94
23 changed files with 1835 additions and 1078 deletions
14
package.json
14
package.json
|
@ -6,7 +6,7 @@
|
||||||
"description": "A new look into listening and enjoying music in style and performance.",
|
"description": "A new look into listening and enjoying music in style and performance.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
"author": "Cider Collective <cryptofyre@cryptofyre.org> (https://cider.sh)",
|
"author": "Cider Collective <cryptofyre@cider.sh> (https://cider.sh)",
|
||||||
"repository": "https://github.com/ciderapp/Cider.git",
|
"repository": "https://github.com/ciderapp/Cider.git",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
|
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
|
||||||
|
@ -16,7 +16,10 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"watch": "tsc --watch",
|
"watch": "tsc --watch",
|
||||||
"start": "npm run build && ELECTRON_ENABLE_LOGGING=true electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
"start": "run-script-os",
|
||||||
|
"start:win32": "npm run build && set ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
|
"start:linux": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
|
"start:darwin": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
"dist": "npm run build && electron-builder",
|
"dist": "npm run build && electron-builder",
|
||||||
"msft": "electron-builder -c msft-package.json",
|
"msft": "electron-builder -c msft-package.json",
|
||||||
|
@ -39,9 +42,10 @@
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"v8-compile-cache": "^2.3.0",
|
"v8-compile-cache": "^2.3.0",
|
||||||
"ws": "^8.4.0",
|
"ws": "^8.4.2",
|
||||||
"xml2js": "^0.4.23",
|
"xml2js": "^0.4.23",
|
||||||
"youtube-search-without-api-key": "^1.0.7"
|
"youtube-search-without-api-key": "^1.0.7"
|
||||||
},
|
},
|
||||||
|
@ -78,9 +82,9 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"build": {
|
"build": {
|
||||||
"electronVersion": "16.0.6",
|
"electronVersion": "16.0.7",
|
||||||
"electronDownload": {
|
"electronDownload": {
|
||||||
"version": "16.0.6+wvcus",
|
"version": "16.0.7+wvcus",
|
||||||
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
||||||
},
|
},
|
||||||
"appId": "cider",
|
"appId": "cider",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @ts-nocheck
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
import * as windowStateKeeper from "electron-window-state";
|
import * as windowStateKeeper from "electron-window-state";
|
||||||
|
@ -5,7 +6,10 @@ import * as express from "express";
|
||||||
import * as getPort from "get-port";
|
import * as getPort from "get-port";
|
||||||
import * as yt from "youtube-search-without-api-key";
|
import * as yt from "youtube-search-without-api-key";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import {Stream} from "stream";
|
import { Stream } from "stream";
|
||||||
|
import * as qrcode from "qrcode-terminal";
|
||||||
|
import * as os from "os";
|
||||||
|
import {wsapi} from "./wsapi";
|
||||||
|
|
||||||
export class Win {
|
export class Win {
|
||||||
win: any | undefined = null;
|
win: any | undefined = null;
|
||||||
|
@ -24,17 +28,21 @@ export class Win {
|
||||||
ciderCache: path.resolve(electron.app.getPath("userData"), "CiderCache"),
|
ciderCache: path.resolve(electron.app.getPath("userData"), "CiderCache"),
|
||||||
themes: path.resolve(electron.app.getPath("userData"), "Themes"),
|
themes: path.resolve(electron.app.getPath("userData"), "Themes"),
|
||||||
plugins: path.resolve(electron.app.getPath("userData"), "Plugins"),
|
plugins: path.resolve(electron.app.getPath("userData"), "Plugins"),
|
||||||
}
|
};
|
||||||
private audioStream: any = new Stream.PassThrough();
|
private audioStream: any = new Stream.PassThrough();
|
||||||
private clientPort: number = 0;
|
private clientPort: number = 0;
|
||||||
|
private remotePort: number = 6942;
|
||||||
private EnvironmentVariables: object = {
|
private EnvironmentVariables: object = {
|
||||||
"env": {
|
env: {
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
dev: electron.app.isPackaged
|
dev: electron.app.isPackaged,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
private options: any = {
|
private options: any = {
|
||||||
icon: path.join(this.paths.resourcePath, `icons/icon.` + (process.platform === "win32" ? "ico" : "png")),
|
icon: path.join(
|
||||||
|
this.paths.resourcePath,
|
||||||
|
`icons/icon.` + (process.platform === "win32" ? "ico" : "png")
|
||||||
|
),
|
||||||
width: 1024,
|
width: 1024,
|
||||||
height: 600,
|
height: 600,
|
||||||
x: undefined,
|
x: undefined,
|
||||||
|
@ -43,8 +51,8 @@ export class Win {
|
||||||
minHeight: 410,
|
minHeight: 410,
|
||||||
frame: false,
|
frame: false,
|
||||||
title: "Cider",
|
title: "Cider",
|
||||||
vibrancy: 'dark',
|
vibrancy: "dark",
|
||||||
transparent: (process.platform === "darwin"),
|
transparent: process.platform === "darwin",
|
||||||
hasShadow: false,
|
hasShadow: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
|
@ -57,27 +65,29 @@ export class Win {
|
||||||
nodeIntegrationInWorker: false,
|
nodeIntegrationInWorker: false,
|
||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
|
|
||||||
preload: path.join(this.paths.srcPath, './preload/cider-preload.js')
|
preload: path.join(this.paths.srcPath, "./preload/cider-preload.js"),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the browser window
|
* Creates the browser window
|
||||||
*/
|
*/
|
||||||
async createWindow(): Promise<void> {
|
async createWindow(): Promise<void> {
|
||||||
this.clientPort = await getPort({port: 9000});
|
this.clientPort = await getPort({ port: 9000 });
|
||||||
this.verifyFiles();
|
this.verifyFiles();
|
||||||
|
|
||||||
// Load the previous state with fallback to defaults
|
// Load the previous state with fallback to defaults
|
||||||
const windowState = windowStateKeeper({
|
const windowState = windowStateKeeper({
|
||||||
defaultWidth: 1024,
|
defaultWidth: 1024,
|
||||||
defaultHeight: 600
|
defaultHeight: 600,
|
||||||
});
|
});
|
||||||
this.options.width = windowState.width;
|
this.options.width = windowState.width;
|
||||||
this.options.height = windowState.height;
|
this.options.height = windowState.height;
|
||||||
|
|
||||||
// Start the webserver for the browser window to load
|
// Start the webserver for the browser window to load
|
||||||
this.startWebServer()
|
const ws = new wsapi()
|
||||||
|
ws.InitWebSockets()
|
||||||
|
this.startWebServer();
|
||||||
|
|
||||||
this.win = new electron.BrowserWindow(this.options);
|
this.win = new electron.BrowserWindow(this.options);
|
||||||
|
|
||||||
|
@ -88,7 +98,6 @@ export class Win {
|
||||||
// Register listeners on Window to track size and position of the Window.
|
// Register listeners on Window to track size and position of the Window.
|
||||||
windowState.manage(this.win);
|
windowState.manage(this.win);
|
||||||
|
|
||||||
|
|
||||||
return this.win;
|
return this.win;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,25 +105,29 @@ export class Win {
|
||||||
* Verifies the files for the renderer to use (Cache, library info, etc.)
|
* Verifies the files for the renderer to use (Cache, library info, etc.)
|
||||||
*/
|
*/
|
||||||
private verifyFiles(): void {
|
private verifyFiles(): void {
|
||||||
const expectedDirectories = [
|
const expectedDirectories = ["CiderCache"];
|
||||||
"CiderCache"
|
|
||||||
]
|
|
||||||
const expectedFiles = [
|
const expectedFiles = [
|
||||||
"library-songs.json",
|
"library-songs.json",
|
||||||
"library-artists.json",
|
"library-artists.json",
|
||||||
"library-albums.json",
|
"library-albums.json",
|
||||||
"library-playlists.json",
|
"library-playlists.json",
|
||||||
"library-recentlyAdded.json",
|
"library-recentlyAdded.json",
|
||||||
]
|
];
|
||||||
for (let i = 0; i < expectedDirectories.length; i++) {
|
for (let i = 0; i < expectedDirectories.length; i++) {
|
||||||
if (!fs.existsSync(path.join(electron.app.getPath("userData"), expectedDirectories[i]))) {
|
if (
|
||||||
fs.mkdirSync(path.join(electron.app.getPath("userData"), expectedDirectories[i]))
|
!fs.existsSync(
|
||||||
|
path.join(electron.app.getPath("userData"), expectedDirectories[i])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fs.mkdirSync(
|
||||||
|
path.join(electron.app.getPath("userData"), expectedDirectories[i])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < expectedFiles.length; i++) {
|
for (let i = 0; i < expectedFiles.length; i++) {
|
||||||
const file = path.join(this.paths.ciderCache, expectedFiles[i])
|
const file = path.join(this.paths.ciderCache, expectedFiles[i]);
|
||||||
if (!fs.existsSync(file)) {
|
if (!fs.existsSync(file)) {
|
||||||
fs.writeFileSync(file, JSON.stringify([]))
|
fs.writeFileSync(file, JSON.stringify([]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,22 +138,30 @@ export class Win {
|
||||||
private startWebServer(): void {
|
private startWebServer(): void {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(express.static(path.join(this.paths.srcPath, './renderer/')));
|
app.use(express.static(path.join(this.paths.srcPath, "./renderer/")));
|
||||||
app.set("views", path.join(this.paths.srcPath, './renderer/views'));
|
app.set("views", path.join(this.paths.srcPath, "./renderer/views"));
|
||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
|
let firstRequest = true;
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && (this.devMode || req.headers["user-agent"].includes("Electron")))) {
|
if (
|
||||||
|
req.url.includes("audio.webm") ||
|
||||||
|
(req.headers.host.includes("localhost") &&
|
||||||
|
(this.devMode || req.headers["user-agent"].includes("Electron")))
|
||||||
|
) {
|
||||||
next();
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect("https://discord.gg/applemusic");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get("/", (req, res) => {
|
||||||
res.render("main", this.EnvironmentVariables)
|
|
||||||
|
|
||||||
|
res.render("main", this.EnvironmentVariables);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/audio.webm', (req, res) => {
|
app.get("/audio.webm", (req, res) => {
|
||||||
try {
|
try {
|
||||||
req.socket.setTimeout(Number.MAX_SAFE_INTEGER);
|
req.socket.setTimeout(Number.MAX_SAFE_INTEGER);
|
||||||
// CiderBase.requests.push({req: req, res: res});
|
// CiderBase.requests.push({req: req, res: res});
|
||||||
|
@ -150,21 +171,45 @@ export class Win {
|
||||||
// requests.splice(pos, 1);
|
// requests.splice(pos, 1);
|
||||||
// console.info("CLOSED", CiderBase.requests.length);
|
// console.info("CLOSED", CiderBase.requests.length);
|
||||||
// });
|
// });
|
||||||
this.audioStream.on('data', (data: any) => {
|
this.audioStream.on("data", (data: any) => {
|
||||||
try {
|
try {
|
||||||
res.write(data);
|
res.write(data);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.log(ex)
|
console.log(ex);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.log(ex)
|
console.log(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
//app.use(express.static())
|
||||||
|
|
||||||
app.listen(this.clientPort, () => {
|
app.listen(this.clientPort, () => {
|
||||||
console.log(`Cider client port: ${this.clientPort}`);
|
console.log(`Cider client port: ${this.clientPort}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remote Client (I had no idea how to add it to our existing express server, so I just made another one) -@quacksire
|
||||||
|
* TODO: Broadcast the remote so that /web-remote/ can connect
|
||||||
|
* https://github.com/ciderapp/Apple-Music-Electron/blob/818ed18940ff600d76eb59d22016723a75885cd5/resources/functions/handler.js#L1173
|
||||||
|
*/
|
||||||
|
const remote = express();
|
||||||
|
remote.use(express.static(path.join(this.paths.srcPath, "./web-remote/")))
|
||||||
|
remote.listen(this.remotePort, () => {
|
||||||
|
console.log(`Cider remote port: ${this.remotePort}`);
|
||||||
|
if (firstRequest) {
|
||||||
|
console.log("---- Ignore Me ;) ---");
|
||||||
|
qrcode.generate(`http://${os.hostname}:${this.remotePort}`);
|
||||||
|
console.log("---- Ignore Me ;) ---");
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* USING https://www.npmjs.com/package/qrcode-terminal for terminal
|
||||||
|
* WE SHOULD USE https://www.npmjs.com/package/qrcode for the remote (or others) for showing to user via an in-app dialog
|
||||||
|
* -@quacksire
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
firstRequest = false;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,181 +219,234 @@ export class Win {
|
||||||
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
||||||
this.win.webContents.session.webRequest.onBeforeRequest(
|
this.win.webContents.session.webRequest.onBeforeRequest(
|
||||||
{
|
{
|
||||||
urls: ["https://*/*.js"]
|
urls: ["https://*/*.js"],
|
||||||
},
|
},
|
||||||
(details: { url: string | string[]; }, callback: (arg0: { redirectURL?: string; cancel?: boolean; }) => void) => {
|
(
|
||||||
|
details: { url: string | string[] },
|
||||||
|
callback: (arg0: { redirectURL?: string; cancel?: boolean }) => void
|
||||||
|
) => {
|
||||||
if (details.url.includes("hls.js")) {
|
if (details.url.includes("hls.js")) {
|
||||||
callback({
|
callback({
|
||||||
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`
|
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`,
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
callback({
|
callback({
|
||||||
cancel: false
|
cancel: false,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
this.win.webContents.session.webRequest.onBeforeSendHeaders(async (details: { url: string; requestHeaders: { [x: string]: string; }; }, callback: (arg0: { requestHeaders: any; }) => void) => {
|
this.win.webContents.session.webRequest.onBeforeSendHeaders(
|
||||||
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
async (
|
||||||
details.requestHeaders['sec-fetch-site'] = 'same-site';
|
details: { url: string; requestHeaders: { [x: string]: string } },
|
||||||
details.requestHeaders['DNT'] = '1';
|
callback: (arg0: { requestHeaders: any }) => void
|
||||||
let itspod = await this.win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`)
|
) => {
|
||||||
if (itspod != null)
|
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
||||||
details.requestHeaders['Cookie'] = `itspod=${itspod}`
|
details.requestHeaders["sec-fetch-site"] = "same-site";
|
||||||
|
details.requestHeaders["DNT"] = "1";
|
||||||
|
let itspod = await this.win.webContents.executeJavaScript(
|
||||||
|
`window.localStorage.getItem("music.ampwebplay.itspod")`
|
||||||
|
);
|
||||||
|
if (itspod != null)
|
||||||
|
details.requestHeaders["Cookie"] = `itspod=${itspod}`;
|
||||||
|
}
|
||||||
|
callback({ requestHeaders: details.requestHeaders });
|
||||||
}
|
}
|
||||||
callback({requestHeaders: details.requestHeaders})
|
);
|
||||||
})
|
|
||||||
|
|
||||||
let location = `http://localhost:${this.clientPort}/`
|
let location = `http://localhost:${this.clientPort}/`;
|
||||||
|
|
||||||
if (electron.app.isPackaged) {
|
if (electron.app.isPackaged) {
|
||||||
this.win.loadURL(location)
|
this.win.loadURL(location);
|
||||||
} else {
|
} else {
|
||||||
this.win.loadURL(location, {userAgent: 'Cider Development Environment'})
|
this.win.loadURL(location, {
|
||||||
|
userAgent: "Cider Development Environment",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the window handlers
|
* Initializes the window handlers
|
||||||
*/
|
*/
|
||||||
private startHandlers(): void {
|
private startHandlers(): void {
|
||||||
|
|
||||||
/**********************************************************************************************************************
|
/**********************************************************************************************************************
|
||||||
* ipcMain Events
|
* ipcMain Events
|
||||||
****************************************************************************************************************** */
|
****************************************************************************************************************** */
|
||||||
electron.ipcMain.on("cider-platform", (event) => {
|
electron.ipcMain.on("cider-platform", (event) => {
|
||||||
event.returnValue = process.platform
|
event.returnValue = process.platform;
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on("get-gpu-mode", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on("is-dev", (event) => {
|
|
||||||
event.returnValue = this.devMode
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('close', () => { // listen for close event
|
|
||||||
this.win.close();
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('put-library-songs', (event, arg) => {
|
|
||||||
fs.writeFileSync(path.join(this.paths.ciderCache, "library-songs.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('put-library-artists', (event, arg) => {
|
|
||||||
fs.writeFileSync(path.join(this.paths.ciderCache, "library-artists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('put-library-albums', (event, arg) => {
|
|
||||||
fs.writeFileSync(path.join(this.paths.ciderCache, "library-albums.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('put-library-playlists', (event, arg) => {
|
|
||||||
fs.writeFileSync(path.join(this.paths.ciderCache, "library-playlists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('put-library-recentlyAdded', (event, arg) => {
|
|
||||||
fs.writeFileSync(path.join(this.paths.ciderCache, "library-recentlyAdded.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('get-library-songs', (event) => {
|
|
||||||
let librarySongs = fs.readFileSync(path.join(this.paths.ciderCache, "library-songs.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(librarySongs)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('get-library-artists', (event) => {
|
|
||||||
let libraryArtists = fs.readFileSync(path.join(this.paths.ciderCache, "library-artists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryArtists)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('get-library-albums', (event) => {
|
|
||||||
let libraryAlbums = fs.readFileSync(path.join(this.paths.ciderCache, "library-albums.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryAlbums)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('get-library-playlists', (event) => {
|
|
||||||
let libraryPlaylists = fs.readFileSync(path.join(this.paths.ciderCache, "library-playlists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryPlaylists)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('get-library-recentlyAdded', (event) => {
|
|
||||||
let libraryRecentlyAdded = fs.readFileSync(path.join(this.paths.ciderCache, "library-recentlyAdded.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryRecentlyAdded)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.handle('getYTLyrics', async (event, track, artist) => {
|
|
||||||
const u = track + " " + artist + " official video";
|
|
||||||
return await yt.search(u)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.handle('setVibrancy', (event, key, value) => {
|
|
||||||
this.win.setVibrancy(value)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
electron.ipcMain.on('maximize', () => { // listen for maximize event
|
electron.ipcMain.on("get-gpu-mode", (event) => {
|
||||||
if (this.win.isMaximized()) {
|
event.returnValue = process.platform;
|
||||||
this.win.unmaximize()
|
});
|
||||||
} else {
|
|
||||||
this.win.maximize()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('minimize', () => { // listen for minimize event
|
electron.ipcMain.on("is-dev", (event) => {
|
||||||
|
event.returnValue = this.devMode;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("close", () => {
|
||||||
|
// listen for close event
|
||||||
|
this.win.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-songs", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-songs.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-artists", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-artists.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-albums", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-albums.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-playlists", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-playlists.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-recentlyAdded", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-songs", (event) => {
|
||||||
|
let librarySongs = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-songs.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(librarySongs);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-artists", (event) => {
|
||||||
|
let libraryArtists = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-artists.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryArtists);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-albums", (event) => {
|
||||||
|
let libraryAlbums = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-albums.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryAlbums);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-playlists", (event) => {
|
||||||
|
let libraryPlaylists = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-playlists.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryPlaylists);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-recentlyAdded", (event) => {
|
||||||
|
let libraryRecentlyAdded = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryRecentlyAdded);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("getYTLyrics", async (event, track, artist) => {
|
||||||
|
const u = track + " " + artist + " official video";
|
||||||
|
return await yt.search(u);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("setVibrancy", (event, key, value) => {
|
||||||
|
this.win.setVibrancy(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("maximize", () => {
|
||||||
|
// listen for maximize event
|
||||||
|
if (this.win.isMaximized()) {
|
||||||
|
this.win.unmaximize();
|
||||||
|
} else {
|
||||||
|
this.win.maximize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("minimize", () => {
|
||||||
|
// listen for minimize event
|
||||||
this.win.minimize();
|
this.win.minimize();
|
||||||
})
|
});
|
||||||
|
|
||||||
// Set scale
|
// Set scale
|
||||||
electron.ipcMain.on('setScreenScale', (event, scale) => {
|
electron.ipcMain.on("setScreenScale", (event, scale) => {
|
||||||
this.win.webContents.setZoomFactor(parseFloat(scale))
|
this.win.webContents.setZoomFactor(parseFloat(scale));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Titlebar #147 - Implemented as plugin
|
||||||
|
electron.ipcMain.on('set-titlebar', (e, titlebar) => {
|
||||||
|
this.win.title = `${titlebar} - Cider`
|
||||||
|
})
|
||||||
|
electron.ipcMain.on('reset-titlebar', () => {
|
||||||
|
this.win.title = `Cider`
|
||||||
})
|
})
|
||||||
|
|
||||||
/* *********************************************************************************************
|
/* *********************************************************************************************
|
||||||
* Window Events
|
* Window Events
|
||||||
* **********************************************************************************************/
|
* **********************************************************************************************/
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
let WND_STATE = {
|
let WND_STATE = {
|
||||||
MINIMIZED: 0,
|
MINIMIZED: 0,
|
||||||
NORMAL: 1,
|
NORMAL: 1,
|
||||||
MAXIMIZED: 2,
|
MAXIMIZED: 2,
|
||||||
FULL_SCREEN: 3
|
FULL_SCREEN: 3,
|
||||||
}
|
};
|
||||||
let wndState = WND_STATE.NORMAL
|
let wndState = WND_STATE.NORMAL;
|
||||||
|
|
||||||
this.win.on("resize", (_: any) => {
|
this.win.on("resize", (_: any) => {
|
||||||
const isMaximized = this.win.isMaximized()
|
const isMaximized = this.win.isMaximized();
|
||||||
const isMinimized = this.win.isMinimized()
|
const isMinimized = this.win.isMinimized();
|
||||||
const isFullScreen = this.win.isFullScreen()
|
const isFullScreen = this.win.isFullScreen();
|
||||||
const state = wndState;
|
const state = wndState;
|
||||||
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
||||||
wndState = WND_STATE.MINIMIZED
|
wndState = WND_STATE.MINIMIZED;
|
||||||
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
||||||
wndState = WND_STATE.FULL_SCREEN
|
wndState = WND_STATE.FULL_SCREEN;
|
||||||
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
||||||
wndState = WND_STATE.MAXIMIZED
|
wndState = WND_STATE.MAXIMIZED;
|
||||||
this.win.webContents.executeJavaScript(`app.chrome.maximized = true`)
|
this.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
|
||||||
} else if (state !== WND_STATE.NORMAL) {
|
} else if (state !== WND_STATE.NORMAL) {
|
||||||
wndState = WND_STATE.NORMAL
|
wndState = WND_STATE.NORMAL;
|
||||||
this.win.webContents.executeJavaScript(`app.chrome.maximized = false`)
|
this.win.webContents.executeJavaScript(
|
||||||
|
`app.chrome.maximized = false`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.win.on("closed", () => {
|
this.win.on("closed", () => {
|
||||||
this.win = null
|
this.win = null;
|
||||||
})
|
});
|
||||||
|
|
||||||
// Set window Handler
|
// Set window Handler
|
||||||
this.win.webContents.setWindowOpenHandler((x: any) => {
|
this.win.webContents.setWindowOpenHandler((x: any) => {
|
||||||
if (x.url.includes("apple") || x.url.includes("localhost")) {
|
if (x.url.includes("apple") || x.url.includes("localhost")) {
|
||||||
return {action: "allow"}
|
return { action: "allow" };
|
||||||
}
|
}
|
||||||
electron.shell.openExternal(x.url).catch(console.error)
|
electron.shell.openExternal(x.url).catch(console.error);
|
||||||
return {action: 'deny'}
|
return { action: "deny" };
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
284
src/main/base/wsapi.ts
Normal file
284
src/main/base/wsapi.ts
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import * as ws from "ws";
|
||||||
|
import * as http from "http";
|
||||||
|
import * as https from "https";
|
||||||
|
import * as url from "url";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as electron from "electron";
|
||||||
|
const WebSocket = ws;
|
||||||
|
const WebSocketServer = ws.Server;
|
||||||
|
|
||||||
|
private class standardResponse {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
data: any;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class wsapi {
|
||||||
|
port: any = 26369
|
||||||
|
wss: any = null
|
||||||
|
clients: []
|
||||||
|
createId() {
|
||||||
|
// create random guid
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async InitWebSockets () {
|
||||||
|
electron.ipcMain.on('wsapi-updatePlaybackState', (event, arg) => {
|
||||||
|
wsapi.updatePlaybackState(arg);
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnQueue', (event, arg) => {
|
||||||
|
wsapi.returnQueue(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnSearch', (event, arg) => {
|
||||||
|
console.log("SEARCH")
|
||||||
|
wsapi.returnSearch(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnSearchLibrary', (event, arg) => {
|
||||||
|
wsapi.returnSearchLibrary(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnDynamic', (event, arg, type) => {
|
||||||
|
wsapi.returnDynamic(JSON.parse(arg), type);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnMusicKitApi', (event, arg, method) => {
|
||||||
|
wsapi.returnMusicKitApi(JSON.parse(arg), method);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnLyrics', (event, arg) => {
|
||||||
|
wsapi.returnLyrics(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
this.wss = new WebSocketServer({
|
||||||
|
port: this.port,
|
||||||
|
perMessageDeflate: {
|
||||||
|
zlibDeflateOptions: {
|
||||||
|
// See zlib defaults.
|
||||||
|
chunkSize: 1024,
|
||||||
|
memLevel: 7,
|
||||||
|
level: 3
|
||||||
|
},
|
||||||
|
zlibInflateOptions: {
|
||||||
|
chunkSize: 10 * 1024
|
||||||
|
},
|
||||||
|
// Other options settable:
|
||||||
|
clientNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverMaxWindowBits: 10, // Defaults to negotiated value.
|
||||||
|
// Below options specified as default values.
|
||||||
|
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
||||||
|
threshold: 1024 // Size (in bytes) below which messages
|
||||||
|
// should not be compressed if context takeover is disabled.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(`WebSocketServer started on port: ${this.port}`);
|
||||||
|
|
||||||
|
const defaultResponse = new standardResponse(0, {}, "OK");
|
||||||
|
|
||||||
|
|
||||||
|
this.wss.on('connection', function connection(ws) {
|
||||||
|
ws.id = wsapi.createId();
|
||||||
|
console.log(`Client ${ws.id} connected`)
|
||||||
|
wsapi.clients.push(ws);
|
||||||
|
ws.on('message', function incoming(message) {
|
||||||
|
|
||||||
|
});
|
||||||
|
// ws on message
|
||||||
|
ws.on('message', function incoming(message) {
|
||||||
|
let data = JSON.parse(message);
|
||||||
|
let response = new standardResponse(0, {}, "OK");;
|
||||||
|
if (data.action) {
|
||||||
|
data.action.toLowerCase();
|
||||||
|
}
|
||||||
|
switch (data.action) {
|
||||||
|
default:
|
||||||
|
response.message = "Action not found";
|
||||||
|
break;
|
||||||
|
case "identify":
|
||||||
|
response.message = "Thanks for identifying!"
|
||||||
|
response.data = {
|
||||||
|
id: ws.id
|
||||||
|
}
|
||||||
|
ws.identity = {
|
||||||
|
name: data.name,
|
||||||
|
author: data.author,
|
||||||
|
description: data.description,
|
||||||
|
version: data.version
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "play-next":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`);
|
||||||
|
response.message = "Play Next";
|
||||||
|
break;
|
||||||
|
case "play-later":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`);
|
||||||
|
response.message = "Play Later";
|
||||||
|
break;
|
||||||
|
case "quick-play":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`);
|
||||||
|
response.message = "Quick Play";
|
||||||
|
break;
|
||||||
|
case "get-lyrics":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.getLyrics()`);
|
||||||
|
break;
|
||||||
|
case "shuffle":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
|
||||||
|
break;
|
||||||
|
case "set-shuffle":
|
||||||
|
if(data.shuffle == true) {
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
|
||||||
|
}else{
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "repeat":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
|
||||||
|
break;
|
||||||
|
case "seek":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
|
||||||
|
response.message = "Seek";
|
||||||
|
break;
|
||||||
|
case "pause":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
|
||||||
|
response.message = "Paused";
|
||||||
|
break;
|
||||||
|
case "play":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
|
||||||
|
response.message = "Playing";
|
||||||
|
break;
|
||||||
|
case "stop":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
|
||||||
|
response.message = "Stopped";
|
||||||
|
break;
|
||||||
|
case "volume":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
|
||||||
|
response.message = "Volume";
|
||||||
|
break;
|
||||||
|
case "mute":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
|
||||||
|
response.message = "Muted";
|
||||||
|
break;
|
||||||
|
case "unmute":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
|
||||||
|
response.message = "Unmuted";
|
||||||
|
break;
|
||||||
|
case "next":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().skipToNextItem()`);
|
||||||
|
response.message = "Next";
|
||||||
|
break;
|
||||||
|
case "previous":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().skipToPreviousItem()`);
|
||||||
|
response.message = "Previous";
|
||||||
|
break;
|
||||||
|
case "musickit-api":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)})`);
|
||||||
|
break;
|
||||||
|
case "musickit-library-api":
|
||||||
|
break;
|
||||||
|
case "set-autoplay":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
|
||||||
|
break;
|
||||||
|
case "queue-move":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
|
||||||
|
break;
|
||||||
|
case "get-queue":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.getQueue()`);
|
||||||
|
break;
|
||||||
|
case "search":
|
||||||
|
if (!data.limit) {
|
||||||
|
data.limit = 10;
|
||||||
|
}
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
|
||||||
|
break;
|
||||||
|
case "library-search":
|
||||||
|
if (!data.limit) {
|
||||||
|
data.limit = 10;
|
||||||
|
}
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
|
||||||
|
break;
|
||||||
|
case "show-window":
|
||||||
|
electron.app.win.show()
|
||||||
|
break;
|
||||||
|
case "hide-window":
|
||||||
|
electron.app.win.hide()
|
||||||
|
break;
|
||||||
|
case "play-mediaitem":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.playTrackById(${data.id}, \`${data.kind}\`)`);
|
||||||
|
response.message = "Playing track";
|
||||||
|
break;
|
||||||
|
case "get-status":
|
||||||
|
response.data = {
|
||||||
|
isAuthorized: true
|
||||||
|
};
|
||||||
|
response.message = "Status";
|
||||||
|
break;
|
||||||
|
case "get-currentmediaitem":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ws.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', function close() {
|
||||||
|
// remove client from list
|
||||||
|
wsapi.clients.splice(wsapi.clients.indexOf(ws), 1);
|
||||||
|
console.log(`Client ${ws.id} disconnected`);
|
||||||
|
});
|
||||||
|
ws.send(JSON.stringify(defaultResponse));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sendToClient(id) {
|
||||||
|
// replace the clients.forEach with a filter to find the client that requested
|
||||||
|
}
|
||||||
|
updatePlaybackState(attr) {
|
||||||
|
const response = new standardResponse(0, attr, "OK", "playbackStateUpdate");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnMusicKitApi(results, method) {
|
||||||
|
const response = new standardResponse(0, results, "OK", `musickitapi.${method}`);
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnDynamic(results, type) {
|
||||||
|
const response = new standardResponse(0, results, "OK", type);
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnLyrics(results) {
|
||||||
|
const response = new standardResponse(0, results, "OK", "lyrics");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnSearch(results) {
|
||||||
|
const response = new standardResponse(0, results, "OK", "searchResults");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnSearchLibrary(results) {
|
||||||
|
const response = new standardResponse(0, results, "OK", "searchResultsLibrary");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnQueue(queue) {
|
||||||
|
const response = new standardResponse(0, queue, "OK", "queue");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import {AppEvents} from "./base/app";
|
||||||
import PluginHandler from "./base/plugins";
|
import PluginHandler from "./base/plugins";
|
||||||
|
|
||||||
// const test = new PluginHandler();
|
// const test = new PluginHandler();
|
||||||
let win = null;
|
let win: Promise<void> | null = null;
|
||||||
const config = new ConfigStore();
|
const config = new ConfigStore();
|
||||||
const App = new AppEvents(config.store);
|
const App = new AppEvents(config.store);
|
||||||
const Cider = new Win(electron.app, config.store)
|
const Cider = new Win(electron.app, config.store)
|
||||||
|
@ -30,8 +30,12 @@ electron.app.on('ready', () => {
|
||||||
require('vue-devtools').install()
|
require('vue-devtools').install()
|
||||||
}
|
}
|
||||||
|
|
||||||
win = Cider.createWindow();
|
electron.components.whenReady().then(() => {
|
||||||
plug.callPlugins('onReady', win);
|
win = Cider.createWindow();
|
||||||
|
plug.callPlugins('onReady', win);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -52,23 +56,27 @@ electron.app.on('before-quit', () => {
|
||||||
console.warn(`${electron.app.getName()} exited.`);
|
console.warn(`${electron.app.getName()} exited.`);
|
||||||
});
|
});
|
||||||
//
|
//
|
||||||
|
// // @ts-ignore
|
||||||
// // Widevine Stuff
|
// // Widevine Stuff
|
||||||
// app.on('widevine-ready', (version, lastVersion) => {
|
// electron.app.on('widevine-ready', (version, lastVersion) => {
|
||||||
// if (null !== lastVersion) {
|
// if (null !== lastVersion) {
|
||||||
// console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
|
// console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
|
||||||
// } else {
|
// } else {
|
||||||
// console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
|
// console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
//
|
|
||||||
// app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
|
// // @ts-ignore
|
||||||
|
// electron.app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
|
||||||
// console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
|
// console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
|
||||||
// })
|
// })
|
||||||
//
|
|
||||||
// app.on('widevine-error', (error) => {
|
// // @ts-ignore
|
||||||
|
// electron.app.on('widevine-error', (error) => {
|
||||||
// console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
|
// console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
|
||||||
// app.exit()
|
// electron.app.exit()
|
||||||
// })
|
// })
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// app.on('open-url', (event, url) => {
|
// app.on('open-url', (event, url) => {
|
||||||
|
|
35
src/main/plugins/Extras/sendSongToTitlebar.ts
Normal file
35
src/main/plugins/Extras/sendSongToTitlebar.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import * as electron from "electron";
|
||||||
|
|
||||||
|
export default class sendSongToTitlebar {
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'sendSongToTitlebar';
|
||||||
|
public description: string = 'Sets the app\'s titlebar to the Song title';
|
||||||
|
public version: string = '0.0.1';
|
||||||
|
public author: string = 'Cider Collective (credit to 8times9 via #147)';
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(): void {}
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {}
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: any): void {
|
||||||
|
electron.ipcRenderer.send('set-titlebar', attributes.name)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {}
|
||||||
|
}
|
5
src/renderer/.jsbeautifyrc
Normal file
5
src/renderer/.jsbeautifyrc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"js": {
|
||||||
|
"beautify.ignore": "src/renderer/index.js"
|
||||||
|
}
|
||||||
|
}
|
103
src/renderer/WSAPI_Interop.js
Normal file
103
src/renderer/WSAPI_Interop.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
const wsapi = {
|
||||||
|
cache: {playParams: {id: 0}, status: null, remainingTime: 0},
|
||||||
|
playbackCache: {status: null, time: Date.now()},
|
||||||
|
search(term, limit) {
|
||||||
|
MusicKit.getInstance().api.search(term, {limit: limit, types: 'songs,artists,albums'}).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnSearch', JSON.stringify(results))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchLibrary(term, limit) {
|
||||||
|
MusicKit.getInstance().api.library.search(term, {limit: limit, types: 'library-songs,library-artists,library-albums'}).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnSearchLibrary', JSON.stringify(results))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getAttributes: function () {
|
||||||
|
const mk = MusicKit.getInstance();
|
||||||
|
const nowPlayingItem = mk.nowPlayingItem;
|
||||||
|
const isPlayingExport = mk.isPlaying;
|
||||||
|
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||||
|
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
|
||||||
|
|
||||||
|
attributes.status = isPlayingExport ? isPlayingExport : false;
|
||||||
|
attributes.name = attributes.name ? attributes.name : 'No Title Found';
|
||||||
|
attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''};
|
||||||
|
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : '';
|
||||||
|
attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'};
|
||||||
|
attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : 'no-id-found';
|
||||||
|
attributes.albumName = attributes.albumName ? attributes.albumName : '';
|
||||||
|
attributes.artistName = attributes.artistName ? attributes.artistName : '';
|
||||||
|
attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
|
||||||
|
attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0;
|
||||||
|
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
|
||||||
|
attributes.startTime = Date.now();
|
||||||
|
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
|
||||||
|
attributes.volume = mk.volume;
|
||||||
|
attributes.shuffleMode = mk.shuffleMode;
|
||||||
|
attributes.repeatMode = mk.repeatMode;
|
||||||
|
attributes.autoplayEnabled = mk.autoplayEnabled;
|
||||||
|
return attributes
|
||||||
|
},
|
||||||
|
moveQueueItem(oldPosition, newPosition) {
|
||||||
|
MusicKit.getInstance().queue._queueItems.splice(newPosition,0,MusicKit.getInstance().queue._queueItems.splice(oldPosition,1)[0])
|
||||||
|
MusicKit.getInstance().queue._reindex()
|
||||||
|
},
|
||||||
|
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());
|
||||||
|
},
|
||||||
|
getLyrics() {
|
||||||
|
return []
|
||||||
|
_lyrics.GetLyrics(1, false)
|
||||||
|
},
|
||||||
|
getQueue() {
|
||||||
|
ipcRenderer.send('wsapi-returnQueue', JSON.stringify(MusicKit.getInstance().queue))
|
||||||
|
},
|
||||||
|
playNext(type, id) {
|
||||||
|
var request = {}
|
||||||
|
request[type] = id
|
||||||
|
MusicKit.getInstance().playNext(request)
|
||||||
|
},
|
||||||
|
playLater(type, id) {
|
||||||
|
var request = {}
|
||||||
|
request[type] = id
|
||||||
|
MusicKit.getInstance().playLater(request)
|
||||||
|
},
|
||||||
|
love() {
|
||||||
|
|
||||||
|
},
|
||||||
|
playTrackById(id, kind = "song") {
|
||||||
|
MusicKit.getInstance().setQueue({ [kind]: id }).then(function (queue) {
|
||||||
|
MusicKit.getInstance().play()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
quickPlay(term) {
|
||||||
|
// Quick play by song name
|
||||||
|
MusicKit.getInstance().api.search(term, { limit: 2, types: 'songs' }).then(function (data) {
|
||||||
|
MusicKit.getInstance().setQueue({ song: data["songs"][0]["id"] }).then(function (queue) {
|
||||||
|
MusicKit.getInstance().play()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toggleShuffle() {
|
||||||
|
MusicKit.getInstance().shuffleMode = MusicKit.getInstance().shuffleMode === 0 ? 1 : 0
|
||||||
|
},
|
||||||
|
toggleRepeat() {
|
||||||
|
if(MusicKit.getInstance().repeatMode == 0) {
|
||||||
|
MusicKit.getInstance().repeatMode = 2
|
||||||
|
}else if(MusicKit.getInstance().repeatMode == 2){
|
||||||
|
MusicKit.getInstance().repeatMode = 1
|
||||||
|
}else{
|
||||||
|
MusicKit.getInstance().repeatMode = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -18,6 +18,7 @@
|
||||||
--navbarHeight: 48px;
|
--navbarHeight: 48px;
|
||||||
--selected: rgb(130 130 130 / 30%);
|
--selected: rgb(130 130 130 / 30%);
|
||||||
--selected-click: rgb(80 80 80 / 30%);
|
--selected-click: rgb(80 80 80 / 30%);
|
||||||
|
--hover: rgb(200 200 200 / 10%);
|
||||||
--keyColor: #fa586a;
|
--keyColor: #fa586a;
|
||||||
--keyColor-rgb: 250, 88, 106;
|
--keyColor-rgb: 250, 88, 106;
|
||||||
--keyColor-rollover: #ff8a9c;
|
--keyColor-rollover: #ff8a9c;
|
||||||
|
@ -254,6 +255,32 @@ input[type="text"], input[type="number"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artworkMaterial {
|
||||||
|
position: relative;
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
position: absolute;
|
||||||
|
width: 200%;
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: brightness(200%) blur(180px) saturate(280%) contrast(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
>img:first-child {
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
>img:last-child {
|
||||||
|
bottom:0;
|
||||||
|
right: 0;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[artwork-hidden] {
|
[artwork-hidden] {
|
||||||
transition: opacity .25s var(--appleEase);
|
transition: opacity .25s var(--appleEase);
|
||||||
|
@ -1102,6 +1129,34 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
filter: contrast(0.8);
|
filter: contrast(0.8);
|
||||||
|
|
||||||
|
.lcdMenu {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
background: transparent;
|
||||||
|
border: 0px;
|
||||||
|
appearance: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: var(--hover);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: var(--selected-click);
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
.svg-icon {
|
||||||
|
--url: url('views/svg/more.svg')!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-chrome .app-chrome-item > .app-playback-controls .playback-info {
|
.app-chrome .app-chrome-item > .app-playback-controls .playback-info {
|
||||||
|
@ -1843,6 +1898,36 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
|
|
||||||
/* Cider */
|
/* Cider */
|
||||||
|
|
||||||
|
.more-btn-round {
|
||||||
|
border-radius: 100%;
|
||||||
|
background: rgba(100, 100, 100, 0.5);
|
||||||
|
box-shadow: var(--ciderShadow-Generic);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
filter: brightness(75%);
|
||||||
|
transform: scale(0.98);
|
||||||
|
transition: transform 0s var(--appleEase), box-shadow 0.2s var(--appleEase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
width: 100%;
|
||||||
|
background: #eee;
|
||||||
|
--url: url("./views/svg/more.svg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.about-page {
|
.about-page {
|
||||||
.teamBtn {
|
.teamBtn {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1898,6 +1983,14 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
|
|
||||||
&.md-btn-block {
|
&.md-btn-block {
|
||||||
display: block;
|
display: block;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.md-btn-glyph {
|
||||||
|
display:flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.md-btn-primary {
|
&.md-btn-primary {
|
||||||
|
@ -2345,115 +2438,171 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
.playlist-page {
|
.playlist-page {
|
||||||
--bgColor: transparent;
|
--bgColor: transparent;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
background: linear-gradient(180deg, var(--bgColor) 32px, var(--bgColor) 59px, transparent 60px, transparent 100%);
|
//background: linear-gradient(180deg, var(--bgColor) 32px, var(--bgColor) 18px, transparent 60px, transparent 100%);
|
||||||
top: 0;
|
top: 0;
|
||||||
padding-top: var(--navigationBarHeight);
|
padding-top: var(--navigationBarHeight);
|
||||||
|
|
||||||
.playlist-body {
|
.playlist-body {
|
||||||
padding: var(--contentInnerPadding);
|
padding: 0px var(--contentInnerPadding) 0px var(--contentInnerPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-bottom: 1px solid rgba(200, 200, 200, 0.05);
|
||||||
|
z-index: 6;
|
||||||
|
padding: 0px 1em;
|
||||||
|
backdrop-filter: blur(32px);
|
||||||
|
background: rgba(24, 24, 24, 0.15);
|
||||||
|
top: var(--navigationBarHeight);
|
||||||
|
transition: opacity 0.1s var(--appleEase);
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-display {
|
.playlist-display {
|
||||||
padding: var(--contentInnerPadding);
|
padding: var(--contentInnerPadding);
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.playlist-info {
|
.artworkContainer {
|
||||||
flex-shrink: unset;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-mask-image: radial-gradient(at top left, black, transparent 70%), radial-gradient(at top right, black, transparent 70%), linear-gradient(180deg, rgb(200 200 200), transparent 98%);
|
||||||
|
opacity: .7;
|
||||||
|
animation: playlistArtworkFadeIn 1s var(--appleEase);
|
||||||
|
|
||||||
|
.artworkMaterial>img {
|
||||||
|
filter: brightness(100%) blur(80px) saturate(100%) contrast(1);
|
||||||
|
object-position: center;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistInfo {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
justify-content: center;
|
||||||
justify-content: flex-end;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.playlist-name {
|
>.row {
|
||||||
font-weight: 700;
|
width: calc(100% - 32px);
|
||||||
font-size: 1.6rem;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
margin-right: 6px;
|
|
||||||
flex-shrink: unset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nameEdit {
|
.playlist-info {
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
margin-right: 6px;
|
|
||||||
flex-shrink: unset;
|
flex-shrink: unset;
|
||||||
background: transparent;
|
display: flex;
|
||||||
border: 0px;
|
flex-flow: column;
|
||||||
color: inherit;
|
justify-content: flex-end;
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playlist-artist {
|
.playlist-name {
|
||||||
font-size: 20px;
|
font-weight: 700;
|
||||||
margin-bottom: 6px;
|
font-size: 1.6rem;
|
||||||
margin-right: 6px;
|
margin-bottom: 6px;
|
||||||
flex-shrink: unset;
|
margin-right: 6px;
|
||||||
}
|
flex-shrink: unset;
|
||||||
|
|
||||||
.playlist-desc {
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 14px;
|
|
||||||
flex-shrink: unset;
|
|
||||||
margin-right: 5px;
|
|
||||||
max-height: 100px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
height: 100px;
|
|
||||||
-webkit-mask-image: -webkit-gradient(linear, left 50%, left 90%, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-btn {
|
.nameEdit {
|
||||||
appearance: none;
|
font-weight: 700;
|
||||||
position: absolute;
|
font-size: 1.6rem;
|
||||||
right: 0;
|
margin-bottom: 6px;
|
||||||
bottom: 0;
|
margin-right: 6px;
|
||||||
padding: 0 5px;
|
flex-shrink: unset;
|
||||||
font-size: 14px;
|
background: transparent;
|
||||||
color: var(--keyColor);
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0px;
|
border: 0px;
|
||||||
cursor: pointer;
|
color: inherit;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
font-weight: 600;
|
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.playlist-desc-expanded {
|
.playlist-artist {
|
||||||
box-sizing: border-box;
|
font-size: 20px;
|
||||||
font-size: 14px;
|
margin-bottom: 6px;
|
||||||
position: relative;
|
margin-right: 6px;
|
||||||
|
flex-shrink: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.more-btn {
|
.playlist-desc {
|
||||||
appearance: none;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 0 5px;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--keyColor);
|
flex-shrink: unset;
|
||||||
background-color: transparent;
|
margin-right: 5px;
|
||||||
border: 0px;
|
max-height: 100px;
|
||||||
cursor: pointer;
|
position: relative;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
.content {
|
||||||
overflow: hidden;
|
height: 100px;
|
||||||
display: flex;
|
-webkit-mask-image: -webkit-gradient(linear, left 50%, left 90%, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0)));
|
||||||
justify-content: flex-end;
|
}
|
||||||
align-items: flex-end;
|
|
||||||
font-weight: 600;
|
.more-btn {
|
||||||
font-family: inherit;
|
appearance: none;
|
||||||
text-transform: uppercase;
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 0 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--keyColor);
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-desc-expanded {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 14px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.more-btn {
|
||||||
|
appearance: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 0 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--keyColor);
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.friends-info {
|
.friends-info {
|
||||||
|
@ -2486,26 +2635,6 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-more {
|
|
||||||
border-radius: 100%;
|
|
||||||
background: var(--keyColor);
|
|
||||||
box-shadow: var(--ciderShadow-Generic);
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
float: right;
|
|
||||||
border: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--keyColor-rollover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--keyColor-pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.playlist-time {
|
.playlist-time {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
|
@ -2513,6 +2642,14 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes playlistArtworkFadeIn {
|
||||||
|
0%{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100%{
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Collection Page
|
// Collection Page
|
||||||
.collection-page {
|
.collection-page {
|
||||||
padding-bottom: 128px;
|
padding-bottom: 128px;
|
||||||
|
@ -2555,8 +2692,21 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
|
.floating-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-bottom: 1px solid rgba(200, 200, 200, 0.05);
|
||||||
|
z-index: 6;
|
||||||
|
padding: 0px 1em;
|
||||||
|
backdrop-filter: blur(32px);
|
||||||
|
background: rgba(24, 24, 24, 0.15);
|
||||||
|
top: var(--navigationBarHeight);
|
||||||
|
transition: opacity 0.1s var(--appleEase);
|
||||||
|
}
|
||||||
|
|
||||||
.artist-header {
|
.artist-header {
|
||||||
background: linear-gradient(45deg, var(--keyColor), #0e0e0e);
|
//background: linear-gradient(45deg, var(--keyColor), #0e0e0e);
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -2564,26 +2714,36 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.artist-more {
|
.header-content {
|
||||||
border-radius: 100%;
|
z-index: 1;
|
||||||
background: var(--keyColor);
|
}
|
||||||
box-shadow: var(--ciderShadow-Generic);
|
|
||||||
width: 36px;
|
.artworkContainer {
|
||||||
height: 36px;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-mask-image: radial-gradient(at top left, black, transparent 70%), radial-gradient(at top right, black, transparent 70%), linear-gradient(180deg, rgb(200 200 200), transparent 98%);
|
||||||
|
opacity: .7;
|
||||||
|
animation: playlistArtworkFadeIn 1s var(--appleEase);
|
||||||
|
|
||||||
|
.artworkMaterial>img {
|
||||||
|
filter: brightness(100%) blur(80px) saturate(100%) contrast(1);
|
||||||
|
object-position: center;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-btn-round {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 26px;
|
bottom: 26px;
|
||||||
right: 32px;
|
right: 32px;
|
||||||
border: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--keyColor-rollover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--keyColor-pressed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.animated {
|
.animated {
|
||||||
|
@ -2650,28 +2810,31 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artist-play {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background: var(--keyColor);
|
||||||
|
border-radius: 100%;
|
||||||
|
box-shadow: var(--mediaItemShadow);
|
||||||
|
display: none;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
border: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--keyColor-rollover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: var(--keyColor-pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
.artist-title {
|
.artist-title {
|
||||||
|
|
||||||
.artist-play {
|
.artist-play {
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
background: var(--keyColor);
|
|
||||||
border-radius: 100%;
|
|
||||||
margin: 14px;
|
|
||||||
box-shadow: var(--mediaItemShadow);
|
|
||||||
display: none;
|
|
||||||
cursor: pointer;
|
|
||||||
appearance: none;
|
|
||||||
border: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
transform: translateY(3px);
|
transform: translateY(3px);
|
||||||
|
margin: 14px;
|
||||||
&:hover {
|
|
||||||
background: var(--keyColor-rollover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--keyColor-pressed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.artist-animation-on {
|
&.artist-animation-on {
|
||||||
|
@ -2689,7 +2852,8 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-body {
|
.artist-body {
|
||||||
padding: var(--contentInnerPadding);
|
padding: 0px var(--contentInnerPadding) 0px var(--contentInnerPadding);
|
||||||
|
margin-top: -48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.showmoreless {
|
.showmoreless {
|
||||||
|
|
37
src/renderer/views/components/artwork-material.ejs
Normal file
37
src/renderer/views/components/artwork-material.ejs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<script type="text/x-template" id="artwork-material">
|
||||||
|
<div class="artworkMaterial">
|
||||||
|
<img :src="src" v-for="image in images"/>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('artwork-material', {
|
||||||
|
template: '#artwork-material',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
src: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.src = app.getMediaItemArtwork(this.url, this.size)
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: false,
|
||||||
|
default: '32'
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: false,
|
||||||
|
default: '2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
0
src/renderer/views/components/mediaitem-info.ejs
Normal file
0
src/renderer/views/components/mediaitem-info.ejs
Normal file
|
@ -1,6 +1,7 @@
|
||||||
<script type="text/x-template" id="mediaitem-scroller-horizontal">
|
<script type="text/x-template" id="mediaitem-scroller-horizontal">
|
||||||
<template>
|
<template>
|
||||||
<div class="cd-hmedia-scroller" :class="kind">
|
<div class="cd-hmedia-scroller" :class="kind">
|
||||||
|
<slot></slot>
|
||||||
<mediaitem-square :kind="kind" :item="item"
|
<mediaitem-square :kind="kind" :item="item"
|
||||||
v-for="item in items"></mediaitem-square>
|
v-for="item in items"></mediaitem-square>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
props: {
|
props: {
|
||||||
'items': {
|
'items': {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: false
|
||||||
},
|
},
|
||||||
'kind': {
|
'kind': {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -113,11 +113,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
|
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
|
||||||
<div class="actions"
|
<div class="actions">
|
||||||
v-if="isInLibrary(mk.nowPlayingItem['attributes']['playParams'])">
|
<button class="lcdMenu" @click="nowPlayingContextMenu">
|
||||||
❤️
|
<div class="svg-icon"></div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions" v-else>🖤</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -659,6 +659,8 @@
|
||||||
</button>
|
</button>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Artwork Material -->
|
||||||
|
<%- include('components/artwork-material') %>
|
||||||
<!-- Menu Panel -->
|
<!-- Menu Panel -->
|
||||||
<%- include('components/menu-panel') %>
|
<%- include('components/menu-panel') %>
|
||||||
<!-- Playlist Listing -->
|
<!-- Playlist Listing -->
|
||||||
|
@ -710,5 +712,6 @@
|
||||||
<script src="index.js?v=1"></script>
|
<script src="index.js?v=1"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/resonance-audio/build/resonance-audio.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/resonance-audio/build/resonance-audio.min.js"></script>
|
||||||
<script src="/audio/audio.js?v=1"></script>
|
<script src="/audio/audio.js?v=1"></script>
|
||||||
|
<script src="/WSAPI_Interop.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,30 @@
|
||||||
<script type="text/x-template" id="cider-artist-feed">
|
<script type="text/x-template" id="cider-artist-feed">
|
||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row nopadding">
|
||||||
|
<div class="col nopadding">
|
||||||
|
<h3>Followed Artists</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="well">
|
||||||
|
<mediaitem-scroller-horizontal>
|
||||||
|
<div v-for="artist in artists" style="margin: 6px;">
|
||||||
|
<mediaitem-square :item="artist" kind="small"></mediaitem-square>
|
||||||
|
<button @click="unfollow(artist.id)" class="md-btn md-btn-glyph" style="display:flex;">
|
||||||
|
<div class="sidebar-icon">
|
||||||
|
<div class="svg-icon" :style="{'--url': 'url(./assets/feather/x-circle.svg)'}"></div>
|
||||||
|
</div> Unfollow
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mediaitem-scroller-horizontal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -30,6 +55,7 @@
|
||||||
app: this.$root,
|
app: this.$root,
|
||||||
followedArtists: this.$root.cfg.home.followedArtists,
|
followedArtists: this.$root.cfg.home.followedArtists,
|
||||||
artistFeed: [],
|
artistFeed: [],
|
||||||
|
artists: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
@ -37,11 +63,26 @@
|
||||||
await this.getArtistFeed()
|
await this.getArtistFeed()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
unfollow(id) {
|
||||||
|
let index = this.followedArtists.indexOf(id)
|
||||||
|
if (index > -1) {
|
||||||
|
this.followedArtists.splice(index, 1)
|
||||||
|
}
|
||||||
|
let artist = this.artists.find(a => a.id == id)
|
||||||
|
let index2 = this.artists.indexOf(artist)
|
||||||
|
if (index2 > -1) {
|
||||||
|
this.artists.splice(index2, 1)
|
||||||
|
}
|
||||||
|
this.getArtistFeed()
|
||||||
|
},
|
||||||
async getArtistFeed() {
|
async getArtistFeed() {
|
||||||
let artists = this.followedArtists
|
let artists = this.followedArtists
|
||||||
let self = this
|
let self = this
|
||||||
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${artists.toString()}&views=featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see&extend=artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero&extend[playlists]=trackCount&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=20&art[url]=f`).then(artistData => {
|
this.artists = []
|
||||||
|
this.artistFeed = []
|
||||||
|
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${artists.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`).then(artistData => {
|
||||||
artistData.data.data.forEach(item => {
|
artistData.data.data.forEach(item => {
|
||||||
|
self.artists.push(item)
|
||||||
if (item.views["latest-release"].data.length != 0) {
|
if (item.views["latest-release"].data.length != 0) {
|
||||||
self.artistFeed.push(item.views["latest-release"].data[0])
|
self.artistFeed.push(item.views["latest-release"].data[0])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,59 @@
|
||||||
<script type="text/x-template" id="cider-artist">
|
<script type="text/x-template" id="cider-artist">
|
||||||
<div class="content-inner artist-page">
|
<div class="content-inner artist-page">
|
||||||
<div class="artist-header" :style="getArtistPalette(data)" :key="data.id">
|
<div class="artist-header" :key="data.id" v-observe-visibility="{callback: isHeaderVisible}">
|
||||||
<animatedartwork-view
|
<animatedartwork-view
|
||||||
:priority="true"
|
:priority="true"
|
||||||
v-if="data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)"
|
v-if="data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)"
|
||||||
:video="data.attributes.editorialVideo.motionArtistWide16x9.video ?? (data.attributes.editorialVideo.motionArtistFullscreen16x9.video ?? '')">
|
:video="data.attributes.editorialVideo.motionArtistWide16x9.video ?? (data.attributes.editorialVideo.motionArtistFullscreen16x9.video ?? '')">
|
||||||
</animatedartwork-view>
|
</animatedartwork-view>
|
||||||
<div class="row">
|
<div class="header-content">
|
||||||
<div class="col-sm" style="width: auto;">
|
<div class="row">
|
||||||
<div class="artist-image" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
|
<div class="col-sm" style="width: auto;">
|
||||||
<mediaitem-artwork
|
<div class="artist-image" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
|
||||||
shadow="large"
|
<mediaitem-artwork
|
||||||
:url="data.attributes.artwork ? data.attributes.artwork.url : ''"
|
shadow="large"
|
||||||
size="190" type="artists"></mediaitem-artwork>
|
:url="data.attributes.artwork ? data.attributes.artwork.url : ''"
|
||||||
<button class="overlay-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
size="190" type="artists"></mediaitem-artwork>
|
||||||
|
<button class="overlay-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
||||||
app.mk.play()
|
app.mk.play()
|
||||||
})">
|
})">
|
||||||
<%- include("../svg/play.svg") %>
|
<%- include("../svg/play.svg") %>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col flex-center artist-title"
|
||||||
<div class="col flex-center artist-title"
|
:class="{'artist-animation-on': (data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) }"
|
||||||
:class="{'artist-animation-on': (data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) }"
|
>
|
||||||
>
|
<button class="artist-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
||||||
<button class="artist-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
|
||||||
app.mk.play()
|
app.mk.play()
|
||||||
})"><%- include("../svg/play.svg") %></button>
|
})"><%- include("../svg/play.svg") %></button>
|
||||||
<h1>{{ data.attributes.name }}</h1>
|
<h1>{{ data.attributes.name }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="more-btn-round" @click="artistMenu">
|
||||||
|
<div class="svg-icon"></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="artworkContainer" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
|
||||||
|
<artwork-material :url="data.attributes.artwork.url" size="190" images="1"></artwork-material>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="floating-header" :style="{opacity: (headerVisible ? 0 : 1),'pointer-events': (headerVisible ? 'none' : '')}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<button class="artist-play" style="display:block;" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
||||||
|
app.mk.play()
|
||||||
|
})"><%- include("../svg/play.svg") %></button>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ data.attributes.name }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<button class="more-btn-round" @click="artistMenu">
|
||||||
|
<div class="svg-icon"></div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="artist-more" @click="artistMenu">
|
|
||||||
<div style=" margin-top: -1px;
|
|
||||||
margin-left: -5px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;">
|
|
||||||
<%- include("../svg/more.svg") %>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="artist-body">
|
<div class="artist-body">
|
||||||
<div class="row well">
|
<div class="row well">
|
||||||
|
@ -129,10 +146,14 @@
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
topSongsExpanded: false,
|
topSongsExpanded: false,
|
||||||
app: this.$root
|
app: this.$root,
|
||||||
|
headerVisible: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isHeaderVisible(visible) {
|
||||||
|
this.headerVisible = visible
|
||||||
|
},
|
||||||
artistMenu (event) {
|
artistMenu (event) {
|
||||||
let self = this
|
let self = this
|
||||||
let followAction = "follow"
|
let followAction = "follow"
|
||||||
|
|
|
@ -7,81 +7,118 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.playlists.loadingState == 1">
|
<template v-if="app.playlists.loadingState == 1">
|
||||||
<div class="playlist-display row"
|
<div class="playlist-display"
|
||||||
:style="{
|
:style="{
|
||||||
background: (data.attributes.artwork != null && data.attributes.artwork['bgColor'] != null) ? ('#' + data.attributes.artwork.bgColor) : '',
|
'--bgColor': (data.attributes.artwork != null && data.attributes.artwork['bgColor'] != null) ? ('#' + data.attributes.artwork.bgColor) : '',
|
||||||
color: (data.attributes.artwork != null && data.attributes.artwork['textColor1'] != null) ? ('#' + data.attributes.artwork.textColor1) : ''
|
'--textColor': (data.attributes.artwork != null && data.attributes.artwork['textColor1'] != null) ? ('#' + data.attributes.artwork.textColor1) : ''
|
||||||
}">
|
}">
|
||||||
<div class="col-auto flex-center">
|
<div class="playlistInfo">
|
||||||
<div style="width: 260px;height:260px;">
|
<div class="row">
|
||||||
<mediaitem-artwork
|
<div class="col-auto flex-center">
|
||||||
:video-priority="true"
|
<div style="width: 260px;height:260px;">
|
||||||
:url="(data.attributes != null && data.attributes.artwork != null) ? data.attributes.artwork.url : ((data.relationships != null && data.relationships.tracks.data.length > 0 && data.relationships.tracks.data[0].attributes != null) ? ((data.relationships.tracks.data[0].attributes.artwork != null)? data.relationships.tracks.data[0].attributes.artwork.url : ''):'')"
|
<mediaitem-artwork
|
||||||
:video="(data.attributes != null && data.attributes.editorialVideo != null) ? (data.attributes.editorialVideo.motionDetailSquare ? data.attributes.editorialVideo.motionDetailSquare.video : (data.attributes.editorialVideo.motionSquareVideo1x1 ? data.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' "
|
shadow="large"
|
||||||
size="260"
|
:video-priority="true"
|
||||||
></mediaitem-artwork>
|
:url="(data.attributes != null && data.attributes.artwork != null) ? data.attributes.artwork.url : ((data.relationships != null && data.relationships.tracks.data.length > 0 && data.relationships.tracks.data[0].attributes != null) ? ((data.relationships.tracks.data[0].attributes.artwork != null)? data.relationships.tracks.data[0].attributes.artwork.url : ''):'')"
|
||||||
</div>
|
:video="(data.attributes != null && data.attributes.editorialVideo != null) ? (data.attributes.editorialVideo.motionDetailSquare ? data.attributes.editorialVideo.motionDetailSquare.video : (data.attributes.editorialVideo.motionSquareVideo1x1 ? data.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' "
|
||||||
</div>
|
size="260"
|
||||||
<div class="col playlist-info">
|
></mediaitem-artwork>
|
||||||
<template v-if="!editorialNotesExpanded">
|
|
||||||
<div>
|
|
||||||
<div class="playlist-name" @click="editPlaylistName()" v-show="!nameEditing">
|
|
||||||
{{data.attributes ? (data.attributes.name ??
|
|
||||||
(data.attributes.title ?? '') ?? '') : ''}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="playlist-name" v-show="nameEditing"><input type="text" spellcheck="false"
|
</div>
|
||||||
class="nameEdit"
|
<div class="col playlist-info">
|
||||||
v-model="data.attributes.name"
|
<template v-if="!editorialNotesExpanded">
|
||||||
@blur="editPlaylist"
|
<div>
|
||||||
@change="editPlaylist"
|
<div class="playlist-name" @click="editPlaylistName()" v-show="!nameEditing">
|
||||||
@keydown.enter="editPlaylist"/></div>
|
{{data.attributes ? (data.attributes.name ??
|
||||||
<div class="playlist-artist item-navigate"
|
(data.attributes.title ?? '') ?? '') : ''}}
|
||||||
v-if="getArtistName(data) != ''"
|
</div>
|
||||||
@click="data.attributes && data.attributes.artistName ? app.searchAndNavigate(data,'artist') : ''">
|
<div class="playlist-name" v-show="nameEditing"><input type="text" spellcheck="false"
|
||||||
{{getArtistName(data)}}
|
class="nameEdit"
|
||||||
</div>
|
v-model="data.attributes.name"
|
||||||
<div class="playlist-desc" v-if="data.attributes.description && (data.attributes.description.standard || data.attributes.description.short)">
|
@blur="editPlaylist"
|
||||||
<div v-if="data.attributes.description.short" class="content" v-html="data.attributes.description.short"></div>
|
@change="editPlaylist"
|
||||||
<div v-else-if="data.attributes.description.standard" class="content" v-html="data.attributes.description.standard"></div>
|
@keydown.enter="editPlaylist"/></div>
|
||||||
<button v-if="data.attributes.description.short" class="more-btn"
|
<div class="playlist-artist item-navigate"
|
||||||
@click="editorialNotesExpanded = !editorialNotesExpanded">
|
v-if="getArtistName(data) != ''"
|
||||||
More
|
@click="data.attributes && data.attributes.artistName ? app.searchAndNavigate(data,'artist') : ''">
|
||||||
|
{{getArtistName(data)}}
|
||||||
|
</div>
|
||||||
|
<div class="playlist-desc" v-if="data.attributes.description && (data.attributes.description.standard || data.attributes.description.short)">
|
||||||
|
<div v-if="data.attributes.description.short" class="content" v-html="data.attributes.description.short"></div>
|
||||||
|
<div v-else-if="data.attributes.description.standard" class="content" v-html="data.attributes.description.standard"></div>
|
||||||
|
<button v-if="data.attributes.description.short" class="more-btn"
|
||||||
|
@click="editorialNotesExpanded = !editorialNotesExpanded">
|
||||||
|
More
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="editorialNotesExpanded">
|
||||||
|
<div class="playlist-desc-expanded">
|
||||||
|
<div class="content"
|
||||||
|
v-html="((data.attributes.editorialNotes) ? (data.attributes.editorialNotes.standard ?? (data.attributes.editorialNotes.short ?? '') ) : (data.attributes.description ? (data.attributes.description.standard ?? (data.attributes.description.short ?? '')) : ''))"></div>
|
||||||
|
<button class="more-btn" @click="editorialNotesExpanded = !editorialNotesExpanded">Less
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="playlist-controls" v-observe-visibility="{callback: isHeaderVisible}">
|
||||||
|
<button class="md-btn" style="min-width: 120px;"
|
||||||
|
@click="app.mk.shuffleMode = 0; play()">
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;"
|
||||||
|
@click="app.mk.shuffleMode = 1;play()">
|
||||||
|
Shuffle
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;" v-if="inLibrary!=null && confirm!=true"
|
||||||
|
@click="confirmButton()">
|
||||||
|
{{ (!inLibrary) ? "Add to Library" : "Remove from Library" }}
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;" v-if="confirm==true"
|
||||||
|
@click="(!inLibrary) ? addToLibrary(data.attributes.playParams.id.toString()) : removeFromLibrary(data.attributes.playParams.id.toString()) ">
|
||||||
|
Confirm?
|
||||||
|
</button>
|
||||||
|
<button class="more-btn-round" style="float:right;" @click="menu">
|
||||||
|
<div class="svg-icon"></div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template v-if="editorialNotesExpanded">
|
</div>
|
||||||
<div class="playlist-desc-expanded">
|
<div class="artworkContainer" v-if="data.attributes.artwork != null">
|
||||||
<div class="content"
|
<artwork-material :url="data.attributes.artwork.url" size="260" images="1"></artwork-material>
|
||||||
v-html="((data.attributes.editorialNotes) ? (data.attributes.editorialNotes.standard ?? (data.attributes.editorialNotes.short ?? '') ) : (data.attributes.description ? (data.attributes.description.standard ?? (data.attributes.description.short ?? '')) : ''))"></div>
|
</div>
|
||||||
<button class="more-btn" @click="editorialNotesExpanded = !editorialNotesExpanded">Less
|
|
||||||
|
</div>
|
||||||
|
<div class="floating-header" :style="{opacity: (headerVisible ? 0 : 1),'pointer-events': (headerVisible ? 'none' : '')}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{data.attributes ? (data.attributes.name ??
|
||||||
|
(data.attributes.title ?? '') ?? '') : ''}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div>
|
||||||
|
<button class="md-btn" style="min-width: 120px;"
|
||||||
|
@click="app.mk.shuffleMode = 0; play()">
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;"
|
||||||
|
@click="app.mk.shuffleMode = 1;play()">
|
||||||
|
Shuffle
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;" v-if="inLibrary!=null && confirm!=true"
|
||||||
|
@click="confirmButton()">
|
||||||
|
{{ (!inLibrary) ? "Add to Library" : "Remove from Library" }}
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;" v-if="confirm==true"
|
||||||
|
@click="(!inLibrary) ? addToLibrary(data.attributes.playParams.id.toString()) : removeFromLibrary(data.attributes.playParams.id.toString()) ">
|
||||||
|
Confirm?
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<div class="playlist-controls">
|
<div class="col-auto flex-center">
|
||||||
<button class="md-btn" style="min-width: 120px;"
|
<button class="more-btn-round" style="float:right;" @click="menu">
|
||||||
@click="app.mk.shuffleMode = 0; play()">
|
<div class="svg-icon"></div>
|
||||||
Play
|
|
||||||
</button>
|
|
||||||
<button class="md-btn" style="min-width: 120px;"
|
|
||||||
@click="app.mk.shuffleMode = 1;play()">
|
|
||||||
Shuffle
|
|
||||||
</button>
|
|
||||||
<button class="md-btn" style="min-width: 120px;" v-if="inLibrary!=null && confirm!=true"
|
|
||||||
@click="confirmButton()">
|
|
||||||
{{ (!inLibrary) ? "Add to Library" : "Remove from Library" }}
|
|
||||||
</button>
|
|
||||||
<button class="md-btn" style="min-width: 120px;" v-if="confirm==true"
|
|
||||||
@click="(!inLibrary) ? addToLibrary(data.attributes.playParams.id.toString()) : removeFromLibrary(data.attributes.playParams.id.toString()) ">
|
|
||||||
Confirm?
|
|
||||||
</button>
|
|
||||||
<button class="playlist-more" @click="menu">
|
|
||||||
<div style=" margin-top: -1px;
|
|
||||||
margin-left: -5px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;">
|
|
||||||
<%- include("../svg/more.svg") %>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,6 +155,20 @@
|
||||||
style="width: 50%;">
|
style="width: 50%;">
|
||||||
{{data.attributes.copyright}}
|
{{data.attributes.copyright}}
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<template v-if="typeof data.meta != 'undefined'">
|
||||||
|
<div v-for="view in data.meta.views.order" v-if="data.views[view].data.length != 0">
|
||||||
|
<div class="row" >
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ data.views[view].attributes.title }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mediaitem-scroller-horizontal :items="data.views[view].data"></mediaitem-scroller-horizontal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -138,7 +189,8 @@
|
||||||
confirm: false,
|
confirm: false,
|
||||||
app: this.$root,
|
app: this.$root,
|
||||||
itemBadges: [],
|
itemBadges: [],
|
||||||
badgesRequested: false
|
badgesRequested: false,
|
||||||
|
headerVisible: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
@ -153,6 +205,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isHeaderVisible(visible) {
|
||||||
|
this.headerVisible = visible
|
||||||
|
},
|
||||||
getBadges() {
|
getBadges() {
|
||||||
return
|
return
|
||||||
if (this.badgesRequested) {
|
if (this.badgesRequested) {
|
||||||
|
|
|
@ -14,13 +14,16 @@
|
||||||
<mediaitem-square v-else :item="item" :type="getKind(item)"></mediaitem-square>
|
<mediaitem-square v-else :item="item" :type="getKind(item)"></mediaitem-square>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<button v-if="triggerEnabled" style="opacity:0;height: 32px;" v-observe-visibility="{callback: visibilityChanged}">Show More</button>
|
<button v-if="triggerEnabled" style="opacity:0;height: 32px;"
|
||||||
|
v-observe-visibility="{callback: visibilityChanged}">Show More
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<transition name="fabfade">
|
<transition name="fabfade">
|
||||||
<button class="top-fab" v-show="showFab" @click="scrollToTop()">
|
<button class="top-fab" v-show="showFab" @click="scrollToTop()">
|
||||||
<%- include("../svg/arrow-up.svg") %>
|
<%- include("../svg/arrow-up.svg") %>
|
||||||
</button>
|
</button>
|
||||||
</transition>
|
</transition>
|
||||||
|
<div class="well" v-show="loading"><div class="spinner"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -47,16 +50,17 @@
|
||||||
canSeeTrigger: false,
|
canSeeTrigger: false,
|
||||||
showFab: false,
|
showFab: false,
|
||||||
commonKind: "song",
|
commonKind: "song",
|
||||||
api: this.$root.mk.api
|
api: this.$root.mk.api,
|
||||||
|
loading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getKind(item) {
|
getKind(item) {
|
||||||
if(typeof item.kind != "undefined") {
|
if (typeof item.kind != "undefined") {
|
||||||
this.commonKind = item.kind;
|
this.commonKind = item.kind;
|
||||||
return item.kind
|
return item.kind
|
||||||
}
|
}
|
||||||
if(typeof item.attributes.playParams != "undefined") {
|
if (typeof item.attributes.playParams != "undefined") {
|
||||||
this.commonKind = item.attributes.playParams.kind
|
this.commonKind = item.attributes.playParams.kind
|
||||||
return item.attributes.playParams.kind
|
return item.attributes.playParams.kind
|
||||||
}
|
}
|
||||||
|
@ -71,73 +75,48 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getNext() {
|
getNext() {
|
||||||
// if this.data.next is not null, then we can run this.data.next() and concat to this.data.data to get the next page
|
let self = this
|
||||||
switch(this.type) {
|
this.triggerEnabled = false;
|
||||||
default:
|
if (typeof this.data.next == "undefined") {
|
||||||
case "artists":
|
return
|
||||||
if (this.data.next && this.triggerEnabled) {
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
|
|
||||||
let nextFn = (data => {
|
|
||||||
console.log(data);
|
|
||||||
this.data.next = data.next;
|
|
||||||
this.data.data = this.data.data.concat(data.data);
|
|
||||||
this.triggerEnabled = true;
|
|
||||||
});
|
|
||||||
if(typeof this.data.next == "function") {
|
|
||||||
this.data.next().then(data => nextFn(data));
|
|
||||||
}else{
|
|
||||||
this.api.v3.music(this.data.next).then(data => nextFn(data));
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
console.log("No next page");
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "search":
|
|
||||||
if (this.data.next && this.triggerEnabled) {
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
this.data.next().then(data => {
|
|
||||||
console.log(data);
|
|
||||||
this.data.next = data[this.data.groups].next;
|
|
||||||
this.data.data = this.data.data.concat(data[this.data.groups].data.data);
|
|
||||||
this.triggerEnabled = true;
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
console.log("No next page");
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "listen_now":
|
|
||||||
case "curator":
|
|
||||||
if (this.data.next && this.triggerEnabled) {
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
app.mk.api.v3.music(this.data.next).then(data => {
|
|
||||||
console.log(data);
|
|
||||||
this.data.next = data.data.next;
|
|
||||||
this.data.data = this.data.data.concat(data.data.data);
|
|
||||||
this.triggerEnabled = true;
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
console.log("No next page");
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
this.api.v3.music(this.data.next, app.collectionList.requestBody).then((response) => {
|
||||||
|
console.log(response)
|
||||||
|
if (!app.collectionList.response.groups) {
|
||||||
|
if (response.data.next) {
|
||||||
|
this.data.data = this.data.data.concat(response.data.data);
|
||||||
|
this.data.next = response.data.next;
|
||||||
|
this.triggerEnabled = true;
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
}else{
|
||||||
|
if(!response.data.results[app.collectionList.response.groups]) {
|
||||||
|
this.loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (response.data.results[app.collectionList.response.groups].next) {
|
||||||
|
this.data.data = this.data.data.concat(response.data.results[app.collectionList.response.groups].data);
|
||||||
|
this.data.next = response.data.results[app.collectionList.response.groups].next;
|
||||||
|
this.triggerEnabled = true;
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
headerVisibility: function (isVisible, entry) {
|
headerVisibility: function (isVisible, entry) {
|
||||||
if(isVisible) {
|
if (isVisible) {
|
||||||
this.showFab = false;
|
this.showFab = false;
|
||||||
}else{
|
} else {
|
||||||
this.showFab = true;
|
this.showFab = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
visibilityChanged: function (isVisible, entry) {
|
visibilityChanged: function (isVisible, entry) {
|
||||||
if(isVisible) {
|
if (isVisible) {
|
||||||
this.canSeeTrigger = true;
|
this.canSeeTrigger = true;
|
||||||
this.getNext();
|
this.getNext();
|
||||||
}else{
|
} else {
|
||||||
this.canSeeTrigger = false;
|
this.canSeeTrigger = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@
|
||||||
async getArtistFeed() {
|
async getArtistFeed() {
|
||||||
let artists = this.followedArtists
|
let artists = this.followedArtists
|
||||||
let self = this
|
let self = this
|
||||||
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${artists.toString()}&views=featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see&extend=artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero&extend[playlists]=trackCount&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=20&art[url]=f`).then(artistData => {
|
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${artists.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`).then(artistData => {
|
||||||
artistData.data.data.forEach(item => {
|
artistData.data.data.forEach(item => {
|
||||||
if (item.views["latest-release"].data.length != 0) {
|
if (item.views["latest-release"].data.length != 0) {
|
||||||
self.artistFeed.push(item.views["latest-release"].data[0])
|
self.artistFeed.push(item.views["latest-release"].data[0])
|
||||||
|
|
|
@ -5,5 +5,8 @@
|
||||||
{{ $store.state.test }}
|
{{ $store.state.test }}
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<button class="md-btn">Cider Button</button>
|
<button class="md-btn">Cider Button</button>
|
||||||
|
<div style="position: relative;width: 300px;height: 300px;">
|
||||||
|
<artwork-material url="https://is3-ssl.mzstatic.com/image/thumb/Music126/v4/13/41/13/1341133b-560f-1aee-461f-c4b32ec049b4/cover.jpg/{w}x{h}bb.jpg"></artwork-material>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body oncontextmenu="return false;">
|
<body oncontextmenu="return false;">
|
||||||
<div id="app" :style="{'--artwork': getAlbumArtUrl()}">
|
<div id="app" :style="{'--artwork': getAlbumArtUrl()}">
|
||||||
<!-- App view when connected -->
|
<!-- App view when connected -->
|
||||||
<template v-if="connectedState == 1">
|
<template v-if="connectedState == 1">
|
||||||
<!-- Streamer Overlay -->
|
<!-- Streamer Overlay -->
|
||||||
<template></template>
|
<template></template>
|
||||||
<!-- Mini Player -->
|
<!-- Mini Player -->
|
||||||
|
@ -52,8 +52,7 @@
|
||||||
<div class="md-container md-container_panel player-panel" v-if="screen == 'player'">
|
<div class="md-container md-container_panel player-panel" v-if="screen == 'player'">
|
||||||
<div class="player_top">
|
<div class="player_top">
|
||||||
<div class="md-body player-artwork-container">
|
<div class="md-body player-artwork-container">
|
||||||
<div class="media-artwork" :class="artworkPlaying()"
|
<div class="media-artwork" :class="artworkPlaying()" :style="{'--artwork': getAlbumArtUrl()}">
|
||||||
:style="{'--artwork': getAlbumArtUrl()}">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,7 +84,7 @@
|
||||||
{{ lyric.line }}
|
{{ lyric.line }}
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
||||||
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
||||||
<div class="lyricWaiting">
|
<div class="lyricWaiting">
|
||||||
|
@ -101,8 +100,7 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer">
|
<div class="md-footer">
|
||||||
<button class="md-btn playback-button--small lyrics active"
|
<button class="md-btn playback-button--small lyrics active" @click="player.lowerPanelState = 'controls'"></button>
|
||||||
@click="player.lowerPanelState = 'controls'"></button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="player_bottom" v-if="player.lowerPanelState == 'controls'">
|
<div class="player_bottom" v-if="player.lowerPanelState == 'controls'">
|
||||||
|
@ -122,14 +120,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer">
|
<div class="md-footer">
|
||||||
<input type="range" min="0"
|
<input type="range" min="0" :value="player.currentMediaItem.durationInMillis - player.currentMediaItem.remainingTime" :max="player.currentMediaItem.durationInMillis" class="web-slider playback-slider" @input="seekTo($event.target.value)">
|
||||||
:value="player.currentMediaItem.durationInMillis - player.currentMediaItem.remainingTime"
|
|
||||||
:max="player.currentMediaItem.durationInMillis" class="web-slider playback-slider"
|
|
||||||
@input="seekTo($event.target.value)">
|
|
||||||
<div class="row nopadding player-duration-container" style="width: 90%;margin: 0 auto;">
|
<div class="row nopadding player-duration-container" style="width: 90%;margin: 0 auto;">
|
||||||
<div class="col nopadding player-duration-time" style="text-align:left">
|
<div class="col nopadding player-duration-time" style="text-align:left">
|
||||||
{{ parseTime(player.currentMediaItem.durationInMillis -
|
{{ parseTime(player.currentMediaItem.durationInMillis - player.currentMediaItem.remainingTime) }}
|
||||||
player.currentMediaItem.remainingTime) }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col nopadding player-duration-time" style="text-align:right">
|
<div class="col nopadding player-duration-time" style="text-align:right">
|
||||||
-{{ parseTime(player.currentMediaItem.remainingTime) }}
|
-{{ parseTime(player.currentMediaItem.remainingTime) }}
|
||||||
|
@ -137,21 +131,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer playback-buttons">
|
<div class="md-footer playback-buttons">
|
||||||
<button class="md-btn playback-button--small repeat" @click="repeat()"
|
<button class="md-btn playback-button--small repeat" @click="repeat()" v-if="player.currentMediaItem.repeatMode == 0"></button>
|
||||||
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 active" @click="repeat()"
|
<button class="md-btn playback-button--small repeat repeatOne" @click="repeat()" v-else-if="player.currentMediaItem.repeatMode == 1"></button>
|
||||||
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 previous" @click="previous()"></button>
|
||||||
<button class="md-btn playback-button pause" @click="pause()"
|
<button class="md-btn playback-button pause" @click="pause()" v-if="player.currentMediaItem.status"></button>
|
||||||
v-if="player.currentMediaItem.status"></button>
|
|
||||||
<button class="md-btn playback-button play" @click="play()" v-else></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 next" @click="next()"></button>
|
||||||
<button class="md-btn playback-button--small shuffle" @click="shuffle()"
|
<button class="md-btn playback-button--small shuffle" @click="shuffle()" v-if="player.currentMediaItem.shuffleMode == 0"></button>
|
||||||
v-if="player.currentMediaItem.shuffleMode == 0"></button>
|
<button class="md-btn playback-button--small shuffle active" @click="shuffle()" v-else></button>
|
||||||
<button class="md-btn playback-button--small shuffle active" @click="shuffle()"
|
|
||||||
v-else></button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer">
|
<div class="md-footer">
|
||||||
<div class="row volume-slider-container">
|
<div class="row volume-slider-container">
|
||||||
|
@ -159,8 +147,7 @@
|
||||||
<div class="player-volume-glyph decrease"></div>
|
<div class="player-volume-glyph decrease"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input type="range" class="web-slider volume-slider" max="1" min="0" step="0.01"
|
<input type="range" class="web-slider volume-slider" max="1" min="0" step="0.01" @input="setVolume($event.target.value)" :value="player.currentMediaItem.volume">
|
||||||
@input="setVolume($event.target.value)" :value="player.currentMediaItem.volume">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="player-volume-glyph increase"></div>
|
<div class="player-volume-glyph increase"></div>
|
||||||
|
@ -170,10 +157,8 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer">
|
<div class="md-footer">
|
||||||
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'"
|
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'" @click="showLyrics()"></button>
|
||||||
@click="showLyrics()"></button>
|
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'landscape'" @click="showLyricsInline()"></button>
|
||||||
<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 queue" @click="showQueue()"></button>
|
||||||
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
|
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -191,43 +176,34 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col" style="display: flex;align-items: center;">
|
<div class="col" style="display: flex;align-items: center;">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input type="text" placeholder="Artists, Songs, Lyrics, and More"
|
<input type="text" placeholder="Artists, Songs, Lyrics, and More" spellcheck="false" v-model="search.query" @change="searchQuery()" v-on:keyup.enter="searchQuery()" class="search-input">
|
||||||
spellcheck="false" v-model="search.query" @change="searchQuery()"
|
|
||||||
v-on:keyup.enter="searchQuery()" class="search-input">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-header search-type-container">
|
<div class="md-header search-type-container">
|
||||||
<button class="search-type-button" @click="search.searchType = 'applemusic';searchQuery()"
|
<button class="search-type-button" @click="search.searchType = 'applemusic';searchQuery()" :class="searchTypeClass('applemusic')" style="width:100%;">Apple Music
|
||||||
:class="searchTypeClass('applemusic')" style="width:100%;">Apple Music
|
|
||||||
</button>
|
</button>
|
||||||
<button class="search-type-button" @click="search.searchType = 'library';searchQuery()"
|
<button class="search-type-button" @click="search.searchType = 'library';searchQuery()" :class="searchTypeClass('library')" style="width:100%;">Library
|
||||||
:class="searchTypeClass('library')" style="width:100%;">Library
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-header search-tab-container" v-if="search.state == 2">
|
<div class="md-header search-tab-container" v-if="search.state == 2">
|
||||||
<button class="search-tab" @click="search.tab = 'all'" :class="searchTabClass('all')">All
|
<button class="search-tab" @click="search.tab = 'all'" :class="searchTabClass('all')">All
|
||||||
Results
|
Results
|
||||||
</button>
|
</button>
|
||||||
<button class="search-tab" @click="search.tab = 'songs'"
|
<button class="search-tab" @click="search.tab = 'songs'" :class="searchTabClass('songs')">Songs
|
||||||
:class="searchTabClass('songs')">Songs
|
|
||||||
</button>
|
</button>
|
||||||
<button class="search-tab" @click="search.tab = 'albums'"
|
<button class="search-tab" @click="search.tab = 'albums'" :class="searchTabClass('albums')">Albums
|
||||||
:class="searchTabClass('albums')">Albums
|
|
||||||
</button>
|
</button>
|
||||||
<button class="search-tab" @click="search.tab = 'artists'"
|
<button class="search-tab" @click="search.tab = 'artists'" :class="searchTabClass('artists')">Artists
|
||||||
:class="searchTabClass('artists')">Artists
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-body-container">
|
<div class="search-body-container">
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="md-body search-body" v-if="search.state == 0">
|
<div class="md-body search-body" v-if="search.state == 0">
|
||||||
<div
|
<div style="font-size: 17px;display:flex;flex-direction: column;justify-content: center;align-items: center;">
|
||||||
style="font-size: 17px;display:flex;flex-direction: column;justify-content: center;align-items: center;">
|
<img src="./assets/search.svg" style="width: 40px;margin: 32px;opacity: 0.85"> Search by song, album, artist, or lyrics.
|
||||||
<img src="./assets/search.svg" style="width: 40px;margin: 32px;opacity: 0.85">
|
|
||||||
Search by song, album, artist, or lyrics.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -237,10 +213,7 @@
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="md-body search-body"
|
<div class="md-body search-body" ref="searchBody" @scroll="searchScroll" style="overflow-y:auto;" v-if="search.state == 2">
|
||||||
ref="searchBody"
|
|
||||||
@scroll="searchScroll"
|
|
||||||
style="overflow-y:auto;" v-if="search.state == 2">
|
|
||||||
<template v-if="canShowSearchTab('songs')">
|
<template v-if="canShowSearchTab('songs')">
|
||||||
<div class="list-entry-header">Songs</div>
|
<div class="list-entry-header">Songs</div>
|
||||||
|
|
||||||
|
@ -377,11 +350,9 @@
|
||||||
</transition>
|
</transition>
|
||||||
<!-- Track Select Actions -->
|
<!-- Track Select Actions -->
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="md-container md-container_panel context-menu" style="overflow-y:auto;"
|
<div class="md-container md-container_panel context-menu" style="overflow-y:auto;" v-if="search.trackSelect">
|
||||||
v-if="search.trackSelect">
|
|
||||||
<div class="md-body context-menu-body">
|
<div class="md-body context-menu-body">
|
||||||
<button class="context-menu-item context-menu-item--left"
|
<button class="context-menu-item context-menu-item--left" @click="playMediaItemById(search.selected.id);clearSelectedTrack()">
|
||||||
@click="playMediaItemById(search.selected.id);clearSelectedTrack()">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto flex-center" v-if="search.selected.artwork"
|
<div class="col-auto flex-center" v-if="search.selected.artwork"
|
||||||
style="display:flex;align-items: center;">
|
style="display:flex;align-items: center;">
|
||||||
|
@ -404,8 +375,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-body context-menu-body" style="height: auto;">
|
<div class="md-body context-menu-body" style="height: auto;">
|
||||||
<button class="context-menu-item context-menu-item--left"
|
<button class="context-menu-item context-menu-item--left" @click="playMediaItemById(search.selected.id);clearSelectedTrack()">
|
||||||
@click="playMediaItemById(search.selected.id);clearSelectedTrack()">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
Play
|
Play
|
||||||
|
@ -415,8 +385,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button class="context-menu-item context-menu-item--left"
|
<button class="context-menu-item context-menu-item--left" @click="playNext('song', search.selected.id);clearSelectedTrack()">
|
||||||
@click="playNext('song', search.selected.id);clearSelectedTrack()">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
Play Next
|
Play Next
|
||||||
|
@ -426,8 +395,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button class="context-menu-item context-menu-item--left"
|
<button class="context-menu-item context-menu-item--left" @click="playLater('song', search.selected.id);clearSelectedTrack()">
|
||||||
@click="playLater('song', search.selected.id);clearSelectedTrack()">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
Play Later
|
Play Later
|
||||||
|
@ -520,24 +488,20 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="album-body-container" :style="getMediaPalette(artistPage.data)">
|
<div class="album-body-container" :style="getMediaPalette(artistPage.data)">
|
||||||
<div class="artist-header" v-if="artistPage.data['artwork']"
|
<div class="artist-header" v-if="artistPage.data['artwork']" :style="getMediaPalette(artistPage.data)">
|
||||||
:style="getMediaPalette(artistPage.data)">
|
<div class="artist-header-portrait" :style="{'--artwork': getAlbumArtUrlList(artistPage.data['artwork']['url'], 600)}"></div>
|
||||||
<div class="artist-header-portrait"
|
|
||||||
:style="{'--artwork': getAlbumArtUrlList(artistPage.data['artwork']['url'], 600)}"></div>
|
|
||||||
<h2>{{ artistPage.data["name"] }}</h2>
|
<h2>{{ artistPage.data["name"] }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-body artist-body">
|
<div class="md-body artist-body">
|
||||||
<h2>Songs</h2>
|
<h2>Songs</h2>
|
||||||
<div class="song-scroller-horizontal">
|
<div class="song-scroller-horizontal">
|
||||||
<button v-for="song in artistPage.data['songs']" class="song-placeholder"
|
<button v-for="song in artistPage.data['songs']" class="song-placeholder" @click="trackSelect(song)">
|
||||||
@click="trackSelect(song)">
|
|
||||||
{{ song.name }}
|
{{ song.name }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h2>Albums</h2>
|
<h2>Albums</h2>
|
||||||
<div class="mediaitem-scroller-horizontal">
|
<div class="mediaitem-scroller-horizontal">
|
||||||
<button v-for="album in artistPage.data['albums']" class="album-placeholder"
|
<button v-for="album in artistPage.data['albums']" class="album-placeholder" @click="showAlbum(album.id)">
|
||||||
@click="showAlbum(album.id)">
|
|
||||||
{{ album.name }}
|
{{ album.name }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -574,30 +538,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-header" style="text-align: right;padding: 5px 16px;">
|
<div class="md-header" style="text-align: right;padding: 5px 16px;">
|
||||||
<button
|
<button class="md-btn playback-button--small autoplay" v-if="!player.currentMediaItem.autoplayEnabled" @click="setAutoplay(true)"></button>
|
||||||
class="md-btn playback-button--small autoplay"
|
<button class="md-btn playback-button--small autoplay activeColor" v-else @click="setAutoplay(false)"></button>
|
||||||
v-if="!player.currentMediaItem.autoplayEnabled"
|
|
||||||
@click="setAutoplay(true)"
|
|
||||||
></button>
|
|
||||||
<button
|
|
||||||
class="md-btn playback-button--small autoplay activeColor"
|
|
||||||
v-else
|
|
||||||
@click="setAutoplay(false)"
|
|
||||||
></button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md-body queue-body" v-if="!player.queue['_queueItems']">
|
<div class="md-body queue-body" v-if="!player.queue['_queueItems']">
|
||||||
Empty
|
Empty
|
||||||
</div>
|
</div>
|
||||||
<div class="md-body queue-body" style="overflow-y:auto;" id="list-queue" v-else>
|
<div class="md-body queue-body" style="overflow-y:auto;" id="list-queue" v-else>
|
||||||
<draggable
|
<draggable v-model="queue.temp" handle=".handle" filter=".passed" @change="queueMove">
|
||||||
v-model="queue.temp"
|
<template v-for="(song, position) in queue.temp" v-if="position > player.queue['_position']">
|
||||||
handle=".handle"
|
|
||||||
filter=".passed"
|
|
||||||
@change="queueMove">
|
|
||||||
<template
|
|
||||||
v-for="(song, position) in queue.temp"
|
|
||||||
v-if="position > player.queue['_position']"
|
|
||||||
>
|
|
||||||
<div class="list-entry" :class="getQueuePositionClass(position)">
|
<div class="list-entry" :class="getQueuePositionClass(position)">
|
||||||
<div class="row" style="width:100%;">
|
<div class="row" style="width:100%;">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
@ -626,11 +575,8 @@
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer">
|
<div class="md-footer">
|
||||||
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'"
|
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'" @click="showLyrics()"></button>
|
||||||
@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 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 queue active" @click="screen = 'player'"></button>
|
||||||
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
|
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -665,7 +611,7 @@
|
||||||
{{ lyric.line }}
|
{{ lyric.line }}
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
||||||
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
||||||
<div class="lyricWaiting">
|
<div class="lyricWaiting">
|
||||||
|
@ -689,9 +635,7 @@
|
||||||
</transition>
|
</transition>
|
||||||
<!-- Album Page -->
|
<!-- Album Page -->
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="md-container md-container_panel md-container_album"
|
<div class="md-container md-container_panel md-container_album" v-if="screen == 'album-page' && albumPage.data['name']">
|
||||||
v-if="screen == 'album-page' && albumPage.data['name']"
|
|
||||||
>
|
|
||||||
<div class="md-header">
|
<div class="md-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
@ -701,8 +645,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="album-body-container">
|
<div class="album-body-container">
|
||||||
<div class="md-header">
|
<div class="md-header">
|
||||||
<div class="albumpage-artwork"
|
<div class="albumpage-artwork" :style="{'--artwork': getAlbumArtUrlList(albumPage.data['artwork']['url'], 300)}">
|
||||||
:style="{'--artwork': getAlbumArtUrlList(albumPage.data['artwork']['url'], 300)}">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="albumpage-album-name">
|
<div class="albumpage-album-name">
|
||||||
{{ albumPage.data["name"] }}
|
{{ albumPage.data["name"] }}
|
||||||
|
@ -711,20 +654,15 @@
|
||||||
{{ albumPage.data["artistName"] }}
|
{{ albumPage.data["artistName"] }}
|
||||||
</div>
|
</div>
|
||||||
<div class="albumpage-misc-info">
|
<div class="albumpage-misc-info">
|
||||||
{{ albumPage.data.genreNames[0] }} ∙ {{ new Date(albumPage.data.releaseDate).getFullYear()
|
{{ albumPage.data.genreNames[0] }} ∙ {{ new Date(albumPage.data.releaseDate).getFullYear() }}
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row" style="margin-top: 20px;">
|
<div class="row" style="margin-top: 20px;">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<button class="wr-btn"
|
<button class="wr-btn" @click="playAlbum(albumPage.data.id, false)" style="width:100%;">Play
|
||||||
@click="playAlbum(albumPage.data.id, false)"
|
|
||||||
style="width:100%;">Play
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<button class="wr-btn" style="width:100%;"
|
<button class="wr-btn" style="width:100%;" @click="playAlbum(albumPage.data.id, true)">Shuffle
|
||||||
@click="playAlbum(albumPage.data.id, true)"
|
|
||||||
>Shuffle
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -737,12 +675,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="md-body artist-body">
|
<div class="md-body artist-body">
|
||||||
<div class="list-entry-header">Tracks</div>
|
<div class="list-entry-header">Tracks</div>
|
||||||
<div class="list-entry" v-for="song in albumPage.data['tracks']"
|
<div class="list-entry" v-for="song in albumPage.data['tracks']" @click="trackSelect(song)">
|
||||||
@click="trackSelect(song)">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto flex-center">
|
<div class="col-auto flex-center">
|
||||||
<div class="list-entry-image" v-if="song.artwork"
|
<div class="list-entry-image" v-if="song.artwork" :style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
|
||||||
:style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col flex-center">
|
<div class="col flex-center">
|
||||||
|
@ -768,92 +704,80 @@
|
||||||
</transition>
|
</transition>
|
||||||
<!-- Album Page - Editorial Notes -->
|
<!-- Album Page - Editorial Notes -->
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="md-container md-container_panel context-menu" v-if="albumPage.editorsNotes"
|
<div class="md-container md-container_panel context-menu" v-if="albumPage.editorsNotes" style="padding-top: 42px;">
|
||||||
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;">
|
||||||
<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"] }}
|
{{ albumPage.data["name"] }}
|
||||||
</div>
|
</div>
|
||||||
<div class="md-body album-page-fullnotes-body"
|
<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']">
|
||||||
:style="getMediaPalette(albumPage.data)"
|
|
||||||
style="background:var(--bgColor);color:var(--textColor1);"
|
|
||||||
v-html="albumPage.data['editorialNotes']['standard']">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer"
|
<div class="md-footer" :style="getMediaPalette(albumPage.data)" style="background:var(--bgColor);color:var(--textColor1);">
|
||||||
:style="getMediaPalette(albumPage.data)"
|
|
||||||
style="background:var(--bgColor);color:var(--textColor1);"
|
|
||||||
>
|
|
||||||
<button class="context-menu-item" @click="albumPage.editorsNotes = false">Close</button>
|
<button class="context-menu-item" @click="albumPage.editorsNotes = false">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
<!-- Loading -->
|
<!-- Loading -->
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="md-container md-container_panel connection-error-panel" v-if="connectedState != 1">
|
<div class="md-container md-container_panel connection-error-panel" v-if="connectedState != 1">
|
||||||
<div class="md-header">
|
<div class="md-header">
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="md-body" style="display:flex;justify-content: center;align-items: center;">
|
|
||||||
<div v-if="connectedState == 0">
|
|
||||||
Loading...
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div class="md-body" style="display:flex;justify-content: center;align-items: center;">
|
||||||
<h3 style="text-align:center;">Connection Interrupted</h3>
|
<div v-if="connectedState == 0">
|
||||||
<button class="md-btn md-btn-primary"
|
Loading...
|
||||||
style="font-weight:500;width: 120px;border-radius: 50px;display:block;margin: 0 auto;"
|
</div>
|
||||||
@click="connect()">Retry
|
<div v-else>
|
||||||
|
<h3 style="text-align:center;">Connection Interrupted</h3>
|
||||||
|
<!--<button class="md-btn md-btn-primary" style="font-weight:500;width: 120px;border-radius: 50px;display:block;margin: 0 auto;" @click="connect()">Retry-->
|
||||||
|
<button class="md-btn md-btn-primary" style="font-weight:500;width: 120px;border-radius: 50px;display:block;margin: 0 auto;" onclick="document.location = document.location">Retry
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-footer">
|
</transition>
|
||||||
|
<!-- Template -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel" v-if="false">
|
||||||
|
<div class="md-header">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="md-body">
|
||||||
</transition>
|
|
||||||
<!-- Template -->
|
|
||||||
<transition name="wpfade">
|
|
||||||
<div class="md-container md-container_panel" v-if="false">
|
|
||||||
<div class="md-header">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md-body">
|
<div class="md-footer">
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="md-footer">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</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>
|
</div>
|
||||||
<div class="col flex-center text-overflow-elipsis" @click="$parent.screen = 'player'">
|
</transition>
|
||||||
<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>
|
</div>
|
||||||
</script>
|
|
||||||
|
|
||||||
<script src="index.js?v=1"></script>
|
<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>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -71,16 +71,16 @@ var app = new Vue({
|
||||||
},
|
},
|
||||||
musicAppVariant() {
|
musicAppVariant() {
|
||||||
if (navigator.userAgent.match(/Android/i)) {
|
if (navigator.userAgent.match(/Android/i)) {
|
||||||
return "Apple Music";
|
return "Cider";
|
||||||
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
||||||
return "Music";
|
return "Cider";
|
||||||
} else {
|
} else {
|
||||||
if (navigator.userAgent.indexOf('Mac') > 0) {
|
if (navigator.userAgent.indexOf('Mac') > 0) {
|
||||||
return 'Music';
|
return 'Music';
|
||||||
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
||||||
return 'Apple Music Electron';
|
return 'Cider';
|
||||||
} else {
|
} else {
|
||||||
return 'Apple Music Electron';
|
return 'Cider';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -283,7 +283,7 @@ var app = new Vue({
|
||||||
var start2 = this.hmsToSecondsOnly(start)
|
var start2 = this.hmsToSecondsOnly(start)
|
||||||
var end2 = this.hmsToSecondsOnly(end)
|
var end2 = this.hmsToSecondsOnly(end)
|
||||||
var currentProgress = ((100 * (currentTime)) / (end2))
|
var currentProgress = ((100 * (currentTime)) / (end2))
|
||||||
// check if currenttime is between start and end
|
// check if currenttime is between start and end
|
||||||
this.player.lyricsDebug.start = start2
|
this.player.lyricsDebug.start = start2
|
||||||
this.player.lyricsDebug.end = end2
|
this.player.lyricsDebug.end = end2
|
||||||
this.player.lyricsDebug.current = currentTime
|
this.player.lyricsDebug.current = currentTime
|
||||||
|
@ -340,10 +340,10 @@ var app = new Vue({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showSearch(reset = false) {
|
showSearch(reset = false) {
|
||||||
if(reset) {
|
if (reset) {
|
||||||
this.search.lastPage = "search"
|
this.search.lastPage = "search"
|
||||||
}
|
}
|
||||||
switch(this.search.lastPage) {
|
switch (this.search.lastPage) {
|
||||||
case "search":
|
case "search":
|
||||||
this.screen = "search"
|
this.screen = "search"
|
||||||
break;
|
break;
|
||||||
|
@ -359,7 +359,7 @@ var app = new Vue({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showArtistByName(name) {
|
showArtistByName(name) {
|
||||||
this.musicKitAPI("search", name, {types: "artists"})
|
this.musicKitAPI("search", name, { types: "artists" })
|
||||||
},
|
},
|
||||||
showAlbum(id) {
|
showAlbum(id) {
|
||||||
this.search.lastPage = "album"
|
this.search.lastPage = "album"
|
||||||
|
@ -369,7 +369,7 @@ var app = new Vue({
|
||||||
showArtist(id) {
|
showArtist(id) {
|
||||||
this.search.lastPage = "artist"
|
this.search.lastPage = "artist"
|
||||||
this.screen = "artist-page"
|
this.screen = "artist-page"
|
||||||
this.musicKitAPI("artist", id, {include: "songs,playlists,albums"})
|
this.musicKitAPI("artist", id, { include: "songs,playlists,albums" })
|
||||||
},
|
},
|
||||||
showQueue() {
|
showQueue() {
|
||||||
this.queue.temp = this.player["queue"]["_queueItems"]
|
this.queue.temp = this.player["queue"]["_queueItems"]
|
||||||
|
@ -418,9 +418,9 @@ var app = new Vue({
|
||||||
return palette
|
return palette
|
||||||
},
|
},
|
||||||
playAlbum(id, shuffle = false) {
|
playAlbum(id, shuffle = false) {
|
||||||
if(shuffle) {
|
if (shuffle) {
|
||||||
this.setShuffle(true)
|
this.setShuffle(true)
|
||||||
}else{
|
} else {
|
||||||
this.setShuffle(false)
|
this.setShuffle(false)
|
||||||
}
|
}
|
||||||
this.playMediaItemById(id, 'album');
|
this.playMediaItemById(id, 'album');
|
||||||
|
@ -464,12 +464,11 @@ var app = new Vue({
|
||||||
document.body.classList.add("streamer-overlay")
|
document.body.classList.add("streamer-overlay")
|
||||||
},
|
},
|
||||||
setMode(mode) {
|
setMode(mode) {
|
||||||
switch(mode) {
|
switch (mode) {
|
||||||
default:
|
default: this.screen = "player"
|
||||||
this.screen = "player"
|
break;
|
||||||
break;
|
|
||||||
case "miniplayer":
|
case "miniplayer":
|
||||||
this.screen = "miniplayer"
|
this.screen = "miniplayer"
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -484,9 +483,9 @@ var app = new Vue({
|
||||||
console.log(e);
|
console.log(e);
|
||||||
console.log('connected');
|
console.log('connected');
|
||||||
app.connectedState = 1;
|
app.connectedState = 1;
|
||||||
if(getParameterByName("mode")) {
|
if (getParameterByName("mode")) {
|
||||||
self.setMode(getParameterByName("mode"))
|
self.setMode(getParameterByName("mode"))
|
||||||
}else{
|
} else {
|
||||||
self.setMode("default")
|
self.setMode("default")
|
||||||
}
|
}
|
||||||
self.clearSelectedTrack()
|
self.clearSelectedTrack()
|
||||||
|
@ -505,45 +504,45 @@ var app = new Vue({
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.onmessage = (e) => {
|
socket.onmessage = (e) => {
|
||||||
|
console.log(e.data)
|
||||||
const response = JSON.parse(e.data);
|
const response = JSON.parse(e.data);
|
||||||
switch (response.type) {
|
switch (response.type) {
|
||||||
default:
|
default: console.log(response);
|
||||||
console.log(response);
|
break;
|
||||||
break;
|
|
||||||
case "musickitapi.search":
|
case "musickitapi.search":
|
||||||
self.showArtist(response.data["artists"][0]["id"]);
|
self.showArtist(response.data["artists"][0]["id"]);
|
||||||
break;
|
break;
|
||||||
case "musickitapi.album":
|
case "musickitapi.album":
|
||||||
if(self.screen == "album-page") {
|
if (self.screen == "album-page") {
|
||||||
self.albumPage.data = response.data
|
self.albumPage.data = response.data
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "musickitapi.artist":
|
case "musickitapi.artist":
|
||||||
if(self.screen == "artist-page") {
|
if (self.screen == "artist-page") {
|
||||||
self.artistPage.data = response.data
|
self.artistPage.data = response.data
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "queue":
|
case "queue":
|
||||||
self.player.queue = response.data;
|
self.player.queue = response.data;
|
||||||
self.queue.temp = response.data["_queueItems"];
|
self.queue.temp = response.data["_queueItems"];
|
||||||
self.$forceUpdate()
|
self.$forceUpdate()
|
||||||
break;
|
break;
|
||||||
case "lyrics":
|
case "lyrics":
|
||||||
self.player.lyrics = response.data;
|
self.player.lyrics = response.data;
|
||||||
self.$forceUpdate()
|
self.$forceUpdate()
|
||||||
break;
|
break;
|
||||||
case "searchResultsLibrary":
|
case "searchResultsLibrary":
|
||||||
self.search.results = response.data;
|
self.search.results = response.data;
|
||||||
self.search.state = 2;
|
self.search.state = 2;
|
||||||
break;
|
break;
|
||||||
case "searchResults":
|
case "searchResults":
|
||||||
self.search.results = response.data;
|
self.search.results = response.data;
|
||||||
self.search.state = 2;
|
self.search.state = 2;
|
||||||
break;
|
break;
|
||||||
case "playbackStateUpdate":
|
case "playbackStateUpdate":
|
||||||
if (!self.player.userInteraction) {
|
if (!self.player.userInteraction) {
|
||||||
self.updatePlaybackState(response.data)
|
self.updatePlaybackState(response.data)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// console.log(e.data);
|
// console.log(e.data);
|
||||||
|
@ -597,10 +596,10 @@ function xmlToJson(xml) {
|
||||||
for (var i = 0; i < xml.childNodes.length; i++) {
|
for (var i = 0; i < xml.childNodes.length; i++) {
|
||||||
var item = xml.childNodes.item(i);
|
var item = xml.childNodes.item(i);
|
||||||
var nodeName = item.nodeName;
|
var nodeName = item.nodeName;
|
||||||
if (typeof (obj[nodeName]) == "undefined") {
|
if (typeof(obj[nodeName]) == "undefined") {
|
||||||
obj[nodeName] = xmlToJson(item);
|
obj[nodeName] = xmlToJson(item);
|
||||||
} else {
|
} else {
|
||||||
if (typeof (obj[nodeName].push) == "undefined") {
|
if (typeof(obj[nodeName].push) == "undefined") {
|
||||||
var old = obj[nodeName];
|
var old = obj[nodeName];
|
||||||
obj[nodeName] = [];
|
obj[nodeName] = [];
|
||||||
obj[nodeName].push(old);
|
obj[nodeName].push(old);
|
||||||
|
@ -612,7 +611,7 @@ function xmlToJson(xml) {
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onresize = function () {
|
window.onresize = function() {
|
||||||
app.resetPlayerUI()
|
app.resetPlayerUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,15 @@
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"name": "AME Remote",
|
"name": "Cider Remote",
|
||||||
"short_name": "AME Remote",
|
"short_name": "Cider Remote",
|
||||||
"description": "Apple Music Electron Remote",
|
"description": "Cider Remote",
|
||||||
"icons": [
|
"developer": {
|
||||||
{
|
"name": "Cider Collective",
|
||||||
|
"url": "https://cider.sh?utm-source=manifest"
|
||||||
|
},
|
||||||
|
"homepage_url": "https://cider.sh?utm-source=manifest",
|
||||||
|
"icons": [{
|
||||||
"src": "/icon-192x192.png",
|
"src": "/icon-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
|
@ -28,5 +32,11 @@
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"protocol_handlers": [{
|
||||||
|
"protocol": "ext+cider",
|
||||||
|
"name": "Cider",
|
||||||
|
"uriTemplate": "/?url=%s"
|
||||||
|
}]
|
||||||
|
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue