Merge branch 'upcoming' of https://github.com/CiderApp/Cider into upcoming

This commit is contained in:
Core 2022-01-18 15:29:00 +00:00
commit a51dc79a94
No known key found for this signature in database
GPG key ID: 1B77805746C47C28
23 changed files with 1835 additions and 1078 deletions

View file

@ -6,7 +6,7 @@
"description": "A new look into listening and enjoying music in style and performance.",
"license": "MIT",
"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",
"bugs": {
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
@ -16,7 +16,10 @@
"scripts": {
"build": "tsc",
"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",
"dist": "npm run build && electron-builder",
"msft": "electron-builder -c msft-package.json",
@ -39,9 +42,10 @@
"qrcode-terminal": "^0.12.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"run-script-os": "^1.1.6",
"source-map-support": "^0.5.21",
"v8-compile-cache": "^2.3.0",
"ws": "^8.4.0",
"ws": "^8.4.2",
"xml2js": "^0.4.23",
"youtube-search-without-api-key": "^1.0.7"
},
@ -78,9 +82,9 @@
}
],
"build": {
"electronVersion": "16.0.6",
"electronVersion": "16.0.7",
"electronDownload": {
"version": "16.0.6+wvcus",
"version": "16.0.7+wvcus",
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
},
"appId": "cider",

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import * as path from "path";
import * as electron from "electron";
import * as windowStateKeeper from "electron-window-state";
@ -6,6 +7,9 @@ import * as getPort from "get-port";
import * as yt from "youtube-search-without-api-key";
import * as fs from "fs";
import { Stream } from "stream";
import * as qrcode from "qrcode-terminal";
import * as os from "os";
import {wsapi} from "./wsapi";
export class Win {
win: any | undefined = null;
@ -24,17 +28,21 @@ export class Win {
ciderCache: path.resolve(electron.app.getPath("userData"), "CiderCache"),
themes: path.resolve(electron.app.getPath("userData"), "Themes"),
plugins: path.resolve(electron.app.getPath("userData"), "Plugins"),
}
};
private audioStream: any = new Stream.PassThrough();
private clientPort: number = 0;
private remotePort: number = 6942;
private EnvironmentVariables: object = {
"env": {
env: {
platform: process.platform,
dev: electron.app.isPackaged
}
dev: electron.app.isPackaged,
},
};
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,
height: 600,
x: undefined,
@ -43,8 +51,8 @@ export class Win {
minHeight: 410,
frame: false,
title: "Cider",
vibrancy: 'dark',
transparent: (process.platform === "darwin"),
vibrancy: "dark",
transparent: process.platform === "darwin",
hasShadow: false,
webPreferences: {
nodeIntegration: true,
@ -57,8 +65,8 @@ export class Win {
nodeIntegrationInWorker: false,
webSecurity: false,
preload: path.join(this.paths.srcPath, './preload/cider-preload.js')
}
preload: path.join(this.paths.srcPath, "./preload/cider-preload.js"),
},
};
/**
@ -71,13 +79,15 @@ export class Win {
// Load the previous state with fallback to defaults
const windowState = windowStateKeeper({
defaultWidth: 1024,
defaultHeight: 600
defaultHeight: 600,
});
this.options.width = windowState.width;
this.options.height = windowState.height;
// 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);
@ -88,7 +98,6 @@ export class Win {
// Register listeners on Window to track size and position of the Window.
windowState.manage(this.win);
return this.win;
}
@ -96,25 +105,29 @@ export class Win {
* Verifies the files for the renderer to use (Cache, library info, etc.)
*/
private verifyFiles(): void {
const expectedDirectories = [
"CiderCache"
]
const expectedDirectories = ["CiderCache"];
const expectedFiles = [
"library-songs.json",
"library-artists.json",
"library-albums.json",
"library-playlists.json",
"library-recentlyAdded.json",
]
];
for (let i = 0; i < expectedDirectories.length; i++) {
if (!fs.existsSync(path.join(electron.app.getPath("userData"), expectedDirectories[i]))) {
fs.mkdirSync(path.join(electron.app.getPath("userData"), expectedDirectories[i]))
if (
!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++) {
const file = path.join(this.paths.ciderCache, expectedFiles[i])
const file = path.join(this.paths.ciderCache, expectedFiles[i]);
if (!fs.existsSync(file)) {
fs.writeFileSync(file, JSON.stringify([]))
fs.writeFileSync(file, JSON.stringify([]));
}
}
}
@ -125,22 +138,30 @@ export class Win {
private startWebServer(): void {
const app = express();
app.use(express.static(path.join(this.paths.srcPath, './renderer/')));
app.set("views", path.join(this.paths.srcPath, './renderer/views'));
app.use(express.static(path.join(this.paths.srcPath, "./renderer/")));
app.set("views", path.join(this.paths.srcPath, "./renderer/views"));
app.set("view engine", "ejs");
let firstRequest = true;
app.use((req, res, next) => {
// @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();
} else {
res.redirect("https://discord.gg/applemusic");
}
});
app.get('/', (req, res) => {
res.render("main", this.EnvironmentVariables)
app.get("/", (req, res) => {
res.render("main", this.EnvironmentVariables);
});
app.get('/audio.webm', (req, res) => {
app.get("/audio.webm", (req, res) => {
try {
req.socket.setTimeout(Number.MAX_SAFE_INTEGER);
// CiderBase.requests.push({req: req, res: res});
@ -150,21 +171,45 @@ export class Win {
// requests.splice(pos, 1);
// console.info("CLOSED", CiderBase.requests.length);
// });
this.audioStream.on('data', (data: any) => {
this.audioStream.on("data", (data: any) => {
try {
res.write(data);
} catch (ex) {
console.log(ex)
}
})
} catch (ex) {
console.log(ex)
console.log(ex);
}
});
} catch (ex) {
console.log(ex);
}
});
//app.use(express.static())
app.listen(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,135 +219,187 @@ 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
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")) {
callback({
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`
})
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`,
});
} else {
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(
async (
details: { url: string; requestHeaders: { [x: string]: string } },
callback: (arg0: { requestHeaders: any }) => void
) => {
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
details.requestHeaders['sec-fetch-site'] = 'same-site';
details.requestHeaders['DNT'] = '1';
let itspod = await this.win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.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}`
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) {
this.win.loadURL(location)
this.win.loadURL(location);
} else {
this.win.loadURL(location, {userAgent: 'Cider Development Environment'})
this.win.loadURL(location, {
userAgent: "Cider Development Environment",
});
}
}
/**
* Initializes the window handlers
*/
private startHandlers(): void {
/**********************************************************************************************************************
* ipcMain Events
****************************************************************************************************************** */
electron.ipcMain.on("cider-platform", (event) => {
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)
event.returnValue = process.platform;
});
electron.ipcMain.on('maximize', () => { // listen for maximize event
if (this.win.isMaximized()) {
this.win.unmaximize()
} else {
this.win.maximize()
}
})
electron.ipcMain.on("get-gpu-mode", (event) => {
event.returnValue = process.platform;
});
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();
})
});
// Set scale
electron.ipcMain.on('setScreenScale', (event, scale) => {
this.win.webContents.setZoomFactor(parseFloat(scale))
electron.ipcMain.on("setScreenScale", (event, 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`
})
/* *********************************************************************************************
@ -314,41 +411,42 @@ export class Win {
MINIMIZED: 0,
NORMAL: 1,
MAXIMIZED: 2,
FULL_SCREEN: 3
}
let wndState = WND_STATE.NORMAL
FULL_SCREEN: 3,
};
let wndState = WND_STATE.NORMAL;
this.win.on("resize", (_: any) => {
const isMaximized = this.win.isMaximized()
const isMinimized = this.win.isMinimized()
const isFullScreen = this.win.isFullScreen()
const isMaximized = this.win.isMaximized();
const isMinimized = this.win.isMinimized();
const isFullScreen = this.win.isFullScreen();
const state = wndState;
if (isMinimized && state !== WND_STATE.MINIMIZED) {
wndState = WND_STATE.MINIMIZED
wndState = WND_STATE.MINIMIZED;
} 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) {
wndState = WND_STATE.MAXIMIZED
this.win.webContents.executeJavaScript(`app.chrome.maximized = true`)
wndState = WND_STATE.MAXIMIZED;
this.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
} else if (state !== WND_STATE.NORMAL) {
wndState = WND_STATE.NORMAL
this.win.webContents.executeJavaScript(`app.chrome.maximized = false`)
wndState = WND_STATE.NORMAL;
this.win.webContents.executeJavaScript(
`app.chrome.maximized = false`
);
}
})
});
}
this.win.on("closed", () => {
this.win = null
})
this.win = null;
});
// Set window Handler
this.win.webContents.setWindowOpenHandler((x: any) => {
if (x.url.includes("apple") || x.url.includes("localhost")) {
return {action: "allow"}
return { action: "allow" };
}
electron.shell.openExternal(x.url).catch(console.error)
return {action: 'deny'}
})
electron.shell.openExternal(x.url).catch(console.error);
return { action: "deny" };
});
}
}

284
src/main/base/wsapi.ts Normal file
View 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));
});
}
}

View file

@ -11,7 +11,7 @@ import {AppEvents} from "./base/app";
import PluginHandler from "./base/plugins";
// const test = new PluginHandler();
let win = null;
let win: Promise<void> | null = null;
const config = new ConfigStore();
const App = new AppEvents(config.store);
const Cider = new Win(electron.app, config.store)
@ -30,8 +30,12 @@ electron.app.on('ready', () => {
require('vue-devtools').install()
}
electron.components.whenReady().then(() => {
win = Cider.createWindow();
plug.callPlugins('onReady', win);
})
});
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -52,23 +56,27 @@ electron.app.on('before-quit', () => {
console.warn(`${electron.app.getName()} exited.`);
});
//
// // @ts-ignore
// // Widevine Stuff
// app.on('widevine-ready', (version, lastVersion) => {
// electron.app.on('widevine-ready', (version, lastVersion) => {
// if (null !== lastVersion) {
// console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
// } else {
// 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 + '!')
// })
//
// app.on('widevine-error', (error) => {
// // @ts-ignore
// electron.app.on('widevine-error', (error) => {
// console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
// app.exit()
// electron.app.exit()
// })
//
//
// app.on('open-url', (event, url) => {

View 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 {}
}

View file

@ -0,0 +1,5 @@
{
"js": {
"beautify.ignore": "src/renderer/index.js"
}
}

View 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
}
}
}

View file

@ -249,6 +249,11 @@ const app = new Vue({
start: 0,
end: 0
},
v3: {
requestBody: {
platform: "web"
}
},
tmpVar: [],
notification: false,
chrome: {
@ -378,8 +383,7 @@ const app = new Vue({
let data = await response.text();
return data;
},
getSocialBadges(cb = () => {
}) {
getSocialBadges(cb = () => {}) {
let self = this
try {
app.mk.api.v3.music("/v1/social/badging-map").then(data => {
@ -464,9 +468,7 @@ const app = new Vue({
}
this.modals.addToPlaylist = false
await app.mk.api.v3.music(
`/v1/me/library/playlists/${playlist_id}/tracks`,
{},
{
`/v1/me/library/playlists/${playlist_id}/tracks`, {}, {
fetchOptions: {
method: "POST",
body: JSON.stringify({
@ -486,6 +488,7 @@ const app = new Vue({
this.mk = MusicKit.getInstance()
this.mk.authorize().then(() => {
self.mkIsReady = true
//document.location.reload()
})
this.$forceUpdate()
if (this.isDev) {
@ -561,7 +564,9 @@ const app = new Vue({
lastItem = JSON.parse(lastItem)
let kind = lastItem.attributes.playParams.kind;
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.setQueue({[truekind]: [lastItem.attributes.playParams.id]})
app.mk.setQueue({
[truekind]: [lastItem.attributes.playParams.id]
})
app.mk.mute()
setTimeout(() => {
app.mk.play().then(() => {
@ -580,8 +585,7 @@ const app = new Vue({
if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) {
try {
app.mk.playLater({ songs: [id] })
} catch (err) {
}
} catch (err) {}
}
i++;
}
@ -611,10 +615,16 @@ const app = new Vue({
}
})
this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, ()=>{
ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
})
this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => {
self.lyriccurrenttime = self.mk.currentPlaybackTime
this.currentSongInfo = a
self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime)
// wsapi
ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
})
this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => {
@ -630,8 +640,7 @@ const app = new Vue({
let previewURL = null
try {
previewURL = app.mk.nowPlayingItem.previewURL
} catch (e) {
}
} catch (e) {}
if (!previewURL) {
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/songs/${app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id}`).then((response) => {
previewURL = response.data.data[0].attributes.previews[0].url
@ -643,14 +652,12 @@ const app = new Vue({
ipcRenderer.send('getPreviewURL', previewURL)
}
} catch (e) {
}
} catch (e) {}
}
try {
a = a.item.attributes;
} catch (_) {
}
} catch (_) {}
let type = (self.mk.nowPlayingItem != null) ? self.mk.nowPlayingItem["type"] ?? '' : '';
if (type.includes("musicVideo") || type.includes("uploadedVideo") || type.includes("music-movie")) {
@ -777,8 +784,7 @@ const app = new Vue({
},
playlistHeaderContextMenu(event) {
let menu = {
items: [
{
items: [{
name: "New Playlist",
action: () => {
this.newPlaylist()
@ -797,9 +803,7 @@ const app = new Vue({
async editPlaylistFolder(id, name = "New Playlist") {
let self = this
this.mk.api.v3.music(
`/v1/me/library/playlist-folders/${id}`,
{},
{
`/v1/me/library/playlist-folders/${id}`, {}, {
fetchOptions: {
method: "PATCH",
body: JSON.stringify({
@ -814,9 +818,7 @@ const app = new Vue({
async editPlaylist(id, name = "New Playlist") {
let self = this
this.mk.api.v3.music(
`/v1/me/library/playlists/${id}`,
{},
{
`/v1/me/library/playlists/${id}`, {}, {
fetchOptions: {
method: "PATCH",
body: JSON.stringify({
@ -839,23 +841,17 @@ const app = new Vue({
if (tracks.length > 0) {
request.tracks = tracks
}
app.mk.api.v3.music(`/v1/me/library/playlists`, {},
{
app.mk.api.v3.music(`/v1/me/library/playlists`, {}, {
fetchOptions: {
method: "POST",
body: JSON.stringify(
{
"attributes":
{ "name": name },
"relationships":
{ "tracks":
{ "data": tracks },
body: JSON.stringify({
"attributes": { "name": name },
"relationships": {
"tracks": { "data": tracks },
}
})
}
)
}
}
).then(res => {
}).then(res => {
res = res.data.data[0]
console.log(res)
self.appRoute(`playlist_` + res.id);
@ -877,13 +873,11 @@ const app = new Vue({
deletePlaylist(id) {
let self = this
if (confirm(`Are you sure you want to delete this playlist?`)) {
app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, {},
{
app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, {}, {
fetchOptions: {
method: "DELETE"
}
}
).then(res => {
}).then(res => {
// remove this playlist from playlists.listing if it exists
let found = self.playlists.listing.find(item => item.id == id)
if (found) {
@ -892,15 +886,18 @@ const app = new Vue({
})
}
},
async showCollection(response, title, type) {
async showCollection(response, title, type, requestBody = {}) {
let self = this
console.log(response)
this.collectionList.requestBody = {}
this.collectionList.response = response
this.collectionList.title = title
this.collectionList.type = type
this.collectionList.requestBody = requestBody
app.appRoute("collection-list")
},
async showArtistView(artist, title, view) {
let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}`)).data
let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}`,{}, {includeResponseMeta: !0})).data
console.log(response)
await this.showCollection(response, title, "artists")
},
@ -909,7 +906,8 @@ const app = new Vue({
await this.showCollection(response, title, "record-labels")
},
async showSearchView(term, group, title) {
let response = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${term}`, {
let requestBody = {
platform: "web",
groups: group,
types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels",
@ -935,14 +933,18 @@ const app = new Vue({
resource: ["autos"]
},
groups: group
}
let response = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${term}`, requestBody , {
includeResponseMeta: !0
})
console.log('searchres', response)
let responseFormat = {
data: response.data.results[group].data,
next: response.data.results[group].data,
next: response.data.results[group].next,
groups: group
}
await this.showCollection(responseFormat, title, "search")
await this.showCollection(responseFormat, title, "search", requestBody)
},
async getPlaylistContinuous(response, transient = false) {
response = response.data.data[0]
@ -1076,7 +1078,8 @@ const app = new Vue({
return `${mins}:${seconds.replace("0.", "")}`
},
hashCode(str) {
let hash = 0, i, chr;
let hash = 0,
i, chr;
if (str.length === 0) return hash;
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
@ -1112,8 +1115,7 @@ const app = new Vue({
},
routeView(item) {
let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? ''));
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));
;
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));;
let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false;
console.log(kind, id, isLibrary)
@ -1151,7 +1153,10 @@ const app = new Vue({
window.location.hash = `${kind}/${id}`
document.querySelector("#app-content").scrollTop = 0
} else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) {
let params = {extend: "editorialVideo"}
let params = {
extend: "offers,editorialVideo",
"views": "appears-on,more-by-artist,related-videos,other-versions,you-might-also-like,video-extras,audio-extras",
}
app.page = (kind) + "_" + (id);
app.getTypeFromID((kind), (id), (isLibrary), params);
window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}`
@ -1195,8 +1200,7 @@ const app = new Vue({
artistId = artistId.substring(artistId.lastIndexOf('ids=') + 4, artistId.lastIndexOf('-'))
}
}
} catch (_) {
}
} catch (_) {}
if (artistId == "") {
let artistQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.artistName}`, {
@ -1208,8 +1212,7 @@ const app = new Vue({
artistId = artistQuery.artists.data[0].id;
console.log(artistId)
}
} catch (e) {
}
} catch (e) {}
}
console.log(artistId);
if (artistId != "")
@ -1230,8 +1233,7 @@ const app = new Vue({
albumId = albumId.substring(0, albumId.indexOf("?i="))
}
}
} catch (_) {
}
} catch (_) {}
if (albumId == "") {
try {
@ -1243,8 +1245,7 @@ const app = new Vue({
albumId = albumQuery.albums.data[0].id;
console.log(albumId)
}
} catch (e) {
}
} catch (e) {}
}
if (albumId != "") {
self.appRoute(`album/${albumId}`)
@ -1254,8 +1255,7 @@ const app = new Vue({
let labelId = '';
try {
labelId = item.relationships['record-labels'].data[0].id
} catch (_) {
}
} catch (_) {}
if (labelId == "") {
try {
@ -1267,8 +1267,7 @@ const app = new Vue({
labelId = labelQuery["record-labels"].data[0].id;
console.log(labelId)
}
} catch (e) {
}
} catch (e) {}
}
if (labelId != "") {
app.showingPlaylist = []
@ -1289,8 +1288,7 @@ const app = new Vue({
},
playMediaItem(item) {
let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? ''));
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));
;
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));;
let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false;
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
console.log(kind, id, isLibrary)
@ -1398,8 +1396,7 @@ const app = new Vue({
} else {
this.getPlaylistContinuous(a)
}
}
;
};
},
searchLibrarySongs() {
let self = this
@ -1906,8 +1903,7 @@ const app = new Vue({
return
}
try {
this.listennow = (await this.mk.api.v3.music(`v1/me/recommendations?timezone=${encodeURIComponent(this.formatTimezoneOffset())}`,
{
this.listennow = (await this.mk.api.v3.music(`v1/me/recommendations?timezone=${encodeURIComponent(this.formatTimezoneOffset())}`, {
name: "listen-now",
with: "friendsMix,library,social",
"art[social-profiles:url]": "c",
@ -1929,8 +1925,7 @@ const app = new Vue({
"meta[stations]": "inflectionPoints",
types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-upsells",
platform: "web"
},
{
}, {
includeResponseMeta: !0,
reload: !0
})).data;
@ -1968,8 +1963,7 @@ const app = new Vue({
return
}
try {
this.radio.personal = (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`,
{
this.radio.personal = (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`, {
"platform": "web",
"art[url]": "f"
})).data.data;
@ -1993,9 +1987,7 @@ const app = new Vue({
newPlaylistFolder(name = "New Folder") {
let self = this
this.mk.api.v3.music(
"/v1/me/library/playlist-folders/",
{},
{
"/v1/me/library/playlist-folders/", {}, {
fetchOptions: {
method: "POST",
body: JSON.stringify({
@ -2058,11 +2050,11 @@ const app = new Vue({
removeFromLibrary(kind, id) {
let self = this
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
{
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`, {}, {
fetchOptions: {
method: "DELETE"
}}).then((data) => {
}
}).then((data) => {
self.getLibrarySongsFull(true)
})
},
@ -2212,8 +2204,7 @@ const app = new Vue({
lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"];
richsync = JSON.parse(lrcrich);
app.richlyrics = richsync;
} catch (_) {
}
} catch (_) {}
}
if (lrcfile == "") {
@ -2442,7 +2433,9 @@ const app = new Vue({
MusicKit.getInstance().play()
});
} else {
this.mk.setQueue({[truekind]: [id]}).then(function (queue) {
this.mk.setQueue({
[truekind]: [id]
}).then(function(queue) {
MusicKit.getInstance().play()
})
}
@ -2487,7 +2480,9 @@ const app = new Vue({
if (childIndex != -1) {
app.mk.changeToMediaAtIndex(childIndex)
} else if (item) {
app.mk.playNext({[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id}).then(function () {
app.mk.playNext({
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id
}).then(function() {
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1)
app.mk.play()
})
@ -2499,7 +2494,9 @@ const app = new Vue({
} else {
app.mk.stop().then(() => {
if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) {
app.mk.setQueue({[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id}).then(function () {
app.mk.setQueue({
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id
}).then(function() {
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function() {
if ((app.showingPlaylist && app.showingPlaylist.id == id)) {
let query = app.showingPlaylist.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
@ -2522,14 +2519,18 @@ const app = new Vue({
})
} else {
this.mk.setQueue({[truekind]: [id]}).then(function (queue) {
this.mk.setQueue({
[truekind]: [id]
}).then(function(queue) {
if (item && ((queue._itemIDs[childIndex] != item.id))) {
childIndex = queue._itemIDs.indexOf(item.id)
}
if (childIndex != -1) {
app.mk.changeToMediaAtIndex(childIndex)
} else if (item) {
app.mk.playNext({[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id}).then(function () {
app.mk.playNext({
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id
}).then(function() {
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1)
app.mk.play()
})
@ -2544,8 +2545,7 @@ const app = new Vue({
console.log(err)
try {
app.mk.stop()
} catch (e) {
}
} catch (e) {}
this.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)
}
@ -2603,8 +2603,7 @@ const app = new Vue({
return
}
//this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}`
this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}`,
{
this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}`, {
types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels",
"relate[editorial-items]": "contents",
"include[editorial-items]": "contents",
@ -2656,18 +2655,23 @@ const app = new Vue({
types[index].id.push(id)
}
}
types2 = types.map(function(item){return {[`ids[${item.type}]`]: [item.id]}})
types2 = types.map(function(item) {
return {
[`ids[${item.type}]`]: [item.id]
}
})
types2 = types2.reduce(function(result, item) {
var key = Object.keys(item)[0]; //first property: a, b, c
result[key] = item[key];
return result;
}, {});
return (await
this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}`, {...{
return (await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}`, {... {
"omit[resource]": "autos",
relate: "library",
fields: "inLibrary"
},...types2})).data.data
},
...types2
})).data.data
},
isInLibrary(playParams) {
let self = this
@ -2759,24 +2763,21 @@ const app = new Vue({
try {
clearInterval(bginterval);
} catch (err) {
}
} catch (err) {}
} else {
this.setLibraryArtBG()
}
} else if (this.mk.nowPlayingItem["id"] == this.currentTrackID) {
try {
clearInterval(bginterval);
} catch (err) {
}
} catch (err) {}
}
} catch (e) {
if (this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] && document.querySelector('.bg-artwork')) {
this.setLibraryArtBG()
try {
clearInterval(bginterval);
} catch (err) {
}
} catch (err) {}
}
}
}, 200)
@ -2829,8 +2830,7 @@ const app = new Vue({
this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50);
try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) {
}
} catch (e) {}
} else {
let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`);
data = data.data.data[0];
@ -2838,14 +2838,12 @@ const app = new Vue({
this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50);
try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) {
}
} catch (e) {}
} else {
this.currentArtUrl = '';
try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) {
}
} catch (e) {}
}
}
} catch (e) {
@ -2863,8 +2861,7 @@ const app = new Vue({
} else {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("")`);
}
} catch (e) {
}
} catch (e) {}
},
async setLibraryArtBG() {
if (typeof this.mk.nowPlayingItem === "undefined") return;
@ -2880,8 +2877,7 @@ const app = new Vue({
self.$store.commit("setLCDArtwork", img)
})
}
} catch (e) {
}
} catch (e) {}
},
quickPlay(query) {
@ -2904,7 +2900,7 @@ const app = new Vue({
}
id = item.id
}
let response = await this.mk.api.v3.music(`/v1/me/ratings/${type}?platform=web&ids=${type.includes('library') ? item.id : id}}`)
let response = await this.mk.api.v3.music(`/v1/me/ratings/${type}?platform=web&ids=${type.includes('library') ? item.id : id}`)
if (response.data.data.length != 0) {
let value = response.data.data[0].attributes.value
return value
@ -2922,17 +2918,14 @@ const app = new Vue({
id = item.id
}
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
fetchOptions:
{
fetchOptions: {
method: "PUT",
body: JSON.stringify(
{
body: JSON.stringify({
"type": "rating",
"attributes": {
"value": 1
}
}
)
})
}
})
},
@ -2946,17 +2939,14 @@ const app = new Vue({
id = item.id
}
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
fetchOptions:
{
fetchOptions: {
method: "PUT",
body: JSON.stringify(
{
body: JSON.stringify({
"type": "rating",
"attributes": {
"value": -1
}
}
)
})
}
})
},
@ -2970,8 +2960,7 @@ const app = new Vue({
id = item.id
}
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
fetchOptions:
{
fetchOptions: {
method: "DELETE",
}
})
@ -3062,8 +3051,7 @@ const app = new Vue({
items: []
},
normal: {
headerItems: [
{
headerItems: [{
"icon": "./assets/feather/heart.svg",
"id": "love",
"name": "Love",
@ -3104,11 +3092,9 @@ const app = new Vue({
}
},
],
items: [
{
items: [{
"icon": "./assets/feather/list.svg",
"name": "Add to Playlist...",
"hidden": true,
"action": function() {
app.promptAddToPlaylist()
}
@ -3241,9 +3227,9 @@ const app = new Vue({
return n
}
const s = e.getTimezoneOffset()
, n = Math.floor(Math.abs(s) / 60)
, d = Math.round(Math.abs(s) % 60);
const s = e.getTimezoneOffset(),
n = Math.floor(Math.abs(s) / 60),
d = Math.round(Math.abs(s) % 60);
let h = "+";
return 0 !== s && (h = s > 0 ? "-" : "+"),
`${h}${leadingZeros(n, 2)}:${leadingZeros(d, 2)}`
@ -3495,8 +3481,7 @@ var checkIfScrollIsStatic = setInterval(() => {
// do something
}
position = document.getElementsByClassName('lyric-body')[0].scrollTop
} catch (e) {
}
} catch (e) {}
}, 50);
@ -3514,5 +3499,3 @@ webGPU().then()
let screenWidth = screen.width;
let screenHeight = screen.height;

View file

@ -18,6 +18,7 @@
--navbarHeight: 48px;
--selected: rgb(130 130 130 / 30%);
--selected-click: rgb(80 80 80 / 30%);
--hover: rgb(200 200 200 / 10%);
--keyColor: #fa586a;
--keyColor-rgb: 250, 88, 106;
--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] {
transition: opacity .25s var(--appleEase);
@ -1102,6 +1129,34 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
justify-content: center;
align-items: center;
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 {
@ -1843,6 +1898,36 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
/* 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 {
.teamBtn {
display: flex;
@ -1898,6 +1983,14 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
&.md-btn-block {
display: block;
width:100%;
}
&.md-btn-glyph {
display:flex;
align-items: center;
justify-content: center;
width: 100%;
}
&.md-btn-primary {
@ -2345,17 +2438,70 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
.playlist-page {
--bgColor: transparent;
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;
padding-top: var(--navigationBarHeight);
.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 {
padding: var(--contentInnerPadding);
min-height: 300px;
position: relative;
.artworkContainer {
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;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
>.row {
width: calc(100% - 32px);
}
.playlist-info {
flex-shrink: unset;
@ -2456,6 +2602,9 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
}
}
}
.friends-info {
display: flex;
flex-flow: column;
@ -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 {
font-size: 0.9em;
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 {
padding-bottom: 128px;
@ -2555,8 +2692,21 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
padding: 0px;
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 {
background: linear-gradient(45deg, var(--keyColor), #0e0e0e);
//background: linear-gradient(45deg, var(--keyColor), #0e0e0e);
color: white;
display: flex;
align-items: center;
@ -2564,26 +2714,36 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
min-height: 400px;
position: relative;
.artist-more {
border-radius: 100%;
background: var(--keyColor);
box-shadow: var(--ciderShadow-Generic);
width: 36px;
height: 36px;
.header-content {
z-index: 1;
}
.artworkContainer {
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;
bottom: 26px;
right: 32px;
border: 0px;
cursor: pointer;
z-index: 5;
&:hover {
background: var(--keyColor-rollover);
}
&:active {
background: var(--keyColor-pressed);
}
}
.animated {
@ -2650,20 +2810,17 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
}
}
.artist-title {
.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);
&:hover {
background: var(--keyColor-rollover);
@ -2673,6 +2830,12 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
background: var(--keyColor-pressed);
}
}
.artist-title {
.artist-play {
transform: translateY(3px);
margin: 14px;
}
&.artist-animation-on {
width: 100%;
@ -2689,7 +2852,8 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
}
.artist-body {
padding: var(--contentInnerPadding);
padding: 0px var(--contentInnerPadding) 0px var(--contentInnerPadding);
margin-top: -48px;
}
.showmoreless {

View 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>

View file

@ -1,6 +1,7 @@
<script type="text/x-template" id="mediaitem-scroller-horizontal">
<template>
<div class="cd-hmedia-scroller" :class="kind">
<slot></slot>
<mediaitem-square :kind="kind" :item="item"
v-for="item in items"></mediaitem-square>
</div>
@ -13,7 +14,7 @@
props: {
'items': {
type: Array,
required: true
required: false
},
'kind': {
type: String,

View file

@ -113,11 +113,11 @@
</div>
</div>
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
<div class="actions"
v-if="isInLibrary(mk.nowPlayingItem['attributes']['playParams'])">
❤️
<div class="actions">
<button class="lcdMenu" @click="nowPlayingContextMenu">
<div class="svg-icon"></div>
</button>
</div>
<div class="actions" v-else>🖤</div>
</template>
</div>
@ -659,6 +659,8 @@
</button>
</script>
<!-- Artwork Material -->
<%- include('components/artwork-material') %>
<!-- Menu Panel -->
<%- include('components/menu-panel') %>
<!-- Playlist Listing -->
@ -710,5 +712,6 @@
<script src="index.js?v=1"></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="/WSAPI_Interop.js"></script>
</body>
</html>

View file

@ -1,5 +1,30 @@
<script type="text/x-template" id="cider-artist-feed">
<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 class="row">
<div class="col">
@ -30,6 +55,7 @@
app: this.$root,
followedArtists: this.$root.cfg.home.followedArtists,
artistFeed: [],
artists: []
}
},
async mounted() {
@ -37,11 +63,26 @@
await this.getArtistFeed()
},
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() {
let artists = this.followedArtists
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 => {
self.artists.push(item)
if (item.views["latest-release"].data.length != 0) {
self.artistFeed.push(item.views["latest-release"].data[0])
}

View file

@ -1,11 +1,12 @@
<script type="text/x-template" id="cider-artist">
<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
:priority="true"
v-if="data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)"
:video="data.attributes.editorialVideo.motionArtistWide16x9.video ?? (data.attributes.editorialVideo.motionArtistFullscreen16x9.video ?? '')">
</animatedartwork-view>
<div class="header-content">
<div class="row">
<div class="col-sm" style="width: auto;">
<div class="artist-image" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
@ -29,15 +30,31 @@
<h1>{{ data.attributes.name }}</h1>
</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 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 class="artist-body">
<div class="row well">
<div class="col">
@ -129,10 +146,14 @@
data: function () {
return {
topSongsExpanded: false,
app: this.$root
app: this.$root,
headerVisible: true
}
},
methods: {
isHeaderVisible(visible) {
this.headerVisible = visible
},
artistMenu (event) {
let self = this
let followAction = "follow"

View file

@ -7,14 +7,17 @@
</div>
</template>
<template v-if="app.playlists.loadingState == 1">
<div class="playlist-display row"
<div class="playlist-display"
:style="{
background: (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) : ''
'--bgColor': (data.attributes.artwork != null && data.attributes.artwork['bgColor'] != null) ? ('#' + data.attributes.artwork.bgColor) : '',
'--textColor': (data.attributes.artwork != null && data.attributes.artwork['textColor1'] != null) ? ('#' + data.attributes.artwork.textColor1) : ''
}">
<div class="playlistInfo">
<div class="row">
<div class="col-auto flex-center">
<div style="width: 260px;height:260px;">
<mediaitem-artwork
shadow="large"
:video-priority="true"
: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 : ''):'')"
: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 : '')) : '' "
@ -58,7 +61,7 @@
</button>
</div>
</template>
<div class="playlist-controls">
<div class="playlist-controls" v-observe-visibility="{callback: isHeaderVisible}">
<button class="md-btn" style="min-width: 120px;"
@click="app.mk.shuffleMode = 0; play()">
Play
@ -75,13 +78,47 @@
@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") %>
<button class="more-btn-round" style="float:right;" @click="menu">
<div class="svg-icon"></div>
</button>
</div>
</div>
</div>
</div>
<div class="artworkContainer" v-if="data.attributes.artwork != null">
<artwork-material :url="data.attributes.artwork.url" size="260" 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">
<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>
</div>
</div>
<div class="col-auto flex-center">
<button class="more-btn-round" style="float:right;" @click="menu">
<div class="svg-icon"></div>
</button>
</div>
</div>
@ -118,6 +155,20 @@
style="width: 50%;">
{{data.attributes.copyright}}
</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>
</template>
</div>
@ -138,7 +189,8 @@
confirm: false,
app: this.$root,
itemBadges: [],
badgesRequested: false
badgesRequested: false,
headerVisible: true
}
},
mounted: function () {
@ -153,6 +205,9 @@
}
},
methods: {
isHeaderVisible(visible) {
this.headerVisible = visible
},
getBadges() {
return
if (this.badgesRequested) {

View file

@ -14,13 +14,16 @@
<mediaitem-square v-else :item="item" :type="getKind(item)"></mediaitem-square>
</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>
<transition name="fabfade">
<button class="top-fab" v-show="showFab" @click="scrollToTop()">
<%- include("../svg/arrow-up.svg") %>
</button>
</transition>
<div class="well" v-show="loading"><div class="spinner"></div></div>
</div>
</script>
<script>
@ -47,7 +50,8 @@
canSeeTrigger: false,
showFab: false,
commonKind: "song",
api: this.$root.mk.api
api: this.$root.mk.api,
loading: false
}
},
methods: {
@ -71,60 +75,35 @@
})
},
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
switch(this.type) {
default:
case "artists":
if (this.data.next && this.triggerEnabled) {
let self = this
this.triggerEnabled = false;
if (typeof this.data.next == "undefined") {
return
}
this.loading = true
let nextFn = (data => {
console.log(data);
this.data.next = data.next;
this.data.data = this.data.data.concat(data.data);
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;
});
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));
}
this.loading = false
}else{
console.log("No next page");
this.triggerEnabled = false;
if(!response.data.results[app.collectionList.response.groups]) {
this.loading = false
return
}
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);
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;
});
}else{
console.log("No next page");
this.triggerEnabled = false;
this.loading = 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;
}
})
},
headerVisibility: function (isVisible, entry) {
if (isVisible) {

View file

@ -145,7 +145,7 @@
async getArtistFeed() {
let artists = this.followedArtists
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 => {
if (item.views["latest-release"].data.length != 0) {
self.artistFeed.push(item.views["latest-release"].data[0])

View file

@ -5,5 +5,8 @@
{{ $store.state.test }}
<div class="spinner"></div>
<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>
</template>

View file

@ -52,8 +52,7 @@
<div class="md-container md-container_panel player-panel" v-if="screen == 'player'">
<div class="player_top">
<div class="md-body player-artwork-container">
<div class="media-artwork" :class="artworkPlaying()"
:style="{'--artwork': getAlbumArtUrl()}">
<div class="media-artwork" :class="artworkPlaying()" :style="{'--artwork': getAlbumArtUrl()}">
</div>
</div>
@ -101,8 +100,7 @@
</template>
</div>
<div class="md-footer">
<button class="md-btn playback-button--small lyrics active"
@click="player.lowerPanelState = 'controls'"></button>
<button class="md-btn playback-button--small lyrics active" @click="player.lowerPanelState = 'controls'"></button>
</div>
</div>
<div class="player_bottom" v-if="player.lowerPanelState == 'controls'">
@ -122,14 +120,10 @@
</div>
</div>
<div class="md-footer">
<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)">
<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)">
<div class="row nopadding player-duration-container" style="width: 90%;margin: 0 auto;">
<div class="col nopadding player-duration-time" style="text-align:left">
{{ parseTime(player.currentMediaItem.durationInMillis -
player.currentMediaItem.remainingTime) }}
{{ parseTime(player.currentMediaItem.durationInMillis - player.currentMediaItem.remainingTime) }}
</div>
<div class="col nopadding player-duration-time" style="text-align:right">
-{{ parseTime(player.currentMediaItem.remainingTime) }}
@ -137,21 +131,15 @@
</div>
</div>
<div class="md-footer playback-buttons">
<button class="md-btn playback-button--small repeat" @click="repeat()"
v-if="player.currentMediaItem.repeatMode == 0"></button>
<button class="md-btn playback-button--small repeat active" @click="repeat()"
v-else-if="player.currentMediaItem.repeatMode == 2"></button>
<button class="md-btn playback-button--small repeat repeatOne" @click="repeat()"
v-else-if="player.currentMediaItem.repeatMode == 1"></button>
<button class="md-btn playback-button--small repeat" @click="repeat()" v-if="player.currentMediaItem.repeatMode == 0"></button>
<button class="md-btn playback-button--small repeat active" @click="repeat()" v-else-if="player.currentMediaItem.repeatMode == 2"></button>
<button class="md-btn playback-button--small repeat repeatOne" @click="repeat()" v-else-if="player.currentMediaItem.repeatMode == 1"></button>
<button class="md-btn playback-button previous" @click="previous()"></button>
<button class="md-btn playback-button pause" @click="pause()"
v-if="player.currentMediaItem.status"></button>
<button class="md-btn playback-button pause" @click="pause()" v-if="player.currentMediaItem.status"></button>
<button class="md-btn playback-button play" @click="play()" v-else></button>
<button class="md-btn playback-button next" @click="next()"></button>
<button class="md-btn playback-button--small shuffle" @click="shuffle()"
v-if="player.currentMediaItem.shuffleMode == 0"></button>
<button class="md-btn playback-button--small shuffle active" @click="shuffle()"
v-else></button>
<button class="md-btn playback-button--small shuffle" @click="shuffle()" v-if="player.currentMediaItem.shuffleMode == 0"></button>
<button class="md-btn playback-button--small shuffle active" @click="shuffle()" v-else></button>
</div>
<div class="md-footer">
<div class="row volume-slider-container">
@ -159,8 +147,7 @@
<div class="player-volume-glyph decrease"></div>
</div>
<div class="col">
<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 type="range" class="web-slider volume-slider" max="1" min="0" step="0.01" @input="setVolume($event.target.value)" :value="player.currentMediaItem.volume">
</div>
<div class="col-auto">
<div class="player-volume-glyph increase"></div>
@ -170,10 +157,8 @@
</div>
<div class="md-footer">
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'"
@click="showLyrics()"></button>
<button class="md-btn playback-button--small lyrics"
v-if="checkOrientation() == 'landscape'" @click="showLyricsInline()"></button>
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'" @click="showLyrics()"></button>
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'landscape'" @click="showLyricsInline()"></button>
<button class="md-btn playback-button--small queue" @click="showQueue()"></button>
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
</div>
@ -191,43 +176,34 @@
</div>
<div class="col" style="display: flex;align-items: center;">
<div class="col">
<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">
<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">
</div>
</div>
</div>
</div>
<div class="md-header search-type-container">
<button class="search-type-button" @click="search.searchType = 'applemusic';searchQuery()"
:class="searchTypeClass('applemusic')" style="width:100%;">Apple Music
<button class="search-type-button" @click="search.searchType = 'applemusic';searchQuery()" :class="searchTypeClass('applemusic')" style="width:100%;">Apple Music
</button>
<button class="search-type-button" @click="search.searchType = 'library';searchQuery()"
:class="searchTypeClass('library')" style="width:100%;">Library
<button class="search-type-button" @click="search.searchType = 'library';searchQuery()" :class="searchTypeClass('library')" style="width:100%;">Library
</button>
</div>
<div class="md-header search-tab-container" v-if="search.state == 2">
<button class="search-tab" @click="search.tab = 'all'" :class="searchTabClass('all')">All
Results
</button>
<button class="search-tab" @click="search.tab = 'songs'"
:class="searchTabClass('songs')">Songs
<button class="search-tab" @click="search.tab = 'songs'" :class="searchTabClass('songs')">Songs
</button>
<button class="search-tab" @click="search.tab = 'albums'"
:class="searchTabClass('albums')">Albums
<button class="search-tab" @click="search.tab = 'albums'" :class="searchTabClass('albums')">Albums
</button>
<button class="search-tab" @click="search.tab = 'artists'"
:class="searchTabClass('artists')">Artists
<button class="search-tab" @click="search.tab = 'artists'" :class="searchTabClass('artists')">Artists
</button>
</div>
</div>
<div class="search-body-container">
<transition name="wpfade">
<div class="md-body search-body" v-if="search.state == 0">
<div
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.
<div 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.
</div>
</div>
</transition>
@ -237,10 +213,7 @@
</div>
</transition>
<transition name="wpfade">
<div class="md-body search-body"
ref="searchBody"
@scroll="searchScroll"
style="overflow-y:auto;" v-if="search.state == 2">
<div class="md-body search-body" ref="searchBody" @scroll="searchScroll" style="overflow-y:auto;" v-if="search.state == 2">
<template v-if="canShowSearchTab('songs')">
<div class="list-entry-header">Songs</div>
@ -377,11 +350,9 @@
</transition>
<!-- Track Select Actions -->
<transition name="wpfade">
<div class="md-container md-container_panel context-menu" style="overflow-y:auto;"
v-if="search.trackSelect">
<div class="md-container md-container_panel context-menu" style="overflow-y:auto;" v-if="search.trackSelect">
<div class="md-body context-menu-body">
<button class="context-menu-item context-menu-item--left"
@click="playMediaItemById(search.selected.id);clearSelectedTrack()">
<button class="context-menu-item context-menu-item--left" @click="playMediaItemById(search.selected.id);clearSelectedTrack()">
<div class="row">
<div class="col-auto flex-center" v-if="search.selected.artwork"
style="display:flex;align-items: center;">
@ -404,8 +375,7 @@
</button>
</div>
<div class="md-body context-menu-body" style="height: auto;">
<button class="context-menu-item context-menu-item--left"
@click="playMediaItemById(search.selected.id);clearSelectedTrack()">
<button class="context-menu-item context-menu-item--left" @click="playMediaItemById(search.selected.id);clearSelectedTrack()">
<div class="row">
<div class="col">
Play
@ -415,8 +385,7 @@
</div>
</div>
</button>
<button class="context-menu-item context-menu-item--left"
@click="playNext('song', search.selected.id);clearSelectedTrack()">
<button class="context-menu-item context-menu-item--left" @click="playNext('song', search.selected.id);clearSelectedTrack()">
<div class="row">
<div class="col">
Play Next
@ -426,8 +395,7 @@
</div>
</div>
</button>
<button class="context-menu-item context-menu-item--left"
@click="playLater('song', search.selected.id);clearSelectedTrack()">
<button class="context-menu-item context-menu-item--left" @click="playLater('song', search.selected.id);clearSelectedTrack()">
<div class="row">
<div class="col">
Play Later
@ -520,24 +488,20 @@
</div>
</div>
<div class="album-body-container" :style="getMediaPalette(artistPage.data)">
<div class="artist-header" v-if="artistPage.data['artwork']"
:style="getMediaPalette(artistPage.data)">
<div class="artist-header-portrait"
:style="{'--artwork': getAlbumArtUrlList(artistPage.data['artwork']['url'], 600)}"></div>
<div class="artist-header" v-if="artistPage.data['artwork']" :style="getMediaPalette(artistPage.data)">
<div class="artist-header-portrait" :style="{'--artwork': getAlbumArtUrlList(artistPage.data['artwork']['url'], 600)}"></div>
<h2>{{ artistPage.data["name"] }}</h2>
</div>
<div class="md-body artist-body">
<h2>Songs</h2>
<div class="song-scroller-horizontal">
<button v-for="song in artistPage.data['songs']" class="song-placeholder"
@click="trackSelect(song)">
<button v-for="song in artistPage.data['songs']" class="song-placeholder" @click="trackSelect(song)">
{{ song.name }}
</button>
</div>
<h2>Albums</h2>
<div class="mediaitem-scroller-horizontal">
<button v-for="album in artistPage.data['albums']" class="album-placeholder"
@click="showAlbum(album.id)">
<button v-for="album in artistPage.data['albums']" class="album-placeholder" @click="showAlbum(album.id)">
{{ album.name }}
</button>
</div>
@ -574,30 +538,15 @@
</div>
</div>
<div class="md-header" style="text-align: right;padding: 5px 16px;">
<button
class="md-btn playback-button--small autoplay"
v-if="!player.currentMediaItem.autoplayEnabled"
@click="setAutoplay(true)"
></button>
<button
class="md-btn playback-button--small autoplay activeColor"
v-else
@click="setAutoplay(false)"
></button>
<button class="md-btn playback-button--small autoplay" 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 class="md-body queue-body" v-if="!player.queue['_queueItems']">
Empty
</div>
<div class="md-body queue-body" style="overflow-y:auto;" id="list-queue" v-else>
<draggable
v-model="queue.temp"
handle=".handle"
filter=".passed"
@change="queueMove">
<template
v-for="(song, position) in queue.temp"
v-if="position > player.queue['_position']"
>
<draggable v-model="queue.temp" 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="row" style="width:100%;">
<div class="col-auto">
@ -626,11 +575,8 @@
</draggable>
</div>
<div class="md-footer">
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'"
@click="showLyrics()"></button>
<button class="md-btn playback-button--small lyrics"
v-if="checkOrientation() == 'landscape'"
@click="screen = 'player';showLyricsInline()"></button>
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'" @click="showLyrics()"></button>
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'landscape'" @click="screen = 'player';showLyricsInline()"></button>
<button class="md-btn playback-button--small queue active" @click="screen = 'player'"></button>
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
</div>
@ -689,9 +635,7 @@
</transition>
<!-- Album Page -->
<transition name="wpfade">
<div class="md-container md-container_panel md-container_album"
v-if="screen == 'album-page' && albumPage.data['name']"
>
<div class="md-container md-container_panel md-container_album" v-if="screen == 'album-page' && albumPage.data['name']">
<div class="md-header">
<div class="row">
<div class="col-auto">
@ -701,8 +645,7 @@
</div>
<div class="album-body-container">
<div class="md-header">
<div class="albumpage-artwork"
:style="{'--artwork': getAlbumArtUrlList(albumPage.data['artwork']['url'], 300)}">
<div class="albumpage-artwork" :style="{'--artwork': getAlbumArtUrlList(albumPage.data['artwork']['url'], 300)}">
</div>
<div class="albumpage-album-name">
{{ albumPage.data["name"] }}
@ -711,20 +654,15 @@
{{ albumPage.data["artistName"] }}
</div>
<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 class="row" style="margin-top: 20px;">
<div class="col">
<button class="wr-btn"
@click="playAlbum(albumPage.data.id, false)"
style="width:100%;">Play
<button class="wr-btn" @click="playAlbum(albumPage.data.id, false)" style="width:100%;">Play
</button>
</div>
<div class="col">
<button class="wr-btn" style="width:100%;"
@click="playAlbum(albumPage.data.id, true)"
>Shuffle
<button class="wr-btn" style="width:100%;" @click="playAlbum(albumPage.data.id, true)">Shuffle
</button>
</div>
</div>
@ -737,12 +675,10 @@
</div>
<div class="md-body artist-body">
<div class="list-entry-header">Tracks</div>
<div class="list-entry" v-for="song in albumPage.data['tracks']"
@click="trackSelect(song)">
<div class="list-entry" v-for="song in albumPage.data['tracks']" @click="trackSelect(song)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="song.artwork"
:style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
<div class="list-entry-image" v-if="song.artwork" :style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
</div>
</div>
<div class="col flex-center">
@ -768,23 +704,13 @@
</transition>
<!-- Album Page - Editorial Notes -->
<transition name="wpfade">
<div class="md-container md-container_panel context-menu" v-if="albumPage.editorsNotes"
style="padding-top: 42px;">
<div class="md-header"
:style="getMediaPalette(albumPage.data)"
style="font-size: 18px;background:var(--bgColor);color:var(--textColor1);text-align: center;border-radius: 10px 10px 0 0;border-top: 1px solid #ffffff1f;"
>
<div class="md-container md-container_panel context-menu" v-if="albumPage.editorsNotes" style="padding-top: 42px;">
<div class="md-header" :style="getMediaPalette(albumPage.data)" style="font-size: 18px;background:var(--bgColor);color:var(--textColor1);text-align: center;border-radius: 10px 10px 0 0;border-top: 1px solid #ffffff1f;">
{{ albumPage.data["name"] }}
</div>
<div class="md-body album-page-fullnotes-body"
:style="getMediaPalette(albumPage.data)"
style="background:var(--bgColor);color:var(--textColor1);"
v-html="albumPage.data['editorialNotes']['standard']">
<div class="md-body album-page-fullnotes-body" :style="getMediaPalette(albumPage.data)" style="background:var(--bgColor);color:var(--textColor1);" v-html="albumPage.data['editorialNotes']['standard']">
</div>
<div class="md-footer"
:style="getMediaPalette(albumPage.data)"
style="background:var(--bgColor);color:var(--textColor1);"
>
<div class="md-footer" :style="getMediaPalette(albumPage.data)" style="background:var(--bgColor);color:var(--textColor1);">
<button class="context-menu-item" @click="albumPage.editorsNotes = false">Close</button>
</div>
</div>
@ -802,9 +728,8 @@
</div>
<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;" @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>
</div>
</div>
@ -845,15 +770,14 @@
</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 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>
<script src="./index.js?v=1"></script>
</body>

View file

@ -71,16 +71,16 @@ var app = new Vue({
},
musicAppVariant() {
if (navigator.userAgent.match(/Android/i)) {
return "Apple Music";
return "Cider";
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
return "Music";
return "Cider";
} else {
if (navigator.userAgent.indexOf('Mac') > 0) {
return 'Music';
} else if (navigator.userAgent.indexOf('Win') > 0) {
return 'Apple Music Electron';
return 'Cider';
} else {
return 'Apple Music Electron';
return 'Cider';
}
}
},
@ -465,8 +465,7 @@ var app = new Vue({
},
setMode(mode) {
switch (mode) {
default:
this.screen = "player"
default: this.screen = "player"
break;
case "miniplayer":
this.screen = "miniplayer"
@ -505,10 +504,10 @@ var app = new Vue({
}
socket.onmessage = (e) => {
console.log(e.data)
const response = JSON.parse(e.data);
switch (response.type) {
default:
console.log(response);
default: console.log(response);
break;
case "musickitapi.search":
self.showArtist(response.data["artists"][0]["id"]);

View file

@ -4,11 +4,15 @@
"display": "standalone",
"scope": "/",
"start_url": "/",
"name": "AME Remote",
"short_name": "AME Remote",
"description": "Apple Music Electron Remote",
"icons": [
{
"name": "Cider Remote",
"short_name": "Cider Remote",
"description": "Cider Remote",
"developer": {
"name": "Cider Collective",
"url": "https://cider.sh?utm-source=manifest"
},
"homepage_url": "https://cider.sh?utm-source=manifest",
"icons": [{
"src": "/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
@ -28,5 +32,11 @@
"sizes": "512x512",
"type": "image/png"
}
]
],
"protocol_handlers": [{
"protocol": "ext+cider",
"name": "Cider",
"uriTemplate": "/?url=%s"
}]
}