This commit is contained in:
N0chteil 2022-01-07 17:40:12 +01:00
parent 1f507900b5
commit fc87a2fd6e
39 changed files with 4500 additions and 3275 deletions

View file

@ -18,12 +18,14 @@
</p> </p>
#### Links #### Links
* [Wiki](https://github.com/ciderapp/Cider/wiki)
* [Request Feature](https://github.com/ciderapp/Cider/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=%5BEnhancement%5D) - [Wiki](https://github.com/ciderapp/Cider/wiki)
* [Report Bug](https://github.com/ciderapp/Cider/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBUG%5D+) - [Request Feature](https://github.com/ciderapp/Cider/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=%5BEnhancement%5D)
* [**View The Releases**](https://github.com/ciderapp/Cider/releases/latest) - [Report Bug](https://github.com/ciderapp/Cider/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBUG%5D+)
- [**View The Releases**](https://github.com/ciderapp/Cider/releases/latest)
### Install Sources ### Install Sources
[![Get it from Github](https://img.shields.io/badge/Get_It_From_GitHub-100000?style=for-the-badge&logo=github&logoColor=white)](https://github.com/ciderapp/cider/releases/latest) [![Get it from Github](https://img.shields.io/badge/Get_It_From_GitHub-100000?style=for-the-badge&logo=github&logoColor=white)](https://github.com/ciderapp/cider/releases/latest)
[![Get it from the Microsoft Store](https://img.shields.io/badge/Get_It_From_The_Microsoft_Store-100000?style=for-the-badge&logo=microsoft)](https://www.microsoft.com/store/apps/9P21XJ9D9G66) [![Get it from the Microsoft Store](https://img.shields.io/badge/Get_It_From_The_Microsoft_Store-100000?style=for-the-badge&logo=microsoft)](https://www.microsoft.com/store/apps/9P21XJ9D9G66)
@ -34,17 +36,21 @@
[![Get it from the Snap Store](https://img.shields.io/badge/Get_It_From_The_Snap_Store-100000?style=for-the-badge&logo=snapcraft)](https://snapcraft.io/apple-music-electron) [![Get it from the Snap Store](https://img.shields.io/badge/Get_It_From_The_Snap_Store-100000?style=for-the-badge&logo=snapcraft)](https://snapcraft.io/apple-music-electron)
--> -->
[![Get it from the AUR](https://img.shields.io/badge/Get_It_From_The_AUR-100000?style=for-the-badge&logo=archlinux)](https://aur.archlinux.org/packages/cider) [![Get it from the AUR](https://img.shields.io/badge/Get_It_From_The_AUR-100000?style=for-the-badge&logo=archlinux)](https://aur.archlinux.org/packages/cider)
### Compiling and Configuration ### Compiling and Configuration
For more information surrounding configuration, compiling and other developer documentation, see the [compilation docs](https://cider.sh/docs/compile). For more information surrounding configuration, compiling and other developer documentation, see the [compilation docs](https://cider.sh/docs/compile).
### Credits ### Credits
![Contributors](https://contrib.rocks/image?repo=ciderapp/Cider) ![Contributors](https://contrib.rocks/image?repo=ciderapp/Cider)
### Disclaimer ### Disclaimer
*This project is NOT affiliated with Apple in any way shape or form. The project is open source and free to use (with an Apple Music subscription)
for any legal concerns contact me at <a href="mailto:cryptofyre@cryptofyre.org">cryptofyre@cryptofyre.org</a>.* _This project is NOT affiliated with Apple in any way shape or form. The project is open source and free to use (with an Apple Music subscription)
for any legal concerns contact me at <a href="mailto:cryptofyre@cryptofyre.org">cryptofyre@cryptofyre.org</a>._
<p align="center"> <p align="center">
<br> <br>

View file

@ -1,9 +1,9 @@
const { BrowserWindow, ipcMain, shell, app, screen } = require("electron") const { BrowserWindow, ipcMain, shell, app, screen } = require('electron');
const { join } = require("path") const { join } = require('path');
const getPort = require("get-port"); const getPort = require('get-port');
const express = require("express"); const express = require('express');
const path = require("path"); const path = require('path');
const windowStateKeeper = require("electron-window-state"); const windowStateKeeper = require('electron-window-state');
const os = require('os'); const os = require('os');
const yt = require('youtube-search-without-api-key'); const yt = require('youtube-search-without-api-key');
const discord = require('./discordrpc'); const discord = require('./discordrpc');
@ -16,8 +16,10 @@ const fetch = require('electron-fetch').default;
const { Stream } = require('stream'); const { Stream } = require('stream');
// Analytics for debugging. // Analytics for debugging.
const ElectronSentry = require("@sentry/electron"); const ElectronSentry = require('@sentry/electron');
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" }); ElectronSentry.init({
dsn: 'https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214'
});
const CiderBase = { const CiderBase = {
win: null, win: null,
@ -25,18 +27,18 @@ const CiderBase = {
audiostream: new Stream.PassThrough(), audiostream: new Stream.PassThrough(),
async Start() { async Start() {
this.clientPort = await getPort({ port: 9000 }); this.clientPort = await getPort({ port: 9000 });
this.win = this.CreateBrowserWindow() this.win = this.CreateBrowserWindow();
}, },
clientPort: 0, clientPort: 0,
CreateBrowserWindow() { CreateBrowserWindow() {
this.VerifyFiles() this.VerifyFiles();
// Set default window sizes // Set default window sizes
const mainWindowState = windowStateKeeper({ const mainWindowState = windowStateKeeper({
defaultWidth: 1024, defaultWidth: 1024,
defaultHeight: 600 defaultHeight: 600
}); });
let win = null let win = null;
const options = { const options = {
icon: join(__dirname, `../../resources/icons/icon.ico`), icon: join(__dirname, `../../resources/icons/icon.ico`),
width: mainWindowState.width, width: mainWindowState.width,
@ -46,7 +48,7 @@ const CiderBase = {
minWidth: 844, minWidth: 844,
minHeight: 410, minHeight: 410,
frame: false, frame: false,
title: "Cider", title: 'Cider',
vibrancy: 'dark', vibrancy: 'dark',
// transparent: true, // transparent: true,
hasShadow: false, hasShadow: false,
@ -63,135 +65,172 @@ const CiderBase = {
contextIsolation: false, contextIsolation: false,
preload: join(__dirname, '../preload/cider-preload.js') preload: join(__dirname, '../preload/cider-preload.js')
} }
} };
CiderBase.InitWebServer() CiderBase.InitWebServer();
// Create the BrowserWindow // Create the BrowserWindow
if (process.platform === "darwin" || process.platform === "linux") { if (process.platform === 'darwin' || process.platform === 'linux') {
win = new BrowserWindow(options) win = new BrowserWindow(options);
} else { } else {
// i don't know why but we have to do this for acrylic to work properly // i don't know why but we have to do this for acrylic to work properly
if (app.cfg.get("visual.window_transparency") !== "disabled") { if (app.cfg.get('visual.window_transparency') !== 'disabled') {
const { BrowserWindow } = require("electron-acrylic-window"); const { BrowserWindow } = require('electron-acrylic-window');
win = new BrowserWindow(options) win = new BrowserWindow(options);
win.setVibrancy("dark") win.setVibrancy('dark');
} else { } else {
win = new BrowserWindow(options) win = new BrowserWindow(options);
win.setVibrancy("dark") win.setVibrancy('dark');
} }
} }
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead // intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
win.webContents.session.webRequest.onBeforeRequest( win.webContents.session.webRequest.onBeforeRequest(
{ {
urls: ["https://*/*.js"] urls: ['https://*/*.js']
}, },
(details, callback) => { (details, callback) => {
if (details.url.includes("hls.js")) { if (details.url.includes('hls.js')) {
callback({ callback({
redirectURL: `http://localhost:${CiderBase.clientPort}/apple-hls.js` redirectURL: `http://localhost:${CiderBase.clientPort}/apple-hls.js`
}) });
} else { } else {
callback({ callback({
cancel: false cancel: false
}) });
} }
} }
) );
win.webContents.session.webRequest.onBeforeSendHeaders(async (details, callback) => { win.webContents.session.webRequest.onBeforeSendHeaders(
if (details.url === "https://buy.itunes.apple.com/account/web/info") { async (details, callback) => {
if (
details.url ===
'https://buy.itunes.apple.com/account/web/info'
) {
details.requestHeaders['sec-fetch-site'] = 'same-site'; details.requestHeaders['sec-fetch-site'] = 'same-site';
details.requestHeaders['DNT'] = '1'; details.requestHeaders['DNT'] = '1';
let itspod = await win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`) let itspod = await win.webContents.executeJavaScript(
`window.localStorage.getItem("music.ampwebplay.itspod")`
);
if (itspod != null) 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:${CiderBase.clientPort}/` let location = `http://localhost:${CiderBase.clientPort}/`;
win.loadURL(location) win.loadURL(location);
win.on("closed", () => { win.on('closed', () => {
win = null win = null;
}) });
// Register listeners on Window to track size and position of the Window. // Register listeners on Window to track size and position of the Window.
mainWindowState.manage(win); mainWindowState.manage(win);
// IPC stuff (senders) // IPC stuff (senders)
ipcMain.on("cider-platform", (event) => { ipcMain.on('cider-platform', (event) => {
event.returnValue = process.platform event.returnValue = process.platform;
}) });
ipcMain.on("get-gpu-mode", (event) => { ipcMain.on('get-gpu-mode', (event) => {
event.returnValue = process.platform event.returnValue = process.platform;
}) });
ipcMain.on("is-dev", (event) => { ipcMain.on('is-dev', (event) => {
event.returnValue = !app.isPackaged event.returnValue = !app.isPackaged;
}) });
// IPC stuff (listeners) // IPC stuff (listeners)
ipcMain.on('close', () => { // listen for close event ipcMain.on('close', () => {
// listen for close event
win.close(); win.close();
}) });
ipcMain.on('put-library-songs', (event, arg) => { ipcMain.on('put-library-songs', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-songs.json"), JSON.stringify(arg)) fs.writeFileSync(
}) join(app.paths.ciderCache, 'library-songs.json'),
JSON.stringify(arg)
);
});
ipcMain.on('put-library-artists', (event, arg) => { ipcMain.on('put-library-artists', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-artists.json"), JSON.stringify(arg)) fs.writeFileSync(
}) join(app.paths.ciderCache, 'library-artists.json'),
JSON.stringify(arg)
);
});
ipcMain.on('put-library-albums', (event, arg) => { ipcMain.on('put-library-albums', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-albums.json"), JSON.stringify(arg)) fs.writeFileSync(
}) join(app.paths.ciderCache, 'library-albums.json'),
JSON.stringify(arg)
);
});
ipcMain.on('put-library-playlists', (event, arg) => { ipcMain.on('put-library-playlists', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-playlists.json"), JSON.stringify(arg)) fs.writeFileSync(
}) join(app.paths.ciderCache, 'library-playlists.json'),
JSON.stringify(arg)
);
});
ipcMain.on('put-library-recentlyAdded', (event, arg) => { ipcMain.on('put-library-recentlyAdded', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), JSON.stringify(arg)) fs.writeFileSync(
}) join(app.paths.ciderCache, 'library-recentlyAdded.json'),
JSON.stringify(arg)
);
});
ipcMain.on('get-library-songs', (event) => { ipcMain.on('get-library-songs', (event) => {
let librarySongs = fs.readFileSync(join(app.paths.ciderCache, "library-songs.json"), "utf8") let librarySongs = fs.readFileSync(
event.returnValue = JSON.parse(librarySongs) join(app.paths.ciderCache, 'library-songs.json'),
}) 'utf8'
);
event.returnValue = JSON.parse(librarySongs);
});
ipcMain.on('get-library-artists', (event) => { ipcMain.on('get-library-artists', (event) => {
let libraryArtists = fs.readFileSync(join(app.paths.ciderCache, "library-artists.json"), "utf8") let libraryArtists = fs.readFileSync(
event.returnValue = JSON.parse(libraryArtists) join(app.paths.ciderCache, 'library-artists.json'),
}) 'utf8'
);
event.returnValue = JSON.parse(libraryArtists);
});
ipcMain.on('get-library-albums', (event) => { ipcMain.on('get-library-albums', (event) => {
let libraryAlbums = fs.readFileSync(join(app.paths.ciderCache, "library-albums.json"), "utf8") let libraryAlbums = fs.readFileSync(
event.returnValue = JSON.parse(libraryAlbums) join(app.paths.ciderCache, 'library-albums.json'),
}) 'utf8'
);
event.returnValue = JSON.parse(libraryAlbums);
});
ipcMain.on('get-library-playlists', (event) => { ipcMain.on('get-library-playlists', (event) => {
let libraryPlaylists = fs.readFileSync(join(app.paths.ciderCache, "library-playlists.json"), "utf8") let libraryPlaylists = fs.readFileSync(
event.returnValue = JSON.parse(libraryPlaylists) join(app.paths.ciderCache, 'library-playlists.json'),
}) 'utf8'
);
event.returnValue = JSON.parse(libraryPlaylists);
});
ipcMain.on('get-library-recentlyAdded', (event) => { ipcMain.on('get-library-recentlyAdded', (event) => {
let libraryRecentlyAdded = fs.readFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), "utf8") let libraryRecentlyAdded = fs.readFileSync(
event.returnValue = JSON.parse(libraryRecentlyAdded) join(app.paths.ciderCache, 'library-recentlyAdded.json'),
}) 'utf8'
);
event.returnValue = JSON.parse(libraryRecentlyAdded);
});
ipcMain.handle('getYTLyrics', async (event, track, artist) => { ipcMain.handle('getYTLyrics', async (event, track, artist) => {
var u = track + " " + artist + " official video"; var u = track + ' ' + artist + ' official video';
const videos = await yt.search(u); const videos = await yt.search(u);
return videos return videos;
}) });
ipcMain.handle('getStoreValue', (event, key, defaultValue) => { ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
return (defaultValue ? app.cfg.get(key, true) : app.cfg.get(key)); return defaultValue ? app.cfg.get(key, true) : app.cfg.get(key);
}); });
ipcMain.handle('setStoreValue', (event, key, value) => { ipcMain.handle('setStoreValue', (event, key, value) => {
@ -199,181 +238,209 @@ const CiderBase = {
}); });
ipcMain.on('getStore', (event) => { ipcMain.on('getStore', (event) => {
event.returnValue = app.cfg.store event.returnValue = app.cfg.store;
})
ipcMain.on('setStore', (event, store) => {
app.cfg.store = store
})
ipcMain.handle('setVibrancy', (event, key, value) => {
win.setVibrancy(value)
}); });
ipcMain.on('maximize', () => { // listen for maximize event ipcMain.on('setStore', (event, store) => {
app.cfg.store = store;
});
ipcMain.handle('setVibrancy', (event, key, value) => {
win.setVibrancy(value);
});
ipcMain.on('maximize', () => {
// listen for maximize event
if (win.isMaximized()) { if (win.isMaximized()) {
win.unmaximize() win.unmaximize();
} else { } else {
win.maximize() win.maximize();
} }
}) });
ipcMain.on('minimize', () => { // listen for minimize event ipcMain.on('minimize', () => {
// listen for minimize event
win.minimize(); win.minimize();
}) });
if (process.platform === "win32") { if (process.platform === 'win32') {
let WND_STATE = { let WND_STATE = {
MINIMIZED: 0, MINIMIZED: 0,
NORMAL: 1, NORMAL: 1,
MAXIMIZED: 2, MAXIMIZED: 2,
FULL_SCREEN: 3 FULL_SCREEN: 3
} };
let wndState = WND_STATE.NORMAL let wndState = WND_STATE.NORMAL;
win.on("resize", (_event) => { win.on('resize', (_event) => {
const isMaximized = win.isMaximized() const isMaximized = win.isMaximized();
const isMinimized = win.isMinimized() const isMinimized = win.isMinimized();
const isFullScreen = win.isFullScreen() const isFullScreen = win.isFullScreen();
const state = wndState; const state = wndState;
if (isMinimized && state !== WND_STATE.MINIMIZED) { if (isMinimized && state !== WND_STATE.MINIMIZED) {
wndState = WND_STATE.MINIMIZED wndState = WND_STATE.MINIMIZED;
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) { } else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
wndState = WND_STATE.FULL_SCREEN wndState = WND_STATE.FULL_SCREEN;
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) { } else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
wndState = WND_STATE.MAXIMIZED wndState = WND_STATE.MAXIMIZED;
win.webContents.executeJavaScript(`app.chrome.maximized = true`) win.webContents.executeJavaScript(
`app.chrome.maximized = true`
);
} else if (state !== WND_STATE.NORMAL) { } else if (state !== WND_STATE.NORMAL) {
wndState = WND_STATE.NORMAL wndState = WND_STATE.NORMAL;
win.webContents.executeJavaScript(`app.chrome.maximized = false`) win.webContents.executeJavaScript(
`app.chrome.maximized = false`
);
} }
}) });
} }
// Set window Handler // Set window Handler
win.webContents.setWindowOpenHandler(({ url }) => { win.webContents.setWindowOpenHandler(({ url }) => {
if (url.includes("apple") || url.includes("localhost")) { if (url.includes('apple') || url.includes('localhost')) {
return { action: "allow" } return { action: 'allow' };
} }
shell.openExternal(url).catch(() => { shell.openExternal(url).catch(() => {});
})
return { return {
action: 'deny' action: 'deny'
} };
}) });
// Set scale // Set scale
ipcMain.on('setScreenScale', (event, scale) => { ipcMain.on('setScreenScale', (event, scale) => {
win.webContents.setZoomFactor(parseFloat(scale)) win.webContents.setZoomFactor(parseFloat(scale));
}) });
win.webContents.setZoomFactor(screen.getPrimaryDisplay().scaleFactor) win.webContents.setZoomFactor(screen.getPrimaryDisplay().scaleFactor);
mpris.connect(win) mpris.connect(win);
lastfm.authenticate() lastfm.authenticate();
// Discord // Discord
discord.connect((app.cfg.get("general.discord_rpc") == 1) ? '911790844204437504' : '886578863147192350'); discord.connect(
app.cfg.get('general.discord_rpc') == 1
? '911790844204437504'
: '886578863147192350'
);
ipcMain.on('playbackStateDidChange', (_event, a) => { ipcMain.on('playbackStateDidChange', (_event, a) => {
app.media = a; app.media = a;
discord.updateActivity(a) discord.updateActivity(a);
mpris.updateState(a) mpris.updateState(a);
lastfm.scrobbleSong(a) lastfm.scrobbleSong(a);
lastfm.updateNowPlayingSong(a) lastfm.updateNowPlayingSong(a);
}); });
ipcMain.on('nowPlayingItemDidChange', (_event, a) => { ipcMain.on('nowPlayingItemDidChange', (_event, a) => {
app.media = a; app.media = a;
discord.updateActivity(a) discord.updateActivity(a);
mpris.updateAttributes(a) mpris.updateAttributes(a);
lastfm.scrobbleSong(a) lastfm.scrobbleSong(a);
lastfm.updateNowPlayingSong(a) lastfm.updateNowPlayingSong(a);
}); });
ipcMain.on("getPreviewURL", (_event, url) => { ipcMain.on('getPreviewURL', (_event, url) => {
fetch(url) fetch(url)
.then(res => res.buffer()) .then((res) => res.buffer())
.then(async (buffer) => { .then(async (buffer) => {
try { try {
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a'); const metadata = await mm.parseBuffer(
SoundCheckTag = metadata.native.iTunes[1].value buffer,
win.webContents.send('SoundCheckTag', SoundCheckTag) 'audio/x-m4a'
);
SoundCheckTag = metadata.native.iTunes[1].value;
win.webContents.send('SoundCheckTag', SoundCheckTag);
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message);
} }
}) });
}); });
ipcMain.on('writeAudio', function (event, buffer) { ipcMain.on('writeAudio', function (event, buffer) {
CiderBase.audiostream.write(Buffer.from(buffer)); CiderBase.audiostream.write(Buffer.from(buffer));
}) });
return win return win;
}, },
VerifyFiles() { VerifyFiles() {
const expectedDirectories = [ const expectedDirectories = ['CiderCache'];
"CiderCache"
]
const expectedFiles = [ const expectedFiles = [
"library-songs.json", 'library-songs.json',
"library-artists.json", 'library-artists.json',
"library-albums.json", 'library-albums.json',
"library-playlists.json", 'library-playlists.json',
"library-recentlyAdded.json", 'library-recentlyAdded.json'
] ];
for (let i = 0; i < expectedDirectories.length; i++) { for (let i = 0; i < expectedDirectories.length; i++) {
if (!existsSync(path.join(app.getPath("userData"), expectedDirectories[i]))) { if (
mkdirSync(path.join(app.getPath("userData"), expectedDirectories[i])) !existsSync(
path.join(app.getPath('userData'), expectedDirectories[i])
)
) {
mkdirSync(
path.join(app.getPath('userData'), expectedDirectories[i])
);
} }
} }
for (let i = 0; i < expectedFiles.length; i++) { for (let i = 0; i < expectedFiles.length; i++) {
const file = path.join(app.paths.ciderCache, expectedFiles[i]) const file = path.join(app.paths.ciderCache, expectedFiles[i]);
if (!existsSync(file)) { if (!existsSync(file)) {
writeFileSync(file, JSON.stringify([])) writeFileSync(file, JSON.stringify([]));
} }
} }
}, },
EnvironmentVariables: { EnvironmentVariables: {
"env": { env: {
platform: os.platform(), platform: os.platform(),
dev: app.isPackaged dev: app.isPackaged
} }
}, },
LinkHandler: (startArgs) => { LinkHandler: (startArgs) => {
if (!startArgs) return; if (!startArgs) return;
console.log("lfmtoken", String(startArgs)) console.log('lfmtoken', String(startArgs));
if (String(startArgs).includes('auth')) { if (String(startArgs).includes('auth')) {
let authURI = String(startArgs).split('/auth/')[1] let authURI = String(startArgs).split('/auth/')[1];
if (authURI.startsWith('lastfm')) { // If we wanted more auth options if (authURI.startsWith('lastfm')) {
// If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1]; const authKey = authURI.split('lastfm?token=')[1];
app.cfg.set('lastfm.enabled', true); app.cfg.set('lastfm.enabled', true);
app.cfg.set('lastfm.auth_token', authKey); app.cfg.set('lastfm.auth_token', authKey);
CiderBase.win.webContents.send('LastfmAuthenticated', authKey); CiderBase.win.webContents.send('LastfmAuthenticated', authKey);
lastfm.authenticate() lastfm.authenticate();
} }
} else { } else {
const formattedSongID = startArgs.replace('ame://', '').replace('/', ''); const formattedSongID = startArgs
console.warn(`[LinkHandler] Attempting to load song id: ${formattedSongID}`); .replace('ame://', '')
.replace('/', '');
console.warn(
`[LinkHandler] Attempting to load song id: ${formattedSongID}`
);
// setQueue can be done with album, song, url, playlist id // setQueue can be done with album, song, url, playlist id
this.win.webContents.executeJavaScript(` this.win.webContents
.executeJavaScript(
`
MusicKit.getInstance().setQueue({ song: '${formattedSongID}'}).then(function(queue) { MusicKit.getInstance().setQueue({ song: '${formattedSongID}'}).then(function(queue) {
MusicKit.getInstance().play(); MusicKit.getInstance().play();
}); });
`).catch((err) => console.error(err)); `
)
.catch((err) => console.error(err));
} }
}, },
async InitWebServer() { async InitWebServer() {
const webapp = express(); const webapp = express();
const webRemotePath = path.join(__dirname, '../renderer/'); const webRemotePath = path.join(__dirname, '../renderer/');
webapp.set("views", path.join(webRemotePath, "views")); webapp.set('views', path.join(webRemotePath, 'views'));
webapp.set("view engine", "ejs"); webapp.set('view engine', 'ejs');
webapp.use(function (req, res, next) { webapp.use(function (req, res, next) {
// if not localhost // if not localhost
if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && req.headers["user-agent"].includes("Cider"))) { if (
req.url.includes('audio.webm') ||
(req.headers.host.includes('localhost') &&
req.headers['user-agent'].includes('Cider'))
) {
next(); next();
} }
}); });
@ -381,7 +448,7 @@ const CiderBase = {
webapp.use(express.static(webRemotePath)); webapp.use(express.static(webRemotePath));
webapp.get('/', function (req, res) { webapp.get('/', function (req, res) {
//res.sendFile(path.join(webRemotePath, 'index_old.html')); //res.sendFile(path.join(webRemotePath, 'index_old.html'));
res.render("main", CiderBase.EnvironmentVariables) res.render('main', CiderBase.EnvironmentVariables);
}); });
webapp.get('/audio.webm', function (req, res) { webapp.get('/audio.webm', function (req, res) {
try { try {
@ -397,16 +464,17 @@ const CiderBase = {
try { try {
res.write(data); res.write(data);
} catch (ex) { } catch (ex) {
console.log(ex) console.log(ex);
}
});
} catch (ex) {
console.log(ex);
} }
})
} catch (ex) { console.log(ex) }
}); });
webapp.listen(CiderBase.clientPort, function () { webapp.listen(CiderBase.clientPort, function () {
console.log(`Cider client port: ${CiderBase.clientPort}`); console.log(`Cider client port: ${CiderBase.clientPort}`);
}); });
}, }
};
}
module.exports = CiderBase; module.exports = CiderBase;

