Merge branch 'upcoming' into fuck-git
1
.gitignore
vendored
|
@ -4,6 +4,7 @@ dist
|
||||||
yarn*
|
yarn*
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.yarnclean
|
.yarnclean
|
||||||
|
build
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.idea
|
.idea
|
||||||
|
|
262
index.js
|
@ -1,262 +0,0 @@
|
||||||
require('v8-compile-cache');
|
|
||||||
const { app, components } = require('electron'), { resolve, join } = require("path"),
|
|
||||||
CiderBase = require('./src/main/cider-base');
|
|
||||||
const customProtocols = require('./package.json').fileAssociations[0].protocols
|
|
||||||
console.log(customProtocols)
|
|
||||||
const comps = components;
|
|
||||||
|
|
||||||
|
|
||||||
// Analytics for debugging.
|
|
||||||
const ElectronSentry = require("@sentry/electron");
|
|
||||||
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" });
|
|
||||||
|
|
||||||
const configDefaults = {
|
|
||||||
"general": {
|
|
||||||
"close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray
|
|
||||||
"startup_behavior": 0, // 0 = nothing, 1 = open on startup
|
|
||||||
"discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music
|
|
||||||
"discordClearActivityOnPause": 1, // 0 = disabled, 1 = enabled
|
|
||||||
"volume": 1
|
|
||||||
},
|
|
||||||
"home": {
|
|
||||||
"followedArtists": [],
|
|
||||||
"favoriteItems": []
|
|
||||||
},
|
|
||||||
"libraryPrefs": {
|
|
||||||
"songs": {
|
|
||||||
"sort": "name",
|
|
||||||
"sortOrder": "asc",
|
|
||||||
"size": "normal"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"audio": {
|
|
||||||
"quality": "990",
|
|
||||||
"seamless_audio": true,
|
|
||||||
"normalization": false,
|
|
||||||
"spatial": false,
|
|
||||||
"spatial_properties": {
|
|
||||||
"presets": [],
|
|
||||||
"gain": 0.8,
|
|
||||||
"listener_position": [0, 0, 0],
|
|
||||||
"audio_position": [0, 0, 0],
|
|
||||||
"room_dimensions": {
|
|
||||||
"width": 32,
|
|
||||||
"height": 12,
|
|
||||||
"depth": 32
|
|
||||||
},
|
|
||||||
"room_materials": {
|
|
||||||
"left": 'metal',
|
|
||||||
"right": 'metal',
|
|
||||||
"front": 'brick-bare',
|
|
||||||
"back": 'brick-bare',
|
|
||||||
"down": 'acoustic-ceiling-tiles',
|
|
||||||
"up": 'acoustic-ceiling-tiles',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"visual": {
|
|
||||||
"theme": "",
|
|
||||||
"scrollbars": 0, // 0 = show on hover, 2 = always hide, 3 = always show
|
|
||||||
"refresh_rate": 0,
|
|
||||||
"animated_artwork": "limited", // 0 = always, 1 = limited, 2 = never
|
|
||||||
"animated_artwork_qualityLevel": 1,
|
|
||||||
"bg_artwork_rotation": false,
|
|
||||||
"hw_acceleration": "default", // default, webgpu, disabled
|
|
||||||
"videoRes": 720
|
|
||||||
},
|
|
||||||
"lyrics": {
|
|
||||||
"enable_mxm": false,
|
|
||||||
"mxm_karaoke": false,
|
|
||||||
"mxm_language": "en",
|
|
||||||
"enable_yt": false,
|
|
||||||
},
|
|
||||||
"lastfm": {
|
|
||||||
"enabled": false,
|
|
||||||
"scrobble_after": 50,
|
|
||||||
"auth_token": "",
|
|
||||||
"enabledRemoveFeaturingArtists": true,
|
|
||||||
"NowPlaying": "true"
|
|
||||||
},
|
|
||||||
"advanced": {
|
|
||||||
"AudioContext": false,
|
|
||||||
"experiments": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const merge = (target, source) => {
|
|
||||||
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
|
||||||
for (const key of Object.keys(source)) {
|
|
||||||
if (source[key] instanceof Object) Object.assign(source[key], merge(target[key], source[key]))
|
|
||||||
}
|
|
||||||
// Join `target` and modified `source`
|
|
||||||
Object.assign(target || {}, source)
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const Store = require("electron-store");
|
|
||||||
app.cfg = new Store({
|
|
||||||
defaults: configDefaults
|
|
||||||
});
|
|
||||||
let currentCfg = app.cfg.get()
|
|
||||||
app.cfg.set(merge(configDefaults, currentCfg))
|
|
||||||
|
|
||||||
app.paths = {
|
|
||||||
ciderCache: resolve(app.getPath("userData"), "CiderCache"),
|
|
||||||
themes: resolve(app.getPath("userData"), "Themes"),
|
|
||||||
plugins: resolve(app.getPath("userData"), "Plugins"),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (app.cfg.get("visual.hw_acceleration")) {
|
|
||||||
default:
|
|
||||||
case "default":
|
|
||||||
app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
|
|
||||||
app.commandLine.appendSwitch('enable-accelerated-video')
|
|
||||||
app.commandLine.appendSwitch('disable-gpu-driver-bug-workarounds')
|
|
||||||
app.commandLine.appendSwitch('ignore-gpu-blacklist')
|
|
||||||
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers')
|
|
||||||
app.commandLine.appendSwitch('enable-accelerated-video-decode');
|
|
||||||
app.commandLine.appendSwitch('enable-gpu-rasterization');
|
|
||||||
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
|
|
||||||
app.commandLine.appendSwitch('enable-oop-rasterization');
|
|
||||||
break;
|
|
||||||
case "webgpu":
|
|
||||||
console.info("WebGPU is enabled.");
|
|
||||||
app.commandLine.appendSwitch('enable-unsafe-webgpu')
|
|
||||||
break;
|
|
||||||
case "disabled":
|
|
||||||
console.info("Hardware acceleration is disabled.");
|
|
||||||
app.commandLine.appendSwitch('disable-gpu')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating the Application Window and Calling all the Functions
|
|
||||||
function CreateWindow() {
|
|
||||||
if (app.isQuiting) {
|
|
||||||
app.quit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** CIDER **/
|
|
||||||
const ciderwin = require("./src/main/cider-base")
|
|
||||||
app.win = ciderwin
|
|
||||||
app.win.Start()
|
|
||||||
/** CIDER **/
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === "linux") {
|
|
||||||
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
|
||||||
}
|
|
||||||
|
|
||||||
app.commandLine.appendSwitch('no-sandbox');
|
|
||||||
// app.commandLine.appendSwitch('js-flags', '--max-old-space-size=1024')
|
|
||||||
|
|
||||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
* App Event Handlers
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
|
||||||
|
|
||||||
app.whenReady().then(async() => {
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
app.commandLine.appendSwitch('high-dpi-support', 'true')
|
|
||||||
app.commandLine.appendSwitch('force-device-scale-factor', '1')
|
|
||||||
app.commandLine.appendSwitch('disable-pinch');
|
|
||||||
}
|
|
||||||
if (comps == null) {
|
|
||||||
app.on("widevine-ready", () => {
|
|
||||||
console.log('[Cider] Application is Ready. Creating Window.')
|
|
||||||
if (!app.isPackaged) {
|
|
||||||
console.info('[Cider] Running in development mode.')
|
|
||||||
require('vue-devtools').install()
|
|
||||||
}
|
|
||||||
CreateWindow()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await comps.whenReady();
|
|
||||||
console.log('components ready:', comps.status());
|
|
||||||
|
|
||||||
console.log('[Cider] Application is Ready. Creating Window.')
|
|
||||||
if (!app.isPackaged) {
|
|
||||||
console.info('[Cider] Running in development mode.')
|
|
||||||
require('vue-devtools').install()
|
|
||||||
}
|
|
||||||
CreateWindow()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
app.on('before-quit', () => {
|
|
||||||
console.warn(`${app.getName()} exited.`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Widevine Stuff
|
|
||||||
app.on('widevine-ready', (version, lastVersion) => {
|
|
||||||
if (null !== lastVersion) {
|
|
||||||
console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
|
|
||||||
} else {
|
|
||||||
console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
|
|
||||||
console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('widevine-error', (error) => {
|
|
||||||
console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
|
|
||||||
app.exit()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (process.defaultApp) {
|
|
||||||
if (process.argv.length >= 2) {
|
|
||||||
customProtocols.forEach((customProtocol) => {
|
|
||||||
app.setAsDefaultProtocolClient(customProtocol, process.execPath, [resolve(process.argv[1])])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* cider - Custom Cider Protocol
|
|
||||||
* ame - Custom AME Protocol (Backwards Compat.)
|
|
||||||
* itms - iTunes HTTP Protocol
|
|
||||||
* itmss - iTunes HTTPS Protocol
|
|
||||||
* musics - macOS Client Protocol
|
|
||||||
* music - macOS Client Protocol
|
|
||||||
*/
|
|
||||||
customProtocols.forEach((customProtocol) => {
|
|
||||||
app.setAsDefaultProtocolClient(customProtocol)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('open-url', (event, url) => {
|
|
||||||
event.preventDefault()
|
|
||||||
if (customProtocols.some(protocol => url.includes(protocol))) {
|
|
||||||
CiderBase.LinkHandler(url)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('second-instance', (_e, argv) => {
|
|
||||||
console.warn(`[InstanceHandler][SecondInstanceHandler] Second Instance Started with args: [${argv.join(', ')}]`)
|
|
||||||
|
|
||||||
// Checks if first instance is authorized and if second instance has protocol args
|
|
||||||
argv.forEach((value) => {
|
|
||||||
if (customProtocols.some(protocol => value.includes(protocol))) {
|
|
||||||
CiderBase.LinkHandler(value);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (argv.includes("--force-quit")) {
|
|
||||||
console.warn('[InstanceHandler][SecondInstanceHandler] Force Quit found. Quitting App.');
|
|
||||||
// app.isQuiting = true
|
|
||||||
app.quit()
|
|
||||||
} else if (CiderBase.win && true) { // If a Second Instance has Been Started
|
|
||||||
console.warn('[InstanceHandler][SecondInstanceHandler] Showing window.');
|
|
||||||
app.win.show()
|
|
||||||
app.win.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!app.requestSingleInstanceLock() && true) {
|
|
||||||
console.warn("[InstanceHandler] Existing Instance is Blocking Second Instance.");
|
|
||||||
app.quit();
|
|
||||||
// app.isQuiting = true
|
|
||||||
}
|
|
33
package.json
|
@ -5,7 +5,8 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A new look into listening and enjoying music in style and performance.",
|
"description": "A new look into listening and enjoying music in style and performance.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Cider Collective <cryptofyre@cryptofyre.org> (https://cider.sh)",
|
"main": "./build/index.js",
|
||||||
|
"author": "Cider Collective <cryptofyre@cider.sh> (https://cider.sh)",
|
||||||
"repository": "https://github.com/ciderapp/Cider.git",
|
"repository": "https://github.com/ciderapp/Cider.git",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
|
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
|
||||||
|
@ -13,10 +14,14 @@
|
||||||
"homepage": "https://cider.sh/",
|
"homepage": "https://cider.sh/",
|
||||||
"buildResources": "resources",
|
"buildResources": "resources",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"init": "yarn install --force",
|
"build": "tsc",
|
||||||
"start": "electron . --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
"watch": "tsc --watch",
|
||||||
|
"start": "run-script-os",
|
||||||
|
"start:win32": "npm run build && set ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
|
"start:linux": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
|
"start:darwin": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
"dist": "electron-builder",
|
"dist": "npm run build && electron-builder",
|
||||||
"msft": "electron-builder -c msft-package.json",
|
"msft": "electron-builder -c msft-package.json",
|
||||||
"postinstall": "electron-builder install-app-deps"
|
"postinstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
|
@ -25,7 +30,7 @@
|
||||||
"discord-rpc": "^4.0.1",
|
"discord-rpc": "^4.0.1",
|
||||||
"ejs": "^3.1.6",
|
"ejs": "^3.1.6",
|
||||||
"electron-fetch": "^1.7.4",
|
"electron-fetch": "^1.7.4",
|
||||||
"electron-log": "^4.4.3",
|
"electron-log": "^4.4.4",
|
||||||
"electron-store": "^8.0.1",
|
"electron-store": "^8.0.1",
|
||||||
"electron-updater": "^4.6.1",
|
"electron-updater": "^4.6.1",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
|
@ -37,17 +42,20 @@
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"v8-compile-cache": "^2.3.0",
|
"v8-compile-cache": "^2.3.0",
|
||||||
"ws": "^8.3.0",
|
"ws": "^8.4.2",
|
||||||
"xml2js": "^0.4.23",
|
"xml2js": "^0.4.23",
|
||||||
"youtube-search-without-api-key": "^1.0.7"
|
"youtube-search-without-api-key": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
"electron": "https://github.com/castlabs/electron-releases.git",
|
"electron": "https://github.com/castlabs/electron-releases.git",
|
||||||
"electron-builder": "^22.14.5",
|
"electron-builder": "^22.14.5",
|
||||||
"electron-webpack": "^2.8.2",
|
"electron-webpack": "^2.8.2",
|
||||||
"musickit-typescript": "^1.2.4",
|
"musickit-typescript": "^1.2.4",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
"vue-devtools": "^5.1.4",
|
"vue-devtools": "^5.1.4",
|
||||||
"webpack": "~5.65.0"
|
"webpack": "~5.65.0"
|
||||||
},
|
},
|
||||||
|
@ -74,9 +82,9 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"build": {
|
"build": {
|
||||||
"electronVersion": "16.0.6",
|
"electronVersion": "16.0.7",
|
||||||
"electronDownload": {
|
"electronDownload": {
|
||||||
"version": "16.0.6+wvcus",
|
"version": "16.0.7+wvcus",
|
||||||
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
||||||
},
|
},
|
||||||
"appId": "cider",
|
"appId": "cider",
|
||||||
|
@ -95,9 +103,9 @@
|
||||||
],
|
],
|
||||||
"extends": null,
|
"extends": null,
|
||||||
"files": [
|
"files": [
|
||||||
"**/*",
|
"./build/**/*",
|
||||||
"./src/**/*",
|
"./resources/icons/icon.*",
|
||||||
"./resources/icons/icon.*"
|
"./src/**/*"
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": [
|
"target": [
|
||||||
|
@ -126,7 +134,8 @@
|
||||||
"icon": "resources/icons/icon.ico"
|
"icon": "resources/icons/icon.ico"
|
||||||
},
|
},
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "."
|
"buildResources": ".",
|
||||||
|
"output": "dist"
|
||||||
},
|
},
|
||||||
"mac": {
|
"mac": {
|
||||||
"icon": "./resources/icons/icon.icns",
|
"icon": "./resources/icons/icon.icns",
|
||||||
|
|
142
src/main/base/app.ts
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export class AppEvents {
|
||||||
|
private static protocols: any = [
|
||||||
|
"ame",
|
||||||
|
"cider",
|
||||||
|
"itms",
|
||||||
|
"itmss",
|
||||||
|
"musics",
|
||||||
|
"music"
|
||||||
|
]
|
||||||
|
|
||||||
|
private static store: any = null;
|
||||||
|
|
||||||
|
constructor(store: any) {
|
||||||
|
console.log('App started');
|
||||||
|
|
||||||
|
AppEvents.store = store
|
||||||
|
AppEvents.start(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all actions that occur for the app on start (Mainly commandline arguments)
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
private static start(store: any): void {
|
||||||
|
console.log('App started');
|
||||||
|
|
||||||
|
/**********************************************************************************************************************
|
||||||
|
* Startup arguments handling
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
if (electron.app.commandLine.hasSwitch('version') || electron.app.commandLine.hasSwitch('v')) {
|
||||||
|
console.log(electron.app.getVersion())
|
||||||
|
electron.app.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbose Check
|
||||||
|
if (electron.app.commandLine.hasSwitch('verbose')) {
|
||||||
|
console.log("[Apple-Music-Electron] User has launched the application with --verbose");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log File Location
|
||||||
|
if (electron.app.commandLine.hasSwitch('log') || electron.app.commandLine.hasSwitch('l')) {
|
||||||
|
console.log(path.join(electron.app.getPath('userData'), 'logs'))
|
||||||
|
electron.app.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************
|
||||||
|
* Commandline arguments
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
switch (store.get("visual.hw_acceleration")) {
|
||||||
|
default:
|
||||||
|
case "default":
|
||||||
|
electron.app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
|
||||||
|
electron.app.commandLine.appendSwitch('enable-accelerated-video')
|
||||||
|
electron.app.commandLine.appendSwitch('disable-gpu-driver-bug-workarounds')
|
||||||
|
electron.app.commandLine.appendSwitch('ignore-gpu-blacklist')
|
||||||
|
electron.app.commandLine.appendSwitch('enable-native-gpu-memory-buffers')
|
||||||
|
electron.app.commandLine.appendSwitch('enable-accelerated-video-decode');
|
||||||
|
electron.app.commandLine.appendSwitch('enable-gpu-rasterization');
|
||||||
|
electron.app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
|
||||||
|
electron.app.commandLine.appendSwitch('enable-oop-rasterization');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "webgpu":
|
||||||
|
console.info("WebGPU is enabled.");
|
||||||
|
electron.app.commandLine.appendSwitch('enable-unsafe-webgpu')
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "disabled":
|
||||||
|
console.info("Hardware acceleration is disabled.");
|
||||||
|
electron.app.commandLine.appendSwitch('disable-gpu')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************
|
||||||
|
* Protocols
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
if (process.defaultApp) {
|
||||||
|
if (process.argv.length >= 2) {
|
||||||
|
this.protocols.forEach((protocol: string) => {
|
||||||
|
electron.app.setAsDefaultProtocolClient(protocol, process.execPath, [path.resolve(process.argv[1])])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.protocols.forEach((protocol: string) => {
|
||||||
|
electron.app.setAsDefaultProtocolClient(protocol)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.app.on('open-url', (event, url) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (this.protocols.some((protocol: string) => url.includes(protocol))) {
|
||||||
|
AppEvents.LinkHandler(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public quit() {
|
||||||
|
console.log('App stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
public ready() {
|
||||||
|
console.log('App ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************
|
||||||
|
* Private methods
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
private static LinkHandler(arg: string) {
|
||||||
|
if (!arg) return;
|
||||||
|
|
||||||
|
// LastFM Auth URL
|
||||||
|
if (arg.includes('auth')) {
|
||||||
|
let authURI = String(arg).split('/auth/')[1]
|
||||||
|
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
||||||
|
const authKey = authURI.split('lastfm?token=')[1];
|
||||||
|
AppEvents.store.set('lastfm.enabled', true);
|
||||||
|
AppEvents.store.set('lastfm.auth_token', authKey);
|
||||||
|
// AppEvents.window.webContents.send('LastfmAuthenticated', authKey);
|
||||||
|
// lastfm.authenticate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Play
|
||||||
|
else if (arg.includes('/play/')) { //Steer away from protocol:// specific conditionals
|
||||||
|
|
||||||
|
const playParam = arg.split('/play/')[1]
|
||||||
|
if (playParam.includes('s/')) { // setQueue can be done with album, song, url, playlist id
|
||||||
|
console.log(playParam)
|
||||||
|
let song = playParam.split('s/')[1]
|
||||||
|
console.warn(`[LinkHandler] Attempting to load song by id: ${song}`);
|
||||||
|
// AppEvents.window.webContents.executeJavaScript(`
|
||||||
|
// MusicKit.getInstance().setQueue({ song: '${song}'}).then(function(queue) {
|
||||||
|
// MusicKit.getInstance().play();
|
||||||
|
// });
|
||||||
|
// `)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
src/main/base/plugins.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as electron from 'electron'
|
||||||
|
|
||||||
|
export default class PluginHandler {
|
||||||
|
private basePluginsPath = path.join(__dirname, '../plugins');
|
||||||
|
private userPluginsPath = path.join(electron.app.getPath('userData'), 'plugins');
|
||||||
|
private pluginsList: any = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
this.pluginsList = this.getPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPlugins(): any {
|
||||||
|
let plugins: any = {};
|
||||||
|
|
||||||
|
|
||||||
|
if (fs.existsSync(this.basePluginsPath)) {
|
||||||
|
fs.readdirSync(this.basePluginsPath).forEach(file => {
|
||||||
|
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||||
|
const plugin = require(path.join(this.basePluginsPath, file)).default;
|
||||||
|
if (plugins[file] || plugin.name in plugins) {
|
||||||
|
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||||
|
} else {
|
||||||
|
plugins[file] = new plugin(electron.app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (fs.existsSync(this.userPluginsPath)) {
|
||||||
|
fs.readdirSync(this.userPluginsPath).forEach(file => {
|
||||||
|
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||||
|
const plugin = require(path.join(this.userPluginsPath, file)).default;
|
||||||
|
if (plugins[file] || plugin in plugins) {
|
||||||
|
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||||
|
} else {
|
||||||
|
plugins[file] = new plugin(electron.app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('loaded plugins:', JSON.stringify(plugins))
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public callPlugins(event: string, ...args: any[]) {
|
||||||
|
for (const plugin in this.pluginsList) {
|
||||||
|
if (this.pluginsList[plugin][event]) {
|
||||||
|
this.pluginsList[plugin][event](...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
130
src/main/base/store.ts
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
import * as Store from 'electron-store';
|
||||||
|
import * as electron from "electron";
|
||||||
|
|
||||||
|
export class ConfigStore {
|
||||||
|
public store: Store | undefined;
|
||||||
|
|
||||||
|
private defaults: any = {
|
||||||
|
"general": {
|
||||||
|
"close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray
|
||||||
|
"startup_behavior": 0, // 0 = nothing, 1 = open on startup
|
||||||
|
"discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music
|
||||||
|
"discordClearActivityOnPause": 1 // 0 = disabled, 1 = enabled
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"followedArtists": [],
|
||||||
|
"favoriteItems": []
|
||||||
|
},
|
||||||
|
"libraryPrefs": {
|
||||||
|
"songs": {
|
||||||
|
"sort": "name",
|
||||||
|
"sortOrder": "asc",
|
||||||
|
"size": "normal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"volume": 1,
|
||||||
|
"quality": "990",
|
||||||
|
"seamless_audio": true,
|
||||||
|
"normalization": false,
|
||||||
|
"spatial": false,
|
||||||
|
"spatial_properties": {
|
||||||
|
"presets": [],
|
||||||
|
"gain": 0.8,
|
||||||
|
"listener_position": [0, 0, 0],
|
||||||
|
"audio_position": [0, 0, 0],
|
||||||
|
"room_dimensions": {
|
||||||
|
"width": 32,
|
||||||
|
"height": 12,
|
||||||
|
"depth": 32
|
||||||
|
},
|
||||||
|
"room_materials": {
|
||||||
|
"left": 'metal',
|
||||||
|
"right": 'metal',
|
||||||
|
"front": 'brick-bare',
|
||||||
|
"back": 'brick-bare',
|
||||||
|
"down": 'acoustic-ceiling-tiles',
|
||||||
|
"up": 'acoustic-ceiling-tiles',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual": {
|
||||||
|
"theme": "",
|
||||||
|
"scrollbars": 0, // 0 = show on hover, 2 = always hide, 3 = always show
|
||||||
|
"refresh_rate": 0,
|
||||||
|
"animated_artwork": "limited", // 0 = always, 1 = limited, 2 = never
|
||||||
|
"animated_artwork_qualityLevel": 1,
|
||||||
|
"bg_artwork_rotation": false,
|
||||||
|
"hw_acceleration": "default" // default, webgpu, disabled
|
||||||
|
},
|
||||||
|
"lyrics": {
|
||||||
|
"enable_mxm": false,
|
||||||
|
"mxm_karaoke": false,
|
||||||
|
"mxm_language": "en",
|
||||||
|
"enable_yt": false,
|
||||||
|
},
|
||||||
|
"lastfm": {
|
||||||
|
"enabled": false,
|
||||||
|
"scrobble_after": 30,
|
||||||
|
"auth_token": "",
|
||||||
|
"enabledRemoveFeaturingArtists": true,
|
||||||
|
"NowPlaying": "true"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"AudioContext": false,
|
||||||
|
"experiments": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private migrations: any = {}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.store = new Store({
|
||||||
|
name: 'cider-config',
|
||||||
|
defaults: this.defaults,
|
||||||
|
migrations: this.migrations,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store.set(this.mergeStore(this.defaults, this.store.store))
|
||||||
|
this.ipcHandler(this.store);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge Configurations
|
||||||
|
* @param target The target configuration
|
||||||
|
* @param source The source configuration
|
||||||
|
*/
|
||||||
|
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
|
||||||
|
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
||||||
|
for (const key of Object.keys(source)) {
|
||||||
|
if (key.includes('migrations')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
|
||||||
|
}
|
||||||
|
// Join `target` and modified `source`
|
||||||
|
Object.assign(target || {}, source)
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC Handler
|
||||||
|
*/
|
||||||
|
private ipcHandler(cfg: Store | any): void {
|
||||||
|
electron.ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
|
||||||
|
return (defaultValue ? cfg.get(key, true) : cfg.get(key));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle('setStoreValue', (event, key, value) => {
|
||||||
|
cfg.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('getStore', (event) => {
|
||||||
|
event.returnValue = cfg.store
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('setStore', (event, store) => {
|
||||||
|
cfg.store = store
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
443
src/main/base/win.ts
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
import * as path from "path";
|
||||||
|
import * as electron from "electron";
|
||||||
|
import * as windowStateKeeper from "electron-window-state";
|
||||||
|
import * as express from "express";
|
||||||
|
import * as getPort from "get-port";
|
||||||
|
import * as yt from "youtube-search-without-api-key";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { Stream } from "stream";
|
||||||
|
import * as qrcode from "qrcode-terminal";
|
||||||
|
import * as os from "os";
|
||||||
|
import {wsapi} from "./wsapi";
|
||||||
|
|
||||||
|
export class Win {
|
||||||
|
win: any | undefined = null;
|
||||||
|
app: any | undefined = null;
|
||||||
|
store: any | undefined = null;
|
||||||
|
devMode: boolean = !electron.app.isPackaged;
|
||||||
|
|
||||||
|
constructor(app: electron.App, store: any) {
|
||||||
|
this.app = app;
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private paths: any = {
|
||||||
|
srcPath: path.join(__dirname, "../../src"),
|
||||||
|
resourcePath: path.join(__dirname, "../../resources"),
|
||||||
|
ciderCache: path.resolve(electron.app.getPath("userData"), "CiderCache"),
|
||||||
|
themes: path.resolve(electron.app.getPath("userData"), "Themes"),
|
||||||
|
plugins: path.resolve(electron.app.getPath("userData"), "Plugins"),
|
||||||
|
};
|
||||||
|
private audioStream: any = new Stream.PassThrough();
|
||||||
|
private clientPort: number = 0;
|
||||||
|
private remotePort: number = 6942;
|
||||||
|
private EnvironmentVariables: object = {
|
||||||
|
env: {
|
||||||
|
platform: process.platform,
|
||||||
|
dev: electron.app.isPackaged,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
private options: any = {
|
||||||
|
icon: path.join(
|
||||||
|
this.paths.resourcePath,
|
||||||
|
`icons/icon.` + (process.platform === "win32" ? "ico" : "png")
|
||||||
|
),
|
||||||
|
width: 1024,
|
||||||
|
height: 600,
|
||||||
|
x: undefined,
|
||||||
|
y: undefined,
|
||||||
|
minWidth: 844,
|
||||||
|
minHeight: 410,
|
||||||
|
frame: false,
|
||||||
|
title: "Cider",
|
||||||
|
vibrancy: "dark",
|
||||||
|
transparent: process.platform === "darwin",
|
||||||
|
hasShadow: false,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
sandbox: true,
|
||||||
|
allowRunningInsecureContent: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
|
||||||
|
webviewTag: true,
|
||||||
|
plugins: true,
|
||||||
|
nodeIntegrationInWorker: false,
|
||||||
|
webSecurity: false,
|
||||||
|
|
||||||
|
preload: path.join(this.paths.srcPath, "./preload/cider-preload.js"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the browser window
|
||||||
|
*/
|
||||||
|
async createWindow(): Promise<void> {
|
||||||
|
this.clientPort = await getPort({ port: 9000 });
|
||||||
|
this.verifyFiles();
|
||||||
|
|
||||||
|
// Load the previous state with fallback to defaults
|
||||||
|
const windowState = windowStateKeeper({
|
||||||
|
defaultWidth: 1024,
|
||||||
|
defaultHeight: 600,
|
||||||
|
});
|
||||||
|
this.options.width = windowState.width;
|
||||||
|
this.options.height = windowState.height;
|
||||||
|
|
||||||
|
// Start the webserver for the browser window to load
|
||||||
|
const ws = new wsapi()
|
||||||
|
ws.InitWebSockets()
|
||||||
|
this.startWebServer();
|
||||||
|
|
||||||
|
this.win = new electron.BrowserWindow(this.options);
|
||||||
|
|
||||||
|
// and load the renderer.
|
||||||
|
this.startSession();
|
||||||
|
this.startHandlers();
|
||||||
|
|
||||||
|
// Register listeners on Window to track size and position of the Window.
|
||||||
|
windowState.manage(this.win);
|
||||||
|
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the files for the renderer to use (Cache, library info, etc.)
|
||||||
|
*/
|
||||||
|
private verifyFiles(): void {
|
||||||
|
const expectedDirectories = ["CiderCache"];
|
||||||
|
const expectedFiles = [
|
||||||
|
"library-songs.json",
|
||||||
|
"library-artists.json",
|
||||||
|
"library-albums.json",
|
||||||
|
"library-playlists.json",
|
||||||
|
"library-recentlyAdded.json",
|
||||||
|
];
|
||||||
|
for (let i = 0; i < expectedDirectories.length; i++) {
|
||||||
|
if (
|
||||||
|
!fs.existsSync(
|
||||||
|
path.join(electron.app.getPath("userData"), expectedDirectories[i])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fs.mkdirSync(
|
||||||
|
path.join(electron.app.getPath("userData"), expectedDirectories[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < expectedFiles.length; i++) {
|
||||||
|
const file = path.join(this.paths.ciderCache, expectedFiles[i]);
|
||||||
|
if (!fs.existsSync(file)) {
|
||||||
|
fs.writeFileSync(file, JSON.stringify([]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the webserver for the renderer process.
|
||||||
|
*/
|
||||||
|
private startWebServer(): void {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.static(path.join(this.paths.srcPath, "./renderer/")));
|
||||||
|
app.set("views", path.join(this.paths.srcPath, "./renderer/views"));
|
||||||
|
app.set("view engine", "ejs");
|
||||||
|
let firstRequest = true;
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (
|
||||||
|
req.url.includes("audio.webm") ||
|
||||||
|
(req.headers.host.includes("localhost") &&
|
||||||
|
(this.devMode || req.headers["user-agent"].includes("Electron")))
|
||||||
|
) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect("https://discord.gg/applemusic");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
|
res.render("main", this.EnvironmentVariables);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/audio.webm", (req, res) => {
|
||||||
|
try {
|
||||||
|
req.socket.setTimeout(Number.MAX_SAFE_INTEGER);
|
||||||
|
// CiderBase.requests.push({req: req, res: res});
|
||||||
|
// var pos = CiderBase.requests.length - 1;
|
||||||
|
// req.on("close", () => {
|
||||||
|
// console.info("CLOSED", CiderBase.requests.length);
|
||||||
|
// requests.splice(pos, 1);
|
||||||
|
// console.info("CLOSED", CiderBase.requests.length);
|
||||||
|
// });
|
||||||
|
this.audioStream.on("data", (data: any) => {
|
||||||
|
try {
|
||||||
|
res.write(data);
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//app.use(express.static())
|
||||||
|
|
||||||
|
app.listen(this.clientPort, () => {
|
||||||
|
console.log(`Cider client port: ${this.clientPort}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remote Client (I had no idea how to add it to our existing express server, so I just made another one) -@quacksire
|
||||||
|
* TODO: Broadcast the remote so that /web-remote/ can connect
|
||||||
|
* https://github.com/ciderapp/Apple-Music-Electron/blob/818ed18940ff600d76eb59d22016723a75885cd5/resources/functions/handler.js#L1173
|
||||||
|
*/
|
||||||
|
const remote = express();
|
||||||
|
remote.use(express.static(path.join(this.paths.srcPath, "./web-remote/")))
|
||||||
|
remote.listen(this.remotePort, () => {
|
||||||
|
console.log(`Cider remote port: ${this.remotePort}`);
|
||||||
|
if (firstRequest) {
|
||||||
|
console.log("---- Ignore Me ;) ---");
|
||||||
|
qrcode.generate(`http://${os.hostname}:${this.remotePort}`);
|
||||||
|
console.log("---- Ignore Me ;) ---");
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* USING https://www.npmjs.com/package/qrcode-terminal for terminal
|
||||||
|
* WE SHOULD USE https://www.npmjs.com/package/qrcode for the remote (or others) for showing to user via an in-app dialog
|
||||||
|
* -@quacksire
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
firstRequest = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the session for the renderer process.
|
||||||
|
*/
|
||||||
|
private startSession(): void {
|
||||||
|
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
||||||
|
this.win.webContents.session.webRequest.onBeforeRequest(
|
||||||
|
{
|
||||||
|
urls: ["https://*/*.js"],
|
||||||
|
},
|
||||||
|
(
|
||||||
|
details: { url: string | string[] },
|
||||||
|
callback: (arg0: { redirectURL?: string; cancel?: boolean }) => void
|
||||||
|
) => {
|
||||||
|
if (details.url.includes("hls.js")) {
|
||||||
|
callback({
|
||||||
|
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback({
|
||||||
|
cancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.win.webContents.session.webRequest.onBeforeSendHeaders(
|
||||||
|
async (
|
||||||
|
details: { url: string; requestHeaders: { [x: string]: string } },
|
||||||
|
callback: (arg0: { requestHeaders: any }) => void
|
||||||
|
) => {
|
||||||
|
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
||||||
|
details.requestHeaders["sec-fetch-site"] = "same-site";
|
||||||
|
details.requestHeaders["DNT"] = "1";
|
||||||
|
let itspod = await this.win.webContents.executeJavaScript(
|
||||||
|
`window.localStorage.getItem("music.ampwebplay.itspod")`
|
||||||
|
);
|
||||||
|
if (itspod != null)
|
||||||
|
details.requestHeaders["Cookie"] = `itspod=${itspod}`;
|
||||||
|
}
|
||||||
|
callback({ requestHeaders: details.requestHeaders });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let location = `http://localhost:${this.clientPort}/`;
|
||||||
|
|
||||||
|
if (electron.app.isPackaged) {
|
||||||
|
this.win.loadURL(location);
|
||||||
|
} else {
|
||||||
|
this.win.loadURL(location, {
|
||||||
|
userAgent: "Cider Development Environment",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the window handlers
|
||||||
|
*/
|
||||||
|
private startHandlers(): void {
|
||||||
|
/**********************************************************************************************************************
|
||||||
|
* ipcMain Events
|
||||||
|
****************************************************************************************************************** */
|
||||||
|
electron.ipcMain.on("cider-platform", (event) => {
|
||||||
|
event.returnValue = process.platform;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-gpu-mode", (event) => {
|
||||||
|
event.returnValue = process.platform;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("is-dev", (event) => {
|
||||||
|
event.returnValue = this.devMode;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("close", () => {
|
||||||
|
// listen for close event
|
||||||
|
this.win.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-songs", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-songs.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-artists", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-artists.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-albums", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-albums.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-playlists", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-playlists.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-recentlyAdded", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-songs", (event) => {
|
||||||
|
let librarySongs = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-songs.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(librarySongs);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-artists", (event) => {
|
||||||
|
let libraryArtists = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-artists.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryArtists);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-albums", (event) => {
|
||||||
|
let libraryAlbums = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-albums.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryAlbums);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-playlists", (event) => {
|
||||||
|
let libraryPlaylists = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-playlists.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryPlaylists);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-recentlyAdded", (event) => {
|
||||||
|
let libraryRecentlyAdded = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryRecentlyAdded);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("getYTLyrics", async (event, track, artist) => {
|
||||||
|
const u = track + " " + artist + " official video";
|
||||||
|
return await yt.search(u);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("setVibrancy", (event, key, value) => {
|
||||||
|
this.win.setVibrancy(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("maximize", () => {
|
||||||
|
// listen for maximize event
|
||||||
|
if (this.win.isMaximized()) {
|
||||||
|
this.win.unmaximize();
|
||||||
|
} else {
|
||||||
|
this.win.maximize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("minimize", () => {
|
||||||
|
// listen for minimize event
|
||||||
|
this.win.minimize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set scale
|
||||||
|
electron.ipcMain.on("setScreenScale", (event, scale) => {
|
||||||
|
this.win.webContents.setZoomFactor(parseFloat(scale));
|
||||||
|
});
|
||||||
|
|
||||||
|
/* *********************************************************************************************
|
||||||
|
* Window Events
|
||||||
|
* **********************************************************************************************/
|
||||||
|
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
let WND_STATE = {
|
||||||
|
MINIMIZED: 0,
|
||||||
|
NORMAL: 1,
|
||||||
|
MAXIMIZED: 2,
|
||||||
|
FULL_SCREEN: 3,
|
||||||
|
};
|
||||||
|
let wndState = WND_STATE.NORMAL;
|
||||||
|
|
||||||
|
this.win.on("resize", (_: any) => {
|
||||||
|
const isMaximized = this.win.isMaximized();
|
||||||
|
const isMinimized = this.win.isMinimized();
|
||||||
|
const isFullScreen = this.win.isFullScreen();
|
||||||
|
const state = wndState;
|
||||||
|
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
||||||
|
wndState = WND_STATE.MINIMIZED;
|
||||||
|
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
||||||
|
wndState = WND_STATE.FULL_SCREEN;
|
||||||
|
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
||||||
|
wndState = WND_STATE.MAXIMIZED;
|
||||||
|
this.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
|
||||||
|
} else if (state !== WND_STATE.NORMAL) {
|
||||||
|
wndState = WND_STATE.NORMAL;
|
||||||
|
this.win.webContents.executeJavaScript(
|
||||||
|
`app.chrome.maximized = false`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.win.on("closed", () => {
|
||||||
|
this.win = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set window Handler
|
||||||
|
this.win.webContents.setWindowOpenHandler((x: any) => {
|
||||||
|
if (x.url.includes("apple") || x.url.includes("localhost")) {
|
||||||
|
return { action: "allow" };
|
||||||
|
}
|
||||||
|
electron.shell.openExternal(x.url).catch(console.error);
|
||||||
|
return { action: "deny" };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
284
src/main/base/wsapi.ts
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import * as ws from "ws";
|
||||||
|
import * as http from "http";
|
||||||
|
import * as https from "https";
|
||||||
|
import * as url from "url";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as electron from "electron";
|
||||||
|
const WebSocket = ws;
|
||||||
|
const WebSocketServer = ws.Server;
|
||||||
|
|
||||||
|
private class standardResponse {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
data: any;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class wsapi {
|
||||||
|
port: any = 26369
|
||||||
|
wss: any = null
|
||||||
|
clients: []
|
||||||
|
createId() {
|
||||||
|
// create random guid
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async InitWebSockets () {
|
||||||
|
electron.ipcMain.on('wsapi-updatePlaybackState', (event, arg) => {
|
||||||
|
wsapi.updatePlaybackState(arg);
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnQueue', (event, arg) => {
|
||||||
|
wsapi.returnQueue(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnSearch', (event, arg) => {
|
||||||
|
console.log("SEARCH")
|
||||||
|
wsapi.returnSearch(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnSearchLibrary', (event, arg) => {
|
||||||
|
wsapi.returnSearchLibrary(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnDynamic', (event, arg, type) => {
|
||||||
|
wsapi.returnDynamic(JSON.parse(arg), type);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnMusicKitApi', (event, arg, method) => {
|
||||||
|
wsapi.returnMusicKitApi(JSON.parse(arg), method);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnLyrics', (event, arg) => {
|
||||||
|
wsapi.returnLyrics(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
this.wss = new WebSocketServer({
|
||||||
|
port: this.port,
|
||||||
|
perMessageDeflate: {
|
||||||
|
zlibDeflateOptions: {
|
||||||
|
// See zlib defaults.
|
||||||
|
chunkSize: 1024,
|
||||||
|
memLevel: 7,
|
||||||
|
level: 3
|
||||||
|
},
|
||||||
|
zlibInflateOptions: {
|
||||||
|
chunkSize: 10 * 1024
|
||||||
|
},
|
||||||
|
// Other options settable:
|
||||||
|
clientNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverMaxWindowBits: 10, // Defaults to negotiated value.
|
||||||
|
// Below options specified as default values.
|
||||||
|
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
||||||
|
threshold: 1024 // Size (in bytes) below which messages
|
||||||
|
// should not be compressed if context takeover is disabled.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(`WebSocketServer started on port: ${this.port}`);
|
||||||
|
|
||||||
|
const defaultResponse = new standardResponse(0, {}, "OK");
|
||||||
|
|
||||||
|
|
||||||
|
this.wss.on('connection', function connection(ws) {
|
||||||
|
ws.id = wsapi.createId();
|
||||||
|
console.log(`Client ${ws.id} connected`)
|
||||||
|
wsapi.clients.push(ws);
|
||||||
|
ws.on('message', function incoming(message) {
|
||||||
|
|
||||||
|
});
|
||||||
|
// ws on message
|
||||||
|
ws.on('message', function incoming(message) {
|
||||||
|
let data = JSON.parse(message);
|
||||||
|
let response = new standardResponse(0, {}, "OK");;
|
||||||
|
if (data.action) {
|
||||||
|
data.action.toLowerCase();
|
||||||
|
}
|
||||||
|
switch (data.action) {
|
||||||
|
default:
|
||||||
|
response.message = "Action not found";
|
||||||
|
break;
|
||||||
|
case "identify":
|
||||||
|
response.message = "Thanks for identifying!"
|
||||||
|
response.data = {
|
||||||
|
id: ws.id
|
||||||
|
}
|
||||||
|
ws.identity = {
|
||||||
|
name: data.name,
|
||||||
|
author: data.author,
|
||||||
|
description: data.description,
|
||||||
|
version: data.version
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "play-next":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`);
|
||||||
|
response.message = "Play Next";
|
||||||
|
break;
|
||||||
|
case "play-later":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`);
|
||||||
|
response.message = "Play Later";
|
||||||
|
break;
|
||||||
|
case "quick-play":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`);
|
||||||
|
response.message = "Quick Play";
|
||||||
|
break;
|
||||||
|
case "get-lyrics":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.getLyrics()`);
|
||||||
|
break;
|
||||||
|
case "shuffle":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
|
||||||
|
break;
|
||||||
|
case "set-shuffle":
|
||||||
|
if(data.shuffle == true) {
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
|
||||||
|
}else{
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "repeat":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
|
||||||
|
break;
|
||||||
|
case "seek":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
|
||||||
|
response.message = "Seek";
|
||||||
|
break;
|
||||||
|
case "pause":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
|
||||||
|
response.message = "Paused";
|
||||||
|
break;
|
||||||
|
case "play":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
|
||||||
|
response.message = "Playing";
|
||||||
|
break;
|
||||||
|
case "stop":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
|
||||||
|
response.message = "Stopped";
|
||||||
|
break;
|
||||||
|
case "volume":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
|
||||||
|
response.message = "Volume";
|
||||||
|
break;
|
||||||
|
case "mute":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
|
||||||
|
response.message = "Muted";
|
||||||
|
break;
|
||||||
|
case "unmute":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
|
||||||
|
response.message = "Unmuted";
|
||||||
|
break;
|
||||||
|
case "next":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().skipToNextItem()`);
|
||||||
|
response.message = "Next";
|
||||||
|
break;
|
||||||
|
case "previous":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().skipToPreviousItem()`);
|
||||||
|
response.message = "Previous";
|
||||||
|
break;
|
||||||
|
case "musickit-api":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)})`);
|
||||||
|
break;
|
||||||
|
case "musickit-library-api":
|
||||||
|
break;
|
||||||
|
case "set-autoplay":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
|
||||||
|
break;
|
||||||
|
case "queue-move":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
|
||||||
|
break;
|
||||||
|
case "get-queue":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.getQueue()`);
|
||||||
|
break;
|
||||||
|
case "search":
|
||||||
|
if (!data.limit) {
|
||||||
|
data.limit = 10;
|
||||||
|
}
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
|
||||||
|
break;
|
||||||
|
case "library-search":
|
||||||
|
if (!data.limit) {
|
||||||
|
data.limit = 10;
|
||||||
|
}
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
|
||||||
|
break;
|
||||||
|
case "show-window":
|
||||||
|
electron.app.win.show()
|
||||||
|
break;
|
||||||
|
case "hide-window":
|
||||||
|
electron.app.win.hide()
|
||||||
|
break;
|
||||||
|
case "play-mediaitem":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.playTrackById(${data.id}, \`${data.kind}\`)`);
|
||||||
|
response.message = "Playing track";
|
||||||
|
break;
|
||||||
|
case "get-status":
|
||||||
|
response.data = {
|
||||||
|
isAuthorized: true
|
||||||
|
};
|
||||||
|
response.message = "Status";
|
||||||
|
break;
|
||||||
|
case "get-currentmediaitem":
|
||||||
|
electron.app.win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ws.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', function close() {
|
||||||
|
// remove client from list
|
||||||
|
wsapi.clients.splice(wsapi.clients.indexOf(ws), 1);
|
||||||
|
console.log(`Client ${ws.id} disconnected`);
|
||||||
|
});
|
||||||
|
ws.send(JSON.stringify(defaultResponse));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sendToClient(id) {
|
||||||
|
// replace the clients.forEach with a filter to find the client that requested
|
||||||
|
}
|
||||||
|
updatePlaybackState(attr) {
|
||||||
|
const response = new standardResponse(0, attr, "OK", "playbackStateUpdate");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnMusicKitApi(results, method) {
|
||||||
|
const response = new standardResponse(0, results, "OK", `musickitapi.${method}`);
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnDynamic(results, type) {
|
||||||
|
const response = new standardResponse(0, results, "OK", type);
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnLyrics(results) {
|
||||||
|
const response = new standardResponse(0, results, "OK", "lyrics");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnSearch(results) {
|
||||||
|
const response = new standardResponse(0, results, "OK", "searchResults");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnSearchLibrary(results) {
|
||||||
|
const response = new standardResponse(0, results, "OK", "searchResultsLibrary");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnQueue(queue) {
|
||||||
|
const response = new standardResponse(0, queue, "OK", "queue");
|
||||||
|
wsapi.clients.forEach(function each(client) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,446 +0,0 @@
|
||||||
const { BrowserWindow, ipcMain, shell, app, screen } = require("electron")
|
|
||||||
const { join } = require("path")
|
|
||||||
const getPort = require("get-port");
|
|
||||||
const express = require("express");
|
|
||||||
const path = require("path");
|
|
||||||
const windowStateKeeper = require("electron-window-state");
|
|
||||||
const os = require('os');
|
|
||||||
const yt = require('youtube-search-without-api-key');
|
|
||||||
const discord = require('./discordrpc');
|
|
||||||
const lastfm = require('./lastfm');
|
|
||||||
const { writeFile, writeFileSync, existsSync, mkdirSync } = require('fs');
|
|
||||||
const fs = require('fs');
|
|
||||||
const mpris = require('./mpris');
|
|
||||||
const mm = require('music-metadata');
|
|
||||||
//const mdns = require('mdns')
|
|
||||||
const qrcode = require('qrcode-terminal')
|
|
||||||
const fetch = require('electron-fetch').default;
|
|
||||||
const { Stream } = require('stream');
|
|
||||||
|
|
||||||
// Analytics for debugging.
|
|
||||||
const ElectronSentry = require("@sentry/electron");
|
|
||||||
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" });
|
|
||||||
|
|
||||||
const CiderBase = {
|
|
||||||
win: null,
|
|
||||||
requests: [],
|
|
||||||
audiostream: new Stream.PassThrough(),
|
|
||||||
async Start() {
|
|
||||||
this.clientPort = await getPort({ port: 9000 });
|
|
||||||
this.win = this.CreateBrowserWindow()
|
|
||||||
},
|
|
||||||
clientPort: 0,
|
|
||||||
CreateBrowserWindow() {
|
|
||||||
this.VerifyFiles()
|
|
||||||
// Set default window sizes
|
|
||||||
const mainWindowState = windowStateKeeper({
|
|
||||||
defaultWidth: 1024,
|
|
||||||
defaultHeight: 600
|
|
||||||
});
|
|
||||||
|
|
||||||
let win = null
|
|
||||||
const options = {
|
|
||||||
icon: join(__dirname, `../../resources/icons/icon.` + (process.platform === "win32" ? "ico" : "png")),
|
|
||||||
width: mainWindowState.width,
|
|
||||||
height: mainWindowState.height,
|
|
||||||
x: mainWindowState.x,
|
|
||||||
y: mainWindowState.y,
|
|
||||||
minWidth: 844,
|
|
||||||
minHeight: 410,
|
|
||||||
frame: false,
|
|
||||||
title: "Cider",
|
|
||||||
vibrancy: 'dark',
|
|
||||||
// transparent: true,
|
|
||||||
hasShadow: false,
|
|
||||||
webPreferences: {
|
|
||||||
webviewTag: true,
|
|
||||||
plugins: true,
|
|
||||||
nodeIntegration: true,
|
|
||||||
nodeIntegrationInWorker: false,
|
|
||||||
webSecurity: false,
|
|
||||||
allowRunningInsecureContent: true,
|
|
||||||
enableRemoteModule: true,
|
|
||||||
sandbox: true,
|
|
||||||
nativeWindowOpen: true,
|
|
||||||
contextIsolation: false,
|
|
||||||
preload: join(__dirname, '../preload/cider-preload.js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CiderBase.InitWebServer()
|
|
||||||
|
|
||||||
// Create the BrowserWindow
|
|
||||||
win = new BrowserWindow(options)
|
|
||||||
|
|
||||||
// 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({
|
|
||||||
urls: ["https://*/*.js"]
|
|
||||||
},
|
|
||||||
(details, callback) => {
|
|
||||||
if (details.url.includes("hls.js")) {
|
|
||||||
callback({
|
|
||||||
redirectURL: `http://localhost:${CiderBase.clientPort}/apple-hls.js`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
callback({
|
|
||||||
cancel: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
win.webContents.session.webRequest.onBeforeSendHeaders(async(details, callback) => {
|
|
||||||
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
|
||||||
details.requestHeaders['sec-fetch-site'] = 'same-site';
|
|
||||||
details.requestHeaders['DNT'] = '1';
|
|
||||||
let itspod = await win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`)
|
|
||||||
if (itspod != null)
|
|
||||||
details.requestHeaders['Cookie'] = `itspod=${itspod}`
|
|
||||||
}
|
|
||||||
callback({ requestHeaders: details.requestHeaders })
|
|
||||||
})
|
|
||||||
|
|
||||||
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
|
||||||
if (details.url.match(/^https:\/\/store-\d{3}\.blobstore\.apple\.com/) || details.url.startsWith("https://store-037.blobstore.apple.com")) {
|
|
||||||
details.responseHeaders['Access-Control-Allow-Origin'] = '*';
|
|
||||||
}
|
|
||||||
callback({ responseHeaders: details.responseHeaders })
|
|
||||||
})
|
|
||||||
|
|
||||||
let location = `http://localhost:${CiderBase.clientPort}/`
|
|
||||||
win.loadURL(location)
|
|
||||||
win.on("closed", () => {
|
|
||||||
win = null
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register listeners on Window to track size and position of the Window.
|
|
||||||
mainWindowState.manage(win);
|
|
||||||
|
|
||||||
// IPC stuff (senders)
|
|
||||||
ipcMain.on("cider-platform", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on("get-gpu-mode", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on("is-dev", (event) => {
|
|
||||||
event.returnValue = !app.isPackaged
|
|
||||||
})
|
|
||||||
|
|
||||||
// IPC stuff (listeners)
|
|
||||||
ipcMain.on('close', () => { // listen for close event
|
|
||||||
win.close();
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-songs', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-songs.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-artists', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-artists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-albums', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-albums.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-playlists', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-playlists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-recentlyAdded', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-songs', (event) => {
|
|
||||||
let librarySongs = fs.readFileSync(join(app.paths.ciderCache, "library-songs.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(librarySongs)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-artists', (event) => {
|
|
||||||
let libraryArtists = fs.readFileSync(join(app.paths.ciderCache, "library-artists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryArtists)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-albums', (event) => {
|
|
||||||
let libraryAlbums = fs.readFileSync(join(app.paths.ciderCache, "library-albums.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryAlbums)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-playlists', (event) => {
|
|
||||||
let libraryPlaylists = fs.readFileSync(join(app.paths.ciderCache, "library-playlists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryPlaylists)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-recentlyAdded', (event) => {
|
|
||||||
let libraryRecentlyAdded = fs.readFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryRecentlyAdded)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('getYTLyrics', async(event, track, artist) => {
|
|
||||||
var u = track + " " + artist + " official video";
|
|
||||||
const videos = await yt.search(u);
|
|
||||||
return videos
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
|
|
||||||
return (defaultValue ? app.cfg.get(key, true) : app.cfg.get(key));
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('setStoreValue', (event, key, value) => {
|
|
||||||
app.cfg.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('getStore', (event) => {
|
|
||||||
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
|
|
||||||
if (win.isMaximized()) {
|
|
||||||
win.unmaximize()
|
|
||||||
} else {
|
|
||||||
win.maximize()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('minimize', () => { // listen for minimize event
|
|
||||||
win.minimize();
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('setFullScreen', (event, flag) => {
|
|
||||||
win.setFullScreen(flag)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
let WND_STATE = {
|
|
||||||
MINIMIZED: 0,
|
|
||||||
NORMAL: 1,
|
|
||||||
MAXIMIZED: 2,
|
|
||||||
FULL_SCREEN: 3
|
|
||||||
}
|
|
||||||
let wndState = WND_STATE.NORMAL
|
|
||||||
|
|
||||||
win.on("resize", (_event) => {
|
|
||||||
const isMaximized = win.isMaximized()
|
|
||||||
const isMinimized = win.isMinimized()
|
|
||||||
const isFullScreen = win.isFullScreen()
|
|
||||||
const state = wndState;
|
|
||||||
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
|
||||||
wndState = WND_STATE.MINIMIZED
|
|
||||||
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
|
||||||
wndState = WND_STATE.FULL_SCREEN
|
|
||||||
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
|
||||||
wndState = WND_STATE.MAXIMIZED
|
|
||||||
win.webContents.executeJavaScript(`app.chrome.maximized = true`)
|
|
||||||
} else if (state !== WND_STATE.NORMAL) {
|
|
||||||
wndState = WND_STATE.NORMAL
|
|
||||||
win.webContents.executeJavaScript(`app.chrome.maximized = false`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set window Handler
|
|
||||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
||||||
if (url.includes("apple") || url.includes("localhost")) {
|
|
||||||
return { action: "allow" }
|
|
||||||
}
|
|
||||||
shell.openExternal(url).catch(() => {})
|
|
||||||
return {
|
|
||||||
action: 'deny'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set scale
|
|
||||||
ipcMain.on('setScreenScale', (event, scale) => {
|
|
||||||
win.webContents.setZoomFactor(parseFloat(scale))
|
|
||||||
})
|
|
||||||
|
|
||||||
win.webContents.setZoomFactor(screen.getPrimaryDisplay().scaleFactor)
|
|
||||||
|
|
||||||
mpris.connect(win)
|
|
||||||
mpris.SetButtons(win, false)
|
|
||||||
|
|
||||||
lastfm.authenticate()
|
|
||||||
// Discord
|
|
||||||
discord.connect((app.cfg.get("general.discord_rpc") == 1) ? '911790844204437504' : '886578863147192350');
|
|
||||||
ipcMain.on('playbackStateDidChange', (_event, a) => {
|
|
||||||
app.media = a;
|
|
||||||
discord.updateActivity(a)
|
|
||||||
mpris.SetButtons(win, a)
|
|
||||||
mpris.updateState(a)
|
|
||||||
lastfm.scrobbleSong(a)
|
|
||||||
lastfm.updateNowPlayingSong(a)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('nowPlayingItemDidChange', (_event, a) => {
|
|
||||||
app.media = a;
|
|
||||||
discord.updateActivity(a)
|
|
||||||
mpris.SetButtons(win, a)
|
|
||||||
mpris.updateAttributes(a)
|
|
||||||
lastfm.scrobbleSong(a)
|
|
||||||
lastfm.updateNowPlayingSong(a)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on("getPreviewURL", (_event, url) => {
|
|
||||||
fetch(url)
|
|
||||||
.then(res => res.buffer())
|
|
||||||
.then(async(buffer) => {
|
|
||||||
try {
|
|
||||||
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a');
|
|
||||||
SoundCheckTag = metadata.native.iTunes[1].value
|
|
||||||
win.webContents.send('SoundCheckTag', SoundCheckTag)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('writeAudio', function(event, buffer) {
|
|
||||||
CiderBase.audiostream.write(Buffer.from(buffer));
|
|
||||||
})
|
|
||||||
|
|
||||||
return win
|
|
||||||
},
|
|
||||||
VerifyFiles() {
|
|
||||||
const expectedDirectories = [
|
|
||||||
"CiderCache"
|
|
||||||
]
|
|
||||||
const expectedFiles = [
|
|
||||||
"library-songs.json",
|
|
||||||
"library-artists.json",
|
|
||||||
"library-albums.json",
|
|
||||||
"library-playlists.json",
|
|
||||||
"library-recentlyAdded.json",
|
|
||||||
]
|
|
||||||
for (let i = 0; i < expectedDirectories.length; i++) {
|
|
||||||
if (!existsSync(path.join(app.getPath("userData"), expectedDirectories[i]))) {
|
|
||||||
mkdirSync(path.join(app.getPath("userData"), expectedDirectories[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < expectedFiles.length; i++) {
|
|
||||||
const file = path.join(app.paths.ciderCache, expectedFiles[i])
|
|
||||||
if (!existsSync(file)) {
|
|
||||||
writeFileSync(file, JSON.stringify([]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
EnvironmentVariables: {
|
|
||||||
"env": {
|
|
||||||
platform: os.platform(),
|
|
||||||
dev: app.isPackaged
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LinkHandler: (startArgs) => {
|
|
||||||
if (!startArgs) return;
|
|
||||||
|
|
||||||
if (String(startArgs).includes('auth')) {
|
|
||||||
let authURI = String(startArgs).split('/auth/')[1]
|
|
||||||
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
|
||||||
console.log("lfmtoken", String(startArgs))
|
|
||||||
const authKey = authURI.split('lastfm?token=')[1];
|
|
||||||
app.cfg.set('lastfm.enabled', true);
|
|
||||||
app.cfg.set('lastfm.auth_token', authKey);
|
|
||||||
CiderBase.win.webContents.send('LastfmAuthenticated', authKey);
|
|
||||||
lastfm.authenticate()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (String(startArgs).includes('/play/')) { //Steer away from protocal:// specific conditionals
|
|
||||||
const playParam = String(startArgs).split('/play/')[1]
|
|
||||||
if (playParam.includes('s/')) { // setQueue can be done with album, song, url, playlist id
|
|
||||||
console.log(playParam)
|
|
||||||
let song = playParam.split('s/')[1]
|
|
||||||
console.warn(`[LinkHandler] Attempting to load song by id: ${song}`);
|
|
||||||
this.win.webContents.executeJavaScript(`
|
|
||||||
MusicKit.getInstance().setQueue({ song: '${song}'}).then(function(queue) {
|
|
||||||
MusicKit.getInstance().play();
|
|
||||||
});
|
|
||||||
`).catch((err) => console.error(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
async InitWebServer() {
|
|
||||||
const webapp = express();
|
|
||||||
const webRemotePath = path.join(__dirname, '../renderer/');
|
|
||||||
webapp.set("views", path.join(webRemotePath, "views"));
|
|
||||||
webapp.set("view engine", "ejs");
|
|
||||||
let firstRequest = true
|
|
||||||
//const webRemoteMDNS = mdns.createAdvertisement(mdns.tcp('https'), 9000, { name: "cider", domain: 'local' })
|
|
||||||
//webRemoteMDNS.start()
|
|
||||||
//* Prep for remote -quack
|
|
||||||
webapp.use(function(req, res, next) {
|
|
||||||
// if not localhost
|
|
||||||
if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && req.headers["user-agent"].includes("Cider"))) {
|
|
||||||
next();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log(req.get('host'))
|
|
||||||
res.redirect("https://discord.gg/applemusic")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
webapp.use(express.static(webRemotePath));
|
|
||||||
webapp.get('/', function(req, res) {
|
|
||||||
//if (!req.headers["user-agent"].includes("Cider"))
|
|
||||||
//res.sendFile(path.join(webRemotePath, 'index_old.html'));
|
|
||||||
if (firstRequest) {
|
|
||||||
console.log("---- Ignore Me ;) ---")
|
|
||||||
qrcode.generate(`http://${os.hostname}:9000`) //Prep for remote
|
|
||||||
console.log("---- Ignore Me ;) ---")
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* USING https://www.npmjs.com/package/qrcode-terminal for terminal
|
|
||||||
* WE SHOULD USE https://www.npmjs.com/package/qrcode for the remote (or others)
|
|
||||||
* -quack
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
firstRequest = false
|
|
||||||
|
|
||||||
res.render("main", CiderBase.EnvironmentVariables)
|
|
||||||
});
|
|
||||||
webapp.get('/audio.webm', function(req, res) {
|
|
||||||
console.log('hi')
|
|
||||||
try {
|
|
||||||
req.connection.setTimeout(Number.MAX_SAFE_INTEGER);
|
|
||||||
// CiderBase.requests.push({req: req, res: res});
|
|
||||||
// var pos = CiderBase.requests.length - 1;
|
|
||||||
// req.on("close", () => {
|
|
||||||
// console.info("CLOSED", CiderBase.requests.length);
|
|
||||||
// requests.splice(pos, 1);
|
|
||||||
// console.info("CLOSED", CiderBase.requests.length);
|
|
||||||
// });
|
|
||||||
CiderBase.audiostream.on('data', (data) => {
|
|
||||||
try {
|
|
||||||
res.write(data);
|
|
||||||
} catch (ex) {
|
|
||||||
console.log(ex)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (ex) { console.log(ex) }
|
|
||||||
});
|
|
||||||
webapp.listen(CiderBase.clientPort, function() {
|
|
||||||
console.log(`Cider hosted on: ${CiderBase.clientPort}`);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CiderBase;
|
|
|
@ -1,142 +0,0 @@
|
||||||
const { app } = require('electron'),
|
|
||||||
DiscordRPC = require('discord-rpc')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to Discord RPC
|
|
||||||
* @param {string} clientId
|
|
||||||
*/
|
|
||||||
connect: function(clientId) {
|
|
||||||
app.discord = { isConnected: false };
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0 || app.discord.isConnected) return;
|
|
||||||
|
|
||||||
DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc.
|
|
||||||
const client = new DiscordRPC.Client({ transport: "ipc" });
|
|
||||||
app.discord = Object.assign(client, { error: false, activityCache: null, isConnected: false });
|
|
||||||
|
|
||||||
// Login to Discord
|
|
||||||
app.discord.login({ clientId })
|
|
||||||
.then(() => {
|
|
||||||
app.discord.isConnected = true;
|
|
||||||
})
|
|
||||||
.catch((e) => console.error(`[DiscordRPC][connect] ${e}`));
|
|
||||||
|
|
||||||
app.discord.on('ready', () => {
|
|
||||||
console.log(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${client.user.username} (${client.user.id})`);
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handles Errors
|
|
||||||
app.discord.on('error', err => {
|
|
||||||
console.error(`[DiscordRPC] ${err}`);
|
|
||||||
this.disconnect()
|
|
||||||
app.discord.isConnected = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects from Discord RPC
|
|
||||||
*/
|
|
||||||
disconnect: function() {
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0 || !app.discord.isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
app.discord.destroy().then(() => {
|
|
||||||
app.discord.isConnected = false;
|
|
||||||
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
|
|
||||||
}).catch((e) => console.error(`[DiscordRPC][disconnect] ${e}`));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the activity of the client
|
|
||||||
* @param {object} attributes
|
|
||||||
*/
|
|
||||||
updateActivity: function(attributes) {
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0) return;
|
|
||||||
|
|
||||||
if (!app.discord.isConnected) {
|
|
||||||
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][updateActivity] ${e}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('[DiscordRPC][updateActivity] Updating Discord Activity.')
|
|
||||||
|
|
||||||
const listenURL = `https://cider.sh/p?s&id=${attributes.playParams.id}` // cider://play/s/[id] (for song)
|
|
||||||
//console.log(attributes)
|
|
||||||
let ActivityObject = {
|
|
||||||
details: attributes.name,
|
|
||||||
state: `by ${attributes.artistName}`,
|
|
||||||
startTimestamp: attributes.startTime,
|
|
||||||
endTimestamp: attributes.endTime,
|
|
||||||
largeImageKey: (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider',
|
|
||||||
largeImageText: attributes.albumName,
|
|
||||||
smallImageKey: (attributes.status ? 'play' : 'pause'),
|
|
||||||
smallImageText: (attributes.status ? 'Playing' : 'Paused'),
|
|
||||||
instance: true,
|
|
||||||
buttons: [
|
|
||||||
{ label: "Listen on Cider", url: listenURL },
|
|
||||||
]
|
|
||||||
};
|
|
||||||
if (ActivityObject.largeImageKey == "" || ActivityObject.largeImageKey == null) {
|
|
||||||
ActivityObject.largeImageKey = (app.cfg.get("general.discord_rpc") == 1) ? "cider" : "logo"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the pause/play icon and test for clear activity on pause
|
|
||||||
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
|
|
||||||
delete ActivityObject.smallImageKey
|
|
||||||
delete ActivityObject.smallImageText
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deletes the timestamp if its not greater than 0
|
|
||||||
if (!((new Date(attributes.endTime)).getTime() > 0)) {
|
|
||||||
delete ActivityObject.startTimestamp
|
|
||||||
delete ActivityObject.endTimestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Artist check
|
|
||||||
if (!attributes.artistName) {
|
|
||||||
delete ActivityObject.state
|
|
||||||
}
|
|
||||||
|
|
||||||
// Album text check
|
|
||||||
if (!ActivityObject.largeImageText || ActivityObject.largeImageText.length < 2) {
|
|
||||||
delete ActivityObject.largeImageText
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the name is greater than 128 because some songs can be that long
|
|
||||||
if (ActivityObject.details.length > 128) {
|
|
||||||
ActivityObject.details = ActivityObject.details.substring(0, 125) + '...'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check if its pausing (false) or playing (true)
|
|
||||||
if (!attributes.status) {
|
|
||||||
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
|
|
||||||
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][clearActivity] ${e}`));
|
|
||||||
ActivityObject = null
|
|
||||||
} else {
|
|
||||||
delete ActivityObject.startTimestamp
|
|
||||||
delete ActivityObject.endTimestamp
|
|
||||||
ActivityObject.smallImageKey = 'pause'
|
|
||||||
ActivityObject.smallImageText = 'Paused'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (ActivityObject && ActivityObject !== app.discord.activityCache && ActivityObject.details && ActivityObject.state) {
|
|
||||||
try {
|
|
||||||
// console.log(`[DiscordRPC][setActivity] Setting activity to ${JSON.stringify(ActivityObject)}`);
|
|
||||||
app.discord.setActivity(ActivityObject)
|
|
||||||
app.discord.activityCache = ActivityObject
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[DiscordRPC][setActivity] ${err}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
116
src/main/index.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
require('v8-compile-cache');
|
||||||
|
|
||||||
|
// Analytics for debugging fun yeah.
|
||||||
|
const ElectronSentry = require("@sentry/electron");
|
||||||
|
ElectronSentry.init({dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214"});
|
||||||
|
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import {Win} from "./base/win";
|
||||||
|
import {ConfigStore} from "./base/store";
|
||||||
|
import {AppEvents} from "./base/app";
|
||||||
|
import PluginHandler from "./base/plugins";
|
||||||
|
|
||||||
|
// const test = new PluginHandler();
|
||||||
|
const config = new ConfigStore();
|
||||||
|
const App = new AppEvents(config.store);
|
||||||
|
const Cider = new Win(electron.app, config.store)
|
||||||
|
const plug = new PluginHandler();
|
||||||
|
|
||||||
|
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* App Event Handlers
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||||
|
|
||||||
|
electron.app.on('ready', () => {
|
||||||
|
App.ready();
|
||||||
|
|
||||||
|
console.log('[Cider] Application is Ready. Creating Window.')
|
||||||
|
if (!electron.app.isPackaged) {
|
||||||
|
console.info('[Cider] Running in development mode.')
|
||||||
|
require('vue-devtools').install()
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.components.whenReady().then(() => {
|
||||||
|
Cider.createWindow().then((win) => {
|
||||||
|
plug.callPlugins('onReady', win);
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* Renderer Event Handlers
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||||
|
|
||||||
|
electron.ipcMain.on('playbackStateDidChange', (event, attributes) => {
|
||||||
|
plug.callPlugins('onPlaybackStateDidChange', attributes);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('nowPlayingItemDidChange', (event, attributes) => {
|
||||||
|
plug.callPlugins('onNowPlayingItemDidChange', attributes);
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
electron.app.on('before-quit', () => {
|
||||||
|
plug.callPlugins('onBeforeQuit');
|
||||||
|
console.warn(`${electron.app.getName()} exited.`);
|
||||||
|
});
|
||||||
|
//
|
||||||
|
// // @ts-ignore
|
||||||
|
// // Widevine Stuff
|
||||||
|
// electron.app.on('widevine-ready', (version, lastVersion) => {
|
||||||
|
// if (null !== lastVersion) {
|
||||||
|
// console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
|
||||||
|
// } else {
|
||||||
|
// console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // @ts-ignore
|
||||||
|
// electron.app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
|
||||||
|
// console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // @ts-ignore
|
||||||
|
// electron.app.on('widevine-error', (error) => {
|
||||||
|
// console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
|
||||||
|
// electron.app.exit()
|
||||||
|
// })
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// app.on('open-url', (event, url) => {
|
||||||
|
// event.preventDefault()
|
||||||
|
// if (url.includes('ame://') || url.includes('itms://') || url.includes('itmss://') || url.includes('musics://') || url.includes('music://')) {
|
||||||
|
// CiderBase.LinkHandler(url)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// app.on('second-instance', (_e, argv) => {
|
||||||
|
// console.warn(`[InstanceHandler][SecondInstanceHandler] Second Instance Started with args: [${argv.join(', ')}]`)
|
||||||
|
//
|
||||||
|
// // Checks if first instance is authorized and if second instance has protocol args
|
||||||
|
// argv.forEach((value) => {
|
||||||
|
// if (value.includes('ame://') || value.includes('itms://') || value.includes('itmss://') || value.includes('musics://') || value.includes('music://')) {
|
||||||
|
// console.warn(`[InstanceHandler][SecondInstanceHandler] Found Protocol!`)
|
||||||
|
// CiderBase.LinkHandler(value);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// if (argv.includes("--force-quit")) {
|
||||||
|
// console.warn('[InstanceHandler][SecondInstanceHandler] Force Quit found. Quitting App.');
|
||||||
|
// app.isQuiting = true
|
||||||
|
// app.quit()
|
||||||
|
// } else if (app.win && !app.cfg.get('advanced.allowMultipleInstances')) { // If a Second Instance has Been Started
|
||||||
|
// console.warn('[InstanceHandler][SecondInstanceHandler] Showing window.');
|
||||||
|
// app.win.show()
|
||||||
|
// app.win.focus()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// if (!app.requestSingleInstanceLock() && !app.cfg.get('advanced.allowMultipleInstances')) {
|
||||||
|
// console.warn("[InstanceHandler] Existing Instance is Blocking Second Instance.");
|
||||||
|
// app.quit();
|
||||||
|
// app.isQuiting = true
|
||||||
|
// }
|
|
@ -1,153 +0,0 @@
|
||||||
const {app, Notification} = require('electron'),
|
|
||||||
fs = require('fs'),
|
|
||||||
{resolve} = require('path'),
|
|
||||||
sessionPath = resolve(app.getPath('userData'), 'session.json'),
|
|
||||||
apiCredentials = require('../../resources/lfmApiCredentials.json'),
|
|
||||||
LastfmAPI = require('lastfmapi');
|
|
||||||
|
|
||||||
const lfm = {
|
|
||||||
authenticateFromFile: function () {
|
|
||||||
let sessionData = require(sessionPath)
|
|
||||||
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
|
|
||||||
app.lastfm.setSessionCredentials(sessionData.name, sessionData.key)
|
|
||||||
console.log("[LastFM][authenticateFromFile] Logged in.")
|
|
||||||
},
|
|
||||||
|
|
||||||
authenticate: function () {
|
|
||||||
if (app.cfg.get('lastfm.auth_token')) {
|
|
||||||
app.cfg.set('lastfm.enabled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.cfg.get('lastfm.enabled') || !app.cfg.get('lastfm.auth_token')) {
|
|
||||||
app.cfg.set('lastfm.enabled', false);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const lfmAPI = new LastfmAPI({
|
|
||||||
'api_key': apiCredentials.key,
|
|
||||||
'secret': apiCredentials.secret
|
|
||||||
});
|
|
||||||
|
|
||||||
app.lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
|
|
||||||
|
|
||||||
fs.stat(sessionPath, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
|
|
||||||
console.log("[LastFM][Auth] Beginning authentication from configuration")
|
|
||||||
app.lastfm.authenticate(app.cfg.get('lastfm.auth_token'), function (err, session) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
console.log("[LastFM] Successfully obtained LastFM session info,", 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) => {
|
|
||||||
if (err)
|
|
||||||
console.log("[LastFM][fs]", err)
|
|
||||||
else {
|
|
||||||
console.log("[LastFM][fs] File was written successfully.")
|
|
||||||
lfm.authenticateFromFile()
|
|
||||||
new Notification({
|
|
||||||
title: app.getName(),
|
|
||||||
body: "Successfully logged into LastFM using Authentication Key."
|
|
||||||
}).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
lfm.authenticateFromFile()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
scrobbleSong: async function (attributes) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, Math.round(attributes.durationInMillis * (app.cfg.get('lastfm.scrobble_after') / 100))));
|
|
||||||
const currentAttributes = app.media;
|
|
||||||
|
|
||||||
if (!app.lastfm || app.lastfm.cachedAttributes === attributes ) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.lastfm.cachedAttributes) {
|
|
||||||
if (app.lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentAttributes.status && currentAttributes === attributes) {
|
|
||||||
if (fs.existsSync(sessionPath)) {
|
|
||||||
// Scrobble playing song.
|
|
||||||
if (attributes.status === true) {
|
|
||||||
app.lastfm.track.scrobble({
|
|
||||||
'artist': lfm.filterArtistName(attributes.artistName),
|
|
||||||
'track': attributes.name,
|
|
||||||
'album': attributes.albumName,
|
|
||||||
'albumArtist': this.filterArtistName(attributes.artistName),
|
|
||||||
'timestamp': new Date().getTime() / 1000
|
|
||||||
}, function (err, scrobbled) {
|
|
||||||
if (err) {
|
|
||||||
return console.error('[LastFM] An error occurred while scrobbling', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
|
|
||||||
});
|
|
||||||
app.lastfm.cachedAttributes = attributes
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.authenticate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return console.log('[LastFM] Did not add ', attributes.name , '—' , lfm.filterArtistName(attributes.artistName), 'because now playing a other song.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
filterArtistName: function (artist) {
|
|
||||||
if (!app.cfg.get('lastfm.enabledRemoveFeaturingArtists')) return artist;
|
|
||||||
|
|
||||||
artist = artist.split(' ');
|
|
||||||
if (artist.includes('&')) {
|
|
||||||
artist.length = artist.indexOf('&');
|
|
||||||
}
|
|
||||||
if (artist.includes('and')) {
|
|
||||||
artist.length = artist.indexOf('and');
|
|
||||||
}
|
|
||||||
artist = artist.join(' ');
|
|
||||||
if (artist.includes(',')) {
|
|
||||||
artist = artist.split(',')
|
|
||||||
artist = artist[0]
|
|
||||||
}
|
|
||||||
return artist.charAt(0).toUpperCase() + artist.slice(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNowPlayingSong: function (attributes) {
|
|
||||||
if (!app.lastfm ||app.lastfm.cachedNowPlayingAttributes === attributes | !app.cfg.get('lastfm.NowPlaying')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.lastfm.cachedNowPlayingAttributes) {
|
|
||||||
if (app.lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(sessionPath)) {
|
|
||||||
// update Now Playing
|
|
||||||
if (attributes.status === true) {
|
|
||||||
app.lastfm.track.updateNowPlaying({
|
|
||||||
'artist': lfm.filterArtistName(attributes.artistName),
|
|
||||||
'track': attributes.name,
|
|
||||||
'album': attributes.albumName,
|
|
||||||
'albumArtist': this.filterArtistName(attributes.artistName)
|
|
||||||
}, function (err, nowPlaying) {
|
|
||||||
if (err) {
|
|
||||||
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
|
|
||||||
});
|
|
||||||
app.lastfm.cachedNowPlayingAttributes = attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.authenticate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = lfm;
|
|
|
@ -1,149 +0,0 @@
|
||||||
const { nativeImage } = require("electron");
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
let mediaPlayer = null;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to the MPRIS interface.
|
|
||||||
* @param {Object} win - The BrowserWindow.
|
|
||||||
*/
|
|
||||||
connect: (win) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
const Player = require('mpris-service');
|
|
||||||
|
|
||||||
mediaPlayer = Player({
|
|
||||||
name: 'Cider',
|
|
||||||
identity: 'Cider',
|
|
||||||
supportedUriSchemes: [],
|
|
||||||
supportedMimeTypes: [],
|
|
||||||
supportedInterfaces: ['player']
|
|
||||||
});
|
|
||||||
mediaPlayer = Object.assign(mediaPlayer, { canQuit: true, canControl: true, canPause: true, canPlay: true, canGoNext: true })
|
|
||||||
|
|
||||||
|
|
||||||
let pos_atr = {durationInMillis: 0};
|
|
||||||
mediaPlayer.getPosition = function () {
|
|
||||||
const durationInMicro = pos_atr.durationInMillis * 1000;
|
|
||||||
const percentage = parseFloat("0") || 0;
|
|
||||||
return durationInMicro * percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaPlayer.active = true
|
|
||||||
|
|
||||||
mediaPlayer.on('playpause', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('play', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('pause', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('next', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('previous', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the MPRIS interface.
|
|
||||||
* @param {Object} attributes - The attributes of the track.
|
|
||||||
*/
|
|
||||||
updateAttributes: (attributes) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
const MetaData = {
|
|
||||||
'mpris:trackid': mediaPlayer.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
|
|
||||||
'mpris:length': attributes.durationInMillis * 1000, // In microseconds
|
|
||||||
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
|
|
||||||
'xesam:title': `${attributes.name}`,
|
|
||||||
'xesam:album': `${attributes.albumName}`,
|
|
||||||
'xesam:artist': [`${attributes.artistName}`,],
|
|
||||||
'xesam:genre': attributes.genreNames
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaPlayer.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaPlayer.metadata = MetaData
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the playback state of the MPRIS interface.
|
|
||||||
* @param {Object} attributes - The attributes of the track.
|
|
||||||
*/
|
|
||||||
updateState: (attributes) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
function setPlaybackIfNeeded(status) {
|
|
||||||
if (mediaPlayer.playbackStatus === status) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mediaPlayer.playbackStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (attributes.status) {
|
|
||||||
case true: // Playing
|
|
||||||
setPlaybackIfNeeded('Playing');
|
|
||||||
break;
|
|
||||||
case false: // Paused
|
|
||||||
setPlaybackIfNeeded('Paused');
|
|
||||||
break;
|
|
||||||
default: // Stopped
|
|
||||||
setPlaybackIfNeeded('Stopped');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
SetButtons: (win, attributes) => {
|
|
||||||
if (process.platform === 'win32') { // Set the Windows Thumbnail Toolbar Buttons
|
|
||||||
win.setThumbarButtons([
|
|
||||||
{
|
|
||||||
tooltip: 'Previous',
|
|
||||||
icon: nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/backwardPng.png')),
|
|
||||||
click() {
|
|
||||||
console.log("Clicked the bc taskbar button!")
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: attributes.status ? 'Pause' : 'Play',
|
|
||||||
//tooltip: 'Play',
|
|
||||||
icon: attributes.status ? nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/pausePng.png')) : nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/playPng.png')),
|
|
||||||
click() {
|
|
||||||
console.log("Clicked the pl taskbar button!")
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: 'Next',
|
|
||||||
icon: nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/forwardPng.png')),
|
|
||||||
click() {
|
|
||||||
console.log("Clicked the fw taskbar button!")
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the MPRIS interface.
|
|
||||||
*/
|
|
||||||
clearActivity: () => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
mediaPlayer.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'}
|
|
||||||
mediaPlayer.playbackStatus = 'Stopped';
|
|
||||||
},
|
|
||||||
}
|
|
58
src/main/plugins/Extras/examplePlugin.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
let i = 1, k = 1;
|
||||||
|
export default class ExamplePlugin {
|
||||||
|
/**
|
||||||
|
* Private variables for interaction in plugins
|
||||||
|
*/
|
||||||
|
private _win: any;
|
||||||
|
private _app: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'examplePlugin';
|
||||||
|
public description: string = 'Example plugin';
|
||||||
|
public version: string = '1.0.0';
|
||||||
|
public author: string = 'Example author';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
constructor(app: any) {
|
||||||
|
this._app = app;
|
||||||
|
console.log('Example plugin loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(win: any): void {
|
||||||
|
this._win = win;
|
||||||
|
console.log('Example plugin ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {
|
||||||
|
console.log('Example plugin stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: object): void {
|
||||||
|
console.log('onPlaybackStateDidChange has been called ' + i +' times');
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {
|
||||||
|
console.log('onNowPlayingDidChange has been called ' + k +' times');
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
src/main/plugins/Extras/sendSongToTitlebar.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import * as electron from "electron";
|
||||||
|
|
||||||
|
export default class sendSongToTitlebar {
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'sendSongToTitlebar';
|
||||||
|
public description: string = 'Sets the app\'s titlebar to the Song title';
|
||||||
|
public version: string = '0.0.1';
|
||||||
|
public author: string = 'Cider Collective (credit to 8times9 via #147)';
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
private _win: any;
|
||||||
|
private _app: any;
|
||||||
|
constructor() {}
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(win: any): void {
|
||||||
|
this._win = win;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {}
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: any): void {
|
||||||
|
this._win.setTitle(`${(attributes != null && attributes.name != null && attributes.name.length > 0) ? (attributes.name + " - ") : ''}Cider`)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
const electron = require('electron')
|
global.ipcRenderer = require('electron').ipcRenderer;
|
||||||
|
|
||||||
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},
|
||||||
|
@ -97,6 +96,5 @@ const MusicKitInterop = {
|
||||||
|
|
||||||
process.once('loaded', () => {
|
process.once('loaded', () => {
|
||||||
console.log("Setting ipcRenderer")
|
console.log("Setting ipcRenderer")
|
||||||
global.ipcRenderer = electron.ipcRenderer;
|
|
||||||
global.MusicKitInterop = MusicKitInterop;
|
global.MusicKitInterop = MusicKitInterop;
|
||||||
});
|
});
|
5
src/renderer/.jsbeautifyrc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"js": {
|
||||||
|
"beautify.ignore": "src/renderer/index.js"
|
||||||
|
}
|
||||||
|
}
|
103
src/renderer/WSAPI_Interop.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
const wsapi = {
|
||||||
|
cache: {playParams: {id: 0}, status: null, remainingTime: 0},
|
||||||
|
playbackCache: {status: null, time: Date.now()},
|
||||||
|
search(term, limit) {
|
||||||
|
MusicKit.getInstance().api.search(term, {limit: limit, types: 'songs,artists,albums'}).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnSearch', JSON.stringify(results))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchLibrary(term, limit) {
|
||||||
|
MusicKit.getInstance().api.library.search(term, {limit: limit, types: 'library-songs,library-artists,library-albums'}).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnSearchLibrary', JSON.stringify(results))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getAttributes: function () {
|
||||||
|
const mk = MusicKit.getInstance();
|
||||||
|
const nowPlayingItem = mk.nowPlayingItem;
|
||||||
|
const isPlayingExport = mk.isPlaying;
|
||||||
|
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||||
|
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
|
||||||
|
|
||||||
|
attributes.status = isPlayingExport ? isPlayingExport : false;
|
||||||
|
attributes.name = attributes.name ? attributes.name : 'No Title Found';
|
||||||
|
attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''};
|
||||||
|
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : '';
|
||||||
|
attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'};
|
||||||
|
attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : 'no-id-found';
|
||||||
|
attributes.albumName = attributes.albumName ? attributes.albumName : '';
|
||||||
|
attributes.artistName = attributes.artistName ? attributes.artistName : '';
|
||||||
|
attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
|
||||||
|
attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0;
|
||||||
|
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
|
||||||
|
attributes.startTime = Date.now();
|
||||||
|
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
|
||||||
|
attributes.volume = mk.volume;
|
||||||
|
attributes.shuffleMode = mk.shuffleMode;
|
||||||
|
attributes.repeatMode = mk.repeatMode;
|
||||||
|
attributes.autoplayEnabled = mk.autoplayEnabled;
|
||||||
|
return attributes
|
||||||
|
},
|
||||||
|
moveQueueItem(oldPosition, newPosition) {
|
||||||
|
MusicKit.getInstance().queue._queueItems.splice(newPosition,0,MusicKit.getInstance().queue._queueItems.splice(oldPosition,1)[0])
|
||||||
|
MusicKit.getInstance().queue._reindex()
|
||||||
|
},
|
||||||
|
setAutoplay(value) {
|
||||||
|
MusicKit.getInstance().autoplayEnabled = value
|
||||||
|
},
|
||||||
|
returnDynamic(data, type) {
|
||||||
|
ipcRenderer.send('wsapi-returnDynamic', JSON.stringify(data), type)
|
||||||
|
},
|
||||||
|
musickitApi(method, id, params) {
|
||||||
|
MusicKit.getInstance().api[method](id, params).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getPlaybackState () {
|
||||||
|
ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
|
||||||
|
},
|
||||||
|
getLyrics() {
|
||||||
|
return []
|
||||||
|
_lyrics.GetLyrics(1, false)
|
||||||
|
},
|
||||||
|
getQueue() {
|
||||||
|
ipcRenderer.send('wsapi-returnQueue', JSON.stringify(MusicKit.getInstance().queue))
|
||||||
|
},
|
||||||
|
playNext(type, id) {
|
||||||
|
var request = {}
|
||||||
|
request[type] = id
|
||||||
|
MusicKit.getInstance().playNext(request)
|
||||||
|
},
|
||||||
|
playLater(type, id) {
|
||||||
|
var request = {}
|
||||||
|
request[type] = id
|
||||||
|
MusicKit.getInstance().playLater(request)
|
||||||
|
},
|
||||||
|
love() {
|
||||||
|
|
||||||
|
},
|
||||||
|
playTrackById(id, kind = "song") {
|
||||||
|
MusicKit.getInstance().setQueue({ [kind]: id }).then(function (queue) {
|
||||||
|
MusicKit.getInstance().play()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
quickPlay(term) {
|
||||||
|
// Quick play by song name
|
||||||
|
MusicKit.getInstance().api.search(term, { limit: 2, types: 'songs' }).then(function (data) {
|
||||||
|
MusicKit.getInstance().setQueue({ song: data["songs"][0]["id"] }).then(function (queue) {
|
||||||
|
MusicKit.getInstance().play()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toggleShuffle() {
|
||||||
|
MusicKit.getInstance().shuffleMode = MusicKit.getInstance().shuffleMode === 0 ? 1 : 0
|
||||||
|
},
|
||||||
|
toggleRepeat() {
|
||||||
|
if(MusicKit.getInstance().repeatMode == 0) {
|
||||||
|
MusicKit.getInstance().repeatMode = 2
|
||||||
|
}else if(MusicKit.getInstance().repeatMode == 2){
|
||||||
|
MusicKit.getInstance().repeatMode = 1
|
||||||
|
}else{
|
||||||
|
MusicKit.getInstance().repeatMode = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -249,6 +249,11 @@ const app = new Vue({
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0
|
end: 0
|
||||||
},
|
},
|
||||||
|
v3: {
|
||||||
|
requestBody: {
|
||||||
|
platform: "web"
|
||||||
|
}
|
||||||
|
},
|
||||||
tmpVar: [],
|
tmpVar: [],
|
||||||
notification: false,
|
notification: false,
|
||||||
chrome: {
|
chrome: {
|
||||||
|
@ -378,13 +383,12 @@ const app = new Vue({
|
||||||
let data = await response.text();
|
let data = await response.text();
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
getSocialBadges(cb = () => {
|
getSocialBadges(cb = () => {}) {
|
||||||
}) {
|
|
||||||
let self = this
|
let self = this
|
||||||
try {
|
try {
|
||||||
app.mk.api.socialBadgingMap().then(data => {
|
app.mk.api.v3.music("/v1/social/badging-map").then(data => {
|
||||||
self.socialBadges.badgeMap = data.badgingMap
|
self.socialBadges.badgeMap = data.data.results.badgingMap
|
||||||
cb(data.badgingMap)
|
cb(data.data.results.badgingMap)
|
||||||
})
|
})
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.socialBadges.badgeMap = {}
|
this.socialBadges.badgeMap = {}
|
||||||
|
@ -436,8 +440,8 @@ const app = new Vue({
|
||||||
})
|
})
|
||||||
} else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) {
|
} else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) {
|
||||||
self.selectedMediaItems[i].kind = "albums"
|
self.selectedMediaItems[i].kind = "albums"
|
||||||
let res = await self.mk.api.albumRelationship(self.selectedMediaItems[i].id, "tracks");
|
let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`);
|
||||||
let ids = res.map(function (i) {
|
let ids = res.data.data.map(function(i) {
|
||||||
return { id: i.id, type: i.type }
|
return { id: i.id, type: i.type }
|
||||||
})
|
})
|
||||||
pl_items = pl_items.concat(ids)
|
pl_items = pl_items.concat(ids)
|
||||||
|
@ -449,8 +453,8 @@ const app = new Vue({
|
||||||
})
|
})
|
||||||
} else if ((self.selectedMediaItems[i].kind == "library-album" || self.selectedMediaItems[i].kind == "library-albums") || (self.selectedMediaItems[i].kind == "album" && self.selectedMediaItems[i].isLibrary == true)) {
|
} else if ((self.selectedMediaItems[i].kind == "library-album" || self.selectedMediaItems[i].kind == "library-albums") || (self.selectedMediaItems[i].kind == "album" && self.selectedMediaItems[i].isLibrary == true)) {
|
||||||
self.selectedMediaItems[i].kind = "library-albums"
|
self.selectedMediaItems[i].kind = "library-albums"
|
||||||
let res = await self.mk.api.library.albumRelationship(self.selectedMediaItems[i].id, "tracks");
|
let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`);
|
||||||
let ids = res.map(function (i) {
|
let ids = res.data.data.map(function(i) {
|
||||||
return { id: i.id, type: i.type }
|
return { id: i.id, type: i.type }
|
||||||
})
|
})
|
||||||
pl_items = pl_items.concat(ids)
|
pl_items = pl_items.concat(ids)
|
||||||
|
@ -463,7 +467,16 @@ const app = new Vue({
|
||||||
|
|
||||||
}
|
}
|
||||||
this.modals.addToPlaylist = false
|
this.modals.addToPlaylist = false
|
||||||
this.mk.api.library.appendTracksToPlaylist(playlist_id, pl_items).then(() => {
|
await app.mk.api.v3.music(
|
||||||
|
`/v1/me/library/playlists/${playlist_id}/tracks`, {}, {
|
||||||
|
fetchOptions: {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: pl_items
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
if (this.page == 'playlist_' + this.showingPlaylist.id) {
|
if (this.page == 'playlist_' + this.showingPlaylist.id) {
|
||||||
this.getPlaylistFromID(this.showingPlaylist.id)
|
this.getPlaylistFromID(this.showingPlaylist.id)
|
||||||
}
|
}
|
||||||
|
@ -475,6 +488,7 @@ const app = new Vue({
|
||||||
this.mk = MusicKit.getInstance()
|
this.mk = MusicKit.getInstance()
|
||||||
this.mk.authorize().then(() => {
|
this.mk.authorize().then(() => {
|
||||||
self.mkIsReady = true
|
self.mkIsReady = true
|
||||||
|
//document.location.reload()
|
||||||
})
|
})
|
||||||
this.$forceUpdate()
|
this.$forceUpdate()
|
||||||
if (this.isDev) {
|
if (this.isDev) {
|
||||||
|
@ -485,8 +499,12 @@ const app = new Vue({
|
||||||
}
|
}
|
||||||
this.mk._services.timing.mode = 0
|
this.mk._services.timing.mode = 0
|
||||||
this.platform = ipcRenderer.sendSync('cider-platform');
|
this.platform = ipcRenderer.sendSync('cider-platform');
|
||||||
|
|
||||||
|
try {
|
||||||
// Set profile name
|
// Set profile name
|
||||||
this.chrome.userinfo = await this.mkapi("personalSocialProfile", false, "")
|
this.chrome.userinfo = (await app.mk.api.v3.music(`/v1/me/social-profile`)).data.data[0]
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
// API Fallback
|
// API Fallback
|
||||||
if (!this.chrome.userinfo) {
|
if (!this.chrome.userinfo) {
|
||||||
this.chrome.userinfo = {
|
this.chrome.userinfo = {
|
||||||
|
@ -500,8 +518,8 @@ const app = new Vue({
|
||||||
}
|
}
|
||||||
MusicKitInterop.init()
|
MusicKitInterop.init()
|
||||||
// Set the volume
|
// Set the volume
|
||||||
this.mk.volume = this.cfg.general.volume
|
this.mk.volume = this.cfg.audio.volume
|
||||||
// ipcRenderer.invoke('getStoreValue', 'general.volume').then((value) => {
|
// ipcRenderer.invoke('getStoreValue', 'audio.volume').then((value) => {
|
||||||
// self.mk.volume = value
|
// self.mk.volume = value
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
@ -546,7 +564,9 @@ const app = new Vue({
|
||||||
lastItem = JSON.parse(lastItem)
|
lastItem = JSON.parse(lastItem)
|
||||||
let kind = lastItem.attributes.playParams.kind;
|
let kind = lastItem.attributes.playParams.kind;
|
||||||
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
app.mk.setQueue({[truekind]: [lastItem.attributes.playParams.id]})
|
app.mk.setQueue({
|
||||||
|
[truekind]: [lastItem.attributes.playParams.id]
|
||||||
|
})
|
||||||
app.mk.mute()
|
app.mk.mute()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
app.mk.play().then(() => {
|
app.mk.play().then(() => {
|
||||||
|
@ -565,8 +585,7 @@ const app = new Vue({
|
||||||
if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) {
|
if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) {
|
||||||
try {
|
try {
|
||||||
app.mk.playLater({ songs: [id] })
|
app.mk.playLater({ songs: [id] })
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@ -596,10 +615,16 @@ const app = new Vue({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, ()=>{
|
||||||
|
ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
|
||||||
|
})
|
||||||
|
|
||||||
this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => {
|
this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => {
|
||||||
self.lyriccurrenttime = self.mk.currentPlaybackTime
|
self.lyriccurrenttime = self.mk.currentPlaybackTime
|
||||||
this.currentSongInfo = a
|
this.currentSongInfo = a
|
||||||
self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime)
|
self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime)
|
||||||
|
// wsapi
|
||||||
|
ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => {
|
this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => {
|
||||||
|
@ -615,11 +640,10 @@ const app = new Vue({
|
||||||
let previewURL = null
|
let previewURL = null
|
||||||
try {
|
try {
|
||||||
previewURL = app.mk.nowPlayingItem.previewURL
|
previewURL = app.mk.nowPlayingItem.previewURL
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
if (!previewURL) {
|
if (!previewURL) {
|
||||||
app.mk.api.song(app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id).then((response) => {
|
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/songs/${app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id}`).then((response) => {
|
||||||
previewURL = response.attributes.previews[0].url
|
previewURL = response.data.data[0].attributes.previews[0].url
|
||||||
if (previewURL)
|
if (previewURL)
|
||||||
ipcRenderer.send('getPreviewURL', previewURL)
|
ipcRenderer.send('getPreviewURL', previewURL)
|
||||||
})
|
})
|
||||||
|
@ -628,14 +652,12 @@ const app = new Vue({
|
||||||
ipcRenderer.send('getPreviewURL', previewURL)
|
ipcRenderer.send('getPreviewURL', previewURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
a = a.item.attributes;
|
a = a.item.attributes;
|
||||||
} catch (_) {
|
} catch (_) {}
|
||||||
}
|
|
||||||
let type = (self.mk.nowPlayingItem != null) ? self.mk.nowPlayingItem["type"] ?? '' : '';
|
let type = (self.mk.nowPlayingItem != null) ? self.mk.nowPlayingItem["type"] ?? '' : '';
|
||||||
|
|
||||||
if (type.includes("musicVideo") || type.includes("uploadedVideo") || type.includes("music-movie")) {
|
if (type.includes("musicVideo") || type.includes("uploadedVideo") || type.includes("music-movie")) {
|
||||||
|
@ -669,7 +691,7 @@ const app = new Vue({
|
||||||
|
|
||||||
|
|
||||||
this.mk.addEventListener(MusicKit.Events.playbackVolumeDidChange, (_a) => {
|
this.mk.addEventListener(MusicKit.Events.playbackVolumeDidChange, (_a) => {
|
||||||
this.cfg.general.volume = this.mk.volume
|
this.cfg.audio.volume = this.mk.volume
|
||||||
})
|
})
|
||||||
|
|
||||||
this.refreshPlaylists()
|
this.refreshPlaylists()
|
||||||
|
@ -762,8 +784,7 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
playlistHeaderContextMenu(event) {
|
playlistHeaderContextMenu(event) {
|
||||||
let menu = {
|
let menu = {
|
||||||
items: [
|
items: [{
|
||||||
{
|
|
||||||
name: "New Playlist",
|
name: "New Playlist",
|
||||||
action: () => {
|
action: () => {
|
||||||
this.newPlaylist()
|
this.newPlaylist()
|
||||||
|
@ -782,9 +803,7 @@ const app = new Vue({
|
||||||
async editPlaylistFolder(id, name = "New Playlist") {
|
async editPlaylistFolder(id, name = "New Playlist") {
|
||||||
let self = this
|
let self = this
|
||||||
this.mk.api.v3.music(
|
this.mk.api.v3.music(
|
||||||
`/v1/me/library/playlist-folders/${id}`,
|
`/v1/me/library/playlist-folders/${id}`, {}, {
|
||||||
{},
|
|
||||||
{
|
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -799,9 +818,7 @@ const app = new Vue({
|
||||||
async editPlaylist(id, name = "New Playlist") {
|
async editPlaylist(id, name = "New Playlist") {
|
||||||
let self = this
|
let self = this
|
||||||
this.mk.api.v3.music(
|
this.mk.api.v3.music(
|
||||||
`/v1/me/library/playlists/${id}`,
|
`/v1/me/library/playlists/${id}`, {}, {
|
||||||
{},
|
|
||||||
{
|
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -824,7 +841,18 @@ const app = new Vue({
|
||||||
if (tracks.length > 0) {
|
if (tracks.length > 0) {
|
||||||
request.tracks = tracks
|
request.tracks = tracks
|
||||||
}
|
}
|
||||||
app.mk.api.library.createPlaylist(request).then(res => {
|
app.mk.api.v3.music(`/v1/me/library/playlists`, {}, {
|
||||||
|
fetchOptions: {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
"attributes": { "name": name },
|
||||||
|
"relationships": {
|
||||||
|
"tracks": { "data": tracks },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
res = res.data.data[0]
|
||||||
console.log(res)
|
console.log(res)
|
||||||
self.appRoute(`playlist_` + res.id);
|
self.appRoute(`playlist_` + res.id);
|
||||||
self.showingPlaylist = [];
|
self.showingPlaylist = [];
|
||||||
|
@ -845,7 +873,11 @@ const app = new Vue({
|
||||||
deletePlaylist(id) {
|
deletePlaylist(id) {
|
||||||
let self = this
|
let self = this
|
||||||
if (confirm(`Are you sure you want to delete this playlist?`)) {
|
if (confirm(`Are you sure you want to delete this playlist?`)) {
|
||||||
app.mk.api.library.deletePlaylist(id).then(res => {
|
app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, {}, {
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
// remove this playlist from playlists.listing if it exists
|
// remove this playlist from playlists.listing if it exists
|
||||||
let found = self.playlists.listing.find(item => item.id == id)
|
let found = self.playlists.listing.find(item => item.id == id)
|
||||||
if (found) {
|
if (found) {
|
||||||
|
@ -854,23 +886,28 @@ const app = new Vue({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async showCollection(response, title, type) {
|
async showCollection(response, title, type, requestBody = {}) {
|
||||||
let self = this
|
let self = this
|
||||||
|
console.log(response)
|
||||||
|
this.collectionList.requestBody = {}
|
||||||
this.collectionList.response = response
|
this.collectionList.response = response
|
||||||
this.collectionList.title = title
|
this.collectionList.title = title
|
||||||
this.collectionList.type = type
|
this.collectionList.type = type
|
||||||
|
this.collectionList.requestBody = requestBody
|
||||||
app.appRoute("collection-list")
|
app.appRoute("collection-list")
|
||||||
},
|
},
|
||||||
async showArtistView(artist, title, view) {
|
async showArtistView(artist, title, view) {
|
||||||
let response = await this.mk.api.artistView(artist, view, {}, {view: view, includeResponseMeta: !0})
|
let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}`,{}, {includeResponseMeta: !0})).data
|
||||||
|
console.log(response)
|
||||||
await this.showCollection(response, title, "artists")
|
await this.showCollection(response, title, "artists")
|
||||||
},
|
},
|
||||||
async showRecordLabelView(label, title, view) {
|
async showRecordLabelView(label, title, view) {
|
||||||
let response = await this.mk.api.recordLabelView(label, view, {}, {view: view, includeResponseMeta: !0})
|
let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/record-labels/${label}/view/${view}`)).data
|
||||||
await this.showCollection(response, title, "record-labels")
|
await this.showCollection(response, title, "record-labels")
|
||||||
},
|
},
|
||||||
async showSearchView(term, group, title) {
|
async showSearchView(term, group, title) {
|
||||||
let response = await this.mk.api.search(term, {
|
|
||||||
|
let requestBody = {
|
||||||
platform: "web",
|
platform: "web",
|
||||||
groups: group,
|
groups: group,
|
||||||
types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels",
|
types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels",
|
||||||
|
@ -894,17 +931,23 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
omit: {
|
omit: {
|
||||||
resource: ["autos"]
|
resource: ["autos"]
|
||||||
}
|
},
|
||||||
}, {groups: group, includeResponseMeta: !0})
|
|
||||||
console.log(response)
|
|
||||||
let responseFormat = {
|
|
||||||
data: response[group].data.data,
|
|
||||||
next: response[group].next,
|
|
||||||
groups: group
|
groups: group
|
||||||
}
|
}
|
||||||
await this.showCollection(responseFormat, title, "search")
|
let response = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${term}`, requestBody , {
|
||||||
|
includeResponseMeta: !0
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('searchres', response)
|
||||||
|
let responseFormat = {
|
||||||
|
data: response.data.results[group].data,
|
||||||
|
next: response.data.results[group].next,
|
||||||
|
groups: group
|
||||||
|
}
|
||||||
|
await this.showCollection(responseFormat, title, "search", requestBody)
|
||||||
},
|
},
|
||||||
async getPlaylistContinuous(response, transient = false) {
|
async getPlaylistContinuous(response, transient = false) {
|
||||||
|
response = response.data.data[0]
|
||||||
let self = this
|
let self = this
|
||||||
let playlistId = response.id
|
let playlistId = response.id
|
||||||
if (!transient) this.playlists.loadingState = 0
|
if (!transient) this.playlists.loadingState = 0
|
||||||
|
@ -937,30 +980,26 @@ const app = new Vue({
|
||||||
include: "tracks",
|
include: "tracks",
|
||||||
platform: "web",
|
platform: "web",
|
||||||
"include[library-playlists]": "catalog,tracks",
|
"include[library-playlists]": "catalog,tracks",
|
||||||
"fields[playlists]": "curatorName,playlistType,name,artwork,url",
|
"fields[playlists]": "curatorName,playlistType,name,artwork,url,playParams",
|
||||||
"include[library-songs]": "catalog,artists,albums",
|
"include[library-songs]": "catalog,artists,albums,playParams,name,artwork,url",
|
||||||
"fields[catalog]": "artistUrl,albumUrl",
|
"fields[catalog]": "artistUrl,albumUrl,url",
|
||||||
"fields[songs]": "artistUrl,albumUrl"
|
"fields[songs]": "artistUrl,albumUrl,playParams,name,artwork,url,artistName,albumName,durationInMillis"
|
||||||
}
|
}
|
||||||
if (!transient) {
|
if (!transient) {
|
||||||
this.playlists.loadingState = 0;
|
this.playlists.loadingState = 0;
|
||||||
}
|
}
|
||||||
let playlistId = ''
|
app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, params).then(res => {
|
||||||
|
|
||||||
try {
|
|
||||||
app.mk.api.library.playlist(id, params).then(res => {
|
|
||||||
self.getPlaylistContinuous(res, transient)
|
self.getPlaylistContinuous(res, transient)
|
||||||
})
|
}).catch((e) => {
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
console.log(e);
|
||||||
try {
|
try {
|
||||||
app.mk.api.library.playlist(id, params).then(res => {
|
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`, params).then(res => {
|
||||||
self.getPlaylistContinuous(res, transient)
|
self.getPlaylistContinuous(res, transient)
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
async getArtistFromID(id) {
|
async getArtistFromID(id) {
|
||||||
|
@ -973,8 +1012,8 @@ const app = new Vue({
|
||||||
"limit[artists:top-songs]": 20,
|
"limit[artists:top-songs]": 20,
|
||||||
"art[url]": "f"
|
"art[url]": "f"
|
||||||
}, { includeResponseMeta: !0 })
|
}, { includeResponseMeta: !0 })
|
||||||
console.log(artistData)
|
console.log(artistData.data.data[0])
|
||||||
this.artistPage.data = artistData.data[0]
|
this.artistPage.data = artistData.data.data[0]
|
||||||
this.page = "artist-page"
|
this.page = "artist-page"
|
||||||
},
|
},
|
||||||
progressBarStyle() {
|
progressBarStyle() {
|
||||||
|
@ -1023,7 +1062,7 @@ const app = new Vue({
|
||||||
this.search.hints = []
|
this.search.hints = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let hints = await app.mkapi("searchHints", false, this.search.term)
|
let hints = await (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search/hints?term=${this.search.term}`)).data.results
|
||||||
this.search.hints = hints ? hints.terms : []
|
this.search.hints = hints ? hints.terms : []
|
||||||
},
|
},
|
||||||
getSongProgress() {
|
getSongProgress() {
|
||||||
|
@ -1039,7 +1078,8 @@ const app = new Vue({
|
||||||
return `${mins}:${seconds.replace("0.", "")}`
|
return `${mins}:${seconds.replace("0.", "")}`
|
||||||
},
|
},
|
||||||
hashCode(str) {
|
hashCode(str) {
|
||||||
let hash = 0, i, chr;
|
let hash = 0,
|
||||||
|
i, chr;
|
||||||
if (str.length === 0) return hash;
|
if (str.length === 0) return hash;
|
||||||
for (i = 0; i < str.length; i++) {
|
for (i = 0; i < str.length; i++) {
|
||||||
chr = str.charCodeAt(i);
|
chr = str.charCodeAt(i);
|
||||||
|
@ -1075,8 +1115,7 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
routeView(item) {
|
routeView(item) {
|
||||||
let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? ''));
|
let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? ''));
|
||||||
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));
|
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));;
|
||||||
;
|
|
||||||
let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false;
|
let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false;
|
||||||
console.log(kind, id, isLibrary)
|
console.log(kind, id, isLibrary)
|
||||||
|
|
||||||
|
@ -1114,7 +1153,10 @@ const app = new Vue({
|
||||||
window.location.hash = `${kind}/${id}`
|
window.location.hash = `${kind}/${id}`
|
||||||
document.querySelector("#app-content").scrollTop = 0
|
document.querySelector("#app-content").scrollTop = 0
|
||||||
} else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) {
|
} else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) {
|
||||||
let params = {extend: "editorialVideo"}
|
let params = {
|
||||||
|
extend: "offers,editorialVideo",
|
||||||
|
"views": "appears-on,more-by-artist,related-videos,other-versions,you-might-also-like,video-extras,audio-extras",
|
||||||
|
}
|
||||||
app.page = (kind) + "_" + (id);
|
app.page = (kind) + "_" + (id);
|
||||||
app.getTypeFromID((kind), (id), (isLibrary), params);
|
app.getTypeFromID((kind), (id), (isLibrary), params);
|
||||||
window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}`
|
window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}`
|
||||||
|
@ -1137,7 +1179,7 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
async getNowPlayingItemDetailed(target) {
|
async getNowPlayingItemDetailed(target) {
|
||||||
let u = await app.mkapi(app.mk.nowPlayingItem.playParams.kind, (app.mk.nowPlayingItem.songId == -1), (app.mk.nowPlayingItem.songId != -1) ? app.mk.nowPlayingItem.songId : app.mk.nowPlayingItem["id"], { "include[songs]": "albums,artists" });
|
let u = await app.mkapi(app.mk.nowPlayingItem.playParams.kind, (app.mk.nowPlayingItem.songId == -1), (app.mk.nowPlayingItem.songId != -1) ? app.mk.nowPlayingItem.songId : app.mk.nowPlayingItem["id"], { "include[songs]": "albums,artists" });
|
||||||
app.searchAndNavigate(u, target)
|
app.searchAndNavigate(u.data.data[0], target)
|
||||||
},
|
},
|
||||||
async searchAndNavigate(item, target) {
|
async searchAndNavigate(item, target) {
|
||||||
let self = this
|
let self = this
|
||||||
|
@ -1158,21 +1200,19 @@ const app = new Vue({
|
||||||
artistId = artistId.substring(artistId.lastIndexOf('ids=') + 4, artistId.lastIndexOf('-'))
|
artistId = artistId.substring(artistId.lastIndexOf('ids=') + 4, artistId.lastIndexOf('-'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {}
|
||||||
}
|
|
||||||
|
|
||||||
if (artistId == "") {
|
if (artistId == "") {
|
||||||
let artistQuery = await app.mk.api.search(item.attributes.artistName, {
|
let artistQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.artistName}`, {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
types: 'artists'
|
types: 'artists'
|
||||||
})
|
})).data.results;
|
||||||
try {
|
try {
|
||||||
if (artistQuery.artists.data.length > 0) {
|
if (artistQuery.artists.data.length > 0) {
|
||||||
artistId = artistQuery.artists.data[0].id;
|
artistId = artistQuery.artists.data[0].id;
|
||||||
console.log(artistId)
|
console.log(artistId)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
console.log(artistId);
|
console.log(artistId);
|
||||||
if (artistId != "")
|
if (artistId != "")
|
||||||
|
@ -1193,21 +1233,19 @@ const app = new Vue({
|
||||||
albumId = albumId.substring(0, albumId.indexOf("?i="))
|
albumId = albumId.substring(0, albumId.indexOf("?i="))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {}
|
||||||
}
|
|
||||||
|
|
||||||
if (albumId == "") {
|
if (albumId == "") {
|
||||||
try {
|
try {
|
||||||
let albumQuery = await app.mk.api.search(item.attributes.albumName + " " + (item.attributes.artistName ?? ""), {
|
let albumQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.albumName + " " + (item.attributes.artistName ?? "")}`, {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
types: 'albums'
|
types: 'albums'
|
||||||
})
|
})).data.results;
|
||||||
if (albumQuery.albums.data.length > 0) {
|
if (albumQuery.albums.data.length > 0) {
|
||||||
albumId = albumQuery.albums.data[0].id;
|
albumId = albumQuery.albums.data[0].id;
|
||||||
console.log(albumId)
|
console.log(albumId)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (albumId != "") {
|
if (albumId != "") {
|
||||||
self.appRoute(`album/${albumId}`)
|
self.appRoute(`album/${albumId}`)
|
||||||
|
@ -1217,21 +1255,19 @@ const app = new Vue({
|
||||||
let labelId = '';
|
let labelId = '';
|
||||||
try {
|
try {
|
||||||
labelId = item.relationships['record-labels'].data[0].id
|
labelId = item.relationships['record-labels'].data[0].id
|
||||||
} catch (_) {
|
} catch (_) {}
|
||||||
}
|
|
||||||
|
|
||||||
if (labelId == "") {
|
if (labelId == "") {
|
||||||
try {
|
try {
|
||||||
let labelQuery = await app.mk.api.search(item.attributes.recordLabel, {
|
let labelQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.recordLabel}`, {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
types: 'record-labels'
|
types: 'record-labels'
|
||||||
})
|
})).data.results;
|
||||||
if (labelQuery["record-labels"].data.length > 0) {
|
if (labelQuery["record-labels"].data.length > 0) {
|
||||||
labelId = labelQuery["record-labels"].data[0].id;
|
labelId = labelQuery["record-labels"].data[0].id;
|
||||||
console.log(labelId)
|
console.log(labelId)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (labelId != "") {
|
if (labelId != "") {
|
||||||
app.showingPlaylist = []
|
app.showingPlaylist = []
|
||||||
|
@ -1252,8 +1288,7 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
playMediaItem(item) {
|
playMediaItem(item) {
|
||||||
let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? ''));
|
let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? ''));
|
||||||
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));
|
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));;
|
||||||
;
|
|
||||||
let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false;
|
let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false;
|
||||||
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
console.log(kind, id, isLibrary)
|
console.log(kind, id, isLibrary)
|
||||||
|
@ -1350,19 +1385,18 @@ const app = new Vue({
|
||||||
a = []
|
a = []
|
||||||
} finally {
|
} finally {
|
||||||
if (kind == "appleCurator") {
|
if (kind == "appleCurator") {
|
||||||
app.appleCurator = a
|
app.appleCurator = a.data.data[0]
|
||||||
} else {
|
} else {
|
||||||
this.getPlaylistContinuous(a)
|
this.getPlaylistContinuous(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (kind == "appleCurator") {
|
if (kind == "appleCurator") {
|
||||||
app.appleCurator = a
|
app.appleCurator = a.data.data[0]
|
||||||
} else {
|
} else {
|
||||||
this.getPlaylistContinuous(a)
|
this.getPlaylistContinuous(a)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
;
|
|
||||||
},
|
},
|
||||||
searchLibrarySongs() {
|
searchLibrarySongs() {
|
||||||
let self = this
|
let self = this
|
||||||
|
@ -1566,14 +1600,17 @@ const app = new Vue({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mkapi(method, library = false, term, params = {}, params2 = {}, attempts = 0) {
|
async mkapi(method, library = false, term, params = {}, params2 = {}, attempts = 0) {
|
||||||
|
if (method.includes(`recordLabel`)) { method = `record-labels` }
|
||||||
|
if (method.includes(`appleCurator`)) { method = `apple-curators` }
|
||||||
if (attempts > 3) {
|
if (attempts > 3) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let truemethod = (!method.endsWith("s")) ? (method + "s") : method;
|
||||||
try {
|
try {
|
||||||
if (library) {
|
if (library) {
|
||||||
return await this.mk.api.library[method](term, params, params2)
|
return await this.mk.api.v3.music(`v1/me/library/${truemethod}/${term.toString()}`, params, params2)
|
||||||
} else {
|
} else {
|
||||||
return await this.mk.api[method](term, params, params2)
|
return await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
@ -1625,14 +1662,14 @@ const app = new Vue({
|
||||||
}
|
}
|
||||||
self.library.songs.downloadState = 1
|
self.library.songs.downloadState = 1
|
||||||
if (downloaded == null) {
|
if (downloaded == null) {
|
||||||
app.mk.api.library.songs("", params, {includeResponseMeta: !0}).then((response) => {
|
app.mk.api.v3.music(`/v1/me/library/songs/`, params).then((response) => {
|
||||||
processChunk(response)
|
processChunk(response.data)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (downloaded.next != null && typeof downloaded.next === "function") {
|
if (downloaded.next != null) {
|
||||||
downloaded.next("", params, {includeResponseMeta: !0}).then((response) => {
|
app.mk.api.v3.music(downloaded.next, params).then((response) => {
|
||||||
processChunk(response)
|
processChunk(response.data)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log("Download next", downloaded.next)
|
console.log("Download next", downloaded.next)
|
||||||
|
@ -1706,13 +1743,13 @@ const app = new Vue({
|
||||||
limit: 100,
|
limit: 100,
|
||||||
}
|
}
|
||||||
if (downloaded == null) {
|
if (downloaded == null) {
|
||||||
app.mk.api.library.albums("", params, {includeResponseMeta: !0}).then((response) => {
|
app.mk.api.v3.music(`/v1/me/library/albums/`, params).then((response) => {
|
||||||
processChunk(response)
|
processChunk(response.data)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (downloaded.next != null && typeof downloaded.next === "function") {
|
if (downloaded.next != null) {
|
||||||
downloaded.next("", params, {includeResponseMeta: !0}).then((response) => {
|
app.mk.api.v3.music(downloaded.next, params).then((response) => {
|
||||||
processChunk(response)
|
processChunk(response.data)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log("Download next", downloaded.next)
|
console.log("Download next", downloaded.next)
|
||||||
|
@ -1787,13 +1824,14 @@ const app = new Vue({
|
||||||
limit: 100,
|
limit: 100,
|
||||||
}
|
}
|
||||||
if (downloaded == null) {
|
if (downloaded == null) {
|
||||||
app.mk.api.library.artists("", params, {includeResponseMeta: !0}).then((response) => {
|
app.mk.api.v3.music(`/v1/me/library/artists/`, params).then((response) => {
|
||||||
processChunk(response)
|
processChunk(response.data)
|
||||||
})
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (downloaded.next != null && typeof downloaded.next === "function") {
|
if (downloaded.next != null) {
|
||||||
downloaded.next("", "artists", {includeResponseMeta: !0}).then((response) => {
|
app.mk.api.v3.music(downloaded.next, params).then((response) => {
|
||||||
processChunk(response)
|
processChunk(response.data)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log("Download next", downloaded.next)
|
console.log("Download next", downloaded.next)
|
||||||
|
@ -1852,21 +1890,20 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
async getLibrarySongs() {
|
async getLibrarySongs() {
|
||||||
let response = await this.mkapi("songs", true, "", { limit: 100 }, { includeResponseMeta: !0 })
|
let response = await this.mkapi("songs", true, "", { limit: 100 }, { includeResponseMeta: !0 })
|
||||||
this.library.songs.listing = response.data
|
this.library.songs.listing = response.data.data
|
||||||
this.library.songs.meta = response.meta
|
this.library.songs.meta = response.data.meta
|
||||||
},
|
},
|
||||||
async getLibraryAlbums() {
|
async getLibraryAlbums() {
|
||||||
let response = await this.mkapi("albums", true, "", { limit: 100 }, { includeResponseMeta: !0 })
|
let response = await this.mkapi("albums", true, "", { limit: 100 }, { includeResponseMeta: !0 })
|
||||||
this.library.albums.listing = response.data
|
this.library.albums.listing = response.data.data
|
||||||
this.library.albums.meta = response.meta
|
this.library.albums.meta = response.data.meta
|
||||||
},
|
},
|
||||||
async getListenNow(attempt = 0) {
|
async getListenNow(attempt = 0) {
|
||||||
if (attempt > 3) {
|
if (attempt > 3) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.listennow = await this.mk.api.personalRecommendations("",
|
this.listennow = (await this.mk.api.v3.music(`v1/me/recommendations?timezone=${encodeURIComponent(this.formatTimezoneOffset())}`, {
|
||||||
{
|
|
||||||
name: "listen-now",
|
name: "listen-now",
|
||||||
with: "friendsMix,library,social",
|
with: "friendsMix,library,social",
|
||||||
"art[social-profiles:url]": "c",
|
"art[social-profiles:url]": "c",
|
||||||
|
@ -1888,11 +1925,10 @@ const app = new Vue({
|
||||||
"meta[stations]": "inflectionPoints",
|
"meta[stations]": "inflectionPoints",
|
||||||
types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-upsells",
|
types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-upsells",
|
||||||
platform: "web"
|
platform: "web"
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
includeResponseMeta: !0,
|
includeResponseMeta: !0,
|
||||||
reload: !0
|
reload: !0
|
||||||
});
|
})).data;
|
||||||
console.log(this.listennow)
|
console.log(this.listennow)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
@ -1904,8 +1940,7 @@ const app = new Vue({
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let browse = await this.mk.api.groupings("",
|
let browse = await app.mk.api.v3.music(`/v1/editorial/${app.mk.storefrontId}/groupings`, {
|
||||||
{
|
|
||||||
platform: "web",
|
platform: "web",
|
||||||
name: "music",
|
name: "music",
|
||||||
"omit[resource:artists]": "relationships",
|
"omit[resource:artists]": "relationships",
|
||||||
|
@ -1916,7 +1951,7 @@ const app = new Vue({
|
||||||
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
|
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
|
||||||
"art[url]": "f"
|
"art[url]": "f"
|
||||||
});
|
});
|
||||||
this.browsepage = browse[0];
|
this.browsepage = browse.data.data[0];
|
||||||
console.log(this.browsepage)
|
console.log(this.browsepage)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
@ -1928,11 +1963,10 @@ const app = new Vue({
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.radio.personal = await this.mkapi("recentRadioStations", false, "",
|
this.radio.personal = (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`, {
|
||||||
{
|
|
||||||
"platform": "web",
|
"platform": "web",
|
||||||
"art[url]": "f"
|
"art[url]": "f"
|
||||||
});
|
})).data.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
this.getRadioStations(attempt + 1)
|
this.getRadioStations(attempt + 1)
|
||||||
|
@ -1953,9 +1987,7 @@ const app = new Vue({
|
||||||
newPlaylistFolder(name = "New Folder") {
|
newPlaylistFolder(name = "New Folder") {
|
||||||
let self = this
|
let self = this
|
||||||
this.mk.api.v3.music(
|
this.mk.api.v3.music(
|
||||||
"/v1/me/library/playlist-folders/",
|
"/v1/me/library/playlist-folders/", {}, {
|
||||||
{},
|
|
||||||
{
|
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -2018,7 +2050,11 @@ const app = new Vue({
|
||||||
removeFromLibrary(kind, id) {
|
removeFromLibrary(kind, id) {
|
||||||
let self = this
|
let self = this
|
||||||
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
this.mk.api.library.remove({[truekind]: id}).then((data) => {
|
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`, {}, {
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
self.getLibrarySongsFull(true)
|
self.getLibrarySongsFull(true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -2168,8 +2204,7 @@ const app = new Vue({
|
||||||
lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"];
|
lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"];
|
||||||
richsync = JSON.parse(lrcrich);
|
richsync = JSON.parse(lrcrich);
|
||||||
app.richlyrics = richsync;
|
app.richlyrics = richsync;
|
||||||
} catch (_) {
|
} catch (_) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lrcfile == "") {
|
if (lrcfile == "") {
|
||||||
|
@ -2398,7 +2433,9 @@ const app = new Vue({
|
||||||
MusicKit.getInstance().play()
|
MusicKit.getInstance().play()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.mk.setQueue({[truekind]: [id]}).then(function (queue) {
|
this.mk.setQueue({
|
||||||
|
[truekind]: [id]
|
||||||
|
}).then(function(queue) {
|
||||||
MusicKit.getInstance().play()
|
MusicKit.getInstance().play()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2443,7 +2480,9 @@ const app = new Vue({
|
||||||
if (childIndex != -1) {
|
if (childIndex != -1) {
|
||||||
app.mk.changeToMediaAtIndex(childIndex)
|
app.mk.changeToMediaAtIndex(childIndex)
|
||||||
} else if (item) {
|
} else if (item) {
|
||||||
app.mk.playNext({[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id}).then(function () {
|
app.mk.playNext({
|
||||||
|
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id
|
||||||
|
}).then(function() {
|
||||||
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1)
|
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1)
|
||||||
app.mk.play()
|
app.mk.play()
|
||||||
})
|
})
|
||||||
|
@ -2455,7 +2494,9 @@ const app = new Vue({
|
||||||
} else {
|
} else {
|
||||||
app.mk.stop().then(() => {
|
app.mk.stop().then(() => {
|
||||||
if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) {
|
if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) {
|
||||||
app.mk.setQueue({[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id}).then(function () {
|
app.mk.setQueue({
|
||||||
|
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id
|
||||||
|
}).then(function() {
|
||||||
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function() {
|
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function() {
|
||||||
if ((app.showingPlaylist && app.showingPlaylist.id == id)) {
|
if ((app.showingPlaylist && app.showingPlaylist.id == id)) {
|
||||||
let query = app.showingPlaylist.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
|
let query = app.showingPlaylist.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
|
||||||
|
@ -2478,14 +2519,18 @@ const app = new Vue({
|
||||||
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.mk.setQueue({[truekind]: [id]}).then(function (queue) {
|
this.mk.setQueue({
|
||||||
|
[truekind]: [id]
|
||||||
|
}).then(function(queue) {
|
||||||
if (item && ((queue._itemIDs[childIndex] != item.id))) {
|
if (item && ((queue._itemIDs[childIndex] != item.id))) {
|
||||||
childIndex = queue._itemIDs.indexOf(item.id)
|
childIndex = queue._itemIDs.indexOf(item.id)
|
||||||
}
|
}
|
||||||
if (childIndex != -1) {
|
if (childIndex != -1) {
|
||||||
app.mk.changeToMediaAtIndex(childIndex)
|
app.mk.changeToMediaAtIndex(childIndex)
|
||||||
} else if (item) {
|
} else if (item) {
|
||||||
app.mk.playNext({[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id}).then(function () {
|
app.mk.playNext({
|
||||||
|
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id
|
||||||
|
}).then(function() {
|
||||||
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1)
|
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1)
|
||||||
app.mk.play()
|
app.mk.play()
|
||||||
})
|
})
|
||||||
|
@ -2500,8 +2545,7 @@ const app = new Vue({
|
||||||
console.log(err)
|
console.log(err)
|
||||||
try {
|
try {
|
||||||
app.mk.stop()
|
app.mk.stop()
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
this.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)
|
this.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2558,8 +2602,8 @@ const app = new Vue({
|
||||||
if (term == "") {
|
if (term == "") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.mk.api.search(this.search.term,
|
//this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}`
|
||||||
{
|
this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}`, {
|
||||||
types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels",
|
types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels",
|
||||||
"relate[editorial-items]": "contents",
|
"relate[editorial-items]": "contents",
|
||||||
"include[editorial-items]": "contents",
|
"include[editorial-items]": "contents",
|
||||||
|
@ -2576,16 +2620,19 @@ const app = new Vue({
|
||||||
"platform": "web",
|
"platform": "web",
|
||||||
limit: 25
|
limit: 25
|
||||||
}).then(function(results) {
|
}).then(function(results) {
|
||||||
self.search.results = results
|
results.data.results["meta"] = results.data.meta
|
||||||
|
self.search.results = results.data.results
|
||||||
})
|
})
|
||||||
await this.mk.api.socialSearch(this.search.term, {
|
|
||||||
|
await app.mk.api.v3.music(`v1/social/${app.mk.storefrontId}/search?term=${app.search.term}`, {
|
||||||
types: ["playlists", "social-profiles"],
|
types: ["playlists", "social-profiles"],
|
||||||
limit: 25,
|
limit: 25,
|
||||||
with: ["serverBubbles", "lyricSnippet"],
|
with: ["serverBubbles", "lyricSnippet"],
|
||||||
"art[url]": "f",
|
"art[url]": "f",
|
||||||
"art[social-profiles:url]": "c"
|
"art[social-profiles:url]": "c"
|
||||||
}, { includeResponseMeta: !0 }).then(function(results) {
|
}, { includeResponseMeta: !0 }).then(function(results) {
|
||||||
self.search.resultsSocial = results
|
results.data.results["meta"] = results.data.meta
|
||||||
|
self.search.resultsSocial = results.data.results
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async inLibrary(items = []) {
|
async inLibrary(items = []) {
|
||||||
|
@ -2608,11 +2655,23 @@ const app = new Vue({
|
||||||
types[index].id.push(id)
|
types[index].id.push(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await this.mk.api.catalogResources(types, {
|
types2 = types.map(function(item) {
|
||||||
|
return {
|
||||||
|
[`ids[${item.type}]`]: [item.id]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
types2 = types2.reduce(function(result, item) {
|
||||||
|
var key = Object.keys(item)[0]; //first property: a, b, c
|
||||||
|
result[key] = item[key];
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
return (await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}`, {... {
|
||||||
"omit[resource]": "autos",
|
"omit[resource]": "autos",
|
||||||
relate: "library",
|
relate: "library",
|
||||||
fields: "inLibrary"
|
fields: "inLibrary"
|
||||||
})
|
},
|
||||||
|
...types2
|
||||||
|
})).data.data
|
||||||
},
|
},
|
||||||
isInLibrary(playParams) {
|
isInLibrary(playParams) {
|
||||||
let self = this
|
let self = this
|
||||||
|
@ -2704,24 +2763,21 @@ const app = new Vue({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clearInterval(bginterval);
|
clearInterval(bginterval);
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.setLibraryArtBG()
|
this.setLibraryArtBG()
|
||||||
}
|
}
|
||||||
} else if (this.mk.nowPlayingItem["id"] == this.currentTrackID) {
|
} else if (this.mk.nowPlayingItem["id"] == this.currentTrackID) {
|
||||||
try {
|
try {
|
||||||
clearInterval(bginterval);
|
clearInterval(bginterval);
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] && document.querySelector('.bg-artwork')) {
|
if (this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] && document.querySelector('.bg-artwork')) {
|
||||||
this.setLibraryArtBG()
|
this.setLibraryArtBG()
|
||||||
try {
|
try {
|
||||||
clearInterval(bginterval);
|
clearInterval(bginterval);
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
|
@ -2774,22 +2830,20 @@ const app = new Vue({
|
||||||
this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50);
|
this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50);
|
||||||
try {
|
try {
|
||||||
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
|
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let data = await this.mk.api.library.song(this.mk.nowPlayingItem.id);
|
let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`);
|
||||||
|
data = data.data.data[0];
|
||||||
if (data != null && data !== "" && data.attributes != null && data.attributes.artwork != null) {
|
if (data != null && data !== "" && data.attributes != null && data.attributes.artwork != null) {
|
||||||
this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50);
|
this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50);
|
||||||
try {
|
try {
|
||||||
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
|
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.currentArtUrl = '';
|
this.currentArtUrl = '';
|
||||||
try {
|
try {
|
||||||
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
|
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -2798,23 +2852,22 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
async setLibraryArt() {
|
async setLibraryArt() {
|
||||||
if (typeof this.mk.nowPlayingItem === "undefined") return;
|
if (typeof this.mk.nowPlayingItem === "undefined") return;
|
||||||
const data = await this.mk.api.library.song(this.mk.nowPlayingItem["id"])
|
|
||||||
try {
|
try {
|
||||||
const data = await this.mk.api.library.song(this.mk.nowPlayingItem.id)
|
const data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`);
|
||||||
|
data = data.data.data[0];
|
||||||
|
|
||||||
if (data != null && data !== "") {
|
if (data != null && data !== "") {
|
||||||
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', 'url("' + (data["attributes"]["artwork"]["url"]).toString() + '")');
|
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', 'url("' + (data["attributes"]["artwork"]["url"]).toString() + '")');
|
||||||
} else {
|
} else {
|
||||||
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("")`);
|
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("")`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async setLibraryArtBG() {
|
async setLibraryArtBG() {
|
||||||
if (typeof this.mk.nowPlayingItem === "undefined") return;
|
if (typeof this.mk.nowPlayingItem === "undefined") return;
|
||||||
const data = await this.mk.api.library.song(this.mk.nowPlayingItem["id"])
|
|
||||||
try {
|
try {
|
||||||
const data = await this.mk.api.library.song(this.mk.nowPlayingItem.id)
|
const data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`);
|
||||||
|
data = data.data.data[0];
|
||||||
|
|
||||||
if (data != null && data !== "") {
|
if (data != null && data !== "") {
|
||||||
getBase64FromUrl((data["attributes"]["artwork"]["url"]).toString()).then(img => {
|
getBase64FromUrl((data["attributes"]["artwork"]["url"]).toString()).then(img => {
|
||||||
|
@ -2824,8 +2877,7 @@ const app = new Vue({
|
||||||
self.$store.commit("setLCDArtwork", img)
|
self.$store.commit("setLCDArtwork", img)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
quickPlay(query) {
|
quickPlay(query) {
|
||||||
|
@ -2848,7 +2900,7 @@ const app = new Vue({
|
||||||
}
|
}
|
||||||
id = item.id
|
id = item.id
|
||||||
}
|
}
|
||||||
let response = await this.mk.api.v3.music(`/v1/me/ratings/${type}?platform=web&ids=${id}`)
|
let response = await this.mk.api.v3.music(`/v1/me/ratings/${type}?platform=web&ids=${type.includes('library') ? item.id : id}`)
|
||||||
if (response.data.data.length != 0) {
|
if (response.data.data.length != 0) {
|
||||||
let value = response.data.data[0].attributes.value
|
let value = response.data.data[0].attributes.value
|
||||||
return value
|
return value
|
||||||
|
@ -2866,17 +2918,14 @@ const app = new Vue({
|
||||||
id = item.id
|
id = item.id
|
||||||
}
|
}
|
||||||
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
|
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
|
||||||
fetchOptions:
|
fetchOptions: {
|
||||||
{
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(
|
body: JSON.stringify({
|
||||||
{
|
|
||||||
"type": "rating",
|
"type": "rating",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -2890,17 +2939,14 @@ const app = new Vue({
|
||||||
id = item.id
|
id = item.id
|
||||||
}
|
}
|
||||||
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
|
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
|
||||||
fetchOptions:
|
fetchOptions: {
|
||||||
{
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(
|
body: JSON.stringify({
|
||||||
{
|
|
||||||
"type": "rating",
|
"type": "rating",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"value": -1
|
"value": -1
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -2914,8 +2960,7 @@ const app = new Vue({
|
||||||
id = item.id
|
id = item.id
|
||||||
}
|
}
|
||||||
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
|
this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, {
|
||||||
fetchOptions:
|
fetchOptions: {
|
||||||
{
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2965,8 +3010,9 @@ const app = new Vue({
|
||||||
},
|
},
|
||||||
fetchPlaylist(id, callback) {
|
fetchPlaylist(id, callback) {
|
||||||
// id can be found in playlist.attributes.playParams.globalId
|
// id can be found in playlist.attributes.playParams.globalId
|
||||||
this.mk.api.playlist(id).then(res => {
|
// this.mk.api.
|
||||||
callback(res)
|
this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`).then(res => {
|
||||||
|
callback(res.data.data[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
// tracks are found in relationship.data
|
// tracks are found in relationship.data
|
||||||
|
@ -3005,8 +3051,7 @@ const app = new Vue({
|
||||||
items: []
|
items: []
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
headerItems: [
|
headerItems: [{
|
||||||
{
|
|
||||||
"icon": "./assets/feather/heart.svg",
|
"icon": "./assets/feather/heart.svg",
|
||||||
"id": "love",
|
"id": "love",
|
||||||
"name": "Love",
|
"name": "Love",
|
||||||
|
@ -3047,11 +3092,9 @@ const app = new Vue({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
items: [
|
items: [{
|
||||||
{
|
|
||||||
"icon": "./assets/feather/list.svg",
|
"icon": "./assets/feather/list.svg",
|
||||||
"name": "Add to Playlist...",
|
"name": "Add to Playlist...",
|
||||||
"hidden": true,
|
|
||||||
"action": function() {
|
"action": function() {
|
||||||
app.promptAddToPlaylist()
|
app.promptAddToPlaylist()
|
||||||
}
|
}
|
||||||
|
@ -3175,8 +3218,23 @@ const app = new Vue({
|
||||||
ipcRenderer.send('setFullScreen', false);
|
ipcRenderer.send('setFullScreen', false);
|
||||||
app.appMode = 'player';
|
app.appMode = 'player';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
formatTimezoneOffset: (e = new Date) => {
|
||||||
|
let leadingZeros = (e, s = 2) => {
|
||||||
|
let n = "" + e;
|
||||||
|
for (; n.length < s;)
|
||||||
|
n = "0" + n;
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const s = e.getTimezoneOffset(),
|
||||||
|
n = Math.floor(Math.abs(s) / 60),
|
||||||
|
d = Math.round(Math.abs(s) % 60);
|
||||||
|
let h = "+";
|
||||||
|
return 0 !== s && (h = s > 0 ? "-" : "+"),
|
||||||
|
`${h}${leadingZeros(n, 2)}:${leadingZeros(d, 2)}`
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3423,8 +3481,7 @@ var checkIfScrollIsStatic = setInterval(() => {
|
||||||
// do something
|
// do something
|
||||||
}
|
}
|
||||||
position = document.getElementsByClassName('lyric-body')[0].scrollTop
|
position = document.getElementsByClassName('lyric-body')[0].scrollTop
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
|
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
|
@ -3442,5 +3499,3 @@ webGPU().then()
|
||||||
|
|
||||||
let screenWidth = screen.width;
|
let screenWidth = screen.width;
|
||||||
let screenHeight = screen.height;
|
let screenHeight = screen.height;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
--navbarHeight: 48px;
|
--navbarHeight: 48px;
|
||||||
--selected: rgb(130 130 130 / 30%);
|
--selected: rgb(130 130 130 / 30%);
|
||||||
--selected-click: rgb(80 80 80 / 30%);
|
--selected-click: rgb(80 80 80 / 30%);
|
||||||
|
--hover: rgb(200 200 200 / 10%);
|
||||||
--keyColor: #fa586a;
|
--keyColor: #fa586a;
|
||||||
--keyColor-rgb: 250, 88, 106;
|
--keyColor-rgb: 250, 88, 106;
|
||||||
--keyColor-rollover: #ff8a9c;
|
--keyColor-rollover: #ff8a9c;
|
||||||
|
@ -254,6 +255,32 @@ input[type="text"], input[type="number"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artworkMaterial {
|
||||||
|
position: relative;
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
position: absolute;
|
||||||
|
width: 200%;
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: brightness(200%) blur(180px) saturate(280%) contrast(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
>img:first-child {
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
>img:last-child {
|
||||||
|
bottom:0;
|
||||||
|
right: 0;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[artwork-hidden] {
|
[artwork-hidden] {
|
||||||
transition: opacity .25s var(--appleEase);
|
transition: opacity .25s var(--appleEase);
|
||||||
|
@ -1105,6 +1132,34 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
filter: contrast(0.8);
|
filter: contrast(0.8);
|
||||||
|
|
||||||
|
.lcdMenu {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
background: transparent;
|
||||||
|
border: 0px;
|
||||||
|
appearance: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: var(--hover);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: var(--selected-click);
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
.svg-icon {
|
||||||
|
--url: url('views/svg/more.svg')!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-chrome .app-chrome-item > .app-playback-controls .playback-info {
|
.app-chrome .app-chrome-item > .app-playback-controls .playback-info {
|
||||||
|
@ -1896,6 +1951,36 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
|
|
||||||
/* Cider */
|
/* Cider */
|
||||||
|
|
||||||
|
.more-btn-round {
|
||||||
|
border-radius: 100%;
|
||||||
|
background: rgba(100, 100, 100, 0.5);
|
||||||
|
box-shadow: var(--ciderShadow-Generic);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
filter: brightness(75%);
|
||||||
|
transform: scale(0.98);
|
||||||
|
transition: transform 0s var(--appleEase), box-shadow 0.2s var(--appleEase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
width: 100%;
|
||||||
|
background: #eee;
|
||||||
|
--url: url("./views/svg/more.svg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.about-page {
|
.about-page {
|
||||||
.teamBtn {
|
.teamBtn {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1951,6 +2036,14 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
|
|
||||||
&.md-btn-block {
|
&.md-btn-block {
|
||||||
display: block;
|
display: block;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.md-btn-glyph {
|
||||||
|
display:flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.md-btn-primary {
|
&.md-btn-primary {
|
||||||
|
@ -2398,17 +2491,70 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
.playlist-page {
|
.playlist-page {
|
||||||
--bgColor: transparent;
|
--bgColor: transparent;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
background: linear-gradient(180deg, var(--bgColor) 32px, var(--bgColor) 59px, transparent 60px, transparent 100%);
|
//background: linear-gradient(180deg, var(--bgColor) 32px, var(--bgColor) 18px, transparent 60px, transparent 100%);
|
||||||
top: 0;
|
top: 0;
|
||||||
padding-top: var(--navigationBarHeight);
|
padding-top: var(--navigationBarHeight);
|
||||||
|
|
||||||
.playlist-body {
|
.playlist-body {
|
||||||
padding: var(--contentInnerPadding);
|
padding: 0px var(--contentInnerPadding) 0px var(--contentInnerPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-bottom: 1px solid rgba(200, 200, 200, 0.05);
|
||||||
|
z-index: 6;
|
||||||
|
padding: 0px 1em;
|
||||||
|
backdrop-filter: blur(32px);
|
||||||
|
background: rgba(24, 24, 24, 0.15);
|
||||||
|
top: var(--navigationBarHeight);
|
||||||
|
transition: opacity 0.1s var(--appleEase);
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-display {
|
.playlist-display {
|
||||||
padding: var(--contentInnerPadding);
|
padding: var(--contentInnerPadding);
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.artworkContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-mask-image: radial-gradient(at top left, black, transparent 70%), radial-gradient(at top right, black, transparent 70%), linear-gradient(180deg, rgb(200 200 200), transparent 98%);
|
||||||
|
opacity: .7;
|
||||||
|
animation: playlistArtworkFadeIn 1s var(--appleEase);
|
||||||
|
|
||||||
|
.artworkMaterial>img {
|
||||||
|
filter: brightness(100%) blur(80px) saturate(100%) contrast(1);
|
||||||
|
object-position: center;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistInfo {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
>.row {
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
}
|
||||||
|
|
||||||
.playlist-info {
|
.playlist-info {
|
||||||
flex-shrink: unset;
|
flex-shrink: unset;
|
||||||
|
@ -2509,6 +2655,9 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.friends-info {
|
.friends-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
|
@ -2539,26 +2688,6 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-more {
|
|
||||||
border-radius: 100%;
|
|
||||||
background: var(--keyColor);
|
|
||||||
box-shadow: var(--ciderShadow-Generic);
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
float: right;
|
|
||||||
border: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--keyColor-rollover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--keyColor-pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.playlist-time {
|
.playlist-time {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
|
@ -2566,6 +2695,14 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes playlistArtworkFadeIn {
|
||||||
|
0%{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100%{
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Collection Page
|
// Collection Page
|
||||||
.collection-page {
|
.collection-page {
|
||||||
padding-bottom: 128px;
|
padding-bottom: 128px;
|
||||||
|
@ -2608,8 +2745,21 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
|
.floating-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-bottom: 1px solid rgba(200, 200, 200, 0.05);
|
||||||
|
z-index: 6;
|
||||||
|
padding: 0px 1em;
|
||||||
|
backdrop-filter: blur(32px);
|
||||||
|
background: rgba(24, 24, 24, 0.15);
|
||||||
|
top: var(--navigationBarHeight);
|
||||||
|
transition: opacity 0.1s var(--appleEase);
|
||||||
|
}
|
||||||
|
|
||||||
.artist-header {
|
.artist-header {
|
||||||
background: linear-gradient(45deg, var(--keyColor), #0e0e0e);
|
//background: linear-gradient(45deg, var(--keyColor), #0e0e0e);
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -2617,26 +2767,36 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.artist-more {
|
.header-content {
|
||||||
border-radius: 100%;
|
z-index: 1;
|
||||||
background: var(--keyColor);
|
}
|
||||||
box-shadow: var(--ciderShadow-Generic);
|
|
||||||
width: 36px;
|
.artworkContainer {
|
||||||
height: 36px;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-mask-image: radial-gradient(at top left, black, transparent 70%), radial-gradient(at top right, black, transparent 70%), linear-gradient(180deg, rgb(200 200 200), transparent 98%);
|
||||||
|
opacity: .7;
|
||||||
|
animation: playlistArtworkFadeIn 1s var(--appleEase);
|
||||||
|
|
||||||
|
.artworkMaterial>img {
|
||||||
|
filter: brightness(100%) blur(80px) saturate(100%) contrast(1);
|
||||||
|
object-position: center;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-btn-round {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 26px;
|
bottom: 26px;
|
||||||
right: 32px;
|
right: 32px;
|
||||||
border: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--keyColor-rollover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--keyColor-pressed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.animated {
|
.animated {
|
||||||
|
@ -2703,20 +2863,17 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-title {
|
|
||||||
.artist-play {
|
.artist-play {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
background: var(--keyColor);
|
background: var(--keyColor);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
margin: 14px;
|
|
||||||
box-shadow: var(--mediaItemShadow);
|
box-shadow: var(--mediaItemShadow);
|
||||||
display: none;
|
display: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
transform: translateY(3px);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--keyColor-rollover);
|
background: var(--keyColor-rollover);
|
||||||
|
@ -2726,6 +2883,12 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
background: var(--keyColor-pressed);
|
background: var(--keyColor-pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.artist-title {
|
||||||
|
|
||||||
|
.artist-play {
|
||||||
|
transform: translateY(3px);
|
||||||
|
margin: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
&.artist-animation-on {
|
&.artist-animation-on {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -2742,7 +2905,8 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-body {
|
.artist-body {
|
||||||
padding: var(--contentInnerPadding);
|
padding: 0px var(--contentInnerPadding) 0px var(--contentInnerPadding);
|
||||||
|
margin-top: -48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.showmoreless {
|
.showmoreless {
|
||||||
|
|
37
src/renderer/views/components/artwork-material.ejs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<script type="text/x-template" id="artwork-material">
|
||||||
|
<div class="artworkMaterial">
|
||||||
|
<img :src="src" v-for="image in images"/>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('artwork-material', {
|
||||||
|
template: '#artwork-material',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
src: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.src = app.getMediaItemArtwork(this.url, this.size)
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: false,
|
||||||
|
default: '32'
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: false,
|
||||||
|
default: '2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -70,14 +70,14 @@
|
||||||
},
|
},
|
||||||
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.v3.music(`/v1/me/library/artists/${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.data.data)}, u.attributes.name?? '', '');
|
||||||
},
|
},
|
||||||
getArtwork(){
|
getArtwork(){
|
||||||
let u = ""
|
let u = ""
|
||||||
|
|
0
src/renderer/views/components/mediaitem-info.ejs
Normal file
|
@ -502,22 +502,21 @@
|
||||||
app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () {
|
app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () {
|
||||||
app.mk.play().then(function (){
|
app.mk.play().then(function (){
|
||||||
var playlistId = id
|
var playlistId = id
|
||||||
function getPlaylist(id, params, isLibrary){
|
function getPlaylist(id, isLibrary){
|
||||||
if (isLibrary){
|
if (isLibrary){
|
||||||
return app.mk.api.library.playlist(id, params)
|
return this.app.mk.api.v3.music(`/v1/me/library/playlists/${id}`)
|
||||||
} else { return app.mk.api.playlist(id, params)}
|
} else { return this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`)}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
getPlaylist(id, isLibrary).then(res => {
|
||||||
getPlaylist(id, params, isLibrary).then(res => {
|
|
||||||
//let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
|
//let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
|
||||||
//if (app.mk.shuffleMode == 1){shuffleArray(query); }
|
//if (app.mk.shuffleMode == 1){shuffleArray(query); }
|
||||||
// console.log(query)
|
// console.log(query)
|
||||||
// app.mk.queue.append(query)
|
// app.mk.queue.append(query)
|
||||||
if (!res.relationships.tracks.next) {
|
if (!res.data.relationships.tracks.next) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
getPlaylistTracks(res.relationships.tracks.next)
|
getPlaylistTracks(res.data.relationships.tracks.next)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlaylistTracks(next) {
|
function getPlaylistTracks(next) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script type="text/x-template" id="mediaitem-scroller-horizontal">
|
<script type="text/x-template" id="mediaitem-scroller-horizontal">
|
||||||
<template>
|
<template>
|
||||||
<div class="cd-hmedia-scroller" :class="kind">
|
<div class="cd-hmedia-scroller" :class="kind">
|
||||||
|
<slot></slot>
|
||||||
<mediaitem-square :kind="kind" :item="item"
|
<mediaitem-square :kind="kind" :item="item"
|
||||||
v-for="item in items"></mediaitem-square>
|
v-for="item in items"></mediaitem-square>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
props: {
|
props: {
|
||||||
'items': {
|
'items': {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: false
|
||||||
},
|
},
|
||||||
'kind': {
|
'kind': {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -122,7 +122,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.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}})
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
},
|
},
|
||||||
async contextMenu(event) {
|
async contextMenu(event) {
|
||||||
|
|
|
@ -96,6 +96,7 @@
|
||||||
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);
|
||||||
|
res = res.data.data[0]
|
||||||
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 {
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
|
@ -105,12 +106,17 @@
|
||||||
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);
|
||||||
|
res = res.data.data[0]
|
||||||
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) {
|
||||||
id = res.relationships.library.data[0].id
|
id = res.relationships.library.data[0].id
|
||||||
}
|
}
|
||||||
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.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}})
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
},
|
},
|
||||||
subtitleSearchNavigate(item) {
|
subtitleSearchNavigate(item) {
|
||||||
|
|
|
@ -145,8 +145,8 @@
|
||||||
let friends = this.badges[id]
|
let friends = this.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.v3.music(`/v1/social/${app.mk.storefrontId}/social-profiles/${friend}`).then(data => {
|
||||||
self.itemBadges.push(data)
|
self.itemBadges.push(data.data.data[0])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,7 @@
|
||||||
"extend": this.revisedRandId()
|
"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);
|
||||||
|
res = res.data.data[0]
|
||||||
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 {
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
|
@ -179,12 +180,17 @@
|
||||||
}
|
}
|
||||||
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);
|
||||||
|
res= res.data.data[0]
|
||||||
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) {
|
||||||
id = res.relationships.library.data[0].id
|
id = res.relationships.library.data[0].id
|
||||||
}
|
}
|
||||||
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.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}})
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
},
|
},
|
||||||
uuidv4() {
|
uuidv4() {
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
this.children = []
|
this.children = []
|
||||||
this.getChildren()
|
this.getChildren()
|
||||||
this.toggleFolder()
|
this.toggleFolder()
|
||||||
this.$root.mk.api.library.playlistFolderChildren(item.id).then(children => {
|
|
||||||
|
this.$root.mk.api.v3.music(`v1/me/library/playlist-folders/${item.id}/children`).then(data => {
|
||||||
|
let children = data.data.data;
|
||||||
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
|
||||||
|
|
|
@ -115,11 +115,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
|
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
|
||||||
<div class="actions"
|
<div class="actions">
|
||||||
v-if="isInLibrary(mk.nowPlayingItem['attributes']['playParams'])">
|
<button class="lcdMenu" @click="nowPlayingContextMenu">
|
||||||
❤️
|
<div class="svg-icon"></div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions" v-else>🖤</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -661,6 +661,8 @@
|
||||||
</button>
|
</button>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Artwork Material -->
|
||||||
|
<%- include('components/artwork-material') %>
|
||||||
<!-- Menu Panel -->
|
<!-- Menu Panel -->
|
||||||
<%- include('components/menu-panel') %>
|
<%- include('components/menu-panel') %>
|
||||||
<!-- Playlist Listing -->
|
<!-- Playlist Listing -->
|
||||||
|
@ -712,5 +714,6 @@
|
||||||
<script src="index.js?v=1"></script>
|
<script src="index.js?v=1"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/resonance-audio/build/resonance-audio.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/resonance-audio/build/resonance-audio.min.js"></script>
|
||||||
<script src="/audio/audio.js?v=1"></script>
|
<script src="/audio/audio.js?v=1"></script>
|
||||||
|
<script src="/WSAPI_Interop.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,30 @@
|
||||||
<script type="text/x-template" id="cider-artist-feed">
|
<script type="text/x-template" id="cider-artist-feed">
|
||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row nopadding">
|
||||||
|
<div class="col nopadding">
|
||||||
|
<h3>Followed Artists</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="well">
|
||||||
|
<mediaitem-scroller-horizontal>
|
||||||
|
<div v-for="artist in artists" style="margin: 6px;">
|
||||||
|
<mediaitem-square :item="artist" kind="small"></mediaitem-square>
|
||||||
|
<button @click="unfollow(artist.id)" class="md-btn md-btn-glyph" style="display:flex;">
|
||||||
|
<div class="sidebar-icon">
|
||||||
|
<div class="svg-icon" :style="{'--url': 'url(./assets/feather/x-circle.svg)'}"></div>
|
||||||
|
</div> Unfollow
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mediaitem-scroller-horizontal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -30,6 +55,7 @@
|
||||||
app: this.$root,
|
app: this.$root,
|
||||||
followedArtists: this.$root.cfg.home.followedArtists,
|
followedArtists: this.$root.cfg.home.followedArtists,
|
||||||
artistFeed: [],
|
artistFeed: [],
|
||||||
|
artists: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
@ -37,19 +63,26 @@
|
||||||
await this.getArtistFeed()
|
await this.getArtistFeed()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
unfollow(id) {
|
||||||
|
let index = this.followedArtists.indexOf(id)
|
||||||
|
if (index > -1) {
|
||||||
|
this.followedArtists.splice(index, 1)
|
||||||
|
}
|
||||||
|
let artist = this.artists.find(a => a.id == id)
|
||||||
|
let index2 = this.artists.indexOf(artist)
|
||||||
|
if (index2 > -1) {
|
||||||
|
this.artists.splice(index2, 1)
|
||||||
|
}
|
||||||
|
this.getArtistFeed()
|
||||||
|
},
|
||||||
async getArtistFeed() {
|
async getArtistFeed() {
|
||||||
let artists = this.followedArtists
|
let artists = this.followedArtists
|
||||||
let self = this
|
let self = this
|
||||||
this.app.mk.api.artists(artists, {
|
this.artists = []
|
||||||
"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",
|
this.artistFeed = []
|
||||||
"extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero",
|
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${artists.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`).then(artistData => {
|
||||||
"extend[playlists]": "trackCount",
|
artistData.data.data.forEach(item => {
|
||||||
"include[songs]": "albums",
|
self.artists.push(item)
|
||||||
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount",
|
|
||||||
"limit[artists:top-songs]": 20,
|
|
||||||
"art[url]": "f"
|
|
||||||
}, {includeResponseMeta: !0}).then(artistData => {
|
|
||||||
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])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<script type="text/x-template" id="cider-artist">
|
<script type="text/x-template" id="cider-artist">
|
||||||
<div class="content-inner artist-page">
|
<div class="content-inner artist-page">
|
||||||
<div class="artist-header" :style="getArtistPalette(data)" :key="data.id">
|
<div class="artist-header" :key="data.id" v-observe-visibility="{callback: isHeaderVisible}">
|
||||||
<animatedartwork-view
|
<animatedartwork-view
|
||||||
:priority="true"
|
:priority="true"
|
||||||
v-if="data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)"
|
v-if="data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)"
|
||||||
:video="data.attributes.editorialVideo.motionArtistWide16x9.video ?? (data.attributes.editorialVideo.motionArtistFullscreen16x9.video ?? '')">
|
:video="data.attributes.editorialVideo.motionArtistWide16x9.video ?? (data.attributes.editorialVideo.motionArtistFullscreen16x9.video ?? '')">
|
||||||
</animatedartwork-view>
|
</animatedartwork-view>
|
||||||
|
<div class="header-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm" style="width: auto;">
|
<div class="col-sm" style="width: auto;">
|
||||||
<div class="artist-image" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
|
<div class="artist-image" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
|
||||||
|
@ -29,15 +30,31 @@
|
||||||
<h1>{{ data.attributes.name }}</h1>
|
<h1>{{ data.attributes.name }}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="artist-more" @click="artistMenu">
|
<button class="more-btn-round" @click="artistMenu">
|
||||||
<div style=" margin-top: -1px;
|
<div class="svg-icon"></div>
|
||||||
margin-left: -6px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;">
|
|
||||||
<%- include("../svg/more.svg") %>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="artworkContainer" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
|
||||||
|
<artwork-material :url="data.attributes.artwork.url" size="190" images="1"></artwork-material>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="floating-header" :style="{opacity: (headerVisible ? 0 : 1),'pointer-events': (headerVisible ? 'none' : '')}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<button class="artist-play" style="display:block;" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
||||||
|
app.mk.play()
|
||||||
|
})"><%- include("../svg/play.svg") %></button>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ data.attributes.name }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<button class="more-btn-round" @click="artistMenu">
|
||||||
|
<div class="svg-icon"></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="artist-body">
|
<div class="artist-body">
|
||||||
<div class="row well">
|
<div class="row well">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -133,10 +150,14 @@
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
topSongsExpanded: false,
|
topSongsExpanded: false,
|
||||||
app: this.$root
|
app: this.$root,
|
||||||
|
headerVisible: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isHeaderVisible(visible) {
|
||||||
|
this.headerVisible = visible
|
||||||
|
},
|
||||||
artistMenu (event) {
|
artistMenu (event) {
|
||||||
let self = this
|
let self = this
|
||||||
let followAction = "follow"
|
let followAction = "follow"
|
||||||
|
|
|
@ -7,14 +7,17 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.playlists.loadingState == 1">
|
<template v-if="app.playlists.loadingState == 1">
|
||||||
<div class="playlist-display row"
|
<div class="playlist-display"
|
||||||
:style="{
|
:style="{
|
||||||
background: (data.attributes.artwork != null && data.attributes.artwork['bgColor'] != null) ? ('#' + data.attributes.artwork.bgColor) : '',
|
'--bgColor': (data.attributes.artwork != null && data.attributes.artwork['bgColor'] != null) ? ('#' + data.attributes.artwork.bgColor) : '',
|
||||||
color: (data.attributes.artwork != null && data.attributes.artwork['textColor1'] != null) ? ('#' + data.attributes.artwork.textColor1) : ''
|
'--textColor': (data.attributes.artwork != null && data.attributes.artwork['textColor1'] != null) ? ('#' + data.attributes.artwork.textColor1) : ''
|
||||||
}">
|
}">
|
||||||
|
<div class="playlistInfo">
|
||||||
|
<div class="row">
|
||||||
<div class="col-auto flex-center">
|
<div class="col-auto flex-center">
|
||||||
<div style="width: 260px;height:260px;">
|
<div style="width: 260px;height:260px;">
|
||||||
<mediaitem-artwork
|
<mediaitem-artwork
|
||||||
|
shadow="large"
|
||||||
:video-priority="true"
|
:video-priority="true"
|
||||||
:url="(data.attributes != null && data.attributes.artwork != null) ? data.attributes.artwork.url : ((data.relationships != null && data.relationships.tracks.data.length > 0 && data.relationships.tracks.data[0].attributes != null) ? ((data.relationships.tracks.data[0].attributes.artwork != null)? data.relationships.tracks.data[0].attributes.artwork.url : ''):'')"
|
:url="(data.attributes != null && data.attributes.artwork != null) ? data.attributes.artwork.url : ((data.relationships != null && data.relationships.tracks.data.length > 0 && data.relationships.tracks.data[0].attributes != null) ? ((data.relationships.tracks.data[0].attributes.artwork != null)? data.relationships.tracks.data[0].attributes.artwork.url : ''):'')"
|
||||||
:video="(data.attributes != null && data.attributes.editorialVideo != null) ? (data.attributes.editorialVideo.motionDetailSquare ? data.attributes.editorialVideo.motionDetailSquare.video : (data.attributes.editorialVideo.motionSquareVideo1x1 ? data.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' "
|
:video="(data.attributes != null && data.attributes.editorialVideo != null) ? (data.attributes.editorialVideo.motionDetailSquare ? data.attributes.editorialVideo.motionDetailSquare.video : (data.attributes.editorialVideo.motionSquareVideo1x1 ? data.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' "
|
||||||
|
@ -58,7 +61,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="playlist-controls">
|
<div class="playlist-controls" v-observe-visibility="{callback: isHeaderVisible}">
|
||||||
<button class="md-btn" style="min-width: 120px;"
|
<button class="md-btn" style="min-width: 120px;"
|
||||||
@click="app.mk.shuffleMode = 0; play()">
|
@click="app.mk.shuffleMode = 0; play()">
|
||||||
Play
|
Play
|
||||||
|
@ -75,13 +78,47 @@
|
||||||
@click="(!inLibrary) ? addToLibrary(data.attributes.playParams.id.toString()) : removeFromLibrary(data.attributes.playParams.id.toString()) ">
|
@click="(!inLibrary) ? addToLibrary(data.attributes.playParams.id.toString()) : removeFromLibrary(data.attributes.playParams.id.toString()) ">
|
||||||
Confirm?
|
Confirm?
|
||||||
</button>
|
</button>
|
||||||
<button class="playlist-more" @click="menu">
|
<button class="more-btn-round" style="float:right;" @click="menu">
|
||||||
<div style=" margin-top: -1px;
|
<div class="svg-icon"></div>
|
||||||
margin-left: -5px;
|
</button>
|
||||||
width: 36px;
|
|
||||||
height: 36px;">
|
|
||||||
<%- include("../svg/more.svg") %>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="artworkContainer" v-if="data.attributes.artwork != null">
|
||||||
|
<artwork-material :url="data.attributes.artwork.url" size="260" images="1"></artwork-material>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="floating-header" :style="{opacity: (headerVisible ? 0 : 1),'pointer-events': (headerVisible ? 'none' : '')}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{data.attributes ? (data.attributes.name ??
|
||||||
|
(data.attributes.title ?? '') ?? '') : ''}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div>
|
||||||
|
<button class="md-btn" style="min-width: 120px;"
|
||||||
|
@click="app.mk.shuffleMode = 0; play()">
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;"
|
||||||
|
@click="app.mk.shuffleMode = 1;play()">
|
||||||
|
Shuffle
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;" v-if="inLibrary!=null && confirm!=true"
|
||||||
|
@click="confirmButton()">
|
||||||
|
{{ (!inLibrary) ? "Add to Library" : "Remove from Library" }}
|
||||||
|
</button>
|
||||||
|
<button class="md-btn" style="min-width: 120px;" v-if="confirm==true"
|
||||||
|
@click="(!inLibrary) ? addToLibrary(data.attributes.playParams.id.toString()) : removeFromLibrary(data.attributes.playParams.id.toString()) ">
|
||||||
|
Confirm?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<button class="more-btn-round" style="float:right;" @click="menu">
|
||||||
|
<div class="svg-icon"></div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,6 +155,20 @@
|
||||||
style="width: 50%;">
|
style="width: 50%;">
|
||||||
{{data.attributes.copyright}}
|
{{data.attributes.copyright}}
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<template v-if="typeof data.meta != 'undefined'">
|
||||||
|
<div v-for="view in data.meta.views.order" v-if="data.views[view].data.length != 0">
|
||||||
|
<div class="row" >
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ data.views[view].attributes.title }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mediaitem-scroller-horizontal :items="data.views[view].data"></mediaitem-scroller-horizontal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -138,7 +189,8 @@
|
||||||
confirm: false,
|
confirm: false,
|
||||||
app: this.$root,
|
app: this.$root,
|
||||||
itemBadges: [],
|
itemBadges: [],
|
||||||
badgesRequested: false
|
badgesRequested: false,
|
||||||
|
headerVisible: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
@ -153,6 +205,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isHeaderVisible(visible) {
|
||||||
|
this.headerVisible = visible
|
||||||
|
},
|
||||||
getBadges() {
|
getBadges() {
|
||||||
return
|
return
|
||||||
if (this.badgesRequested) {
|
if (this.badgesRequested) {
|
||||||
|
@ -171,8 +226,8 @@
|
||||||
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.v3.music(`/v1/social/${app.mk.storefrontId}/social-profiles/${friend}`).then(data => {
|
||||||
self.itemBadges.push(data)
|
self.itemBadges.push(data.data.data[0])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -205,7 +260,7 @@
|
||||||
"relate": "library"
|
"relate": "library"
|
||||||
};
|
};
|
||||||
const 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);
|
const 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.data.data[0] && res.data.data[0].attributes && res.data.data[0].attributes.inLibrary) ? res.data.data[0].attributes.inLibrary : false
|
||||||
console.log(res)
|
console.log(res)
|
||||||
} else {
|
} else {
|
||||||
this.inLibrary = true
|
this.inLibrary = true
|
||||||
|
@ -229,12 +284,16 @@
|
||||||
const params = {"fields[somgs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"};
|
const 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
|
||||||
const 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);
|
const 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.data.data[0] && res.data.data[0].relationships && res.data.data[0].relationships.library && res.data.data[0].relationships.library.data && res.data.data[0].relationships.library.data.length > 0) {
|
||||||
id = res.relationships.library.data[0].id
|
id = res.data.data[0].relationships.library.data[0].id
|
||||||
}
|
}
|
||||||
let kind = this.data.attributes.playParams.kind ?? this.data.type ?? '';
|
let kind = this.data.attributes.playParams.kind ?? this.data.type ?? '';
|
||||||
const truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
const truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
app.mk.api.library.remove({[truekind]: id})
|
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}})
|
||||||
this.inLibrary = false
|
this.inLibrary = false
|
||||||
this.confirm = false
|
this.confirm = false
|
||||||
},
|
},
|
||||||
|
@ -274,7 +333,19 @@
|
||||||
if (!this.data.attributes.canEdit) {
|
if (!this.data.attributes.canEdit) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await app.mk.api.library.putPlaylistTracklisting(this.data.attributes.playParams.id, this.convert())
|
console.log('sds',this.convert())
|
||||||
|
await app.mk.api.v3.music(
|
||||||
|
`/v1/me/library/playlists/${this.data.attributes.playParams.id}/tracks`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: this.convert()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
async remove() {
|
async remove() {
|
||||||
if (!this.data.attributes.canEdit) {
|
if (!this.data.attributes.canEdit) {
|
||||||
|
|
|
@ -14,13 +14,16 @@
|
||||||
<mediaitem-square v-else :item="item" :type="getKind(item)"></mediaitem-square>
|
<mediaitem-square v-else :item="item" :type="getKind(item)"></mediaitem-square>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<button v-if="triggerEnabled" style="opacity:0;height: 32px;" v-observe-visibility="{callback: visibilityChanged}">Show More</button>
|
<button v-if="triggerEnabled" style="opacity:0;height: 32px;"
|
||||||
|
v-observe-visibility="{callback: visibilityChanged}">Show More
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<transition name="fabfade">
|
<transition name="fabfade">
|
||||||
<button class="top-fab" v-show="showFab" @click="scrollToTop()">
|
<button class="top-fab" v-show="showFab" @click="scrollToTop()">
|
||||||
<%- include("../svg/arrow-up.svg") %>
|
<%- include("../svg/arrow-up.svg") %>
|
||||||
</button>
|
</button>
|
||||||
</transition>
|
</transition>
|
||||||
|
<div class="well" v-show="loading"><div class="spinner"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -47,7 +50,8 @@
|
||||||
canSeeTrigger: false,
|
canSeeTrigger: false,
|
||||||
showFab: false,
|
showFab: false,
|
||||||
commonKind: "song",
|
commonKind: "song",
|
||||||
api: this.$root.mk.api
|
api: this.$root.mk.api,
|
||||||
|
loading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -71,60 +75,35 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getNext() {
|
getNext() {
|
||||||
// if this.data.next is not null, then we can run this.data.next() and concat to this.data.data to get the next page
|
let self = this
|
||||||
switch(this.type) {
|
|
||||||
default:
|
|
||||||
case "artists":
|
|
||||||
if (this.data.next && this.triggerEnabled) {
|
|
||||||
this.triggerEnabled = false;
|
this.triggerEnabled = false;
|
||||||
|
if (typeof this.data.next == "undefined") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
let nextFn = (data => {
|
this.api.v3.music(this.data.next, app.collectionList.requestBody).then((response) => {
|
||||||
console.log(data);
|
console.log(response)
|
||||||
this.data.next = data.next;
|
if (!app.collectionList.response.groups) {
|
||||||
this.data.data = this.data.data.concat(data.data);
|
if (response.data.next) {
|
||||||
|
this.data.data = this.data.data.concat(response.data.data);
|
||||||
|
this.data.next = response.data.next;
|
||||||
this.triggerEnabled = true;
|
this.triggerEnabled = true;
|
||||||
});
|
|
||||||
if(typeof this.data.next == "function") {
|
|
||||||
this.data.next().then(data => nextFn(data));
|
|
||||||
}else{
|
|
||||||
this.api.v3.music(this.data.next).then(data => nextFn(data));
|
|
||||||
}
|
}
|
||||||
|
this.loading = false
|
||||||
}else{
|
}else{
|
||||||
console.log("No next page");
|
if(!response.data.results[app.collectionList.response.groups]) {
|
||||||
this.triggerEnabled = false;
|
this.loading = false
|
||||||
|
return
|
||||||
}
|
}
|
||||||
break;
|
if (response.data.results[app.collectionList.response.groups].next) {
|
||||||
case "search":
|
this.data.data = this.data.data.concat(response.data.results[app.collectionList.response.groups].data);
|
||||||
if (this.data.next && this.triggerEnabled) {
|
this.data.next = response.data.results[app.collectionList.response.groups].next;
|
||||||
this.triggerEnabled = false;
|
|
||||||
this.data.next().then(data => {
|
|
||||||
console.log(data);
|
|
||||||
this.data.next = data[this.data.groups].next;
|
|
||||||
this.data.data = this.data.data.concat(data[this.data.groups].data.data);
|
|
||||||
this.triggerEnabled = true;
|
this.triggerEnabled = true;
|
||||||
});
|
this.loading = false
|
||||||
}else{
|
|
||||||
console.log("No next page");
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "listen_now":
|
|
||||||
case "curator":
|
|
||||||
if (this.data.next && this.triggerEnabled) {
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
app.mk.api.v3.music(this.data.next).then(data => {
|
|
||||||
console.log(data);
|
|
||||||
this.data.next = data.data.next;
|
|
||||||
this.data.data = this.data.data.concat(data.data.data);
|
|
||||||
this.triggerEnabled = true;
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
console.log("No next page");
|
|
||||||
this.triggerEnabled = false;
|
|
||||||
}
|
}
|
||||||
break;
|
})
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
headerVisibility: function (isVisible, entry) {
|
headerVisibility: function (isVisible, entry) {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
|
|
|
@ -132,31 +132,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (playlists.length != 0) {
|
if (playlists.length != 0) {
|
||||||
this.app.mk.api.playlists(playlists).then(playlistsData => {
|
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${playlists.toString()}`).then(playlistsData => {
|
||||||
self.favorites.push(...playlistsData)
|
self.favorites.push(...playlistsData.data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (libraryPlaylists.length != 0) {
|
if (libraryPlaylists.length != 0) {
|
||||||
this.app.mk.api.library.playlists(libraryPlaylists).then(playlistsData => {
|
this.app.mk.api.v3.music(`v1/me/library/playlists/${playlists.toString()}`).then(playlistsData => {
|
||||||
self.favorites.push(...playlistsData)
|
self.favorites.push(...playlistsData.data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getArtistFeed() {
|
async getArtistFeed() {
|
||||||
let artists = this.followedArtists
|
let artists = this.followedArtists
|
||||||
let self = this
|
let self = this
|
||||||
this.app.mk.api.artists(artists, {
|
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${artists.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`).then(artistData => {
|
||||||
"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",
|
artistData.data.data.forEach(item => {
|
||||||
"extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero",
|
|
||||||
"extend[playlists]": "trackCount",
|
|
||||||
"include[songs]": "albums",
|
|
||||||
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount",
|
|
||||||
"limit[artists:top-songs]": 20,
|
|
||||||
"art[url]": "f"
|
|
||||||
}, {
|
|
||||||
includeResponseMeta: !0
|
|
||||||
}).then(artistData => {
|
|
||||||
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])
|
||||||
}
|
}
|
||||||
|
@ -176,35 +166,10 @@
|
||||||
},
|
},
|
||||||
async getListenNowData() {
|
async getListenNowData() {
|
||||||
let self = this
|
let self = this
|
||||||
this.app.mk.api.personalRecommendations("", {
|
this.app.mk.api.v3.music(`/v1/me/recommendations?timezone=${encodeURIComponent(app.formatTimezoneOffset())}&name=listen-now&with=friendsMix,library,social&art[social-profiles:url]=c&art[url]=c,f&omit[resource]=autos&relate[editorial-items]=contents&extend=editorialCard,editorialVideo&extend[albums]=artistUrl&extend[library-albums]=artistUrl,editorialVideo&extend[playlists]=artistNames,editorialArtwork,editorialVideo&extend[library-playlists]=artistNames,editorialArtwork,editorialVideo&extend[social-profiles]=topGenreNames&include[albums]=artists&include[songs]=artists&include[music-videos]=artists&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,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-upsells&platform=web`).then((data) => {
|
||||||
name: "listen-now",
|
console.log(data.data.data[1])
|
||||||
with: "friendsMix,library,social",
|
|
||||||
"art[social-profiles:url]": "c",
|
|
||||||
"art[url]": "c,f",
|
|
||||||
"omit[resource]": "autos",
|
|
||||||
"relate[editorial-items]": "contents",
|
|
||||||
extend: ["editorialCard", "editorialVideo"],
|
|
||||||
"extend[albums]": ["artistUrl"],
|
|
||||||
"extend[library-albums]": ["artistUrl", "editorialVideo"],
|
|
||||||
"extend[playlists]": ["artistNames", "editorialArtwork", "editorialVideo"],
|
|
||||||
"extend[library-playlists]": ["artistNames", "editorialArtwork", "editorialVideo"],
|
|
||||||
"extend[social-profiles]": "topGenreNames",
|
|
||||||
"include[albums]": "artists",
|
|
||||||
"include[songs]": "artists",
|
|
||||||
"include[music-videos]": "artists",
|
|
||||||
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "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",
|
|
||||||
platform: "web"
|
|
||||||
}, {
|
|
||||||
includeResponseMeta: !0,
|
|
||||||
reload: !0
|
|
||||||
}).then((data) => {
|
|
||||||
console.log(data.data[1])
|
|
||||||
try {
|
try {
|
||||||
self.madeForYou = data.data.filter(section => {
|
self.madeForYou = data.data.data.filter(section => {
|
||||||
if (section.meta.metrics.moduleType == "6") {
|
if (section.meta.metrics.moduleType == "6") {
|
||||||
return section
|
return section
|
||||||
};
|
};
|
||||||
|
@ -213,8 +178,8 @@
|
||||||
self.sectionsReady.push("madeForYou")
|
self.sectionsReady.push("madeForYou")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
self.recentlyPlayed = data.data[1].relationships.contents.data
|
self.recentlyPlayed = data.data.data[1].relationships.contents.data
|
||||||
self.friendsListeningTo = data.data.filter(section => {
|
self.friendsListeningTo = data.data.data.filter(section => {
|
||||||
if (section.meta.metrics.moduleType == "11") {
|
if (section.meta.metrics.moduleType == "11") {
|
||||||
return section
|
return section
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<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 v-if="search != null && search != [] && search.term != ''">
|
||||||
<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()">
|
||||||
<template>
|
<template>
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="search.results['meta']">
|
<template v-if="search.results['meta'] != null">
|
||||||
<template
|
<template
|
||||||
v-for="section in search.results.meta.results.order" v-if="section != 'song' && section != 'top'">
|
v-for="section in search.results.meta.results.order" v-if="section != 'song' && section != 'top'">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -58,30 +59,44 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>Shared Playlists</h3>
|
<h3>Shared Playlists</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto flex-center" v-if="search.resultsSocial.playlist.data.data.length >= 10">
|
<div class="col-auto flex-center" v-if="search.resultsSocial.playlist.data.length >= 10">
|
||||||
<button class="cd-btn-seeall"
|
<button class="cd-btn-seeall"
|
||||||
@click="app.showCollection(search.resultsSocial.playlist.data, 'Shared Playlists', 'default')">See All
|
@click="app.showCollection(search.resultsSocial.playlist, 'Shared Playlists', 'default')">See All
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mediaitem-scroller-horizontal-large
|
<mediaitem-scroller-horizontal-large
|
||||||
:items="search.resultsSocial.playlist.data.data.limit(10)"></mediaitem-scroller-horizontal-large>
|
:items="search.resultsSocial.playlist.data.limit(10)"></mediaitem-scroller-horizontal-large>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="search.resultsSocial.profile">
|
<template v-if="search.resultsSocial.profile">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>People</h3>
|
<h3>People</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto flex-center" v-if="search.resultsSocial.profile.data.data.length >= 10">
|
<div class="col-auto flex-center" v-if="search.resultsSocial.profile.data.length >= 10">
|
||||||
<button class="cd-btn-seeall"
|
<button class="cd-btn-seeall"
|
||||||
@click="app.showCollection(search.resultsSocial.profile.data, 'People', 'default')">See All
|
@click="app.showCollection(search.resultsSocial.profile, 'People', 'default')">See All
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mediaitem-scroller-horizontal-large
|
<mediaitem-scroller-horizontal-large
|
||||||
:items="search.resultsSocial.profile.data.data.limit(10)"></mediaitem-scroller-horizontal-large>
|
:items="search.resultsSocial.profile.data.limit(10)"></mediaitem-scroller-horizontal-large>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="categoriesReady || getCategories()">
|
||||||
|
<div>
|
||||||
|
<div class="col" v-if="categoriesView != null && categoriesView != [] && categoriesView[0].attributes != null && categoriesView[0].attributes.title != null">
|
||||||
|
<h3>{{categoriesView[0].attributes.title.stringForDisplay ?? ""}}</h3>
|
||||||
|
</div>
|
||||||
|
<mediaitem-square :kind="'385'" size="600"
|
||||||
|
:item="item ? (item.attributes.kind ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"
|
||||||
|
:imagesize="800"
|
||||||
|
v-for="item in categoriesView[1].relationships.contents.data.filter(item => item.type != 'editorial-items')">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -90,7 +105,9 @@
|
||||||
props: ['search'],
|
props: ['search'],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
app: this.$root
|
app: this.$root,
|
||||||
|
categoriesView : [],
|
||||||
|
categoriesReady : false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -100,6 +117,13 @@
|
||||||
} catch( error ) {
|
} catch( error ) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async getCategories() {
|
||||||
|
if(this.categoriesView != [] && this.categoriesView.length > 0) {this.categoriesReady = true; return await true;} else {
|
||||||
|
let response = await this.app.mk.api.v3.music(`/v1/recommendations/${this.app.mk.storefrontId}?timezone=${encodeURIComponent(this.app.formatTimezoneOffset())}&name=search-landing&platform=web&extend=editorialArtwork&art%5Burl%5D=f%2Cc&types=editorial-items%2Capple-curators%2Cactivities`);
|
||||||
|
this.categoriesView = response.data.data;
|
||||||
|
this.categoriesReady = true;
|
||||||
|
return await true;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,5 +5,8 @@
|
||||||
{{ $store.state.test }}
|
{{ $store.state.test }}
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<button class="md-btn">Cider Button</button>
|
<button class="md-btn">Cider Button</button>
|
||||||
|
<div style="position: relative;width: 300px;height: 300px;">
|
||||||
|
<artwork-material url="https://is3-ssl.mzstatic.com/image/thumb/Music126/v4/13/41/13/1341133b-560f-1aee-461f-c4b32ec049b4/cover.jpg/{w}x{h}bb.jpg"></artwork-material>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
22
src/web-remote/assets/Grabber.svg
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 28 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,-11,-10)">
|
||||||
|
<path d="M39,12.24C39,11.004 37.996,10 36.76,10L13.24,10C12.004,10 11,11.004 11,12.24L11,71.76C11,72.996 12.004,74 13.24,74L36.76,74C37.996,74 39,72.996 39,71.76L39,12.24Z" style="fill:rgb(108,108,108);fill-opacity:0.43;"/>
|
||||||
|
<g transform="matrix(0.714286,0,0,1,7.14286,0)">
|
||||||
|
<rect x="18" y="41" width="14" height="2" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.714286,0,0,1,7.14286,-5)">
|
||||||
|
<rect x="18" y="41" width="14" height="2" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.714286,0,0,1,7.14286,5)">
|
||||||
|
<rect x="18" y="41" width="14" height="2" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.571429,0,0,0.6,10.7143,10.4)">
|
||||||
|
<path d="M25,26L32,36L18,36L25,26Z" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.571429,0,0,-0.6,10.7143,73.6)">
|
||||||
|
<path d="M25,26L32,36L18,36L25,26Z" style="fill:rgb(231,231,231);fill-opacity:0.77;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
src/web-remote/assets/arrow-left.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L7 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 114.8c9.8 9.3 10 24.8.4 34.3z"/></svg>
|
After Width: | Height: | Size: 521 B |
1
src/web-remote/assets/backward.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M11.5 280.6l192 160c20.6 17.2 52.5 2.8 52.5-24.6V96c0-27.4-31.9-41.8-52.5-24.6l-192 160c-15.3 12.8-15.3 36.4 0 49.2zm256 0l192 160c20.6 17.2 52.5 2.8 52.5-24.6V96c0-27.4-31.9-41.8-52.5-24.6l-192 160c-15.3 12.8-15.3 36.4 0 49.2z"/></svg>
|
After Width: | Height: | Size: 500 B |
1
src/web-remote/assets/forward.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M500.5 231.4l-192-160C287.9 54.3 256 68.6 256 96v320c0 27.4 31.9 41.8 52.5 24.6l192-160c15.3-12.8 15.3-36.4 0-49.2zm-256 0l-192-160C31.9 54.3 0 68.6 0 96v320c0 27.4 31.9 41.8 52.5 24.6l192-160c15.3-12.8 15.3-36.4 0-49.2z"/></svg>
|
After Width: | Height: | Size: 493 B |
1
src/web-remote/assets/infinity.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" fill="white"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M471.1 96C405 96 353.3 137.3 320 174.6 286.7 137.3 235 96 168.9 96 75.8 96 0 167.8 0 256s75.8 160 168.9 160c66.1 0 117.8-41.3 151.1-78.6 33.3 37.3 85 78.6 151.1 78.6 93.1 0 168.9-71.8 168.9-160S564.2 96 471.1 96zM168.9 320c-40.2 0-72.9-28.7-72.9-64s32.7-64 72.9-64c38.2 0 73.4 36.1 94 64-20.4 27.6-55.9 64-94 64zm302.2 0c-38.2 0-73.4-36.1-94-64 20.4-27.6 55.9-64 94-64 40.2 0 72.9 28.7 72.9 64s-32.7 64-72.9 64z"/></svg>
|
After Width: | Height: | Size: 684 B |
1
src/web-remote/assets/list.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"/></svg>
|
After Width: | Height: | Size: 831 B |
1
src/web-remote/assets/pause.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z"/></svg>
|
After Width: | Height: | Size: 487 B |
1
src/web-remote/assets/play.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"/></svg>
|
After Width: | Height: | Size: 384 B |
1
src/web-remote/assets/quote-right.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M464 32H336c-26.5 0-48 21.5-48 48v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48zm-288 0H48C21.5 32 0 53.5 0 80v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48z"/></svg>
|
After Width: | Height: | Size: 640 B |
47
src/web-remote/assets/repeat.svg
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 60 60" style="enable-background:new 0 0 60 60;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path fill="white" d="M42,12H20.414l7.293-7.293c0.391-0.391,0.391-1.023,0-1.414s-1.023-0.391-1.414,0l-8.999,8.999
|
||||||
|
c-0.093,0.092-0.166,0.203-0.217,0.326c-0.101,0.244-0.101,0.52,0,0.764c0.051,0.123,0.124,0.234,0.217,0.326l8.999,8.999
|
||||||
|
C26.488,22.902,26.744,23,27,23s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414L20.414,14H42c8.822,0,16,7.178,16,16
|
||||||
|
c0,4.252-1.668,8.264-4.696,11.295c-0.391,0.391-0.391,1.024,0,1.414c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293
|
||||||
|
C58.124,39.3,60,34.786,60,30C60,20.075,51.925,12,42,12z"/>
|
||||||
|
<path fill="white" d="M35.707,37.293c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414L41.586,46H18C9.178,46,2,38.822,2,30
|
||||||
|
c0-3.783,1.359-7.46,3.828-10.354c0.358-0.421,0.309-1.052-0.111-1.41c-0.419-0.359-1.052-0.31-1.41,0.111
|
||||||
|
C1.529,21.604,0,25.741,0,30c0,9.925,8.075,18,18,18h23.586l-7.293,7.293c-0.391,0.391-0.391,1.023,0,1.414
|
||||||
|
C34.488,56.902,34.744,57,35,57s0.512-0.098,0.707-0.293l9-9c0.391-0.391,0.391-1.023,0-1.414L35.707,37.293z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
src/web-remote/assets/search.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"/></svg>
|
After Width: | Height: | Size: 618 B |
45
src/web-remote/assets/shuffle.svg
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 230.055 230.055" style="enable-background:new 0 0 230.055 230.055;" xml:space="preserve">
|
||||||
|
<path fill="white" d="M199.419,124.497c-3.516-3.515-9.213-3.515-12.729,0c-3.515,3.515-3.515,9.213,0,12.728l12.637,12.636h-8.406
|
||||||
|
c-8.177,0-16.151-2.871-22.453-8.083l-32.346-26.751l32.345-26.751c6.303-5.212,14.277-8.083,22.454-8.083h8.406L186.69,92.83
|
||||||
|
c-3.515,3.515-3.515,9.213,0,12.728c1.758,1.757,4.061,2.636,6.364,2.636s4.606-0.879,6.364-2.636l28-28
|
||||||
|
c3.515-3.515,3.515-9.213,0-12.728l-28-28c-3.516-3.515-9.213-3.515-12.729,0c-3.515,3.515-3.515,9.213,0,12.728l12.637,12.636
|
||||||
|
h-8.406c-12.354,0-24.403,4.337-33.926,12.211L122,103.348L82.564,70.733c-6.658-5.507-15.084-8.54-23.724-8.54H9
|
||||||
|
c-4.971,0-9,4.029-9,9s4.029,9,9,9h49.841c4.462,0,8.813,1.566,12.252,4.411l36.786,30.423L71.094,145.45
|
||||||
|
c-3.439,2.844-7.791,4.411-12.253,4.411H9c-4.971,0-9,4.029-9,9s4.029,9,9,9h49.841c8.64,0,17.065-3.033,23.725-8.54L122,126.707
|
||||||
|
l34.996,28.943c9.521,7.875,21.57,12.211,33.925,12.211h8.406l-12.637,12.636c-3.515,3.515-3.515,9.213,0,12.728
|
||||||
|
c1.758,1.757,4.061,2.636,6.364,2.636s4.606-0.879,6.364-2.636l28-28c3.515-3.515,3.515-9.213,0-12.728L199.419,124.497z"/>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
src/web-remote/assets/volume-down.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M215.03 72.04L126.06 161H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V89.02c0-21.47-25.96-31.98-40.97-16.98zm123.2 108.08c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 229.28 336 242.62 336 257c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.87z"/></svg>
|
After Width: | Height: | Size: 710 B |
1
src/web-remote/assets/volume-up.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256s-23.73 90.03-63.48 115.42c-11.19 7.14-14.5 22.07-7.41 33.36 6.51 10.36 21.12 15.14 33.12 7.46C447.94 377.94 480 319.54 480 256zm-141.77-76.87c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 228.28 336 241.63 336 256c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1044
src/web-remote/ciderframework.css
Normal file
BIN
src/web-remote/icon-192x192.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/web-remote/icon-256x256.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/web-remote/icon-384x384.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
src/web-remote/icon-512x512.png
Normal file
After Width: | Height: | Size: 38 KiB |
|
@ -1 +1,784 @@
|
||||||
Web Remote
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<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">
|
||||||
|
<title>Web Remote</title>
|
||||||
|
<link rel="stylesheet" href="style.css?v=2">
|
||||||
|
<script src="vue.js"></script>
|
||||||
|
<script src="sortable.min.js"></script>
|
||||||
|
<script src="vuedraggable.umd.min.js"></script>
|
||||||
|
<link rel="manifest" href="./manifest.json?v=2">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body oncontextmenu="return false;">
|
||||||
|
<div id="app" :style="{'--artwork': getAlbumArtUrl()}">
|
||||||
|
<!-- App view when connected -->
|
||||||
|
<template v-if="connectedState == 1">
|
||||||
|
<!-- Streamer Overlay -->
|
||||||
|
<template></template>
|
||||||
|
<!-- Mini Player -->
|
||||||
|
<template v-if="screen == 'miniplayer'">
|
||||||
|
<div class="miniplayer-main">
|
||||||
|
<div class="media-artwork--miniplayer" :class="artworkPlaying()"
|
||||||
|
:style="{'--artwork': getAlbumArtUrl()}">
|
||||||
|
</div>
|
||||||
|
<div class="miniplayer-draggable">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="miniplayer-controls">
|
||||||
|
<button class="md-btn playback-button--small repeat" @click="repeat()"
|
||||||
|
v-if="player.currentMediaItem.repeatMode == 0"></button>
|
||||||
|
<button class="md-btn playback-button--small repeat active" @click="repeat()"
|
||||||
|
v-else-if="player.currentMediaItem.repeatMode == 2"></button>
|
||||||
|
<button class="md-btn playback-button--small repeat repeatOne" @click="repeat()"
|
||||||
|
v-else-if="player.currentMediaItem.repeatMode == 1"></button>
|
||||||
|
<button class="md-btn playback-button previous" @click="previous()"></button>
|
||||||
|
<button class="md-btn playback-button pause" @click="pause()"
|
||||||
|
v-if="player.currentMediaItem.status"></button>
|
||||||
|
<button class="md-btn playback-button play" @click="play()" v-else></button>
|
||||||
|
<button class="md-btn playback-button next" @click="next()"></button>
|
||||||
|
<button class="md-btn playback-button--small shuffle" @click="shuffle()"
|
||||||
|
v-if="player.currentMediaItem.shuffleMode == 0"></button>
|
||||||
|
<button class="md-btn playback-button--small shuffle active" @click="shuffle()"
|
||||||
|
v-else></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- Player -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel player-panel" v-if="screen == 'player'">
|
||||||
|
<div class="player_top">
|
||||||
|
<div class="md-body player-artwork-container">
|
||||||
|
<div class="media-artwork" :class="artworkPlaying()" :style="{'--artwork': getAlbumArtUrl()}">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="player_bottom" v-if="player.lowerPanelState == 'lyrics'">
|
||||||
|
<div class="md-header" style="width:100%;">
|
||||||
|
<div class="list-entry" v-if="false">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" :style="{'--artwork': getAlbumArtUrl()}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ player.currentMediaItem.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ player.currentMediaItem.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-body lyric-body" style="width:100%;">
|
||||||
|
<template v-if="player.lyrics">
|
||||||
|
<template v-for="lyric in player.lyrics" v-if="lyric.line != 'lrcInstrumental'">
|
||||||
|
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
||||||
|
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
||||||
|
{{ lyric.line }}
|
||||||
|
</h3>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
||||||
|
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
||||||
|
<div class="lyricWaiting">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
No Lyrics Available
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<button class="md-btn playback-button--small lyrics active" @click="player.lowerPanelState = 'controls'"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="player_bottom" v-if="player.lowerPanelState == 'controls'">
|
||||||
|
<div class="md-footer">
|
||||||
|
<div class="row player-track-info">
|
||||||
|
<div class="col nopadding text-overflow-elipsis">
|
||||||
|
<div class="player-song-title text-overflow-elipsis">
|
||||||
|
{{ player.currentMediaItem.name }}
|
||||||
|
</div>
|
||||||
|
<div class="player-song-artist text-overflow-elipsis" @click="searchArtist()">
|
||||||
|
{{ player.currentMediaItem.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto nopadding player-more-container" v-if="false" style="">
|
||||||
|
<button @click="player.songActions = true;" class="player-more-button">...</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<input type="range" min="0" :value="player.currentMediaItem.durationInMillis - player.currentMediaItem.remainingTime" :max="player.currentMediaItem.durationInMillis" class="web-slider playback-slider" @input="seekTo($event.target.value)">
|
||||||
|
<div class="row nopadding player-duration-container" style="width: 90%;margin: 0 auto;">
|
||||||
|
<div class="col nopadding player-duration-time" style="text-align:left">
|
||||||
|
{{ parseTime(player.currentMediaItem.durationInMillis - player.currentMediaItem.remainingTime) }}
|
||||||
|
</div>
|
||||||
|
<div class="col nopadding player-duration-time" style="text-align:right">
|
||||||
|
-{{ parseTime(player.currentMediaItem.remainingTime) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer playback-buttons">
|
||||||
|
<button class="md-btn playback-button--small repeat" @click="repeat()" v-if="player.currentMediaItem.repeatMode == 0"></button>
|
||||||
|
<button class="md-btn playback-button--small repeat active" @click="repeat()" v-else-if="player.currentMediaItem.repeatMode == 2"></button>
|
||||||
|
<button class="md-btn playback-button--small repeat repeatOne" @click="repeat()" v-else-if="player.currentMediaItem.repeatMode == 1"></button>
|
||||||
|
<button class="md-btn playback-button previous" @click="previous()"></button>
|
||||||
|
<button class="md-btn playback-button pause" @click="pause()" v-if="player.currentMediaItem.status"></button>
|
||||||
|
<button class="md-btn playback-button play" @click="play()" v-else></button>
|
||||||
|
<button class="md-btn playback-button next" @click="next()"></button>
|
||||||
|
<button class="md-btn playback-button--small shuffle" @click="shuffle()" v-if="player.currentMediaItem.shuffleMode == 0"></button>
|
||||||
|
<button class="md-btn playback-button--small shuffle active" @click="shuffle()" v-else></button>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<div class="row volume-slider-container">
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="player-volume-glyph decrease"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<input type="range" class="web-slider volume-slider" max="1" min="0" step="0.01" @input="setVolume($event.target.value)" :value="player.currentMediaItem.volume">
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="player-volume-glyph increase"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'" @click="showLyrics()"></button>
|
||||||
|
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'landscape'" @click="showLyricsInline()"></button>
|
||||||
|
<button class="md-btn playback-button--small queue" @click="showQueue()"></button>
|
||||||
|
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Search -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel search-panel" v-if="screen == 'search'">
|
||||||
|
<div class="search-header">
|
||||||
|
<div class="md-header">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="back-button" @click="screen = 'player'"></button>
|
||||||
|
</div>
|
||||||
|
<div class="col" style="display: flex;align-items: center;">
|
||||||
|
<div class="col">
|
||||||
|
<input type="text" placeholder="Artists, Songs, Lyrics, and More" spellcheck="false" v-model="search.query" @change="searchQuery()" v-on:keyup.enter="searchQuery()" class="search-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-header search-type-container">
|
||||||
|
<button class="search-type-button" @click="search.searchType = 'applemusic';searchQuery()" :class="searchTypeClass('applemusic')" style="width:100%;">Apple Music
|
||||||
|
</button>
|
||||||
|
<button class="search-type-button" @click="search.searchType = 'library';searchQuery()" :class="searchTypeClass('library')" style="width:100%;">Library
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="md-header search-tab-container" v-if="search.state == 2">
|
||||||
|
<button class="search-tab" @click="search.tab = 'all'" :class="searchTabClass('all')">All
|
||||||
|
Results
|
||||||
|
</button>
|
||||||
|
<button class="search-tab" @click="search.tab = 'songs'" :class="searchTabClass('songs')">Songs
|
||||||
|
</button>
|
||||||
|
<button class="search-tab" @click="search.tab = 'albums'" :class="searchTabClass('albums')">Albums
|
||||||
|
</button>
|
||||||
|
<button class="search-tab" @click="search.tab = 'artists'" :class="searchTabClass('artists')">Artists
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search-body-container">
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-body search-body" v-if="search.state == 0">
|
||||||
|
<div style="font-size: 17px;display:flex;flex-direction: column;justify-content: center;align-items: center;">
|
||||||
|
<img src="./assets/search.svg" style="width: 40px;margin: 32px;opacity: 0.85"> Search by song, album, artist, or lyrics.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-body search-body" v-if="search.state == 1">
|
||||||
|
<!-- loading state -->
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-body search-body" ref="searchBody" @scroll="searchScroll" style="overflow-y:auto;" v-if="search.state == 2">
|
||||||
|
<template v-if="canShowSearchTab('songs')">
|
||||||
|
<div class="list-entry-header">Songs</div>
|
||||||
|
|
||||||
|
<div class="list-entry" v-for="song in search.results.songs"
|
||||||
|
@click="trackSelect(song)">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" v-if="song.artwork"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ song.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ song.artistName }}
|
||||||
|
<span class="lossless-badge" v-if="song.audioTraits.includes('lossless')">Lossless</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-entry" v-for="song in search.results['library-songs']"
|
||||||
|
@click="trackSelect(song)">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" v-if="song.artwork"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ song.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ song.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<template v-if="canShowSearchTab('albums')">
|
||||||
|
<div class="list-entry-header">Albums</div>
|
||||||
|
|
||||||
|
<div class="list-entry" v-for="album in search.results.albums"
|
||||||
|
@click="showAlbum(album.id)">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" v-if="album.artwork"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(album.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ album.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ album.artistName }}
|
||||||
|
<span class="lossless-badge" v-if="album.audioTraits.includes('lossless')">Lossless</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-entry" v-for="album in search.results['library-albums']">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" v-if="album.artwork"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(album.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ album.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ album.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<template v-if="canShowSearchTab('artists')">
|
||||||
|
<div class="list-entry-header">Artists</div>
|
||||||
|
|
||||||
|
<div class="list-entry"
|
||||||
|
@click="showArtist(artist.id)"
|
||||||
|
v-for="artist in search.results.artists"
|
||||||
|
>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image artist" v-if="artist.artwork"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(artist.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ artist.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-entry" v-for="artist in search.results['library-artists']">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image artist" v-if="artist.artwork"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(artist.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ artist.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<footer-player></footer-player>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Track Select Actions -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel context-menu" style="overflow-y:auto;" v-if="search.trackSelect">
|
||||||
|
<div class="md-body context-menu-body">
|
||||||
|
<button class="context-menu-item context-menu-item--left" @click="playMediaItemById(search.selected.id);clearSelectedTrack()">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center" v-if="search.selected.artwork"
|
||||||
|
style="display:flex;align-items: center;">
|
||||||
|
<div class="list-entry-image"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(search.selected.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center" style="display:flex;align-items: center;">
|
||||||
|
<div style="width:100%;font-size: 18px;">
|
||||||
|
{{ search.selected.name }}
|
||||||
|
</div>
|
||||||
|
<div style="width:100%;font-size: 16px;">
|
||||||
|
{{ search.selected.artistName }}
|
||||||
|
</div>
|
||||||
|
<div style="width:100%;font-size: 14px;">
|
||||||
|
{{ parseTime(search.selected.durationInMillis) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="md-body context-menu-body" style="height: auto;">
|
||||||
|
<button class="context-menu-item context-menu-item--left" @click="playMediaItemById(search.selected.id);clearSelectedTrack()">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Play
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
▶️
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="context-menu-item context-menu-item--left" @click="playNext('song', search.selected.id);clearSelectedTrack()">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Play Next
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
⏭️
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="context-menu-item context-menu-item--left" @click="playLater('song', search.selected.id);clearSelectedTrack()">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Play Later
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
⌛
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="context-menu-item context-menu-item--left" v-if="false">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Open in {{ musicAppVariant() }}
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
🎵
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<button class="context-menu-item" @click="clearSelectedTrack()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Song Actions -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel context-menu" v-if="player.songActions">
|
||||||
|
<div class="md-header">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="md-body context-menu-body">
|
||||||
|
<button class="context-menu-item context-menu-item--left" v-if="false">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Add To Library
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
➕
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="context-menu-item context-menu-item--left" v-if="false">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Love
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
❤️
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="context-menu-item context-menu-item--left">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Share
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
🌐
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button class="context-menu-item context-menu-item--left">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
Open in {{ musicAppVariant() }}
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
🎵
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<button class="context-menu-item" @click="player.songActions = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Artist Page -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel" v-if="screen == 'artist-page'" v-if="artistPage.data['name']">
|
||||||
|
<div class="md-header">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="back-button" @click="showSearch(true)"></button>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
{{ artistPage.data["name"] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="album-body-container" :style="getMediaPalette(artistPage.data)">
|
||||||
|
<div class="artist-header" v-if="artistPage.data['artwork']" :style="getMediaPalette(artistPage.data)">
|
||||||
|
<div class="artist-header-portrait" :style="{'--artwork': getAlbumArtUrlList(artistPage.data['artwork']['url'], 600)}"></div>
|
||||||
|
<h2>{{ artistPage.data["name"] }}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="md-body artist-body">
|
||||||
|
<h2>Songs</h2>
|
||||||
|
<div class="song-scroller-horizontal">
|
||||||
|
<button v-for="song in artistPage.data['songs']" class="song-placeholder" @click="trackSelect(song)">
|
||||||
|
{{ song.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h2>Albums</h2>
|
||||||
|
<div class="mediaitem-scroller-horizontal">
|
||||||
|
<button v-for="album in artistPage.data['albums']" class="album-placeholder" @click="showAlbum(album.id)">
|
||||||
|
{{ album.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h2>Playlists</h2>
|
||||||
|
<div class="mediaitem-scroller-horizontal">
|
||||||
|
<button v-for="playlist in artistPage.data['playlists']" class="album-placeholder">
|
||||||
|
{{ playlist.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer-player></footer-player>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Queue -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel" v-if="screen == 'queue'">
|
||||||
|
<div class="md-header">
|
||||||
|
<div class="list-entry" @click="screen = 'player'">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" :style="{'--artwork': getAlbumArtUrl()}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ player.currentMediaItem.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ player.currentMediaItem.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-header" style="text-align: right;padding: 5px 16px;">
|
||||||
|
<button class="md-btn playback-button--small autoplay" v-if="!player.currentMediaItem.autoplayEnabled" @click="setAutoplay(true)"></button>
|
||||||
|
<button class="md-btn playback-button--small autoplay activeColor" v-else @click="setAutoplay(false)"></button>
|
||||||
|
</div>
|
||||||
|
<div class="md-body queue-body" v-if="!player.queue['_queueItems']">
|
||||||
|
Empty
|
||||||
|
</div>
|
||||||
|
<div class="md-body queue-body" style="overflow-y:auto;" id="list-queue" v-else>
|
||||||
|
<draggable v-model="queue.temp" handle=".handle" filter=".passed" @change="queueMove">
|
||||||
|
<template v-for="(song, position) in queue.temp" v-if="position > player.queue['_position']">
|
||||||
|
<div class="list-entry" :class="getQueuePositionClass(position)">
|
||||||
|
<div class="row" style="width:100%;">
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="handle">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" v-if="song.item.attributes.artwork"
|
||||||
|
:style="{'--artwork': getAlbumArtUrlList(song.item.attributes.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ song.item.attributes.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ song.item.attributes.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex-center" style="text-align:right;">
|
||||||
|
<div v-if="position == player.queue['_position']">▶️</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'portrait'" @click="showLyrics()"></button>
|
||||||
|
<button class="md-btn playback-button--small lyrics" v-if="checkOrientation() == 'landscape'" @click="screen = 'player';showLyricsInline()"></button>
|
||||||
|
<button class="md-btn playback-button--small queue active" @click="screen = 'player'"></button>
|
||||||
|
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Lyrics -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel" v-if="screen == 'lyrics'">
|
||||||
|
<div class="md-header">
|
||||||
|
<div class="list-entry" @click="screen = 'player'">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" :style="{'--artwork': getAlbumArtUrl()}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ player.currentMediaItem.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ player.currentMediaItem.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-body lyric-body">
|
||||||
|
<template v-if="player.lyrics">
|
||||||
|
<template v-for="lyric in player.lyrics" v-if="lyric.line != 'lrcInstrumental'">
|
||||||
|
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
||||||
|
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
||||||
|
{{ lyric.line }}
|
||||||
|
</h3>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<h3 class="lyric-line" @click="seekTo(lyric.startTime, false)"
|
||||||
|
:class="getLyricClass(lyric.startTime, lyric.endTime)">
|
||||||
|
<div class="lyricWaiting">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
No Lyrics Available
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<button class="md-btn playback-button--small lyrics active" @click="screen = 'player'"></button>
|
||||||
|
<button class="md-btn playback-button--small queue" @click="showQueue()"></button>
|
||||||
|
<button class="md-btn playback-button--small search" @click="showSearch()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Album Page -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel md-container_album" v-if="screen == 'album-page' && albumPage.data['name']">
|
||||||
|
<div class="md-header">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="back-button" @click="showSearch(true)"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="album-body-container">
|
||||||
|
<div class="md-header">
|
||||||
|
<div class="albumpage-artwork" :style="{'--artwork': getAlbumArtUrlList(albumPage.data['artwork']['url'], 300)}">
|
||||||
|
</div>
|
||||||
|
<div class="albumpage-album-name">
|
||||||
|
{{ albumPage.data["name"] }}
|
||||||
|
</div>
|
||||||
|
<div class="albumpage-artist-name" @click="showArtist(albumPage.data['artists'][0]['id'])">
|
||||||
|
{{ albumPage.data["artistName"] }}
|
||||||
|
</div>
|
||||||
|
<div class="albumpage-misc-info">
|
||||||
|
{{ albumPage.data.genreNames[0] }} ∙ {{ new Date(albumPage.data.releaseDate).getFullYear() }}
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top: 20px;">
|
||||||
|
<div class="col">
|
||||||
|
<button class="wr-btn" @click="playAlbum(albumPage.data.id, false)" style="width:100%;">Play
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<button class="wr-btn" style="width:100%;" @click="playAlbum(albumPage.data.id, true)">Shuffle
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="albumpage-album-notes" v-if="albumPage.data['editorialNotes']">
|
||||||
|
<div class="notes-preview" v-html="albumPage.data['editorialNotes']['standard']">
|
||||||
|
</div>
|
||||||
|
<button @click="albumPage.editorsNotes = true" class="notes-more">More</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="md-body artist-body">
|
||||||
|
<div class="list-entry-header">Tracks</div>
|
||||||
|
<div class="list-entry" v-for="song in albumPage.data['tracks']" @click="trackSelect(song)">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<div class="list-entry-image" v-if="song.artwork" :style="{'--artwork': getAlbumArtUrlList(song.artwork.url)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<div class="list-entry-name">
|
||||||
|
{{ song.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist">
|
||||||
|
{{ song.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<div>{{ albumPage.data['tracks'].length }} Tracks</div>
|
||||||
|
<div>
|
||||||
|
{{ albumPage.data['copyright'] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer-player></footer-player>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Album Page - Editorial Notes -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel context-menu" v-if="albumPage.editorsNotes" style="padding-top: 42px;">
|
||||||
|
<div class="md-header" :style="getMediaPalette(albumPage.data)" style="font-size: 18px;background:var(--bgColor);color:var(--textColor1);text-align: center;border-radius: 10px 10px 0 0;border-top: 1px solid #ffffff1f;">
|
||||||
|
{{ albumPage.data["name"] }}
|
||||||
|
</div>
|
||||||
|
<div class="md-body album-page-fullnotes-body" :style="getMediaPalette(albumPage.data)" style="background:var(--bgColor);color:var(--textColor1);" v-html="albumPage.data['editorialNotes']['standard']">
|
||||||
|
</div>
|
||||||
|
<div class="md-footer" :style="getMediaPalette(albumPage.data)" style="background:var(--bgColor);color:var(--textColor1);">
|
||||||
|
<button class="context-menu-item" @click="albumPage.editorsNotes = false">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
<!-- Loading -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel connection-error-panel" v-if="connectedState != 1">
|
||||||
|
<div class="md-header">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="md-body" style="display:flex;justify-content: center;align-items: center;">
|
||||||
|
<div v-if="connectedState == 0">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<h3 style="text-align:center;">Connection Interrupted</h3>
|
||||||
|
<!--<button class="md-btn md-btn-primary" style="font-weight:500;width: 120px;border-radius: 50px;display:block;margin: 0 auto;" @click="connect()">Retry-->
|
||||||
|
<button class="md-btn md-btn-primary" style="font-weight:500;width: 120px;border-radius: 50px;display:block;margin: 0 auto;" onclick="document.location = document.location">Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<!-- Template -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<div class="md-container md-container_panel" v-if="false">
|
||||||
|
<div class="md-header">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="md-body">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/x-template" id="footer-player">
|
||||||
|
<div class="footer-player" v-show="$parent.player.currentMediaItem['name']">
|
||||||
|
<div class="row" style="width:100%;margin:0px;">
|
||||||
|
<div class="col-auto flex-center" style="padding:0 6px;" @click="$parent.screen = 'player'">
|
||||||
|
<div class="list-entry-image" :style="{'--artwork': $parent.getAlbumArtUrl()}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center text-overflow-elipsis" @click="$parent.screen = 'player'">
|
||||||
|
<div class="list-entry-name text-overflow-elipsis">
|
||||||
|
{{ $parent.player.currentMediaItem.name }}
|
||||||
|
</div>
|
||||||
|
<div class="list-entry-artist text-overflow-elipsis">
|
||||||
|
{{ $parent.player.currentMediaItem.artistName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="md-btn playback-button pause" @click="$parent.pause()" v-if="$parent.player.currentMediaItem.status"></button>
|
||||||
|
<button class="md-btn playback-button play" @click="$parent.play()" v-else></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="./index.js?v=1"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
618
src/web-remote/index.js
Normal file
|
@ -0,0 +1,618 @@
|
||||||
|
var socket;
|
||||||
|
|
||||||
|
Vue.component('footer-player', {
|
||||||
|
template: '#footer-player'
|
||||||
|
});
|
||||||
|
|
||||||
|
// vue instance
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
screen: "player",
|
||||||
|
player: {
|
||||||
|
currentMediaItem: {},
|
||||||
|
songActions: false,
|
||||||
|
lyrics: {},
|
||||||
|
lyricsMediaItem: {},
|
||||||
|
lyricsDebug: {
|
||||||
|
current: 0,
|
||||||
|
start: 0,
|
||||||
|
end: 0
|
||||||
|
},
|
||||||
|
queue: {},
|
||||||
|
lowerPanelState: "controls",
|
||||||
|
userInteraction: false
|
||||||
|
},
|
||||||
|
queue: {
|
||||||
|
temp: []
|
||||||
|
},
|
||||||
|
artistPage: {
|
||||||
|
data: {},
|
||||||
|
editorsNotes: false
|
||||||
|
},
|
||||||
|
albumPage: {
|
||||||
|
data: {},
|
||||||
|
editorsNotes: false
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
query: "",
|
||||||
|
results: [],
|
||||||
|
state: 0,
|
||||||
|
tab: "all",
|
||||||
|
searchType: "applemusic",
|
||||||
|
trackSelect: false,
|
||||||
|
selected: {},
|
||||||
|
queue: {},
|
||||||
|
lastPage: "search",
|
||||||
|
lastY: 0
|
||||||
|
},
|
||||||
|
lastPage: "player",
|
||||||
|
connectedState: 0,
|
||||||
|
url: window.location.hostname,
|
||||||
|
mode: "default",
|
||||||
|
// url: "localhost",
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
searchScroll(e) {
|
||||||
|
this.search.lastY = e.target.scrollTop;
|
||||||
|
},
|
||||||
|
musicKitAPI(method, id, params) {
|
||||||
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
action: "musickit-api",
|
||||||
|
method: method,
|
||||||
|
id: id,
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
resetPlayerUI() {
|
||||||
|
this.player.lowerPanelState = "controls";
|
||||||
|
},
|
||||||
|
musicAppVariant() {
|
||||||
|
if (navigator.userAgent.match(/Android/i)) {
|
||||||
|
return "Cider";
|
||||||
|
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
||||||
|
return "Cider";
|
||||||
|
} else {
|
||||||
|
if (navigator.userAgent.indexOf('Mac') > 0) {
|
||||||
|
return 'Music';
|
||||||
|
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
||||||
|
return 'Cider';
|
||||||
|
} else {
|
||||||
|
return 'Cider';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkOrientation() {
|
||||||
|
// check orientation of device
|
||||||
|
if (window.innerHeight > window.innerWidth) {
|
||||||
|
return 'portrait'
|
||||||
|
} else {
|
||||||
|
return 'landscape';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkPlatformMD() {
|
||||||
|
// check if platfom is desktop or mobile
|
||||||
|
if (navigator.userAgent.match(/Android/i)) {
|
||||||
|
return "mobile";
|
||||||
|
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
||||||
|
return "mobile";
|
||||||
|
} else {
|
||||||
|
if (navigator.userAgent.indexOf('Mac') > 0) {
|
||||||
|
return 'desktop';
|
||||||
|
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
||||||
|
return 'desktop';
|
||||||
|
} else {
|
||||||
|
return 'desktop';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkPlatform() {
|
||||||
|
if (navigator.userAgent.match(/Android/i)) {
|
||||||
|
return "android";
|
||||||
|
} else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
|
||||||
|
return "ios";
|
||||||
|
} else {
|
||||||
|
if (navigator.userAgent.indexOf('Mac') > 0) {
|
||||||
|
return 'mac';
|
||||||
|
} else if (navigator.userAgent.indexOf('Win') > 0) {
|
||||||
|
return 'win';
|
||||||
|
} else {
|
||||||
|
return 'linux';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
artworkPlaying() {
|
||||||
|
if (this.player.currentMediaItem.status) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
return ["paused"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setAutoplay(value) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"action": "set-autoplay",
|
||||||
|
"autoplay": value
|
||||||
|
}));
|
||||||
|
this.getCurrentMediaItem()
|
||||||
|
if (value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getQueue()
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
this.getQueue()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
seekTo(time, adjust = true) {
|
||||||
|
if (adjust) {
|
||||||
|
time = parseInt(time / 1000)
|
||||||
|
}
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "seek",
|
||||||
|
time: time
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
setVolume(volume) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "volume",
|
||||||
|
volume: volume
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
getQueue() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "get-queue"
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
play() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "play"
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
pause() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "pause"
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
next() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "next"
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
previous() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "previous"
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
searchArtist() {
|
||||||
|
this.search.query = this.player.currentMediaItem.artistName;
|
||||||
|
this.screen = "search";
|
||||||
|
this.searchQuery();
|
||||||
|
},
|
||||||
|
trackSelect(song) {
|
||||||
|
this.search.selected = song;
|
||||||
|
this.search.trackSelect = true
|
||||||
|
},
|
||||||
|
clearSelectedTrack() {
|
||||||
|
this.search.selected = {}
|
||||||
|
this.search.trackSelect = false
|
||||||
|
},
|
||||||
|
getArtworkColor(hex) {
|
||||||
|
return `#${hex}`
|
||||||
|
},
|
||||||
|
playMediaItemById(id, kind = "song") {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "play-mediaitem",
|
||||||
|
id: id,
|
||||||
|
kind: kind
|
||||||
|
}))
|
||||||
|
this.screen = "player";
|
||||||
|
},
|
||||||
|
playNext(type, id) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "play-next",
|
||||||
|
type: type,
|
||||||
|
id: id
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
playLater(type, id) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "play-later",
|
||||||
|
type: type,
|
||||||
|
id: id
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
searchQuery() {
|
||||||
|
if (this.search.query.length == 0) {
|
||||||
|
this.search.state = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.search.state = 1;
|
||||||
|
var actionType = "search"
|
||||||
|
if (this.search.searchType == "library") {
|
||||||
|
actionType = "library-search"
|
||||||
|
}
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"action": actionType,
|
||||||
|
"term": this.search.query,
|
||||||
|
"limit": 20
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
quickSearch() {
|
||||||
|
var search = prompt("Search for a song", "")
|
||||||
|
if (search == null || search == "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "quick-play",
|
||||||
|
term: search
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
parseTime(value) {
|
||||||
|
var minutes = Math.floor(value / 60000);
|
||||||
|
var seconds = ((value % 60000) / 1000).toFixed(0);
|
||||||
|
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
|
||||||
|
},
|
||||||
|
parseTimeDecimal(value) {
|
||||||
|
var minutes = Math.floor(value / 60000);
|
||||||
|
var seconds = ((value % 60000) / 1000).toFixed(0);
|
||||||
|
return minutes + "." + (seconds < 10 ? '0' : '') + seconds;
|
||||||
|
},
|
||||||
|
hmsToSecondsOnly(str) {
|
||||||
|
var p = str.split(':'),
|
||||||
|
s = 0,
|
||||||
|
m = 1;
|
||||||
|
|
||||||
|
while (p.length > 0) {
|
||||||
|
s += m * parseInt(p.pop(), 10);
|
||||||
|
m *= 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
getCurrentTime() {
|
||||||
|
return parseFloat(this.hmsToSecondsOnly(this.parseTime(this.player.currentMediaItem.durationInMillis - this.player.currentMediaItem.remainingTime)));
|
||||||
|
},
|
||||||
|
percentage(partial, full) {
|
||||||
|
return (100 * partial) / full
|
||||||
|
},
|
||||||
|
getLyricBGStyle(start, end) {
|
||||||
|
var currentTime = this.getCurrentTime();
|
||||||
|
var duration = this.player.currentMediaItem.durationInMillis
|
||||||
|
var start2 = this.hmsToSecondsOnly(start)
|
||||||
|
var end2 = this.hmsToSecondsOnly(end)
|
||||||
|
var currentProgress = ((100 * (currentTime)) / (end2))
|
||||||
|
// check if currenttime is between start and end
|
||||||
|
this.player.lyricsDebug.start = start2
|
||||||
|
this.player.lyricsDebug.end = end2
|
||||||
|
this.player.lyricsDebug.current = currentTime
|
||||||
|
if (currentTime >= start2 && currentTime <= end2) {
|
||||||
|
return {
|
||||||
|
"--bgSpeed": `${(end2 - start2)}s`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getLyricClass(start, end) {
|
||||||
|
var currentTime = this.getCurrentTime();
|
||||||
|
// check if currenttime is between start and end
|
||||||
|
if (currentTime >= start && currentTime <= end) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.querySelector(".lyric-line.active")) {
|
||||||
|
document.querySelector(".lyric-line.active").scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "center"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
return "active"
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAlbumArtUrl(size = 600) {
|
||||||
|
if (this.player.currentMediaItem.artwork) {
|
||||||
|
return `url("${this.player.currentMediaItem.artwork.url.replace('{w}', size).replace('{h}', size)}")`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAlbumArtUrlList(url, size = 64) {
|
||||||
|
return `url("${url.replace('{w}', size).replace('{h}', size)}")`;
|
||||||
|
},
|
||||||
|
searchTabClass(tab) {
|
||||||
|
if (tab == this.search.tab) {
|
||||||
|
return "active";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchTypeClass(type) {
|
||||||
|
if (type == this.search.searchType) {
|
||||||
|
return "active";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getQueuePositionClass(position) {
|
||||||
|
if (this.player.queue["_position"] == position) {
|
||||||
|
return ["playing", "passed"]
|
||||||
|
} else if (this.player.queue["_position"] > position) {
|
||||||
|
return ["passed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showSearch(reset = false) {
|
||||||
|
if (reset) {
|
||||||
|
this.search.lastPage = "search"
|
||||||
|
}
|
||||||
|
switch (this.search.lastPage) {
|
||||||
|
case "search":
|
||||||
|
this.screen = "search"
|
||||||
|
break;
|
||||||
|
case "album":
|
||||||
|
this.screen = "album-page"
|
||||||
|
break;
|
||||||
|
case "artist":
|
||||||
|
this.screen = "artist-page"
|
||||||
|
break;
|
||||||
|
case "playlist":
|
||||||
|
this.screen = "playlist-page"
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showArtistByName(name) {
|
||||||
|
this.musicKitAPI("search", name, { types: "artists" })
|
||||||
|
},
|
||||||
|
showAlbum(id) {
|
||||||
|
this.search.lastPage = "album"
|
||||||
|
this.screen = "album-page"
|
||||||
|
this.musicKitAPI("album", id, {})
|
||||||
|
},
|
||||||
|
showArtist(id) {
|
||||||
|
this.search.lastPage = "artist"
|
||||||
|
this.screen = "artist-page"
|
||||||
|
this.musicKitAPI("artist", id, { include: "songs,playlists,albums" })
|
||||||
|
},
|
||||||
|
showQueue() {
|
||||||
|
this.queue.temp = this.player["queue"]["_queueItems"]
|
||||||
|
this.screen = "queue"
|
||||||
|
this.getQueue()
|
||||||
|
},
|
||||||
|
queueMove(evt) {
|
||||||
|
console.log(evt)
|
||||||
|
console.log(`new: ${evt.moved.newIndex} old: ${evt.moved.oldIndex}`)
|
||||||
|
this.queue.temp.splice(evt.moved.newIndex, 0, this.queue.temp.splice(evt.moved.oldIndex, 1)[0])
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "queue-move",
|
||||||
|
from: evt.moved.oldIndex,
|
||||||
|
to: evt.moved.newIndex
|
||||||
|
}))
|
||||||
|
this.getQueue()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
repeat() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "repeat"
|
||||||
|
}))
|
||||||
|
this.getCurrentMediaItem()
|
||||||
|
},
|
||||||
|
shuffle() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "shuffle"
|
||||||
|
}))
|
||||||
|
this.getCurrentMediaItem()
|
||||||
|
},
|
||||||
|
setShuffle(val) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "set-shuffle",
|
||||||
|
shuffle: val
|
||||||
|
}))
|
||||||
|
this.getCurrentMediaItem()
|
||||||
|
},
|
||||||
|
getMediaPalette(data) {
|
||||||
|
var palette = {
|
||||||
|
'--bgColor': `#${data['artwork']['bgColor']}`,
|
||||||
|
'--textColor1': `#${data['artwork']['textColor1']}`,
|
||||||
|
'--textColor2': `#${data['artwork']['textColor2']}`,
|
||||||
|
'--textColor3': `#${data['artwork']['textColor3']}`,
|
||||||
|
'--textColor4': `#${data['artwork']['textColor4']}`
|
||||||
|
}
|
||||||
|
return palette
|
||||||
|
},
|
||||||
|
playAlbum(id, shuffle = false) {
|
||||||
|
if (shuffle) {
|
||||||
|
this.setShuffle(true)
|
||||||
|
} else {
|
||||||
|
this.setShuffle(false)
|
||||||
|
}
|
||||||
|
this.playMediaItemById(id, 'album');
|
||||||
|
},
|
||||||
|
getLyrics() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "get-lyrics",
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
showLyrics() {
|
||||||
|
this.getLyrics()
|
||||||
|
this.screen = "lyrics"
|
||||||
|
},
|
||||||
|
showLyricsInline() {
|
||||||
|
this.getLyrics()
|
||||||
|
this.player.lowerPanelState = "lyrics"
|
||||||
|
},
|
||||||
|
parseLyrics() {
|
||||||
|
var xml = this.stringToXml(this.player.lyricsMediaItem.ttml)
|
||||||
|
var json = xmlToJson(xml);
|
||||||
|
this.player.lyrics = json
|
||||||
|
},
|
||||||
|
stringToXml(st) {
|
||||||
|
// string to xml
|
||||||
|
var xml = (new DOMParser()).parseFromString(st, "text/xml");
|
||||||
|
return xml;
|
||||||
|
},
|
||||||
|
canShowSearchTab(tab) {
|
||||||
|
if (tab == this.search.tab || this.search.tab == "all") {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getCurrentMediaItem() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "get-currentmediaitem"
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
setStreamerOverlay() {
|
||||||
|
document.body.classList.add("streamer-overlay")
|
||||||
|
},
|
||||||
|
setMode(mode) {
|
||||||
|
switch (mode) {
|
||||||
|
default: this.screen = "player"
|
||||||
|
break;
|
||||||
|
case "miniplayer":
|
||||||
|
this.screen = "miniplayer"
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
connect() {
|
||||||
|
let self = this;
|
||||||
|
this.connectedState = 0;
|
||||||
|
if (this.url === "") {
|
||||||
|
this.url = prompt("Host IP", "localhost")
|
||||||
|
}
|
||||||
|
socket = new WebSocket(`ws://${this.url}:26369`);
|
||||||
|
socket.onopen = (e) => {
|
||||||
|
console.log(e);
|
||||||
|
console.log('connected');
|
||||||
|
app.connectedState = 1;
|
||||||
|
if (getParameterByName("mode")) {
|
||||||
|
self.setMode(getParameterByName("mode"))
|
||||||
|
} else {
|
||||||
|
self.setMode("default")
|
||||||
|
}
|
||||||
|
self.clearSelectedTrack()
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = (e) => {
|
||||||
|
console.log(e);
|
||||||
|
console.log('disconnected');
|
||||||
|
app.connectedState = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = (e) => {
|
||||||
|
console.log(e);
|
||||||
|
console.log('error');
|
||||||
|
app.connectedState = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onmessage = (e) => {
|
||||||
|
console.log(e.data)
|
||||||
|
const response = JSON.parse(e.data);
|
||||||
|
switch (response.type) {
|
||||||
|
default: console.log(response);
|
||||||
|
break;
|
||||||
|
case "musickitapi.search":
|
||||||
|
self.showArtist(response.data["artists"][0]["id"]);
|
||||||
|
break;
|
||||||
|
case "musickitapi.album":
|
||||||
|
if (self.screen == "album-page") {
|
||||||
|
self.albumPage.data = response.data
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "musickitapi.artist":
|
||||||
|
if (self.screen == "artist-page") {
|
||||||
|
self.artistPage.data = response.data
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "queue":
|
||||||
|
self.player.queue = response.data;
|
||||||
|
self.queue.temp = response.data["_queueItems"];
|
||||||
|
self.$forceUpdate()
|
||||||
|
break;
|
||||||
|
case "lyrics":
|
||||||
|
self.player.lyrics = response.data;
|
||||||
|
self.$forceUpdate()
|
||||||
|
break;
|
||||||
|
case "searchResultsLibrary":
|
||||||
|
self.search.results = response.data;
|
||||||
|
self.search.state = 2;
|
||||||
|
break;
|
||||||
|
case "searchResults":
|
||||||
|
self.search.results = response.data;
|
||||||
|
self.search.state = 2;
|
||||||
|
break;
|
||||||
|
case "playbackStateUpdate":
|
||||||
|
if (!self.player.userInteraction) {
|
||||||
|
self.updatePlaybackState(response.data)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// console.log(e.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updatePlaybackState(mediaitem) {
|
||||||
|
var lyricsDisplayed = this.screen == "lyrics" || this.player.lowerPanelState == "lyrics"
|
||||||
|
if (this.player.currentMediaItem["isrc"] != mediaitem["isrc"]) {
|
||||||
|
if (lyricsDisplayed) {
|
||||||
|
this.getLyrics()
|
||||||
|
}
|
||||||
|
if (this.screen == "queue") {
|
||||||
|
this.getQueue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.player.currentMediaItem = mediaitem
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function getParameterByName(name, url) {
|
||||||
|
if (!url) url = window.location.href;
|
||||||
|
name = name.replace(/[\[\]]/g, '\\$&');
|
||||||
|
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
||||||
|
results = regex.exec(url);
|
||||||
|
if (!results) return null;
|
||||||
|
if (!results[2]) return '';
|
||||||
|
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
function xmlToJson(xml) {
|
||||||
|
|
||||||
|
// Create the return object
|
||||||
|
var obj = {};
|
||||||
|
|
||||||
|
if (xml.nodeType == 1) { // element
|
||||||
|
// do attributes
|
||||||
|
if (xml.attributes.length > 0) {
|
||||||
|
obj["@attributes"] = {};
|
||||||
|
for (var j = 0; j < xml.attributes.length; j++) {
|
||||||
|
var attribute = xml.attributes.item(j);
|
||||||
|
obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (xml.nodeType == 3) { // text
|
||||||
|
obj = xml.nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do children
|
||||||
|
if (xml.hasChildNodes()) {
|
||||||
|
for (var i = 0; i < xml.childNodes.length; i++) {
|
||||||
|
var item = xml.childNodes.item(i);
|
||||||
|
var nodeName = item.nodeName;
|
||||||
|
if (typeof(obj[nodeName]) == "undefined") {
|
||||||
|
obj[nodeName] = xmlToJson(item);
|
||||||
|
} else {
|
||||||
|
if (typeof(obj[nodeName].push) == "undefined") {
|
||||||
|
var old = obj[nodeName];
|
||||||
|
obj[nodeName] = [];
|
||||||
|
obj[nodeName].push(old);
|
||||||
|
}
|
||||||
|
obj[nodeName].push(xmlToJson(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onresize = function() {
|
||||||
|
app.resetPlayerUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
app.connect()
|
42
src/web-remote/manifest.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#000000",
|
||||||
|
"display": "standalone",
|
||||||
|
"scope": "/",
|
||||||
|
"start_url": "/",
|
||||||
|
"name": "Cider Remote",
|
||||||
|
"short_name": "Cider Remote",
|
||||||
|
"description": "Cider Remote",
|
||||||
|
"developer": {
|
||||||
|
"name": "Cider Collective",
|
||||||
|
"url": "https://cider.sh?utm-source=manifest"
|
||||||
|
},
|
||||||
|
"homepage_url": "https://cider.sh?utm-source=manifest",
|
||||||
|
"icons": [{
|
||||||
|
"src": "/icon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-256x256.png",
|
||||||
|
"sizes": "256x256",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-384x384.png",
|
||||||
|
"sizes": "384x384",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"protocol_handlers": [{
|
||||||
|
"protocol": "ext+cider",
|
||||||
|
"name": "Cider",
|
||||||
|
"uriTemplate": "/?url=%s"
|
||||||
|
}]
|
||||||
|
|
||||||
|
}
|
2
src/web-remote/sortable.min.js
vendored
Normal file
1023
src/web-remote/style.css
Normal file
6
src/web-remote/vue.js
Normal file
2
src/web-remote/vuedraggable.umd.min.js
vendored
Normal file
18
tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "commonjs",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strict": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./build",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"*": ["node_modules/*"]
|
||||||
|
},
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|