View file

@ -1,40 +1,48 @@
const {app} = require('electron'), const { app } = require('electron'),
DiscordRPC = require('discord-rpc') DiscordRPC = require('discord-rpc');
module.exports = { module.exports = {
/** /**
* Connects to Discord RPC * Connects to Discord RPC
* @param {string} clientId * @param {string} clientId
*/ */
connect: function (clientId) { connect: function (clientId) {
app.discord = {isConnected: false}; app.discord = { isConnected: false };
if (app.cfg.get('general.discord_rpc') == 0) return; if (app.cfg.get('general.discord_rpc') == 0) return;
DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc. DiscordRPC.register(clientId); // Apparently needed for ask to join, join, spectate etc.
const client = new DiscordRPC.Client({transport: "ipc"}); const client = new DiscordRPC.Client({ transport: 'ipc' });
app.discord = Object.assign(client, {error: false, activityCache: null, isConnected: false}); app.discord = Object.assign(client, {
error: false,
activityCache: null,
isConnected: false
});
// Login to Discord // Login to Discord
app.discord.login({clientId}) app.discord
.login({ clientId })
.then(() => { .then(() => {
app.discord.isConnected = true; app.discord.isConnected = true;
}) })
.catch((e) => console.error(`[DiscordRPC][connect] ${e}`)); .catch((e) => console.error(`[DiscordRPC][connect] ${e}`));
app.discord.on('ready', () => { app.discord.on('ready', () => {
console.log(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${client.user.username} (${client.user.id})`); console.log(
`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${client.user.username} (${client.user.id})`
);
if (app.discord.activityCache) { if (app.discord.activityCache) {
client.setActivity(app.discord.activityCache).catch((e) => console.error(e)); client
.setActivity(app.discord.activityCache)
.catch((e) => console.error(e));
app.discord.activityCache = null; app.discord.activityCache = null;
} }
}) });
// Handles Errors // Handles Errors
app.discord.on('error', err => { app.discord.on('error', (err) => {
console.error(`[DiscordRPC] ${err}`); console.error(`[DiscordRPC] ${err}`);
this.disconnect() this.disconnect();
app.discord.isConnected = false; app.discord.isConnected = false;
}); });
}, },
@ -43,15 +51,21 @@ module.exports = {
* Disconnects from Discord RPC * Disconnects from Discord RPC
*/ */
disconnect: function () { disconnect: function () {
if (app.cfg.get('general.discord_rpc') == 0 || !app.discord.isConnected) return; if (app.cfg.get('general.discord_rpc') == 0 || !app.discord.isConnected)
return;
try { try {
app.discord.destroy().then(() => { app.discord
.destroy()
.then(() => {
app.discord.isConnected = false; app.discord.isConnected = false;
console.log('[DiscordRPC][disconnect] Disconnected from discord.') console.log(
}).catch((e) => console.error(`[DiscordRPC][disconnect] ${e}`)); '[DiscordRPC][disconnect] Disconnected from discord.'
);
})
.catch((e) => console.error(`[DiscordRPC][disconnect] ${e}`));
} catch (err) { } catch (err) {
console.error(err) console.error(err);
} }
}, },
@ -63,76 +77,87 @@ module.exports = {
if (app.cfg.get('general.discord_rpc') == 0) return; if (app.cfg.get('general.discord_rpc') == 0) return;
if (!app.discord.isConnected) { if (!app.discord.isConnected) {
this.connect() this.connect();
} }
if (!app.discord.isConnected) return; if (!app.discord.isConnected) return;
// console.log('[DiscordRPC][updateActivity] Updating Discord Activity.') // console.log('[DiscordRPC][updateActivity] Updating Discord Activity.')
const listenURL = `https://applemusicelectron.com/p?id=${attributes.playParams.id}` const listenURL = `https://applemusicelectron.com/p?id=${attributes.playParams.id}`;
//console.log(attributes) //console.log(attributes)
let ActivityObject = { let ActivityObject = {
details: attributes.name, details: attributes.name,
state: `by ${attributes.artistName}`, state: `by ${attributes.artistName}`,
startTimestamp: attributes.startTime, startTimestamp: attributes.startTime,
endTimestamp: attributes.endTime, endTimestamp: attributes.endTime,
largeImageKey: (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider', largeImageKey:
attributes.artwork.url
.replace('{w}', '1024')
.replace('{h}', '1024') ?? 'cider',
largeImageText: attributes.albumName, largeImageText: attributes.albumName,
smallImageKey: (attributes.status ? 'play' : 'pause'), smallImageKey: attributes.status ? 'play' : 'pause',
smallImageText: (attributes.status ? 'Playing' : 'Paused'), smallImageText: attributes.status ? 'Playing' : 'Paused',
instance: true, instance: true,
buttons: [ buttons: [{ label: 'Listen on Cider', url: listenURL }]
{label: "Listen on Cider", url: listenURL},
]
}; };
if (ActivityObject.largeImageKey == "" || ActivityObject.largeImageKey == null) { if (
ActivityObject.largeImageKey = (app.cfg.get("general.discord_rpc") == 1) ? "cider" : "logo" ActivityObject.largeImageKey == '' ||
ActivityObject.largeImageKey == null
) {
ActivityObject.largeImageKey =
app.cfg.get('general.discord_rpc') == 1 ? 'cider' : 'logo';
} }
// console.log(`[LinkHandler] Listening URL has been set to: ${listenURL}`); // console.log(`[LinkHandler] Listening URL has been set to: ${listenURL}`);
if (app.cfg.get('general.discordClearActivityOnPause') == 1) { if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
delete ActivityObject.smallImageKey delete ActivityObject.smallImageKey;
delete ActivityObject.smallImageText delete ActivityObject.smallImageText;
} }
// Check all the values work // Check all the values work
if (!((new Date(attributes.endTime)).getTime() > 0)) { if (!(new Date(attributes.endTime).getTime() > 0)) {
delete ActivityObject.startTimestamp delete ActivityObject.startTimestamp;
delete ActivityObject.endTimestamp delete ActivityObject.endTimestamp;
} }
if (!attributes.artistName) { if (!attributes.artistName) {
delete ActivityObject.state delete ActivityObject.state;
} }
if (!ActivityObject.largeImageText || ActivityObject.largeImageText.length < 2) { if (
delete ActivityObject.largeImageText !ActivityObject.largeImageText ||
ActivityObject.largeImageText.length < 2
) {
delete ActivityObject.largeImageText;
} }
if (ActivityObject.details.length > 128) { if (ActivityObject.details.length > 128) {
AcitivityObject.details = ActivityObject.details.substring(0, 125) + '...' AcitivityObject.details =
ActivityObject.details.substring(0, 125) + '...';
} }
// Clear if if needed // Clear if if needed
if (!attributes.status) { if (!attributes.status) {
if (app.cfg.get('general.discordClearActivityOnPause') == 1) { if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][clearActivity] ${e}`)); app.discord
ActivityObject = null .clearActivity()
} else .catch((e) =>
{ console.error(`[DiscordRPC][clearActivity] ${e}`)
delete ActivityObject.startTimestamp );
delete ActivityObject.endTimestamp ActivityObject = null;
ActivityObject.smallImageKey = 'pause' } else {
ActivityObject.smallImageText = 'Paused' delete ActivityObject.startTimestamp;
delete ActivityObject.endTimestamp;
ActivityObject.smallImageKey = 'pause';
ActivityObject.smallImageText = 'Paused';
} }
} }
if (ActivityObject) { if (ActivityObject) {
try { try {
// console.log(`[DiscordRPC][setActivity] Setting activity to ${JSON.stringify(ActivityObject)}`); // console.log(`[DiscordRPC][setActivity] Setting activity to ${JSON.stringify(ActivityObject)}`);
app.discord.setActivity(ActivityObject) app.discord.setActivity(ActivityObject);
} catch (err) { } catch (err) {
console.error(`[DiscordRPC][setActivity] ${err}`) console.error(`[DiscordRPC][setActivity] ${err}`);
} }
} }
}, }
} };

View file

@ -1,16 +1,18 @@
const {app, Notification} = require('electron'), const { app, Notification } = require('electron'),
fs = require('fs'), fs = require('fs'),
{resolve} = require('path'), { resolve } = require('path'),
sessionPath = resolve(app.getPath('userData'), 'session.json'), sessionPath = resolve(app.getPath('userData'), 'session.json'),
apiCredentials = require('../../resources/lfmApiCredentials.json'), apiCredentials = require('../../resources/lfmApiCredentials.json'),
LastfmAPI = require('lastfmapi'); LastfmAPI = require('lastfmapi');
const lfm = { const lfm = {
authenticateFromFile: function () { authenticateFromFile: function () {
let sessionData = require(sessionPath) let sessionData = require(sessionPath);
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.") console.log(
app.lastfm.setSessionCredentials(sessionData.name, sessionData.key) '[LastFM][authenticateFromFile] Logging in with Session Info.'
console.log("[LastFM][authenticateFromFile] Logged in.") );
app.lastfm.setSessionCredentials(sessionData.name, sessionData.key);
console.log('[LastFM][authenticateFromFile] Logged in.');
}, },
authenticate: function () { authenticate: function () {
@ -18,84 +20,125 @@ const lfm = {
app.cfg.set('lastfm.enabled', true); app.cfg.set('lastfm.enabled', true);
} }
if (!app.cfg.get('lastfm.enabled') || !app.cfg.get('lastfm.auth_token')) { if (
!app.cfg.get('lastfm.enabled') ||
!app.cfg.get('lastfm.auth_token')
) {
app.cfg.set('lastfm.enabled', false); app.cfg.set('lastfm.enabled', false);
return return;
} }
const lfmAPI = new LastfmAPI({ const lfmAPI = new LastfmAPI({
'api_key': apiCredentials.key, api_key: apiCredentials.key,
'secret': apiCredentials.secret secret: apiCredentials.secret
}); });
app.lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false}); app.lastfm = Object.assign(lfmAPI, {
cachedAttributes: false,
cachedNowPlayingAttributes: false
});
fs.stat(sessionPath, function (err) { fs.stat(sessionPath, function (err) {
if (err) { if (err) {
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err) console.error(
console.log("[LastFM][Auth] Beginning authentication from configuration") "[LastFM][Session] Session file couldn't be opened or doesn't exist,",
app.lastfm.authenticate(app.cfg.get('lastfm.auth_token'), function (err, session) { err
);
console.log(
'[LastFM][Auth] Beginning authentication from configuration'
);
app.lastfm.authenticate(
app.cfg.get('lastfm.auth_token'),
function (err, session) {
if (err) { if (err) {
throw err; throw err;
} }
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"} console.log(
console.log("[LastFM] Saving session info to disk.") '[LastFM] Successfully obtained LastFM session info,',
let tempData = JSON.stringify(session) session
); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
console.log('[LastFM] Saving session info to disk.');
let tempData = JSON.stringify(session);
fs.writeFile(sessionPath, tempData, (err) => { fs.writeFile(sessionPath, tempData, (err) => {
if (err) if (err) console.log('[LastFM][fs]', err);
console.log("[LastFM][fs]", err)
else { else {
console.log("[LastFM][fs] File was written successfully.") console.log(
lfm.authenticateFromFile() '[LastFM][fs] File was written successfully.'
);
lfm.authenticateFromFile();
new Notification({ new Notification({
title: app.getName(), title: app.getName(),
body: "Successfully logged into LastFM using Authentication Key." body: 'Successfully logged into LastFM using Authentication Key.'
}).show() }).show();
} }
})
}); });
} else {
lfm.authenticateFromFile()
} }
}) );
} else {
lfm.authenticateFromFile();
}
});
}, },
scrobbleSong: async function (attributes) { scrobbleSong: async function (attributes) {
await new Promise(resolve => setTimeout(resolve, app.cfg.get('lastfm.scrobble_after') * 1000)); await new Promise((resolve) =>
setTimeout(resolve, app.cfg.get('lastfm.scrobble_after') * 1000)
);
const currentAttributes = app.media; const currentAttributes = app.media;
if (!app.lastfm || app.lastfm.cachedAttributes === attributes ) { if (!app.lastfm || app.lastfm.cachedAttributes === attributes) {
return return;
} }
if (app.lastfm.cachedAttributes) { if (app.lastfm.cachedAttributes) {
if (app.lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return; if (
app.lastfm.cachedAttributes.playParams.id ===
attributes.playParams.id
)
return;
} }
if (currentAttributes.status && currentAttributes === attributes) { if (currentAttributes.status && currentAttributes === attributes) {
if (fs.existsSync(sessionPath)) { if (fs.existsSync(sessionPath)) {
// Scrobble playing song. // Scrobble playing song.
if (attributes.status === true) { if (attributes.status === true) {
app.lastfm.track.scrobble({ app.lastfm.track.scrobble(
'artist': lfm.filterArtistName(attributes.artistName), {
'track': attributes.name, artist: lfm.filterArtistName(attributes.artistName),
'album': attributes.albumName, track: attributes.name,
'albumArtist': this.filterArtistName(attributes.artistName), album: attributes.albumName,
'timestamp': new Date().getTime() / 1000 albumArtist: this.filterArtistName(
}, function (err, scrobbled) { attributes.artistName
),
timestamp: new Date().getTime() / 1000
},
function (err, scrobbled) {
if (err) { if (err) {
return console.error('[LastFM] An error occurred while scrobbling', err); return console.error(
'[LastFM] An error occurred while scrobbling',
err
);
} }
console.log('[LastFM] Successfully scrobbled: ', scrobbled); console.log(
}); '[LastFM] Successfully scrobbled: ',
app.lastfm.cachedAttributes = attributes scrobbled
);
}
);
app.lastfm.cachedAttributes = attributes;
} }
} else { } else {
this.authenticate(); this.authenticate();
} }
} else { } else {
return console.log('[LastFM] Did not add ', attributes.name , '-' , lfm.filterArtistName(attributes.artistName), 'because now playing a other song.'); return console.log(
'[LastFM] Did not add ',
attributes.name,
'-',
lfm.filterArtistName(attributes.artistName),
'because now playing a other song.'
);
} }
}, },
@ -111,43 +154,61 @@ const lfm = {
} }
artist = artist.join(' '); artist = artist.join(' ');
if (artist.includes(',')) { if (artist.includes(',')) {
artist = artist.split(',') artist = artist.split(',');
artist = artist[0] artist = artist[0];
} }
return artist.charAt(0).toUpperCase() + artist.slice(1); return artist.charAt(0).toUpperCase() + artist.slice(1);
}, },
updateNowPlayingSong: function (attributes) { updateNowPlayingSong: function (attributes) {
if (!app.lastfm ||app.lastfm.cachedNowPlayingAttributes === attributes | !app.cfg.get('lastfm.NowPlaying')) { if (
return !app.lastfm ||
app.lastfm.cachedNowPlayingAttributes === attributes ||
!app.cfg.get('lastfm.NowPlaying')
) {
return;
} }
if (app.lastfm.cachedNowPlayingAttributes) { if (app.lastfm.cachedNowPlayingAttributes) {
if (app.lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return; if (
app.lastfm.cachedNowPlayingAttributes.playParams.id ===
attributes.playParams.id
)
return;
} }
if (fs.existsSync(sessionPath)) { if (fs.existsSync(sessionPath)) {
// update Now Playing // update Now Playing
if (attributes.status === true) { if (attributes.status === true) {
app.lastfm.track.updateNowPlaying({ app.lastfm.track.updateNowPlaying(
'artist': lfm.filterArtistName(attributes.artistName), {
'track': attributes.name, artist: lfm.filterArtistName(attributes.artistName),
'album': attributes.albumName, track: attributes.name,
'albumArtist': this.filterArtistName(attributes.artistName) album: attributes.albumName,
}, function (err, nowPlaying) { albumArtist: this.filterArtistName(
attributes.artistName
)
},
function (err, nowPlaying) {
if (err) { if (err) {
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err); return console.error(
'[LastFM] An error occurred while updating nowPlayingSong',
err
);
} }
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying); console.log(
}); '[LastFM] Successfully updated nowPlayingSong',
app.lastfm.cachedNowPlayingAttributes = attributes nowPlaying
);
}
);
app.lastfm.cachedNowPlayingAttributes = attributes;
} }
} else { } else {
this.authenticate() this.authenticate();
} }
} }
} };
module.exports = lfm; module.exports = lfm;

View file

@ -1,13 +1,12 @@
let mediaPlayer = null; let mediaPlayer = null;
module.exports = { module.exports = {
/** /**
* Connects to the MPRIS interface. * Connects to the MPRIS interface.
* @param {Object} win - The BrowserWindow. * @param {Object} win - The BrowserWindow.
*/ */
connect: (win) => { connect: (win) => {
if (process.platform !== "linux") return; if (process.platform !== 'linux') return;
const Player = require('mpris-service'); const Player = require('mpris-service');
@ -18,36 +17,51 @@ module.exports = {
supportedMimeTypes: [], supportedMimeTypes: [],
supportedInterfaces: ['player'] supportedInterfaces: ['player']
}); });
mediaPlayer = Object.assign(mediaPlayer, { canQuit: true, canControl: true, canPause: true, canPlay: true, canGoNext: true }) mediaPlayer = Object.assign(mediaPlayer, {
canQuit: true,
canControl: true,
canPause: true,
canPlay: true,
canGoNext: true
});
let pos_atr = { durationInMillis: 0 };
let pos_atr = {durationInMillis: 0};
mediaPlayer.getPosition = function () { mediaPlayer.getPosition = function () {
const durationInMicro = pos_atr.durationInMillis * 1000; const durationInMicro = pos_atr.durationInMillis * 1000;
const percentage = parseFloat("0") || 0; const percentage = parseFloat('0') || 0;
return durationInMicro * percentage; return durationInMicro * percentage;
} };
mediaPlayer.active = true mediaPlayer.active = true;
mediaPlayer.on('playpause', async () => { mediaPlayer.on('playpause', async () => {
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err)) win.webContents
.executeJavaScript('MusicKitInterop.pausePlay()')
.catch((err) => console.error(err));
}); });
mediaPlayer.on('play', async () => { mediaPlayer.on('play', async () => {
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err)) win.webContents
.executeJavaScript('MusicKitInterop.pausePlay()')
.catch((err) => console.error(err));
}); });
mediaPlayer.on('pause', async () => { mediaPlayer.on('pause', async () => {
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err)) win.webContents
.executeJavaScript('MusicKitInterop.pausePlay()')
.catch((err) => console.error(err));
}); });
mediaPlayer.on('next', async () => { mediaPlayer.on('next', async () => {
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err)) win.webContents
.executeJavaScript('MusicKitInterop.nextTrack()')
.catch((err) => console.error(err));
}); });
mediaPlayer.on('previous', async () => { mediaPlayer.on('previous', async () => {
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err)) win.webContents
.executeJavaScript('MusicKitInterop.previousTrack()')
.catch((err) => console.error(err));
}); });
}, },
@ -56,23 +70,29 @@ module.exports = {
* @param {Object} attributes - The attributes of the track. * @param {Object} attributes - The attributes of the track.
*/ */
updateAttributes: (attributes) => { updateAttributes: (attributes) => {
if (process.platform !== "linux") return; if (process.platform !== 'linux') return;
const MetaData = { const MetaData = {
'mpris:trackid': mediaPlayer.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`), 'mpris:trackid': mediaPlayer.objectPath(
`track/${attributes.playParams.id.replace(/[.]+/g, '')}`
),
'mpris:length': attributes.durationInMillis * 1000, // In microseconds 'mpris:length': attributes.durationInMillis * 1000, // In microseconds
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'), 'mpris:artUrl': attributes.artwork.url
.replace('/{w}x{h}bb', '/512x512bb')
.replace('/2000x2000bb', '/35x35bb'),
'xesam:title': `${attributes.name}`, 'xesam:title': `${attributes.name}`,
'xesam:album': `${attributes.albumName}`, 'xesam:album': `${attributes.albumName}`,
'xesam:artist': [`${attributes.artistName}`,], 'xesam:artist': [`${attributes.artistName}`],
'xesam:genre': attributes.genreNames 'xesam:genre': attributes.genreNames
};
if (
mediaPlayer.metadata['mpris:trackid'] === MetaData['mpris:trackid']
) {
return;
} }
if (mediaPlayer.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) { mediaPlayer.metadata = MetaData;
return
}
mediaPlayer.metadata = MetaData
}, },
/** /**
@ -80,11 +100,11 @@ module.exports = {
* @param {Object} attributes - The attributes of the track. * @param {Object} attributes - The attributes of the track.
*/ */
updateState: (attributes) => { updateState: (attributes) => {
if (process.platform !== "linux") return; if (process.platform !== 'linux') return;
function setPlaybackIfNeeded(status) { function setPlaybackIfNeeded(status) {
if (mediaPlayer.playbackStatus === status) { if (mediaPlayer.playbackStatus === status) {
return return;
} }
mediaPlayer.playbackStatus = status; mediaPlayer.playbackStatus = status;
} }
@ -96,7 +116,8 @@ module.exports = {
case false: // Paused case false: // Paused
setPlaybackIfNeeded('Paused'); setPlaybackIfNeeded('Paused');
break; break;
default: // Stopped default:
// Stopped
setPlaybackIfNeeded('Stopped'); setPlaybackIfNeeded('Stopped');
break; break;
} }
@ -106,8 +127,10 @@ module.exports = {
* Closes the MPRIS interface. * Closes the MPRIS interface.
*/ */
clearActivity: () => { clearActivity: () => {
if (process.platform !== "linux") return; if (process.platform !== 'linux') return;
mediaPlayer.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'} mediaPlayer.metadata = {
'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'
};
mediaPlayer.playbackStatus = 'Stopped'; mediaPlayer.playbackStatus = 'Stopped';
}, }
} };

View file

@ -1,72 +1,135 @@
const electron = require('electron') const electron = require('electron');
console.log('Loaded Preload') console.log('Loaded Preload');
let cache = {playParams: {id: 0}, status: null, remainingTime: 0}, let cache = { playParams: { id: 0 }, status: null, remainingTime: 0 },
playbackCache = {status: null, time: Date.now()}; playbackCache = { status: null, time: Date.now() };
const MusicKitInterop = { const MusicKitInterop = {
init: function () { init: function () {
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => { MusicKit.getInstance().addEventListener(
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), true, false)) { MusicKit.Events.playbackStateDidChange,
console.log("ayy"); () => {
global.ipcRenderer.send('playbackStateDidChange', MusicKitInterop.getAttributes()) if (
MusicKitInterop.filterTrack(
MusicKitInterop.getAttributes(),
true,
false
)
) {
console.log('ayy');
global.ipcRenderer.send(
'playbackStateDidChange',
MusicKitInterop.getAttributes()
);
// if (typeof _plugins != "undefined") { // if (typeof _plugins != "undefined") {
// _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()}) // _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()})
// } // }
} }
});
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, () => {
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), false, true)) {
global.ipcRenderer.send('nowPlayingItemDidChange', MusicKitInterop.getAttributes());
} }
}); );
MusicKit.getInstance().addEventListener(MusicKit.Events.authorizationStatusDidChange, () => { MusicKit.getInstance().addEventListener(
global.ipcRenderer.send('authorizationStatusDidChange', MusicKit.getInstance().authorizationStatus) MusicKit.Events.nowPlayingItemDidChange,
}) () => {
if (
MusicKitInterop.filterTrack(
MusicKitInterop.getAttributes(),
false,
true
)
) {
global.ipcRenderer.send(
'nowPlayingItemDidChange',
MusicKitInterop.getAttributes()
);
}
}
);
MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => { MusicKit.getInstance().addEventListener(
MusicKit.Events.authorizationStatusDidChange,
() => {
global.ipcRenderer.send(
'authorizationStatusDidChange',
MusicKit.getInstance().authorizationStatus
);
}
);
MusicKit.getInstance().addEventListener(
MusicKit.Events.mediaPlaybackError,
(e) => {
console.warn(`[mediaPlaybackError] ${e}`); console.warn(`[mediaPlaybackError] ${e}`);
}) }
);
}, },
getAttributes: function () { getAttributes: function () {
const nowPlayingItem = MusicKit.getInstance().nowPlayingItem; const nowPlayingItem = MusicKit.getInstance().nowPlayingItem;
const isPlayingExport = MusicKit.getInstance().isPlaying; const isPlayingExport = MusicKit.getInstance().isPlaying;
const remainingTimeExport = MusicKit.getInstance().currentPlaybackTimeRemaining; const remainingTimeExport =
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {}); MusicKit.getInstance().currentPlaybackTimeRemaining;
const attributes =
nowPlayingItem != null ? nowPlayingItem.attributes : {};
attributes.status = isPlayingExport ? isPlayingExport : false; attributes.status = isPlayingExport ? isPlayingExport : false;
attributes.name = attributes.name ? attributes.name : 'No Title Found'; attributes.name = attributes.name ? attributes.name : 'No Title Found';
attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''}; attributes.artwork = attributes.artwork
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : ''; ? attributes.artwork
attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'}; : { url: '' };
attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : 'no-id-found'; 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.albumName = attributes.albumName ? attributes.albumName : '';
attributes.artistName = attributes.artistName ? attributes.artistName : ''; attributes.artistName = attributes.artistName
attributes.genreNames = attributes.genreNames ? attributes.genreNames : []; ? attributes.artistName
attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0; : '';
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0; attributes.genreNames = attributes.genreNames
? attributes.genreNames
: [];
attributes.remainingTime = remainingTimeExport
? remainingTimeExport * 1000
: 0;
attributes.durationInMillis = attributes.durationInMillis
? attributes.durationInMillis
: 0;
attributes.startTime = Date.now(); attributes.startTime = Date.now();
attributes.endTime = Math.round((attributes.playParams.id === cache.playParams.id ? (Date.now() + attributes.remainingTime) : (attributes.startTime + attributes.durationInMillis))); attributes.endTime = Math.round(
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now(); attributes.playParams.id === cache.playParams.id
return attributes ? Date.now() + attributes.remainingTime
: attributes.startTime + attributes.durationInMillis
);
attributes.endTime = attributes.endTime
? attributes.endTime
: Date.now();
return attributes;
}, },
filterTrack: function (a, playbackCheck, mediaCheck) { filterTrack: function (a, playbackCheck, mediaCheck) {
if (a.title === "No Title Found" || a.playParams.id === "no-id-found") { if (a.title === 'No Title Found' || a.playParams.id === 'no-id-found') {
return; return;
} else if (mediaCheck && a.playParams.id === cache.playParams.id) { } else if (mediaCheck && a.playParams.id === cache.playParams.id) {
return; return;
} else if (playbackCheck && a.status === playbackCache.status) { } else if (playbackCheck && a.status === playbackCache.status) {
return; return;
} else if (playbackCheck && !a.status && a.remainingTime === playbackCache.time) { /* Pretty much have to do this to prevent multiple runs when a song starts playing */ } else if (
playbackCheck &&
!a.status &&
a.remainingTime === playbackCache.time
) {
/* Pretty much have to do this to prevent multiple runs when a song starts playing */
return; return;
} }
cache = a; cache = a;
if (playbackCheck) playbackCache = {status: a.status, time: a.remainingTime}; if (playbackCheck)
playbackCache = { status: a.status, time: a.remainingTime };
return true; return true;
}, },
@ -74,23 +137,31 @@ const MusicKitInterop = {
if (MusicKit.getInstance().isPlaying) { if (MusicKit.getInstance().isPlaying) {
MusicKit.getInstance().pause(); MusicKit.getInstance().pause();
} else if (MusicKit.getInstance().nowPlayingItem != null) { } else if (MusicKit.getInstance().nowPlayingItem != null) {
MusicKit.getInstance().play().then(r => console.log(`[MusicKitInterop] Playing ${r}`)); MusicKit.getInstance()
.play()
.then((r) => console.log(`[MusicKitInterop] Playing ${r}`));
} }
}, },
nextTrack: function () { nextTrack: function () {
MusicKit.getInstance().skipToNextItem().then(r => console.log(`[MusicKitInterop] Skipping to Next ${r}`)); MusicKit.getInstance()
.skipToNextItem()
.then((r) =>
console.log(`[MusicKitInterop] Skipping to Next ${r}`)
);
}, },
previousTrack: function () { previousTrack: function () {
MusicKit.getInstance().skipToPreviousItem().then(r => console.log(`[MusicKitInterop] Skipping to Previous ${r}`)); MusicKit.getInstance()
.skipToPreviousItem()
.then((r) =>
console.log(`[MusicKitInterop] Skipping to Previous ${r}`)
);
} }
};
}
process.once('loaded', () => { process.once('loaded', () => {
console.log("Setting ipcRenderer") console.log('Setting ipcRenderer');
global.ipcRenderer = electron.ipcRenderer; global.ipcRenderer = electron.ipcRenderer;
global.MusicKitInterop = MusicKitInterop; global.MusicKitInterop = MusicKitInterop;
}); });

File diff suppressed because it is too large Load diff

View file

@ -2,15 +2,15 @@
<html lang="en"> <html lang="en">
<head> <head>
<link rel="preconnect" href="https://amp-api.music.apple.com/" crossorigin/> <link rel="preconnect" href="https://amp-api.music.apple.com/" crossorigin />
<link rel="preconnect" href="https://api.music.apple.com/" crossorigin/> <link rel="preconnect" href="https://api.music.apple.com/" crossorigin />
<link rel="preconnect" href="https://is1-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is1-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is2-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is2-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is3-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is3-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is4-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is4-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is5-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is5-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://play.itunes.apple.com/" crossorigin/> <link rel="preconnect" href="https://play.itunes.apple.com/" crossorigin />
<link rel="preconnect" href="https://aod-ssl.itunes.apple.com/" crossorigin/> <link rel="preconnect" href="https://aod-ssl.itunes.apple.com/" crossorigin />
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
@ -23,7 +23,7 @@
</head> </head>
<body oncontextmenu="return false;" loading="1"> <body oncontextmenu="return false;" loading="1">
<div id="app"> <div id="app">
<div id="app-main"> <div id="app-main">
<div class="app-chrome"> <div class="app-chrome">
<div class="app-chrome--left"> <div class="app-chrome--left">
@ -33,7 +33,8 @@
<div class="app-chrome-item"> <div class="app-chrome-item">
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0" <button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0"
@click="mk.shuffleMode = 1"></button> @click="mk.shuffleMode = 1"></button>
<button class="playback-button--small shuffle active" v-else @click="mk.shuffleMode = 0"></button> <button class="playback-button--small shuffle active" v-else
@click="mk.shuffleMode = 0"></button>
</div> </div>
<div class="app-chrome-item"> <div class="app-chrome-item">
<button class="playback-button previous" @click="mk.skipToPreviousItem()"></button> <button class="playback-button previous" @click="mk.skipToPreviousItem()"></button>
@ -70,8 +71,7 @@
<div class="song-progress"> <div class="song-progress">
<input type="range" step="0.01" min="0" <input type="range" step="0.01" min="0"
@change="mk.seekToTime($event.target.value)" @change="mk.seekToTime($event.target.value)"
:max="mk.currentPlaybackDuration" :max="mk.currentPlaybackDuration" :value="playerLCD.playbackDuration">
:value="playerLCD.playbackDuration">
</div> </div>
</div> </div>
<div class="actions">❤️</div> <div class="actions">❤️</div>
@ -100,13 +100,13 @@
</div> </div>
<div class="app-chrome-item generic"> <div class="app-chrome-item generic">
<button class="playback-button--small lyrics" <button class="playback-button--small lyrics"
@click="drawertest = !drawertest; lyricon =!lyricon; if(drawertest == true){loadLyrics();}" @click="drawertest = !drawertest; lyricon =!lyricon; if(drawertest == true){loadLyrics();}"></button>
></button>
</div> </div>
<div class="app-chrome-item full-height"> <div class="app-chrome-item full-height">
<div class="window-controls"> <div class="window-controls">
<div class="minimize" @click="ipcRenderer.send('minimize')"></div> <div class="minimize" @click="ipcRenderer.send('minimize')"></div>
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div> <div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')">
</div>
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div> <div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
<div class="close" @click="ipcRenderer.send('close')"></div> <div class="close" @click="ipcRenderer.send('close')"></div>
</div> </div>
@ -118,12 +118,8 @@
<div class="app-sidebar-header"> <div class="app-sidebar-header">
<div class="search-input-container"> <div class="search-input-container">
<div class="search-input--icon"></div> <div class="search-input--icon"></div>
<input type="search" <input type="search" spellcheck="false" @click="showSearch()"
spellcheck="false" @change="showSearch();searchQuery()" placeholder="Search..." v-model="search.term"
@click="showSearch()"
@change="showSearch();searchQuery()"
placeholder="Search..."
v-model="search.term"
class="search-input"> class="search-input">
</div> </div>
</div> </div>
@ -144,7 +140,8 @@
<div class="app-sidebar-header-text"> <div class="app-sidebar-header-text">
Playlists Playlists
</div> </div>
<button class="app-sidebar-item" v-for="item in playlists.listing" :key="item.id" :href="item.href" <button class="app-sidebar-item" v-for="item in playlists.listing" :key="item.id"
:href="item.href"
@click='app.page=`playlist_` + item.id ; showingPlaylist = [];getPlaylistFromID(app.page.substring(9))'> @click='app.page=`playlist_` + item.id ; showingPlaylist = [];getPlaylistFromID(app.page.substring(9))'>
{{ item.attributes.name }} {{ item.attributes.name }}
</button> </button>
@ -174,14 +171,13 @@
@click="chrome.menuOpened = !chrome.menuOpened"> @click="chrome.menuOpened = !chrome.menuOpened">
<template v-if="chrome.userinfo.attributes"> <template v-if="chrome.userinfo.attributes">
<img class="sidebar-user-icon" loading="lazy" <img class="sidebar-user-icon" loading="lazy"
:src="getMediaItemArtwork(chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : '', 26)" />
:src="getMediaItemArtwork(chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : '', 26)"
/>
</template> </template>
<div class="sidebar-user-text" v-if="!chrome.hideUserInfo"> <div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
<template v-if="chrome.userinfo.attributes"> <template v-if="chrome.userinfo.attributes">
<div class="fullname text-overflow-elipsis">{{ chrome.userinfo.attributes.name }}</div> <div class="fullname text-overflow-elipsis">{{ chrome.userinfo.attributes.name }}
</div>
<div class="handle-text text-overflow-elipsis">@{{ chrome.userinfo.attributes.handle <div class="handle-text text-overflow-elipsis">@{{ chrome.userinfo.attributes.handle
}} }}
</div> </div>
@ -224,7 +220,8 @@
<button id="apple-music-authorize" class="md-btn md-btn-primary" @click="init()">Start <button id="apple-music-authorize" class="md-btn md-btn-primary" @click="init()">Start
MusicKit MusicKit
</button> </button>
<button id="apple-music-unauthorize" class="md-btn md-btn-primary" @click="unauthorize()"> <button id="apple-music-unauthorize" class="md-btn md-btn-primary"
@click="unauthorize()">
Stop Stop
MusicKit MusicKit
</button> </button>
@ -239,7 +236,8 @@
</div> </div>
<h1 class="header-text">Browse</h1> <h1 class="header-text">Browse</h1>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, urna eu tincidunt Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, urna eu
tincidunt
consectetur, nisl nunc euismod nisi, eu porttitor nisl nisi euismod nisi. consectetur, nisl nunc euismod nisi, eu porttitor nisl nisi euismod nisi.
</p> </p>
<div class="media-item--small"> <div class="media-item--small">
@ -297,16 +295,11 @@
<h1 class="header-text">Songs</h1> <h1 class="header-text">Songs</h1>
<div class="search-input-container" style="width:100%;margin: 16px 0px;"> <div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div> <div class="search-input--icon"></div>
<input type="search" <input type="search" style="width:100%;" spellcheck="false" placeholder="Search..."
style="width:100%;" @input="searchLibrarySongs" v-model="library.songs.search" class="search-input">
spellcheck="false"
placeholder="Search..."
@input="searchLibrarySongs"
v-model="library.songs.search"
class="search-input">
</div> </div>
<mediaitem-list-item :item="item" <mediaitem-list-item :item="item" v-for="item in library.songs.displayListing">
v-for="item in library.songs.displayListing"></mediaitem-list-item> </mediaitem-list-item>
</div> </div>
</template> </template>
</transition> </transition>
@ -317,38 +310,34 @@
<h1 class="header-text">Albums</h1> <h1 class="header-text">Albums</h1>
<div class="search-input-container" style="width:100%;margin: 16px 0px;"> <div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div> <div class="search-input--icon"></div>
<input type="search" <input type="search" style="width:100%;" spellcheck="false" placeholder="Search..."
style="width:100%;"
spellcheck="false"
placeholder="Search..."
class="search-input"> class="search-input">
</div> </div>
<mediaitem-square-large :item="item" <mediaitem-square-large :item="item" v-for="item in library.albums.listing">
v-for="item in library.albums.listing"></mediaitem-square-large> </mediaitem-square-large>
</div> </div>
</template> </template>
</transition> </transition>
</div> </div>
<transition name="drawertransition"> <transition name="drawertransition">
<div class="app-drawer" v-if="drawertest"> <div class="app-drawer" v-if="drawertest">
<lyrics-view v-if="drawertest && lyricon" :time="lyriccurrenttime" :lyrics="lyrics"></lyrics-view> <lyrics-view v-if="drawertest && lyricon" :time="lyriccurrenttime" :lyrics="lyrics">
</lyrics-view>
</div> </div>
</transition> </transition>
</div> </div>
</div> </div>
<transition name="wpfade"> <transition name="wpfade">
<img v-show="chrome.artworkReady" <img v-show="chrome.artworkReady" @load="chrome.artworkReady = true" class="bg-artwork"
@load="chrome.artworkReady = true"
class="bg-artwork"
:src="getNowPlayingArtworkBG(32)"> :src="getNowPlayingArtworkBG(32)">
</transition> </transition>
<transition name="wpfade"> <transition name="wpfade">
<div class="bg-artwork--placeholder" v-else></div> <div class="bg-artwork--placeholder" v-else></div>
</transition> </transition>
</div> </div>
<script type="text/x-template" id="mediaitem-artwork"> <script type="text/x-template" id="mediaitem-artwork">
<template v-if="type == 'artists'"> <template v-if="type == 'artists'">
<div class="mediaitem-artwork rounded" <div class="mediaitem-artwork rounded"
> >
@ -365,8 +354,8 @@
</template> </template>
</script> </script>
<!-- Generic Collection of MediaItems --> <!-- Generic Collection of MediaItems -->
<script type="text/x-template" id="collection-view-generic"> <script type="text/x-template" id="collection-view-generic">
<template> <template>
<div class="content-inner"> <div class="content-inner">
@ -374,8 +363,8 @@
</template> </template>
</script> </script>
<!-- Listen Now --> <!-- Listen Now -->
<script type="text/x-template" id="cider-listen-now"> <script type="text/x-template" id="cider-listen-now">
<div class="content-inner"> <div class="content-inner">
<h1 class="header-text">Listen Now</h1> <h1 class="header-text">Listen Now</h1>
<template v-for="recom in data.data"> <template v-for="recom in data.data">
@ -401,8 +390,8 @@
</div> </div>
</script> </script>
<!-- Album / Playlist View --> <!-- Album / Playlist View -->
<script type="text/x-template" id="cider-playlist"> <script type="text/x-template" id="cider-playlist">
<div class="content-inner"> <div class="content-inner">
<template v-if="data != [] && data.attributes != []"> <template v-if="data != [] && data.attributes != []">
<div class="playlist-display row"> <div class="playlist-display row">
@ -429,8 +418,8 @@
</div> </div>
</script> </script>
<!-- Search --> <!-- Search -->
<script type="text/x-template" id="cider-search"> <script type="text/x-template" id="cider-search">
<div class="content-inner"> <div class="content-inner">
<div class="row"> <div class="row">
<div class="col-sm" style="width: auto;" v-if="getTopResult()"> <div class="col-sm" style="width: auto;" v-if="getTopResult()">
@ -496,20 +485,20 @@
</div> </div>
</script> </script>
<script type="text/x-template" id="am-musiccovershelf"> <script type="text/x-template" id="am-musiccovershelf">
<h1>{{ component.attributes.title.stringForDisplay }}</h1> <h1>{{ component.attributes.title.stringForDisplay }}</h1>
</script> </script>
<!-- Sidebar Item --> <!-- Sidebar Item -->
<script type="text/x-template" id="sidebar-library-item"> <script type="text/x-template" id="sidebar-library-item">
<button class="app-sidebar-item" <button class="app-sidebar-item"
:class="$parent.getSidebarItemClass(page)" :class="$parent.getSidebarItemClass(page)"
@click="$parent.page = page">{{ name }} @click="$parent.page = page">{{ name }}
</button> </button>
</script> </script>
<!-- Horizontal MediaItem Scroller --> <!-- Horizontal MediaItem Scroller -->
<script type="text/x-template" id="mediaitem-scroller-horizontal"> <script type="text/x-template" id="mediaitem-scroller-horizontal">
<template> <template>
<div class="cd-hmedia-scroller"> <div class="cd-hmedia-scroller">
<mediaitem-square :item="item" <mediaitem-square :item="item"
@ -518,8 +507,8 @@
</template> </template>
</script> </script>
<!-- Horizontal MediaItem Scroller (Large) --> <!-- Horizontal MediaItem Scroller (Large) -->
<script type="text/x-template" id="mediaitem-scroller-horizontal-large"> <script type="text/x-template" id="mediaitem-scroller-horizontal-large">
<template> <template>
<div class="cd-hmedia-scroller"> <div class="cd-hmedia-scroller">
<mediaitem-square-large :item="item" <mediaitem-square-large :item="item"
@ -528,9 +517,9 @@
</template> </template>
</script> </script>
<!-- Horizontal MediaItem Scroller (SP : Special) --> <!-- Horizontal MediaItem Scroller (SP : Special) -->
<script type="text/x-template" id="mediaitem-scroller-horizontal-sp"> <script type="text/x-template" id="mediaitem-scroller-horizontal-sp">
<template> <template>
<div class="cd-hmedia-scroller"> <div class="cd-hmedia-scroller">
<mediaitem-square-sp :item="item" <mediaitem-square-sp :item="item"
@ -539,9 +528,9 @@
</template> </template>
</script> </script>
<!-- MediaItem List Item --> <!-- MediaItem List Item -->
<script type="text/x-template" id="mediaitem-list-item"> <script type="text/x-template" id="mediaitem-list-item">
<template> <template>
<div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)" <div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)"
class="cd-mediaitem-list-item"> class="cd-mediaitem-list-item">
@ -572,8 +561,8 @@
</template> </template>
</script> </script>
<!-- MediaItem Horizontal Rectangle --> <!-- MediaItem Horizontal Rectangle -->
<script type="text/x-template" id="mediaitem-hrect"> <script type="text/x-template" id="mediaitem-hrect">
<template> <template>
<div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)" <div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)"
class="cd-mediaitem-hrect"> class="cd-mediaitem-hrect">
@ -599,9 +588,9 @@
</template> </template>
</script> </script>
<!-- MediaItem Square --> <!-- MediaItem Square -->
<script type="text/x-template" id="mediaitem-square"> <script type="text/x-template" id="mediaitem-square">
<template> <template>
<div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)" <div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)"
class="cd-mediaitem-square"> class="cd-mediaitem-square">
@ -622,9 +611,9 @@
</template> </template>
</script> </script>
<!-- MediaItem Square (Large) --> <!-- MediaItem Square (Large) -->
<script type="text/x-template" id="mediaitem-square-large"> <script type="text/x-template" id="mediaitem-square-large">
<template> <template>
<div style="position: relative; display: inline-flex;"> <div style="position: relative; display: inline-flex;">
<div @click.self='app.routeView(item)' <div @click.self='app.routeView(item)'
@ -680,8 +669,8 @@
</template> </template>
</script> </script>
<!-- MediaItem Square SP --> <!-- MediaItem Square SP -->
<script type="text/x-template" id="mediaitem-square-sp"> <script type="text/x-template" id="mediaitem-square-sp">
<template> <template>
<div style="position: relative; display: inline-flex;"> <div style="position: relative; display: inline-flex;">
<div @click.self='app.routeView(item)' <div @click.self='app.routeView(item)'
@ -742,7 +731,7 @@
</div> </div>
</template> </template>
</script> </script>
<script type="text/x-template" id="lyrics-view"> <script type="text/x-template" id="lyrics-view">
<div class="md-body lyric-body"> <div class="md-body lyric-body">
<template v-if="lyrics"> <template v-if="lyrics">
<template v-for="lyric in lyrics" v-if="lyric.line != 'lrcInstrumental'"> <template v-for="lyric in lyrics" v-if="lyric.line != 'lrcInstrumental'">
@ -771,8 +760,8 @@
</template> </template>
</div> </div>
</script> </script>
<script src="https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js"></script> <script src="https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js"></script>
<script src="index.js?v=1"></script> <script src="index.js?v=1"></script>
</body> </body>
</html> </html>

View file

@ -1,156 +1,191 @@
// Apple Music Listen Now Page // Apple Music Listen Now Page
// URL : https://amp-api.music.apple.com/v1/me/recommendations?timezone=+00:00 // URL : https://amp-api.music.apple.com/v1/me/recommendations?timezone=+00:00
// &with=friendsMix,library,social&art[social-profiles:url]=c // &with=friendsMix,library,social&art[social-profiles:url]=c
// &name=listen-now&art[url]=c,f&omit[resource]=autos // &name=listen-now&art[url]=c,f&omit[resource]=autos
// &relate[editorial-items]=contents // &relate[editorial-items]=contents
// &extend=editorialCard,editorialVideo // &extend=editorialCard,editorialVideo
// &extend[albums]=artistUrl // &extend[albums]=artistUrl
// &extend[library-albums]=artistUrl // &extend[library-albums]=artistUrl
// &extend[playlists]=artistNames,editorialArtwork // &extend[playlists]=artistNames,editorialArtwork
// &extend[library-playlists]=artistNames,editorialArtwork // &extend[library-playlists]=artistNames,editorialArtwork
// &extend[social-profiles]=topGenreNames&include[albums]=artists // &extend[social-profiles]=topGenreNames&include[albums]=artists
// &include[songs]=artists&include[music-videos]=artists // &include[songs]=artists&include[music-videos]=artists
// &fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url // &fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url
// &fields[artists]=name,url&extend[stations]=airDate,supportsAirTimeUpdates&meta[stations]=inflectionPoints // &fields[artists]=name,url&extend[stations]=airDate,supportsAirTimeUpdates&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-profiles,social-upsells // &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-profiles,social-upsells
// &l=en-gb&platform=web // &l=en-gb&platform=web
await app.mk.api.personalRecommendations("", await app.mk.api.personalRecommendations(
'',
{ {
name: "listen-now", name: 'listen-now',
with: "friendsMix,library,social", with: 'friendsMix,library,social',
"art[social-profiles:url]":"c", 'art[social-profiles:url]': 'c',
"art[url]": "c,f", 'art[url]': 'c,f',
"omit[resource]": "autos", 'omit[resource]': 'autos',
"relate[editorial-items]": "contents", 'relate[editorial-items]': 'contents',
extend: ["editorialCard", "editorialVideo"], extend: ['editorialCard', 'editorialVideo'],
"extend[albums]": ["artistUrl"], 'extend[albums]': ['artistUrl'],
"extend[library-albums]": ["artistUrl"], 'extend[library-albums]': ['artistUrl'],
"extend[playlists]": ["artistNames", "editorialArtwork"], 'extend[playlists]': ['artistNames', 'editorialArtwork'],
"extend[library-playlists]": ["artistNames", "editorialArtwork"], 'extend[library-playlists]': ['artistNames', 'editorialArtwork'],
"extend[social-profiles]": "topGenreNames", 'extend[social-profiles]': 'topGenreNames',
"include[albums]": "artists", 'include[albums]': 'artists',
"include[songs]": "artists", 'include[songs]': 'artists',
"include[music-videos]": "artists", 'include[music-videos]': 'artists',
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"], 'fields[albums]': [
"fields[artists]": ["name", "url"], 'artistName',
"extend[stations]": ["airDate", "supportsAirTimeUpdates"], 'artistUrl',
"meta[stations]": "inflectionPoints", 'artwork',
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-profiles,social-upsells", 'contentRating',
l:"en-gb", 'editorialArtwork',
platform:"web" 'editorialVideo',
'name',
'playParams',
'releaseDate',
'url'
],
'fields[artists]': ['name', 'url'],
'extend[stations]': ['airDate', 'supportsAirTimeUpdates'],
'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-profiles,social-upsells',
l: 'en-gb',
platform: 'web'
}, },
{ {
includeResponseMeta: !0, includeResponseMeta: !0,
reload: !0 reload: !0
}); }
);
// Browse page // Browse page
await app.mk.api.groupings("", await app.mk.api.groupings('', {
{ platform: 'web',
platform: "web", name: 'music',
name: "music", l: 'en-gb',
l: "en-gb", 'omit[resource:artists]': 'relationships',
"omit[resource:artists]": "relationships", 'include[albums]': 'artists',
"include[albums]": "artists", 'include[songs]': 'artists',
"include[songs]": "artists", 'include[music-videos]': 'artists',
"include[music-videos]": "artists", extend: 'editorialArtwork,artistUrl',
extend: "editorialArtwork,artistUrl", 'fields[artists]':
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes", 'name,url,artwork,editorialArtwork,genreNames,editorialNotes',
"art[url]": "f" 'art[url]': 'f'
}); });
// Radio page // Radio page
await app.mk.api.recentRadioStations("", await app.mk.api.recentRadioStations('', {
{l: "en-gb", l: 'en-gb',
"platform": "web", platform: 'web',
"art[url]": "f"}); 'art[url]': 'f'
});
// Recently Added // Recently Added
await app.mk.api.library.recentlyAdded({ await app.mk.api.library.recentlyAdded(
"platform": "web", {
platform: 'web',
include: { include: {
"library-albums": ["artists"], 'library-albums': ['artists'],
"library-artists": ["catalog"] 'library-artists': ['catalog']
}, },
fields: { fields: {
artists: ["url"], artists: ['url'],
albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url" albums: 'artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url'
}, },
includeOnly: ["catalog", "artists"], includeOnly: ['catalog', 'artists'],
limit: 25 limit: 25
}, { },
{
reload: !0, reload: !0,
includePagination: !0 includePagination: !0
}) }
);
// Songs // Songs
await app.mk.api.library.songs({limit: 100}).then((data)=>{ await app.mk.api.library.songs({ limit: 100 }).then((data) => {
console.log(data) console.log(data);
}) });
// Artists // Artists
await app.mk.api.library.artists({limit: 100}).then((data)=>{ await app.mk.api.library.artists({ limit: 100 }).then((data) => {
console.log(data) console.log(data);
}) });
// Artists // Artists
await app.mk.api.library.albums({limit: 100}).then((data)=>{ await app.mk.api.library.albums({ limit: 100 }).then((data) => {
console.log(data) console.log(data);
}) });
// Albums // Albums
// does not like limit = 100 for some reason // does not like limit = 100 for some reason
await app.mk.api.library.albums({limit: 50}).then((data)=>{ await app.mk.api.library.albums({ limit: 50 }).then((data) => {
console.log(data) console.log(data);
}) });
// Made For You // Made For You
app.mk.api.recommendations("",{extend: "editorialArtwork,artistUrl"}) app.mk.api.recommendations('', { extend: 'editorialArtwork,artistUrl' });
// Library with library length // Library with library length
await app.mk.api.library.songs("", {limit: 100}, {includeResponseMeta: !0}).then((data)=>{ await app.mk.api.library
console.log(data) .songs('', { limit: 100 }, { includeResponseMeta: !0 })
}) .then((data) => {
console.log(data);
});
// Artist View Top Songs // Artist View Top Songs
app.mk.api.artistView("325096253", "top-songs", {}, {view: "top-songs", includeResponseMeta: !0}) app.mk.api.artistView(
'325096253',
'top-songs',
{},
{ view: 'top-songs', includeResponseMeta: !0 }
);
// Artist Page Data // Artist Page Data
app.mkapi("artists", false, "412778295", { app.mkapi(
"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", 'artists',
"extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero", false,
"extend[playlists]": "trackCount", '412778295',
"omit[resource:songs]": "relationships", {
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url,trackCount", 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',
"limit[artists:top-songs]": 20, extend: 'artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero',
"art[url]": "f" 'extend[playlists]': 'trackCount',
}, {includeResponseMeta: !0}).then((data)=>{ 'omit[resource:songs]': 'relationships',
console.log(data) 'fields[albums]':
}) 'artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url,trackCount',
'limit[artists:top-songs]': 20,
'art[url]': 'f'
},
{ includeResponseMeta: !0 }
).then((data) => {
console.log(data);
});
// download entire library // download entire library
var library = [] var library = [];
var downloaded = null; var downloaded = null;
function downloadChunk () { function downloadChunk() {
if(downloaded == null) { if (downloaded == null) {
app.mk.api.library.songs("", {limit: 100}, {includeResponseMeta: !0}).then((response)=>{ app.mk.api.library
processChunk(response) .songs('', { limit: 100 }, { includeResponseMeta: !0 })
}) .then((response) => {
}else{ processChunk(response);
downloaded.next("", {limit: 100}, {includeResponseMeta: !0}).then((response)=>{ });
processChunk(response) } else {
}) downloaded
.next('', { limit: 100 }, { includeResponseMeta: !0 })
.then((response) => {
processChunk(response);
});
} }
} }
function processChunk (response) { function processChunk(response) {
downloaded = response downloaded = response;
library = library.concat(downloaded.data) library = library.concat(downloaded.data);
if (downloaded.meta.total > library.length) { if (downloaded.meta.total > library.length) {
console.log(`downloading next chunk - ${library.length} songs so far`) console.log(`downloading next chunk - ${library.length} songs so far`);
downloadChunk() downloadChunk();
} else { } else {
console.log(library) console.log(library);
} }
} }
@ -158,23 +193,26 @@ function processChunk (response) {
// recentPlayed() -> recently played songs ? // recentPlayed() -> recently played songs ?
// create Artist / Song/ Album stations: // create Artist / Song/ Album stations:
app.mk.setStationQueue({artist:"1258279972"}) app.mk.setStationQueue({ artist: '1258279972' });
app.mk.setStationQueue({song:"1437308307"}) // yes the song id here can be the albumId, but just keep using the song: app.mk.setStationQueue({ song: '1437308307' }); // yes the song id here can be the albumId, but just keep using the song:
// Sorting Playlists, send an array of tracks in the format below // Sorting Playlists, send an array of tracks in the format below
// playlist must be fully recursively downloaded first before sorting // playlist must be fully recursively downloaded first before sorting
app.mk.api.library.putPlaylistTracklisting(app.showingPlaylist.attributes.playParams.id, [ app.mk.api.library.putPlaylistTracklisting(
app.showingPlaylist.attributes.playParams.id,
[
{ {
"id": relationships.tracks.data[X].id, id: relationships.tracks.data[X].id,
"type": relationships.tracks.data[X].type type: relationships.tracks.data[X].type
}, },
{ {
"id": relationships.tracks.data[X].id, id: relationships.tracks.data[X].id,
"type": relationships.tracks.data[X].type type: relationships.tracks.data[X].type
}, },
{ {
"id": relationships.tracks.data[X].id, id: relationships.tracks.data[X].id,
"type": relationships.tracks.data[X].type type: relationships.tracks.data[X].type
}, }
]) ]
);

View file

@ -54,8 +54,8 @@
persistentState: "required" persistentState: "required"
} }
let p = { let p = {
platformInfo: {requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h}, platformInfo: { requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h },
appData: {serviceName: "Apple Music"} appData: { serviceName: "Apple Music" }
} }
this.hls.attachMedia(this.$refs.video); this.hls.attachMedia(this.$refs.video);
this.hls.loadSource(this.video); this.hls.loadSource(this.video);

View file

@ -41,14 +41,14 @@
} }
}, },
props: { props: {
'item': {type: Object, required: true}, 'item': { type: Object, required: true },
'parent': {type: String, required: false}, 'parent': { type: String, required: false },
'index': {type: Number, required: false, default: -1}, 'index': { type: Number, required: false, default: -1 },
'show-artwork': {type: Boolean, default: true}, 'show-artwork': { type: Boolean, default: true },
'show-library-status': {type: Boolean, default: true}, 'show-library-status': { type: Boolean, default: true },
'show-meta-data': {type: Boolean, default: false}, 'show-meta-data': { type: Boolean, default: false },
'show-duration': {type: Boolean, default: true}, 'show-duration': { type: Boolean, default: true },
'contextExt': {type: Object, required: false}, 'contextExt': { type: Object, required: false },
}, },
methods: { methods: {
uuidv4() { uuidv4() {
@ -70,20 +70,23 @@
}, },
async select(e) { async select(e) {
let u = this.item let u = this.item
let u1 = await app.mk.api.library.artistRelationship(u.id,"albums", let u1 = await app.mk.api.library.artistRelationship(u.id, "albums",
{platform: "web", {
platform: "web",
"include[library-albums]": "artists,tracks", "include[library-albums]": "artists,tracks",
"include[library-artists]": "catalog", "include[library-artists]": "catalog",
"fields[artists]": "url", "fields[artists]": "url",
"includeOnly": "catalog,artists"} "includeOnly": "catalog,artists"
}
) )
app.showCollection({data : Object.assign({},u1)}, u.attributes.name?? '', ''); app.showCollection({ data: Object.assign({}, u1) }, u.attributes.name ?? '', '');
}, },
getArtwork(){ getArtwork() {
let u = "" let u = ""
try{ try {
u = this.item.relationships.catalog.data[0].attributes.artwork.url} u = this.item.relationships.catalog.data[0].attributes.artwork.url
catch (e){}; }
catch (e) { };
return u; return u;
}, },
contextMenu(event) { contextMenu(event) {
@ -122,7 +125,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playNext({[kind + "s"]: itemsToPlay[kind]}) app.mk.playNext({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
console.log(itemsToPlay) console.log(itemsToPlay)
@ -143,7 +146,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playLater({[kind + "s"]: itemsToPlay[kind]}) app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
app.selectedMediaItems = [] app.selectedMediaItems = []
@ -162,7 +165,7 @@
{ {
"name": "Start Radio", "name": "Start Radio",
"action": function () { "action": function () {
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => { app.mk.setStationQueue({ song: self.item.attributes.playParams.id ?? self.item.id }).then(() => {
app.mk.play() app.mk.play()
app.selectedMediaItems = [] app.selectedMediaItems = []
}) })
@ -171,7 +174,7 @@
{ {
"name": "Play Next", "name": "Play Next",
"action": function () { "action": function () {
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -179,7 +182,7 @@
{ {
"name": "Play Later", "name": "Play Later",
"action": function () { "action": function () {
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -227,7 +230,7 @@
}, },
async removeFromLibrary() { async removeFromLibrary() {
let item = this.item let item = this.item
let params = {"fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"} let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" }
let id = item.id ?? item.attributes.playParams.id let id = item.id ?? item.attributes.playParams.id
let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params); let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) { if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {

View file

@ -45,7 +45,7 @@
}, },
data: function () { data: function () {
return { return {
app:this.$root, app: this.$root,
isVisible: false, isVisible: false,
style: { style: {
"box-shadow": "" "box-shadow": ""
@ -58,9 +58,9 @@
}, },
methods: { methods: {
getVideoPriority() { getVideoPriority() {
if(app.cfg.visual.animated_artwork == "always") { if (app.cfg.visual.animated_artwork == "always") {
return true; return true;
}else if (this.videoPriority && app.cfg.visual.animated_artwork == "limited") { } else if (this.videoPriority && app.cfg.visual.animated_artwork == "limited") {
return true return true
} else if (app.cfg.visual.animated_artwork == "disabled") { } else if (app.cfg.visual.animated_artwork == "disabled") {
return false return false

View file

@ -76,14 +76,14 @@
} }
}, },
props: { props: {
'item': {type: Object, required: true}, 'item': { type: Object, required: true },
'parent': {type: String, required: false}, 'parent': { type: String, required: false },
'index': {type: Number, required: false, default: -1}, 'index': { type: Number, required: false, default: -1 },
'show-artwork': {type: Boolean, default: true}, 'show-artwork': { type: Boolean, default: true },
'show-library-status': {type: Boolean, default: true}, 'show-library-status': { type: Boolean, default: true },
'show-meta-data': {type: Boolean, default: false}, 'show-meta-data': { type: Boolean, default: false },
'show-duration': {type: Boolean, default: true}, 'show-duration': { type: Boolean, default: true },
'contextExt': {type: Object, required: false}, 'contextExt': { type: Object, required: false },
}, },
mounted() { mounted() {
let duration = this.item.attributes.durationInMillis ?? 0 let duration = this.item.attributes.durationInMillis ?? 0
@ -215,7 +215,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playNext({[kind + "s"]: itemsToPlay[kind]}) app.mk.playNext({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
console.log(itemsToPlay) console.log(itemsToPlay)
@ -236,7 +236,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playLater({[kind + "s"]: itemsToPlay[kind]}) app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
app.selectedMediaItems = [] app.selectedMediaItems = []
@ -256,7 +256,7 @@
{ {
"name": "Play Next", "name": "Play Next",
"action": function () { "action": function () {
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -264,7 +264,7 @@
{ {
"name": "Play Later", "name": "Play Later",
"action": function () { "action": function () {
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -273,7 +273,7 @@
"icon": "./assets/feather/radio.svg", "icon": "./assets/feather/radio.svg",
"name": "Start Radio", "name": "Start Radio",
"action": function () { "action": function () {
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => { app.mk.setStationQueue({ song: self.item.attributes.playParams.id ?? self.item.id }).then(() => {
app.mk.play() app.mk.play()
app.selectedMediaItems = [] app.selectedMediaItems = []
}) })
@ -348,19 +348,19 @@
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple) menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
} }
} }
try{ try {
let rating = await app.getRating(self.item).catch(); let rating = await app.getRating(self.item).catch();
if (rating) { if (rating) {
if(rating == 0) { if (rating == 0) {
menus.normal.items.find(x => x.id == 'love').disabled = false menus.normal.items.find(x => x.id == 'love').disabled = false
menus.normal.items.find(x => x.id == 'dislike').disabled = false menus.normal.items.find(x => x.id == 'dislike').disabled = false
}else if(rating == 1) { } else if (rating == 1) {
menus.normal.items.find(x => x.id == 'unlove').disabled = false menus.normal.items.find(x => x.id == 'unlove').disabled = false
}else if(rating == -1) { } else if (rating == -1) {
menus.normal.items.find(x => x.id == 'undo_dislike').disabled = false menus.normal.items.find(x => x.id == 'undo_dislike').disabled = false
} }
} }
} catch(_){} } catch (_) { }
CiderContextMenu.Create(event, menus[useMenu]) CiderContextMenu.Create(event, menus[useMenu])
}, },
visibilityChanged: function (isVisible, entry) { visibilityChanged: function (isVisible, entry) {
@ -380,7 +380,7 @@
}, },
async removeFromLibrary() { async removeFromLibrary() {
let item = this.item let item = this.item
let params = {"fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"} let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" }
let id = item.id ?? item.attributes.playParams.id let id = item.id ?? item.attributes.playParams.id
let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params); let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) { if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {

View file

@ -122,7 +122,7 @@
} }
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? ''; let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.api.library.remove({[truekind]: id}) app.mk.api.library.remove({ [truekind]: id })
this.addedToLibrary = true this.addedToLibrary = true
}, },
async contextMenu(event) { async contextMenu(event) {
@ -187,7 +187,7 @@
{ {
"name": "Play Next", "name": "Play Next",
"action": function () { "action": function () {
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -195,7 +195,7 @@
{ {
"name": "Play Later", "name": "Play Later",
"action": function () { "action": function () {
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }

View file

@ -85,7 +85,7 @@
return { return {
app: this.$root, app: this.$root,
isVisible: true, isVisible: true,
addedToLibrary : false, addedToLibrary: false,
} }
}, },
methods: { methods: {
@ -94,7 +94,7 @@
}, },
async isInLibrary() { async isInLibrary() {
if (this.item.type && !this.item.type.includes("library")) { if (this.item.type && !this.item.type.includes("library")) {
var params = {"fields[playlists]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()} var params = { "fields[playlists]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId() }
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params); var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
} else { } else {
@ -102,7 +102,7 @@
} }
}, },
async removeFromLibrary(id) { async removeFromLibrary(id) {
var params = {"fields[playlists]": "inLibrary","fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()} var params = { "fields[playlists]": "inLibrary", "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId() }
var id = this.item.id ?? this.item.attributes.playParams.id var id = this.item.id ?? this.item.attributes.playParams.id
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params); var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) { if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
@ -110,11 +110,11 @@
} }
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? ''; let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.api.library.remove({[truekind]: id}) app.mk.api.library.remove({ [truekind]: id })
this.addedToLibrary = true this.addedToLibrary = true
}, },
subtitleSearchNavigate(item) { subtitleSearchNavigate(item) {
if((item.attributes.editorialNotes == null) && item.attributes.artistName)app.searchAndNavigate(item,'artist') if ((item.attributes.editorialNotes == null) && item.attributes.artistName) app.searchAndNavigate(item, 'artist')
}, },
clickContext() { clickContext() {
var evt = document.createEvent('MouseEvent'); var evt = document.createEvent('MouseEvent');
@ -165,7 +165,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playNext({[kind + "s"]: itemsToPlay[kind]}) app.mk.playNext({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
console.log(itemsToPlay) console.log(itemsToPlay)
@ -186,7 +186,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playLater({[kind + "s"]: itemsToPlay[kind]}) app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
app.selectedMediaItems = [] app.selectedMediaItems = []
@ -200,7 +200,7 @@
{ {
"name": "Play Next", "name": "Play Next",
"action": function () { "action": function () {
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -208,7 +208,7 @@
{ {
"name": "Play Later", "name": "Play Later",
"action": function () { "action": function () {
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -257,8 +257,8 @@
"action": async function () { "action": async function () {
let item_id = self.item.attributes.playParams.id ?? self.item.id; let item_id = self.item.attributes.playParams.id ?? self.item.id;
let data_type = self.item.attributes.playParams.kind ?? self.item.type; let data_type = self.item.attributes.playParams.kind ?? self.item.type;
if (self.addedToLibrary != true) { console.log("add"); app.addToLibrary(item_id); self.addedToLibrary = true} if (self.addedToLibrary != true) { console.log("add"); app.addToLibrary(item_id); self.addedToLibrary = true }
else { console.log("remove"); await self.removeFromLibrary(item_id); self.addedToLibrary = false}; else { console.log("remove"); await self.removeFromLibrary(item_id); self.addedToLibrary = false };
} }
}, },
@ -266,12 +266,12 @@
} }
} }
let rating = await app.getRating(self.item) let rating = await app.getRating(self.item)
if(rating == 0) { if (rating == 0) {
menus.normal.items.find(x => x.id == 'love').disabled = false menus.normal.items.find(x => x.id == 'love').disabled = false
menus.normal.items.find(x => x.id == 'dislike').disabled = false menus.normal.items.find(x => x.id == 'dislike').disabled = false
}else if(rating == 1) { } else if (rating == 1) {
menus.normal.items.find(x => x.id == 'unlove').disabled = false menus.normal.items.find(x => x.id == 'unlove').disabled = false
}else if(rating == -1) { } else if (rating == -1) {
menus.normal.items.find(x => x.id == 'undo_dislike').disabled = false menus.normal.items.find(x => x.id == 'undo_dislike').disabled = false
} }
if ((self.item.attributes.playParams.kind ?? self.item.type).includes("playlist")) { if ((self.item.attributes.playParams.kind ?? self.item.type).includes("playlist")) {

View file

@ -57,7 +57,7 @@
type: String, type: String,
default: '190' default: '190'
}, },
'contextExt': {type: Object, required: false}, 'contextExt': { type: Object, required: false },
}, },
data: function () { data: function () {
return { return {
@ -89,14 +89,14 @@
console.log(luma) console.log(luma)
if (luma > 140) { if (luma > 140) {
return "#aaaaaa" return "#aaaaaa"
}else{ } else {
return color return color
} }
}, },
getSubtitle() { getSubtitle() {
if(this.kind == 'card') { if (this.kind == 'card') {
try { try {
if (typeof this.item.attributes.artistNames != "undefined") { if (typeof this.item.attributes.artistNames != "undefined") {
return this.item.attributes.artistNames return this.item.attributes.artistNames
@ -107,10 +107,10 @@
} else { } else {
return '' return ''
} }
}catch(e) { } catch (e) {
return '' return ''
} }
}else { } else {
if (typeof this.item.attributes.artistName != "undefined") { if (typeof this.item.attributes.artistName != "undefined") {
return this.item.attributes.artistName return this.item.attributes.artistName
} else { } else {
@ -119,23 +119,23 @@
} }
}, },
getSubtitleNavigation() { getSubtitleNavigation() {
if(this.kind == 'card') { if (this.kind == 'card') {
try { try {
if (typeof this.item.attributes.artistNames != "undefined") { if (typeof this.item.attributes.artistNames != "undefined") {
return app.routeView(this.item) return app.routeView(this.item)
} else if (typeof this.item.attributes.editorialNotes != "undefined") { } else if (typeof this.item.attributes.editorialNotes != "undefined") {
return app.routeView(this.item) return app.routeView(this.item)
} else if (typeof this.item.attributes.artistName != "undefined") { } else if (typeof this.item.attributes.artistName != "undefined") {
return app.searchAndNavigate(this.item,'artist') return app.searchAndNavigate(this.item, 'artist')
} else { } else {
return app.routeView(this.item) return app.routeView(this.item)
} }
}catch(e) { } catch (e) {
return app.routeView(this.item) return app.routeView(this.item)
} }
}else { } else {
if (typeof this.item.attributes.artistName != "undefined") { if (typeof this.item.attributes.artistName != "undefined") {
return app.searchAndNavigate(this.item,'artist') return app.searchAndNavigate(this.item, 'artist')
} else { } else {
return app.routeView(this.item) return app.routeView(this.item)
} }
@ -186,7 +186,7 @@
} }
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? ''; let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.api.library.remove({[truekind]: id}) app.mk.api.library.remove({ [truekind]: id })
this.addedToLibrary = true this.addedToLibrary = true
}, },
uuidv4() { uuidv4() {
@ -196,7 +196,7 @@
}, },
getArtworkUrl(size = -1, includeUrl = false) { getArtworkUrl(size = -1, includeUrl = false) {
let artwork = this.item.attributes.artwork ? this.item.attributes.artwork.url : '' let artwork = this.item.attributes.artwork ? this.item.attributes.artwork.url : ''
if(size != -1) { if (size != -1) {
artwork = artwork.replace('{w}', size).replace('{h}', size).replace('{f}', "webp").replace('{c}', ((size === 900) ? "sr" : "cc")) artwork = artwork.replace('{w}', size).replace('{h}', size).replace('{f}', "webp").replace('{c}', ((size === 900) ? "sr" : "cc"))
} }
switch (this.kind) { switch (this.kind) {
@ -204,9 +204,9 @@
artwork = this.item.attributes.editorialArtwork.subscriptionHero.url artwork = this.item.attributes.editorialArtwork.subscriptionHero.url
break; break;
} }
if(!includeUrl) { if (!includeUrl) {
return artwork return artwork
}else{ } else {
return `url("${artwork}")` return `url("${artwork}")`
} }
}, },
@ -273,7 +273,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playNext({[kind + "s"]: itemsToPlay[kind]}) app.mk.playNext({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
console.log(itemsToPlay) console.log(itemsToPlay)
@ -294,7 +294,7 @@
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
app.mk.playLater({[kind + "s"]: itemsToPlay[kind]}) app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
app.selectedMediaItems = [] app.selectedMediaItems = []
@ -315,7 +315,7 @@
{ {
"name": "Play Next", "name": "Play Next",
"action": function () { "action": function () {
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -323,7 +323,7 @@
{ {
"name": "Play Later", "name": "Play Later",
"action": function () { "action": function () {
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id}) app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }

View file

@ -57,8 +57,8 @@
}, },
methods: { methods: {
select(e, position) { select(e, position) {
if(e.ctrlKey || e.shiftKey) { if (e.ctrlKey || e.shiftKey) {
if(this.selectedItems.indexOf(position) == -1) { if (this.selectedItems.indexOf(position) == -1) {
this.selectedItems.push(position) this.selectedItems.push(position)
} else { } else {
this.selectedItems.splice(this.selectedItems.indexOf(position), 1) this.selectedItems.splice(this.selectedItems.indexOf(position), 1)
@ -70,7 +70,7 @@
queueContext(event, item, position) { queueContext(event, item, position) {
let self = this let self = this
let useMenu = "single" let useMenu = "single"
if(this.selectedItems.length > 1) { if (this.selectedItems.length > 1) {
useMenu = "multiple" useMenu = "multiple"
} }
let menus = { let menus = {

View file

@ -64,9 +64,9 @@
rename() { rename() {
this.renaming = false this.renaming = false
if(this.item.type === "library-playlist-folders") { if (this.item.type === "library-playlist-folders") {
this.$root.editPlaylistFolder(this.item.id, this.item.attributes.name) this.$root.editPlaylistFolder(this.item.id, this.item.attributes.name)
}else{ } else {
this.$root.editPlaylist(this.item.id, this.item.attributes.name) this.$root.editPlaylist(this.item.id, this.item.attributes.name)
} }
}, },
@ -74,7 +74,7 @@
let self = this let self = this
this.children = [] this.children = []
this.children = this.$root.playlists.listing.filter(child => { this.children = this.$root.playlists.listing.filter(child => {
if(child.parent == self.item.id) { if (child.parent == self.item.id) {
return child return child
} }
}) })
@ -98,13 +98,13 @@
// find the item in this.$root.playlists.listing and store it in a variable // find the item in this.$root.playlists.listing and store it in a variable
this.$root.playlists.listing.filter(playlist => { this.$root.playlists.listing.filter(playlist => {
if(playlist.id == item.id) { if (playlist.id == item.id) {
console.log(playlist) console.log(playlist)
playlist.parent = sendTo.id playlist.parent = sendTo.id
} }
}) })
if(typeof this.$parent.getChildren == "function") { if (typeof this.$parent.getChildren == "function") {
this.$parent.getChildren() this.$parent.getChildren()
console.log(this.$parent.children) console.log(this.$parent.children)
} }
@ -123,14 +123,14 @@
id: this.playlistRoot, id: this.playlistRoot,
type: "library-playlist-folders" type: "library-playlist-folders"
}) })
setTimeout(()=>{self.getChildren()}, 2000) setTimeout(() => { self.getChildren() }, 2000)
} }
}, },
"rename": { "rename": {
name: "Rename", name: "Rename",
action: () => { action: () => {
this.renaming = true this.renaming = true
setTimeout(()=>{ setTimeout(() => {
document.querySelector(".pl-rename-field").focus() document.querySelector(".pl-rename-field").focus()
document.querySelector(".pl-rename-field").select() document.querySelector(".pl-rename-field").select()
}, 100) }, 100)
@ -151,7 +151,7 @@
} }
} }
} }
if(this.item.type === "library-playlist-folders") { if (this.item.type === "library-playlist-folders") {
menu.items.addToFavorites.disabled = true menu.items.addToFavorites.disabled = true
} }
CiderContextMenu.Create(event, menu) CiderContextMenu.Create(event, menu)
@ -160,23 +160,23 @@
evt.preventDefault(); evt.preventDefault();
evt.dataTransfer.dropEffect = "move"; evt.dataTransfer.dropEffect = "move";
}, },
onDrop (evt) { onDrop(evt) {
let data = JSON.parse(evt.dataTransfer.getData("text/plain")) let data = JSON.parse(evt.dataTransfer.getData("text/plain"))
evt.preventDefault(); evt.preventDefault();
if(data.id == this.item.id) { if (data.id == this.item.id) {
return; return;
} }
console.log(data) console.log(data)
if(data) { if (data) {
if(this.item.type == "library-playlists" || this.item.type == "library-playlist-folders") { if (this.item.type == "library-playlists" || this.item.type == "library-playlist-folders") {
if(data.type == "library-playlists" && this.item.type == "library-playlists") { if (data.type == "library-playlists" && this.item.type == "library-playlists") {
return return
} }
this.move(data, this.item) this.move(data, this.item)
} }
} }
}, },
startDrag (evt) { startDrag(evt) {
evt.dataTransfer.dropEffect = 'move' evt.dataTransfer.dropEffect = 'move'
evt.dataTransfer.effectAllowed = 'move' evt.dataTransfer.effectAllowed = 'move'
evt.dataTransfer.setData('text/plain', JSON.stringify(this.item)) evt.dataTransfer.setData('text/plain', JSON.stringify(this.item))
@ -188,7 +188,7 @@
this.toggleFolder() this.toggleFolder()
this.$root.mk.api.library.playlistFolderChildren(item.id).then(children => { this.$root.mk.api.library.playlistFolderChildren(item.id).then(children => {
children.forEach(child => { children.forEach(child => {
if(!self.$root.playlists.listing.find(listing => listing.id == child.id)) { if (!self.$root.playlists.listing.find(listing => listing.id == child.id)) {
child.parent = self.item.id child.parent = self.item.id
self.$root.playlists.listing.push(child) self.$root.playlists.listing.push(child)
} }
@ -207,7 +207,7 @@
}) })
}, },
isPlaylistSelected(item) { isPlaylistSelected(item) {
if(this.$root.showingPlaylist.id == item.id) { if (this.$root.showingPlaylist.id == item.id) {
return ["active"] return ["active"]
} else { } else {
return [] return []

View file

@ -294,7 +294,7 @@
this.room_materials = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.room_materials)) this.room_materials = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.room_materials))
this.audio_position = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.audio_position)) this.audio_position = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.audio_position))
this.listener_position = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.listener_position)) this.listener_position = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.listener_position))
if(typeof this.app.mk.nowPlayingItem != "undefined") { if (typeof this.app.mk.nowPlayingItem != "undefined") {
this.setRoom() this.setRoom()
} }
this.ready = true this.ready = true
@ -320,7 +320,7 @@
}, },
objectContainerStyle() { objectContainerStyle() {
let scale = 1 let scale = 1
if(this.room_dimensions.width * this.visualMultiplier > 300) { if (this.room_dimensions.width * this.visualMultiplier > 300) {
scale = 300 / (this.room_dimensions.width * this.visualMultiplier) scale = 300 / (this.room_dimensions.width * this.visualMultiplier)
} }
let style = { let style = {
@ -346,7 +346,7 @@
window.CiderAudio.audioNodes.spatialNode.setRoomProperties(this.room_dimensions, this.room_materials); window.CiderAudio.audioNodes.spatialNode.setRoomProperties(this.room_dimensions, this.room_materials);
CiderAudio.audioNodes.spatialInput.setPosition(...this.audio_position) CiderAudio.audioNodes.spatialInput.setPosition(...this.audio_position)
CiderAudio.audioNodes.spatialNode.setListenerPosition(...this.listener_position) CiderAudio.audioNodes.spatialNode.setListenerPosition(...this.listener_position)
if(!this.app.cfg.audio.normalization) { if (!this.app.cfg.audio.normalization) {
window.CiderAudio.audioNodes.gainNode.gain.value = app.cfg.audio.spatial_properties.gain window.CiderAudio.audioNodes.gainNode.gain.value = app.cfg.audio.spatial_properties.gain
} }
} }

View file

@ -2,22 +2,22 @@
<html lang="en"> <html lang="en">
<head> <head>
<link rel="preconnect" href="https://amp-api.music.apple.com/" crossorigin/> <link rel="preconnect" href="https://amp-api.music.apple.com/" crossorigin />
<link rel="preconnect" href="https://api.music.apple.com/" crossorigin/> <link rel="preconnect" href="https://api.music.apple.com/" crossorigin />
<link rel="preconnect" href="https://is1-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is1-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is2-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is2-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is3-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is3-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is4-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is4-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://is5-ssl.mzstatic.com/" crossorigin/> <link rel="preconnect" href="https://is5-ssl.mzstatic.com/" crossorigin />
<link rel="preconnect" href="https://play.itunes.apple.com/" crossorigin/> <link rel="preconnect" href="https://play.itunes.apple.com/" crossorigin />
<link rel="preconnect" href="https://aod-ssl.itunes.apple.com/" crossorigin/> <link rel="preconnect" href="https://aod-ssl.itunes.apple.com/" crossorigin />
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Cider</title> <title>Cider</title>
<link rel="stylesheet/less" type="text/css" href="style.less"/> <link rel="stylesheet/less" type="text/css" href="style.less" />
<script src="less.js"></script> <script src="less.js"></script>
<script src="<%- (env.dev ? "vue.js" : "vue.dev.js") %>"></script> <script src="<%- (env.dev ? 'vue.js' : 'vue.dev.js') %>"></script>
<script src="vuex.min.js"></script> <script src="vuex.min.js"></script>
<script src="sortable.min.js"></script> <script src="sortable.min.js"></script>
<script src="vue-observe-visibility.min.js"></script> <script src="vue-observe-visibility.min.js"></script>
@ -30,7 +30,7 @@
</head> </head>
<body oncontextmenu="return false;" loading="1" platform="<%= env.platform %>"> <body oncontextmenu="return false;" loading="1" platform="<%= env.platform %>">
<div id="app"> <div id="app">
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div id="app-main" v-show="appMode == 'player'"> <div id="app-main" v-show="appMode == 'player'">
<div class="mv-chrome" v-if="chrome.topChromeVisible == false"></div> <div class="mv-chrome" v-if="chrome.topChromeVisible == false"></div>
@ -90,10 +90,12 @@
</div> </div>
<div class="song-progress"> <div class="song-progress">
<div class="song-duration" style="justify-content: space-between; height: 1px;" <div class="song-duration"
style="justify-content: space-between; height: 1px;"
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]"> :style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
<p style="width: auto">{{ convertToMins(getSongProgress()) }}</p> <p style="width: auto">{{ convertToMins(getSongProgress()) }}</p>
<p style="width: auto">{{ convertToMins(mk.currentPlaybackDuration) }}</p> <p style="width: auto">{{ convertToMins(mk.currentPlaybackDuration) }}
</p>
</div> </div>
<input type="range" step="0.01" min="0" :style="progressBarStyle()" <input type="range" step="0.01" min="0" :style="progressBarStyle()"
@ -118,8 +120,8 @@
<div class="app-chrome--right"> <div class="app-chrome--right">
<div class="app-chrome-item volume display--large"> <div class="app-chrome-item volume display--large">
<div class="app-chrome-item volume-icon"></div> <div class="app-chrome-item volume-icon"></div>
<input type="range" class="" @wheel="volumeWheel" step="0.01" min="0" max="1" v-model="mk.volume" <input type="range" class="" @wheel="volumeWheel" step="0.01" min="0" max="1"
v-if="typeof mk.volume != 'undefined'"> v-model="mk.volume" v-if="typeof mk.volume != 'undefined'">
</div> </div>
<div class="app-chrome-item generic" v-if="false"> <div class="app-chrome-item generic" v-if="false">
<button class="playback-button--small"> <button class="playback-button--small">
@ -137,7 +139,8 @@
<div class="app-chrome-item full-height"> <div class="app-chrome-item full-height">
<div class="window-controls"> <div class="window-controls">
<div class="minimize" @click="ipcRenderer.send('minimize')"></div> <div class="minimize" @click="ipcRenderer.send('minimize')"></div>
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"> <div class="minmax restore" v-if="chrome.maximized"
@click="ipcRenderer.send('maximize')">
</div> </div>
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div> <div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
<div class="close" @click="ipcRenderer.send('close')"></div> <div class="close" @click="ipcRenderer.send('close')"></div>
@ -153,11 +156,9 @@
<input type="search" spellcheck="false" @click="showSearch()" <input type="search" spellcheck="false" @click="showSearch()"
@focus="search.showHints = true" @focus="search.showHints = true"
@blur="setTimeout(()=>{search.showHints = false}, 300)" @blur="setTimeout(()=>{search.showHints = false}, 300)"
v-on:keyup.enter="searchQuery();search.showHints = false" v-on:keyup.enter="searchQuery();search.showHints = false" @change="showSearch();"
@change="showSearch();" @input="getSearchHints()" placeholder="Search..." @input="getSearchHints()" placeholder="Search..." v-model="search.term"
v-model="search.term" ref="searchInput" class="search-input">
ref="searchInput"
class="search-input">
</div> </div>
</div> </div>
<div class="search-hints-container" v-if="search.showHints && search.hints.length != 0"> <div class="search-hints-container" v-if="search.showHints && search.hints.length != 0">
@ -172,21 +173,30 @@
<div class="app-sidebar-header-text"> <div class="app-sidebar-header-text">
Apple Music Apple Music
</div> </div>
<sidebar-library-item name="Home" svg-icon="./assets/feather/home.svg" page="home"></sidebar-library-item> <sidebar-library-item name="Home" svg-icon="./assets/feather/home.svg" page="home">
<sidebar-library-item name="Listen Now" svg-icon="./assets/feather/play-circle.svg" page="listen_now"></sidebar-library-item> </sidebar-library-item>
<sidebar-library-item name="Browse" svg-icon="./assets/feather/globe.svg" page="browse"></sidebar-library-item> <sidebar-library-item name="Listen Now" svg-icon="./assets/feather/play-circle.svg"
<sidebar-library-item name="Radio" svg-icon="./assets/feather/radio.svg" page="radio"></sidebar-library-item> page="listen_now"></sidebar-library-item>
<sidebar-library-item name="Browse" svg-icon="./assets/feather/globe.svg" page="browse">
</sidebar-library-item>
<sidebar-library-item name="Radio" svg-icon="./assets/feather/radio.svg" page="radio">
</sidebar-library-item>
<div class="app-sidebar-header-text"> <div class="app-sidebar-header-text">
Library Library
</div> </div>
<sidebar-library-item name="Recently Added" svg-icon="./assets/feather/plus-circle.svg" page="library-recentlyadded"></sidebar-library-item> <sidebar-library-item name="Recently Added" svg-icon="./assets/feather/plus-circle.svg"
<sidebar-library-item name="Songs" svg-icon="./assets/feather/music.svg" page="library-songs"></sidebar-library-item> page="library-recentlyadded"></sidebar-library-item>
<sidebar-library-item name="Albums" svg-icon="./assets/feather/disc.svg" page="library-albums"></sidebar-library-item> <sidebar-library-item name="Songs" svg-icon="./assets/feather/music.svg"
<sidebar-library-item name="Artists" svg-icon="./assets/feather/user.svg" page="library-artists"></sidebar-library-item> page="library-songs"></sidebar-library-item>
<sidebar-library-item name="Albums" svg-icon="./assets/feather/disc.svg"
page="library-albums"></sidebar-library-item>
<sidebar-library-item name="Artists" svg-icon="./assets/feather/user.svg"
page="library-artists"></sidebar-library-item>
<div class="app-sidebar-header-text" @contextmenu="playlistHeaderContextMenu"> <div class="app-sidebar-header-text" @contextmenu="playlistHeaderContextMenu">
Playlists Playlists
</div> </div>
<sidebar-playlist v-for="item in getPlaylistFolderChildren('p.playlistsroot')" :item="item"></sidebar-playlist> <sidebar-playlist v-for="item in getPlaylistFolderChildren('p.playlistsroot')" :item="item">
</sidebar-playlist>
</div> </div>
<transition name="wpfade"> <transition name="wpfade">
<div class="usermenu-container" v-if="chrome.menuOpened"> <div class="usermenu-container" v-if="chrome.menuOpened">
@ -211,7 +221,8 @@
</div> </div>
</div> </div>
</button> </button>
<button class="usermenu-item" v-if="cfg.advanced.AudioContext && cfg.audio.spatial" @click="modals.spatialProperties = true"> <button class="usermenu-item" v-if="cfg.advanced.AudioContext && cfg.audio.spatial"
@click="modals.spatialProperties = true">
Spatialized Audio Settings Spatialized Audio Settings
</button> </button>
<button class="usermenu-item"> <button class="usermenu-item">
@ -231,21 +242,21 @@
</transition> </transition>
<div class="app-sidebar-footer"> <div class="app-sidebar-footer">
<input type="range" class="web-slider display--small" step="0.01" min="0" max="1" <input type="range" class="web-slider display--small" step="0.01" min="0" max="1"
v-model="mk.volume" @wheel="volumeWheel" v-model="mk.volume" @wheel="volumeWheel" v-if="typeof mk.volume != 'undefined'">
v-if="typeof mk.volume != 'undefined'"> <button class="app-sidebar-button" style="width:100%" :class="{active: chrome.menuOpened}"
<button class="app-sidebar-button" style="width:100%"
:class="{active: chrome.menuOpened}"
@blur="setTimeout(()=>{chrome.menuOpened = false}, 100)" @blur="setTimeout(()=>{chrome.menuOpened = false}, 100)"
@click="(chrome.userinfo.id) ? chrome.menuOpened = !chrome.menuOpened : false"> @click="(chrome.userinfo.id) ? chrome.menuOpened = !chrome.menuOpened : false">
<img class="sidebar-user-icon" loading="lazy" <img class="sidebar-user-icon" loading="lazy"
:src="getMediaItemArtwork(chrome.hideUserInfo ? 'http://localhost:9000/assets/logocut.png' : (chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : ''), 26)"/> :src="getMediaItemArtwork(chrome.hideUserInfo ? 'http://localhost:9000/assets/logocut.png' : (chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : ''), 26)" />
<div class="sidebar-user-text" v-if="!chrome.hideUserInfo"> <div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
<template v-if="chrome.userinfo.id"> <template v-if="chrome.userinfo.id">
<div class="fullname text-overflow-elipsis">{{ chrome.userinfo.attributes.name }} <div class="fullname text-overflow-elipsis">{{ chrome.userinfo.attributes.name
}}
</div> </div>
<div class="handle-text text-overflow-elipsis">@{{ chrome.userinfo.attributes.handle <div class="handle-text text-overflow-elipsis">@{{
chrome.userinfo.attributes.handle
}} }}
</div> </div>
</template> </template>
@ -272,9 +283,12 @@
</div> </div>
<div id="app-content"> <div id="app-content">
<div id="navigation-bar"> <div id="navigation-bar">
<button class="nav-item" @click="navigateBack()"><%- include('svg/chevron-left.svg') %></button> <button class="nav-item" @click="navigateBack()">
<button class="nav-item" <%- include('svg/chevron-left.svg') %>
@click="navigateForward()"><%- include('svg/chevron-right.svg') %></button> </button>
<button class="nav-item" @click="navigateForward()">
<%- include('svg/chevron-right.svg') %>
</button>
</div> </div>
<!-- Artist Page --> <!-- Artist Page -->
<transition name="wpfade"> <transition name="wpfade">
@ -457,7 +471,8 @@
<lyrics-view v-if="drawer.panel == 'lyrics'" :time="lyriccurrenttime" :lyrics="lyrics" <lyrics-view v-if="drawer.panel == 'lyrics'" :time="lyriccurrenttime" :lyrics="lyrics"
:richlyrics="richlyrics"></lyrics-view> :richlyrics="richlyrics"></lyrics-view>
<div v-if="drawer.panel == 'lyrics'" class="lyric-footer"> <div v-if="drawer.panel == 'lyrics'" class="lyric-footer">
<button class="md-btn" @click="modularUITest(!fullscreenLyrics)">{{fullscreenLyrics ? "Default View":'Fullscreen View'}}</button> <button class="md-btn" @click="modularUITest(!fullscreenLyrics)">{{fullscreenLyrics ?
"Default View":'Fullscreen View'}}</button>
</div> </div>
<cider-queue ref="queue" v-if="drawer.panel == 'queue'"></cider-queue> <cider-queue ref="queue" v-if="drawer.panel == 'queue'"></cider-queue>
</div> </div>
@ -467,15 +482,15 @@
</transition> </transition>
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'fullscreen'"> <div class="fullscreen-view-container" v-if="appMode == 'fullscreen'">
<fullscreen-view :image="currentArtUrl.replace('50x50', '600x600')" :time="lyriccurrenttime" :lyrics="lyrics" :richlyrics="richlyrics"></fullscreen-view> <fullscreen-view :image="currentArtUrl.replace('50x50', '600x600')" :time="lyriccurrenttime"
:lyrics="lyrics" :richlyrics="richlyrics"></fullscreen-view>
</div> </div>
</transition> </transition>
<transition name="wpfade"> <transition name="wpfade">
<div class="bg-artwork-container" :class="{noanimation: (!cfg.visual.bg_artwork_rotation || !animateBackground)}"> <div class="bg-artwork-container"
<img @load="chrome.artworkReady = true" class="bg-artwork a " :class="{noanimation: (!cfg.visual.bg_artwork_rotation || !animateBackground)}">
> <img @load="chrome.artworkReady = true" class="bg-artwork a ">
<img class="bg-artwork b" <img class="bg-artwork b">
>
</div> </div>
</transition> </transition>
<transition name="wpfade"> <transition name="wpfade">
@ -494,14 +509,16 @@
aria-role="presentation" focusable="false"> aria-role="presentation" focusable="false">
<path <path
d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z" d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z"
fill-rule="nonzero"/> fill-rule="nonzero" />
</svg> </svg>
</div> </div>
<div id="captions">{{((lyricon) ? ((lyrics.length > 0 && lyrics[currentLyricsLine] && <div id="captions">{{((lyricon) ? ((lyrics.length > 0 && lyrics[currentLyricsLine] &&
lyrics[currentLyricsLine].line ) ? lyrics[currentLyricsLine].line ) ?
lyrics[currentLyricsLine].line.replace('lrcInstrumental','') : "") : '') + ((lyricon) ? ((lyrics.length lyrics[currentLyricsLine].line.replace('lrcInstrumental','') : "") : '') + ((lyricon) ?
((lyrics.length
> 0 && lyrics[currentLyricsLine] && lyrics[currentLyricsLine].line ) ? > 0 && lyrics[currentLyricsLine] && lyrics[currentLyricsLine].line ) ?
(lyrics[currentLyricsLine].translation ? ('\n\r' + lyrics[currentLyricsLine].translation) : ""): "") : (lyrics[currentLyricsLine].translation ? ('\n\r' + lyrics[currentLyricsLine].translation) : ""): "")
:
'')}} '')}}
</div> </div>
<div id="player-pip" <div id="player-pip"
@ -517,49 +534,49 @@
</div> </div>
<div id="apple-music-video-player"></div> <div id="apple-music-video-player"></div>
</div> </div>
</div> </div>
<!-- Media Item Artwork--> <!-- Media Item Artwork-->
<%- include("components/mediaitem-artwork"); %> <%- include("components/mediaitem-artwork"); %>
<!-- Browse --> <!-- Browse -->
<%- include('pages/browse') %> <%- include('pages/browse') %>
<!-- Settings --> <!-- Settings -->
<%- include('pages/settings') %> <%- include('pages/settings') %>
<!-- Listen Now --> <!-- Listen Now -->
<%- include('pages/listen_now') %> <%- include('pages/listen_now') %>
<!-- Home --> <!-- Home -->
<%- include('pages/home') %> <%- include('pages/home') %>
<!-- Artist Feed --> <!-- Artist Feed -->
<%- include('pages/artist-feed') %> <%- include('pages/artist-feed') %>
<!-- Playlists / Albums --> <!-- Playlists / Albums -->
<%- include('pages/cider-playlist') %> <%- include('pages/cider-playlist') %>
<!-- Record Label --> <!-- Record Label -->
<%- include('pages/recordLabel') %> <%- include('pages/recordLabel') %>
<!-- Collection List --> <!-- Collection List -->
<%- include('pages/collection-list') %> <%- include('pages/collection-list') %>
<!-- Apple Curator --> <!-- Apple Curator -->
<%- include('pages/apple-curator') %> <%- include('pages/apple-curator') %>
<!-- Artist Page --> <!-- Artist Page -->
<%- include('pages/artist') %> <%- include('pages/artist') %>
<!-- Search --> <!-- Search -->
<%- include('pages/search') %> <%- include('pages/search') %>
<script type="text/x-template" id="am-musiccovershelf"> <script type="text/x-template" id="am-musiccovershelf">
<h1>{{ component.attributes.title.stringForDisplay }}</h1> <h1>{{ component.attributes.title.stringForDisplay }}</h1>
</script> </script>
<!-- Sidebar Item --> <!-- Sidebar Item -->
<script type="text/x-template" id="sidebar-library-item"> <script type="text/x-template" id="sidebar-library-item">
<button class="app-sidebar-item" <button class="app-sidebar-item"
:class="$parent.getSidebarItemClass(page)" @click="$root.appRoute(page)"> :class="$parent.getSidebarItemClass(page)" @click="$root.appRoute(page)">
<div class="sidebar-icon" v-html="svgIconData" v-if="svgIconData != ''"></div> <div class="sidebar-icon" v-html="svgIconData" v-if="svgIconData != ''"></div>
@ -567,54 +584,74 @@
</button> </button>
</script> </script>
<!-- Playlist Listing --> <!-- Playlist Listing -->
<%- include('components/sidebar-playlist') %> <%- include('components/sidebar-playlist') %>
<!-- Spatial Properties --> <!-- Spatial Properties -->
<%- include('components/spatial-properties') %> <%- include('components/spatial-properties') %>
<!-- Add to playlist --> <!-- Add to playlist -->
<%- include('components/add-to-playlist') %> <%- include('components/add-to-playlist') %>
<!-- Queue --> <!-- Queue -->
<%- include('components/queue') %> <%- include('components/queue') %>
<!-- Queue Item --> <!-- Queue Item -->
<%- include('components/queue-item') %> <%- include('components/queue-item') %>
<!-- Horizontal MediaItem Scroller --> <!-- Horizontal MediaItem Scroller -->
<%- include('components/mediaitem-scroller-horizontal') %> <%- include('components/mediaitem-scroller-horizontal')
<!-- Horizontal MediaItem Scroller (Large) --> %>
<%- include('components/mediaitem-scroller-horizontal-large') %> <!-- Horizontal MediaItem Scroller (Large) -->
<!-- Horizontal MediaItem Scroller (SP : Special) --> <%- include('components/mediaitem-scroller-horizontal-large')
<%- include('components/mediaitem-scroller-horizontal-sp') %> %>
<!-- Horizontal MediaItem Scroller (MV) --> <!-- Horizontal MediaItem Scroller (SP : Special) -->
<%- include('components/mediaitem-scroller-horizontal-mvview') %> <%- include('components/mediaitem-scroller-horizontal-sp')
<!-- MediaItem List Item --> %>
<%- include('components/mediaitem-list-item') %> <!-- Horizontal MediaItem Scroller (MV) -->
<!-- MediaItem Horizontal Rectangle --> <%- include('components/mediaitem-scroller-horizontal-mvview')
<%- include('components/mediaitem-hrect') %> %>
<!-- MediaItem Square --> <!-- MediaItem List Item -->
<%- include('components/mediaitem-square') %> <%- include('components/mediaitem-list-item')
<!-- MediaItem Square SP --> %>
<%- include('components/mediaitem-square-sp') %> <!-- MediaItem Horizontal Rectangle -->
<!-- MediaItem MusicVideo --> <%- include('components/mediaitem-hrect')
<%- include('components/mediaitem-mvview') %> %>
<!-- MediaItem MusicVideo --> <!-- MediaItem Square -->
<%- include('components/libraryartist-item') %> <%- include('components/mediaitem-square')
<%- include('components/listennow-child') %> %>
<!-- MediaItem MusicVideo SP --> <!-- MediaItem Square SP -->
<%- include('components/mediaitem-mvview-sp') %> <%- include('components/mediaitem-square-sp')
<!-- Animated Artwork View --> %>
<%- include('components/animatedartwork-view') %> <!-- MediaItem MusicVideo -->
<!-- Lyrics View --> <%- include('components/mediaitem-mvview')
<%- include('components/lyrics-view') %> %>
<!-- Fullscreen View --> <!-- MediaItem MusicVideo -->
<%- include('components/fullscreen') %> <%- include('components/libraryartist-item')
%>
<%- include('components/listennow-child')
%>
<!-- MediaItem MusicVideo SP -->
<%- include('components/mediaitem-mvview-sp')
%>
<!-- Animated Artwork View -->
<%- include('components/animatedartwork-view')
%>
<!-- Lyrics View -->
<%- include('components/lyrics-view')
%>
<!-- Fullscreen View -->
<%- include('components/fullscreen')
%>
<script src="musickit.js?v=1"></script> <script
<script> src="musickit.js?v=1"></script>
<script>
if (typeof MusicKit == 'undefined') { if (typeof MusicKit == 'undefined') {
document.write(unescape("%3Cscript src='https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js' type='text/javascript'%3E%3C/script%3E")); document.write(unescape("%3Cscript src='https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js' type='text/javascript'%3E%3C/script%3E"));
} }
</script> </script>
<script src="index.js?v=1"></script> <script
<script src="https://cdn.jsdelivr.net/npm/resonance-audio/build/resonance-audio.min.js"></script> src="index.js?v=1"></script>
<script src="/audio/audio.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>
</body> </body>
</html> </html>

View file

@ -1,7 +1,6 @@
Major thanks to the Cider Development Team and all of our contributors. Major thanks to the Cider Development Team and all of our contributors.
<p>"Apple Music" - Copyright © 2021 <a href="https://www.apple.com/" class="dt-footer__link" <p>"Apple Music" - Copyright © 2021 <a href="https://www.apple.com/" class="dt-footer__link" target="_blank"
target="_blank"
rel="noopener" data-dt-link-to-exclude="">Apple Inc.</a> rel="noopener" data-dt-link-to-exclude="">Apple Inc.</a>
All Rights All Rights
Reserved.</p> Reserved.</p>
@ -14,6 +13,5 @@ vapormusic - Developer - https://github.com/vapormusic
Void - Social Communications Team - https://twitter.com/MoonyVoid Void - Social Communications Team - https://twitter.com/MoonyVoid
NoseySG - Social Communications Team - https://twitter.com/noah_grose NoseySG - Social Communications Team - https://twitter.com/noah_grose
<img class="md-contributors" <img class="md-contributors" onclick="window.open('https://github.com/ciderapp/Cider/graphs/contributors')"
onclick="window.open('https://github.com/ciderapp/Cider/graphs/contributors')" src="https://contrib.rocks/image?repo=ciderapp/Cider" />
src="https://contrib.rocks/image?repo=ciderapp/Cider"/>

View file

@ -33,7 +33,7 @@
Vue.component('cider-applecurator', { Vue.component('cider-applecurator', {
template: "#cider-applecurator", template: "#cider-applecurator",
props: ["data"], props: ["data"],
data: function() { data: function () {
return { return {
app: this.$root app: this.$root
} }

View file

@ -48,7 +48,7 @@
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount", "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount",
"limit[artists:top-songs]": 20, "limit[artists:top-songs]": 20,
"art[url]": "f" "art[url]": "f"
}, {includeResponseMeta: !0}).then(artistData => { }, { includeResponseMeta: !0 }).then(artistData => {
artistData.data.forEach(item => { artistData.data.forEach(item => {
if (item.views["latest-release"].data.length != 0) { if (item.views["latest-release"].data.length != 0) {
self.artistFeed.push(item.views["latest-release"].data[0]) self.artistFeed.push(item.views["latest-release"].data[0])

View file

@ -133,19 +133,19 @@
} }
}, },
methods: { methods: {
artistMenu (event) { artistMenu(event) {
let self = this let self = this
let followAction = "follow" let followAction = "follow"
let followActions = { let followActions = {
follow: { follow: {
name: "Follow Artist", name: "Follow Artist",
action: ()=>{ action: () => {
self.app.cfg.home.followedArtists.push(self.data.id) self.app.cfg.home.followedArtists.push(self.data.id)
} }
}, },
unfollow: { unfollow: {
name: "Unfollow Artist", name: "Unfollow Artist",
action: ()=>{ action: () => {
let index = self.app.cfg.home.followedArtists.indexOf(self.data.id) let index = self.app.cfg.home.followedArtists.indexOf(self.data.id)
if (index > -1) { if (index > -1) {
self.app.cfg.home.followedArtists.splice(index, 1) self.app.cfg.home.followedArtists.splice(index, 1)
@ -153,15 +153,15 @@
} }
} }
} }
if(this.app.cfg.home.followedArtists.includes(self.data.id)) { if (this.app.cfg.home.followedArtists.includes(self.data.id)) {
followAction = "unfollow" followAction = "unfollow"
} }
CiderContextMenu.Create(event, { CiderContextMenu.Create(event, {
items: [ items: [
{ {
name: "Play Artist Radio", name: "Play Artist Radio",
action: ()=>{ action: () => {
app.mk.setStationQueue({artist:self.data.id}).then(()=>{ app.mk.setStationQueue({ artist: self.data.id }).then(() => {
app.mk.play() app.mk.play()
}) })
} }
@ -169,7 +169,7 @@
followActions[followAction], followActions[followAction],
{ {
name: "Share", name: "Share",
action: ()=>{ action: () => {
self.app.copyToClipboard(self.data.attributes.url) self.app.copyToClipboard(self.data.attributes.url)
} }
} }

View file

@ -138,7 +138,7 @@
methods: { methods: {
getBadges() { getBadges() {
return return
if(this.badgesRequested) { if (this.badgesRequested) {
return return
} }
this.badgesRequested = true this.badgesRequested = true
@ -152,7 +152,7 @@
} }
this.$root.getSocialBadges((badges) => { this.$root.getSocialBadges((badges) => {
let friends = badges[id] let friends = badges[id]
if(friends) { if (friends) {
friends.forEach(function (friend) { friends.forEach(function (friend) {
self.app.mk.api.socialProfile(friend).then(data => { self.app.mk.api.socialProfile(friend).then(data => {
self.itemBadges.push(data) self.itemBadges.push(data)
@ -164,7 +164,7 @@
async isInLibrary() { async isInLibrary() {
if (this.data.type && !this.data.type.includes("library")) { if (this.data.type && !this.data.type.includes("library")) {
// please keep using vars here // please keep using vars here
var params = {"fields[playlists]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"} var params = { "fields[playlists]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" }
var res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params); var res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params);
this.inLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false this.inLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
console.log(res) console.log(res)
@ -186,7 +186,7 @@
this.inLibrary = true this.inLibrary = true
}, },
async removeFromLibrary(id) { async removeFromLibrary(id) {
var params = {"fields[somgs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"} var params = { "fields[somgs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" }
var id = this.data.id ?? this.data.attributes.playParams.id var id = this.data.id ?? this.data.attributes.playParams.id
var res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params); var res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) { if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
@ -194,7 +194,7 @@
} }
let kind = this.data.attributes.playParams.kind ?? this.data.type ?? ''; let kind = this.data.attributes.playParams.kind ?? this.data.type ?? '';
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.api.library.remove({[truekind]: id}) app.mk.api.library.remove({ [truekind]: id })
this.inLibrary = false this.inLibrary = false
}, },
editPlaylistName() { editPlaylistName() {
@ -276,7 +276,7 @@
if (date == null || date === "") return ""; if (date == null || date === "") return "";
try { try {
var releaseDate = new Date(date); var releaseDate = new Date(date);
month = new Intl.DateTimeFormat('en-US', {month: 'long'}).format(releaseDate); month = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(releaseDate);
date = releaseDate.getDate(); date = releaseDate.getDate();
year = releaseDate.getFullYear(); year = releaseDate.getFullYear();

View file

@ -52,11 +52,11 @@
}, },
methods: { methods: {
getKind(item) { getKind(item) {
if(typeof item.kind != "undefined") { if (typeof item.kind != "undefined") {
this.commonKind = item.kind; this.commonKind = item.kind;
return item.kind return item.kind
} }
if(typeof item.attributes.playParams != "undefined") { if (typeof item.attributes.playParams != "undefined") {
this.commonKind = item.attributes.playParams.kind this.commonKind = item.attributes.playParams.kind
return item.attributes.playParams.kind return item.attributes.playParams.kind
} }
@ -72,7 +72,7 @@
}, },
getNext() { getNext() {
// if this.data.next is not null, then we can run this.data.next() and concat to this.data.data to get the next page // 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) { switch (this.type) {
default: default:
case "artists": case "artists":
if (this.data.next && this.triggerEnabled) { if (this.data.next && this.triggerEnabled) {
@ -84,12 +84,12 @@
this.data.data = this.data.data.concat(data.data); this.data.data = this.data.data.concat(data.data);
this.triggerEnabled = true; this.triggerEnabled = true;
}); });
if(typeof this.data.next == "function") { if (typeof this.data.next == "function") {
this.data.next().then(data => nextFn(data)); this.data.next().then(data => nextFn(data));
}else{ } else {
this.api.v3.music(this.data.next).then(data => nextFn(data)); this.api.v3.music(this.data.next).then(data => nextFn(data));
} }
}else{ } else {
console.log("No next page"); console.log("No next page");
this.triggerEnabled = false; this.triggerEnabled = false;
} }
@ -103,7 +103,7 @@
this.data.data = this.data.data.concat(data[this.data.groups].data.data); this.data.data = this.data.data.concat(data[this.data.groups].data.data);
this.triggerEnabled = true; this.triggerEnabled = true;
}); });
}else{ } else {
console.log("No next page"); console.log("No next page");
this.triggerEnabled = false; this.triggerEnabled = false;
} }
@ -118,7 +118,7 @@
this.data.data = this.data.data.concat(data.data.data); this.data.data = this.data.data.concat(data.data.data);
this.triggerEnabled = true; this.triggerEnabled = true;
}); });
}else{ } else {
console.log("No next page"); console.log("No next page");
this.triggerEnabled = false; this.triggerEnabled = false;
} }
@ -127,17 +127,17 @@
}, },
headerVisibility: function (isVisible, entry) { headerVisibility: function (isVisible, entry) {
if(isVisible) { if (isVisible) {
this.showFab = false; this.showFab = false;
}else{ } else {
this.showFab = true; this.showFab = true;
} }
}, },
visibilityChanged: function (isVisible, entry) { visibilityChanged: function (isVisible, entry) {
if(isVisible) { if (isVisible) {
this.canSeeTrigger = true; this.canSeeTrigger = true;
this.getNext(); this.getNext();
}else{ } else {
this.canSeeTrigger = false; this.canSeeTrigger = false;
} }
} }

View file

@ -73,7 +73,7 @@
<script> <script>
Vue.component('cider-home', { Vue.component('cider-home', {
template: '#cider-home', template: '#cider-home',
data: function() { data: function () {
return { return {
app: this.$root, app: this.$root,
followedArtists: this.$root.cfg.home.followedArtists, followedArtists: this.$root.cfg.home.followedArtists,
@ -105,7 +105,7 @@
let self = this let self = this
return { return {
name: "Remove from Favorites", name: "Remove from Favorites",
action: function(item) { action: function (item) {
let index = self.favoriteItems.findIndex(x => x.id == item.id) let index = self.favoriteItems.findIndex(x => x.id == item.id)
if (index > -1) { if (index > -1) {
self.favoriteItems.splice(index, 1) self.favoriteItems.splice(index, 1)
@ -203,7 +203,7 @@
return section return section
}; };
})[0].relationships.contents.data })[0].relationships.contents.data
} catch (err) {} } catch (err) { }
self.sectionsReady.push("madeForYou") self.sectionsReady.push("madeForYou")
try { try {
@ -213,7 +213,7 @@
return section return section
}; };
})[0].relationships.contents.data })[0].relationships.contents.data
} catch (err) {} } catch (err) { }
self.sectionsReady.push("recentlyPlayed") self.sectionsReady.push("recentlyPlayed")
self.sectionsReady.push("friendsListeningTo") self.sectionsReady.push("friendsListeningTo")
}); });

View file

@ -5,19 +5,18 @@
<h1 class="header-text">Albums</h1> <h1 class="header-text">Albums</h1>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button v-if="library.albums.downloadState == 2" @click="getLibraryAlbumsFull(true, 1)" class="reload-btn"><%- include('../svg/redo.svg') %></button> <button v-if="library.albums.downloadState == 2" @click="getLibraryAlbumsFull(true, 1)"
class="reload-btn">
<%- include('../svg/redo.svg') %>
</button>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col" style="padding:0px;"> <div class="col" style="padding:0px;">
<div class="search-input-container" style="width:100%;margin: 16px 0px;"> <div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div> <div class="search-input--icon"></div>
<input type="search" <input type="search" style="width:100%;" spellcheck="false" placeholder="Search..."
style="width:100%;" @input="searchLibraryAlbums" v-model="library.albums.search" class="search-input">
spellcheck="false"
placeholder="Search..."
@input="searchLibraryAlbums"
v-model="library.albums.search" class="search-input">
</div> </div>
</div> </div>
<div class="col-auto flex-center"> <div class="col-auto flex-center">
@ -25,12 +24,14 @@
<div class="col"> <div class="col">
<select class="md-select" v-model="library.albums.sorting[1]" @change="searchLibraryAlbums(1)"> <select class="md-select" v-model="library.albums.sorting[1]" @change="searchLibraryAlbums(1)">
<optgroup label="Sort By"> <optgroup label="Sort By">
<option v-for="(sort, index) in library.albums.sortingOptions" :value="index">{{ sort }}</option> <option v-for="(sort, index) in library.albums.sortingOptions" :value="index">{{ sort }}
</option>
</optgroup> </optgroup>
</select> </select>
</div> </div>
<div class="col"> <div class="col">
<select class="md-select" v-model="library.albums.sortOrder[1]" @change="searchLibraryAlbums(1)"> <select class="md-select" v-model="library.albums.sortOrder[1]"
@change="searchLibraryAlbums(1)">
<optgroup label="Sort Order"> <optgroup label="Sort Order">
<option value="asc">Ascending</option> <option value="asc">Ascending</option>
<option value="desc">Descending</option> <option value="desc">Descending</option>
@ -51,11 +52,13 @@
<div class="well"> <div class="well">
<div class="albums-square-container"> <div class="albums-square-container">
<div> <div>
<mediaitem-square v-if="library.albums.viewAs == 'covers'" :size="'150'" :item="item" v-for="item in library.albums.displayListing"> <mediaitem-square v-if="library.albums.viewAs == 'covers'" :size="'150'" :item="item"
v-for="item in library.albums.displayListing">
</mediaitem-square> </mediaitem-square>
</div> </div>
</div> </div>
<mediaitem-list-item v-if="library.albums.viewAs == 'list'" :show-duration="false" :show-meta-data="true" :show-library-status="false" :item="item" v-for="item in library.albums.displayListing"> <mediaitem-list-item v-if="library.albums.viewAs == 'list'" :show-duration="false" :show-meta-data="true"
:show-library-status="false" :item="item" v-for="item in library.albums.displayListing">
</mediaitem-list-item> </mediaitem-list-item>
</div> </div>
</div> </div>

View file

@ -1,4 +1,4 @@
<div class="content-inner"> <div class="content-inner">
<div class="row"> <div class="row">
<div class="col" style="padding:0px;"> <div class="col" style="padding:0px;">
<h1 class="header-text">Artists</h1> <h1 class="header-text">Artists</h1>
@ -9,12 +9,8 @@
<div class="col" style="padding:0px;"> <div class="col" style="padding:0px;">
<div class="search-input-container" style="width:100%;margin: 16px 0px;"> <div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div> <div class="search-input--icon"></div>
<input type="search" <input type="search" style="width:100%;" spellcheck="false" placeholder="Search..."
style="width:100%;" @input="searchLibraryArtists" v-model="library.artists.search" class="search-input">
spellcheck="false"
placeholder="Search..."
@input="searchLibraryArtists"
v-model="library.artists.search" class="search-input">
</div> </div>
</div> </div>
<div class="col-auto flex-center"> <div class="col-auto flex-center">
@ -48,7 +44,8 @@
<div class="well"> <div class="well">
<!-- <mediaitem-square v-if="library.artists.viewAs == 'covers'" :item="item" v-for="item in library.artists.displayListing"> <!-- <mediaitem-square v-if="library.artists.viewAs == 'covers'" :item="item" v-for="item in library.artists.displayListing">
</mediaitem-square> --> </mediaitem-square> -->
<libraryartist-item :show-duration="false" :show-meta-data="true" :show-library-status="false" :item="item" v-for="item in library.artists.displayListing"> <libraryartist-item :show-duration="false" :show-meta-data="true" :show-library-status="false" :item="item"
v-for="item in library.artists.displayListing">
</libraryartist-item> </libraryartist-item>
</div> </div>
</div> </div>

View file

@ -6,19 +6,17 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button v-if="library.albums.downloadState == 2" @click="getLibraryAlbumsFull(true, 0)" <button v-if="library.albums.downloadState == 2" @click="getLibraryAlbumsFull(true, 0)"
class="reload-btn"><%- include('../svg/redo.svg') %></button> class="reload-btn">
<%- include('../svg/redo.svg') %>
</button>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col" style="padding:0px;"> <div class="col" style="padding:0px;">
<div class="search-input-container" style="width:100%;margin: 16px 0px;"> <div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div> <div class="search-input--icon"></div>
<input type="search" <input type="search" style="width:100%;" spellcheck="false" placeholder="Search..."
style="width:100%;" @input="searchLibraryAlbums" v-model="library.albums.search" class="search-input">
spellcheck="false"
placeholder="Search..."
@input="searchLibraryAlbums"
v-model="library.albums.search" class="search-input">
</div> </div>
</div> </div>
<div class="col-auto flex-center"> <div class="col-auto flex-center">
@ -48,8 +46,7 @@
v-for="item in library.albums.displayListing"> v-for="item in library.albums.displayListing">
</mediaitem-square> </mediaitem-square>
<mediaitem-list-item v-if="library.albums.viewAs == 'list'" :show-duration="false" :show-meta-data="true" <mediaitem-list-item v-if="library.albums.viewAs == 'list'" :show-duration="false" :show-meta-data="true"
:show-library-status="false" :item="item" :show-library-status="false" :item="item" v-for="item in library.albums.displayListing">
v-for="item in library.albums.displayListing">
</mediaitem-list-item> </mediaitem-list-item>
</div> </div>
</div> </div>

View file

@ -5,19 +5,17 @@
<h1 class="header-text">Songs</h1> <h1 class="header-text">Songs</h1>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button v-if="library.songs.downloadState == 2" @click="getLibrarySongsFull(true)" class="reload-btn"><%- include('../svg/redo.svg') %></button> <button v-if="library.songs.downloadState == 2" @click="getLibrarySongsFull(true)" class="reload-btn">
<%- include('../svg/redo.svg') %>
</button>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col" style="padding:0px;"> <div class="col" style="padding:0px;">
<div class="search-input-container" style="width:100%;margin: 16px 0px;"> <div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div> <div class="search-input--icon"></div>
<input type="search" <input type="search" style="width:100%;" spellcheck="false" placeholder="Search..."
style="width:100%;" @input="searchLibrarySongs" v-model="library.songs.search" class="search-input">
spellcheck="false"
placeholder="Search..."
@input="searchLibrarySongs"
v-model="library.songs.search" class="search-input">
</div> </div>
</div> </div>
<div class="col-auto flex-center"> <div class="col-auto flex-center">
@ -25,7 +23,8 @@
<div class="col"> <div class="col">
<select class="md-select" v-model="library.songs.sorting" @change="searchLibrarySongs()"> <select class="md-select" v-model="library.songs.sorting" @change="searchLibrarySongs()">
<optgroup label="Sort By"> <optgroup label="Sort By">
<option v-for="(sort, index) in library.songs.sortingOptions" :value="index">{{ sort }}</option> <option v-for="(sort, index) in library.songs.sortingOptions" :value="index">{{ sort }}
</option>
</optgroup> </optgroup>
</select> </select>
</div> </div>
@ -41,6 +40,7 @@
</div> </div>
</div> </div>
<div v-if="library.songs.downloadState == 3">Library contains no songs.</div> <div v-if="library.songs.downloadState == 3">Library contains no songs.</div>
<mediaitem-list-item :item="item" :parent="'librarysongs'" :index="index" :show-meta-data="true" :show-library-status="false" v-for="(item, index) in library.songs.displayListing"></mediaitem-list-item> <mediaitem-list-item :item="item" :parent="'librarysongs'" :index="index" :show-meta-data="true"
:show-library-status="false" v-for="(item, index) in library.songs.displayListing"></mediaitem-list-item>
</div> </div>
</template> </template>

View file

@ -8,8 +8,8 @@
</script> </script>
<script> <script>
Vue.component('cider-listen-now', { Vue.component('cider-listen-now', {
template: "#cider-listen-now", template: "#cider-listen-now",
props: ["data"] props: ["data"]
}) })
</script> </script>

View file

@ -1,4 +1,4 @@
<div class="content-inner"> <div class="content-inner">
<div class="row"> <div class="row">
<div class="col" style="padding:0px"> <div class="col" style="padding:0px">
<h1 class="header-text">Made For You</h1> <h1 class="header-text">Made For You</h1>
@ -8,4 +8,4 @@
<mediaitem-square :item="item" v-for="item in madeforyou.data"> <mediaitem-square :item="item" v-for="item in madeforyou.data">
</mediaitem-square> </mediaitem-square>
</div> </div>
</div> </div>

View file

@ -564,37 +564,39 @@
} }
}, },
mounted: function () { mounted: function () {
if (app.cfg.lastfm.enabled){ if (app.cfg.lastfm.enabled) {
const element = document.getElementById('lfmConnect'); const element = document.getElementById('lfmConnect');
if (element){ if (element) {
element.innerHTML = `Disconnect\n<p style="font-size: 8px"><i>(Authed: ${app.cfg.lastfm.auth_token})</i></p>`; element.innerHTML = `Disconnect\n<p style="font-size: 8px"><i>(Authed: ${app.cfg.lastfm.auth_token})</i></p>`;
element.onclick = app.LastFMDeauthorize; element.onclick = app.LastFMDeauthorize;
} }
} }
}, },
methods: { methods: {
toggleAudioContext: function(){ toggleAudioContext: function () {
if (app.cfg.advanced.AudioContext){ if (app.cfg.advanced.AudioContext) {
CiderAudio.init(); CiderAudio.init();
if (app.cfg.audio.normalization){ if (app.cfg.audio.normalization) {
CiderAudio.normalizerOn()} CiderAudio.normalizerOn()
if (app.cfg.audio.spatial){ }
CiderAudio.spatialOn()} if (app.cfg.audio.spatial) {
CiderAudio.spatialOn()
}
} else { } else {
CiderAudio.off(); CiderAudio.off();
} }
}, },
toggleNormalization : function(){ toggleNormalization: function () {
if (app.cfg.audio.normalization){ if (app.cfg.audio.normalization) {
CiderAudio.normalizerOn() CiderAudio.normalizerOn()
} else {CiderAudio.normalizerOff()} } else { CiderAudio.normalizerOff() }
}, },
toggleSpatial : function(){ toggleSpatial: function () {
if (app.cfg.audio.spatial){ if (app.cfg.audio.spatial) {
CiderAudio.spatialOn() CiderAudio.spatialOn()
} else {CiderAudio.spatialOff()} } else { CiderAudio.spatialOff() }
}, },
changeAudioQuality : function(){ changeAudioQuality: function () {
app.mk.bitrate = app.cfg.audio.quality app.mk.bitrate = app.cfg.audio.quality
} }
} }

View file

@ -2,7 +2,8 @@ module.exports = {
globDirectory: 'src/renderer/', globDirectory: 'src/renderer/',
swDest: 'src/renderer/sw.js', swDest: 'src/renderer/sw.js',
// Define runtime caching rules. // Define runtime caching rules.
runtimeCaching: [{ runtimeCaching: [
{
// Match any request that ends with .png, .jpg, .jpeg or .svg. // Match any request that ends with .png, .jpg, .jpeg or .svg.
urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/, urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/,
@ -11,11 +12,10 @@ module.exports = {
options: { options: {
// Use a custom cache name. // Use a custom cache name.
cacheName: 'imageinternet', cacheName: 'imageinternet'
// Only cache 10 images. // Only cache 10 images.
}
},
}, },
{ {
urlPattern: /https:\/\/amp-api.music.apple.com\/v1\//, urlPattern: /https:\/\/amp-api.music.apple.com\/v1\//,
@ -23,18 +23,19 @@ module.exports = {
options: { options: {
cacheName: 'amp-api', cacheName: 'amp-api',
cacheableResponse: { cacheableResponse: {
statuses: [0, 200], statuses: [0, 200]
}, }
}, }
}, },
{ {
urlPattern: /https:\/\/is[0-9]-ssl\.mzstatic\.com\/image+/, urlPattern: /https:\/\/is[0-9]-ssl\.mzstatic\.com\/image+/,
handler: "CacheFirst", handler: 'CacheFirst'
}, },
{ {
urlPattern: /^https:\/\/store-\d{3}\.blobstore\.apple\.com\/.{65}\/image+/, urlPattern:
handler: "CacheFirst", /^https:\/\/store-\d{3}\.blobstore\.apple\.com\/.{65}\/image+/,
}, handler: 'CacheFirst'
}
], ],
ignoreURLParametersMatching: [ ignoreURLParametersMatching: [
/^utm_/, /^utm_/,
@ -44,6 +45,6 @@ module.exports = {
/^X-Amz-SignedHeaders/, /^X-Amz-SignedHeaders/,
/^X-Amz-Expires/, /^X-Amz-Expires/,
/^X-Amz-Credential/, /^X-Amz-Credential/,
/^X-Amz-Signature/, /^X-Amz-Signature/
] ]
}; };